<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Ehlxr&#39;s Blog</title>
  
  <subtitle>「闲言碎语」</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://ehlxr.me/"/>
  <updated>2025-03-07T11:18:19.000Z</updated>
  <id>https://ehlxr.me/</id>
  
  <author>
    <name>!!@_@ ᵛᵉ</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Go 语言是面向对象的编程语言吗？</title>
    <link href="https://ehlxr.me/2025/03/07/go-oop/"/>
    <id>https://ehlxr.me/2025/03/07/go-oop/</id>
    <published>2025-03-07T08:35:44.000Z</published>
    <updated>2025-03-07T11:18:19.000Z</updated>
    
    <content type="html"><![CDATA[<p>Go 语言不能简单地归类为面向对象的编程语言，它更准确的说法是：Go 语言是一种支持部分面向对象特性的多范式编程语言。</p><h3 id="支持的面向对象特性："><a href="#支持的面向对象特性：" class="headerlink" title="支持的面向对象特性："></a>支持的面向对象特性：</h3><ol><li>封装</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> person <span class="keyword">struct</span> &#123;</span><br><span class="line">    name <span class="keyword">string</span>  <span class="comment">// 小写私有</span></span><br><span class="line">    Age  <span class="keyword">int</span>     <span class="comment">// 大写公开</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法封装</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p *person)</span> <span class="title">GetName</span><span class="params">()</span> <span class="title">string</span></span> &#123;</span><br><span class="line">    <span class="keyword">return</span> p.name</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><a id="more"></a><ol start="2"><li>多态（通过接口实现）</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Animal <span class="keyword">interface</span> &#123;</span><br><span class="line">    Speak() <span class="keyword">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Dog <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"><span class="keyword">type</span> Cat <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(d *Dog)</span> <span class="title">Speak</span><span class="params">()</span> <span class="title">string</span></span> &#123; <span class="keyword">return</span> <span class="string">"Woof!"</span> &#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *Cat)</span> <span class="title">Speak</span><span class="params">()</span> <span class="title">string</span></span> &#123; <span class="keyword">return</span> <span class="string">"Meow!"</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 多态使用</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">MakeSound</span><span class="params">(a Animal)</span> <span class="title">string</span></span> &#123;</span><br><span class="line">    <span class="keyword">return</span> a.Speak()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>组合（代替继承）</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Engine <span class="keyword">struct</span> &#123;</span><br><span class="line">    Power <span class="keyword">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Car <span class="keyword">struct</span> &#123;</span><br><span class="line">    Engine  <span class="comment">// 通过组合获得Engine的能力</span></span><br><span class="line">    Brand <span class="keyword">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="不支持的面向对象特性："><a href="#不支持的面向对象特性：" class="headerlink" title="不支持的面向对象特性："></a>不支持的面向对象特性：</h3><ol><li>继承<ul><li>Go 语言不支持类和继承</li><li>使用组合代替继承</li><li>没有 <code>extends</code> 或 <code>class</code> 关键字</li></ul></li><li>构造函数<ul><li>没有专门的构造函数语法</li><li>通常使用工厂函数模式</li></ul></li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 显式的错误处理</span></span><br><span class="line">result, err := SomeFunction()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">    <span class="comment">// 处理错误</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>方法重载<ul><li>不支持同名不同参数的方法</li><li>每个方法名必须唯一</li></ul></li><li>泛型（Go 语言 1.18 之前）<ul><li>早期版本完全不支持泛型</li><li>Go 语言 1.18 后添加了泛型支持</li></ul></li></ol><h3 id="Go-语言的设计理念："><a href="#Go-语言的设计理念：" class="headerlink" title="Go 语言的设计理念："></a>Go 语言的设计理念：</h3><ol><li>简单性<ul><li>语言特性较少</li><li>避免特性重叠</li><li>语法简洁明了</li></ul></li><li>实用性<ul><li>注重工程实践</li><li>关注并发编程</li><li>快速编译</li></ul></li><li>组合优于继承</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 优先使用组合</span></span><br><span class="line"><span class="keyword">type</span> Writer <span class="keyword">struct</span> &#123;</span><br><span class="line">    Buffer *bytes.Buffer</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 而不是继承</span></span><br></pre></td></tr></table></figure><ol start="4"><li>显式优于隐式</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用组合和接口</span></span><br><span class="line"><span class="keyword">type</span> Service <span class="keyword">interface</span> &#123;</span><br><span class="line">    Process() error</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> BaseService <span class="keyword">struct</span> &#123;</span><br><span class="line">    name <span class="keyword">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> SpecificService <span class="keyword">struct</span> &#123;</span><br><span class="line">    BaseService</span><br><span class="line">    <span class="comment">// 特定字段</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现接口</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *SpecificService)</span> <span class="title">Process</span><span class="params">()</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="comment">// 实现逻辑</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="总结："><a href="#总结：" class="headerlink" title="总结："></a>总结：</h3><ol><li>Go 语言不是传统意义上的面向对象语言<ul><li>没有类的概念</li><li>不支持继承</li><li>没有对象实例的概念</li></ul></li><li>Go 语言是多范式语言<ul><li>支持过程式编程<ul><li>支持部分面向对象特性</li></ul></li><li>支持函数式编程特性</li></ul></li><li>Go 语言的设计特点<ul><li>简单性和实用性</li><li>组合优于继承</li><li>接口隐式实现</li><li>强调代码的清晰和可维护性</li></ul></li><li>实际应用建议</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用组合和接口</span></span><br><span class="line"><span class="keyword">type</span> Service <span class="keyword">interface</span> &#123;</span><br><span class="line">    Process() error</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> BaseService <span class="keyword">struct</span> &#123;</span><br><span class="line">    name <span class="keyword">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> SpecificService <span class="keyword">struct</span> &#123;</span><br><span class="line">    BaseService</span><br><span class="line">    <span class="comment">// 特定字段</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现接口</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *SpecificService)</span> <span class="title">Process</span><span class="params">()</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="comment">// 实现逻辑</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以，与其说 Go 语言是不是面向对象语言，不如说 Go 语言提供了一种独特的方式来处理程序设计中的问题，它汲取了面向对象编程的优点，但避免了传统 OOP 中的一些复杂性和限制。这种设计使得 Go 语言特别适合构建大型、可维护的系统，尤其是在云计算和网络服务领域</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Go 语言不能简单地归类为面向对象的编程语言，它更准确的说法是：Go 语言是一种支持部分面向对象特性的多范式编程语言。&lt;/p&gt;
&lt;h3 id=&quot;支持的面向对象特性：&quot;&gt;&lt;a href=&quot;#支持的面向对象特性：&quot; class=&quot;headerlink&quot; title=&quot;支持的面向对象特性：&quot;&gt;&lt;/a&gt;支持的面向对象特性：&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;封装&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight go&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;type&lt;/span&gt; person &lt;span class=&quot;keyword&quot;&gt;struct&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    name &lt;span class=&quot;keyword&quot;&gt;string&lt;/span&gt;  &lt;span class=&quot;comment&quot;&gt;// 小写私有&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    Age  &lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt;     &lt;span class=&quot;comment&quot;&gt;// 大写公开&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// 方法封装&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;(p *person)&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;GetName&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;string&lt;/span&gt;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; p.name&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
      <category term="Golang" scheme="https://ehlxr.me/categories/Golang/"/>
    
    
      <category term="Golang" scheme="https://ehlxr.me/tags/Golang/"/>
    
      <category term="Go" scheme="https://ehlxr.me/tags/Go/"/>
    
  </entry>
  
  <entry>
    <title>Python 虚拟环境对比</title>
    <link href="https://ehlxr.me/2025/03/07/python_virtual_env/"/>
    <id>https://ehlxr.me/2025/03/07/python_virtual_env/</id>
    <published>2025-03-07T08:35:44.000Z</published>
    <updated>2025-03-07T09:02:44.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Python 开发中，虚拟环境是非常重要的工具，用于隔离不同项目之间的依赖关系，避免版本冲突。<code>venv</code> 和 <code>Conda</code> 是两种常见的虚拟环境管理工具，它们各有优缺点，适用于不同的场景。以下是对两者的详细比较以及如何选择的建议。</p><h2 id="1-venv"><a href="#1-venv" class="headerlink" title="1. venv"></a>1. venv</h2><p><code>venv</code> 是 Python 标准库自带的虚拟环境管理工具，从 Python 3.3 开始内置支持。</p><h3 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h3><ul><li><strong>轻量级</strong>：<code>venv</code> 是一个简单的工具，专注于创建和管理 Python 虚拟环境。</li><li><strong>无需额外安装</strong>：因为是 Python 自带的功能，所以不需要额外安装其他软件。</li><li><strong>纯 Python 环境</strong>：<code>venv</code> 创建的虚拟环境仅包含 Python 解释器及其依赖包，适合纯 Python 项目。</li><li><strong>依赖管理工具</strong>：通常与 <code>pip</code> 配合使用来管理包。<a id="more"></a></li></ul><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul><li>使用简单，开箱即用。</li><li>对于纯 Python 项目来说足够高效。</li><li>不需要额外学习成本，适合初学者。</li></ul><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ul><li>不支持非 Python 的依赖（如 C 库等）。</li><li>环境隔离能力有限，无法管理多语言或复杂的科学计算环境。</li><li>在 Windows 上配置可能稍显复杂。</li></ul><h2 id="2-Conda"><a href="#2-Conda" class="headerlink" title="2. Conda"></a>2. Conda</h2><p><code>Conda</code> 是一个跨平台的包管理和环境管理工具，最初由 Anaconda 团队开发，广泛应用于数据科学和机器学习领域。</p><h3 id="特点-1"><a href="#特点-1" class="headerlink" title="特点"></a>特点</h3><ul><li><strong>强大的包管理功能</strong>：不仅支持 Python 包，还支持非 Python 的依赖（如 C、R 等）。</li><li><strong>环境隔离</strong>：可以创建完全独立的环境，包括 Python 版本和其他系统依赖。</li><li><strong>跨平台支持</strong>：对 Windows、macOS 和 Linux 都有良好的支持。</li><li><strong>生态系统丰富</strong>：Anaconda 提供了大量预编译的科学计算和数据分析包。</li></ul><h3 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h3><ul><li>支持多语言和复杂依赖的项目。</li><li>对科学计算和机器学习友好，许多常用库（如 NumPy、SciPy、TensorFlow 等）已经经过优化并预编译。</li><li>提供图形化界面（Anaconda Navigator），适合不熟悉命令行操作的用户。</li><li>更好的性能优化，尤其是在处理大型数据集时。</li></ul><h3 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h3><ul><li>安装体积较大，尤其是完整版的 Anaconda。</li><li>对于纯 Python 项目来说可能显得过于复杂。</li><li>某些情况下，<code>conda</code> 和 <code>pip</code> 的兼容性问题可能导致包管理混乱。</li></ul><h3 id="如何选择？"><a href="#如何选择？" class="headerlink" title="如何选择？"></a>如何选择？</h3><h3 id="选择-venv-的场景"><a href="#选择-venv-的场景" class="headerlink" title="选择 venv 的场景"></a>选择 venv 的场景</h3><ol><li><strong>纯 Python 项目</strong>：如果项目只涉及 Python 代码，且依赖较为简单，<code>venv</code> 是更好的选择。</li><li><strong>轻量级需求</strong>：如果你希望快速搭建环境，且不想引入额外的工具或依赖，<code>venv</code> 是更轻便的选择。</li><li><strong>熟悉命令行操作</strong>：对于习惯使用命令行工具的开发者，<code>venv</code> 提供了简单直接的接口。</li></ol><h3 id="选择-Conda-的场景"><a href="#选择-Conda-的场景" class="headerlink" title="选择 Conda 的场景"></a>选择 Conda 的场景</h3><ol><li><strong>数据科学和机器学习项目</strong>：如果你从事数据科学、机器学习或深度学习相关的工作，<code>Conda</code> 的生态系统会显著提升效率。</li><li><strong>复杂依赖管理</strong>：当项目需要安装非 Python 的依赖（如 C 库、R 包等），或者需要特定版本的系统库时，<code>Conda</code> 更加适合。</li><li><strong>跨平台开发</strong>：如果你需要在不同操作系统之间切换，<code>Conda</code> 提供了更好的一致性和兼容性。</li><li><strong>新手友好</strong>：对于不熟悉命令行操作的用户，<code>Conda</code> 提供了图形化界面（Anaconda Navigator），降低了学习门槛。</li></ol><h3 id="总结对比表"><a href="#总结对比表" class="headerlink" title="总结对比表"></a>总结对比表</h3><table><thead><tr><th align="left">特性</th><th align="left"><code>venv</code></th><th align="left"><code>Conda</code></th></tr></thead><tbody><tr><td align="left"><strong>是否内置</strong></td><td align="left">是</td><td align="left">否</td></tr><tr><td align="left"><strong>安装大小</strong></td><td align="left">轻量级</td><td align="left">较大</td></tr><tr><td align="left"><strong>支持的语言</strong></td><td align="left">仅 Python</td><td align="left">多语言（Python、C、R 等）</td></tr><tr><td align="left"><strong>依赖管理范围</strong></td><td align="left">仅 Python 包</td><td align="left">Python 包 + 系统依赖</td></tr><tr><td align="left"><strong>适用场景</strong></td><td align="left">纯 Python 项目</td><td align="left">数据科学、机器学习、复杂依赖项目</td></tr><tr><td align="left"><strong>学习曲线</strong></td><td align="left">低</td><td align="left">中</td></tr></tbody></table><table><thead><tr><th align="left"><strong>方面</strong></th><th align="left"><strong>venv</strong></th><th align="left"><strong>Conda</strong></th></tr></thead><tbody><tr><td align="left"><strong>适用范围</strong></td><td align="left">仅限 Python</td><td align="left">支持多种语言（如 Python、R、Ruby）</td></tr><tr><td align="left"><strong>依赖管理</strong></td><td align="left">依赖 pip，适合 Python 包，但非 Python 依来可能需额外配置</td><td align="left">自带包管理器，高效处理 Python 和非 Python 包（如 C/C++ 库）</td></tr><tr><td align="left"><strong>安装方式</strong></td><td align="left">内置于 Python 3.3+，无需额外安装</td><td align="left">需要安装 Anaconda 或 Miniconda</td></tr><tr><td align="left"><strong>环境管理</strong></td><td align="left">环境存储在项目文件夹（如 .venv），本地化管理</td><td align="left">环境集中存储（如 Anaconda3/envs），可跨项目共享</td></tr><tr><td align="left"><strong>性能与速度</strong></td><td align="left">轻量，创建和使用较快，适合简单项目</td><td align="left">创建环境可能较慢，但工具如 Mamba 和 Pixi 可优化性能</td></tr><tr><td align="left"><strong>社区与支持</strong></td><td align="left">Python 社区广泛支持，文档丰富（如 <a href="https://docs.python.org/3/library/venv.html" target="_blank" rel="noopener">Python 官方文档</a>）</td><td align="left">数据科学社区强力支持，文档详尽（如 <a href="https://docs.conda.io/en/latest/" target="_blank" rel="noopener">Conda 官方文档</a>）</td></tr></tbody></table><h3 id="实际建议"><a href="#实际建议" class="headerlink" title="实际建议"></a>实际建议</h3><ul><li>如果你是 Python 初学者，或者项目比较简单，推荐使用 <code>venv</code>。</li><li>如果你从事数据科学、机器学习或其他需要复杂依赖管理的工作，推荐使用 <code>Conda</code>。</li><li>如果不确定，可以先尝试 <code>venv</code>，遇到复杂需求时再迁移到 <code>Conda</code>。尤其是在云计算和网络服务领域</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在 Python 开发中，虚拟环境是非常重要的工具，用于隔离不同项目之间的依赖关系，避免版本冲突。&lt;code&gt;venv&lt;/code&gt; 和 &lt;code&gt;Conda&lt;/code&gt; 是两种常见的虚拟环境管理工具，它们各有优缺点，适用于不同的场景。以下是对两者的详细比较以及如何选择的建议。&lt;/p&gt;
&lt;h2 id=&quot;1-venv&quot;&gt;&lt;a href=&quot;#1-venv&quot; class=&quot;headerlink&quot; title=&quot;1. venv&quot;&gt;&lt;/a&gt;1. venv&lt;/h2&gt;&lt;p&gt;&lt;code&gt;venv&lt;/code&gt; 是 Python 标准库自带的虚拟环境管理工具，从 Python 3.3 开始内置支持。&lt;/p&gt;
&lt;h3 id=&quot;特点&quot;&gt;&lt;a href=&quot;#特点&quot; class=&quot;headerlink&quot; title=&quot;特点&quot;&gt;&lt;/a&gt;特点&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;轻量级&lt;/strong&gt;：&lt;code&gt;venv&lt;/code&gt; 是一个简单的工具，专注于创建和管理 Python 虚拟环境。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无需额外安装&lt;/strong&gt;：因为是 Python 自带的功能，所以不需要额外安装其他软件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;纯 Python 环境&lt;/strong&gt;：&lt;code&gt;venv&lt;/code&gt; 创建的虚拟环境仅包含 Python 解释器及其依赖包，适合纯 Python 项目。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;依赖管理工具&lt;/strong&gt;：通常与 &lt;code&gt;pip&lt;/code&gt; 配合使用来管理包。
    
    </summary>
    
      <category term="Python" scheme="https://ehlxr.me/categories/Python/"/>
    
    
      <category term="Python" scheme="https://ehlxr.me/tags/Python/"/>
    
      <category term="venv" scheme="https://ehlxr.me/tags/venv/"/>
    
      <category term="Conda" scheme="https://ehlxr.me/tags/Conda/"/>
    
  </entry>
  
  <entry>
    <title>Docker 搭建 Keepalived 实现 Nginx 双机热备</title>
    <link href="https://ehlxr.me/2022/07/10/docker-keepalived-nginx-ha/"/>
    <id>https://ehlxr.me/2022/07/10/docker-keepalived-nginx-ha/</id>
    <published>2022-07-10T13:12:14.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/keepalived-nginx-ha.jpg" alt="keepalived Nginx HA"></p><p>docker 搭建 keepalived 实现 nginx 双机热备</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">❯ docker run --privileged -d --name node1 debian:11 top -b</span><br><span class="line">❯ docker run --privileged -d --name node2 debian:11 top -b</span><br></pre></td></tr></table></figure><p><code>–-privileged</code> 是指以特权模式启动容器，否则 keepalived 无法成功生成虚拟 IP</p><p>分别进入 node1、node2 容器节点（<code>docker exec -it node1 /bin/bash</code> 和 <code>docker exec -it node2 /bin/bash</code> ）</p><p>安装以下软件 <code>apt update &amp;&amp; apt install curl vim iproute2 inetutils-ping psmisc net-tools systemctl nginx keepalived -y</code></p><a id="more"></a><p>创建以下两个文件：</p><blockquote><p><code>vi /etc/keepalived/chk_nginx.sh</code></p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash">!/bin/bash</span></span><br><span class="line">echo $(date) "start check nginx..." &gt;&gt; /etc/keepalived/check_nginx.log</span><br><span class="line"></span><br><span class="line">counter=$(systemctl status nginx | grep running | wc -l)</span><br><span class="line">if [ "$&#123;counter&#125;" = "0"  ]; then</span><br><span class="line">   echo $(date)  "nginx is not running, restarting..." &gt;&gt; /etc/keepalived/check_nginx.log</span><br><span class="line">   systemctl start nginx</span><br><span class="line">   sleep 2</span><br><span class="line">   counter=$(systemctl status nginx | grep running | wc -l)</span><br><span class="line">   if [ "$&#123;counter&#125;" =  "0" ]; then</span><br><span class="line">      echo $(date) "nginx is down, kill all keepalived..." &gt;&gt; /etc/keepalived/check_nginx.log</span><br><span class="line">      killall keepalived</span><br><span class="line">   fi</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><p>脚本作用是查看是否存在 nginx 进程，不存在就重启 nginx，然后再查看一次，还不存在就杀掉 keepalived 进程（这样备既就会自动上线）<br>脚本权限需设置为 755（<code>chmod 755 /etc/keepalived/chk_nginx.sh</code>），否则 keepalived 会认为它不安全<br>注意首行一定是 <code>#!/bin/bash</code>，而不是 <code>#/bin/bash</code>，否则脚本不会被 keepalived 执行</p><p>node1 容器节点为虚拟 IP <code>172.17.0.201</code> 的主节点、虚拟IP <code>172.17.0.202</code> 的备用节点， keepalived 配置文件如下：</p><blockquote><p><code>vi /etc/keepalived/keepalived.conf</code></p></blockquote><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">! Configuration File for keepalived</span></span><br><span class="line"></span><br><span class="line"><span class="attr">global_defs</span> <span class="string">&#123;</span></span><br><span class="line">    <span class="attr">router_id</span> <span class="string">localhost</span></span><br><span class="line">    <span class="attr">script_user</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">enable_script_security</span></span><br><span class="line"><span class="attr">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">vrrp_script</span> <span class="string">chk_http_port &#123;</span></span><br><span class="line">    <span class="attr">script</span> <span class="string">/etc/keepalived/chk_nginx.sh</span></span><br><span class="line">    <span class="attr">interval</span> <span class="string">10 # 间隔几秒执行脚本（注意最好大于脚本的执行时间）</span></span><br><span class="line">    <span class="attr">weight</span> <span class="string">-20</span></span><br><span class="line"><span class="attr">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">vrrp_instance</span> <span class="string">VI_1 &#123;</span></span><br><span class="line">    <span class="attr">state</span> <span class="string">MASTER # 1、备机改为：BACKUP</span></span><br><span class="line">    <span class="attr">interface</span> <span class="string">eth0 # 2、替换为实际网卡名称，可使用 ifconfig 或 ip addr 查看</span></span><br><span class="line">    <span class="attr">virtual_router_id</span> <span class="string">51</span></span><br><span class="line">    <span class="attr">priority</span> <span class="string">100 # 3、备机优先级设置小一点，例如：90</span></span><br><span class="line">    <span class="attr">advert_int</span> <span class="string">1</span></span><br><span class="line">    <span class="attr">authentication</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">auth_type</span> <span class="string">PASS</span></span><br><span class="line">        <span class="attr">auth_pass</span> <span class="string">1111</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line">    <span class="attr">track_script</span> <span class="string">&#123;</span></span><br><span class="line">       <span class="attr">chk_http_port</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line">    <span class="attr">virtual_ipaddress</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="meta">172.17.0.201</span> <span class="string"># 虚拟 IP（和 node1、node2 容器节点在同一网段的任意闲置 IP 即可）</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line"><span class="attr">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">vrrp_instance</span> <span class="string">VI_2 &#123;</span></span><br><span class="line">    <span class="attr">state</span> <span class="string">BACKUP</span></span><br><span class="line">    <span class="attr">interface</span> <span class="string">eth0 #</span></span><br><span class="line">    <span class="attr">virtual_router_id</span> <span class="string">52</span></span><br><span class="line">    <span class="attr">priority</span> <span class="string">90</span></span><br><span class="line">    <span class="attr">advert_int</span> <span class="string">1</span></span><br><span class="line">    <span class="attr">authentication</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">auth_type</span> <span class="string">PASS</span></span><br><span class="line">        <span class="attr">auth_pass</span> <span class="string">1111</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line">    <span class="attr">track_script</span> <span class="string">&#123;</span></span><br><span class="line">       <span class="attr">chk_http_port</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line">    <span class="attr">virtual_ipaddress</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">172.17.0.202</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line"><span class="attr">&#125;</span></span><br></pre></td></tr></table></figure><p>node2 容器节点为虚拟 IP <code>172.17.0.202</code> 的主节点、虚拟IP <code>172.17.0.201</code> 的备用节点， keepalived 配置文件如下：</p><blockquote><p><code>vi /etc/keepalived/keepalived.conf</code></p></blockquote><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">! Configuration File for keepalived</span></span><br><span class="line"></span><br><span class="line"><span class="attr">global_defs</span> <span class="string">&#123;</span></span><br><span class="line">    <span class="attr">router_id</span> <span class="string">localhost</span></span><br><span class="line">    <span class="attr">script_user</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">enable_script_security</span></span><br><span class="line"><span class="attr">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">vrrp_script</span> <span class="string">chk_http_port &#123;</span></span><br><span class="line">    <span class="attr">script</span> <span class="string">/etc/keepalived/chk_nginx.sh</span></span><br><span class="line">    <span class="attr">interval</span> <span class="string">10</span></span><br><span class="line">    <span class="attr">weight</span> <span class="string">-20</span></span><br><span class="line"><span class="attr">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">vrrp_instance</span> <span class="string">VI_1 &#123;</span></span><br><span class="line">    <span class="attr">state</span> <span class="string">BACKUP</span></span><br><span class="line">    <span class="attr">interface</span> <span class="string">eth0</span></span><br><span class="line">    <span class="attr">virtual_router_id</span> <span class="string">51</span></span><br><span class="line">    <span class="attr">priority</span> <span class="string">90</span></span><br><span class="line">    <span class="attr">advert_int</span> <span class="string">1</span></span><br><span class="line">    <span class="attr">authentication</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">auth_type</span> <span class="string">PASS</span></span><br><span class="line">        <span class="attr">auth_pass</span> <span class="string">1111</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line">    <span class="attr">track_script</span> <span class="string">&#123;</span></span><br><span class="line">       <span class="attr">chk_http_port</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line">    <span class="attr">virtual_ipaddress</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">172.17.0.201</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line"><span class="attr">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">vrrp_instance</span> <span class="string">VI_2 &#123;</span></span><br><span class="line">    <span class="attr">state</span> <span class="string">MASTER</span></span><br><span class="line">    <span class="attr">interface</span> <span class="string">eth0</span></span><br><span class="line">    <span class="attr">virtual_router_id</span> <span class="string">52</span></span><br><span class="line">    <span class="attr">priority</span> <span class="string">100</span></span><br><span class="line">    <span class="attr">advert_int</span> <span class="string">1</span></span><br><span class="line">    <span class="attr">authentication</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">auth_type</span> <span class="string">PASS</span></span><br><span class="line">        <span class="attr">auth_pass</span> <span class="string">1111</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line">    <span class="attr">track_script</span> <span class="string">&#123;</span></span><br><span class="line">       <span class="attr">chk_http_port</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line">    <span class="attr">virtual_ipaddress</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">172.17.0.202</span></span><br><span class="line">    <span class="attr">&#125;</span></span><br><span class="line"><span class="attr">&#125;</span></span><br></pre></td></tr></table></figure><p>node1 和 node2 容器节点互为主备， keepalived 配置文件除过注释标注的 3 处不同外，其余配置项保持一致即可<br>可使用 <code>keepalived -t</code> 检测配置文件是否有误</p><p>为了验证主备切换效果，可分别在主备 nginx 默认界面添加不同信息加以区分（<code>vi /var/www/html/index.nginx-debian.html</code>）</p><p>1、分别启动 node1 和 node2 容器节点的 keepalived（<code>systemctl start keepalived</code>）</p><p>2、在 node1 节点容器使用 ifconfig 或 ip addr 命令可看到绑定的虚拟 IP <code>172.17.0.201</code>，此时通过访问虚拟 IP：<code>curl http://172.17.0.201</code> 展示的是 node1 节点的 nginx 信息</p><p>3、在 node2 节点容器使用 ifconfig 或 ip addr 命令可看到绑定的虚拟 IP <code>172.17.0.202</code>，此时通过访问虚拟 IP：<code>curl http://172.17.0.202</code> 展示的是 node2 节点的 nginx 信息</p><p>4、故意改错 node1 上的 nginx 配置文件让其无法启动，并且停止 nginx（<code>systemctl stop nginx</code>）（模拟宕机情况），node1 节点的 keepalived 检测到 nginx 进程不存在，然后尝试启动 nginx，发现启动失败，所以会杀掉 keepalived 进程，释放虚拟 IP <code>172.17.0.201</code></p><p>5、node2 节点 keepalived 感知到 node1 节点下线后会绑定虚拟 IP <code>172.17.0.201</code> 接管请求，完成主备切换。此时在 node2 节点容器使用 ifconfig 或 ip addr 命令可看到绑定的虚拟 IP <code>172.17.0.201</code> 和 <code>172.17.0.202</code>，此时通过访问 <code>curl http://172.17.0.201</code> 和 <code>curl http://172.17.0.201</code> 展示的都是 node2 节点的 nginx 信息</p><p>6、当恢复 node1 节点 nginx 配置文件并重新加载，然后启动 keepalived，在 node1 容器节点使用 ifconfig 或 ip addr 命令可再次看到绑定的虚拟 IP <code>172.17.0.201</code>，此时通过访问 <code>curl http://172.17.0.201</code> 展示的又是 node1 节点的 nginx 信息，访问 <code>curl http://172.17.0.201</code> 展示的是 node2 节点的 nginx 信息</p><hr>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;img src=&quot;https://cdn.jsdelivr.net/gh/0vo/oss/images/keepalived-nginx-ha.jpg&quot; alt=&quot;keepalived Nginx HA&quot;&gt;&lt;/p&gt;
&lt;p&gt;docker 搭建 keepalived 实现 nginx 双机热备&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;❯ docker run --privileged -d --name node1 debian:11 top -b&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;❯ docker run --privileged -d --name node2 debian:11 top -b&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code&gt;–-privileged&lt;/code&gt; 是指以特权模式启动容器，否则 keepalived 无法成功生成虚拟 IP&lt;/p&gt;
&lt;p&gt;分别进入 node1、node2 容器节点（&lt;code&gt;docker exec -it node1 /bin/bash&lt;/code&gt; 和 &lt;code&gt;docker exec -it node2 /bin/bash&lt;/code&gt; ）&lt;/p&gt;
&lt;p&gt;安装以下软件 &lt;code&gt;apt update &amp;amp;&amp;amp; apt install curl vim iproute2 inetutils-ping psmisc net-tools systemctl nginx keepalived -y&lt;/code&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="架构" scheme="https://ehlxr.me/categories/%E6%9E%B6%E6%9E%84/"/>
    
    
      <category term="Nginx" scheme="https://ehlxr.me/tags/Nginx/"/>
    
      <category term="Docker" scheme="https://ehlxr.me/tags/Docker/"/>
    
      <category term="Keepalived" scheme="https://ehlxr.me/tags/Keepalived/"/>
    
      <category term="HA" scheme="https://ehlxr.me/tags/HA/"/>
    
  </entry>
  
  <entry>
    <title>快速排序理解</title>
    <link href="https://ehlxr.me/2022/03/19/quick-sort/"/>
    <id>https://ehlxr.me/2022/03/19/quick-sort/</id>
    <published>2022-03-19T17:20:32.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>快速排序算法核心思想，取待排序序列中的某个元素作为分区点，大于分区点的元素挪到分区点右边（从小到大排序），小于分区点的元素挪到分区点左边。然后分区点左右两边的子序列循环以上操作，直至子序列长度为 <code>1</code>。</p><p><strong>左右指针法实现思路</strong></p><p>1、首先定义分区点（pivot）<code>p</code>，<code>p</code> 一般为数组 <code>a</code> 的第一个元素或最后一个元素<br>2、然后定义左（<code>l</code>）、右（<code>r</code>）两个指针分别指向数组的第一个元素（<code>a[0]</code>）和最后一个元素 (<code>a[a.length - 1]</code>)<br>3、如果 <code>a[l] &gt; a[p]</code>，<code>l、p</code> 下标元素互换，<code>l</code> 前进 <code>1</code> 位<br>4、如果 <code>a[r] &lt; a[p]</code>，<code>r、p</code> 下标元素互换，<code>r</code> 后退 <code>1</code> 位<br>5、如果 <code>l &gt;= r</code>，排序结束</p><a id="more"></a><p><strong>快速排序左右指针法图解过程</strong></p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/quick-sort.jpg" alt="快速排序左右指针法图解过程"></p><p><strong>代码实现</strong></p><p>以递归方式实现编码，首先找出分析出递归条件：</p><p>1、递归方程：<code>quickSort(a[l..r]) = quickSort(a[l..p-1]) + quickSort(a[p+1..r])</code><br>2、递归退出条件：<code>l &gt;= r</code></p><blockquote><p>注意点：如果选择分区点 <code>p = l</code>，必须<strong>先从右边找到小于 <code>a[p]</code> 的第一个元素</strong>开始</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 快速排序</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> a 待排序数组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> l 第一个元素下标</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> r 最后一个元素下标</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sort</span><span class="params">(<span class="keyword">int</span>[] a, <span class="keyword">int</span> l, <span class="keyword">int</span> r)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (a == <span class="keyword">null</span> || l &gt;= r) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">int</span> i = l, j = r;</span><br><span class="line">  <span class="keyword">int</span> p = l; <span class="comment">// 选择最左边的元素为 pivot</span></span><br><span class="line">  <span class="keyword">while</span> (l &lt; r) &#123;</span><br><span class="line">    <span class="comment">// 如果选择 p = l 必须先从右边找到小于 a[p] 的第一个元素</span></span><br><span class="line">    <span class="keyword">while</span> (l &lt; r &amp;&amp; a[r] &gt;= a[p]) &#123;</span><br><span class="line">      r--;</span><br><span class="line">    &#125;</span><br><span class="line">    swap(a, r, p);</span><br><span class="line">    p = r;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从左边找到大于 a[p] 的第一个元素</span></span><br><span class="line">    <span class="keyword">while</span> (l &lt; r &amp;&amp; a[l] &lt;= a[p]) &#123;</span><br><span class="line">      l++;</span><br><span class="line">    &#125;</span><br><span class="line">    swap(a, l, p);</span><br><span class="line">    p = l;</span><br><span class="line">    System.out.println(Arrays.toString(a));</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  sort(a, i, p - <span class="number">1</span>);</span><br><span class="line">  sort(a, p + <span class="number">1</span>, j);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span>[] a, <span class="keyword">int</span> i, <span class="keyword">int</span> j)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> tmp = a[i];</span><br><span class="line">    a[i] = a[j];</span><br><span class="line">    a[j] = tmp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;快速排序算法核心思想，取待排序序列中的某个元素作为分区点，大于分区点的元素挪到分区点右边（从小到大排序），小于分区点的元素挪到分区点左边。然后分区点左右两边的子序列循环以上操作，直至子序列长度为 &lt;code&gt;1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;左右指针法实现思路&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1、首先定义分区点（pivot）&lt;code&gt;p&lt;/code&gt;，&lt;code&gt;p&lt;/code&gt; 一般为数组 &lt;code&gt;a&lt;/code&gt; 的第一个元素或最后一个元素&lt;br&gt;2、然后定义左（&lt;code&gt;l&lt;/code&gt;）、右（&lt;code&gt;r&lt;/code&gt;）两个指针分别指向数组的第一个元素（&lt;code&gt;a[0]&lt;/code&gt;）和最后一个元素 (&lt;code&gt;a[a.length - 1]&lt;/code&gt;)&lt;br&gt;3、如果 &lt;code&gt;a[l] &amp;gt; a[p]&lt;/code&gt;，&lt;code&gt;l、p&lt;/code&gt; 下标元素互换，&lt;code&gt;l&lt;/code&gt; 前进 &lt;code&gt;1&lt;/code&gt; 位&lt;br&gt;4、如果 &lt;code&gt;a[r] &amp;lt; a[p]&lt;/code&gt;，&lt;code&gt;r、p&lt;/code&gt; 下标元素互换，&lt;code&gt;r&lt;/code&gt; 后退 &lt;code&gt;1&lt;/code&gt; 位&lt;br&gt;5、如果 &lt;code&gt;l &amp;gt;= r&lt;/code&gt;，排序结束&lt;/p&gt;
    
    </summary>
    
      <category term="排序算法" scheme="https://ehlxr.me/categories/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="排序算法" scheme="https://ehlxr.me/tags/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/"/>
    
      <category term="快速排序" scheme="https://ehlxr.me/tags/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>堆排序理解</title>
    <link href="https://ehlxr.me/2022/03/19/heap-sort/"/>
    <id>https://ehlxr.me/2022/03/19/heap-sort/</id>
    <published>2022-03-19T15:09:01.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>堆排序的关键是构建大（小）顶堆，堆顶元素就是最大（小）的元素，然后堆顶元素和末尾元素交换位置，再次堆化除最后一个元素外的其它元素，循环次过程即可完成排序。</p><p>翻译成代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sort</span><span class="params">(<span class="keyword">int</span> a)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = a.length - <span class="number">1</span>; i &gt; <span class="number">0</span>; i--) &#123;</span><br><span class="line">        buildHeap(a, i);</span><br><span class="line">        <span class="comment">// 堆顶元素和最后一个元素交换，除过最后一个元素外其它元素再次构建大顶堆</span></span><br><span class="line">        swap(a, <span class="number">0</span>, i);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><a id="more"></a><p><strong>堆化过程</strong></p><p>1、从序列最<strong>后一个非叶子节点</strong>开始<br>2、堆化规则：替换节点为当前节点和叶子节点中值最大的节点<br>3、倒序依次处理其它非叶子节点<br>4、须保证叶子节点也满足堆化规则</p><p><strong>前置知识</strong></p><p>1、堆是完全二叉树<br>2、满二叉树使用数组存储最省空间<br>3、若节点在数组中的下标为 <code>n</code>，其左叶子节点在数组中的下标为 <code>2 * n + 1</code>，右子节点下标为 <code>2 * n + 2</code>，父节点下标为 <code>(n - 2)/2</code></p><p><strong>堆化过程图解</strong></p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/heapify.jpg" alt="堆化过程"></p><p>翻译成代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 构建大顶堆</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> a 数组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> n 最后一个元素下标</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">buildHeap</span><span class="params">(<span class="keyword">int</span>[] a, <span class="keyword">int</span> n)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 从最后一个非叶子节点开始，倒序依次处理其它非叶子节点</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = (n -<span class="number">1</span>) / <span class="number">2</span>; i &gt;=<span class="number">0</span>; i--) &#123;</span><br><span class="line">        heapify(a, n, i);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 堆化</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> a 数组</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> n 最后一个元素下标</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> i 需要调整的父节点下标</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">heapify</span><span class="params">(<span class="keyword">int</span>[] a,<span class="keyword">int</span> n, <span class="keyword">int</span> i)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">        <span class="comment">// 大顶堆规则：替换节点为当前节点和叶子节点中值最大的节点</span></span><br><span class="line">        <span class="keyword">int</span> maxPos = i;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">int</span> l = <span class="number">2</span> * i + <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span> (a[l] &gt; a[maxPos]) &#123;</span><br><span class="line">            maxPos = l;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">int</span> r = <span class="number">2</span> * i + <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (a[r] &gt; a[maxPos]) &#123;</span><br><span class="line">            maxPos = r;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 当前节点最大时退出</span></span><br><span class="line">        <span class="keyword">if</span> (maxPos == i) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        swap(a, maxPos, i);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 保证叶子节点也满足堆化规则</span></span><br><span class="line">        i = maxPos;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">swap</span><span class="params">(<span class="keyword">int</span>[] a, <span class="keyword">int</span> i, <span class="keyword">int</span> j)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> tmp = a[i];</span><br><span class="line">    a[i] = a[j];</span><br><span class="line">    a[j] = tmp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;堆排序的关键是构建大（小）顶堆，堆顶元素就是最大（小）的元素，然后堆顶元素和末尾元素交换位置，再次堆化除最后一个元素外的其它元素，循环次过程即可完成排序。&lt;/p&gt;
&lt;p&gt;翻译成代码如下：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; a)&lt;/span&gt; &lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt;(&lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; i = a.length - &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;; i &amp;gt; &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;; i--) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        buildHeap(a, i);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// 堆顶元素和最后一个元素交换，除过最后一个元素外其它元素再次构建大顶堆&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        swap(a, &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;, i);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
      <category term="排序算法" scheme="https://ehlxr.me/categories/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="排序算法" scheme="https://ehlxr.me/tags/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/"/>
    
      <category term="堆排序" scheme="https://ehlxr.me/tags/%E5%A0%86%E6%8E%92%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>KMP 算法理解</title>
    <link href="https://ehlxr.me/2022/03/12/the-kmp/"/>
    <id>https://ehlxr.me/2022/03/12/the-kmp/</id>
    <published>2022-03-12T22:44:00.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="字符串前缀-与字符串后缀"><a href="#字符串前缀-与字符串后缀" class="headerlink" title="字符串前缀 与字符串后缀"></a><strong>字符串前缀</strong> 与<strong>字符串后缀</strong></h2><ul><li><p>字符串前缀(<code>Proper prefix</code>) ：包含第一个字符，不包含最后一个字符的所有子串<br>例如：<code>abababca</code> 的前缀：<code>a、ab、aba、abab、ababa、ababab、abababc</code></p></li><li><p>字符串后缀(<code>Proper suffix</code>)：不包含第一个字符，包含最后一个字符的所有子串<br>例如：<code>abababca</code> 的后缀：<code>a、ca、bca、abca、babca、ababca、bababca</code></p></li></ul><h2 id="字符串部分匹配表"><a href="#字符串部分匹配表" class="headerlink" title="字符串部分匹配表"></a><strong>字符串部分匹配表</strong></h2><p>字符串部分匹配表 (<code>Partial Match Table</code>) 也称为 <code>next</code> 数组，例如：<code>abababca</code>  的部分匹配表为：</p><table><thead><tr><th>char</th><th>a</th><th>b</th><th>a</th><th>b</th><th>a</th><th>b</th><th>c</th><th>a</th></tr></thead><tbody><tr><td>index</td><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td></tr><tr><td>value</td><td>0</td><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>0</td><td>1</td></tr></tbody></table><a id="more"></a><p>每列 <code>value</code> 值表示前 <code>index + 1</code> 个字符子串的<strong>最大字符串前缀与字符串后缀相匹配的长度</strong>，例如：</p><ul><li>index 为 0 的子串为 a，没有字符串前缀和字符串后缀，所以 value 为 0</li><li>index 为 1 的子串为 ab，字符串前缀为 a，字符串后缀为 b，没有相匹配的字符串前缀与字符串后缀，所以 value 为 0</li><li>index 为 2 的子串为 aba，字符串前缀为 a、ab，字符串后缀为 a、ba，字符串前缀与字符串后缀相匹配的子串为 a，长度为 1，所以 value 为 1</li><li>index 为 3 的子串为 abab，字符串前缀为 a、ab、aba，字符串后缀为 b、ab、bab，字符串前缀与字符串后缀相匹配的子串为 ab，长度 2，所以 value 为 2</li><li>index 为 4 的子串为 ababa，字符串前缀为 a、ab、aba、abab，字符串后缀为 a、ba、aba、baba，字符串前缀与字符串后缀相匹配的子串为 a、aba，长度最大的为 aba ，所以 value 为 3</li><li>…</li><li>index 为 7 的子串为 abababca，字符串前缀为 a、ab、aba、abab、ababa、ababab、abababc，字符串后缀为 a、ca、bca、abca、babca、ababca、bababca，字符串前缀与字符串后缀相匹配的子串为 a，所以 value 为 1</li></ul><h2 id="KMP-算法思路"><a href="#KMP-算法思路" class="headerlink" title="KMP 算法思路"></a>KMP 算法思路</h2><p>KMP 算法就是利用字符串部分匹配表可以计算出当模式串与主串不匹配时，模式串可以<strong>多后移几位</strong> (默认后移 1 位)</p><p>当模式串与主串不匹配时，如果<strong>不匹配字符</strong>对应模式串下标大于 <code>0</code> (非首个模式串字符)，取此字符前一个字符对应字符串部分匹配表中的 <code>value</code> ，如果 <code>value</code> 大于 <code>0</code>，模式串中不匹配字符之前 (不含不匹配字符) 子串长度减去 <code>value</code> 即模式串为后移的位数。</p><p>暴力匹配算法当模式串和主串不匹配时，主串匹配下标 <code>+1</code>，模式串匹配下标置为 <code>0</code>，<code>KMP</code> 算法优化点在于模式串匹配下标置为 <code>value</code>。</p><p>例如在主串 <code>bacbababaabcbab</code> 中查找模式串 <code>abababca</code></p><ul><li>第一次符合以上规则的情况如下，模式串与主串不匹配字符 (<code>b</code>) 前一个字符为 <code>a</code>，对应字符串部分匹配表 <code>index</code> 为 <code>0</code>，<code>value</code> 为 <code>0</code>，所以不存在多后移情况</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bacbababaabcbab</span><br><span class="line">  |</span><br><span class="line"> abababca</span><br></pre></td></tr></table></figure><ul><li>第二次符合以上规则的情况如下</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bacbababaabcbab</span><br><span class="line">         |</span><br><span class="line">    abababca</span><br></pre></td></tr></table></figure><ul><li>模式串与主串不匹配字符 (<code>b</code>) 前一个字符是 <code>a</code>，对应字符串部分匹配表 <code>index</code> 为 <code>4</code>，<code>value</code> 为 <code>3</code>，不匹配字符之前模式串为 <code>ababa</code> 长度为 <code>5</code>， 所以后移 <code>5-3=2</code> 位</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bacbababaabcbab</span><br><span class="line">         |</span><br><span class="line">      abababca</span><br></pre></td></tr></table></figure><ul><li>模式串与主串不匹配字符 (<code>b</code>) 前一个字符是 <code>a</code>，对应字符串部分匹配表 <code>index</code> 为 <code>2</code>，<code>value</code> 为 <code>1</code>，不匹配字符之前模式串为 <code>aba</code> 长度为 <code>3</code>， 所以后移 <code>3-1=2</code> 位</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">bacbababaabcbab</span><br><span class="line">           |</span><br><span class="line">         abababca</span><br></pre></td></tr></table></figure><ul><li>此时，模式串长度已经比剩余主串长，匹配结束。</li></ul><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><p>next 数组的代码实现思路参考 <a href="https://www.cnblogs.com/tangzhengyue/p/4315393.html" target="_blank" rel="noopener">KMP 算法的 Next 数组详解</a></p><blockquote><p>Golang 暴力匹配代码实现</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 暴力匹配</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Bf</span><span class="params">(s, p <span class="keyword">string</span>)</span> <span class="title">int</span></span> &#123;</span><br><span class="line">    n := <span class="built_in">len</span>(s)</span><br><span class="line">    m := <span class="built_in">len</span>(p)</span><br><span class="line">    <span class="keyword">if</span> n &lt; m &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt;= n-m; i++ &#123;</span><br><span class="line">        j := <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> j &lt; m &#123;</span><br><span class="line">            <span class="comment">// 如果主串与模式串不匹配，则主串向右移动一个字符,模式串从头开始匹配</span></span><br><span class="line">            <span class="keyword">if</span> s[i+j] != p[j] &#123;</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            &#125;</span><br><span class="line">            j++</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> j == m &#123;</span><br><span class="line">            <span class="keyword">return</span> i</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>Golang KMP 代码实现</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Kmp</span><span class="params">(s, p <span class="keyword">string</span>)</span> <span class="title">int</span></span> &#123;</span><br><span class="line">    n := <span class="built_in">len</span>(s)</span><br><span class="line">    m := <span class="built_in">len</span>(p)</span><br><span class="line">    <span class="keyword">if</span> n &lt; m &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span></span><br><span class="line">    &#125;</span><br><span class="line">    next := GetNext(p)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt;= n-m; i++ &#123;</span><br><span class="line">        j := <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> j &lt; m &#123;</span><br><span class="line">            <span class="comment">// 暴力匹配算法当模式串和主串不匹配时，主串匹配下标 +1，模式串匹配下标置为 0，</span></span><br><span class="line">            <span class="comment">// KMP 算法优化点在于将模式串下标置为不匹配字符前一个字符对应 next 数组的值</span></span><br><span class="line">            <span class="keyword">if</span> s[i+j] != p[j] &#123;</span><br><span class="line">                <span class="comment">// 当模式串与主串不匹配时，如果不匹配字符对应模式串下标大于 j &gt; 0 (非首个模式串字符)，</span></span><br><span class="line">                <span class="comment">// 并且此字符前一个字符对应字符串部分匹配表中的值 next[j - 1] 也大于 0，</span></span><br><span class="line">                <span class="comment">// j - next[j - 1] 即模式串为后移的位数，等价于 j 置为 next[j - 1]</span></span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> j != <span class="number">0</span> &amp;&amp; next[j<span class="number">-1</span>] != <span class="number">0</span> &#123;</span><br><span class="line">                    <span class="comment">// j 后移 j-next[j-1]，等价于 j = next[j-1]</span></span><br><span class="line">                    j = next[j<span class="number">-1</span>]</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            j++</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> j == m &#123;</span><br><span class="line">            <span class="keyword">return</span> i</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GetNext</span><span class="params">(p <span class="keyword">string</span>)</span> []<span class="title">int</span></span> &#123;</span><br><span class="line">    n := <span class="built_in">len</span>(p)</span><br><span class="line">    next := <span class="built_in">make</span>([]<span class="keyword">int</span>, n)</span><br><span class="line">    next[<span class="number">0</span>] = <span class="number">0</span></span><br><span class="line">    k := <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据已知 next 数组的前 i-1 位推测第 i 位</span></span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">1</span>; i &lt; n; i++ &#123;</span><br><span class="line">        <span class="keyword">for</span> k &gt; <span class="number">0</span> &amp;&amp; p[k] != p[i] &#123;</span><br><span class="line">            <span class="comment">// k 为 p[0, i) 子串最大匹配前后缀长度</span></span><br><span class="line">            <span class="comment">// p[0, k) 为 p[0, i) 子串最大匹配前缀子串</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// 若：1、p[k] != p[i]，则求 p[0, i] 子串最大匹配前后缀长度问题</span></span><br><span class="line">            <span class="comment">// 转换成了求 p[0, k) 子串最大匹配前后缀长度问题</span></span><br><span class="line">            <span class="comment">// 循环直到 p[k] == p[i] (下一步处理) 或 k == 0</span></span><br><span class="line">            k = next[k]</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 若：2、p[k] == p[i]，则 p[0, i] 子串最大匹配前后缀长度为 k + 1</span></span><br><span class="line">        <span class="keyword">if</span> p[k] == p[i] &#123;</span><br><span class="line">            k++</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        next[i] = k</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> next</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>Java KMP 代码实现</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.github.ehlxr.algorithm.match;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 字符串匹配算法 KPM</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> ehlxr</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2022-03-13 10:06.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Kmp</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        String s = <span class="string">"bacbababaabcbab"</span>;</span><br><span class="line">        String p = <span class="string">"abab"</span>;</span><br><span class="line"></span><br><span class="line">        System.out.println(Arrays.toString(getNexts(p)));</span><br><span class="line">        System.out.println(kmp(s, p));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">kmp</span><span class="params">(String s, String p)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">char</span>[] scs = s.toCharArray();</span><br><span class="line">        <span class="keyword">char</span>[] pcs = p.toCharArray();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">int</span> m = s.length();</span><br><span class="line">        <span class="keyword">int</span> n = p.length();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">int</span>[] next = getNexts(p);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt;= m - n; i++) &#123;</span><br><span class="line">            <span class="keyword">int</span> j = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">for</span> (; j &lt; n; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (scs[i + j] != pcs[j]) &#123;</span><br><span class="line">                    <span class="comment">// 暴力匹配算法当模式串和主串不匹配时，主串匹配下标 +1，模式串匹配下标置为 0，</span></span><br><span class="line">                    <span class="comment">// KMP 算法优化点在于将模式串下标置为不匹配字符前一个字符对应 next 数组的值</span></span><br><span class="line">                    <span class="keyword">if</span> (j &gt; <span class="number">0</span> &amp;&amp; next[j - <span class="number">1</span>] &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                        <span class="comment">// 当模式串与主串不匹配时，如果**不匹配字符**对应模式串下标大于 j &gt; 0 (非首个模式串字符)，</span></span><br><span class="line">                        <span class="comment">// 并且此字符前一个字符对应字符串部分匹配表中的值 next[j - 1] 也大于 0，</span></span><br><span class="line">                        <span class="comment">// j - next[j - 1] 即模式串为后移的位数，等价于 j 置为 next[j - 1]</span></span><br><span class="line">                        j = next[j - <span class="number">1</span>];</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (j == n) &#123;</span><br><span class="line">                <span class="keyword">return</span> i;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span>[] getNexts(String p) &#123;</span><br><span class="line">        <span class="keyword">int</span> m = p.length();</span><br><span class="line">        <span class="keyword">char</span>[] b = p.toCharArray();</span><br><span class="line">        <span class="keyword">int</span>[] next = <span class="keyword">new</span> <span class="keyword">int</span>[m];</span><br><span class="line"></span><br><span class="line">        next[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">int</span> k = <span class="number">0</span>; <span class="comment">// 表示前后缀相匹配的最大长度</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 根据已知 next 数组的前 i-1 位推测第 i 位</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i &lt; m; ++i) &#123;</span><br><span class="line">            <span class="keyword">while</span> (k != <span class="number">0</span> &amp;&amp; b[k] != b[i]) &#123;</span><br><span class="line">                <span class="comment">// k 为 b[0, i) 子串最大匹配前后缀长度</span></span><br><span class="line">                <span class="comment">// b[0, k) 为 b[0, i) 子串最大匹配前缀子串</span></span><br><span class="line"></span><br><span class="line">                <span class="comment">// 若：1、b[k] != b[i]，则求 b[0, i] 子串最大匹配前后缀长度问题</span></span><br><span class="line">                <span class="comment">// 转换成了求 b[0, k) 子串最大匹配前后缀长度问题</span></span><br><span class="line">                <span class="comment">// 循环直到 b[k] == b[i] (下一步处理) 或 k == 0</span></span><br><span class="line">                k = next[k];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 若：2、b[k] == b[i]，则 b[0, i] 子串最大匹配前后缀长度为 k + 1</span></span><br><span class="line">            <span class="keyword">if</span> (b[k] == b[i]) &#123;</span><br><span class="line">                ++k;</span><br><span class="line">            &#125;</span><br><span class="line">            next[i] = k;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>参考 <a href="http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/" target="_blank" rel="noopener">The Knuth-Morris-Pratt Algorithm in my own words</a></p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;字符串前缀-与字符串后缀&quot;&gt;&lt;a href=&quot;#字符串前缀-与字符串后缀&quot; class=&quot;headerlink&quot; title=&quot;字符串前缀 与字符串后缀&quot;&gt;&lt;/a&gt;&lt;strong&gt;字符串前缀&lt;/strong&gt; 与&lt;strong&gt;字符串后缀&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;字符串前缀(&lt;code&gt;Proper prefix&lt;/code&gt;) ：包含第一个字符，不包含最后一个字符的所有子串&lt;br&gt;例如：&lt;code&gt;abababca&lt;/code&gt; 的前缀：&lt;code&gt;a、ab、aba、abab、ababa、ababab、abababc&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;字符串后缀(&lt;code&gt;Proper suffix&lt;/code&gt;)：不包含第一个字符，包含最后一个字符的所有子串&lt;br&gt;例如：&lt;code&gt;abababca&lt;/code&gt; 的后缀：&lt;code&gt;a、ca、bca、abca、babca、ababca、bababca&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;字符串部分匹配表&quot;&gt;&lt;a href=&quot;#字符串部分匹配表&quot; class=&quot;headerlink&quot; title=&quot;字符串部分匹配表&quot;&gt;&lt;/a&gt;&lt;strong&gt;字符串部分匹配表&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;字符串部分匹配表 (&lt;code&gt;Partial Match Table&lt;/code&gt;) 也称为 &lt;code&gt;next&lt;/code&gt; 数组，例如：&lt;code&gt;abababca&lt;/code&gt;  的部分匹配表为：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;char&lt;/th&gt;
&lt;th&gt;a&lt;/th&gt;
&lt;th&gt;b&lt;/th&gt;
&lt;th&gt;a&lt;/th&gt;
&lt;th&gt;b&lt;/th&gt;
&lt;th&gt;a&lt;/th&gt;
&lt;th&gt;b&lt;/th&gt;
&lt;th&gt;c&lt;/th&gt;
&lt;th&gt;a&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;index&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
    
    </summary>
    
      <category term="算法" scheme="https://ehlxr.me/categories/%E7%AE%97%E6%B3%95/"/>
    
    
      <category term="KMP" scheme="https://ehlxr.me/tags/KMP/"/>
    
      <category term="字符串匹配" scheme="https://ehlxr.me/tags/%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D/"/>
    
  </entry>
  
  <entry>
    <title>JVM TLAB</title>
    <link href="https://ehlxr.me/2021/07/27/jvm-tlab/"/>
    <id>https://ehlxr.me/2021/07/27/jvm-tlab/</id>
    <published>2021-07-27T09:35:05.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p><strong>TLAB（Thread Local Allocation Buffer）</strong> 线程本地分配缓存区</p><ol><li>由于对象一般分配在堆上，而堆是线程共用的，因此可能会有多个线程在堆上申请空间，而每一次的<strong>对象分配都必须加锁保证线程同步</strong>，会使分配的效率下降。考虑到对象分配几乎是 <code>Java</code> 中最常用的操作，因此 <code>JVM</code> 使用了 <code>TLAB</code> 这样的线程专有区域来避免多线程冲突，提高对象分配的效率。</li><li>我们说 <code>TLAB</code> 是线程独享的，但是只是在 <strong>“分配”</strong> 这个动作上是线程独享的，至于在读取、垃圾回收等动作上都是线程共享的。而且在使用上也没有什么区别</li><li><code>JVM</code> 为了提升对象内存分配的效率，对于所创建的线程都会分配一块独立的空间 <code>TLAB</code>，其大小由 <code>JVM</code> 根据运行的情况计算而得，在 <code>TLAB</code> 上分配对象时不需要加锁，因此 <strong>JVM 在给线程的对象分配内存时会尽量的在 TLAB 上分配</strong>，在这种情况下 JVM 中分配对象内存的性能和 <code>C</code> 基本是一样高效的，但如果对象过大的话则仍然是直接使用堆空间分配</li><li>在 <code>TLAB</code> 分配之后，并不影响对象的移动和回收，也就是说，虽然对象刚开始可能通过 <code>TLAB</code> 分配内存，存放在 <code>Eden</code> 区，但是还是会被垃圾回收或者被移到 <code>Survivor Space、Old Gen</code> 等。</li><li><strong>“堆是线程共享的内存区域” 这句话并不完全正确</strong>，因为 <code>TLAB</code> 是堆内存的一部分，它在读取上确实是线程共享的，但是在内存分配上，是线程独享的。</li><li><code>TLAB</code> 的空间其实并不大（默认是 <code>eden</code> 区空间的 <code>1%</code>），所以大对象还是可能需要在堆内存中直接分配。那么，对象的内存分配步骤就是先尝试 <code>TLAB</code> 分配，空间不足之后，再判断是否应该直接进入老年代，然后再确定是再 <code>eden</code> 分配还是在老年代分配。<a id="more"></a></li></ol><p><code>TLAB</code> 对象分配过程</p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/92dcfb688fa1414c9480e423efdd27d5.png" alt=""></p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/41eb453d8c00439aa719d6c5396db3ad.png" alt=""></p><p>参考链接</p><ul><li><a href="http://www.atguigu.com/download_detail.shtml?v=279" target="_blank" rel="noopener">尚硅谷-宋红康-详解 Java 虚拟机</a></li><li><a href="https://www.cnblogs.com/myseries/p/12884249.html" target="_blank" rel="noopener">JVM 关于对象分配在堆、栈、TLAB 的理解</a></li><li><a href="https://blog.csdn.net/QGhurt/article/details/107289843" target="_blank" rel="noopener">TLAB（Thread Local Allocation Buffer）</a></li></ul><hr>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;strong&gt;TLAB（Thread Local Allocation Buffer）&lt;/strong&gt; 线程本地分配缓存区&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;由于对象一般分配在堆上，而堆是线程共用的，因此可能会有多个线程在堆上申请空间，而每一次的&lt;strong&gt;对象分配都必须加锁保证线程同步&lt;/strong&gt;，会使分配的效率下降。考虑到对象分配几乎是 &lt;code&gt;Java&lt;/code&gt; 中最常用的操作，因此 &lt;code&gt;JVM&lt;/code&gt; 使用了 &lt;code&gt;TLAB&lt;/code&gt; 这样的线程专有区域来避免多线程冲突，提高对象分配的效率。&lt;/li&gt;
&lt;li&gt;我们说 &lt;code&gt;TLAB&lt;/code&gt; 是线程独享的，但是只是在 &lt;strong&gt;“分配”&lt;/strong&gt; 这个动作上是线程独享的，至于在读取、垃圾回收等动作上都是线程共享的。而且在使用上也没有什么区别&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JVM&lt;/code&gt; 为了提升对象内存分配的效率，对于所创建的线程都会分配一块独立的空间 &lt;code&gt;TLAB&lt;/code&gt;，其大小由 &lt;code&gt;JVM&lt;/code&gt; 根据运行的情况计算而得，在 &lt;code&gt;TLAB&lt;/code&gt; 上分配对象时不需要加锁，因此 &lt;strong&gt;JVM 在给线程的对象分配内存时会尽量的在 TLAB 上分配&lt;/strong&gt;，在这种情况下 JVM 中分配对象内存的性能和 &lt;code&gt;C&lt;/code&gt; 基本是一样高效的，但如果对象过大的话则仍然是直接使用堆空间分配&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;TLAB&lt;/code&gt; 分配之后，并不影响对象的移动和回收，也就是说，虽然对象刚开始可能通过 &lt;code&gt;TLAB&lt;/code&gt; 分配内存，存放在 &lt;code&gt;Eden&lt;/code&gt; 区，但是还是会被垃圾回收或者被移到 &lt;code&gt;Survivor Space、Old Gen&lt;/code&gt; 等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“堆是线程共享的内存区域” 这句话并不完全正确&lt;/strong&gt;，因为 &lt;code&gt;TLAB&lt;/code&gt; 是堆内存的一部分，它在读取上确实是线程共享的，但是在内存分配上，是线程独享的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TLAB&lt;/code&gt; 的空间其实并不大（默认是 &lt;code&gt;eden&lt;/code&gt; 区空间的 &lt;code&gt;1%&lt;/code&gt;），所以大对象还是可能需要在堆内存中直接分配。那么，对象的内存分配步骤就是先尝试 &lt;code&gt;TLAB&lt;/code&gt; 分配，空间不足之后，再判断是否应该直接进入老年代，然后再确定是再 &lt;code&gt;eden&lt;/code&gt; 分配还是在老年代分配。
    
    </summary>
    
      <category term="JVM" scheme="https://ehlxr.me/categories/JVM/"/>
    
    
      <category term="JVM" scheme="https://ehlxr.me/tags/JVM/"/>
    
      <category term="TLAB" scheme="https://ehlxr.me/tags/TLAB/"/>
    
      <category term="JAVA" scheme="https://ehlxr.me/tags/JAVA/"/>
    
  </entry>
  
  <entry>
    <title>MySQL InnoDB 事务隔离级别</title>
    <link href="https://ehlxr.me/2021/01/16/mysql-innodb-tx-isolation/"/>
    <id>https://ehlxr.me/2021/01/16/mysql-innodb-tx-isolation/</id>
    <published>2021-01-16T21:54:32.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="SQL-事务隔离级别说明"><a href="#SQL-事务隔离级别说明" class="headerlink" title="SQL 事务隔离级别说明"></a>SQL 事务隔离级别说明</h3><p>SQL 标准定义了 4 类隔离级别，包括了一些具体规则，用来限定事务内外的哪些改变是可见的，哪些是不可见的。低级别的隔离级一般支持更高的并发处理，并拥有更低的系统开销。</p><p><strong>Read Uncommitted（读取未提交内容）</strong></p><p>在该隔离级别，所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用，因为它的性能也不比其他级别好多少。读取未提交的数据，也被称之为脏读（Dirty Read）。</p><p><strong>Read Committed（读取提交内容）</strong></p><p>这是大多数数据库系统的默认隔离级别（但不是 MySQL 默认的）。它满足了隔离的简单定义：一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读（Nonrepeatable Read），因为同一事务的其他实例在该实例处理其间可能会有新的 commit，所以同一 select 可能返回不同结果。</p><p><strong>Repeatable Read（可重读）</strong></p><p>这是 MySQL 的默认事务隔离级别，它确保同一事务的多个实例在并发读取数据时，会看到同样的数据行。不过理论上，这会导致另一个棘手的问题：幻读 （Phantom Read），简单的说，幻读指当用户读取某一范围的数据行时，另一个事务又在该范围内插入了新行，当用户再读取该范围的数据行时，会发现有新的 <code>“幻影”</code> 行。</p><blockquote><p>InnoDB 存储引擎通过 <code>MVCC</code> 机制（快照读）和 <code>next-key lock</code>（当前读）解决了该问题。</p></blockquote><p><strong>Serializable（可串行化）</strong></p><p>这是最高的隔离级别，它通过强制事务排序，使之不可能相互冲突，从而解决幻读问题。简言之，它是在每个读的数据行上加上共享锁。在这个级别，可能导致大量的超时现象和锁竞争。</p><a id="more"></a><h3 id="事务隔离带来的问题"><a href="#事务隔离带来的问题" class="headerlink" title="事务隔离带来的问题"></a>事务隔离带来的问题</h3><p>这四种隔离级别采取不同的锁类型来实现，若读取的是同一个数据的话，就容易发生问题。例如：</p><p><strong>脏读 (Drity Read)</strong>：一个事务读取到另一事务未提交的更新数据。当一个事务正在访问数据，并且对数据进行了修改，而这种修改还没有提交到数据库中（这个数据在有可能会回滚），这时，另外一个事务也访问这个数据，然后使用了这个数据。</p><p><strong>不可重复读 (Non-repeatable read)</strong>：在一个事务内，前后两次读到的数据是不一样。在 T1 事务两次读取同一数据之间，T2 事务对该数据进行了修改，就会发生 T1 事务中的两次数据读取不一样的结果。相反， <strong>可重复读</strong>：在同一事务中多次读取数据时，能够保证所读数据一样，也就是后续读取不能读到另一事务已提交的更新数据。</p><p><strong>幻读 (Phantom Read)</strong>：指当事务不是独立执行时发生的一种现象，例如：T1 事务对表中的 “全部数据行” 进行了修改，同时 T2 事务向表中插入了一行 “新数据”，操作 T1 事务的用户发现表中<strong>还存在没有修改</strong>的数据行，就好象发生了幻觉一 样。一般解决幻读的方法是增加范围锁 RangeS，锁定检锁范围为只读，这样就避免了幻读。</p><p><strong>不可重复读和幻读的异同</strong></p><ul><li>两者都表现为两次读取的结果不一致</li><li>不可重复读是由于另一个事务对数据的更改所造成的</li><li>幻读是由于另一个事务插入或删除引起的</li><li>对于不可重复读，只需要锁住满足条件的记录</li><li>对于幻读，要锁住满足条件及其相近的记录</li><li>不可重复读表达的是：记录（一行或多行）的值在同一次事务中出现两个不同的结果</li><li>幻读表达的是：同一事务中查询两次得到两个不同的结果集</li><li>不可重复读侧重表达 <code>读 - 读</code></li><li>幻读则是说 <code>读 - 写</code>，用写来证实读的是鬼影</li></ul><p><a href="https://www.zhihu.com/question/47007926/answer/222348887" target="_blank" rel="noopener"><strong>关于幻读</strong></a></p><p>这里给出 MySQL 在 Repeatable Read 隔离界别下幻读的比较形象的场景：</p><table><thead><tr><th>时间</th><th>事务 1</th><th>事务 2</th></tr></thead><tbody><tr><td>T1</td><td>start transaction;<br />select * from users where id = 1;<br />结果为：0</td><td></td></tr><tr><td>T2</td><td></td><td>start transaction;<br />insert into users(id, name) values (1, ‘big cat’);<br />commit;</td></tr><tr><td>T3</td><td>insert into users(id, name) values (1, ‘big cat’);<br />主键冲突，插入失败</td><td></td></tr><tr><td>T4</td><td>select * from users where id = 1;<br />结果为：0</td><td></td></tr><tr><td>T5</td><td>rollback;</td><td></td></tr></tbody></table><p>假设 users 表中 id 为主键</p><ul><li><p>T1 的时间点事务 1 检测表中没有 id 为 1 的记录</p></li><li><p>T2 时间点事务 2 插入 id 为 1 的记录并提交事务</p></li><li><p>T3 时间点事务1 尝试插入 id 为 1 的数据时提示主键冲突</p></li><li><p>T4 时间点再去检查表中还是没有 id 为 1 的记录（由于 Repeatable Read 隔离级别，事务 2 的插入提交事务 1 读取不到）</p></li></ul><h3 id="MVCC"><a href="#MVCC" class="headerlink" title="MVCC"></a>MVCC</h3><p><code>MVCC</code> 多版本并发控制（Multiversion Concurrency Control）。</p><p>每一条记录都有一些隐藏字段，其中 <code>trx_id</code>（事务 id）和 <code>roll_pointer</code>（回滚指针）字段就和 <code>MVCC</code> 密切相关</p><p><strong>版本链</strong><br>每次对数据修改都会使 <code>roll_pointer</code> 指向生成 <code>undo</code> 日志，即形成了数据修改的 <code>版本链</code>，版本链的头节点就是当前数据的最新值。</p><p><strong>Read View</strong><br>对于 <code>RC</code>、<code>RR</code> 隔离级别来说，事务启动后在不同的时机会生成 <code>Read View</code>，用于判断当前记录是否在事务中可见。<code>Read View</code> 包含创建时刻所有活跃的 <code>trx_id</code> 的集合 <code>m_ids</code>。当判断数据是否在当前事务中显示时，需要从头到尾遍历版本链，依次取得数据的历史记录判断，直到取到符合条件的历史版本，如果所有的历史记录都不满足则忽略此条数据。</p><ul><li>当前版本数据记录的 <code>trx_id</code> 如果小于 <code>m_ids</code> 中最小的 <code>trx_id</code>，说明当前版本数据是在当前事务 <code>Read View</code> 生成之前生成的，所以对当前事务可见。</li><li>当前版本数据记录的 <code>trx_id</code> 如果大于 <code>m_ids</code> 中最大的 <code>trx_id</code>，说明当前版本数据是在当前事务 <code>Read View</code> 生成之后生成的，所以对当前事务不可见。</li><li>当前版本数据记录的 <code>trx_id</code> 如果在 <code>m_ids</code> 中，说明当前版本数据在当前事务 <code>Read View</code> 生成之前还未提交，所以对当前事务不可见。</li><li>当前版本数据记录的 <code>trx_id</code> 如果不在 <code>m_ids</code> 中，说明当前版本数据是在当前事务 <code>Read View</code> 生成之前已提交，所以对当前事务可见。</li></ul><blockquote><p><code>RC</code> 隔离级别在每次读取数据前都会生成 <code>Read View</code>，<code>RR</code> 隔离级别在第一次读取数据时生成 <code>Read View</code>，运用以上可见规则就很容易推断出这两种隔离级别的隔离能力了。</p></blockquote><p>另外，<code>undo</code> 日志会在系统判断没有比它更早的 <code>Read View</code> 存在时就会被删除。所以当系统中存在大量长事务的时候，会导致 <code>undo</code> 日志不能被及时清理而占用大量的存储空间。</p><h3 id="MySQL-隔离级别"><a href="#MySQL-隔离级别" class="headerlink" title="MySQL 隔离级别"></a>MySQL 隔离级别</h3><table><thead><tr><th align="left">隔离级别</th><th align="left">脏读（Dirty Read）</th><th align="left">不可重复读（NonRepeatable Read）</th><th align="left">幻读（Phantom Read）</th></tr></thead><tbody><tr><td align="left">未提交读（Read uncommitted）</td><td align="left">可能</td><td align="left">可能</td><td align="left">可能</td></tr><tr><td align="left">已提交读（Read committed）</td><td align="left">不可能</td><td align="left">可能</td><td align="left">可能</td></tr><tr><td align="left">可重复读（Repeatable read）</td><td align="left">不可能</td><td align="left">不可能</td><td align="left">可能</td></tr><tr><td align="left">可串行化（SERIALIZABLE）</td><td align="left">不可能</td><td align="left">不可能</td><td align="left">不可能</td></tr></tbody></table><h3 id="MySQL-事务隔离级别设置"><a href="#MySQL-事务隔离级别设置" class="headerlink" title="MySQL 事务隔离级别设置"></a>MySQL 事务隔离级别设置</h3><p><strong>InnoDB 默认是可重复读的（REPEATABLE READ）</strong></p><p>修改全局默认的事务级别，在 my.inf 文件的 [mysqld] 节里类似如下设置该选项（不推荐）</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">transaction-isolation &#x3D; &#123;READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE&#125;</span><br></pre></td></tr></table></figure><p><strong>改变单个会话或者所有新进连接的隔离级别（推荐使用）</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL &#123;READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE&#125;</span><br></pre></td></tr></table></figure><p><strong>查询全局和会话事务隔离级别方法</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">#查询全局的事务隔离级别</span><br><span class="line">SELECT @@global.tx_isolation;</span><br><span class="line">#查询当前会话的事务级别</span><br><span class="line">SELECT @@session.tx_isolation;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;SQL-事务隔离级别说明&quot;&gt;&lt;a href=&quot;#SQL-事务隔离级别说明&quot; class=&quot;headerlink&quot; title=&quot;SQL 事务隔离级别说明&quot;&gt;&lt;/a&gt;SQL 事务隔离级别说明&lt;/h3&gt;&lt;p&gt;SQL 标准定义了 4 类隔离级别，包括了一些具体规则，用来限定事务内外的哪些改变是可见的，哪些是不可见的。低级别的隔离级一般支持更高的并发处理，并拥有更低的系统开销。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read Uncommitted（读取未提交内容）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在该隔离级别，所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用，因为它的性能也不比其他级别好多少。读取未提交的数据，也被称之为脏读（Dirty Read）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read Committed（读取提交内容）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是大多数数据库系统的默认隔离级别（但不是 MySQL 默认的）。它满足了隔离的简单定义：一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读（Nonrepeatable Read），因为同一事务的其他实例在该实例处理其间可能会有新的 commit，所以同一 select 可能返回不同结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repeatable Read（可重读）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是 MySQL 的默认事务隔离级别，它确保同一事务的多个实例在并发读取数据时，会看到同样的数据行。不过理论上，这会导致另一个棘手的问题：幻读 （Phantom Read），简单的说，幻读指当用户读取某一范围的数据行时，另一个事务又在该范围内插入了新行，当用户再读取该范围的数据行时，会发现有新的 &lt;code&gt;“幻影”&lt;/code&gt; 行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;InnoDB 存储引擎通过 &lt;code&gt;MVCC&lt;/code&gt; 机制（快照读）和 &lt;code&gt;next-key lock&lt;/code&gt;（当前读）解决了该问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Serializable（可串行化）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是最高的隔离级别，它通过强制事务排序，使之不可能相互冲突，从而解决幻读问题。简言之，它是在每个读的数据行上加上共享锁。在这个级别，可能导致大量的超时现象和锁竞争。&lt;/p&gt;
    
    </summary>
    
      <category term="SQL" scheme="https://ehlxr.me/categories/SQL/"/>
    
    
      <category term="MySQL" scheme="https://ehlxr.me/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>Docker 安装 Gitea/Gogs 与主机共享 22 端口</title>
    <link href="https://ehlxr.me/2021/01/06/docker-gitea-share-port-22-with-host/"/>
    <id>https://ehlxr.me/2021/01/06/docker-gitea-share-port-22-with-host/</id>
    <published>2021-01-06T12:02:12.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>如果主机的 22 端口已被使用，使用 <code>Docker</code> 安装 <code>Gitea</code> 时只能把容器的 22 端口映射到主机的其它端口（如：10022），这是没有任何问题的。但是以 <code>SSH</code> 方式 <code>clone</code> 项目时，<code>URL</code> 长这样<br><code>ssh://git@git.example.com:10022:username/project.git</code></p><p>如果我们想要类似以下这样的 <code>URL</code> 时就需要把 <code>Gitea</code> 容器的和主机共享 22 端口<br><code>git@git.example.com:username/project.git</code></p><p>下面总结一下使用 <code>Docker</code> 安装 <code>Gitea</code> 共享主机 22 端口的主要步骤，<code>Gogs</code> 应该是同理。</p><h3 id="创建-git-用户"><a href="#创建-git-用户" class="headerlink" title="创建 git 用户"></a>创建 git 用户</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Create git user</span></span><br><span class="line">adduser git</span><br><span class="line"></span><br><span class="line"><span class="comment"># Make sure user has UID and GID 1000</span></span><br><span class="line">usermod -u 1000 -g 1000 git</span><br><span class="line"></span><br><span class="line"><span class="comment"># Create docker group</span></span><br><span class="line">groupadd docker</span><br><span class="line"></span><br><span class="line"><span class="comment"># Add git user to docker group</span></span><br><span class="line">usermod -aG docker git</span><br><span class="line"></span><br><span class="line"><span class="comment"># Create the gitea data directory</span></span><br><span class="line"></span><br><span class="line">mkdir -p /home/git/gitea/data</span><br></pre></td></tr></table></figure><a id="more"></a><h3 id="安装-Gitea"><a href="#安装-Gitea" class="headerlink" title="安装 Gitea"></a>安装 Gitea</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --name=gitea -p 10022:22 -p 10080:3000 -v /home/git/gitea/data:/data --restart=always gitea/gitea:latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># Create a symlink between the container authorized_keys and the host git user authorized_keys</span></span><br><span class="line">ln -s /home/git/gitea/data/git/.ssh /home/git/</span><br></pre></td></tr></table></figure><h3 id="生成-SSH-key"><a href="#生成-SSH-key" class="headerlink" title="生成 SSH key"></a>生成 SSH key</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sudo -u git ssh-keygen -t rsa -b 4096 -C <span class="string">"Gitea Host Key"</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <span class="variable">$(cat /home/git/.ssh/id_rsa.pub)</span>"</span> &gt;&gt; /home/git/.ssh/authorized_keys</span><br><span class="line"></span><br><span class="line">chmod 600 /home/git/.ssh/authorized_keys</span><br></pre></td></tr></table></figure><h3 id="配置-SSH-passthrough"><a href="#配置-SSH-passthrough" class="headerlink" title="配置 SSH passthrough"></a>配置 SSH passthrough</h3><p>配置 <code>passthrough</code> 连接到 <code>Gitea</code> 容器的 <code>SSH</code> 映射端口 10022</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p /app/gitea/</span><br><span class="line"></span><br><span class="line">cat &gt;/app/gitea/gitea &lt;&lt;<span class="string">'END'</span></span><br><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line">ssh -p 10022 -o StrictHostKeyChecking=no git@127.0.0.1 \</span><br><span class="line"><span class="string">"SSH_ORIGINAL_COMMAND=\"<span class="variable">$SSH_ORIGINAL_COMMAND</span>\" <span class="variable">$0</span> <span class="variable">$@</span>"</span></span><br><span class="line">END</span><br><span class="line"></span><br><span class="line">chmod +x /app/gitea/gitea</span><br></pre></td></tr></table></figure><h3 id="Caddy-反向代理配置"><a href="#Caddy-反向代理配置" class="headerlink" title="Caddy 反向代理配置"></a>Caddy 反向代理配置</h3><p>这里使用 <code>Caddy</code> 反向代理配置域名，<code>Caddyfile</code> 配置信息如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">git.example.com &#123;</span><br><span class="line">    encode zstd gzip</span><br><span class="line"></span><br><span class="line">    reverse_proxy localhost:10080</span><br><span class="line">    header &#x2F; Strict-Transport-Security &quot;max-age&#x3D;31536000;&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>配置完域名之后，输入域名进行安装，现在就可以修改 【SSH 服务域名】 为 <code>git.example.com</code>，【Gitea 基本 URL】 为 <code>https://git.example.com/</code>，也可以后通过 <code>/home/git/gogs/data/gogs/conf/app.ini</code> 配置文件修改相关配置。</p><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ul><li><p>由于 <code>docker</code> 启动容器的默认 <code>uid</code> 和 <code>gid</code> 是 1000，所以 <code>git</code> 用户的 <code>uid</code>、<code>gid</code> 必须为 1000，如果 <code>git</code> 用户的 <code>uid</code> 和 <code>gid</code> 不是 1000（比如：1002），尝试通过 <code>docker run --user 1002:1002</code>、 <code>docker run -e &quot;PUID=1002&quot; -e &quot;PGID=1002&quot;</code> 等方式启动 <code>docker</code> 容器都不管用。</p></li><li><p>保证 <code>git</code> 用户下的所有文件都属于 <code>git</code> 用户和 <code>git</code> 组</p></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[git]$ ls -la /home/git/gitea/data</span><br><span class="line">total 20</span><br><span class="line">drwxrwxr-x  5 git git 4096 Jan  5 14:14 .</span><br><span class="line">drwxrwxr-x  3 git git 4096 Jan  5 13:56 ..</span><br><span class="line">drwxr-xr-x  5 git git 4096 Jan  5 14:20 git</span><br><span class="line">drwxr-xr-x 10 git git 4096 Jan  5 14:40 gitea</span><br><span class="line">drwx------  2 git git 4096 Jan  5 14:14 ssh</span><br></pre></td></tr></table></figure><h3 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h3><ul><li><a href="https://raincal.com/post/gogs-share-22-port" target="_blank" rel="noopener">Gogs 与主机共享 22 端口</a></li><li><a href="https://gitea.com/jwobith/docker-gitea#additional-steps" target="_blank" rel="noopener">docker-gitea</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;如果主机的 22 端口已被使用，使用 &lt;code&gt;Docker&lt;/code&gt; 安装 &lt;code&gt;Gitea&lt;/code&gt; 时只能把容器的 22 端口映射到主机的其它端口（如：10022），这是没有任何问题的。但是以 &lt;code&gt;SSH&lt;/code&gt; 方式 &lt;code&gt;clone&lt;/code&gt; 项目时，&lt;code&gt;URL&lt;/code&gt; 长这样&lt;br&gt;&lt;code&gt;ssh://git@git.example.com:10022:username/project.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果我们想要类似以下这样的 &lt;code&gt;URL&lt;/code&gt; 时就需要把 &lt;code&gt;Gitea&lt;/code&gt; 容器的和主机共享 22 端口&lt;br&gt;&lt;code&gt;git@git.example.com:username/project.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;下面总结一下使用 &lt;code&gt;Docker&lt;/code&gt; 安装 &lt;code&gt;Gitea&lt;/code&gt; 共享主机 22 端口的主要步骤，&lt;code&gt;Gogs&lt;/code&gt; 应该是同理。&lt;/p&gt;
&lt;h3 id=&quot;创建-git-用户&quot;&gt;&lt;a href=&quot;#创建-git-用户&quot; class=&quot;headerlink&quot; title=&quot;创建 git 用户&quot;&gt;&lt;/a&gt;创建 git 用户&lt;/h3&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Create git user&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;adduser git&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Make sure user has UID and GID 1000&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;usermod -u 1000 -g 1000 git&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Create docker group&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;groupadd docker&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Add git user to docker group&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;usermod -aG docker git&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# Create the gitea data directory&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;mkdir -p /home/git/gitea/data&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
      <category term="Git" scheme="https://ehlxr.me/categories/Git/"/>
    
    
      <category term="Gogs" scheme="https://ehlxr.me/tags/Gogs/"/>
    
      <category term="Git" scheme="https://ehlxr.me/tags/Git/"/>
    
      <category term="Docker" scheme="https://ehlxr.me/tags/Docker/"/>
    
      <category term="Gitea" scheme="https://ehlxr.me/tags/Gitea/"/>
    
  </entry>
  
  <entry>
    <title>使用 Lambda 优雅的处理 Java 异常</title>
    <link href="https://ehlxr.me/2020/12/06/replace-java-try-catch-with-lambda/"/>
    <id>https://ehlxr.me/2020/12/06/replace-java-try-catch-with-lambda/</id>
    <published>2020-12-06T21:05:42.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>使用过 <code>Java</code> 的函数接口，就会被简介的语法深深的吸引，苦于代码中大量的 <code>try...catch</code> 繁琐代码，最近借鉴 <code>java.util.Optional</code> 的实现写了个简化的小工具。</p><p>以 <code>Long.valueOf()</code> 为例，假如需要把一个字符串转换为<code>long</code>，如果转换失败则设置默认值为 <code>-1</code>，一般会作如下处理：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">String param = <span class="string">"10s"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">long</span> result;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    result = Long.parseLong(param);</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    <span class="comment">// 捕获异常处理</span></span><br><span class="line"></span><br><span class="line">    result = -<span class="number">1L</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果使用简化工具：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Long result = Try.of(() -&gt; Long.valueOf(param)).trap(e -&gt; &#123;</span><br><span class="line">    <span class="comment">// 自行异常处理</span></span><br><span class="line">&#125;).get(-<span class="number">1L</span>);</span><br></pre></td></tr></table></figure><p>或者：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Long result = Try.&lt;String, Long&gt;of(Long::valueOf).trap(Throwable::printStackTrace).apply(param).get(-<span class="number">1L</span>);</span><br></pre></td></tr></table></figure><p>如果不需要异常处理：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Long result = Try.of(() -&gt; Long.valueOf(param)).get(-<span class="number">1L</span>);</span><br><span class="line"><span class="comment">// Long result = Try.&lt;String, Long&gt;of(Long::valueOf).apply(param).get(-1L);</span></span><br></pre></td></tr></table></figure><p>如果处理没有返回值的代码，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ArrayList&lt;String&gt; list = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line"></span><br><span class="line">Try.&lt;String&gt;of(v -&gt; list.add(<span class="number">10</span>, v))</span><br><span class="line">        .trap(e -&gt; System.out.println(e.getMessage()))</span><br><span class="line">        .accept(<span class="string">"test"</span>);</span><br></pre></td></tr></table></figure><a id="more"></a><p>其它情况的简单使用示例：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 有返回值，无入参</span></span><br><span class="line">    String param = <span class="string">"s"</span>;</span><br><span class="line">    Long result = Try.of(() -&gt; Long.valueOf(param)).get(<span class="number">0L</span>);</span><br><span class="line">    System.out.println(<span class="string">"Long.valueOf 1: "</span> + result);</span><br><span class="line"></span><br><span class="line">    result = Try.of(() -&gt; Long.valueOf(param)).get();</span><br><span class="line">    System.out.println(<span class="string">"Long.valueOf 2: "</span> + result);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 有返回值，有入参</span></span><br><span class="line">    result = Try.&lt;String, Long&gt;of(s -&gt; Long.valueOf(s))</span><br><span class="line">            .apply(param)</span><br><span class="line">            .trap((e) -&gt; System.out.println(<span class="string">"Long.valueOf exception: "</span> + e.getMessage()))</span><br><span class="line">            .andFinally(() -&gt; System.out.println(<span class="string">"Long.valueOf finally run code."</span>))</span><br><span class="line">            .finallyTrap((e) -&gt; System.out.println(<span class="string">"Long.valueOf finally exception: "</span> + e.getMessage()))</span><br><span class="line">            .get();</span><br><span class="line">    System.out.println(<span class="string">"Long.valueOf 3: "</span> + result);</span><br><span class="line"></span><br><span class="line">    ArrayList&lt;String&gt; list = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 无返回值，无入参</span></span><br><span class="line">    Try.of(() -&gt; Thread.sleep(-<span class="number">1L</span>))</span><br><span class="line">            .andFinally(() -&gt; list.clear())</span><br><span class="line">            <span class="comment">// .andFinally(list::clear) //https://stackoverflow.com/questions/37413106/java-lang-nullpointerexception-is-thrown-using-a-method-reference-but-not-a-lamb</span></span><br><span class="line">            .run();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 无返回值，有入参</span></span><br><span class="line">    Try.&lt;String&gt;of(v -&gt; list.add(<span class="number">0</span>, v)).accept(<span class="string">"test"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://github.com/ehlxr/budd/blob/master/src/main/java/io/github/ehlxr/utils/Try.java" target="_blank" rel="noopener"><strong>小工具的实现链接</strong></a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;使用过 &lt;code&gt;Java&lt;/code&gt; 的函数接口，就会被简介的语法深深的吸引，苦于代码中大量的 &lt;code&gt;try...catch&lt;/code&gt; 繁琐代码，最近借鉴 &lt;code&gt;java.util.Optional&lt;/code&gt; 的实现写了个简化的小工具。&lt;/p&gt;
&lt;p&gt;以 &lt;code&gt;Long.valueOf()&lt;/code&gt; 为例，假如需要把一个字符串转换为&lt;code&gt;long&lt;/code&gt;，如果转换失败则设置默认值为 &lt;code&gt;-1&lt;/code&gt;，一般会作如下处理：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;String param = &lt;span class=&quot;string&quot;&gt;&quot;10s&quot;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;long&lt;/span&gt; result;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    result = Long.parseLong(param);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125; &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Exception e) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 捕获异常处理&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    result = -&lt;span class=&quot;number&quot;&gt;1L&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;如果使用简化工具：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;Long result = Try.of(() -&amp;gt; Long.valueOf(param)).trap(e -&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// 自行异常处理&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;).get(-&lt;span class=&quot;number&quot;&gt;1L&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;或者：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;Long result = Try.&amp;lt;String, Long&amp;gt;of(Long::valueOf).trap(Throwable::printStackTrace).apply(param).get(-&lt;span class=&quot;number&quot;&gt;1L&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;如果不需要异常处理：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;Long result = Try.of(() -&amp;gt; Long.valueOf(param)).get(-&lt;span class=&quot;number&quot;&gt;1L&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// Long result = Try.&amp;lt;String, Long&amp;gt;of(Long::valueOf).apply(param).get(-1L);&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;如果处理没有返回值的代码，如下：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;ArrayList&amp;lt;String&amp;gt; list = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; ArrayList&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Try.&amp;lt;String&amp;gt;of(v -&amp;gt; list.add(&lt;span class=&quot;number&quot;&gt;10&lt;/span&gt;, v))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        .trap(e -&amp;gt; System.out.println(e.getMessage()))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        .accept(&lt;span class=&quot;string&quot;&gt;&quot;test&quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
      <category term="Java开发技术" scheme="https://ehlxr.me/categories/Java%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Java" scheme="https://ehlxr.me/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>HTTPS 笔记</title>
    <link href="https://ehlxr.me/2020/03/11/https-note/"/>
    <id>https://ehlxr.me/2020/03/11/https-note/</id>
    <published>2020-03-11T17:25:43.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>随着互联网的迅速发展，网络安全问题日益凸显，现在 Chrome 浏览器已经开始阻止非 https 网站的访问了。对于 https 的流程一直不是十分清晰，借着还没有完全复工有时间，大概画了个图总结一下。</p><p>想要了解 https 流程，CA 的相关知识，加密方式（对称加密、非对称加密），以及哈希计算（例如：MD5、sha256）等技术必须得掌握，这里先不做介绍，后续有时间再进行归纳总结。</p><p>https 是在 http 的基础上加入了 SSL 协议，SSL 依靠证书来验证服务器的身份，并为浏览器和服务器之间的通信进行加密。</p><a id="more"></a><h2 id="HTTPS-的验证流程"><a href="#HTTPS-的验证流程" class="headerlink" title="HTTPS 的验证流程"></a>HTTPS 的验证流程</h2><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/https.jpg" alt="https"></p><ol><li>客户端发起 https 请求</li><li>服务端返回数字证书文件（X.509 格式）</li><li>客户端验证数字证书，并且提取服务端公钥</li><li>如果客户端验证数字证书通过，则随机生成一个对称加密的 key，并使用服务器公钥对 key 加密</li><li>客户端发送加密后的 key 到服务端</li><li>服务端使用私钥解密拿到 key</li><li>客户端与服务端使用该 key 对称加解密通讯信息</li></ol><h2 id="客户端验证数字证书"><a href="#客户端验证数字证书" class="headerlink" title="客户端验证数字证书"></a>客户端验证数字证书</h2><ol><li>浏览器安装后会自带一些权威 CA 公钥</li><li>使用相匹配的 CA 公钥对数字证书中的数字签名解密，如果能够解密则得到数字证书的摘要，由此证明数字证书是可信的</li><li>根据数字证书中的散列算法对网站信息进行哈希运算，将得到的结果与上一步得到的摘要对比，如果两者一致，就证明证书未被修改过</li></ol><h2 id="数字证书的签发过程"><a href="#数字证书的签发过程" class="headerlink" title="数字证书的签发过程"></a>数字证书的签发过程</h2><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/digital_signature.jpg" alt="digital signature"></p><hr>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;随着互联网的迅速发展，网络安全问题日益凸显，现在 Chrome 浏览器已经开始阻止非 https 网站的访问了。对于 https 的流程一直不是十分清晰，借着还没有完全复工有时间，大概画了个图总结一下。&lt;/p&gt;
&lt;p&gt;想要了解 https 流程，CA 的相关知识，加密方式（对称加密、非对称加密），以及哈希计算（例如：MD5、sha256）等技术必须得掌握，这里先不做介绍，后续有时间再进行归纳总结。&lt;/p&gt;
&lt;p&gt;https 是在 http 的基础上加入了 SSL 协议，SSL 依靠证书来验证服务器的身份，并为浏览器和服务器之间的通信进行加密。&lt;/p&gt;
    
    </summary>
    
      <category term="Java开发技术" scheme="https://ehlxr.me/categories/Java%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Java" scheme="https://ehlxr.me/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Maven 配置文件 settings.xml 详解</title>
    <link href="https://ehlxr.me/2020/03/02/maven-setting-config/"/>
    <id>https://ehlxr.me/2020/03/02/maven-setting-config/</id>
    <published>2020-03-02T16:56:01.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p><code>settings.xml</code> 是 <code>maven</code> 的配置文件，用户配置文件存放于 <code>${user.home}/.m2/</code> 目录下，系统全局配置文件放置于 <code>${maven.home}/conf/</code> 目录下，<code>pom.xml</code> 是 <code>maven</code> 的项目的配置文件。</p><p>配置文件的优先级从<code>高到低</code>为：<code>pom.xml</code>、用户配置 <code>settings.xml</code>、全局系统 <code>settings.xml</code>。如果这些文件同时存在，在应用配置时，会合并它们的内容，如果有重复的配置，优先级高的配置会覆盖优先级低的。</p><a id="more"></a><p>现抽空把 <code>settings.xml</code> 相关配置属性总结一下。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">settings</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/SETTINGS/1.0.0"</span></span></span><br><span class="line"><span class="tag">          <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">          <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 构建系统本地仓库的路径。其默认值：~/.m2/repository --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;localRepository&gt;$&#123;user.home&#125;/.m2/repository&lt;/localRepository&gt; --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 是否需要和用户交互以获得输入。默认为 true --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;interactiveMode&gt;true&lt;/interactiveMode&gt; --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 是否需要使用 ~/.m2/plugin-registry.xml 文件来管理插件版本。默认为 false --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;usePluginRegistry&gt;false&lt;/usePluginRegistry&gt; --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 是否需要在离线模式下运行，默认为 false。当由于网络设置原因或者安全因素，构建服务器不能连接远程仓库的时候，该配置就十分有用 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;offline&gt;false&lt;/offline&gt; --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 当插件的 groupId 没有显式提供时，供搜寻插件 groupId 的列表。使用某个插件，如果没有指定 groupId 的时候，maven 就会使用该列表。</span></span><br><span class="line"><span class="comment">        默认情况下该列表包含了 org.apache.maven.plugins 和 org.codehaus.mojo --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;pluginGroups&gt; --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- plugin 的 groupId --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- &lt;pluginGroup&gt;org.codehaus.mojo&lt;/pluginGroup&gt; --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;/pluginGroups&gt; --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 配置服务端的一些设置。如安全证书之类的信息应该配置在 settings.xml 文件中，避免配置在 pom.xml 中 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;servers&gt; --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- &lt;server&gt; --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 这是 server 的 id（注意不是用户登陆的 id），该 id 与 distributionManagement 中 repository 元素的 id 相匹配 --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- &lt;id&gt;server001&lt;/id&gt; --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 鉴权用户名 --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- &lt;username&gt;my_login&lt;/username&gt; --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 鉴权密码 --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- &lt;password&gt;my_password&lt;/password&gt; --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 鉴权时使用的私钥位置。默认是 $&#123;user.home&#125;/.ssh/id_dsa --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- &lt;privateKey&gt;$&#123;usr.home&#125;/.ssh/id_dsa&lt;/privateKey&gt; --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 鉴权时使用的私钥密码 --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- &lt;passphrase&gt;some_passphrase&lt;/passphrase&gt; --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 文件被创建时的权限。如果在部署的时候会创建一个仓库文件或者目录，这时候就可以使用该权限。其对应了 unix 文件系统的权限，如：664、775 --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- &lt;filePermissions&gt;664&lt;/filePermissions&gt; --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 目录被创建时的权限 --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- &lt;directoryPermissions&gt;775&lt;/directoryPermissions&gt; --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- &lt;/server&gt; --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;/servers&gt; --&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 下载镜像列表 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mirrors</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 设置多个镜像只会识别第一个镜像下载 jar 包--&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">mirror</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 该镜像的唯一标识符。id 用来区分不同的 mirror 元素 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">id</span>&gt;</span>aliyunmaven<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 被镜像的服务器的 id。如果我们要设置了一个 maven 中央仓库（http://repo.maven.apache.org/maven2/）的镜像，就需要将该元素设置成 central。</span></span><br><span class="line"><span class="comment">                可以使用 * 表示任意远程库。例如：external:* 表示任何不在 localhost 和文件系统中的远程库，r1,r2 表示 r1 库或者 r2 库，*,!r1 表示除了 r1 库之外的任何远程库 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">mirrorOf</span>&gt;</span>*<span class="tag">&lt;/<span class="name">mirrorOf</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 镜像名称 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">name</span>&gt;</span>阿里云公共仓库<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 镜像的 URL --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">url</span>&gt;</span>https://maven.aliyun.com/repository/public<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">mirror</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">mirrors</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 用来配置不同的代理 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">proxies</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">proxy</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 代理的唯一定义符，用来区分不同的代理元素 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">id</span>&gt;</span>myproxy<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 是否激活。当我们声明了一组代理，而某个时候只需要激活一个代理的时候 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">active</span>&gt;</span>false<span class="tag">&lt;/<span class="name">active</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 代理的协议 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">protocol</span>&gt;</span>http<span class="tag">&lt;/<span class="name">protocol</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 代理的主机名 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">host</span>&gt;</span>proxy.somewhere.com<span class="tag">&lt;/<span class="name">host</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 代理的端口 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">port</span>&gt;</span>8080<span class="tag">&lt;/<span class="name">port</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 代理的用户名，用户名和密码表示代理服务器认证的登录名和密码 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">username</span>&gt;</span>proxyuser<span class="tag">&lt;/<span class="name">username</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 代理的密码 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">password</span>&gt;</span>somepassword<span class="tag">&lt;/<span class="name">password</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 不该被代理的主机名列表。该列表的分隔符由代理服务器指定；例子中使用了竖线分隔符，逗号分隔也很常见 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">nonProxyHosts</span>&gt;</span>*.google.com|ibiblio.org<span class="tag">&lt;/<span class="name">nonProxyHosts</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">proxy</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">proxies</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 根据环境参数来调整构建配置的列表。对应 pom.xml 中 profile 元素（只包含 id、activation、repositories、pluginRepositories 和 properties 元素）</span></span><br><span class="line"><span class="comment">        如果一个 settings.xml 中的 profile 被激活，它的值会覆盖任何定义在 pom.xml 中带有相同 id 的 profile --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">profiles</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">profile</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- profile 的唯一标识 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">id</span>&gt;</span>test<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 自动触发 profile 的条件逻辑。也可通过 activeProfile 元素以及使用 -P 标记激活（如：mvn clean install -P test）</span></span><br><span class="line"><span class="comment">                在 maven 工程的 pom.xml 所在目录下执行 mvn help:active-profiles 命令可以查看生效的 profile --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">activation</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 默认是否激活 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">activeByDefault</span>&gt;</span>false<span class="tag">&lt;/<span class="name">activeByDefault</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 当匹配的 jdk 被检测到，profile 被激活。例如：1.4 激活 JDK1.4、1.4.0_2，而 !1.4 激活所有版本不是以 1.4 开头的 JDK --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">jdk</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">jdk</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 当匹配的操作系统属性被检测到，profile 被激活。os 元素可以定义一些操作系统相关的属性 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">os</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 激活 profile的 操作系统的名字 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">name</span>&gt;</span>Windows XP<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!--激活 profile 的操作系统所属家族。如：windows --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">family</span>&gt;</span>Windows<span class="tag">&lt;/<span class="name">family</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!--激活 profile 的操作系统体系结构 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">arch</span>&gt;</span>x86<span class="tag">&lt;/<span class="name">arch</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!--激活p rofile 的操作系统版本 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">version</span>&gt;</span>5.1.2600<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">os</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 如果 maven 检测到某一个属性（其值可以在 pom.xml 中通过 $&#123;name&#125; 引用），其拥有对应的 name=值，Profile 就会被激活。如果值字段是空的，</span></span><br><span class="line"><span class="comment">                    那么存在属性名称字段就会激活 profile，否则按区分大小写方式匹配属性值字段 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">property</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 激活 profile 的属性的名称 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">name</span>&gt;</span>mavenVersion<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 激活 profile 的属性的值 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">value</span>&gt;</span>2.0.3<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 通过检测该文件的存在或不存在来激活 profile--&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">file</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 如果指定的文件存在，则激活 profile --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">exists</span>&gt;</span>$&#123;basedir&#125;/file2.properties<span class="tag">&lt;/<span class="name">exists</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 如果指定的文件不存在，则激活 profile --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">missing</span>&gt;</span>$&#123;basedir&#125;/file1.properties<span class="tag">&lt;/<span class="name">missing</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">file</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">activation</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 对应 profile 的扩展属性列表。maven 属性和 ant 中的属性一样，可以用来存放一些值。这些值可以在 pom.xml 中的任何地方使用标记 $&#123;X&#125; 来使用，这里 X 是指属性的名称。</span></span><br><span class="line"><span class="comment">                属性有五种不同的形式，并且都能在 settings.xml 文件中访问：</span></span><br><span class="line"><span class="comment">                1. env.X：在一个变量前加上 "env." 的前缀，会返回一个 shell 环境变量。例如："env.PATH" 指代了 $path 环境变量（在 Windows 上是 %PATH%）</span></span><br><span class="line"><span class="comment">                2. project.x：指代了 pom.xml 中对应的元素值。例如：&lt;project&gt;&lt;version&gt;1.0&lt;/version&gt;&lt;/project&gt; 通过 $&#123;project.version&#125; 获得 version 的值</span></span><br><span class="line"><span class="comment">                3. settings.x：指代了 settings.xml 中对应元素的值。例如：&lt;settings&gt;&lt;offline&gt;false&lt;/offline&gt;&lt;/settings&gt; 通过 $&#123;settings.offline&#125; 获得 offline 的值</span></span><br><span class="line"><span class="comment">                4. Java System Properties：所有可通过 java.lang.System.getProperties() 访问的属性都能在 pom.xml 中使用该形式访问，例如：$&#123;java.home&#125;</span></span><br><span class="line"><span class="comment">                5. x：在 &lt;properties/&gt; 元素中，或者外部文件中设置，以 $&#123;someVar&#125; 的形式使用</span></span><br><span class="line"><span class="comment">            --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 如果该 profile 被激活，则可以在 pom.xml 中使用 $&#123;user.install&#125; --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">user.install</span>&gt;</span>$&#123;user.home&#125;/our-project<span class="tag">&lt;/<span class="name">user.install</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 远程仓库列表。它是 maven 用来填充构建系统本地仓库所使用的一组远程仓库 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">repositories</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!--包含需要连接到远程仓库的信息 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">repository</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 远程仓库唯一标识 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">id</span>&gt;</span>codehausSnapshots<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 远程仓库名称 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">name</span>&gt;</span>Codehaus Snapshots<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 如何处理远程仓库里 releases 的下载 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">releases</span>&gt;</span></span><br><span class="line">                        <span class="comment">&lt;!-- 是否开启 --&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">enabled</span>&gt;</span>false<span class="tag">&lt;/<span class="name">enabled</span>&gt;</span></span><br><span class="line">                        <span class="comment">&lt;!-- 该元素指定更新发生的频率。maven 会比较本地 pom.xml 和远程 pom.xml 的时间戳。</span></span><br><span class="line"><span class="comment">                            这里的选项是：always（一直），daily（默认，每日），interval：X（这里 X 是以分钟为单位的时间间隔），或者 never（从不）。 --&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">updatePolicy</span>&gt;</span>always<span class="tag">&lt;/<span class="name">updatePolicy</span>&gt;</span></span><br><span class="line">                        <span class="comment">&lt;!-- 当 maven 验证构件校验文件失败时该怎么做：ignore（忽略），fail（失败），或者 warn（警告）--&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">checksumPolicy</span>&gt;</span>warn<span class="tag">&lt;/<span class="name">checksumPolicy</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">releases</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 如何处理远程仓库里快照版本的下载。有了 releases 和 snapshots 这两组配置，pom.xml 就可以在每个单独的仓库中，为每种类型的构件采取不同的策略。</span></span><br><span class="line"><span class="comment">                        例如：可能有人会决定只为开发目的开启对快照版本下载的支持 --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">snapshots</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">enabled</span>/&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">updatePolicy</span>/&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">checksumPolicy</span>/&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">snapshots</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 远程仓库 URL --&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">url</span>&gt;</span>http://snapshots.maven.codehaus.org/maven2<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 用于定位和排序构件的仓库布局类型。可以是 default（默认）或者 legacy（遗留）--&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">layout</span>&gt;</span>default<span class="tag">&lt;/<span class="name">layout</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">repository</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">repositories</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!-- 插件的远程仓库列表。和 repositories 类似，repositories 管理 jar 包依赖的仓库，pluginRepositories 则是管理插件的仓库 --&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">pluginRepositories</span>&gt;</span></span><br><span class="line">                <span class="comment">&lt;!-- 每个 pluginRepository 元素指定一个 maven 可以用来寻找新插件的远程地址 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">pluginRepository</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">id</span>/&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">name</span>/&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">releases</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">enabled</span>/&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">updatePolicy</span>/&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">checksumPolicy</span>/&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">releases</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">snapshots</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">enabled</span>/&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">updatePolicy</span>/&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">checksumPolicy</span>/&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">snapshots</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">url</span>/&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">layout</span>/&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">pluginRepository</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">pluginRepositories</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">profile</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">profiles</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 手动激活 profiles 的列表 --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- &lt;activeProfiles&gt; --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 要激活的 profile id。例如：env-test，则在 pom.xml 或 settings.xml 中对应 id 的 profile 会被激活。如果运行过程中找不到对应的 profile 则忽略配置 --&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- &lt;activeProfile&gt;env-test&lt;/activeProfile&gt; --&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- &lt;/activeProfiles&gt; --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">settings</span>&gt;</span></span><br></pre></td></tr></table></figure><hr><p>参考资料</p><ul><li><a href="https://maven.apache.org/settings.html" target="_blank" rel="noopener">maven 官方文档之 settings</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;code&gt;settings.xml&lt;/code&gt; 是 &lt;code&gt;maven&lt;/code&gt; 的配置文件，用户配置文件存放于 &lt;code&gt;${user.home}/.m2/&lt;/code&gt; 目录下，系统全局配置文件放置于 &lt;code&gt;${maven.home}/conf/&lt;/code&gt; 目录下，&lt;code&gt;pom.xml&lt;/code&gt; 是 &lt;code&gt;maven&lt;/code&gt; 的项目的配置文件。&lt;/p&gt;
&lt;p&gt;配置文件的优先级从&lt;code&gt;高到低&lt;/code&gt;为：&lt;code&gt;pom.xml&lt;/code&gt;、用户配置 &lt;code&gt;settings.xml&lt;/code&gt;、全局系统 &lt;code&gt;settings.xml&lt;/code&gt;。如果这些文件同时存在，在应用配置时，会合并它们的内容，如果有重复的配置，优先级高的配置会覆盖优先级低的。&lt;/p&gt;
    
    </summary>
    
      <category term="Java开发技术" scheme="https://ehlxr.me/categories/Java%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Java" scheme="https://ehlxr.me/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>[转]理解 Java 动态代理</title>
    <link href="https://ehlxr.me/2020/01/02/java-dynamic-proxy/"/>
    <id>https://ehlxr.me/2020/01/02/java-dynamic-proxy/</id>
    <published>2020-01-02T15:44:55.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://juejin.im/post/5a99048a6fb9a028d5668e62" target="_blank" rel="noopener">原文地址</a></p><p>动态代理是 <code>Java</code> 语言中非常经典的一种设计模式，也是所有设计模式中最难理解的一种。本文将通过一个简单的例子模拟 <code>JDK</code> 动态代理实现，让你彻底明白动态代理设计模式的本质。</p><h2 id="什么是代理"><a href="#什么是代理" class="headerlink" title="什么是代理"></a>什么是代理</h2><p>从字面意思来看，代理比较好理解，无非就是代为处理的意思。举个例子，你在上大学的时候，总是喜欢逃课。因此，你拜托你的同学帮你答到，而自己却窝在宿舍玩游戏… 你的这个同学恰好就充当了代理的作用，代替你去上课。</p><a id="more"></a><p>是的，你没有看错，代理就是这么简单！</p><p>理解了代理的意思，你脑海中恐怕还有两个巨大的疑问：</p><ul><li>怎么实现代理模式</li><li>代理模式有什么实际用途</li></ul><p>要理解这两个问题，看一个简单的例子：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Flyable</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">fly</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Bird</span> <span class="keyword">implements</span> <span class="title">Flyable</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fly</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"Bird is flying..."</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="keyword">new</span> Random().nextInt(<span class="number">1000</span>));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>很简单的一个例子，用一个随机睡眠时间模拟小鸟在空中的飞行时间。接下来问题来了，如果我要知道小鸟在天空中飞行了多久，怎么办？</p><p>有人说，很简单，在 <code>Bird-&gt;fly ()</code> 方法的开头记录起始时间，在方法结束记录完成时间，两个时间相减就得到了飞行时间。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fly</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">long</span> start = System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">"Bird is flying..."</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      Thread.sleep(<span class="keyword">new</span> Random().nextInt(<span class="number">1000</span>));</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">      e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">"Fly time = "</span> + (end - start));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>的确，这个方法没有任何问题，接下来加大问题的难度。如果 <code>Bird</code> 这个类来自于某个 <code>SDK</code>（或者说 <code>Jar</code> 包）提供，你无法改动源码，怎么办？</p><p>一定会有人说，我可以在调用的地方这样写：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    Bird bird = <span class="keyword">new</span> Bird();</span><br><span class="line">    <span class="keyword">long</span> start = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">  bird.fly();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">"Fly time = "</span> + (end - start));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个方案看起来似乎没有问题，但其实你忽略了准备这些方法所需要的时间，执行一个方法，需要开辟栈内存、压栈、出栈等操作，这部分时间也是不可以忽略的。因此，这个解决方案不可行。那么，还有什么方法可以做到呢？</p><p>使用继承，继承是最直观的解决方案，相信你已经想到了，至少我最开始想到的解决方案就是继承。 为此，我们重新创建一个类 <code>Bird2</code>，在 <code>Bird2</code> 中我们只做一件事情，就是调用父类的 <code>fly</code> 方法，在前后记录时间，并打印时间差：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Bird2</span> <span class="keyword">extends</span> <span class="title">Bird</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fly</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> start = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">super</span>.fly();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">        System.out.println(<span class="string">"Fly time = "</span> + (end - start));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是一种解决方案，还有一种解决方案叫做：<strong>聚合</strong>，其实也是比较容易想到的。 我们再次创建新类 <code>Bird3</code>，在 <code>Bird3</code> 的构造方法中传入 <code>Bird</code> 实例。同时，让 <code>Bird3</code> 也实现 <code>Flyable</code> 接口，并在 <code>fly</code>方法中调用传入的 <code>Bird</code> 实例的 <code>fly</code> 方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Bird3</span> <span class="keyword">implements</span> <span class="title">Flyable</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Bird bird;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Bird3</span><span class="params">(Bird bird)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.bird = bird;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fly</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> start = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">        bird.fly();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">        System.out.println(<span class="string">"Fly time = "</span> + (end - start));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了记录 <code>Bird-&gt;fly ()</code> 方法的执行时间，我们在前后添加了记录时间的代码。同样地，通过这种方法我们也可以获得小鸟的飞行时间。那么，这两种方法孰优孰劣呢？咋一看，不好评判！</p><p>继续深入思考，用问题推导来解答这个问题：</p><p><strong>问题一</strong>：如果我还需要在 <code>fly</code> 方法前后打印日志，记录飞行开始和飞行结束，怎么办？ 有人说，很简单！继承 <code>Bird2</code> 并在在前后添加打印语句即可。那么，问题来了，请看问题二。</p><p><strong>问题二</strong>：如果我需要调换执行顺序，先打印日志，再获取飞行时间，怎么办？ 有人说，再新建一个类 <code>Bird4</code> 继承 <code>Bird</code>，打印日志。再新建一个类 <code>Bird5</code> 继承 <code>Bird4</code>，获取方法执行时间。</p><p>问题显而易见：使用继承将导致类无限制扩展，同时灵活性也无法获得保障。那么，使用聚合是否可以避免这个问题呢？ 答案是：可以！但我们的类需要稍微改造一下。修改 <code>Bird3</code> 类，将聚合对象 <code>Bird</code> 类型修改为 <code>Flyable</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Bird3</span> <span class="keyword">implements</span> <span class="title">Flyable</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Flyable flyable;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Bird3</span><span class="params">(Flyable flyable)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.flyable = flyable;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fly</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> start = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">        flyable.fly();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">        System.out.println(<span class="string">"Fly time = "</span> + (end - start));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了让你看的更清楚，我将 <code>Bird3</code> 更名为 <code>BirdTimeProxy</code>，即用于获取方法执行时间的代理的意思。同时我们新建 <code>BirdLogProxy</code> 代理类用于打印日志：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BirdLogProxy</span> <span class="keyword">implements</span> <span class="title">Flyable</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Flyable flyable;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BirdLogProxy</span><span class="params">(Flyable flyable)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.flyable = flyable;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fly</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"Bird fly start..."</span>);</span><br><span class="line"></span><br><span class="line">        flyable.fly();</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">"Bird fly end..."</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接下来神奇的事情发生了，如果我们需要先记录日志，再获取飞行时间，可以在调用的地方这么做：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    Bird bird = <span class="keyword">new</span> Bird();</span><br><span class="line">    BirdLogProxy p1 = <span class="keyword">new</span> BirdLogProxy(bird);</span><br><span class="line">    BirdTimeProxy p2 = <span class="keyword">new</span> BirdTimeProxy(p1);</span><br><span class="line"></span><br><span class="line">    p2.fly();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>反过来，可以这么做：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    Bird bird = <span class="keyword">new</span> Bird();</span><br><span class="line">    BirdTimeProxy p2 = <span class="keyword">new</span> BirdTimeProxy(bird);</span><br><span class="line">    BirdLogProxy p1 = <span class="keyword">new</span> BirdLogProxy(p2);</span><br><span class="line"></span><br><span class="line">    p1.fly();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看到这里，有同学可能会有疑问了。虽然现象看起来，聚合可以灵活调换执行顺序。可是，为什么聚合可以做到，而继承不行呢。我们用一张图来解释一下：</p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/161e5ba292275e28.png" alt=""></p><h2 id="静态代理"><a href="#静态代理" class="headerlink" title="静态代理"></a>静态代理</h2><p>接下来，观察上面的类 <code>BirdTimeProxy</code>，在它的 <code>fly</code> 方法中我们直接调用了 <code>flyable-&gt;fly ()</code> 方法。换而言之，<code>BirdTimeProxy</code> 其实代理了传入的 <code>Flyable</code> 对象，这就是典型的静态代理实现。</p><p>从表面上看，静态代理已经完美解决了我们的问题。可是，试想一下，如果我们需要计算 <code>SDK</code> 中 100 个方法的运行时间，同样的代码至少需要重复 100 次，并且创建至少 100 个代理类。往小了说，如果 <code>Bird</code> 类有多个方法，我们需要知道其他方法的运行时间，同样的代码也至少需要重复多次。因此，静态代理至少有以下两个局限性问题：</p><ul><li>如果同时代理多个类，依然会导致类无限制扩展</li><li>如果类中有多个方法，同样的逻辑需要反复实现</li></ul><p>那么，我们是否可以使用同一个代理类来代理任意对象呢？我们以获取方法运行时间为例，是否可以使用同一个类（例如：<code>TimeProxy</code>）来计算任意对象的任一方法的执行时间呢？甚至再大胆一点，代理的逻辑也可以自己指定。比如，获取方法的执行时间，打印日志，这类逻辑都可以自己指定。这就是本文重点探讨的问题，也是最难理解的部分：<strong>动态代理</strong>。</p><h2 id="动态代理"><a href="#动态代理" class="headerlink" title="动态代理"></a>动态代理</h2><p>继续回到上面这个问题：是否可以使用同一个类（例如：<code>TimeProxy</code>）来计算任意对象的任一方法的执行时间呢。</p><p>这个部分需要一定的抽象思维，我想，你脑海中的第一个解决方案应该是使用反射。反射是用于获取已创建实例的方法或者属性，并对其进行调用或者赋值。很明显，在这里，反射解决不了问题。但是，再大胆一点，如果我们可以动态生成 <code>TimeProxy</code> 这个类，并且动态编译。然后，再通过反射创建对象并加载到内存中，不就实现了对任意对象进行代理了吗？为了防止你依然一头雾水，我们用一张图来描述接下来要做什么：</p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/161e5ba2923a81ba.png" alt=""></p><p>动态生成 <code>Java</code> 源文件并且排版是一个非常繁琐的工作，为了简化操作，我们使用 <a href="https://github.com/square/javapoet" target="_blank" rel="noopener"><code>JavaPoet</code></a> 这个第三方库帮我们生成 <code>TimeProxy</code> 的源码。希望 <code>JavaPoet</code> 不要成为你的负担，不理解 <code>JavaPoet</code> 没有关系，你只要把它当成一个 <code>Java</code> 源码生成工具使用即可。</p><p><strong>PS：</strong>你记住，任何工具库的使用都不会太难，它是为了简化某些操作而出现的，目标是简化而不是繁琐。因此，只要你适应它的规则就轻车熟路了。</p><h3 id="第一步：生成-TimeProxy-源码"><a href="#第一步：生成-TimeProxy-源码" class="headerlink" title="第一步：生成 TimeProxy 源码"></a>第一步：生成 TimeProxy 源码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Proxy</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title">newProxyInstance</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(<span class="string">"TimeProxy"</span>).addSuperinterface(Flyable<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"></span><br><span class="line">        FieldSpec fieldSpec = FieldSpec.builder(Flyable.class, "flyable", Modifier.PRIVATE).build();</span><br><span class="line">        typeSpecBuilder.addField(fieldSpec);</span><br><span class="line"></span><br><span class="line">        MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()</span><br><span class="line">                .addModifiers(Modifier.PUBLIC)</span><br><span class="line">                .addParameter(Flyable.class, "flyable")</span><br><span class="line">                .addStatement(<span class="string">"this.flyable = flyable"</span>)</span><br><span class="line">                .build();</span><br><span class="line">        typeSpecBuilder.addMethod(constructorMethodSpec);</span><br><span class="line"></span><br><span class="line">        Method[] methods = Flyable<span class="class">.<span class="keyword">class</span>.<span class="title">getDeclaredMethods</span>()</span>;</span><br><span class="line">        <span class="keyword">for</span> (Method method : methods) &#123;</span><br><span class="line">            MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())</span><br><span class="line">                    .addModifiers(Modifier.PUBLIC)</span><br><span class="line">                    .addAnnotation(Override<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">                    .<span class="title">returns</span>(<span class="title">method</span>.<span class="title">getReturnType</span>())</span></span><br><span class="line">                    .addStatement("long start = $T.currentTimeMillis()", System.class)</span><br><span class="line">                    .addCode(<span class="string">"\n"</span>)</span><br><span class="line">                    .addStatement(<span class="string">"this.flyable."</span> + method.getName() + <span class="string">"()"</span>)</span><br><span class="line">                    .addCode(<span class="string">"\n"</span>)</span><br><span class="line">                    .addStatement(<span class="string">"long end = $T.currentTimeMillis()"</span>, System<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line">                    .addStatement("$T.out.println(\"Fly Time =\" + (end - start))", System.class)</span><br><span class="line">                    .build();</span><br><span class="line">            typeSpecBuilder.addMethod(methodSpec);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        JavaFile javaFile = JavaFile.builder(<span class="string">"com.youngfeng.proxy"</span>, typeSpecBuilder.build()).build();</span><br><span class="line">        <span class="comment">// 为了看的更清楚，我将源码文件生成到桌面</span></span><br><span class="line">        javaFile.writeTo(<span class="keyword">new</span> File(<span class="string">"/Users/ouyangfeng/Desktop/"</span>));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>main</code> 方法中调用 <code>Proxy.newProxyInstance ()</code>，你将看到桌面已经生成了 <code>TimeProxy.java</code> 文件，生成的内容如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.youngfeng.proxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.Override;</span><br><span class="line"><span class="keyword">import</span> java.lang.System;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TimeProxy</span> <span class="keyword">implements</span> <span class="title">Flyable</span> </span>&#123;</span><br><span class="line">  <span class="keyword">private</span> Flyable flyable;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="title">TimeProxy</span><span class="params">(Flyable flyable)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.flyable = flyable;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fly</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">long</span> start = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.flyable.fly();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">"Fly Time ="</span> + (end - start));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="第二步：编译-TimeProxy-源码"><a href="#第二步：编译-TimeProxy-源码" class="headerlink" title="第二步：编译 TimeProxy 源码"></a>第二步：编译 TimeProxy 源码</h3><p>编译 <code>TimeProxy</code> 源码我们直接使用 <code>JDK</code> 提供的编译工具即可，为了使你看起来更清晰，我使用一个新的辅助类来完成编译操作：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JavaCompiler</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">compile</span><span class="params">(File javaFile)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">        javax.tools.JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();</span><br><span class="line">        StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(<span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line">        Iterable iterable = fileManager.getJavaFileObjects(javaFile);</span><br><span class="line">        javax.tools.JavaCompiler.CompilationTask task = javaCompiler.getTask(<span class="keyword">null</span>, fileManager, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>, iterable);</span><br><span class="line">        task.call();</span><br><span class="line">        fileManager.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>Proxy-&gt;newProxyInstance ()</code> 方法中调用该方法，编译顺利完成：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 为了看的更清楚，我将源码文件生成到桌面</span></span><br><span class="line">String sourcePath = <span class="string">"/Users/ouyangfeng/Desktop/"</span>;</span><br><span class="line">javaFile.writeTo(<span class="keyword">new</span> File(sourcePath));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译</span></span><br><span class="line">JavaCompiler.compile(<span class="keyword">new</span> File(sourcePath + <span class="string">"/com/youngfeng/proxy/TimeProxy.java"</span>));</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/161e5ba2923ae39e.png" alt=""></p><h3 id="第三步：加载到内存中并创建对象"><a href="#第三步：加载到内存中并创建对象" class="headerlink" title="第三步：加载到内存中并创建对象"></a>第三步：加载到内存中并创建对象</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">URL[] urls = <span class="keyword">new</span> URL[] &#123;<span class="keyword">new</span> URL(<span class="string">"file:/"</span> + sourcePath)&#125;;</span><br><span class="line">URLClassLoader classLoader = <span class="keyword">new</span> URLClassLoader(urls);</span><br><span class="line"></span><br><span class="line">Class clazz = classLoader.loadClass(<span class="string">"com.youngfeng.proxy.TimeProxy"</span>);</span><br><span class="line">Constructor constructor = clazz.getConstructor(Flyable<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">Flyable flyable = (Flyable) constructor.newInstance(<span class="keyword">new</span> Bird());</span><br><span class="line"></span><br><span class="line">flyable.fly();</span><br></pre></td></tr></table></figure><p>通过以上三个步骤，我们至少解决了下面两个问题：</p><ul><li>不再需要手动创建 <code>TimeProxy</code></li><li>可以代理任意实现了 <code>Flyable</code> 接口的类对象，并获取接口方法的执行时间</li></ul><p>可是，说好的任意对象呢？</p><h3 id="第四步：增加-InvocationHandler-接口"><a href="#第四步：增加-InvocationHandler-接口" class="headerlink" title="第四步：增加 InvocationHandler 接口"></a>第四步：增加 InvocationHandler 接口</h3><p>查看 <code>Proxy-&gt;newProxyInstance ()</code> 的源码，代理类继承的接口我们是写死的，为了增加灵活性，我们将接口类型作为参数传入：</p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/161e5ba293f2e59e.png" alt=""></p><p>接口的灵活性问题解决了，<code>TimeProxy</code> 的局限性依然存在，它只能用于获取方法的执行时间，而如果要在方法执行前后打印日志则需要重新创建一个代理类，显然这是不妥的！</p><p>为了增加控制的灵活性，我们考虑针将代理的处理逻辑也抽离出来（这里的处理就是打印方法的执行时间）。新增 <code>InvocationHandler</code> 接口，用于处理自定义逻辑：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">InvocationHandler</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>想象一下，如果客户程序员需要对代理类进行自定义的处理，只要实现该接口，并在 <code>invoke</code> 方法中进行相应的处理即可。这里我们在接口中设置了三个参数（其实也是为了和 JDK 源码保持一致）：</p><ul><li><strong>proxy：</strong>这个参数指定动态生成的代理类，这里是 <code>TimeProxy</code></li><li><strong>method：</strong>这个参数表示传入接口中的所有 <code>Method</code> 对象</li><li><strong>args：</strong>这个参数对应当前 <code>method</code> 方法中的参数</li></ul><p>引入了 <code>InvocationHandler</code> 接口之后，我们的调用顺序应该变成了这样：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">MyInvocationHandler handler = <span class="keyword">new</span> MyInvocationHandler();</span><br><span class="line">Flyable proxy = Proxy.newProxyInstance(Flyable<span class="class">.<span class="keyword">class</span>, <span class="title">handler</span>)</span>;</span><br><span class="line"></span><br><span class="line">proxy.fly();</span><br></pre></td></tr></table></figure><p>方法执行流：<code>proxy.fly() =&gt; handler.invoke()</code></p><p>为此，我们需要在 <code>Proxy.newProxyInstance ()</code> 方法中做如下改动：</p><ul><li>在 <code>newProxyInstance</code> 方法中传入 <code>InvocationHandler</code></li><li>在生成的代理类中增加成员变量 <code>handler</code></li><li>在生成的代理类方法中，调用 <code>invoke</code> 方法</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title">newProxyInstance</span><span class="params">(Class inf, InvocationHandler handler)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(<span class="string">"TimeProxy"</span>)</span><br><span class="line">                .addModifiers(Modifier.PUBLIC)</span><br><span class="line">                .addSuperinterface(inf);</span><br><span class="line"></span><br><span class="line">        FieldSpec fieldSpec = FieldSpec.builder(InvocationHandler.class, "handler", Modifier.PRIVATE).build();</span><br><span class="line">        typeSpecBuilder.addField(fieldSpec);</span><br><span class="line"></span><br><span class="line">        MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()</span><br><span class="line">                .addModifiers(Modifier.PUBLIC)</span><br><span class="line">                .addParameter(InvocationHandler.class, "handler")</span><br><span class="line">                .addStatement(<span class="string">"this.handler = handler"</span>)</span><br><span class="line">                .build();</span><br><span class="line"></span><br><span class="line">        typeSpecBuilder.addMethod(constructorMethodSpec);</span><br><span class="line"></span><br><span class="line">        Method[] methods = inf.getDeclaredMethods();</span><br><span class="line">        <span class="keyword">for</span> (Method method : methods) &#123;</span><br><span class="line">            MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())</span><br><span class="line">                    .addModifiers(Modifier.PUBLIC)</span><br><span class="line">                    .addAnnotation(Override<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">                    .<span class="title">returns</span>(<span class="title">method</span>.<span class="title">getReturnType</span>())</span></span><br><span class="line">                    .addCode("try &#123;\n")</span><br><span class="line">                    .addStatement(<span class="string">"\t$T method = "</span> + inf.getName() + <span class="string">".class.getMethod(\""</span> + method.getName() + <span class="string">"\")"</span>, Method<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">                    // 为了简单起见，这里参数直接写死为空</span></span><br><span class="line">                    .addStatement("\tthis.handler.invoke(this, method, null)")</span><br><span class="line">                    .addCode(<span class="string">"&#125; catch(Exception e) &#123;\n"</span>)</span><br><span class="line">                    .addCode(<span class="string">"\te.printStackTrace();\n"</span>)</span><br><span class="line">                    .addCode(<span class="string">"&#125;\n"</span>)</span><br><span class="line">                    .build();</span><br><span class="line">            typeSpecBuilder.addMethod(methodSpec);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        JavaFile javaFile = JavaFile.builder(<span class="string">"com.youngfeng.proxy"</span>, typeSpecBuilder.build()).build();</span><br><span class="line">        <span class="comment">// 为了看的更清楚，我将源码文件生成到桌面</span></span><br><span class="line">        String sourcePath = <span class="string">"/Users/ouyangfeng/Desktop/"</span>;</span><br><span class="line">        javaFile.writeTo(<span class="keyword">new</span> File(sourcePath));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 编译</span></span><br><span class="line">        JavaCompiler.compile(<span class="keyword">new</span> File(sourcePath + <span class="string">"/com/youngfeng/proxy/TimeProxy.java"</span>));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 使用反射load到内存</span></span><br><span class="line">        URL[] urls = <span class="keyword">new</span> URL[] &#123;<span class="keyword">new</span> URL(<span class="string">"file:"</span> + sourcePath)&#125;;</span><br><span class="line">        URLClassLoader classLoader = <span class="keyword">new</span> URLClassLoader(urls);</span><br><span class="line">        Class clazz = classLoader.loadClass(<span class="string">"com.youngfeng.proxy.TimeProxy"</span>);</span><br><span class="line">        Constructor constructor = clazz.getConstructor(InvocationHandler<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">        Object obj = constructor.newInstance(handler);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> obj;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码你可能看起来比较吃力，我们直接调用该方法，查看最后生成的源码。在 <code>main</code> 方法中测试 <code>newProxyInstance</code> 查看生成的 <code>TimeProxy</code> 源码：</p><p><strong>测试代码</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Proxy.newProxyInstance(Flyable<span class="class">.<span class="keyword">class</span>, <span class="title">new</span> <span class="title">MyInvocationHandler</span>(<span class="title">new</span> <span class="title">Bird</span>()))</span>;</span><br></pre></td></tr></table></figure><p><strong>生成的 TimeProxy.java 源码</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.youngfeng.proxy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.Override;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TimeProxy</span> <span class="keyword">implements</span> <span class="title">Flyable</span> </span>&#123;</span><br><span class="line">  <span class="keyword">private</span> InvocationHandler handler;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="title">TimeProxy</span><span class="params">(InvocationHandler handler)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.handler = handler;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fly</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">    Method method = com.youngfeng.proxy.Flyable.class.getMethod("fly");</span><br><span class="line">    <span class="keyword">this</span>.handler.invoke(<span class="keyword">this</span>, method, <span class="keyword">null</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span>(Exception e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>MyInvocationHandler.java</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyInvocationHandler</span> <span class="keyword">implements</span> <span class="title">InvocationHandler</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Bird bird;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">MyInvocationHandler</span><span class="params">(Bird bird)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.bird = bird;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> start = System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            method.invoke(bird, <span class="keyword">new</span> Object[] &#123;&#125;);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IllegalAccessException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InvocationTargetException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">long</span> end = System.currentTimeMillis();</span><br><span class="line">        System.out.println(<span class="string">"Fly time = "</span> + (end - start));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此，整个方法栈的调用栈变成了这样：</p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/161e5ba294367cdd.png" alt=""></p><p>看到这里，估计很多同学已经晕了，在静态代理部分，我们在代理类中传入了被代理对象。可是，使用 <code>newProxyInstance</code> 生成动态代理对象的时候，我们居然不再需要传入被代理对象了。我们传入了的实际对象是 <code>InvocationHandler</code> 实现类的实例，这看起来有点像生成了 <code>InvocationHandler</code> 的代理对象，在动态生成的代理类的任意方法中都会间接调用 <code>InvocationHandler-&gt;invoke (proxy, method, args)</code> 方法。</p><p>其实的确是这样。<code>TimeProxy</code> 真正代理的对象就是 <code>InvocationHandler</code>，不过这里设计的巧妙之处在于，<code>InvocationHandler</code> 是一个接口，真正的实现由用户指定。另外，在每一个方法执行的时候，<code>invoke</code> 方法都会被调用 ，这个时候如果你需要对某个方法进行自定义逻辑处理，可以根据 <code>method</code> 的特征信息进行判断分别处理。</p><h2 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h2><p>上面这段解释是告诉你在执行 <code>Proxy-&gt;newProxyInstance</code> 方法的时候真正发生的事情，而在实际使用过程中你完全可以忘掉上面的解释。按照设计者的初衷，我们做如下简单归纳：</p><ul><li><code>Proxy-&gt;newProxyInstance (infs, handler)</code> 用于生成代理对象</li><li><code>InvocationHandler</code>这个接口主要用于自定义代理逻辑处理</li><li>为了完成对被代理对象的方法拦截，我们需要在 <code>InvocationHandler</code> 对象中传入被代理对象实例。</li></ul><p>查看上面的代码，你可以看到我将 <code>Bird</code> 实例已经传入到了 <code>MyInvocationHandler</code> 中，原因就是第三点。</p><p>这样设计有什么好处呢？有人说，我们大费周章，饶了一大圈，最终变成了这个样子，到底图什么呢？</p><p>想象一下，到此为止，如果我们还需要对其它任意对象进行代理，是否还需要改动 <code>newProxyInstance</code> 方法的源码，答案是：完全不需要！</p><p>只要你在 <code>newProxyInstance</code> 方法中指定代理需要实现的接口，指定用于自定义处理的 <code>InvocationHandler</code> 对象，整个代理的逻辑处理都在你自定义的 <code>InvocationHandler</code> 实现类中进行处理。至此，而我们终于可以从不断地写代理类用于实现自定义逻辑的重复工作中解放出来了，从此需要做什么，交给 <code>InvocationHandler</code>。</p><p>事实上，我们之前给自己定下的目标 “使用同一个类来计算任意对象的任一方法的执行时间” 已经实现了。严格来说，是我们超额完成了任务，<code>TimeProxy</code> 不仅可以计算方法执行的时间，也可以打印方法执行日志，这完全取决于你的 <code>InvocationHandler</code> 接口实现。因此，这里取名为 <code>TimeProxy</code> 其实已经不合适了。我们可以修改为和 <code>JDK</code> 命名一致，即 <code>$Proxy0</code>，感兴趣的同学请自行实践。</p><h2 id="JDK-实现揭秘"><a href="#JDK-实现揭秘" class="headerlink" title="JDK 实现揭秘"></a>JDK 实现揭秘</h2><p>通过上面的这些步骤，我们完成了一个简易的仿 <code>JDK</code> 实现的动态代理逻辑。接下来，我们一起来看一看 <code>JDK</code> 实现的动态代理和我们到底有什么不同。</p><p><strong>Proxy.java</strong></p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/161e5ba294623577.png" alt=""></p><p><strong>InvocationHandler</strong></p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/161e5ba2d87bf2f1.png" alt=""></p><p>可以看到，官方版本 <code>Proxy</code> 类提供的方法多一些，而我们主要使用的接口 <code>newProxyInstance</code> 参数也和我们设计的不太一样。这里给大家简单解释一下，每个参数的意义：</p><ul><li><strong>Classloader：</strong>类加载器，你可以使用自定义的类加载器，我们的实现版本为了简化，直接在代码中写死了 <code>Classloader</code>。</li><li><strong>Class&lt;?&gt;[]：</strong>第二个参数也和我们的实现版本不一致，这个其实很容易理解，我们应该允许我们自己实现的代理类同时实现多个接口。前面设计只传入一个接口，只是为了简化实现，让你专注核心逻辑实现而已。</li></ul><p>最后一个参数就不用说了，和我们实现的版本完全是一样的。</p><p>仔细观察官方版本的 <code>InvocationHandler</code>，它和我们自己的实现的版本也有一个细微的差别：官方版本 <code>invoke</code> 方法有返回值，而我们的版本中是没有返回值的。那么，返回值到底有什么作用呢？直接来看官方文档：</p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/161e5ba2d92efdbe.png" alt=""></p><p><em>核心思想：这里的返回值类型必须和传入接口的返回值类型一致，或者与其封装对象的类型一致。</em></p><p>遗憾的是，这里并没有说明返回值的用途，其实这里稍微发挥一下想象力就知道了。在我们的版本实现中，<code>Flyable</code> 接口的所有方法都是没有返回值的，问题是，如果有返回值呢？是的，你没有猜错，这里的 <code>invoke</code> 方法对应的就是传入接口中方法的返回值。</p><h2 id="答疑解惑"><a href="#答疑解惑" class="headerlink" title="答疑解惑"></a>答疑解惑</h2><h3 id="invoke-方法的第一个参数-proxy-到底有什么作用？"><a href="#invoke-方法的第一个参数-proxy-到底有什么作用？" class="headerlink" title="invoke 方法的第一个参数 proxy 到底有什么作用？"></a>invoke 方法的第一个参数 proxy 到底有什么作用？</h3><p>这个问题其实也好理解，如果你的接口中有方法需要返回自身，如果在 <code>invoke</code> 中没有传入这个参数，将导致实例无法正常返回。在这种场景中，<code>proxy</code> 的用途就表现出来了。简单来说，这其实就是最近非常火的链式编程的一种应用实现。</p><h3 id="动态代理到底有什么用？"><a href="#动态代理到底有什么用？" class="headerlink" title="动态代理到底有什么用？"></a>动态代理到底有什么用？</h3><p>学习任何一门技术，一定要问一问自己，这到底有什么用。其实，在这篇文章的讲解过程中，我们已经说出了它的主要用途。你发现没，使用动态代理我们居然可以在不改变源码的情况下，直接在方法中插入自定义逻辑。这有点不太符合我们的一条线走到底的编程逻辑，这种编程模型有一个专业名称叫 <code>AOP</code>。所谓的 <code>AOP</code>，就像刀一样，抓住时机，趁机插入。</p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/161e5ba2dfeb24bb.png" alt=""></p><p>基于这样一种动态特性，我们可以用它做很多事情，例如：</p><ul><li>事务提交或回退（<code>Web</code> 开发中很常见）</li><li>权限管理</li><li>自定义缓存逻辑处理</li><li><code>SDK Bug</code> 修复 …</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>到此为止，关于动态代理的所有讲解已经结束了，原谅我使用了一个诱导性的标题 “骗” 你进来阅读这篇文章。如果你不是一个久经沙场的 “老司机”，10 分钟完全看懂动态代理设计模式还是有一定难度的。但即使没有看懂也没关系，如果你在第一次阅读完这篇文章后依然一头雾水，就不妨再仔细阅读一次。在阅读的过程中，一定要跟着文章思路去敲代码。反反复复，一定会看懂的。我在刚刚学习动态代理设计模式的时候就反复看了不下 5 遍，并且亲自敲代码实践了多次。</p><p>为了让你少走弯路，我认为看懂这篇文章，你至少需要学习以下知识点：</p><ul><li>至少已经理解了面向对象语言的多态特性</li><li>了解简单的反射用法</li><li>会简单使用 <code>JavaPoet</code> 生成 <code>Java</code> 源码</li></ul>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://juejin.im/post/5a99048a6fb9a028d5668e62&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;原文地址&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;动态代理是 &lt;code&gt;Java&lt;/code&gt; 语言中非常经典的一种设计模式，也是所有设计模式中最难理解的一种。本文将通过一个简单的例子模拟 &lt;code&gt;JDK&lt;/code&gt; 动态代理实现，让你彻底明白动态代理设计模式的本质。&lt;/p&gt;
&lt;h2 id=&quot;什么是代理&quot;&gt;&lt;a href=&quot;#什么是代理&quot; class=&quot;headerlink&quot; title=&quot;什么是代理&quot;&gt;&lt;/a&gt;什么是代理&lt;/h2&gt;&lt;p&gt;从字面意思来看，代理比较好理解，无非就是代为处理的意思。举个例子，你在上大学的时候，总是喜欢逃课。因此，你拜托你的同学帮你答到，而自己却窝在宿舍玩游戏… 你的这个同学恰好就充当了代理的作用，代替你去上课。&lt;/p&gt;
    
    </summary>
    
      <category term="Java开发技术" scheme="https://ehlxr.me/categories/Java%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Java" scheme="https://ehlxr.me/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>三十而立之年</title>
    <link href="https://ehlxr.me/2019/12/31/thirty-years-old/"/>
    <id>https://ehlxr.me/2019/12/31/thirty-years-old/</id>
    <published>2019-12-31T17:35:24.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>又一年即将结束，时间过得真的很快，快到不敢静下来细细回忆。小时候觉得二十多岁的人年纪好大，再后来觉得三十岁应该离自己很远吧，没想到自己的三十而立之年来的这么“快”，总感觉自己还很是个小孩，总感觉自己还没长大，实际已经到了上有老下有小的年纪…</p><p>19 年最开心的事儿就是当了爸爸，真心的感谢媳妇艰辛付出，感激上天给我们最好的礼物。最不幸的事是父亲得了一场大病，家人辛苦奔波不说父亲也是受尽了病痛的折磨，医院真是最不愿意去的地方。有时候天真的想，如果能让家人健健康康无病无痛，付出多少都会有人愿意吧！</p><a id="more"></a><p>在北京已经整整 8 年了，虽不说劳苦奔波，但每天也很疲惫，一直想着回西安去好好安顿自己的家，尤其是面对不管奋斗多久也在这个城市安不了一个家的现实的时候。但有孩子之后经济压力徒增，回去之后恐怕入不敷出，一年一年的过的很快，那就再在北京待一年再看情况吧！为了能够不焦虑的回西安 20 年得让自己有点儿改观了。</p><p>19 年最后一天也快下班了，20 年继续加油吧！</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;又一年即将结束，时间过得真的很快，快到不敢静下来细细回忆。小时候觉得二十多岁的人年纪好大，再后来觉得三十岁应该离自己很远吧，没想到自己的三十而立之年来的这么“快”，总感觉自己还很是个小孩，总感觉自己还没长大，实际已经到了上有老下有小的年纪…&lt;/p&gt;
&lt;p&gt;19 年最开心的事儿就是当了爸爸，真心的感谢媳妇艰辛付出，感激上天给我们最好的礼物。最不幸的事是父亲得了一场大病，家人辛苦奔波不说父亲也是受尽了病痛的折磨，医院真是最不愿意去的地方。有时候天真的想，如果能让家人健健康康无病无痛，付出多少都会有人愿意吧！&lt;/p&gt;
    
    </summary>
    
      <category term="年末总结" scheme="https://ehlxr.me/categories/%E5%B9%B4%E6%9C%AB%E6%80%BB%E7%BB%93/"/>
    
    
      <category term="年末总结" scheme="https://ehlxr.me/tags/%E5%B9%B4%E6%9C%AB%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>Rust 中字符串总结</title>
    <link href="https://ehlxr.me/2019/08/04/string-in-rust/"/>
    <id>https://ehlxr.me/2019/08/04/string-in-rust/</id>
    <published>2019-08-04T11:06:43.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p><code>Rust</code> 中字符串和 <code>Java</code> 和 <code>Go</code> 中的表示有很大的区别，刚开始接触的时候有点儿懵，今天花点时间总结备忘一下。</p><p><code>Rust</code> 字符串有两种形式：<code>str</code> 和 <code>String</code>，<code>str</code> 是内置的原始数据类型，通常是以借用的形式（<code>&amp;str</code> 字符串 <code>slice</code>）使用，而 <code>String</code> 是标准库提供的一种结构体，内部存储一个 <code>u8</code> 类型的 <code>Vec</code>：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="class"><span class="keyword">struct</span> <span class="title">String</span></span> &#123;</span><br><span class="line">    vec: <span class="built_in">Vec</span>&lt;<span class="built_in">u8</span>&gt;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><a id="more"></a><h2 id="str"><a href="#str" class="headerlink" title="str"></a>str</h2><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> s = <span class="string">"hello world!"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// let s: &amp;'static str = "hello world!";</span></span><br></pre></td></tr></table></figure><p><code>s</code> 变量是一个 <code>&amp;str</code> 类型，不可变的字符串，也可称为字面量，文本被直接储存在程序的二进制文件中，拥有固态生命周期（<code>&#39;static</code>），是 <code>let s: &amp;&#39;static str = &quot;hello world!&quot;;</code> 的简写定义方式。</p><h2 id="String"><a href="#String" class="headerlink" title="String"></a>String</h2><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">mut</span> s1 = <span class="built_in">String</span>::new();</span><br><span class="line"><span class="comment">// let mut s1 = String::from("hello world!");</span></span><br><span class="line">s1.push_str(<span class="string">"hello world!"</span>);</span><br><span class="line">s1.push_str(<span class="string">" welcome."</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">println!</span>(<span class="string">"&#123;&#125;"</span>, s1);</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// hello world! welcome.</span></span><br></pre></td></tr></table></figure><p><code>String</code> 是可变的、有所有权的、堆上分配的 <code>UTF-8</code> 的字节缓冲区。</p><h2 id="相互转换"><a href="#相互转换" class="headerlink" title="相互转换"></a>相互转换</h2><ul><li>&amp;str -&gt; String</li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> s = <span class="string">"hello world!"</span>; <span class="comment">// variable s: &amp;str</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> s1 = <span class="built_in">String</span>::from(s); <span class="comment">// variable s1: String</span></span><br><span class="line"><span class="keyword">let</span> s1 = s.to_string();</span><br><span class="line"><span class="keyword">let</span> s1 = s.to_owned();</span><br></pre></td></tr></table></figure><ul><li>String -&gt; &amp;str</li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> s = <span class="built_in">String</span>::from(<span class="string">"hello world!"</span>); <span class="comment">// variable s: String</span></span><br><span class="line"><span class="keyword">let</span> s1 = s.as_str(); <span class="comment">// variable s1: &amp;str</span></span><br><span class="line"><span class="keyword">let</span> s1 = &amp;s[..]; <span class="comment">// 相当于：&amp;s[0..s.len()];</span></span><br></pre></td></tr></table></figure><p><code>&amp;String</code> 可以当做是 <code>&amp;str</code>，例如：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> s = <span class="built_in">String</span>::from(<span class="string">"hello world!"</span>);</span><br><span class="line">    foo(&amp;s);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">foo</span></span>(s: &amp;<span class="built_in">str</span>) &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">"&#123;&#125;"</span>, s);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="号运算"><a href="#号运算" class="headerlink" title="+ 号运算"></a>+ 号运算</h2><p>字符串 <code>+</code> 号运算是 <code>String</code> 的一个内联函数，定义如下：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">impl</span> Add&lt;&amp;<span class="built_in">str</span>&gt; <span class="keyword">for</span> <span class="built_in">String</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">type</span> <span class="title">Output</span></span> = <span class="built_in">String</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">#[inline]</span></span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">add</span></span>(<span class="keyword">mut</span> <span class="keyword">self</span>, other: &amp;<span class="built_in">str</span>) -&gt; <span class="built_in">String</span> &#123;</span><br><span class="line">        <span class="keyword">self</span>.push_str(other);</span><br><span class="line">        <span class="keyword">self</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以两个字面量字符串（<code>&amp;str</code>）不能使用 <code>+</code>，例如：<code>&quot;hello &quot; + &quot;world&quot;;</code> 会报错误： <code>&#39;+&#39; cannot be used to concatenate two &#39;&amp;str&#39; strings</code>。</p><p>根据 <code>+</code> 的定义，一个可变的 <code>String</code> 字符串进行 <code>+</code> 后会失去所有权，例如：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">mut</span> s = <span class="built_in">String</span>::from(<span class="string">"hello world!"</span>);</span><br><span class="line"><span class="keyword">let</span> s1 = s + <span class="string">" welcome."</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// println!("&#123;&#125;, &#123;&#125;", s, s1);</span></span><br><span class="line"><span class="comment">//                    ^ value borrowed here after move</span></span><br></pre></td></tr></table></figure><p>以上代码会出现以下警告：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F;   |</span><br><span class="line">&#x2F;&#x2F;   |     let mut s &#x3D; String::from(&quot;hello world!&quot;);</span><br><span class="line">&#x2F;&#x2F;   |         ----^</span><br><span class="line">&#x2F;&#x2F;   |         |</span><br><span class="line">&#x2F;&#x2F;   |         help: remove this &#96;mut&#96;</span><br><span class="line">&#x2F;&#x2F;   |</span><br><span class="line">&#x2F;&#x2F;   &#x3D; note: #[warn(unused_mut)] on by default</span><br></pre></td></tr></table></figure><p>查阅说是有 <code>#[warn(unused_mut)]</code> 注解后 <strong>variable does not need to be mutable</strong>。去掉以上代码中 <code>String</code> 定义时候用以标识可变的 <code>mut</code> 关键字就可以了。不太明白为啥可以这样，待日后详查。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;code&gt;Rust&lt;/code&gt; 中字符串和 &lt;code&gt;Java&lt;/code&gt; 和 &lt;code&gt;Go&lt;/code&gt; 中的表示有很大的区别，刚开始接触的时候有点儿懵，今天花点时间总结备忘一下。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Rust&lt;/code&gt; 字符串有两种形式：&lt;code&gt;str&lt;/code&gt; 和 &lt;code&gt;String&lt;/code&gt;，&lt;code&gt;str&lt;/code&gt; 是内置的原始数据类型，通常是以借用的形式（&lt;code&gt;&amp;amp;str&lt;/code&gt; 字符串 &lt;code&gt;slice&lt;/code&gt;）使用，而 &lt;code&gt;String&lt;/code&gt; 是标准库提供的一种结构体，内部存储一个 &lt;code&gt;u8&lt;/code&gt; 类型的 &lt;code&gt;Vec&lt;/code&gt;：&lt;/p&gt;
&lt;figure class=&quot;highlight rust&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;class&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;String&lt;/span&gt;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    vec: &lt;span class=&quot;built_in&quot;&gt;Vec&lt;/span&gt;&amp;lt;&lt;span class=&quot;built_in&quot;&gt;u8&lt;/span&gt;&amp;gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
      <category term="Rust" scheme="https://ehlxr.me/categories/Rust/"/>
    
    
      <category term="Rust" scheme="https://ehlxr.me/tags/Rust/"/>
    
  </entry>
  
  <entry>
    <title>按比例控制流量的一种实现</title>
    <link href="https://ehlxr.me/2019/07/19/control-traffic-by-rate/"/>
    <id>https://ehlxr.me/2019/07/19/control-traffic-by-rate/</id>
    <published>2019-07-19T09:51:25.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>网关做灰度的时候，要控制流量的比例，比如 3:7 的分发流量到两个不同版本的服务上去。刚开始的想法是每次流量过来生成 100 以内的随机数，随机数落在那个区间就转到那个版本的服务上去，但是发现这样无法较精准的保证 3:7 的比例，因为有可能某段时间内生成的随机数大范围的落在某个区间内，比如请求了 100 次，每次生成的随机数都是大于 30 的，这样 70% 比例的服务就承受了 100% 的流量。</p><p>接下来想到了第二种解决方案，能够保证 10（基数） 倍的流量比例正好是 3:7，思路如下：</p><p>1、生成 0 - 99 的数组（集合）<br>2、打乱数组（集合）的顺序，为了防止出现某比例的流量集中出现<br>3、全局的计数器，要考虑原子性<br>4、从数组（集合）中取出计数器和 100 取余后位置的值<br>5、判断取到的值落在那个区间</p><a id="more"></a><p>以下是 Java 版本的 2:3:5 比例分流的简单实现：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.github.ehlxr.rate;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.Collections;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.atomic.AtomicInteger;</span><br><span class="line"><span class="keyword">import</span> java.util.stream.IntStream;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 按比例控制流量</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> ehlxr</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2019-07-19.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RateBarrier</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> AtomicInteger op = <span class="keyword">new</span> AtomicInteger(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; source;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> base;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">rate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> source.get(op.incrementAndGet() % base);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">RateBarrier</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">RateBarrier</span><span class="params">(<span class="keyword">int</span> base)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.base = base;</span><br><span class="line"></span><br><span class="line">        source = <span class="keyword">new</span> ArrayList&lt;&gt;(base);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; base; i++) &#123;</span><br><span class="line">            source.add(i);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 打乱集合顺序</span></span><br><span class="line">        Collections.shuffle(source);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        RateBarrier rateBarrier = <span class="keyword">new</span> RateBarrier(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">        IntStream.range(<span class="number">0</span>, <span class="number">20</span>).parallel().forEach(i -&gt; &#123;</span><br><span class="line">            <span class="keyword">int</span> rate = rateBarrier.rate();</span><br><span class="line">            <span class="keyword">if</span> (rate &lt; <span class="number">2</span>) &#123;</span><br><span class="line">                System.out.println(<span class="string">"this is on 2"</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (rate &lt; <span class="number">5</span>) &#123;</span><br><span class="line">                System.out.println(<span class="string">"this is on 3"</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                System.out.println(<span class="string">"this is on 5"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// final Thread[] threads = new Thread[20];</span></span><br><span class="line">        <span class="comment">// for (int i = 0; i &lt; threads.length; i++) &#123;</span></span><br><span class="line">        <span class="comment">//     threads[i] = new Thread(() -&gt; &#123;</span></span><br><span class="line">        <span class="comment">//         if (rateBarrier.allow()) &#123;</span></span><br><span class="line">        <span class="comment">//             System.out.println("this is on 3");</span></span><br><span class="line">        <span class="comment">//         &#125; else &#123;</span></span><br><span class="line">        <span class="comment">//             System.out.println("this is on 7");</span></span><br><span class="line">        <span class="comment">//         &#125;</span></span><br><span class="line">        <span class="comment">//     &#125;);</span></span><br><span class="line">        <span class="comment">//     threads[i].start();</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line">        <span class="comment">//</span></span><br><span class="line">        <span class="comment">// for (Thread t : threads) &#123;</span></span><br><span class="line">        <span class="comment">//     try &#123;</span></span><br><span class="line">        <span class="comment">//         t.join();</span></span><br><span class="line">        <span class="comment">//     &#125; catch (InterruptedException e) &#123;</span></span><br><span class="line">        <span class="comment">//         e.printStackTrace();</span></span><br><span class="line">        <span class="comment">//     &#125;</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">this is on 3</span></span><br><span class="line"><span class="comment">this is on 3</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">this is on 2</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">this is on 3</span></span><br><span class="line"><span class="comment">this is on 3</span></span><br><span class="line"><span class="comment">this is on 3</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">this is on 2</span></span><br><span class="line"><span class="comment">this is on 2</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">this is on 3</span></span><br><span class="line"><span class="comment">this is on 2</span></span><br><span class="line"><span class="comment">this is on 5</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>以下是 Golang 版本 2:3:5 比例分流的简单实现：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">"fmt"</span></span><br><span class="line"><span class="string">"math/rand"</span></span><br><span class="line"><span class="string">"sync"</span></span><br><span class="line"><span class="string">"sync/atomic"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> RateBarrier <span class="keyword">struct</span> &#123;</span><br><span class="line">source []<span class="keyword">int</span></span><br><span class="line">op     <span class="keyword">uint64</span></span><br><span class="line">base   <span class="keyword">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewRateBarrier</span><span class="params">(base <span class="keyword">int</span>)</span> *<span class="title">RateBarrier</span></span> &#123;</span><br><span class="line">source := <span class="built_in">make</span>([]<span class="keyword">int</span>, base, base)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; base; i++ &#123;</span><br><span class="line">source[i] = i</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 随机排序</span></span><br><span class="line">rand.Shuffle(base, <span class="function"><span class="keyword">func</span><span class="params">(i, j <span class="keyword">int</span>)</span></span> &#123;</span><br><span class="line">source[i], source[j] = source[j], source[i]</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> &amp;RateBarrier&#123;</span><br><span class="line">source: source,</span><br><span class="line">base:   base,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *RateBarrier)</span> <span class="title">Rate</span><span class="params">()</span> <span class="title">int</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> b.source[<span class="keyword">int</span>(atomic.AddUint64(&amp;b.op, <span class="number">1</span>))%b.base]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line">wg.Add(<span class="number">20</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2:3:5</span></span><br><span class="line">b := NewRateBarrier(<span class="number">10</span>)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">20</span>; i++ &#123;</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">rate := b.Rate()</span><br><span class="line"><span class="keyword">switch</span> &#123;</span><br><span class="line"><span class="keyword">case</span> rate &lt; <span class="number">2</span>:</span><br><span class="line">fmt.Println(<span class="string">"this is on 20%"</span>)</span><br><span class="line"><span class="keyword">case</span> rate &gt;= <span class="number">2</span> &amp;&amp; rate &lt; <span class="number">5</span>:</span><br><span class="line">fmt.Println(<span class="string">"this is on 30%"</span>)</span><br><span class="line"><span class="keyword">case</span> rate &gt;= <span class="number">5</span>:</span><br><span class="line">fmt.Println(<span class="string">"this is on 50%"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">wg.Done()</span><br><span class="line">&#125;()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">wg.Wait()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">this is on 30%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 30%</span></span><br><span class="line"><span class="comment">this is on 20%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 20%</span></span><br><span class="line"><span class="comment">this is on 30%</span></span><br><span class="line"><span class="comment">this is on 20%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 30%</span></span><br><span class="line"><span class="comment">this is on 30%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 20%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 50%</span></span><br><span class="line"><span class="comment">this is on 30%</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;网关做灰度的时候，要控制流量的比例，比如 3:7 的分发流量到两个不同版本的服务上去。刚开始的想法是每次流量过来生成 100 以内的随机数，随机数落在那个区间就转到那个版本的服务上去，但是发现这样无法较精准的保证 3:7 的比例，因为有可能某段时间内生成的随机数大范围的落在某个区间内，比如请求了 100 次，每次生成的随机数都是大于 30 的，这样 70% 比例的服务就承受了 100% 的流量。&lt;/p&gt;
&lt;p&gt;接下来想到了第二种解决方案，能够保证 10（基数） 倍的流量比例正好是 3:7，思路如下：&lt;/p&gt;
&lt;p&gt;1、生成 0 - 99 的数组（集合）&lt;br&gt;2、打乱数组（集合）的顺序，为了防止出现某比例的流量集中出现&lt;br&gt;3、全局的计数器，要考虑原子性&lt;br&gt;4、从数组（集合）中取出计数器和 100 取余后位置的值&lt;br&gt;5、判断取到的值落在那个区间&lt;/p&gt;
    
    </summary>
    
      <category term="笔记" scheme="https://ehlxr.me/categories/%E7%AC%94%E8%AE%B0/"/>
    
    
      <category term="流控" scheme="https://ehlxr.me/tags/%E6%B5%81%E6%8E%A7/"/>
    
  </entry>
  
  <entry>
    <title>关于跑步</title>
    <link href="https://ehlxr.me/2019/04/21/run/"/>
    <id>https://ehlxr.me/2019/04/21/run/</id>
    <published>2019-04-21T19:47:32.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>天气暖和了，19 年的跑步计划已经在心中盘算好久了，本打算昨天生日的时候去开始的，结果天公不作美，一直在下小雨。</p><p>关于跑步，一开始为了减肥，现在慢慢喜欢上了这项运动。其实跑步很折磨人，总得有个目标才能坚持下来，或是为了能够在朋友圈炫耀，或是享受跑完步之后的大汗淋漓。</p><p>我会下载一个跑步软件，戴上耳机，每完成一公里就会提醒我，我也会心中默默给自己设定个目标，十公里或一个小时，每次跑步过程中我都在和自己较劲，想要放弃的时候心中总会默念，再坚持一下，已经完成了三分之二了，已经完成五分之四了……设了一个目标，总会能够达成，即使过程很艰难，但更喜欢达标后的小小满足感。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;天气暖和了，19 年的跑步计划已经在心中盘算好久了，本打算昨天生日的时候去开始的，结果天公不作美，一直在下小雨。&lt;/p&gt;
&lt;p&gt;关于跑步，一开始为了减肥，现在慢慢喜欢上了这项运动。其实跑步很折磨人，总得有个目标才能坚持下来，或是为了能够在朋友圈炫耀，或是享受跑完步之后的大汗
      
    
    </summary>
    
      <category term="杂谈" scheme="https://ehlxr.me/categories/%E6%9D%82%E8%B0%88/"/>
    
    
      <category term="杂谈" scheme="https://ehlxr.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title>有人把生活过成了诗，有人却在彷徨挣扎…</title>
    <link href="https://ehlxr.me/2019/04/18/essay/"/>
    <id>https://ehlxr.me/2019/04/18/essay/</id>
    <published>2019-04-18T10:16:21.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>有人把生活过成了诗，有人却在彷徨挣扎…</p><p>总有一些人能让自己触动心弦，仔细想，也许他们并没有比自己“能耐”多少，或许只是自己已习惯奔波，没有了理想，活在世俗里无法自拔…</p><p>我想试着重新生活，找回自己喜欢并且想要成为的那个我，也许吧，这一次我能持之以恒。不再愿意立下各种 flag，不是因为害怕被啪啪打脸，反正都是给自己的。</p><p>但是，为什么每次都是想着要改变自己了，真有这么讨厌自己吗？每次都想要改变什么了？给自己制定一套目标，努力克制自己去实现？追寻自己的内心去生活不应该是自己向往的吗，我找不到答案。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;有人把生活过成了诗，有人却在彷徨挣扎…&lt;/p&gt;
&lt;p&gt;总有一些人能让自己触动心弦，仔细想，也许他们并没有比自己“能耐”多少，或许只是自己已习惯奔波，没有了理想，活在世俗里无法自拔…&lt;/p&gt;
&lt;p&gt;我想试着重新生活，找回自己喜欢并且想要成为的那个我，也许吧，这一次我能持之以恒
      
    
    </summary>
    
      <category term="杂谈" scheme="https://ehlxr.me/categories/%E6%9D%82%E8%B0%88/"/>
    
    
      <category term="杂谈" scheme="https://ehlxr.me/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title>可靠消息最终一致性分布式事务实现方案</title>
    <link href="https://ehlxr.me/2019/01/25/eventually-consistency/"/>
    <id>https://ehlxr.me/2019/01/25/eventually-consistency/</id>
    <published>2019-01-25T15:59:11.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>提到分布式应用，就不得不考虑分布式事务。在分布式事务中，常见的有 <code>CAP</code>，<code>BASE</code> 理论，解决方案也有很多种，比如：<code>2PC</code>、<code>TCC</code> 、最终一致性等。</p><p><code>2PC</code>（两阶段提交）比较适合单块应用，跨多个库的分布式事务。因为严重依赖于数据库层面来搞定复杂的事务，效率很低，绝对不适合高并发的场景，而且，对于微服务而言，不推荐一个服务出现跨多个数据库操作， 如果需要操作其它数据库数据，推荐通过调用别的服务接口来实现。</p><p><code>TCC</code> 属于强一致性事务的方案，适用资金流转业务相关业务，比如：支付、交易等场景。根据 <code>CAP</code> 理论，这种实现需要牺牲可用性。</p><p>如果是一般的分布式事务场景，比如：订单插入之后要调用库存服务更新库存，库存数据没有资金那么的敏感，可以用可靠消息最终一致性方案。</p><p>下面是一种可靠消息最终一致性事务方案的实现流程：</p><a id="more"></a><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/eventually-consistency.png" alt="Eventually Consistency"></p><p>正常流程：</p><ol><li><p><strong>A 系统</strong>发送<code>预发送</code>消息给<strong>消息服务系统</strong>。</p></li><li><p><strong>消息服务系统</strong>存储预发送的消息到消息数据库。</p></li><li><p><strong>消息服务系统</strong>返回存储预发送消息的结果到 <strong>A 系统</strong>。</p></li><li><p>如果第 3 步返回的结果是成功的， <strong>A 系统</strong>则执行业务操作，否则不执行。</p></li><li><p><strong>A 系统</strong>业务操作成功后，通知<strong>消息服务系统</strong> 。</p></li><li><p><strong>消息服务系统</strong>发送消息到 <strong>MQ</strong> ，并且更新<code>预发送</code>消息状态为<code>已发送</code>（但不是<code>已消费</code>）。</p></li><li><p><strong>MQ</strong> 发送消息到 <strong>B 系统</strong>。</p></li><li><p><strong>B 系统</strong>执行业务操作，保证幂等性，防止同一个消息重复执行。</p></li><li><p><strong>B 系统</strong>向 <strong>MQ</strong>  ack 此条消息，并向<strong>消息服务系统</strong>进行确认成功消费消息，让<strong>消息服务系统</strong>将消息状态置为<code>已消费</code>。</p></li><li><p><strong>消息恢复系统</strong>定时去<strong>消息服务系统</strong>查一下消息数据，查看有没有状态为<strong>非</strong><code>已消费</code>（<code>预发送</code>和<code>已发送</code>）状态的<strong>超时</strong>（比如 2 分钟以上还未消费的）消息。</p></li><li><p>如果第 <strong>10</strong> 步发现有<strong>非</strong><code>已消费</code>状态的超时消息，调用 <strong>A 系统</strong>提供的查询接口，查询次条消息对应的业务数据是否为处理成功。</p></li><li><p>如果业务数据是处理成功的状态，那么就再次调用确认并发送消息，即进入第 <strong>6</strong> 步。如果业务数据是处理失败的，那么就调用<strong>消息服务系统</strong>进行删除该条消息数据。</p></li></ol><p>再来看看有错（比如说网络断了或者服务器挂了）的时候，这个系统是怎么保证一致性的：</p><ul><li>第 <strong>1</strong> 步失败，相当于什么都没做。</li><li>第 <strong>2</strong> 步失败，第 <strong>3</strong> 步会返回失败结果，<strong>A 系统</strong>不执行业务操作。</li><li>第 <strong>3</strong> 步失败，<strong>A 系统</strong>不执行业务操作，<strong>消息恢复系统</strong>在第 <strong>12</strong> 步判断业务处理失败。</li><li>第 <strong>4</strong> 步失败，<strong>A 系统</strong>回滚业务，同样<strong>消息恢复系统</strong>在第 <strong>12</strong> 步判断业务处理失败。</li><li>第 <strong>5、6、7、8、9</strong>  步失败，<strong>消息恢复系统</strong>在第 <strong>12</strong> 步判断业务处理成功，重试第 <strong>6</strong> 步直到成功为止。如果在第 <strong>9</strong> 步失败了，<strong>B 系统</strong>会重复消费某条消息，所以 <strong>B 系统</strong>要设计成幂等操作，对于同一操作发起的一次请求或者多次请求的结果是一致的，不会因为多次调用而产生了副作用。</li></ul><p>所以，只要消息数据持久化了，我们就可以假设后面一定会被消费，就算后面挂了一堆东西，但是我们把挂掉的服务再全部启动，这条消息还是会被消费，不会丢失，可以保证最终一致性。</p><p>这种实现方案弱化了对消息中间件（<strong>MQ</strong>）的依赖，选用 <code>RabbitMQ</code> 或者 <code>ActiveMQ</code> 就可以实现。如果使用支持消息事物的 <code>RocketMQ</code> 也可以简化消息恢复系统和消息服务系统。</p><hr>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;提到分布式应用，就不得不考虑分布式事务。在分布式事务中，常见的有 &lt;code&gt;CAP&lt;/code&gt;，&lt;code&gt;BASE&lt;/code&gt; 理论，解决方案也有很多种，比如：&lt;code&gt;2PC&lt;/code&gt;、&lt;code&gt;TCC&lt;/code&gt; 、最终一致性等。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;2PC&lt;/code&gt;（两阶段提交）比较适合单块应用，跨多个库的分布式事务。因为严重依赖于数据库层面来搞定复杂的事务，效率很低，绝对不适合高并发的场景，而且，对于微服务而言，不推荐一个服务出现跨多个数据库操作， 如果需要操作其它数据库数据，推荐通过调用别的服务接口来实现。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TCC&lt;/code&gt; 属于强一致性事务的方案，适用资金流转业务相关业务，比如：支付、交易等场景。根据 &lt;code&gt;CAP&lt;/code&gt; 理论，这种实现需要牺牲可用性。&lt;/p&gt;
&lt;p&gt;如果是一般的分布式事务场景，比如：订单插入之后要调用库存服务更新库存，库存数据没有资金那么的敏感，可以用可靠消息最终一致性方案。&lt;/p&gt;
&lt;p&gt;下面是一种可靠消息最终一致性事务方案的实现流程：&lt;/p&gt;
    
    </summary>
    
      <category term="分布式" scheme="https://ehlxr.me/categories/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
    
      <category term="分布式事务" scheme="https://ehlxr.me/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/"/>
    
      <category term="事务" scheme="https://ehlxr.me/tags/%E4%BA%8B%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>浅谈分布式事务</title>
    <link href="https://ehlxr.me/2019/01/25/distributed-system-transaction/"/>
    <id>https://ehlxr.me/2019/01/25/distributed-system-transaction/</id>
    <published>2019-01-25T15:57:42.000Z</published>
    <updated>2025-03-07T07:31:51.000Z</updated>
    
    <content type="html"><![CDATA[<p>通俗的理解，事务是一组原子操作单元。我们希望一些列的操作能够全部正确执行，如果这一组操作中的任意一个步骤发生错误，那么就需要回滚之前已经完成的操作。也就是同一个事务中的所有操作，要么全都正确执行，要么全都不要执行。</p><p>传统的单机应用系统一般使用一个关系型数据库，利用数据库事务来保证数据的一致性，下面先了解一下本地数据库事务的一些特性。</p><h2 id="一、本地数据库事务"><a href="#一、本地数据库事务" class="headerlink" title="一、本地数据库事务"></a>一、本地数据库事务</h2><p>事务从数据库角度说，就是一组 <code>SQL</code> 指令，要么全部执行成功，若因为某个原因其中一条指令执行有错误，则撤销先前执行过的所有指令。</p><p>关系型数据库（例如：<code>MySQL</code>、<code>SQL Server</code>、<code>Oracle</code> 等）事务都有以下几个特性：<strong>原子性</strong>（<code>Atomicity</code>）、<strong>一致性</strong>（<code>Consistency</code>）、<strong>隔离性或独立性</strong>（<code>Isolation</code>）<strong>和持久性</strong>（<code>Durabilily</code>），简称就是 <strong>ACID</strong>。</p><ul><li><strong>原子性</strong>：表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。</li><li><strong>一致性</strong>：表示当事务执行失败时，所有被该事务影响的数据都应该恢复到事务执行前的状态。</li><li><strong>隔离性</strong>：表示在事务执行过程中对数据的修改，在事务提交之前对其他事务不可见。</li><li><strong>持久性</strong>：表示已提交的数据在事务执行失败时，数据的状态都应该正确。</li></ul><a id="more"></a><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/acid.jpg" alt=""></p><p>数据库事务操作也比较简单：开始一个事务，改变（插入，删除，更新）很多行，然后提交事务（如果有异常时回滚事务）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">Connection con = <span class="keyword">null</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">​    <span class="comment">// 工具类得到 connection 对象</span></span><br><span class="line">​    con = JdbcUtils.getConnection();</span><br><span class="line"></span><br><span class="line">​    <span class="comment">// 关闭自动提交，开启事务</span></span><br><span class="line">​    con.setAutoCommit(<span class="keyword">false</span>);</span><br><span class="line"></span><br><span class="line">​    <span class="comment">// 增、删、改 等操作</span></span><br><span class="line">​    ...</span><br><span class="line"></span><br><span class="line">​    <span class="comment">// 成功操作后提交事务</span></span><br><span class="line">​    con.commit();</span><br><span class="line">​    con.close();</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">​    <span class="keyword">try</span> &#123;</span><br><span class="line">​        <span class="comment">// 如果有异常时回滚事务</span></span><br><span class="line">​        con.rollback();</span><br><span class="line">​        con.close();</span><br><span class="line">​    &#125; <span class="keyword">catch</span> (SQLException e1) &#123;</span><br><span class="line">​        e1.printStackTrace();</span><br><span class="line">​    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>更进一步，借助开发平台中的数据访问技术和框架（如：<code>Spring</code>），我们需要做的事情更少，只需要关注数据本身的改变。</p><p>随着组织规模不断扩大，业务量不断增长，单机应用和数据库已经不足以支持庞大的业务量和数据量，这个时候需要对应用和数据库进行拆分，就出现了一个应用需要同时访问两个或两个以上的数据库情况。开始我们用分布式事务来保证一致性。</p><h2 id="二、分布式事务理论"><a href="#二、分布式事务理论" class="headerlink" title="二、分布式事务理论"></a>二、分布式事务理论</h2><h3 id="2-1-CAP-定理"><a href="#2-1-CAP-定理" class="headerlink" title="2.1 CAP 定理"></a>2.1 CAP 定理</h3><p><code>CAP</code> 定理是由加州大学伯克利分校 <code>Eric Brewer</code> 教授提出来的，他指出 <code>WEB</code> 服务无法同时满足以下 <code>3</code> 个属性：</p><ul><li><strong>一致性（Consistency）</strong>： 客户端知道一系列的操作都会同时发生（生效）</li><li><strong>可用性（Availability）</strong>：每个操作都必须以可预期的响应结束</li><li><strong>分区容错性（Partition tolerance）</strong>：即使出现单个组件无法可用, 操作依然可以完成</li></ul><p><code>CAP</code> 理论告诉我们，在分布式系统中，<strong>C、A、P</strong> 三个条件中我们最多只能选择两个。那么问题来了，究竟选择哪两个条件较为合适呢？</p><p>对于一个业务系统来说，可用性和分区容错性是必须要满足的两个条件，并且这两者是相辅相成的。业务系统之所以使用分布式系统，主要原因有两个：</p><ul><li>提升整体性能：当业务量猛增，单个服务器已经无法满足我们的业务需求的时候，就需要使用分布式系统，使用多个节点提供相同的功能，从而整体上提升系统的性能，这就是使用分布式系统的第一个原因。</li><li>实现分区容错性：单一节点或多个节点处于相同的网络环境下，那么会存在一定的风险，万一该机房断电、该地区发生自然灾害，那么业务系统就全面瘫痪了。为了防止这一问题，采用分布式系统，将多个子系统分布在不同的地域、不同的机房中，从而保证系统高可用性。</li></ul><p>这说明分区容错性是分布式系统的根本，如果分区容错性不能满足，那使用分布式系统将失去意义。</p><p>此外，可用性对业务系统也尤为重要。在大谈用户体验的今天，如果业务系统时常出现 “系统异常”、响应时间过长等情况，这使得用户对系统的好感度大打折扣，在互联网行业竞争激烈的今天，相同领域的竞争者不甚枚举，系统的间歇性不可用会立马导致用户流向竞争对手。因此，我们只能通过牺牲一致性来换取系统的<strong>可用性（A）</strong>和<strong>分区容错性（P）</strong>。这也就是下面要介绍的 <code>BASE</code> 理论。</p><h3 id="2-2-BASE-理论"><a href="#2-2-BASE-理论" class="headerlink" title="2.2 BASE 理论"></a>2.2 BASE 理论</h3><p><code>CAP</code> 理论告诉我们一个悲惨但不得不接受的事实——我们只能在 <strong>C、A、P</strong> 中选择两个条件。而对于业务系统而言，我们往往选择牺牲一致性来换取系统的可用性和分区容错性。不过这里要指出的是，所谓的 “牺牲一致性” 并不是完全放弃数据一致性，而是牺牲强一致性换取弱一致性。下面来介绍下 <code>BASE</code> 理论。</p><ul><li><strong>Basically Available（基本可用）</strong>整个系统在某些不可抗力的情况下，仍然能够保证 “可用性”，即一定时间内仍然能够返回一个明确的结果。</li><li><strong>Soft state（软状态）</strong>同一数据的不同副本的状态，可以不需要实时一致。</li><li><strong>Eventually Consistent（最终一致性）</strong>同一数据的不同副本的状态，可以不需要实时一致，但一定要保证经过一定时间后仍然是一致的。</li></ul><p><code>BASE</code> 理论是对 <code>CAP</code> 中的一致性和可用性进行一个权衡的结果，理论的核心思想就是：<strong><em>我们无法做到强一致，但每个应用都可以根据自身的业务特点，采用适当的方式来使系统达到最终一致性</em></strong>（<code>Eventual Consistency</code>）。</p><p>有了以上理论之后，我们来看一下分布式事务的解决方案。</p><h2 id="三、分布式事务"><a href="#三、分布式事务" class="headerlink" title="三、分布式事务"></a>三、分布式事务</h2><h3 id="3-1-两阶段提交（2PC）"><a href="#3-1-两阶段提交（2PC）" class="headerlink" title="3.1 两阶段提交（2PC）"></a>3.1 两阶段提交（2PC）</h3><p>两阶段提交协议（Two-phase Commit，2PC）经常被用来实现分布式事务。一般分为<code>协调器</code>和若干<code>事务执行者</code>两种角色，这里的事务执行者就是具体的数据库，抽象点可以说是可以控制数据库的程序。 协调器可以和事务执行器在一台机器上。</p><p>在分布式系统中，每个节点虽然可以知晓自己的操作时成功或者失败，却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时，为了保持事务的 <code>ACID</code> 特性，需要引入一个作为协调者的组件来统一掌控所有节点 (称作参与者)。</p><p>图示从支付宝向余额宝转账是怎样保证一致性的</p><p><img src="https://cdn.jsdelivr.net/gh/0vo/oss/images/687148dbly1g10y9di2gxj20l908xta6.jpg" alt=""></p><ol><li>用户发起转账申请，首先到事务协调器</li><li>事务协调器通知（prepare）支付宝扣款，同时通知余额宝收款</li><li>支付宝、余额宝分别执行扣款、收款业务操作，本地事务不提交。并且把执行结果反馈给事务协调器，成功反馈 yes，失败反馈 no</li><li>事务协调器收到支付宝和余额宝的反馈如果都是 yes，则通知支付宝和余额宝系统提交事务（commit），否则通知回顾事务（abort）</li><li>事务协调器、支付宝和余额宝在整个过程收到通知都要记录日志（log），类似日常生活中的凭证。如果某个节点宕机，可以保证从日志中恢复后续操作。</li></ol><p>两阶段提交这种解决方案属于牺牲了一部分可用性来换取的一致性。</p><p><strong>优点：</strong> 尽量保证了数据的强一致，适合对数据强一致要求很高的关键领域。（其实也不能 <code>100%</code> 保证强一致）<br><strong>缺点：</strong> 涉及多次节点间的网络通信时，牺牲了可用性，对性能影响较大，不适合高并发高性能场景，如果分布式系统跨接口调用。</p><h3 id="3-2-补偿事务（TCC）"><a href="#3-2-补偿事务（TCC）" class="headerlink" title="3.2 补偿事务（TCC）"></a>3.2 补偿事务（TCC）</h3><p><code>TCC</code> 其实就是采用的补偿机制，其核心思想是：针对每个操作，都要注册一个与其对应的确认和补偿（撤销）操作。它分为三个阶段：</p><ul><li><strong>Try 阶段</strong>：主要是对业务系统做检测及资源预留</li><li><strong>Confirm 阶段</strong>：主要是对业务系统做确认提交，<code>Try</code> 阶段执行成功并开始执行 <code>Confirm</code> 阶段时，默认 <code>Confirm</code> 阶段是不会出错的。即：只要 <code>Try</code> 成功，<code>Confirm</code> 一定成功。</li><li><strong>Cancel 阶段</strong>：主要是在业务执行错误，需要回滚的状态下执行的业务取消，预留资源释放。</li></ul><p>举个例子，假如 <code>Bob</code> 要向 <code>Smith</code> 转账，思路大概是：</p><p>我们有一个本地方法，里面依次调用</p><ol><li>首先在 <code>Try</code> 阶段，要先调用远程接口把 <code>Smith</code> 和 <code>Bob</code> 的钱给冻结起来。</li><li>在 <code>Confirm</code> 阶段，执行远程调用的转账的操作，转账成功进行解冻。</li><li>如果第 2 步执行成功，那么转账成功，如果第二步执行失败，则调用远程冻结接口对应的解冻方法（<code>Cancel</code>）。</li></ol><p><strong>优点：</strong> 跟 <code>2PC</code> 比起来，实现以及流程相对简单了一些，但数据的一致性比 <code>2PC</code> 也要差一些</p><p><strong>缺点：</strong> 缺点还是比较明显的，在 <code>2</code>、<code>3</code> 步中都有可能失败。<code>TCC</code> 属于应用层的一种补偿方式，所以需要程序员在实现的时候多写很多补偿的代码，在一些场景中，一些业务流程可能用 <code>TCC</code> 不太好定义及处理。</p><h3 id="3-3-分布式事务框架"><a href="#3-3-分布式事务框架" class="headerlink" title="3.3 分布式事务框架"></a>3.3 分布式事务框架</h3><p>常用开源的分布式事务框架：</p><ul><li>阿里巴巴开源的分布式事务解决方案 <a href="https://github.com/seata/seata" target="_blank" rel="noopener">Seata</a></li><li>一款事务协调性框架 <a href="https://github.com/codingapi/tx-lcn" target="_blank" rel="noopener">TX-LCN</a></li><li>强一致分布式事务框架 <a href="https://github.com/dromara/raincat" target="_blank" rel="noopener">Raincat</a></li><li>提供柔性事务的支持，包含 TCC, TAC(自动生成回滚 SQL) 方案 <a href="https://github.com/dromara/hmily" target="_blank" rel="noopener">Hmily</a></li><li>TCC 型事务 java 实现 <a href="https://github.com/changmingxie/tcc-transaction" target="_blank" rel="noopener">tcc-transaction</a></li><li>基于事务管理器（TransactionManager）实现的 TCC 全局事务 <a href="https://github.com/liuyangming/ByteTCC" target="_blank" rel="noopener">ByteTCC</a></li></ul><hr>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;通俗的理解，事务是一组原子操作单元。我们希望一些列的操作能够全部正确执行，如果这一组操作中的任意一个步骤发生错误，那么就需要回滚之前已经完成的操作。也就是同一个事务中的所有操作，要么全都正确执行，要么全都不要执行。&lt;/p&gt;
&lt;p&gt;传统的单机应用系统一般使用一个关系型数据库，利用数据库事务来保证数据的一致性，下面先了解一下本地数据库事务的一些特性。&lt;/p&gt;
&lt;h2 id=&quot;一、本地数据库事务&quot;&gt;&lt;a href=&quot;#一、本地数据库事务&quot; class=&quot;headerlink&quot; title=&quot;一、本地数据库事务&quot;&gt;&lt;/a&gt;一、本地数据库事务&lt;/h2&gt;&lt;p&gt;事务从数据库角度说，就是一组 &lt;code&gt;SQL&lt;/code&gt; 指令，要么全部执行成功，若因为某个原因其中一条指令执行有错误，则撤销先前执行过的所有指令。&lt;/p&gt;
&lt;p&gt;关系型数据库（例如：&lt;code&gt;MySQL&lt;/code&gt;、&lt;code&gt;SQL Server&lt;/code&gt;、&lt;code&gt;Oracle&lt;/code&gt; 等）事务都有以下几个特性：&lt;strong&gt;原子性&lt;/strong&gt;（&lt;code&gt;Atomicity&lt;/code&gt;）、&lt;strong&gt;一致性&lt;/strong&gt;（&lt;code&gt;Consistency&lt;/code&gt;）、&lt;strong&gt;隔离性或独立性&lt;/strong&gt;（&lt;code&gt;Isolation&lt;/code&gt;）&lt;strong&gt;和持久性&lt;/strong&gt;（&lt;code&gt;Durabilily&lt;/code&gt;），简称就是 &lt;strong&gt;ACID&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原子性&lt;/strong&gt;：表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一致性&lt;/strong&gt;：表示当事务执行失败时，所有被该事务影响的数据都应该恢复到事务执行前的状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隔离性&lt;/strong&gt;：表示在事务执行过程中对数据的修改，在事务提交之前对其他事务不可见。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持久性&lt;/strong&gt;：表示已提交的数据在事务执行失败时，数据的状态都应该正确。&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
      <category term="分布式" scheme="https://ehlxr.me/categories/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
    
      <category term="分布式事务" scheme="https://ehlxr.me/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/"/>
    
      <category term="事务" scheme="https://ehlxr.me/tags/%E4%BA%8B%E5%8A%A1/"/>
    
  </entry>
  
</feed>
