<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>baofeidyz's blog</title>
    <link>https://baofeidyz.com/</link>
    <description>记录生活，记录成长</description>
    <language>zh-CN</language>
    <copyright>All rights reserved 2026, baofeidyz's blog</copyright>
    <lastBuildDate>Wed, 15 Apr 2026 08:25:08 GMT</lastBuildDate>
    <generator>Hexo</generator>
    <image>
      <url>https://baofeidyz.com/icon.png</url>
      <title>baofeidyz's blog</title>
      <link>https://baofeidyz.com/</link>
    </image>
    <atom:link href="https://baofeidyz.com/rss2.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>2026年主流API调试工具对比</title>
      <link>https://baofeidyz.com/2026/8f0dca627425/</link>
      <description>
        <![CDATA[<h1 id="2026年主流API调试工具对比"><a href="#2026年主流API调试工具对比" class="headerlink" title="2026年主流API调试工具对比"></a>2026年主流API调试工具对比</h1><p><img]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/api/">api</category>
      <category domain="https://baofeidyz.com/tags/%E5%B7%A5%E5%85%B7/">工具</category>
      <category domain="https://baofeidyz.com/tags/apifox/">apifox</category>
      <category domain="https://baofeidyz.com/tags/postman/">postman</category>
      <category domain="https://baofeidyz.com/tags/bruno/">bruno</category>
      <category domain="https://baofeidyz.com/tags/insomnia/">insomnia</category>
      <category domain="https://baofeidyz.com/tags/reqable/">reqable</category>
      <category domain="https://baofeidyz.com/tags/hoppscotch/">hoppscotch</category>
      <category domain="https://baofeidyz.com/tags/yaak/">yaak</category>
      <pubDate>Wed, 15 Apr 2026 08:15:28 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="2026年主流API调试工具对比"><a href="#2026年主流API调试工具对比" class="headerlink" title="2026年主流API调试工具对比"></a>2026年主流API调试工具对比</h1><p><img src="https://cdn.baofeidyz.com/img/20260415162401623.png"></p><blockquote><p>在经历了apifox的投毒事件以后，我就一直在寻找一个相对完美的替代品，终于我找到了一款叫yaak的开源神器！<br>关于apifox投毒事件可见：<a href="https://rce.moe/2026/03/25/apifox-supply-chain-attack-analysis/">Apifox 供应链投毒攻击 — 完整技术分析</a></p></blockquote><h2 id="一、工具对比总览"><a href="#一、工具对比总览" class="headerlink" title="一、工具对比总览"></a>一、工具对比总览</h2><table><thead><tr><th>工具</th><th>本地优先</th><th>Git 协作</th><th>多 Workspace 免费</th><th>开源</th><th>性能</th><th>安全性</th><th>免费商用</th></tr></thead><tbody><tr><td>Apifox</td><td>❌</td><td>❌</td><td>✅</td><td>❌</td><td>⚠️</td><td>⚠️</td><td>⚠️（当前还是免费，未来不确定）</td></tr><tr><td>Postman</td><td>❌</td><td>❌</td><td>❌</td><td>❌</td><td>❌</td><td>⚠️</td><td>⚠️</td></tr><tr><td>Bruno</td><td>✅</td><td>✅</td><td>✅</td><td>⚠️（部分）</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>Insomnia</td><td>⚠️</td><td>❌</td><td>❌</td><td>⚠️</td><td>⚠️</td><td>⚠️</td><td>⚠️</td></tr><tr><td>Reqable</td><td>✅</td><td>❌</td><td>✅</td><td>❌</td><td>✅</td><td>✅</td><td>⚠️</td></tr><tr><td>Hoppscotch</td><td>⚠️</td><td>❌</td><td>✅</td><td>✅</td><td>✅</td><td>⚠️</td><td>⚠️</td></tr><tr><td>Yaak</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>⚠️（二进制分发商用需付费，自己编译可免费商用）</td></tr></tbody></table><p>说明：</p><ul><li>✅：完全支持 &#x2F; 优秀</li><li>❌：不支持 &#x2F; 明显不足</li><li>⚠️：部分支持 &#x2F; 存在限制</li></ul><h2 id="二、核心能力对比"><a href="#二、核心能力对比" class="headerlink" title="二、核心能力对比"></a>二、核心能力对比</h2><h3 id="1-架构与数据控制"><a href="#1-架构与数据控制" class="headerlink" title="1. 架构与数据控制"></a>1. 架构与数据控制</h3><table><thead><tr><th>工具</th><th>完全本地</th><th>数据可控</th><th>依赖远程资源</th></tr></thead><tbody><tr><td>Apifox</td><td>❌</td><td>❌</td><td>✅</td></tr><tr><td>Postman</td><td>❌</td><td>❌</td><td>✅</td></tr><tr><td>Bruno</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>Insomnia</td><td>⚠️</td><td>⚠️</td><td>⚠️</td></tr><tr><td>Reqable</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>Hoppscotch</td><td>⚠️</td><td>⚠️</td><td>✅</td></tr><tr><td>Yaak</td><td>✅</td><td>✅</td><td>❌</td></tr></tbody></table><h3 id="2-协作能力"><a href="#2-协作能力" class="headerlink" title="2. 协作能力"></a>2. 协作能力</h3><table><thead><tr><th>工具</th><th>内建团队协作</th><th>Git 协作</th><th>是否收费</th></tr></thead><tbody><tr><td>Apifox</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>Postman</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>Bruno</td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td>Insomnia</td><td>⚠️</td><td>❌</td><td>❌</td></tr><tr><td>Reqable</td><td>❌</td><td>❌</td><td>✅</td></tr><tr><td>Hoppscotch</td><td>⚠️</td><td>❌</td><td>✅</td></tr><tr><td>Yaak</td><td>❌</td><td>✅</td><td>✅</td></tr></tbody></table><h3 id="3-功能与体验"><a href="#3-功能与体验" class="headerlink" title="3. 功能与体验"></a>3. 功能与体验</h3><table><thead><tr><th>工具</th><th>功能完整度</th><th>启动速度</th><th>UI 复杂度</th></tr></thead><tbody><tr><td>Apifox</td><td>✅</td><td>❌</td><td>高</td></tr><tr><td>Postman</td><td>✅</td><td>❌</td><td>很高</td></tr><tr><td>Bruno</td><td>⚠️</td><td>✅</td><td>中</td></tr><tr><td>Insomnia</td><td>⚠️</td><td>⚠️</td><td>中</td></tr><tr><td>Reqable</td><td>❌</td><td>✅</td><td>低</td></tr><tr><td>Hoppscotch</td><td>❌</td><td>✅</td><td>低</td></tr><tr><td>Yaak</td><td>⚠️</td><td>✅</td><td>低</td></tr></tbody></table><h3 id="4-安全性"><a href="#4-安全性" class="headerlink" title="4. 安全性"></a>4. 安全性</h3><table><thead><tr><th>工具</th><th>本地执行安全</th><th>云风险</th><th>可审计性</th></tr></thead><tbody><tr><td>Apifox</td><td>❌</td><td>❌</td><td>❌</td></tr><tr><td>Postman</td><td>❌</td><td>❌</td><td>❌</td></tr><tr><td>Bruno</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Insomnia</td><td>⚠️</td><td>⚠️</td><td>⚠️</td></tr><tr><td>Reqable</td><td>✅</td><td>✅</td><td>⚠️</td></tr><tr><td>Hoppscotch</td><td>⚠️</td><td>⚠️</td><td>❌</td></tr><tr><td>Yaak</td><td>✅</td><td>✅</td><td>✅</td></tr></tbody></table><h2 id="三、结论与选择"><a href="#三、结论与选择" class="headerlink" title="三、结论与选择"></a>三、结论与选择</h2><p>从对比可以看出：</p><ul><li>云工具（Apifox &#x2F; Postman）在功能和协作上占优，但存在数据与安全风险</li><li>本地工具（Bruno &#x2F; Yaak）在安全与可控性上更有优势</li><li>轻量工具（Hoppscotch &#x2F; Reqable）适合辅助使用（Reqable非常适合用于https协议抓包）</li></ul><h2 id="四、为什么最终选择-Yaak"><a href="#四、为什么最终选择-Yaak" class="headerlink" title="四、为什么最终选择 Yaak"></a>四、为什么最终选择 Yaak</h2><p>最终选择 Yaak，主要基于以下几点：</p><h3 id="1-完全本地优先"><a href="#1-完全本地优先" class="headerlink" title="1. 完全本地优先"></a>1. 完全本地优先</h3><ul><li>数据不经过云端</li><li>不依赖远程资源</li><li>风险可控</li></ul><h3 id="2-Git-原生协作"><a href="#2-Git-原生协作" class="headerlink" title="2. Git 原生协作"></a>2. Git 原生协作</h3><ul><li>使用 Git 进行团队同步</li><li>支持版本管理、回滚、审计</li><li>不依赖平台</li></ul><h3 id="3-多-Workspace-免费"><a href="#3-多-Workspace-免费" class="headerlink" title="3. 多 Workspace 免费"></a>3. 多 Workspace 免费</h3><ul><li>不限制项目数量</li><li>无额外收费</li><li>不锁功能</li></ul><h3 id="4-性能与体验"><a href="#4-性能与体验" class="headerlink" title="4. 性能与体验"></a>4. 性能与体验</h3><ul><li>启动速度快</li><li>界面简洁</li><li>无冗余功能</li></ul><h3 id="5-开源与可控性"><a href="#5-开源与可控性" class="headerlink" title="5. 开源与可控性"></a>5. 开源与可控性</h3><ul><li>可审计代码</li><li>可自托管或 fork</li><li>无商业绑定风险</li></ul><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>API 工具的选择，本质取决于：</p><ul><li>是否需要云协作</li><li>是否关注数据安全</li><li>是否需要高度可控</li></ul><p>在安全性、性能、协作方式和成本之间权衡后：</p><p>👉 <strong>Yaak 是当前更均衡且更可控的选择</strong></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>PageIndex预研报告</title>
      <link>https://baofeidyz.com/2026/990a549aba7e/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/20260227174857663.png"></p>
<h1 id="概述"><a href="#概述" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/AI/">AI</category>
      <category domain="https://baofeidyz.com/tags/RAG/">RAG</category>
      <pubDate>Fri, 27 Feb 2026 08:55:21 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/20260227174857663.png"></p><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p><a href="https://pageindex.ai/">PageIndex</a>是无向量模型依赖的RAG解决方案，通过推理模型将PDF或者markdown文档转换为JSON文件来实现检索</p><h1 id="基本信息"><a href="#基本信息" class="headerlink" title="基本信息"></a>基本信息</h1><ol><li>官网地址：<a href="https://pageindex.ai/">https://pageindex.ai</a></li><li>GitHub地址：<a href="https://github.com/VectifyAI">https://github.com/VectifyAI</a></li></ol><h1 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h1><ol><li>基于特定资料库的问答</li></ol><h1 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h1><h2 id="1-部分开源"><a href="#1-部分开源" class="headerlink" title="1. 部分开源"></a>1. 部分开源</h2><p>PDF&#x2F;markdown转换为JSON有做开源（<a href="https://github.com/VectifyAI/PageIndex%EF%BC%89%EF%BC%8C%E4%BD%86%E6%A0%B8%E5%BF%83%E6%9C%8D%E5%8A%A1%E6%98%AF%E9%97%AD%E6%BA%90%E7%9A%84%EF%BC%8C%E5%8F%AA%E8%83%BD%E9%80%9A%E8%BF%87%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%EF%BC%88sdk/http">https://github.com/VectifyAI/PageIndex），但核心服务是闭源的，只能通过服务调用（sdk/http</a> rest api&#x2F;mcp http&#x2F;mcp stdio）完成。<br>如果需要私有化部署需要单独联系采购</p><h2 id="MCP没有支持文档转换"><a href="#MCP没有支持文档转换" class="headerlink" title="MCP没有支持文档转换"></a>MCP没有支持文档转换</h2><p>基于http的mcp不支持PDF&#x2F;markdown转换为JSON。需要自己在后台页面中上传，操作较为繁琐<br><img src="https://cdn.baofeidyz.com/img/20260227170827069.png"></p><h2 id="PDF-markdown转换为JSON效果不稳定"><a href="#PDF-markdown转换为JSON效果不稳定" class="headerlink" title="PDF&#x2F;markdown转换为JSON效果不稳定"></a>PDF&#x2F;markdown转换为JSON效果不稳定</h2><p>我上传了一份20+页的PDF，在后台看到的结果中只有2页。<br>因为无法直接上传我通过<a href="https://github.com/VectifyAI/PageIndex">PageIndex</a>生成好的json文件，只能使用云端默认的模型</p><h1 id="评价"><a href="#评价" class="headerlink" title="评价"></a>评价</h1><p>就我自己本身的应用场景来看，目前基于向量模型的方案没有遇到瓶颈，暂不考虑PageIndex</p><p>另外<a href="https://github.com/VectifyAI/PageIndex">PageIndex</a>这个服务只是作者引流的一个开源仓库，实际服务与这个开源仓库关系不大</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Claude Code安装Oceanbase MCP Server</title>
      <link>https://baofeidyz.com/2026/3e8125af2182/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/20260227175758771.png"></p>
<h1 id="本地依赖"><a href="#本地依赖" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/AI/">AI</category>
      <category domain="https://baofeidyz.com/tags/Claude-Code/">Claude Code</category>
      <category domain="https://baofeidyz.com/tags/MCP/">MCP</category>
      <pubDate>Wed, 11 Feb 2026 08:12:37 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/20260227175758771.png"></p><h1 id="本地依赖"><a href="#本地依赖" class="headerlink" title="本地依赖"></a>本地依赖</h1><p><code>python</code> + <code>uv</code></p><h1 id="安装步骤"><a href="#安装步骤" class="headerlink" title="安装步骤"></a>安装步骤</h1><ol><li>从GitHub仓库中clone源码到本地</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/oceanbase/awesome-oceanbase-mcp.git</span><br></pre></td></tr></table></figure><ol start="2"><li>使用<code>uv</code>安装<code>python</code>依赖</li></ol><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"><span class="built_in">cd</span> awesome-oceanbase-mcp &amp;&amp; \</span><br><span class="line">uv venv &amp;&amp; \</span><br><span class="line"><span class="built_in">source</span> .venv/bin/activate &amp;&amp; \</span><br><span class="line">uv pip install .</span><br></pre></td></tr></table></figure><ol start="3"><li>使用<code>claude</code>命令添加<code>MCP Server</code></li></ol><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><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">claude mcp add-json oceanbase <span class="string">&#x27;&#123;</span></span><br><span class="line"><span class="string">&quot;type&quot;:&quot;stdio&quot;,</span></span><br><span class="line"><span class="string">&quot;command&quot;: &quot;uv&quot;,</span></span><br><span class="line"><span class="string">&quot;args&quot;: [</span></span><br><span class="line"><span class="string"> &quot;--directory&quot;, </span></span><br><span class="line"><span class="string"> &quot;你的本地路径/awesome-oceanbase-mcp/src/oceanbase_mcp_server&quot;,</span></span><br><span class="line"><span class="string"> &quot;run&quot;,</span></span><br><span class="line"><span class="string"> &quot;oceanbase_mcp_server&quot;</span></span><br><span class="line"><span class="string"> ],</span></span><br><span class="line"><span class="string">&quot;env&quot;: &#123;</span></span><br><span class="line"><span class="string"> &quot;OB_HOST&quot;: &quot;***&quot;,</span></span><br><span class="line"><span class="string"> &quot;OB_PORT&quot;: &quot;***&quot;,</span></span><br><span class="line"><span class="string"> &quot;OB_USER&quot;: &quot;***&quot;,</span></span><br><span class="line"><span class="string"> &quot;OB_PASSWORD&quot;: &quot;***&quot;,</span></span><br><span class="line"><span class="string"> &quot;OB_DATABASE&quot;: &quot;***&quot;</span></span><br><span class="line"><span class="string"> &#125;</span></span><br><span class="line"><span class="string">&#125;&#x27;</span></span><br></pre></td></tr></table></figure><h1 id="验证方法"><a href="#验证方法" class="headerlink" title="验证方法"></a>验证方法</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">claude mcp list</span><br></pre></td></tr></table></figure><p>没有报错信息或者提示<code>Connected</code>即为成功</p><h1 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h1><ol><li>整个仓库中不止有oceanbase数据库的mcp server，所以你在写本地路径时一定要仔细看。</li><li>如果claude报错了，你可以选择让claude自己修复</li><li><strong>一定要有<code>&quot;type&quot;:&quot;stdio&quot;</code>的声明</strong></li></ol><h1 id="FAQ"><a href="#FAQ" class="headerlink" title="FAQ"></a>FAQ</h1><p>暂无</p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul><li><a href="https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000004316553">OceanBase MCP Server 与 Claude Code 集成</a></li><li><a href="https://code.claude.com/docs/zh-CN/mcp#%E4%BB%8E-json-%E9%85%8D%E7%BD%AE%E6%B7%BB%E5%8A%A0-mcp-%E6%9C%8D%E5%8A%A1%E5%99%A8">从 JSON 配置添加 MCP 服务器</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>提示词优化思路「一」：Few-Shot Prompting导致的幻觉问题</title>
      <link>https://baofeidyz.com/2026/144193f7271c/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/20260227175343533.png"></p>
<h1 id="原因"><a href="#原因" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/AI/">AI</category>
      <category domain="https://baofeidyz.com/tags/AI/">AI</category>
      <category domain="https://baofeidyz.com/tags/Prompt/">Prompt</category>
      <category domain="https://baofeidyz.com/tags/%E6%8F%90%E7%A4%BA%E8%AF%8D%E5%B7%A5%E7%A8%8B/">提示词工程</category>
      <pubDate>Mon, 09 Feb 2026 08:46:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/20260227175343533.png"></p><h1 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h1><p>根本原因是大模型没有正确识别“输入”和“示例”的区别，ta会把你给出的“示例”当成了”输入“从而导致生成的结果和输入不符合出现幻觉</p><h1 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h1><p>需要加强模型对于”输入“和”示例“的理解，可以设定一个逻辑步骤，要求ta按照第一步、第二步…这样去生成</p><p>然后针对GPT模型可以考虑使用<code>xml</code>标签来隔离“输入”和“示例”</p><p>比如：</p><figure class="highlight plaintext"><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">你是xxx，你的任务是xxx</span><br><span class="line"></span><br><span class="line">**处理逻辑**</span><br><span class="line">请严格遵守以下信息处理逻辑：</span><br><span class="line">1. 浏览 &lt;reference_cases&gt; 中的内容，仅用于xxx。</span><br><span class="line">2. 绝对禁止提取 &lt;reference_cases&gt; 中的任何xxx。</span><br><span class="line">3. 仅针对 &lt;current_session&gt; 标签中的xxx进行xxx。</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">&lt;reference_cases&gt;</span><br><span class="line">...</span><br><span class="line">&lt;/reference_cases&gt;</span><br><span class="line"></span><br><span class="line">&lt;current_session&gt;</span><br><span class="line">...</span><br><span class="line">&lt;/current_session&gt;</span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>分享使用OpenClaw管理Claude遇到的问题和解决思路</title>
      <link>https://baofeidyz.com/2026/9d2e1096025d/</link>
      <description>
        <![CDATA[<h1 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h1><p>需要安装好OpenClaw和Claude，并且配置相应的模型或者账号权限</p>
<h1 id="问题集锦"><a]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/AI/">AI</category>
      <category domain="https://baofeidyz.com/tags/AI/">AI</category>
      <category domain="https://baofeidyz.com/tags/OpenClaw/">OpenClaw</category>
      <category domain="https://baofeidyz.com/tags/Claude/">Claude</category>
      <pubDate>Mon, 09 Feb 2026 08:33:13 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h1><p>需要安装好OpenClaw和Claude，并且配置相应的模型或者账号权限</p><h1 id="问题集锦"><a href="#问题集锦" class="headerlink" title="问题集锦"></a>问题集锦</h1><h1 id="1-OpenClaw一直提示需要-login"><a href="#1-OpenClaw一直提示需要-login" class="headerlink" title="1. OpenClaw一直提示需要/login"></a>1. OpenClaw一直提示需要<code>/login</code></h1><h2 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h2><p>我本地使用的Claude是使用<code>litellm</code>本地服务启动的，我有设置环境变量，是通过<code>~/.zshrc</code>文件维护。<br>OpenClaw一直报这个错，就是因为ta没有使用我的<code>~/.zshrc</code>文件</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>告诉<code>OpenClaw</code>尝试先执行<code>source ~/.zshrc</code>再调用Claude</p><h2 id="2-OpenClaw会因为Claude超时而失败"><a href="#2-OpenClaw会因为Claude超时而失败" class="headerlink" title="2. OpenClaw会因为Claude超时而失败"></a>2. OpenClaw会因为Claude超时而失败</h2><h2 id="原因-1"><a href="#原因-1" class="headerlink" title="原因"></a>原因</h2><p>OpenClaw在调用Claude时，不会使用Claude的交互模式，一般都是直接运行<code>Claude -p &quot;具体任务&quot;</code>，而OpenClaw默认等待时间是60s左右，当你Claude超过这个时间响应时，就会导致OpenClaw因Claude超时失败</p><h2 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h2><p>让OpenClaw异步调用Claude，把Claude输出的日志放到一个临时文件中，然后定时轮询这个临时文件查看日志即可</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>歌单推荐-2025年3月</title>
      <link>https://baofeidyz.com/song-recommendation-2025-03/</link>
      <description>
        <![CDATA[<p><img src="https://a5.mzstatic.com/us/r1000/0/Music211/v4/ec/7c/8d/ec7c8dfb-61cd-3ca3-1bd5-76d0cd94bc2d/cover.jpg"></p>
<p><del>感觉3月没有听到啥好听的新歌，选来选去还是把封面给王力宏的《在梅边》，后面的rap很不错。</del><br>今天才听到路虎3月发的新歌《流行歌手》，好听！</p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/Apple-Music/">Apple Music</category>
      <category domain="https://baofeidyz.com/tags/AppleMusic/">AppleMusic</category>
      <category domain="https://baofeidyz.com/tags/%E6%AD%8C%E5%8D%95%E6%8E%A8%E8%8D%90/">歌单推荐</category>
      <pubDate>Fri, 11 Apr 2025 09:25:28 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://a5.mzstatic.com/us/r1000/0/Music211/v4/ec/7c/8d/ec7c8dfb-61cd-3ca3-1bd5-76d0cd94bc2d/cover.jpg"></p><p><del>感觉3月没有听到啥好听的新歌，选来选去还是把封面给王力宏的《在梅边》，后面的rap很不错。</del><br>今天才听到路虎3月发的新歌《流行歌手》，好听！</p><span id="more"></span><p>2025年歌单链接：<a href="https://music.apple.com/tr/playlist/2025/pl.u-jV89bXVIDNxA47W">2025年歌单</a></p><blockquote><p>歌单链接没有办法直接打开，需要复制链接到iMessage消息中，然后点击才会跳转。</p></blockquote><h1 id="流行歌手（快发3Live重生版）-路虎"><a href="#流行歌手（快发3Live重生版）-路虎" class="headerlink" title="流行歌手（快发3Live重生版） - 路虎"></a>流行歌手（快发3Live重生版） - 路虎</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E6%B5%81%E8%A1%8C%E6%AD%8C%E6%89%8B-%E5%BF%AB%E5%8F%913live%E9%87%8D%E7%94%9F%E7%89%88/1804777239?i=1804777247"></iframe><p>反复听了很多遍，词曲都非常好，目前是我的2025年最佳单曲之一。</p><h1 id="Small-Girl-Lee-Young-Ji"><a href="#Small-Girl-Lee-Young-Ji" class="headerlink" title="Small Girl - Lee Young Ji"></a>Small Girl - Lee Young Ji</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/small-girl-feat-d-o/1753178651?i=1753178654"></iframe><h1 id="Still-Bad-Lizzo"><a href="#Still-Bad-Lizzo" class="headerlink" title="Still Bad - Lizzo"></a>Still Bad - Lizzo</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/still-bad/1800906041?i=1800906042"></iframe><h1 id="For-a-Better-Day-WeiBird"><a href="#For-a-Better-Day-WeiBird" class="headerlink" title="For a Better Day - WeiBird"></a>For a Better Day - WeiBird</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/for-a-better-day-feat-lien-binh-phat-tv-series/1799902899?i=1799902900"></iframe><h1 id="About-Damn-Time-Lizzo"><a href="#About-Damn-Time-Lizzo" class="headerlink" title="About Damn Time - Lizzo"></a>About Damn Time - Lizzo</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/about-damn-time/1778462224?i=1778464014"></iframe><h1 id="在梅边-王力宏"><a href="#在梅边-王力宏" class="headerlink" title="在梅边 - 王力宏"></a>在梅边 - 王力宏</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/by-the-plum-blossom/1134353291?i=1134353769"></iframe><h1 id="放开你的心-王力宏"><a href="#放开你的心-王力宏" class="headerlink" title="放开你的心 - 王力宏"></a>放开你的心 - 王力宏</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/by-the-plum-blossom/1134353291?i=1134353769"></iframe><h1 id="Safe-and-Sound-Capital-Cities"><a href="#Safe-and-Sound-Capital-Cities" class="headerlink" title="Safe and Sound - Capital Cities"></a>Safe and Sound - Capital Cities</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/safe-and-sound/1766005775?i=1766005776"></iframe>]]>
      </content:encoded>
    </item>
    <item>
      <title>Java使用JS引擎小记</title>
      <link>https://baofeidyz.com/2025/0e2be77168b5/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202503241008174.png"></p>
<p>最近做了一个小需求，需要解析js文件，获取一些配置项信息，并转存为properteis的格式。<br>目前技术栈是JDK15，并且因为这个版本踩了不少坑，记录一下。</p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <pubDate>Thu, 20 Mar 2025 08:42:56 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202503241008174.png"></p><p>最近做了一个小需求，需要解析js文件，获取一些配置项信息，并转存为properteis的格式。<br>目前技术栈是JDK15，并且因为这个版本踩了不少坑，记录一下。</p><span id="more"></span><h1 id="nashorn在jdk15被遗弃"><a href="#nashorn在jdk15被遗弃" class="headerlink" title="nashorn在jdk15被遗弃"></a>nashorn在jdk15被遗弃</h1><p>nashorn是JDK 15之前默认的js引擎，可以看看<a href="https://stackoverflow.com/questions/65265629/how-to-use-nashorn-in-java-15-and-later">stackoverflow</a>的这个讨论。</p><h1 id="nashorn-不支持-es6语法"><a href="#nashorn-不支持-es6语法" class="headerlink" title="nashorn 不支持 es6语法"></a>nashorn 不支持 es6语法</h1><p>JDK 17虽然不提供<code>nashorn</code>，但是可以单独增加依赖。我有尝试单独安装了<code>nashorn</code>的依赖，但是在解析的时候发现会因为<code>const</code>报错，我在<code>openjdk</code>官网查到了这个<a href="https://bugs.openjdk.org/browse/JDK-8024712">JDK-8024712</a>bug，另外还在<a href="https://www.w3schools.com/js/js_const.asp">w3schools</a>确认了一下，确实不支持，所以就放弃了<code>nashorn</code></p><p>在网上查了一下可以使用<code>GraalVM JavaScript Implementation</code>作为替代品。</p><h1 id="GraalVM-JavaScript-Implementation-最低支持JDK-17"><a href="#GraalVM-JavaScript-Implementation-最低支持JDK-17" class="headerlink" title="GraalVM JavaScript Implementation 最低支持JDK 17"></a>GraalVM JavaScript Implementation 最低支持JDK 17</h1><p>可以从<a href="https://www.graalvm.org/jdk17/reference-manual/js/">官网</a>查到其最低都是JDK 17了</p><p><img src="https://cdn.baofeidyz.com/img/202503201620233.png"></p><p>但是我目前技术栈选型的问题，没办法直接升JDK版本，会带来更多的问题，所以只能作罢。</p><h1 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h1><p>最终是用的低版本的<code>GraalVM JavaScript Implementation</code>，包名和版本号和JDK 17的都有差别，对应的应该是</p><figure class="highlight gradle"><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">implementation <span class="string">&#x27;org.graalvm.js:js:20.2.0&#x27;</span>  </span><br><span class="line">implementation <span class="string">&#x27;org.graalvm.js:js-scriptengine:20.2.0&#x27;</span></span><br></pre></td></tr></table></figure><p>demo：</p><blockquote><p>代码是在原来的基础上做了大量简化和脱敏，不保证可以直接运行哈</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.baofeidyz.demo;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.alibaba.fastjson.JSONObject;  </span><br><span class="line"><span class="keyword">import</span> com.alibaba.fastjson.parser.Feature;</span><br><span class="line"><span class="keyword">import</span> org.graalvm.polyglot.Context;  </span><br><span class="line"><span class="keyword">import</span> org.graalvm.polyglot.Value;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Map&lt;String, Object&gt; <span class="title function_">parse</span><span class="params">(String content)</span> &#123;  </span><br><span class="line">        <span class="keyword">try</span> (  </span><br><span class="line">                <span class="type">Context</span> <span class="variable">context</span> <span class="operator">=</span> Context.newBuilder(<span class="string">&quot;js&quot;</span>)  </span><br><span class="line">                        .allowAllAccess(<span class="literal">true</span>)  </span><br><span class="line">                        .build()) &#123;  </span><br><span class="line">            <span class="comment">// 手动定义 module 和 exports  （这是因为我解析的js文件是配置文件，没有module和exports，为了避免报错，所以手动加了）</span></span><br><span class="line">            context.getBindings(<span class="string">&quot;js&quot;</span>).putMember(<span class="string">&quot;module&quot;</span>, context.eval(<span class="string">&quot;js&quot;</span>, <span class="string">&quot;(&#123;&#125;)&quot;</span>));  </span><br><span class="line">            context.getBindings(<span class="string">&quot;js&quot;</span>).putMember(<span class="string">&quot;exports&quot;</span>, context.eval(<span class="string">&quot;js&quot;</span>, <span class="string">&quot;(&#123;&#125;)&quot;</span>));  </span><br><span class="line">            <span class="comment">// 执行 JavaScript 代码  </span></span><br><span class="line">            context.eval(<span class="string">&quot;js&quot;</span>, content);  </span><br><span class="line">            <span class="comment">// 获取 exports 对象  </span></span><br><span class="line">            <span class="type">Value</span> <span class="variable">exports</span> <span class="operator">=</span> context.getBindings(<span class="string">&quot;js&quot;</span>).getMember(<span class="string">&quot;GLOBAL_CONFIG&quot;</span>);  </span><br><span class="line">            <span class="comment">// 解析 JSON 字符串</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">exports</span> != <span class="literal">null</span> &amp;&amp; !<span class="keyword">exports</span>.isNull()) &#123;</span><br><span class="line">                <span class="comment">// 用 JSON.stringify 正确转换对象为 JSON 字符串</span></span><br><span class="line">                json = context.eval(<span class="string">&quot;js&quot;</span>, <span class="string">&quot;JSON.stringify&quot;</span>).execute(<span class="keyword">exports</span>).asString();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> JSONObject.parseObject(json, Feature.OrderedField);</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><h1 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h1><p>我有注意到hutools也有提供一些类和方法去调用js引擎，前期我确实是想通过hutools套一下的，后面觉得意义不是特别大，就直接用了。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>歌单推荐-2025年2月</title>
      <link>https://baofeidyz.com/song-recommendation-2025-02/</link>
      <description>
        <![CDATA[<p><img src="https://a5.mzstatic.com/us/r1000/0/Music115/v4/f3/99/31/f399318c-3f0d-bfd5-7a69-0b78b22a90df/075679921338.jpg"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/Apple-Music/">Apple Music</category>
      <category domain="https://baofeidyz.com/tags/AppleMusic/">AppleMusic</category>
      <category domain="https://baofeidyz.com/tags/%E6%AD%8C%E5%8D%95%E6%8E%A8%E8%8D%90/">歌单推荐</category>
      <pubDate>Mon, 03 Mar 2025 16:25:47 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://a5.mzstatic.com/us/r1000/0/Music115/v4/f3/99/31/f399318c-3f0d-bfd5-7a69-0b78b22a90df/075679921338.jpg"></p><span id="more"></span><p>2025年歌单链接：<a href="https://music.apple.com/tr/playlist/2025/pl.u-jV89bXVIDNxA47W">2025年歌单</a></p><blockquote><p>歌单链接没有办法直接打开，需要复制链接到iMessage消息中，然后点击才会跳转。</p></blockquote><h1 id="不重逢-华晨宇"><a href="#不重逢-华晨宇" class="headerlink" title="不重逢 - 华晨宇"></a>不重逢 - 华晨宇</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E4%B8%8D%E9%87%8D%E9%80%A2/1789292336?i=1789292725"></iframe><h1 id="If-We-Ever-Broke-Up-Mae-Stephens"><a href="#If-We-Ever-Broke-Up-Mae-Stephens" class="headerlink" title="If We Ever Broke Up - Mae Stephens"></a>If We Ever Broke Up - Mae Stephens</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/if-we-ever-broke-up/1669378794?i=1669378800"></iframe><h1 id="Down-Bad-Taylor-Swift"><a href="#Down-Bad-Taylor-Swift" class="headerlink" title="Down Bad - Taylor Swift"></a>Down Bad - Taylor Swift</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/down-bad/1742057774?i=1742058080"></iframe><h1 id="LUNCH-Billie-Eilish"><a href="#LUNCH-Billie-Eilish" class="headerlink" title="LUNCH - Billie Eilish"></a>LUNCH - Billie Eilish</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/lunch/1739659134?i=1739659140"></iframe><h1 id="Satisfied"><a href="#Satisfied" class="headerlink" title="Satisfied"></a>Satisfied</h1><blockquote><p>来自音乐剧《汉密尔顿》中的一个节选，推荐大家去看一下这部分音乐剧</p></blockquote><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/satisfied/1025210938?i=1025212459"></iframe><h1 id="我杯茶-陈奕迅"><a href="#我杯茶-陈奕迅" class="headerlink" title="我杯茶 - 陈奕迅"></a>我杯茶 - 陈奕迅</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E6%88%91%E6%9D%AF%E8%8C%B6-feat-%E8%8E%AB%E6%96%87%E8%94%9A/1604745788?i=1604746307"></iframe><h1 id="Don’t-Ask-J-Sheon"><a href="#Don’t-Ask-J-Sheon" class="headerlink" title="Don’t Ask - J.Sheon"></a>Don’t Ask - J.Sheon</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/dont-ask/1229125892?i=1229126596"></iframe><h1 id="荒废的一天-新裤子"><a href="#荒废的一天-新裤子" class="headerlink" title="荒废的一天 - 新裤子"></a>荒废的一天 - 新裤子</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E8%8D%92%E5%BA%9F%E7%9A%84%E4%B8%80%E5%A4%A9/1678665479?i=1678665487"></iframe><h1 id="夕子-Pharaoh"><a href="#夕子-Pharaoh" class="headerlink" title="夕子 - Pharaoh"></a>夕子 - Pharaoh</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E5%A4%95%E5%AD%90-feat-%E6%9D%A8%E6%B3%A0%E9%A3%8E/1537746124?i=1537747161"></iframe><h1 id="Used-To-Be-Young-Miley-Cyrus"><a href="#Used-To-Be-Young-Miley-Cyrus" class="headerlink" title="Used To Be Young - Miley Cyrus"></a>Used To Be Young - Miley Cyrus</h1><blockquote><p>词曲都非常恰如其分，MV拍得也很棒</p></blockquote><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/used-to-be-young/1702906527?i=1702906876"></iframe>]]>
      </content:encoded>
    </item>
    <item>
      <title>Unraid应用推荐</title>
      <link>https://baofeidyz.com/unraid-application-recommendation/</link>
      <description>
        <![CDATA[<h1 id="Community-Applications"><a href="#Community-Applications" class="headerlink" title="Community Applications"></a>Community Applications</h1><p>简称CA，属于是Unraid OS<strong>必装</strong>应用，推荐指数爆表。其功能就是提供了应用安装方式。</p>
<h1 id="Tailscale-Plugin"><a href="#Tailscale-Plugin" class="headerlink" title="Tailscale (Plugin)"></a>Tailscale (Plugin)</h1><p><img src="https://cdn.baofeidyz.com/img/202502071047330.png"></p>
<ul>
<li>推荐指数：🌟🌟🌟🌟🌟</li>
<li>功能介绍：tailscale是一款非常出色且免费的网络软件，多个设备登录并使用tailscale后，可以在多个设备之间建立内网连接。</li>
</ul>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Unraid/">Unraid</category>
      <pubDate>Fri, 07 Feb 2025 02:55:25 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="Community-Applications"><a href="#Community-Applications" class="headerlink" title="Community Applications"></a>Community Applications</h1><p>简称CA，属于是Unraid OS<strong>必装</strong>应用，推荐指数爆表。其功能就是提供了应用安装方式。</p><h1 id="Tailscale-Plugin"><a href="#Tailscale-Plugin" class="headerlink" title="Tailscale (Plugin)"></a>Tailscale (Plugin)</h1><p><img src="https://cdn.baofeidyz.com/img/202502071047330.png"></p><ul><li>推荐指数：🌟🌟🌟🌟🌟</li><li>功能介绍：tailscale是一款非常出色且免费的网络软件，多个设备登录并使用tailscale后，可以在多个设备之间建立内网连接。</li></ul><span id="more"></span><h1 id="Alist"><a href="#Alist" class="headerlink" title="Alist"></a>Alist</h1><p><img src="https://cdn.baofeidyz.com/img/202502071048534.png"></p><ul><li>推荐指数：🌟🌟🌟</li><li>功能介绍：alist本身就是一个很出名的应用，提供了多个网盘的挂载方式。推荐同时挂载Unraid OS本地磁盘路径，然后通过alist提供的跨盘复制功能来实现远程下载</li></ul><h1 id="homeassistant"><a href="#homeassistant" class="headerlink" title="homeassistant"></a>homeassistant</h1><p><img src="https://cdn.baofeidyz.com/img/202502071048242.png"></p><ul><li>推荐指数：🌟🌟🌟🌟</li><li>功能介绍：简称HA，是一个智能家居大集成工具，有着大量的三方组件，在一番折腾后，可以将全屋家居集成到一起管理。最近小米官方也下场做了HA的组件支持。同时还可以将HA中支持的设备转到Apple的HomeKit中。</li></ul><h1 id="icloudpd"><a href="#icloudpd" class="headerlink" title="icloudpd"></a>icloudpd</h1><p><img src="https://cdn.baofeidyz.com/img/202502071049433.png"></p><ul><li>推荐指数：🌟🌟🌟🌟</li><li>功能介绍：icloudpd是一款使用python编写，实现了苹果iCloud协议的开源软件，其作用是备份或转移iCloud照片。UnraidOS提供镜像是另外一个作者做了二次封装，在原有的基础上提供了通知等功能的镜像。</li></ul><h1 id="qbittorrent"><a href="#qbittorrent" class="headerlink" title="qbittorrent"></a>qbittorrent</h1><p><img src="https://cdn.baofeidyz.com/img/202502071049981.png"></p><ul><li>推荐指数：🌟🌟🌟🌟</li><li>功能介绍：P2P下载软件，不论你是BT玩家还是PT玩家都是必备</li></ul><h1 id="transmission"><a href="#transmission" class="headerlink" title="transmission"></a>transmission</h1><p><img src="https://cdn.baofeidyz.com/img/202502071052614.png"></p><ul><li>推荐指数：🌟🌟🌟</li><li>功能介绍：和qbittorrent类似，但是比qbittorrent多一个制作种子的功能</li></ul><h1 id="User-Scripts"><a href="#User-Scripts" class="headerlink" title="User Scripts"></a>User Scripts</h1><p><img src="https://cdn.baofeidyz.com/img/202502071053335.png"></p><ul><li>推荐指数：🌟🌟🌟</li><li>功能介绍：其实就是cron的图形化管理，更便于使用。一个比较特殊的用法是当你使用了UPS，可以写一个轮询检测是否存在断电情况，存在就尽快关机避免硬盘损坏。</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>歌单推荐-2025年1月</title>
      <link>https://baofeidyz.com/song-recommendation-2025-01/</link>
      <description>
        <![CDATA[<p><img src="https://a5.mzstatic.com/us/r1000/0/Music211/v4/8e/cf/f6/8ecff62c-ce12-cbd1-55ed-1613d857bd10/cover.jpg"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/Apple-Music/">Apple Music</category>
      <category domain="https://baofeidyz.com/tags/AppleMusic/">AppleMusic</category>
      <category domain="https://baofeidyz.com/tags/%E6%AD%8C%E5%8D%95%E6%8E%A8%E8%8D%90/">歌单推荐</category>
      <pubDate>Tue, 21 Jan 2025 07:13:22 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://a5.mzstatic.com/us/r1000/0/Music211/v4/8e/cf/f6/8ecff62c-ce12-cbd1-55ed-1613d857bd10/cover.jpg"></p><span id="more"></span><p>2025年歌单链接：<a href="https://music.apple.com/tr/playlist/2025/pl.u-jV89bXVIDNxA47W">2025年歌单</a></p><blockquote><p>歌单链接没有办法直接打开，需要复制链接到iMessage消息中，然后点击才会跳转。</p></blockquote><h1 id="小狗落回你身旁-动物园钉子户"><a href="#小狗落回你身旁-动物园钉子户" class="headerlink" title="小狗落回你身旁 - 动物园钉子户"></a>小狗落回你身旁 - 动物园钉子户</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E5%B0%8F%E7%8B%97%E8%90%BD%E5%9B%9E%E4%BD%A0%E8%BA%AB%E6%97%81/1641239534?i=1641239995"></iframe><h1 id="我真的需要吗-汪峰"><a href="#我真的需要吗-汪峰" class="headerlink" title="我真的需要吗 - 汪峰"></a>我真的需要吗 - 汪峰</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E6%88%91%E7%9C%9F%E7%9A%84%E9%9C%80%E8%A6%81%E5%90%97-do-i-really-need-it/1789081099?i=1789081100"></iframe><h1 id="光阴副本-林俊杰"><a href="#光阴副本-林俊杰" class="headerlink" title="光阴副本 - 林俊杰"></a>光阴副本 - 林俊杰</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/above-the-fray/1784644584?i=1784644585"></iframe><h1 id="不如这样-陈奕迅"><a href="#不如这样-陈奕迅" class="headerlink" title="不如这样 - 陈奕迅"></a>不如这样 - 陈奕迅</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E4%B8%8D%E5%A6%82%E9%80%99%E6%A8%A3/542592136?i=542592142"></iframe><h1 id="孩子气-周笔畅"><a href="#孩子气-周笔畅" class="headerlink" title="孩子气 - 周笔畅"></a>孩子气 - 周笔畅</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E5%AD%A9%E5%AD%90%E6%B0%94like-a-child/1761801274?i=1761801869"></iframe><h1 id="赢者全拿-AMINATO-2CHANGE-KNOWTIS"><a href="#赢者全拿-AMINATO-2CHANGE-KNOWTIS" class="headerlink" title="赢者全拿 - AMINATO,2CHANGE &amp; KNOWTIS"></a>赢者全拿 - AMINATO,2CHANGE &amp; KNOWTIS</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E8%B4%8F%E8%80%85%E5%85%A8%E6%8B%BF/1787620751?i=1787620819"></iframe><h1 id="Pink-于贞"><a href="#Pink-于贞" class="headerlink" title="Pink - 于贞"></a>Pink - 于贞</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/pink/1784668775?i=1784669029"></iframe><h1 id="超过时间的海-Galaxy-Express"><a href="#超过时间的海-Galaxy-Express" class="headerlink" title="超过时间的海 - Galaxy Express"></a>超过时间的海 - Galaxy Express</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/%E8%B6%8A%E8%BF%87%E6%97%B6%E9%97%B4%E7%9A%84%E6%B5%B7-launch/1781841788?i=1781842191"></iframe><h1 id="Last-Days-She-Her-Her-Hers"><a href="#Last-Days-She-Her-Her-Hers" class="headerlink" title="Last Days - She Her Her Hers"></a>Last Days - She Her Her Hers</h1><iframe allow="autoplay *; encrypted-media *;" frameborder="0" height="150" style="width:100%;max-width:660px;overflow:hidden;background:transparent;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/tr/album/last-days/1773944511?i=1773944523"></iframe>]]>
      </content:encoded>
    </item>
    <item>
      <title>PDF元数据解析：流对象和过滤器</title>
      <link>https://baofeidyz.com/2024/8e8edebf287d/</link>
      <description>
        <![CDATA[<p>PDF主要由Objects、File structure、Document structure、Content streams组成。其中Objects又细分为：</p>
<ul>
<li>Boolean objects</li>
<li>Numeric objects</li>
<li>String objects</li>
<li>Name objects</li>
<li>Array objects</li>
<li>Dictionary objects</li>
<li>Stream objects</li>
<li>Null object</li>
<li>Indirect objects<br>这篇博客主要是介绍一下Stream objects在PDF ISO标准文件中的信息以及itext core代码实现。</li>
</ul>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/PDF/">PDF</category>
      <pubDate>Mon, 09 Dec 2024 10:19:27 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>PDF主要由Objects、File structure、Document structure、Content streams组成。其中Objects又细分为：</p><ul><li>Boolean objects</li><li>Numeric objects</li><li>String objects</li><li>Name objects</li><li>Array objects</li><li>Dictionary objects</li><li>Stream objects</li><li>Null object</li><li>Indirect objects<br>这篇博客主要是介绍一下Stream objects在PDF ISO标准文件中的信息以及itext core代码实现。</li></ul><span id="more"></span><h1 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h1><p>A stream object, like a string object, is a sequence of bytes. Furthermore, a stream may be of unlimited<br>length, whereas a string shall be subject to an implementation limit. For this reason, objects with<br>potentially large amounts of data, such as images and page descriptions, shall be represented as<br>streams.<br>这段话的意思是，流对象向较于字符而言，没有长度限制，因此对于一些有大量数据的对象，比如图片或者页面描述等信息时，就需要用到流对象。</p><p>我这边随便找一个拥有流对象的pdf，然后通过我在之前的博客中介绍的方法（见<a href="https://baofeidyz.com/2024/f2f4221aa1fd/">PDF元数据解析</a>)，可以直观的看到一个大致的流对象结构：<br><img src="https://cdn.baofeidyz.com/img/202412091749726.png"></p><p>从途中我们可以看到有一个非常明确的开始和结束标记，即</p><figure class="highlight plaintext"><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">stream</span><br><span class="line">endstream</span><br></pre></td></tr></table></figure><p>然后中间是一段乱码。需要补充说明一下，这里的乱码是因为我是以txt格式直接打开，而我使用的文本编辑器显然是不支持PDF流对象预览的。</p><h1 id="过滤器"><a href="#过滤器" class="headerlink" title="过滤器"></a>过滤器</h1><p>过滤器是我这篇博客主要想记录和分享的内容。如果你仔细观察上图，会发现这行记录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;&lt;/Filter/FlateDecode/Length 3365&gt;&gt;stream</span><br></pre></td></tr></table></figure><p>在<code>Filter</code>之后，紧跟着一个<code>FlateDecode</code>，相信你一定能觉察到这里面是有一定的关联的，但具体是什么可能就不太清楚，这正是我想分享的内容。<br>在PDF标准文档中，有单独的一个章节去讲解这个过滤器结构。虽然名字是叫Filter，但实际上我个人感觉更类似于编码器或解码器的作用。</p><p>PDF标准文档中一共提到了10种标准过滤器，我这里主要分享两种过滤器（未来会继续填坑）：</p><ul><li>FlateDecode</li><li>DCTDecode</li></ul><h2 id="FlateDecode"><a href="#FlateDecode" class="headerlink" title="FlateDecode"></a>FlateDecode</h2><p>中文翻译过来叫平面解码，其来自PDF 1.2，解压缩时会使用 zlib&#x2F;deflate 压缩方法编码的数据，再现原始文本或二进制数据。</p><h2 id="DCTDecode"><a href="#DCTDecode" class="headerlink" title="DCTDecode"></a>DCTDecode</h2><p>对使用基于 JPEG 标准 (ISO&#x2F;IEC 10918) 的 DCT（离散余弦变换）技术编码的数据进行解压缩，再现近似原始数据的图像样本数据。</p><blockquote><p>Decompresses data encoded using a DCT (discrete cosine transform) technique based on the JPEG standard (ISO&#x2F;IEC 10918), reproducing image sample data that approximates the original data.</p></blockquote><p>最常见的就是PDF中插入了一张图片，这张图片往往就是采用的DCT，我使用了<a href="https://baofeidyz.com/2024/f2f4221aa1fd/#%E6%96%B9%E6%B3%95%E4%BA%8C%EF%BC%9A%E4%BD%BF%E7%94%A8itext-rups">RUPS</a>解析了一个PDF文件：<br><img src="https://cdn.baofeidyz.com/img/202412091803295.png"><br>在左侧可以看到这个流对象的Filter为DCTDecode，在右下角的Stream预览框中可以看到其对应的预览效果，同时因为<a href="https://baofeidyz.com/2024/f2f4221aa1fd/#%E6%96%B9%E6%B3%95%E4%BA%8C%EF%BC%9A%E4%BD%BF%E7%94%A8itext-rups">RUPS</a>实现了DCTDecode，我们还可以导出为一个独立的图片文件。</p><h1 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h1><h2 id="itext-core-中关于Filters的实现"><a href="#itext-core-中关于Filters的实现" class="headerlink" title="itext core 中关于Filters的实现"></a>itext core 中关于Filters的实现</h2><p>对于我个人来说，我学习PDF标准文件的主要目的还是为了更好的编码，所以当我知道PDF有这么一个过滤器设计的时候，我第一时间是想去了解itext中对应的实现逻辑。<br>不出意料，itext core在实现时，抽了一个interface：<a href="https://github.com/itext/itext-java/blob/develop/kernel/src/main/java/com/itextpdf/kernel/pdf/filters/IFilterHandler.java">IFilterHandler</a><br>其代码也非常简洁：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/*  </span></span><br><span class="line"><span class="comment">    This file is part of the iText (R) project.    Copyright (c) 1998-2024 Apryse Group NV    Authors: Apryse Software.  </span></span><br><span class="line"><span class="comment">    This program is offered under a commercial and under the AGPL license.    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.  </span></span><br><span class="line"><span class="comment">    AGPL licensing:    This program is free software: you can redistribute it and/or modify    it under the terms of the GNU Affero General Public License as published by    the Free Software Foundation, either version 3 of the License, or    (at your option) any later version.  </span></span><br><span class="line"><span class="comment">    This program is distributed in the hope that it will be useful,    but WITHOUT ANY WARRANTY; without even the implied warranty of    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    GNU Affero General Public License for more details.  </span></span><br><span class="line"><span class="comment">    You should have received a copy of the GNU Affero General Public License    along with this program.  If not, see &lt;https://www.gnu.org/licenses/&gt;. */</span><span class="keyword">package</span> com.itextpdf.kernel.pdf.filters;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> com.itextpdf.kernel.pdf.PdfDictionary;  </span><br><span class="line"><span class="keyword">import</span> com.itextpdf.kernel.pdf.PdfName;  </span><br><span class="line"><span class="keyword">import</span> com.itextpdf.kernel.pdf.PdfObject;  </span><br><span class="line">  </span><br><span class="line"><span class="comment">/**  </span></span><br><span class="line"><span class="comment"> * The main interface for creating a new &#123;<span class="doctag">@code</span> FilterHandler&#125;  </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">IFilterHandler</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">/**  </span></span><br><span class="line"><span class="comment">     * Decode the byte[] using the provided filterName.     *     * <span class="doctag">@param</span> b                the bytes that need to be decoded  </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> filterName       PdfName of the filter  </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> decodeParams     decode parameters  </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> streamDictionary the dictionary of the stream. Can contain additional information needed to decode the  </span></span><br><span class="line"><span class="comment">     *                         byte[].     * <span class="doctag">@return</span> decoded byte array  </span></span><br><span class="line"><span class="comment">     */</span>    <span class="type">byte</span>[] decode(<span class="type">byte</span>[] b, PdfName filterName, PdfObject decodeParams, PdfDictionary streamDictionary);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里涉及到四个参数：</p><ul><li>b：PDF流对象的字节数组</li><li>filterName：对应的策略标识，通过分析方法的调用关系，我们可以得知这个主要是在<code>PdfReader</code>中去实现解析的，传到这个方法中只是为了做一个标识</li><li>decodeParams：这个参数其实是因为不同的Filter的实现逻辑有所不同。我们都知道PDF是支持加密的，PDF 1.5中新增了Crypt，这个参数就是其密钥。当然还有其它的Filter也需要这个参数，具体后面再展开分享</li><li>streamDictionary：主要是为了从目录结构信息中获取更多的信息，有点我们编码中常用的类似上下文信息</li></ul><p>接着我们看看这个interface的实现：<br><img src="https://cdn.baofeidyz.com/img/202412091815599.png"></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></pre></td><td class="code"><pre><span class="line"><span class="comment">/*  </span></span><br><span class="line"><span class="comment">    This file is part of the iText (R) project.    Copyright (c) 1998-2024 Apryse Group NV    Authors: Apryse Software.  </span></span><br><span class="line"><span class="comment">    This program is offered under a commercial and under the AGPL license.    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.  </span></span><br><span class="line"><span class="comment">    AGPL licensing:    This program is free software: you can redistribute it and/or modify    it under the terms of the GNU Affero General Public License as published by    the Free Software Foundation, either version 3 of the License, or    (at your option) any later version.  </span></span><br><span class="line"><span class="comment">    This program is distributed in the hope that it will be useful,    but WITHOUT ANY WARRANTY; without even the implied warranty of    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    GNU Affero General Public License for more details.  </span></span><br><span class="line"><span class="comment">    You should have received a copy of the GNU Affero General Public License    along with this program.  If not, see &lt;https://www.gnu.org/licenses/&gt;. */</span><span class="keyword">package</span> com.itextpdf.kernel.pdf;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> com.itextpdf.kernel.pdf.filters.IFilterHandler;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> java.io.ByteArrayOutputStream;  </span><br><span class="line">  </span><br><span class="line"><span class="comment">/**  </span></span><br><span class="line"><span class="comment"> * Handles memory limits aware processing. * * <span class="doctag">@see</span> MemoryLimitsAwareHandler  </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">MemoryLimitsAwareFilter</span> <span class="keyword">implements</span> <span class="title class_">IFilterHandler</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">/**  </span></span><br><span class="line"><span class="comment">     * Creates a &#123;<span class="doctag">@link</span> MemoryLimitsAwareOutputStream&#125; which will be used for decompression of the passed pdf stream.  </span></span><br><span class="line"><span class="comment">     *     * <span class="doctag">@param</span> streamDictionary the pdf stream which is going to be decompressed.  </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> the &#123;<span class="doctag">@link</span> ByteArrayOutputStream&#125; which will be used for decompression of the passed pdf stream  </span></span><br><span class="line"><span class="comment">     */</span>    </span><br><span class="line">     <span class="keyword">public</span> ByteArrayOutputStream <span class="title function_">enableMemoryLimitsAwareHandler</span><span class="params">(PdfDictionary streamDictionary)</span> &#123;  </span><br><span class="line">        <span class="type">MemoryLimitsAwareOutputStream</span> <span class="variable">outputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MemoryLimitsAwareOutputStream</span>();  </span><br><span class="line">        <span class="type">MemoryLimitsAwareHandler</span> <span class="variable">memoryLimitsAwareHandler</span> <span class="operator">=</span> <span class="literal">null</span>;  </span><br><span class="line">        <span class="keyword">if</span> (<span class="literal">null</span> != streamDictionary.getIndirectReference()) &#123;  </span><br><span class="line">            memoryLimitsAwareHandler = streamDictionary.getIndirectReference().getDocument().memoryLimitsAwareHandler;  </span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;  </span><br><span class="line">            <span class="comment">// We do not reuse some static instance because one can process pdfs in different threads.  </span></span><br><span class="line">            memoryLimitsAwareHandler = <span class="keyword">new</span> <span class="title class_">MemoryLimitsAwareHandler</span>();  </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="keyword">if</span> (<span class="literal">null</span> != memoryLimitsAwareHandler &amp;&amp; memoryLimitsAwareHandler.considerCurrentPdfStream) &#123;  </span><br><span class="line">            outputStream.setMaxStreamSize(memoryLimitsAwareHandler.getMaxSizeOfSingleDecompressedPdfStream());  </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="keyword">return</span> outputStream;  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是一个抽象类，封装了一个公用的方法以减少重复代码，这也是我常用的代码结构，非常不错的结构，安利给所有javaer。</p><p>这里我就不展开了，大致就是做了一个内存限制以避免OOM</p><h1 id="关联博客"><a href="#关联博客" class="headerlink" title="关联博客"></a>关联博客</h1><ul><li><a href="https://baofeidyz.com/tags/PDF/">PDF专栏</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>脚本分享-使用ffmepg对视频截图</title>
      <link>https://baofeidyz.com/2024/b4f470b7780b/</link>
      <description>
        <![CDATA[<h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>依赖于ffmpeg，允许传递起始时间、截图间隔、截图数量、视频文件地址等四个参数，其中时间单位为秒。</p>
<p>我在Linux Alpine中使用，可能部分依赖在其它Linux环境中会遇到问题，欢迎评论告知。</p>
<h1 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h1><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">sh snapshot.sh 600 800 6 Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265.mkv</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>你会得到六个文件，文件命名如下：</p>
<figure class="highlight plaintext"><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">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_001.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_002.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_003.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_004.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_005.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_006.jpg</span><br></pre></td></tr></table></figure>

<p><img src="https://cdn.baofeidyz.com/img/202412061031760.jpg"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Shell/">Shell</category>
      <category domain="https://baofeidyz.com/tags/%E8%84%9A%E6%9C%AC/">脚本</category>
      <category domain="https://baofeidyz.com/tags/ffmepg/">ffmepg</category>
      <pubDate>Fri, 06 Dec 2024 02:16:22 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>依赖于ffmpeg，允许传递起始时间、截图间隔、截图数量、视频文件地址等四个参数，其中时间单位为秒。</p><p>我在Linux Alpine中使用，可能部分依赖在其它Linux环境中会遇到问题，欢迎评论告知。</p><h1 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h1><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">sh snapshot.sh 600 800 6 Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265.mkv</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>你会得到六个文件，文件命名如下：</p><figure class="highlight plaintext"><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">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_001.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_002.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_003.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_004.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_005.jpg</span><br><span class="line">Dune.Part.Two.2024.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.H265_frame_006.jpg</span><br></pre></td></tr></table></figure><p><img src="https://cdn.baofeidyz.com/img/202412061031760.jpg"></p><span id="more"></span><p><img src="https://cdn.baofeidyz.com/img/202412061031759.jpg"><br><img src="https://cdn.baofeidyz.com/img/202412061031758.jpg"><br><img src="https://cdn.baofeidyz.com/img/202412061031757.jpg"><br><img src="https://cdn.baofeidyz.com/img/202412061031756.jpg"><br><img src="https://cdn.baofeidyz.com/img/202412061031754.jpg"></p><h1 id="脚本文件"><a href="#脚本文件" class="headerlink" title="脚本文件"></a>脚本文件</h1><p><code>snapshot.sh</code></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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查参数个数是否满足要求</span></span><br><span class="line"><span class="keyword">if</span> [ <span class="variable">$#</span> -lt 4 ]; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;用法: sh ffmepg-snapshot.sh 起始时间 截图间隔 截图数量 视频文件&quot;</span></span><br><span class="line">  <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取起始时间、截图间隔和截图数量</span></span><br><span class="line">start_time=<span class="variable">$1</span>       <span class="comment"># 起始时间</span></span><br><span class="line">interval=<span class="variable">$2</span>         <span class="comment"># 截图间隔</span></span><br><span class="line">num_images=<span class="variable">$3</span>       <span class="comment"># 截图数量</span></span><br><span class="line">input_video=<span class="variable">$4</span>      <span class="comment"># 视频文件</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查起始时间、间隔和截图数量是否为正整数</span></span><br><span class="line"><span class="keyword">if</span> ! <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$start_time</span>&quot;</span> | grep -qE <span class="string">&#x27;^[0-9]+$&#x27;</span> || ! <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$interval</span>&quot;</span> | grep -qE <span class="string">&#x27;^[0-9]+$&#x27;</span> || ! <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$num_images</span>&quot;</span> | grep -qE <span class="string">&#x27;^[0-9]+$&#x27;</span>; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;错误：起始时间、时间间隔和截图数量必须是正整数!&quot;</span></span><br><span class="line">  <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查视频文件是否存在</span></span><br><span class="line"><span class="keyword">if</span> [ ! -f <span class="string">&quot;<span class="variable">$input_video</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;错误：视频文件 <span class="variable">$input_video</span> 不存在!&quot;</span></span><br><span class="line">  <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取视频文件名（去掉扩展名）</span></span><br><span class="line">filename=$(<span class="built_in">basename</span> -- <span class="string">&quot;<span class="variable">$input_video</span>&quot;</span>)</span><br><span class="line"><span class="built_in">basename</span>=<span class="string">&quot;<span class="variable">$&#123;filename%.*&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 提取图像</span></span><br><span class="line">i=0</span><br><span class="line"><span class="keyword">while</span> [ <span class="variable">$i</span> -lt <span class="variable">$num_images</span> ]; <span class="keyword">do</span></span><br><span class="line">  <span class="comment"># 使用 expr 进行数学计算</span></span><br><span class="line">  timestamp=$(<span class="built_in">expr</span> <span class="variable">$start_time</span> + <span class="variable">$i</span> \* <span class="variable">$interval</span>)</span><br><span class="line"></span><br><span class="line">  <span class="comment"># 确保时间戳不为空并且格式正确</span></span><br><span class="line">  <span class="keyword">if</span> [ -z <span class="string">&quot;<span class="variable">$timestamp</span>&quot;</span> ] || [ <span class="string">&quot;<span class="variable">$timestamp</span>&quot;</span> -lt 0 ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;错误：无效的时间戳 <span class="variable">$timestamp</span>，跳过此帧。&quot;</span></span><br><span class="line">    <span class="built_in">continue</span></span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 输出计算的时间戳</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;提取图像，时间戳：<span class="variable">$timestamp</span> 秒&quot;</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment"># 使用 ffmpeg 提取图像</span></span><br><span class="line">  ffmpeg -ss <span class="string">&quot;<span class="variable">$timestamp</span>&quot;</span> -i <span class="string">&quot;<span class="variable">$input_video</span>&quot;</span> -vframes 1 -q:v 2 <span class="string">&quot;<span class="variable">$&#123;basename&#125;</span>_frame_<span class="subst">$(printf <span class="string">&quot;%03d&quot;</span> $(expr $i + 1)</span>).jpg&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 增加图片索引</span></span><br><span class="line">  i=$(<span class="built_in">expr</span> <span class="variable">$i</span> + 1)</span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;已从视频 <span class="variable">$input_video</span> 提取 <span class="variable">$num_images</span> 张图像，文件名格式为 <span class="variable">$&#123;basename&#125;</span>_frame_001.jpg, <span class="variable">$&#123;basename&#125;</span>_frame_002.jpg, ...&quot;</span></span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>解决Docker镜像Mediainfo使用WebUI选择文件时，无法展示中文的问题</title>
      <link>https://baofeidyz.com/2024/77ac4e30964c/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202412051428824.png"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Docker/">Docker</category>
      <category domain="https://baofeidyz.com/tags/Mediainfo/">Mediainfo</category>
      <category domain="https://baofeidyz.com/tags/OpenSource/">OpenSource</category>
      <pubDate>Thu, 05 Dec 2024 07:05:54 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202412051428824.png"></p><span id="more"></span><blockquote><p>2024年12月16日更新：经过和作者的沟通，目前docker镜像最新版本已解决这个问题，需要在启动参数中增加环境变量<code>ENABLE_CJK_FONT</code>，值为1即可。当然如果你用这个方案会遇到一些错误，你也可以尝试用我这篇博客中的方法来解决。</p></blockquote><h1 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h1><p>镜像 jlesage&#x2F;mediainfo 用的是Linux Alpine，通过<code>echo &quot;中文&quot;</code>测试确认其实是支持中文字符集的；</p><p>另外尝试检查是不是因为中文字体没有导致的，一查果然是。</p><h1 id="问题解决"><a href="#问题解决" class="headerlink" title="问题解决"></a>问题解决</h1><p>进入终端后，</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apk add ttf-dejavu font-noto-cjk</span><br></pre></td></tr></table></figure><p>然后重启容器即可（切记不是重新创建容器）<br><img src="https://cdn.baofeidyz.com/img/202412051432463.png"></p><h1 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h1><p>因为容器是由原始镜像创建而来，后续升级镜像版本时，必然会重现此问题，想根本解决，其实就需要去调整镜像源，所以我给作者提交了<a href="https://github.com/jlesage/docker-mediainfo/pull/10">Pull Request</a>，等作者合并后这个问题就不会再出现了。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>周报-第7期：全平台免费抓包工具reqable、FastDFS实现文件下载重命名、Maven切换到Gradle</title>
      <link>https://baofeidyz.com/2024/da7e26352bd8/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202411151743587.png"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Maven/">Maven</category>
      <category domain="https://baofeidyz.com/tags/%E5%91%A8%E6%8A%A5/">周报</category>
      <category domain="https://baofeidyz.com/tags/reqable/">reqable</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%93%E5%8C%85/">抓包</category>
      <category domain="https://baofeidyz.com/tags/FastDFS/">FastDFS</category>
      <category domain="https://baofeidyz.com/tags/Gradle/">Gradle</category>
      <pubDate>Thu, 14 Nov 2024 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202411151743587.png"></p><span id="more"></span><h1 id="全平台免费抓包工具reqable"><a href="#全平台免费抓包工具reqable" class="headerlink" title="全平台免费抓包工具reqable"></a>全平台免费抓包工具reqable</h1><p>了解了一下原来是叫HttpCanary，现在官网地址是：<a href="https://reqable.com/">https://reqable.com/</a>，对于macOS还提供了homebrew的安装方案</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install reqable</span><br></pre></td></tr></table></figure><p>我最看中的就是免费的https抓包，竞品fiddler、proxyman价格都比较高，我个人来说偶尔用一下抓包工具，所以能有免费的替代品自然是更好的。<br>但实际体验来说，reqable非常成熟的交互，还支持了二级代理，抓包也有对应的图标展示，推荐给所有需要抓包场景的朋友试一下，价格也不贵，喜欢就直接买断。</p><h1 id="FastDFS实现文件下载重命名"><a href="#FastDFS实现文件下载重命名" class="headerlink" title="FastDFS实现文件下载重命名"></a>FastDFS实现文件下载重命名</h1><blockquote><p>主要参考文章：<a href="https://cloud.tencent.com/developer/article/2129707">https://cloud.tencent.com/developer/article/2129707</a></p></blockquote><p>总的来说，只能是借助于nginx，通过请求url中增加文件名称，再通过nginx的函数修改response的header</p><p>这样浏览器就自动实现了报文解析</p><h1 id="Maven-切换到-Gradle"><a href="#Maven-切换到-Gradle" class="headerlink" title="Maven 切换到 Gradle"></a>Maven 切换到 Gradle</h1><p>找到一篇蛮好的文章，直接看原文吧：<a href="https://www.flydean.com/gradle-vs-maven/">https://www.flydean.com/gradle-vs-maven/</a></p><blockquote><p>原文如下：</p><h2 id="5-深入了解gradle和maven的区别"><a href="#5-深入了解gradle和maven的区别" class="headerlink" title="5. 深入了解gradle和maven的区别"></a>5. 深入了解gradle和maven的区别</h2><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>gradle和maven都可以用来构建java程序，甚至在某些情况下，两者还可以互相转换，那么他们两个的共同点和不同点是什么？我们如何在项目中选择使用哪种技术呢？一起来看看吧。</p><h2 id="gradle和maven的比较"><a href="#gradle和maven的比较" class="headerlink" title="gradle和maven的比较"></a>gradle和maven的比较</h2><p>虽然gradle和maven都可以作为java程序的构建工具。但是两者还是有很大的不同之处的。我们可以从下面几个方面来进行分析。</p><h3 id="可扩展性"><a href="#可扩展性" class="headerlink" title="可扩展性"></a>可扩展性</h3><p>Google选择gradle作为android的构建工具不是没有理由的，其中一个非常重要的原因就是因为gradle够灵活。一方面是因为gradle使用的是groovy或者kotlin语言作为脚本的编&gt; 写语言，这样极大的提高了脚本的灵活性，但是其本质上的原因是gradle的基础架构能够支持这种灵活性。</p><p>你可以使用gradle来构建native的C&#x2F;C++程序，甚至扩展到任何语言的构建。</p><p>相对而言，maven的灵活性就差一些，并且自定义起来也比较麻烦，但是maven的项目比较容易看懂，并且上手简单。</p><p>所以如果你的项目没有太多自定义构建需求的话还是推荐使用maven，但是如果有自定义的构建需求，那么还是投入gradle的怀抱吧。</p><h3 id="性能比较"><a href="#性能比较" class="headerlink" title="性能比较"></a>性能比较</h3><p>虽然现在大家的机子性能都比较强劲，好像在做项目构建的时候性能的优势并不是那么的迫切，但是对于大型项目来说，一次构建可能会需要很长的时间，尤其对于自动化构建和CI的环境&gt; 来说，当然希望这个构建是越快越好。</p><p>Gradle和Maven都支持并行的项目构建和依赖解析。但是gradle的三个特点让gradle可以跑的比maven快上一点：</p><ul><li>增量构建</li></ul><p>gradle为了提升构建的效率，提出了增量构建的概念，为了实现增量构建，gradle将每一个task都分成了三部分，分别是input输入，任务本身和output输出。下图是一个典型的&gt; java编译的task。</p><p><img src="https://cdn.baofeidyz.com/img/202504151753242.png"></p><p>以上图为例，input就是目标jdk的版本，源代码等，output就是编译出来的class文件。</p><p>增量构建的原理就是监控input的变化，只有input发送变化了，才重新执行task任务，否则gradle认为可以重用之前的执行结果。</p><p>所以在编写gradle的task的时候，需要指定task的输入和输出。</p><p>并且要注意只有会对输出结果产生变化的才能被称为输入，如果你定义了对初始结果完全无关的变量作为输入，则这些变量的变化会导致gradle重新执行task，导致了不必要的性能的损&gt; 耗。</p><p>还要注意不确定执行结果的任务，比如说同样的输入可能会得到不同的输出结果，那么这样的任务将不能够被配置为增量构建任务。</p><ul><li>构建缓存</li></ul><p>gradle可以重用同样input的输出作为缓存，大家可能会有疑问了，这个缓存和增量编译不是一个意思吗？</p><p>在同一个机子上是的，但是缓存可以跨机器共享.如果你是在一个CI服务的话，build cache将会非常有用。因为developer的build可以直接从CI服务器上面拉取构建结果，非常的方&gt; 便。</p><ul><li>Gradle守护进程</li></ul><p>gradle会开启一个守护进程来和各个build任务进行交互，优点就是不需要每次构建都初始化需要的组件和服务。</p><p>同时因为守护进程是一个一直运行的进程，除了可以避免每次JVM启动的开销之外，还可以缓存项目结构，文件，task和其他的信息，从而提升运行速度。</p><p>我们可以运行 gradle –status 来查看正在运行的daemons进程。</p><p>从Gradle 3.0之后，daemons是默认开启的，你可以使用 org.gradle.daemon&#x3D;false 来禁止daemons。</p><p>我们可以通过下面的几个图来直观的感受一下gradle和maven的性能比较：</p><ul><li>使用gradle和maven构建 Apache Commons Lang 3的比较：</li></ul><p><img src="https://cdn.baofeidyz.com/img/202504151753023.png"></p><ul><li>使用gradle和maven构建小项目（10个模块，每个模块50个源文件和50个测试文件）的比较：</li></ul><p><img src="https://cdn.baofeidyz.com/img/202504151754764.png"></p><ul><li>使用gradle和maven构建大项目（500个模块，每个模块100个源文件和100个测试文件）的比较：</li></ul><p><img src="https://cdn.baofeidyz.com/img/202504151754798.png"></p><p>可以看到gradle性能的提升是非常明显的。</p><h3 id="依赖的区别"><a href="#依赖的区别" class="headerlink" title="依赖的区别"></a>依赖的区别</h3><p>gralde和maven都可以本地缓存依赖文件，并且都支持依赖文件的并行下载。</p><p>在maven中只可以通过版本号来覆盖一个依赖项。而gradle更加灵活，你可以自定义依赖关系和替换规则，通过这些替换规则，gradle可以构建非常复杂的项目。</p><h2 id="从maven迁移到gradle"><a href="#从maven迁移到gradle" class="headerlink" title="从maven迁移到gradle"></a>从maven迁移到gradle</h2><p>因为maven出现的时间比较早，所以基本上所有的java项目都支持maven，但是并不是所有的项目都支持gradle。如果你有需要把maven项目迁移到gradle的想法，那么就一起来看看&gt; 吧。</p><p>根据我们之前的介绍，大家可以发现gradle和maven从本质上来说就是不同的，gradle通过task的DAG图来组织任务，而maven则是通过attach到phases的goals来执行任务。</p><p>虽然两者的构建有很大的不同，但是得益于gradle和maven相识的各种约定规则，从maven移植到gradle并不是那么难。</p><p>要想从maven移植到gradle，首先要了解下maven的build生命周期，maven的生命周期包含了clean，compile，test，package，verify，install和deploy这几个phase。</p><p>我们需要将maven的生命周期phase转换为gradle的生命周期task。这里需要使用到gradle的Base Plugin，Java Plugin和Maven Publish Plugin。</p><p>先看下怎么引入这三个plugin：</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">plugins &#123;</span><br><span class="line">    id <span class="string">&#x27;base&#x27;</span></span><br><span class="line">    id <span class="string">&#x27;java&#x27;</span></span><br><span class="line">    id <span class="string">&#x27;maven-publish&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>clean会被转换成为clean task，compile会被转换成为classes task，test会被转换成为test task，package会被转换成为assemble task，verify 会被转换成为check &gt; task，install会被转换成为 Maven Publish Plugin 中的publishToMavenLocal task，deploy 会被转换成为Maven Publish Plugin 中的publish task。</p><p>有了这些task之间的对应关系，我们就可以尝试进行maven到gradle的转换了。</p><h3 id="自动转换"><a href="#自动转换" class="headerlink" title="自动转换"></a>自动转换</h3><p>我们除了可以使用 gradle init 命令来创建一个gradle的架子之外，还可以使用这个命令来将maven项目转换成为gradle项目，gradle init命令会去读取pom文件，并将其转换成&gt; 为gradle项目。</p><h3 id="转换依赖"><a href="#转换依赖" class="headerlink" title="转换依赖"></a>转换依赖</h3><p>gradle和maven的依赖都包含了group ID, artifact ID 和版本号。两者本质上是一样的，只是形式不同，我们看一个转换的例子：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>log4j<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>log4j<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.2.12<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><p>上是一个maven的例子，我们看下gradle的例子怎写：</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">dependencies &#123;</span><br><span class="line">    implementation <span class="string">&#x27;log4j:log4j:1.2.12&#x27;</span>  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到gradle比maven写起来要简单很多。</p><p>注意这里的implementation实际上是由 Java Plugin 来实现的。</p><p>我们在maven的依赖中有时候还会用到scope选项，用来表示依赖的范围，我们看下这些范围该如何进行转换：</p><ul><li>compile：</li></ul><p>在gradle可以有两种配置来替换compile，我们可以使用implementation或者api。</p><p>前者在任何使用Java Plugin的gradle中都可以使用，而api只能在使用Java Library Plugin的项目中使用。</p><p>当然两者是有区别的，如果你是构建应用程序或者webapp，那么推荐使用implementation，如果你是在构建Java libraries，那么推荐使用api。</p><ul><li>runtime：</li></ul><p>可以替换成 runtimeOnly 。</p><ul><li>test：</li></ul><p>gradle中的test分为两种，一种是编译test项目的时候需要，那么可以使用testImplementation，一种是运行test项目的时候需要，那么可以使用testRuntimeOnly。</p><ul><li>provided：</li></ul><p>可以替换成为compileOnly。</p><ul><li>import：</li></ul><p>在maven中，import经常用在dependencyManagement中，通常用来从一个pom文件中导入依赖项，从而保证项目中依赖项目版本的一致性。</p><p>在gradle中，可以使用 platform() 或者 enforcedPlatform() 来导入pom文件：</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">dependencies &#123;</span><br><span class="line">    implementation <span class="title function_">platform</span><span class="params">(<span class="string">&#x27;org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE&#x27;</span>)</span> </span><br><span class="line"></span><br><span class="line">    implementation <span class="string">&#x27;com.google.code.gson:gson&#x27;</span> </span><br><span class="line">    implementation <span class="string">&#x27;dom4j:dom4j&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>比如上面的例子中，我们导入了spring-boot-dependencies。因为这个pom中已经定义了依赖项的版本号，所以我们在后面引入gson的时候就不需要指定版本号了。</p><p>platform和enforcedPlatform的区别在于，enforcedPlatform会将导入的pom版本号覆盖其他导入的版本号：</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">dependencies &#123;</span><br><span class="line">    <span class="comment">// import a BOM. The versions used in this file will override any other version found in the graph</span></span><br><span class="line">    implementation <span class="title function_">enforcedPlatform</span><span class="params">(<span class="string">&#x27;org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE&#x27;</span>)</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// define dependencies without versions</span></span><br><span class="line">    implementation <span class="string">&#x27;com.google.code.gson:gson&#x27;</span></span><br><span class="line">    implementation <span class="string">&#x27;dom4j:dom4j&#x27;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// this version will be overridden by the one found in the BOM</span></span><br><span class="line">    implementation <span class="string">&#x27;org.codehaus.groovy:groovy:1.8.6&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="转换repositories仓库"><a href="#转换repositories仓库" class="headerlink" title="转换repositories仓库"></a>转换repositories仓库</h3><p>gradle可以兼容使用maven或者lvy的repository。gradle没有默认的仓库地址，所以你必须手动指定一个。</p><p>你可以在gradle使用maven的仓库：</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">repositories &#123;</span><br><span class="line">    mavenCentral()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们还可以直接指定maven仓库的地址：</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">repositories &#123;</span><br><span class="line">    maven &#123;</span><br><span class="line">        url <span class="string">&quot;http://repo.mycompany.com/maven2&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果你想使用maven本地的仓库，则可以这样使用：</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">repositories &#123;</span><br><span class="line">    mavenLocal()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是mavenLocal是不推荐使用的，为什么呢？</p><p>mavenLocal只是maven在本地的一个cache，它包含的内容并不完整。比如说一个本地的maven repository module可能只包含了jar包文件，并没有包含source或者javadoc文&gt; 件。那么我们将不能够在gradle中查看这个module的源代码，因为gradle会首先在maven本地的路径中查找这个module。</p><p>并且本地的repository是不可信任的，因为里面的内容可以轻易被修改，并没有任何的验证机制。</p><h3 id="控制依赖的版本"><a href="#控制依赖的版本" class="headerlink" title="控制依赖的版本"></a>控制依赖的版本</h3><p>如果同一个项目中对同一个模块有不同版本的两个依赖的话，默认情况下Gradle会在解析完DAG之后，选择版本最高的那个依赖包。</p><p>但是这样做并不一定就是正确的， 所以我们需要自定义依赖版本的功能。</p><p>首先就是上面我们提到的使用platform()和enforcedPlatform() 来导入BOM（packaging类型是POM的）文件。</p><p>如果我们项目中依赖了某个module，而这个module又依赖了另外的module，我们叫做传递依赖。在这种情况下，如果我们希望控制传递依赖的版本，比如说将传递依赖的版本升级为一&gt; 个新的版本，那么可以使用dependency constraints：</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">dependencies &#123;</span><br><span class="line">    implementation <span class="string">&#x27;org.apache.httpcomponents:httpclient&#x27;</span></span><br><span class="line">    constraints &#123;</span><br><span class="line">        implementation(<span class="string">&#x27;org.apache.httpcomponents:httpclient:4.5.3&#x27;</span>) &#123;</span><br><span class="line">            because <span class="string">&#x27;previous versions have a bug impacting this application&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">        implementation(<span class="string">&#x27;commons-codec:commons-codec:1.11&#x27;</span>) &#123;</span><br><span class="line">            because <span class="string">&#x27;version 1.9 pulled from httpclient has bugs affecting this application&#x27;</span></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><blockquote><p>注意，dependency constraints只对传递依赖有效，如果上面的例子中commons-codec并不是传递依赖，那么将不会有任何影响。</p></blockquote><blockquote><p>同时 Dependency constraints需要Gradle Module Metadata的支持，也就是说只有你的module是发布在gradle中才支持这个特性，如果是发布在maven或者ivy中是不支持&gt; 的。</p></blockquote><p>上面讲的是传递依赖的版本升级。同样是传递依赖，如果本项目也需要使用到这个传递依赖的module，但是需要使用到更低的版本（因为默认gradle会使用最新的版本），就需要用到版&gt; 本降级了。</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></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">    implementation <span class="string">&#x27;org.apache.httpcomponents:httpclient:4.5.4&#x27;</span></span><br><span class="line">    implementation(<span class="string">&#x27;commons-codec:commons-codec&#x27;</span>) &#123;</span><br><span class="line">        version &#123;</span><br><span class="line">            strictly <span class="string">&#x27;1.9&#x27;</span></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>我们可以在implementation中指定特定的version即可。</p><p>strictly表示的是强制匹配特定的版本号，除了strictly之外，还有require，表示需要的版本号大于等于给定的版本号。prefer，如果没有指定其他的版本号，那么就使用prefer&gt; 这个。reject，拒绝使用这个版本。</p><p>除此之外，你还可以使用Java Platform Plugin来指定特定的platform，从而限制版本号。</p><p>最后看一下如何exclude一个依赖：</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">dependencies &#123;</span><br><span class="line">    implementation(<span class="string">&#x27;commons-beanutils:commons-beanutils:1.9.4&#x27;</span>) &#123;</span><br><span class="line">        exclude group: <span class="string">&#x27;commons-collections&#x27;</span>, <span class="keyword">module</span>: <span class="string">&#x27;commons-collections&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="多模块项目"><a href="#多模块项目" class="headerlink" title="多模块项目"></a>多模块项目</h3><p>maven中可以创建多模块项目：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">modules</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">module</span>&gt;</span>simple-weather<span class="tag">&lt;/<span class="name">module</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">module</span>&gt;</span>simple-webapp<span class="tag">&lt;/<span class="name">module</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">modules</span>&gt;</span></span><br></pre></td></tr></table></figure><p>我们可以在gradle中做同样的事情settings.gradle：</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">rootProject.name = <span class="string">&#x27;simple-multi-module&#x27;</span>  </span><br><span class="line"></span><br><span class="line">include <span class="string">&#x27;simple-weather&#x27;</span>, <span class="string">&#x27;simple-webapp&#x27;</span>  </span><br></pre></td></tr></table></figure><h3 id="profile和属性"><a href="#profile和属性" class="headerlink" title="profile和属性"></a>profile和属性</h3><p>maven中可以使用profile来区别不同的环境，在gradle中，我们可以定义好不同的profile文件，然后通过脚本来加载他们：</p><p>build.gradle：</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="keyword">if</span> (!hasProperty(<span class="string">&#x27;buildProfile&#x27;</span>)) ext.buildProfile = <span class="string">&#x27;default&#x27;</span>  </span><br><span class="line"></span><br><span class="line">apply from: <span class="string">&quot;profile-$&#123;buildProfile&#125;.gradle&quot;</span>  </span><br><span class="line"></span><br><span class="line">task greeting &#123;</span><br><span class="line">    doLast &#123;</span><br><span class="line">        println message  </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>profile-default.gradle：</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">ext.message = <span class="string">&#x27;foobar&#x27;</span>  </span><br></pre></td></tr></table></figure><p>profile-test.gradle：</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">ext.message = <span class="string">&#x27;testing 1 2 3&#x27;</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">&gt; gradle greeting</span><br><span class="line">foobar</span><br><span class="line"></span><br><span class="line">&gt; gradle -PbuildProfile=test greeting</span><br><span class="line">testing <span class="number">1</span> <span class="number">2</span> <span class="number">3</span></span><br></pre></td></tr></table></figure><h3 id="资源处理"><a href="#资源处理" class="headerlink" title="资源处理"></a>资源处理</h3><p>在maven中有一个process-resources阶段，可以执行resources:resources用来进行resource文件的拷贝操作。</p><p>在Gradle中的Java plugin的processResources task也可以做相同的事情。</p><p>比如我可以执行copy任务：</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">task <span class="title function_">copyReport</span><span class="params">(type: Copy)</span> &#123;</span><br><span class="line">    from <span class="title function_">file</span><span class="params">(<span class="string">&quot;$buildDir/reports/my-report.pdf&quot;</span>)</span></span><br><span class="line">    into <span class="title function_">file</span><span class="params">(<span class="string">&quot;$buildDir/toArchive&quot;</span>)</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><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">task <span class="title function_">copyPdfReportsForArchiving</span><span class="params">(type: Copy)</span> &#123;</span><br><span class="line">    from <span class="string">&quot;$buildDir/reports&quot;</span></span><br><span class="line">    include <span class="string">&quot;*.pdf&quot;</span></span><br><span class="line">    into <span class="string">&quot;$buildDir/toArchive&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然拷贝还有更加复杂的应用。这里就不详细讲解了。</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>SQL版本管理工具技术选型</title>
      <link>https://baofeidyz.com/2024/9c337aae9199/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202409291801939.png"></p>
<h1 id="需求点分析"><a href="#需求点分析" class="headerlink" title="需求点分析"></a>需求点分析</h1><ol>
<li>是否支持多类数据库以及脚本区分？</li>
<li>如何撤销变更？</li>
<li>版本是如何管理的？</li>
<li>费用问题？是否开源？开源协议是什么？</li>
</ol>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B/">技术选型</category>
      <category domain="https://baofeidyz.com/tags/SQL/">SQL</category>
      <pubDate>Sat, 28 Sep 2024 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202409291801939.png"></p><h1 id="需求点分析"><a href="#需求点分析" class="headerlink" title="需求点分析"></a>需求点分析</h1><ol><li>是否支持多类数据库以及脚本区分？</li><li>如何撤销变更？</li><li>版本是如何管理的？</li><li>费用问题？是否开源？开源协议是什么？</li></ol><span id="more"></span><h1 id="flyway-VS-liquibase-VS-bytebase"><a href="#flyway-VS-liquibase-VS-bytebase" class="headerlink" title="flyway VS liquibase VS bytebase"></a>flyway VS liquibase VS bytebase</h1><blockquote><p>其中bytebase我很早就放弃了，所以对比结果不全，重点还是放在了flyway和liquibase中</p></blockquote><table><thead><tr><th></th><th>flyway</th><th>liquibase</th><th>bytebase</th></tr></thead><tbody><tr><td>版本&amp;价格</td><td>- 🆓 Community 社区版<br>- 💰 Teams 团队版 $597&#x2F;每人每年<br>- 💰💰 Enterprise企业版 价格未知❓<br><br><a href="https://www.red-gate.com/products/flyway/">https://www.red-gate.com/products/flyway/</a></td><td>- 🆓 Open Source 开源版<br>- 💰 Liquebase Pro 专业版 价格未知❓<br><br><br><a href="https://www.liquibase.com/pricing">https://www.liquibase.com/pricing</a></td><td>- 🆓  ommunity 社区版<br>- 💰 Pro 专业版 $100&#x2F;每数据库实例每月<br>- 💰💰 enterprise 企业版本 价格未知❓<br><br><a href="https://www.bytebase.com/pricing/">https://www.bytebase.com/pricing/</a></td></tr><tr><td>功能清单</td><td>- 🆓 Flyway API&#x2F;CLI 核心功能<br>- 🆓 Flyway Desktop GUI Flyway桌面图形用户界面<br>- 🆓 6 basic commands: Migrate, Clean, Info, Validate, Baseline and Repair 6 个基本命令：迁移、清理、信息、验证、基线和修复<br>- 🆓 Support for current DB versions 支持当前数据库版本<br>- 🆓 Community support 社区支持<br>- 💰 <font color="red">Dry run 试运行</font><br>- 💰 <font color="red">Undo migration scripts 撤消迁移脚本</font><br>- 💰 Cherry pick<br>- 💰 Object-level versioning 对象级版本控制（仅限SQL Server、Oracle、PostgreSQL和MySQL）<br>- 💰 Built-in Git client 内置 Git 客户端<br>- 💰 Support for older DB versions  支持旧数据库版本<br>- 💰 Access to technical support  获得技术支持<br>- 💰💰 Change reports 变更报告（仅限SQL Server、Oracle、PostgreSQL和MySQL）<br>- 💰💰 Drift detection 漂移检测（仅限SQL Server、Oracle、PostgreSQL和MySQL）<br>- 💰💰 Create custom code analysis rules 创建自定义代码分析规则<br>- 💰💰 Create regular expression rules 创建正则表达式规则<br>- 💰💰 Migration script auto-generation 迁移脚本自动生成（仅限SQL Server、Oracle、PostgreSQL和MySQL）<br>- 💰💰 Schema comparison 架构比较（仅限SQL Server、Oracle、PostgreSQL和MySQL）<br>- 💰💰 Static data versioning 静态数据版本控制（仅限SQL Server、Oracle）<br>- 💰💰 Data comparison 数据对比（仅限SQL Server、Oracle）<br>- 💰💰 Support for extended set of DB versions 支持扩展的数据库版本集</td><td>- 🆓 Native SQL Dialect<br>- 🆓 Change format：SQL, XML, YAML, JSON<br>- 🆓 Change creation &amp; management：CLI or 3rd party automation tools<br>- 🆓 Smart database updates 自动确定并部署所有尚未部署的变更<br>- 🆓 Preconditions 前提条件<br>- 🆓 Set labels and contexts 设置标签和上下文<br>- 🆓 <font color="red">Change preview &#x2F; dry run 更改预览&#x2F;试运行</font><br>- 🆓 <font color="red">Rollback 回滚</font><br>- 🆓 Change generation：根据数据库生成更改集<br>- 🆓 Snapshot 快照：生成架构快照<br>- 🆓 Diff 差异：生成两个数据库之间的比较（或差异）以检测意外错误<br>- 🆓 Installation 安装：通过 CLI、API、Spring、Docker、Maven、Gradle、Ant、Debian&#x2F;Ubuntu 和 Red Hat&#x2F;CentOS 下载并安装 Liquibase<br>- 🆓 Deployment 部署：手动或通过自动化流程部署更改<br>- 🆓 Standard JDBC 标准 JDBC：通过 CLI 或属性文件传递凭据和连接属性<br>- 🆓 Command line &amp; properties file 命令行和属性文件：使用单个文件、CLI 或 Java 系统属性配置和指定详细信息，例如数据库连接和类路径信息<br>- 🆓 社区支持<br>- 💰 Targeted database updates 对数据库执行特定更改<br>- 💰 Advanced rollback<br>- 💰 Native executors 原生执行器：使用本机执行器 SQL<em>Plus、PSQL、SQLCMD 和 MongoSH 运行高级更改<br>- 💰 Stored logic 存储过程：在选定平台（包括 Oracle SQL</em>Plus）上使用存储过程代码<br>- 💰 Advanced change generation：高级变更生成：根据数据库生成更改集（包括存储过程）<br>- 💰 Advanced snapshot 高级快照：生成架构快照，包括具有高级功能的存储过程<br>- 💰 Remote files 远程文件：运行在 Amazon S3 上远程托管的 Liquibase 文件<br>- 💰 Advanced diff &amp; drift detection 先进的差异和漂移检测：<br>- 💰 Policy Checks 政策检查：在部署之前根据一组规则按需或自动检查数据库代码的质量和安全性<br>- 💰 Flows 流程：编排数据库更改工作流程和最佳实践，以实现即时和一致的部署<br>- 💰 Structured Logging 结构化日志记录：结构化和可定制的日志记录可增强 BI 工具和仪表板的可观察性和安全性<br>- 💰 Operations reports 运营报告：用于运营洞察和错误检测的便携式报告<br>- 💰 Secrets management 保密管理：与 AWS Secrets Manager 和 HashiCorp Vault 的内置集成<br>- 💰 高级支持<br>- 💰 入门培训<br>- 💰 技术客服经理：由技术客服经理协调的个性化服务和帐户审查</td><td>- 🆓 社区支持<br>- 🆓 最多20个团队成员<br>- 🆓 最多10个数据库实例<br>- 🆓 SQL lint, GitOps<br>- 💰 电子邮件支持<br>- 💰 无限用户<br>- 💰 最多 20 个数据库实例<br>- 💰 人工审核、计划上线<br>- 💰💰 SLA 支持<br>- 💰💰 无限数据库实例<br>- 💰💰 SSO、SCIM、2FA、审核日志<br>- 💰💰 自定义审批、动态数据脱敏<br>- 💰💰 通过SSH隧道连接数据库</td></tr><tr><td>文件类型</td><td>- SQL</td><td>- SQL<br>- JSON&#x2F;YML&#x2F;XML</td><td>- SQL</td></tr><tr><td>版本控制</td><td>通过文件名控制排序，<code>V/U/R0001__ADD_NEW_COLUMN.sql</code><br>其中：<br>- <code>V</code>表示正常版本迭代<br>- <code>U</code>表示撤销（<font color="red">社区版不支持撤销</font>）<br>- <code>R</code>表示可重复<br><br>双下划线<code>__</code>后面的描述内容则属于用于说明，便于理解。<br><a href="https://www.red-gate.com/blog/database-devops/flyway-naming-patterns-matter">Flyway: Naming Patterns Matter</a></td><td>通过维护统一的changset文件来管理，详见<a href="#%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6">版本控制之liquibase</a></td><td>高级版支持审批流</td></tr><tr><td>历史记录</td><td>建表：flyway_schema_history</td><td>建表：<br>- DATABASECHANGELOG<br>- DATABASECHANGELOGLOCK</td><td></td></tr><tr><td>执行顺序</td><td>文件名，V开头的序号</td><td>通过changelog和changeset来管理，如果有小数点需要使用双引号引用，详见<a href="https://docs.liquibase.com/concepts/changelogs/changeset.html">官方博客</a>中Attributes模块中对于<code>id</code>的描述</td><td></td></tr><tr><td>回滚</td><td>- <code>U</code>开头的SQL文件<br>- <font color="red">社区版不支持撤销</font></td><td>针对SQL文件，是通过制定rollback语句；<br>针对JSON&#x2F;YML&#x2F;XML文件，对部分类型，比如createTable会有默认实现，部分需要自己定义，类似于使用SQL文件维护</td><td></td></tr><tr><td>不同环境部署不同的脚本</td><td>❌</td><td>有两个参数用于解决这个问题，分别是<code>context</code>和<code>labels</code>。<br>简单来说<code>context</code>主要是为了区分研发环境&#x2F;测试环境&#x2F;生产环境，<br>而<code>labels</code>则是用于区分不同的业务。详见<a href="#context%20vs%20labels">liquibase补充说明之context vs labels</a></td><td></td></tr><tr><td>数据库快照</td><td>❌</td><td>通过CLI完成：<code>liquibase snapshot --snapshot-format=json --output-file=Vx.x.x.json</code>，会得到一个json格式的快照文件，是针对整个数据库的结构、索引等信息的快照，不包含数据</td><td></td></tr></tbody></table><h1 id="版本控制"><a href="#版本控制" class="headerlink" title="版本控制"></a>版本控制</h1><h2 id="flyway"><a href="#flyway" class="headerlink" title="flyway"></a>flyway</h2><p>通过文件名控制排序，<code>V/U/R0001__ADD_NEW_COLUMN.sql</code><br>其中：</p><ul><li><code>V</code>表示正常版本迭代</li><li><code>U</code>表示撤销（<font color="red">社区版不支持撤销</font>）</li><li><code>R</code>表示可重复</li></ul><p>双下划线<code>__</code>后面的描述内容则属于用于说明，便于理解。<br><a href="https://www.red-gate.com/blog/database-devops/flyway-naming-patterns-matter">Flyway: Naming Patterns Matter</a></p><h2 id="liquibase"><a href="#liquibase" class="headerlink" title="liquibase"></a>liquibase</h2><blockquote><p>官方博客：<a href="https://docs.liquibase.com/concepts/changelogs/changeset.html">https://docs.liquibase.com/concepts/changelogs/changeset.html</a></p></blockquote><p>liquibase是通过维护changelog和changeset来统一管理版本的。格式可以是SQL、JSON、YAML、XML。详见<a href="#changelog%E4%B8%8Echangeset">liquibase补充说明之changelog与changeset</a></p><p>从我自己的理解来看选择SQL会更合适一些。理由如下：</p><ol><li>SQL中不需要再多去了解liquibase自己定义的建表，加字段的语法，学习成本低</li><li>SQL中也有完整的功能，而且针对不同的数据库写不同的脚本也更简单</li></ol><h1 id="liquibase补充说明"><a href="#liquibase补充说明" class="headerlink" title="liquibase补充说明"></a>liquibase补充说明</h1><h2 id="changelog与changeset"><a href="#changelog与changeset" class="headerlink" title="changelog与changeset"></a>changelog与changeset</h2><p>changelog是changeset的集合。</p><p>changeset是一个维护性的配置文件，支持用SQL、JSON、YAML、XML等格式维护。通过一些固定的标签或者语法去维护变更。<br>推荐使用SQL作为维护格式。因为JSON、YAML、XML等格式有学习成本，需要按liquibase自己定义的标签去操作数据库。<br>可以看看示例，会更容易理解</p><h3 id="SQL"><a href="#SQL" class="headerlink" title="SQL"></a>SQL</h3><figure class="highlight sql"><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"><span class="comment">--changeset nvoxland:1 </span></span><br><span class="line"><span class="keyword">create table</span> company ( id <span class="type">int</span> <span class="keyword">primary key</span>, address <span class="type">varchar</span>(<span class="number">255</span>) );</span><br></pre></td></tr></table></figure><h3 id="JSON"><a href="#JSON" class="headerlink" title="JSON"></a>JSON</h3><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;changeSet&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="string">&quot;nvoxland&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;changes&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;createTable&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;tableName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;company&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;columns&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;column&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;address&quot;</span></span><br><span class="line">              <span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="YAML"><a href="#YAML" class="headerlink" title="YAML"></a>YAML</h3><figure class="highlight yaml"><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"><span class="attr">databaseChangeLog:</span></span><br><span class="line">  <span class="bullet">-</span>  <span class="attr">changeSet:</span>  </span><br><span class="line">      <span class="attr">id:</span>  <span class="number">1</span></span><br><span class="line">      <span class="attr">author:</span>  <span class="string">nvoxland</span></span><br><span class="line">      <span class="attr">changes:</span></span><br><span class="line">        <span class="bullet">-</span>  <span class="attr">createTable:</span></span><br><span class="line">            <span class="attr">tableName:</span>  <span class="string">company</span></span><br><span class="line">            <span class="attr">columns:</span></span><br><span class="line">              <span class="bullet">-</span>  <span class="attr">column:</span></span><br><span class="line">                  <span class="attr">name:</span>  <span class="string">address</span></span><br></pre></td></tr></table></figure><h3 id="XML"><a href="#XML" class="headerlink" title="XML"></a>XML</h3><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">changeSet</span>  <span class="attr">id</span>=<span class="string">&quot;1&quot;</span>  <span class="attr">author</span>=<span class="string">&quot;nvoxland&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">createTable</span>  <span class="attr">tableName</span>=<span class="string">&quot;company&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">column</span>  <span class="attr">name</span>=<span class="string">&quot;address&quot;</span>  <span class="attr">type</span>=<span class="string">&quot;varchar(255)&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">createTable</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">changeSet</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="context-vs-labels"><a href="#context-vs-labels" class="headerlink" title="context vs labels"></a>context vs labels</h2><p>在维护changset时，有两个关键字，分别是<code>context</code>和<code>labels</code>。简单来说<code>context</code>主要是为了区分研发环境&#x2F;测试环境&#x2F;生产环境，而<code>labels</code>则是用于区分不同的业务。</p><p>在官方博客中明确提到你可以按自己的理解去使用，但使用之后需要注意你的changeset对于单个环境来说可能是复杂，冗余的，可能会带来维护上的成本。</p><p>同时博客中也提到你可以定义不同的changset文件来解决，不一定非要使用<code>context</code>和<code>labels</code></p><blockquote><p>官方博客：<a href="https://www.liquibase.com/blog/contexts-vs-labels">https://www.liquibase.com/blog/contexts-vs-labels</a></p><p>Whether you should use contexts or labels comes down to whether the changeset author&#x2F;developer or the Liquibase executor (deployment manager) best understands and&#x2F;or needs the most control over which changesets to execute.<br>是否应该使用上下文或标签取决于变更集作者&#x2F;开发人员或 Liquibase 执行者（部署经理）是否最好地理解和&#x2F;或需要对要执行的变更集进行最大程度的控制。</p><ul><li>If you want to describe&#x2F;tag the environment and have the changeset authors decide which environments they should run in, use contexts.<br>如果您想描述&#x2F;标记环境并让变更集作者决定他们应该在哪些环境中运行，请使用上下文。</li><li>If you want to describe&#x2F;tag the changesets and have the deployment manager decide which changesets to run? If so, use labels.<br> 如果您想描述&#x2F;标记变更集并让部署管理器决定运行哪些变更集？如果是这样，请使用标签。<br>Remember: you can use both.<br>请记住：您可以同时使用两者。<br>While there are many use cases for contexts and labels, they should be used judiciously. The reason you use Liquibase in the first place is to ensure consistent deployment logic from development through production; Contexts and labels are inherently breaking that consistency, allowing some statements to run on some environments and not in others.<br>虽然上下文和标签有很多用例，但应谨慎使用它们。使用 Liquibase 的首要原因是确保从开发到生产的部署逻辑一致；上下文和标签本质上破坏了这种一致性，允许某些语句在某些环境中运行，而不能在其他环境中运行。<br>When you do find yourself looking to use labels or contexts first ask “is there a better way to do this?”<br>当您确实发现自己想要使用标签或上下文时，首先问“有更好的方法吗？”</li><li>If you are using contexts for feature selection, perhaps independent changelogs per feature would work better?<br>如果您使用上下文进行功能选择，也许每个功能独立的变更日志会更好？</li><li>If you are using labels for managing changes by version, perhaps a different version control or artifact management would work better?<br>如果您使用标签来按版本管理更改，也许不同的版本控制或工件管理会更好？<br>The answer will often be “labels or contexts work best”, but always be aware of any conditional logic you are adding to your changelogs.<br>答案通常是“标签或上下文效果最好”，但请始终注意您添加到变更日志中的任何条件逻辑。</li></ul></blockquote><h3 id="为什么要使用context来区别研发-测试-生产环境？"><a href="#为什么要使用context来区别研发-测试-生产环境？" class="headerlink" title="为什么要使用context来区别研发&#x2F;测试&#x2F;生产环境？"></a>为什么要使用<code>context</code>来区别研发&#x2F;测试&#x2F;生产环境？</h3><p>我们可能会有一些数据需要做调整，但每个环境中的数据值有差异，这种情况下使用context就满不错的。</p><p>举个例子，在研发环境中，我们需要初始化一些测试数据，比如配置是否校验短信验证，研发环境中为了节约短信资源肯定是不会真的验证的，但是生产环境是一定要验证的，那么这里研发环境和生产环境就明显区分了，那么这里使用<code>context</code>就能很好的解决这个问题。</p><h3 id="为什么要使用labels区分业务？"><a href="#为什么要使用labels区分业务？" class="headerlink" title="为什么要使用labels区分业务？"></a>为什么要使用<code>labels</code>区分业务？</h3><p>其实我觉得我自己的这个描述不是特别合理，更多的是官方博客中提到的管理器问题。对应我们常说的CI&#x2F;CD中的CD流程。我们可能线上有多套环境，每套环境针对的业务有所差异，对于环境A中的功能和脚本，环境B中其实并不需要，那么B中就可以不去执行。这时候用<code>labels</code>就比较合理。</p><p>当然如果只是<strong>不需要</strong>这种简单场景，我还是建议都执行比较好，过于碎片化很难在测试阶段中复现问题。但如果是一些数据矫正的SQL，那么可能就不得不使用<code>labels</code>拆分了</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>flyway、liquibase、bytebase三者在目前免费提供的功能中都是不完美的状态，都属于是加钱才能变强，但总体来说liquibase是三者中功能最全的。</p><p>优缺点总结（仅针对各自免费版）：</p><ol><li>flyway不支持撤销变更；liquibase支持，但需要研发单独维护rollback语句，测试同学也会增加工作量，同时如果研发没有及时维护和验证rollback语句，且此时已被执行后也需要手动处理；</li><li>liquibase不支持多文件管理，include&#x2F;includeAll被定义为专业版功能；flyway本身就是通过多文件管理；</li><li>bytebase单纯就是一个SQL执行器</li></ol><p>无论使用哪款工具，都需要注意：</p><ol><li>不建议管理索引创建等较为耗时的操作，可能会导致应用无法启动；</li><li>不建议修改已发布的语句，会发生冲突；</li></ol><h2 id="需求点分析-1"><a href="#需求点分析-1" class="headerlink" title="需求点分析"></a>需求点分析</h2><h3 id="问题1：是否支持多类数据库以及脚本区分？"><a href="#问题1：是否支持多类数据库以及脚本区分？" class="headerlink" title="问题1：是否支持多类数据库以及脚本区分？"></a>问题1：是否支持多类数据库以及脚本区分？</h3><p>可以肯定的是flyway和liquibase都具备，可以指定不同的数据库执行不同的SQL。<br>对于bytebase来说，由于免费版没有审批流程，更像是一个sql执行器，所以这里我就没有深度研究对比了。</p><h3 id="问题2：如何撤销变更？"><a href="#问题2：如何撤销变更？" class="headerlink" title="问题2：如何撤销变更？"></a>问题2：如何撤销变更？</h3><ul><li>liquibase通过定义为changeset文件中维护<code>--rollback</code>标签以实现</li><li>flyway免费版则不支持。</li></ul><p>我之前有深度使用过flyway，flyway在执行比较多的历史变更时可能会出现栈溢出，且由于flyway免费版没有undo机制，一旦测试过程进行中，因为一些bug不得不需要重新调整SQL时，就会带来灾难级的问题和繁琐的解决过程。</p><p><strong>liquibase在undo上是免费提供的，但也需要研发投入精力单独维护，同时对于测试来说新增了一个脚本undo的测试用例，工作量会有所增加。</strong></p><h3 id="问题3：版本是如何管理的？"><a href="#问题3：版本是如何管理的？" class="headerlink" title="问题3：版本是如何管理的？"></a>问题3：版本是如何管理的？</h3><ul><li>flyway是通过文件名以<code>V</code>开头后接序号；</li><li>liquibase是通过维护changelog中的changeset实现；</li></ul><h3 id="问题4：费用问题？是否开源？开源协议是什么？"><a href="#问题4：费用问题？是否开源？开源协议是什么？" class="headerlink" title="问题4：费用问题？是否开源？开源协议是什么？"></a>问题4：费用问题？是否开源？开源协议是什么？</h3><p>详见<a href="#flyway%20VS%20liquibase%20VS%20bytebase">表格</a></p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><h2 id="对比类"><a href="#对比类" class="headerlink" title="对比类"></a>对比类</h2><ol><li><a href="https://www.baeldung.com/liquibase-vs-flyway">baeldung Liquibase vs Flyway</a></li><li><a href="https://my.oschina.net/koderover/blog/5577782">oschina 数据库&#x2F;SQL版本管理工具选型指北</a></li><li><a href="https://www.liquibase.com/liquibase-vs-flyway">liquibase liquibase-vs-flyway</a></li><li><a href="https://blog.csdn.net/qq_41854273/article/details/124963965">csdn liquibase介绍,liquibase这一篇就够了</a></li></ol><h2 id="Liquibase"><a href="#Liquibase" class="headerlink" title="Liquibase"></a>Liquibase</h2><ol><li><a href="https://docs.liquibase.com/concepts/bestpractices.html">liquibase 最佳实践</a></li><li><a href="https://www.cnblogs.com/zhaoyanhaoBlog/p/11327039.html">cnblogs Spring Boot使用Liquibase最佳实践</a></li><li><a href="https://docs.spring.io/spring-boot/docs/2.0.0.M5/reference/html/howto-database-initialization.html#howto-use-a-higher-level-database-migration-tool">Spring Docs Use a higher-level database migration tool</a></li><li><a href="https://docs.liquibase.com/concepts/connections/creating-config-properties.html">Liquibase 配置文件说明</a></li><li><a href="https://docs.liquibase.com/concepts/changelogs/sql-format.html">Liquibase SQL formate配置文件说明</a></li></ol><h2 id="Flyway"><a href="#Flyway" class="headerlink" title="Flyway"></a>Flyway</h2><ol><li><a href="https://docs.spring.io/spring-boot/docs/2.0.0.M5/reference/html/howto-database-initialization.html#howto-use-a-higher-level-database-migration-tool">Spring Docs Use a higher-level database migration tool</a></li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>cglib动态代理后的实例在尝试获取所实现接口的范型信息时出现循环递归导致栈溢出</title>
      <link>https://baofeidyz.com/2024/368dd08a8154/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202409271408737.png"></p>
<h1 id="现象描述"><a href="#现象描述" class="headerlink" title="现象描述"></a>现象描述</h1><p>正常启动服务时，突然遇到栈溢出异常，日志输出内容如下：</p>
<figure class="highlight plaintext"><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">java.lang.StackOverflowError: null</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.createInfo(TypeDiscoverer.java:113) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:443) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.ClassTypeInformation.getSuperTypeInformation(ClassTypeInformation.java:42) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:449) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.ClassTypeInformation.getSuperTypeInformation(ClassTypeInformation.java:42) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:449) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.ClassTypeInformation.getSuperTypeInformation(ClassTypeInformation.java:42) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:449) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.ClassTypeInformation.getSuperTypeInformation(ClassTypeInformation.java:42) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:449) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  ...</span><br></pre></td></tr></table></figure>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/%E8%B8%A9%E5%9D%91/">踩坑</category>
      <category domain="https://baofeidyz.com/tags/%E6%95%85%E9%9A%9C%E6%8E%92%E6%9F%A5%E8%AE%B0%E5%BD%95/">故障排查记录</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%80%E6%9C%AF/">技术</category>
      <pubDate>Fri, 27 Sep 2024 06:05:26 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202409271408737.png"></p><h1 id="现象描述"><a href="#现象描述" class="headerlink" title="现象描述"></a>现象描述</h1><p>正常启动服务时，突然遇到栈溢出异常，日志输出内容如下：</p><figure class="highlight plaintext"><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">java.lang.StackOverflowError: null</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.createInfo(TypeDiscoverer.java:113) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:443) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.ClassTypeInformation.getSuperTypeInformation(ClassTypeInformation.java:42) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:449) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.ClassTypeInformation.getSuperTypeInformation(ClassTypeInformation.java:42) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:449) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.ClassTypeInformation.getSuperTypeInformation(ClassTypeInformation.java:42) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:449) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.ClassTypeInformation.getSuperTypeInformation(ClassTypeInformation.java:42) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:449) ~[spring-data-commons-2.5.1.jar:2.5.1]</span><br><span class="line">  ...</span><br></pre></td></tr></table></figure><span id="more"></span><h1 id="排查过程"><a href="#排查过程" class="headerlink" title="排查过程"></a>排查过程</h1><p>日志中能得到的有效信息只有栈溢出和<code>TypeDiscoverer</code>，根本没有实际的调用栈。</p><p>这时候就得借助调试工具，我比较习惯用IDEA的breakpoint断点工具<br><img src="https://cdn.baofeidyz.com/img/202409271118748.png"><br><img src="https://cdn.baofeidyz.com/img/202409271119536.png"></p><p>然后以debug的方式启动系统就可以定位到具体的源码了。<br><img src="https://cdn.baofeidyz.com/img/202409271122543.png"><br>通过IDEA的栈查看器来查看和定位上下文<br><img src="https://cdn.baofeidyz.com/img/202409271124446.png"><br>直接跳转到具体出现问题的源码：</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">Map&lt;Class,Map&lt;Class,List&lt;WorkerDoneListener&gt;&gt;&gt; listenerMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();  </span><br><span class="line"><span class="keyword">if</span>(listeners != <span class="literal">null</span>)&#123;  </span><br><span class="line">    <span class="keyword">for</span> (WorkerDoneListener l : listeners) &#123;  </span><br><span class="line">        List&lt;TypeInformation&lt;?&gt;&gt; types = ClassTypeInformation.from(l.getClass()).getRequiredSuperTypeInformation(WorkerDoneListener.class).getTypeArguments();  </span><br><span class="line">        <span class="type">Class</span> <span class="variable">request</span> <span class="operator">=</span> types.get(<span class="number">0</span>).getType();  </span><br><span class="line">        <span class="type">Class</span> <span class="variable">response</span> <span class="operator">=</span> types.get(<span class="number">1</span>).getType();  </span><br><span class="line">        listenerMap.computeIfAbsent(request,k-&gt;<span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;()).computeIfAbsent(response,k-&gt;<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;()).add(l);  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码中，<code>listeners</code>是通过Spring依赖注入的接口<code>WorkerDoneListener</code>的实现类集合。</p><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">List&lt;TypeInformation&lt;?&gt;&gt; types = ClassTypeInformation.from(l.getClass()).getRequiredSuperTypeInformation(WorkerDoneListener.class).getTypeArguments();  </span><br></pre></td></tr></table></figure><p>这行代码正试图获取接口<code>WorkerDoneListener</code>的实现类的范型数据。</p><p>这里我贴一下接口<code>WorkerDoneListener</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="keyword">interface</span> <span class="title class_">WorkerDoneListener</span>&lt;T,R&gt;&#123;  </span><br><span class="line">    <span class="comment">// ...省略</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"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AService</span> <span class="keyword">implements</span> <span class="title class_">WorkerDoneListener</span>&lt;RequestA,ResponseA&gt;&#123;</span><br><span class="line">  <span class="comment">// ...省略</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>其实就是想拿到<code>AService</code>中两个范型<code>RequestA</code>和<code>ResponseA</code></strong>。</p><p><code>getRequiredSuperTypeInformation</code>方法是接口<code>org.springframework.data.util.TypeInformation</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">default</span> TypeInformation&lt;?&gt; getRequiredSuperTypeInformation(Class&lt;?&gt; superType) &#123;  </span><br><span class="line">  </span><br><span class="line">    TypeInformation&lt;?&gt; result = getSuperTypeInformation(superType);  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">if</span> (result == <span class="literal">null</span>) &#123;  </span><br><span class="line">       <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(String.format(  </span><br><span class="line">             <span class="string">&quot;Can&#x27;t retrieve super type information for %s! Does current type really implement the given one?&quot;</span>,  </span><br><span class="line">             superType));  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">return</span> result;  </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></pre></td><td class="code"><pre><span class="line">TypeInformation&lt;?&gt; result = getSuperTypeInformation(superType);  </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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> TypeInformation&lt;?&gt; getSuperTypeInformation(Class&lt;?&gt; superType) &#123;  </span><br><span class="line">    Class&lt;?&gt; rawType = getType();  </span><br><span class="line">    <span class="keyword">if</span> (!superType.isAssignableFrom(rawType)) &#123;  </span><br><span class="line">       <span class="keyword">return</span> <span class="literal">null</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="keyword">if</span> (getType().equals(superType)) &#123;  </span><br><span class="line">       <span class="keyword">return</span> <span class="built_in">this</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line">    List&lt;Type&gt; candidates = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();  </span><br><span class="line">    <span class="type">Type</span> <span class="variable">genericSuperclass</span> <span class="operator">=</span> rawType.getGenericSuperclass();  </span><br><span class="line">    <span class="keyword">if</span> (genericSuperclass != <span class="literal">null</span>) &#123;  </span><br><span class="line">       candidates.add(genericSuperclass);  </span><br><span class="line">    &#125;  </span><br><span class="line">    candidates.addAll(Arrays.asList(rawType.getGenericInterfaces()));  </span><br><span class="line">    <span class="keyword">for</span> (Type candidate : candidates) &#123;  </span><br><span class="line">       TypeInformation&lt;?&gt; candidateInfo = createInfo(candidate);  </span><br><span class="line">       <span class="keyword">if</span> (superType.equals(candidateInfo.getType())) &#123;  </span><br><span class="line">          <span class="keyword">return</span> candidateInfo;  </span><br><span class="line">       &#125; <span class="keyword">else</span> &#123;  </span><br><span class="line">          TypeInformation&lt;?&gt; nestedSuperType = candidateInfo.getSuperTypeInformation(superType);  </span><br><span class="line">          <span class="keyword">if</span> (nestedSuperType != <span class="literal">null</span>) &#123;  </span><br><span class="line">             <span class="keyword">return</span> nestedSuperType;  </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">return</span> <span class="literal">null</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></pre></td><td class="code"><pre><span class="line">TypeInformation&lt;?&gt; nestedSuperType = candidateInfo.getSuperTypeInformation(superType);  </span><br></pre></td></tr></table></figure><p>这行代码导致的递归调用</p><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">candidates.addAll(Arrays.asList(rawType.getGenericInterfaces()));  </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">rawType.getGenericInterfaces()</span><br></pre></td></tr></table></figure><p>方法的调用，我通过debug调试，发现这个方法返回的是：<br><img src="https://cdn.baofeidyz.com/img/202409271341064.png"><br>根本就没有他实际实现的接口信息，再加上递归查询的时候，仍然传递的是<code>superType</code>这个参数，而<code>superType</code>没有发生任何变化，所以就造成了递归，最终导致了栈溢出。</p><hr><p><strong>那么为什么<code>rawType.getGenericInterfaces()</code>方法无法获取到其实现的接口方法呢？</strong></p><p>这里需要特别注意<code>rawType</code>实例的信息：<code>com.xxxxx.service.AService$$EnhancerBySpringCGLIB$$87959439</code>，可以看到这个实例是cglib动态代理生成的，问题就出在这里，因为经由cglib动态代理之后的类，是没有办法直接使用<code>Class</code>类提供的<code>getGenericInterfaces()</code>方法获取其原实现的接口信息的。</p><blockquote><p>另外补充一下动态代理的两种类型信息：</p><ol><li>JDK动态代理：基于接口实现的，被代理的类仅能实现一个接口</li><li>CGLIB动态代理：基于类实现的</li></ol></blockquote><hr><p><strong>那么为什么这个<code>AService</code>突然就被cglib动态代理了呢？</strong></p><p>首先我查了一下配置相关是否有新的变更，但事后证明这个思路是错的。<br>应该直接看这个类的变更历史，最终发现最近一次提交中，<code>AService</code>的一个方法上新增了<code>javax.transaction.Transactional</code>注解，到这里就彻底水落石出了。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ol><li>栈溢出没有堆异常栈信息时，借助IDEA的Java Exception Breakpoints阻塞进行调试；</li><li>耐心分析出现错误的源码逻辑，认真、仔细的阅读，对于不理解的源码，多看多学官方文档，耐心理解；</li><li>知识的积累很重要，如果你对于动态代理的了解不够，或者说是你没有注意到<code>rawType</code>的实例信息，你忽视了<code>rawType</code>是由cglib动态代理生成的，那你可能就找不到正确的排查思路</li><li>后续再尝试获取一个类的相关信息时，务必小心动态代理带来的变化点，避免踩坑</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>周报-第6期：解决Windows平台Shift+F6被占用</title>
      <link>https://baofeidyz.com/2024/95d8d3cdba3b/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/cover%20(1).png"></p>
<ul>
<li><a]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Windows/">Windows</category>
      <category domain="https://baofeidyz.com/tags/%E5%91%A8%E6%8A%A5/">周报</category>
      <pubDate>Thu, 12 Sep 2024 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/cover%20(1).png"></p><ul><li><a href="https://baofeidyz.com/2024/453088b054ed/#Windows%E6%9F%A5%E7%9C%8B%E5%BF%AB%E6%8D%B7%E9%94%AE%E5%8D%A0%E7%94%A8%E7%A8%8B%E5%BA%8F">Windows查看快捷键占用程序</a></li><li><a href="https://baofeidyz.com/2024/453088b054ed/#%E8%A7%A3%E5%86%B3Windows%E5%B9%B3%E5%8F%B0-IDEA-Shift-F6-%E5%BF%AB%E6%8D%B7%E9%94%AE%E5%86%B2%E7%AA%81">解决Windows平台 IDEA Shift+F6 快捷键冲突</a></li><li>发布了博客：<a href="https://baofeidyz.com/2024/282bbd18bc13/">银联银行卡号校验</a></li></ul><h1 id="Windows查看快捷键占用程序"><a href="#Windows查看快捷键占用程序" class="headerlink" title="Windows查看快捷键占用程序"></a>Windows查看快捷键占用程序</h1><p>使用<a href="https://github.com/BlackINT3/OpenArk/releases/">openark</a>即可</p><p>实际并不是很准确，我就没找到占用Shift+F6的程序是哪个。</p><h1 id="解决Windows平台-IDEA-Shift-F6-快捷键冲突"><a href="#解决Windows平台-IDEA-Shift-F6-快捷键冲突" class="headerlink" title="解决Windows平台 IDEA Shift+F6 快捷键冲突"></a>解决Windows平台 IDEA Shift+F6 快捷键冲突</h1><p>实际是因为微软拼音输入法占用了，需要开启兼容模式</p><p><img src="https://cdn.baofeidyz.com/img/20240909115824.png"></p><blockquote><p>参考：<a href="https://answers.microsoft.com/zh-hans/windows/forum/all/%E5%BE%AE%E8%BD%AF%E6%8B%BC%E9%9F%B3%E8%BE%93/456b9c2a-b4cd-4add-b84f-4c174afced32">微软拼音输入法占用Shift+F6快捷键</a></p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>银联银行卡号校验</title>
      <link>https://baofeidyz.com/2024/282bbd18bc13/</link>
      <description>
        <![CDATA[<p>这个话题还是很有意思的，银行卡号有点类似于身份证号的设计。</p>
<p>银行卡的卡号长度及结构符合ISO 7812-1]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/%E9%93%B6%E8%81%94/">银联</category>
      <category domain="https://baofeidyz.com/tags/%E9%93%B6%E8%A1%8C%E5%8D%A1%E5%8F%B7/">银行卡号</category>
      <pubDate>Mon, 09 Sep 2024 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>这个话题还是很有意思的，银行卡号有点类似于身份证号的设计。</p><p>银行卡的卡号长度及结构符合ISO 7812-1 有关规定，由13-19位数字表示，具体由以下几部分组成：</p><table><thead><tr><th>发卡行标识代码</th><th>自定义位</th><th>校验位</th></tr></thead><tbody><tr><td>XXXXX</td><td>X……X</td><td>X</td></tr></tbody></table><p>其中校验位是通过一个名为<a href="http://camlmac.pbc.gov.cn/eportal/fileDir/rhwg/20001801f102.htm">Luhn</a>的校验算法得出的。</p><p>然而实际上一些商业银行不遵守这个规范，所以导致Luhn设计被废弃了，具体可以见这个<a href="https://github.com/dromara/hutool/issues/2510">Github Issue</a>的讨论。</p><p>现在更推荐使用一些开放平台提供的API接口来实现，比如<a href="https://open.alipay.com/portal/forum/post/62801045">支付宝开放平台</a>以及<a href="https://open.unionpay.com/tjweb/api/detail?apiSvcId=21&index=1">中国银联开放平台</a>。</p><p>中国银联开放平台有准入门槛，需要先申请，如果只是做银行卡是否有效的验证，不校验对应所有人身份信息的话可以直接用支付宝提供的接口。</p><p>接口请求示例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl <span class="string">&quot;https://ccdcapi.alipay.com/validateAndCacheCardInfo.json?_input_charset=utf-8&amp;cardNo=银行卡卡号&amp;cardBinCheck=true&quot;</span></span><br></pre></td></tr></table></figure><p>比如我们请求：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl https://ccdcapi.alipay.com/validateAndCacheCardInfo.json?_input_charset=utf-8&amp;cardNo=6214832018989180&amp;cardBinCheck=<span class="literal">true</span></span><br></pre></td></tr></table></figure><p>我们可以拿到请求结果为：</p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">    cardType<span class="punctuation">:</span> <span class="string">&quot;DC&quot;</span><span class="punctuation">,</span></span><br><span class="line">    bank<span class="punctuation">:</span> <span class="string">&quot;CMB&quot;</span><span class="punctuation">,</span></span><br><span class="line">    key<span class="punctuation">:</span> <span class="string">&quot;6214832018989180&quot;</span><span class="punctuation">,</span></span><br><span class="line">    messages<span class="punctuation">:</span> <span class="punctuation">[</span> <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    validated<span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    stat<span class="punctuation">:</span> <span class="string">&quot;ok&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li><code>bank</code>字段返回的<code>CMB</code>则可以通过<a href="https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.M7qlQG&treeId=63&articleId=103763&docType=1">支付宝开放平台-文档中心：银行简码——混合渠道</a>查阅得知这是招行银行的卡</li><li><code>cardType</code>字段则用于区分是借记卡（<code>DC</code>）还是贷计卡（<code>CC</code>）</li></ul><p>这里需要注意的是<a href="https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.M7qlQG&treeId=63&articleId=103763&docType=1">支付宝开放平台-文档中心：银行简码——混合渠道</a>这篇文章是2015年所写的，一直也没有更新，只是个局部参考，如果真的要相对稳定的接口，还是建议使用中国银联开放平台。</p><blockquote><p>注：</p><ol><li><a href="https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.M7qlQG&treeId=63&articleId=103763&docType=1">支付宝开放平台-文档中心：银行简码——混合渠道</a>这篇文章是2015年所写的，一直也没有更新，只是个局部参考，如果真的要相对稳定的接口，还是建议使用中国银联开放平台；</li><li>支付宝的这个接口其实也支持其他卡组织的卡号校验（境外卡号我没有试过）；</li></ol></blockquote><blockquote><p>本文参考：</p><ul><li><a href="http://camlmac.pbc.gov.cn/eportal/fileDir/rhwg/20001801f101.htm">中国人民银行：银行卡发卡行标识代码及卡号</a></li><li><a href="https://hbba.sacinfo.org.cn/attachment/onlineRead/dbcbceac0c2d234ff1132cc1989378dd412d03612769d38e7074e4744461c75f">中华人民共和国金融行业标准-银行卡发卡行标识代码及卡号-JR&#x2F;T 0008-2000</a></li><li><a href="https://github.com/dromara/hutool/issues/2510">Github Issue:【建议】新增银行卡号真伪校验</a></li><li><a href="https://open.alipay.com/portal/forum/post/62801045">支付宝开放平台：validateAndCacheCardInfo</a></li><li><a href="https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.M7qlQG&treeId=63&articleId=103763&docType=1">支付宝开放平台-文档中心：银行简码——混合渠道</a></li><li><a href="https://open.unionpay.com/tjweb/api/detail?apiSvcId=21&index=1">中国银联开放平台-银行卡信息验证</a></li><li><a href="https://github.com/digglife/cnbankcard">Github：cnbankcard</a></li></ul></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>周报-第5期：Server-Sent Events、Windows查看文件占用、fastfds、微信公众号获取openId</title>
      <link>https://baofeidyz.com/2024/a8a68375145d/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/cover.png"></p>
<ul>
<li>发布了两篇博客：<a href="https://baofeidyz.com/2024/415dd5c7b8b3/">Windows 安装 oh-my-zsh</a>、<a href="https://baofeidyz.com/2024/38514a82b6d7/">oh-my-zsh安装方法以及插件主题推荐</a></li>
<li><a href="https://baofeidyz.com/2024/a8a68375145d/#Server-Sent-Events">Server-Sent Events</a></li>
<li><a href="https://baofeidyz.com/2024/a8a68375145d/#Windows%E6%9F%A5%E7%9C%8B%E6%96%87%E4%BB%B6%E5%8D%A0%E7%94%A8">Windows查看文件占用</a></li>
<li><a href="https://baofeidyz.com/2024/a8a68375145d/#fastfds">fastfds：分布式文件存储</a></li>
<li><a href="https://baofeidyz.com/2024/a8a68375145d/#%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E8%8E%B7%E5%8F%96openId">微信公众号获取openId</a></li>
</ul>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B/">技术选型</category>
      <category domain="https://baofeidyz.com/tags/Windows/">Windows</category>
      <category domain="https://baofeidyz.com/tags/%E5%91%A8%E6%8A%A5/">周报</category>
      <category domain="https://baofeidyz.com/tags/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7/">微信公众号</category>
      <pubDate>Thu, 05 Sep 2024 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/cover.png"></p><ul><li>发布了两篇博客：<a href="https://baofeidyz.com/2024/415dd5c7b8b3/">Windows 安装 oh-my-zsh</a>、<a href="https://baofeidyz.com/2024/38514a82b6d7/">oh-my-zsh安装方法以及插件主题推荐</a></li><li><a href="https://baofeidyz.com/2024/a8a68375145d/#Server-Sent-Events">Server-Sent Events</a></li><li><a href="https://baofeidyz.com/2024/a8a68375145d/#Windows%E6%9F%A5%E7%9C%8B%E6%96%87%E4%BB%B6%E5%8D%A0%E7%94%A8">Windows查看文件占用</a></li><li><a href="https://baofeidyz.com/2024/a8a68375145d/#fastfds">fastfds：分布式文件存储</a></li><li><a href="https://baofeidyz.com/2024/a8a68375145d/#%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E8%8E%B7%E5%8F%96openId">微信公众号获取openId</a></li></ul><span id="more"></span><h1 id="Server-Sent-Events"><a href="#Server-Sent-Events" class="headerlink" title="Server-Sent Events"></a>Server-Sent Events</h1><p>简称为SSE，基于http协议实现的推流操作，服务端向客户端推流，流信息可以是多条信息。</p><p>其他详细的内容推荐阅读阮一峰博客：<a href="https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html">Server-Sent Events 教程</a></p><h1 id="Windows查看文件占用"><a href="#Windows查看文件占用" class="headerlink" title="Windows查看文件占用"></a>Windows查看文件占用</h1><p>在删除一些文件的时候，发现删除不了，提示busy，简单找了一下，有一个微软自己官方的工具，在网站最后有下载地址，下载以后通过cmd使用即可。</p><p>官网地址：<a href="https://learn.microsoft.com/en-us/sysinternals/downloads/handle">https://learn.microsoft.com/en-us/sysinternals/downloads/handle</a></p><p>命令行参考：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">handler c:\xxx\xxx</span><br></pre></td></tr></table></figure><p>但我感觉没啥太大用处，还是不如macOS好用</p><h1 id="fastfds"><a href="#fastfds" class="headerlink" title="fastfds"></a>fastfds</h1><blockquote><p>内容摘自：<a href="https://github.com/happyfish100/fastdfs/blob/master/README_zh.md">https://github.com/happyfish100/fastdfs/blob/master/README_zh.md</a></p></blockquote><p>FastDFS是一款开源的分布式文件系统，功能主要包括：文件存储、文件同步、文件访问（文件上传、文件下载）等，解决了文件大容量存储和高性能访问的问题。FastDFS特别适合以文件为载体的在线服务，如图片、视频、文档等等服务。</p><p>FastDFS作为一款轻量级分布式文件系统，版本V6.01代码量6.3万行。FastDFS用C语言实现，支持Linux、FreeBSD、MacOS等类UNIX系统。FastDFS类似google FS，属于应用级文件系统，不是通用的文件系统，只能通过专有API访问，目前提供了C客户端和Java SDK，以及PHP扩展SDK。</p><p>FastDFS为互联网应用量身定做，解决大容量文件存储问题，实现高性能和高扩展性。FastDFS可以看做是基于文件的key value存储系统，key为文件ID，value为文件本身，因此称作分布式文件存储服务更为合适。</p><p>FastDFS的架构比较简单，如下图所示：</p><p><img src="https://cdn.baofeidyz.com/img/20240903174420.png"></p><ul><li>GitHub地址：<a href="https://github.com/happyfish100/fastdfs">https://github.com/happyfish100/fastdfs</a></li><li>中文介绍：<a href="https://github.com/happyfish100/fastdfs/blob/master/README_zh.md">https://github.com/happyfish100/fastdfs/blob/master/README_zh.md</a></li></ul><p>需要注意的是作者使用的GPL 3.0协议</p><p>但是我更好奇为啥不选择minio，在掘金上看到一篇博客<a href="https://juejin.cn/post/7162040763801894943">MinIO 介绍 与 FastDFS 对比</a>，感觉像是翻译来的，内容也不是很硬核</p><p>我又找到一篇博客：<a href="https://juejin.cn/post/6857661685718024199">MinIO很强-让我放弃FastDFS拥抱MinIO的8个理由</a></p><p>我觉得好像fastfds确实不太行？</p><h1 id="微信公众号获取openId"><a href="#微信公众号获取openId" class="headerlink" title="微信公众号获取openId"></a>微信公众号获取openId</h1><p>其实这活我读书那会儿就做过微信公众号网页开发，但是我早就忘记了得是<a href="https://developers.weixin.qq.com/community/develop/doc/000ea06c4acc586aa92c42e1657809?highLine=scope">服务号类型才可以调用api</a>。<br>微信这个<a href="https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html">获取openId的流程文档</a>也没有先声明要求，有点恶心到我了。</p><p>相关文档：</p><ul><li><a href="https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html">微信公众号获取openId流程</a></li><li><a href="https://developers.weixin.qq.com/community/develop/doc/000ea06c4acc586aa92c42e1657809?highLine=scope">微信公众号必须是服务号才可以获取openId</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Windows 安装 oh-my-zsh</title>
      <link>https://baofeidyz.com/2024/415dd5c7b8b3/</link>
      <description>
        <![CDATA[<p>Windows 是有方案可以实现类似于Linux的终端环境，比如<a href="https://cygwin.com/">cygwin</a>。</p>
<p>我很久以前有用过，但是我在尝试安装<a href="https://sdkman.io/">SDKMan</a>的时候发现<a href="%5Bcygwin%5D(https://cygwin.com/)">cygwin</a>不受支持了。然后<a href="https://sdkman.io/">SDKMan</a>推荐了msys</p>
<p><img src="https://cdn.baofeidyz.com/img/20240903150057.png"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Windows/">Windows</category>
      <pubDate>Wed, 04 Sep 2024 01:55:24 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Windows 是有方案可以实现类似于Linux的终端环境，比如<a href="https://cygwin.com/">cygwin</a>。</p><p>我很久以前有用过，但是我在尝试安装<a href="https://sdkman.io/">SDKMan</a>的时候发现<a href="%5Bcygwin%5D(https://cygwin.com/)">cygwin</a>不受支持了。然后<a href="https://sdkman.io/">SDKMan</a>推荐了msys</p><p><img src="https://cdn.baofeidyz.com/img/20240903150057.png"></p><span id="more"></span><p>查了一下，我找到了<a href="https://www.msys2.org/">MSYS2</a></p><p>安装<a href="https://www.msys2.org/">MSYS2</a>以后，有一个叫<code>MSYS2 UCRT64.exe</code>程序，打开以后就是我们熟悉的bash环境</p><p>最后基于<a href="https://www.msys2.org/">MSYS2</a>安装<code>oh-my-zsh</code>即可。</p><p>首先我们需要安装<code>zsh</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S zsh</span><br></pre></td></tr></table></figure><blockquote><p><code>pacman</code>是MSYS2提供的包管理工具</p></blockquote><p>关于oh-my-zsh的安装以及插件和主题的推荐我有单独发博客，请点<a href="https://baofeidyz.com/2024/38514a82b6d7/">这里</a>查看</p><p>插件和主题都可以在msys2中正常运行。</p><p>至于SDKMan的安装，则可以完全按照官网的脚本运行即可。你可能会遇到一些组件不存在的错误提示，比如<code>git</code>、<code>zip</code>、<code>unzip</code>等，可以使用<code>msys2</code>提供的包管理工具<code>pacman</code>安装</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></pre></td><td class="code"><pre><span class="line">pacman -S git</span><br><span class="line">pacman -S zip</span><br><span class="line">pacman -S unzip</span><br></pre></td></tr></table></figure><p>即可搞定</p><h1 id="Windows-idea-terminal切换终端为msys2"><a href="#Windows-idea-terminal切换终端为msys2" class="headerlink" title="Windows idea terminal切换终端为msys2"></a>Windows idea terminal切换终端为msys2</h1><p>在idea的设置中，tools-terminal下有一个路径，我们输入</p><p><code>C:\msys64\usr\bin\bash.exe --login -i</code></p><p>即可</p><p><img src="https://cdn.baofeidyz.com/img/20240904093755.png"></p><blockquote><p>如果你没有选择安装在C盘或者是第一级目录名称不叫<code>msys64</code>，那么你需要自己调整一下，不要完全照抄</p></blockquote><h1 id="Windows-vscode-终端切换为msys2"><a href="#Windows-vscode-终端切换为msys2" class="headerlink" title="Windows vscode 终端切换为msys2"></a>Windows vscode 终端切换为msys2</h1><p>vscode其实本身就支持切换终端，只是入口可能大家找不到，如下图所示，用心找找就解决问题了</p><p><img src="https://cdn.baofeidyz.com/img/20240904094300.png"></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>oh-my-zsh安装方法以及插件主题推荐</title>
      <link>https://baofeidyz.com/2024/38514a82b6d7/</link>
      <description>
        <![CDATA[<p>oh-my-zsh的Github仓库地址：<a href="https://github.com/ohmyzsh/ohmyzsh">https://github.com/ohmyzsh/ohmyzsh</a></p>
<h1 id="安装zsh"><a]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/macOS/">macOS</category>
      <category domain="https://baofeidyz.com/tags/Shell/">Shell</category>
      <category domain="https://baofeidyz.com/tags/Linux/">Linux</category>
      <pubDate>Wed, 04 Sep 2024 01:22:53 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>oh-my-zsh的Github仓库地址：<a href="https://github.com/ohmyzsh/ohmyzsh">https://github.com/ohmyzsh/ohmyzsh</a></p><h1 id="安装zsh"><a href="#安装zsh" class="headerlink" title="安装zsh"></a>安装zsh</h1><p>oh-my-zsh安装之前必须要先将shell环境切换成<code>zsh</code></p><p><a href="https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH">https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH</a></p><h2 id="for-centos"><a href="#for-centos" class="headerlink" title="for centos"></a>for centos</h2><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">sudo yum update &amp;&amp; sudo yum -y install zsh &amp;&amp; \</span><br><span class="line">chsh -s $(which zsh)</span><br></pre></td></tr></table></figure><h2 id="for-debian"><a href="#for-debian" class="headerlink" title="for debian"></a>for debian</h2><figure class="highlight bash"><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"><span class="built_in">sudo</span> apt install -y zsh &amp;&amp; \</span><br><span class="line">chsh -s $(<span class="built_in">which</span> zsh)</span><br></pre></td></tr></table></figure><h1 id="安装-on-my-zsh"><a href="#安装-on-my-zsh" class="headerlink" title="安装 on-my-zsh"></a>安装 on-my-zsh</h1><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh -c &quot;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh -c &quot;$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;</span><br></pre></td></tr></table></figure><blockquote><p>国内安装特别慢，建议使用魔法</p></blockquote><h1 id="插件安装"><a href="#插件安装" class="headerlink" title="插件安装"></a>插件安装</h1><p>一键安装脚本：</p><blockquote><p>第五行中是针对第一次安装oh-my-zsh的的内容做文本替换，如果不是第一次安装会替换失败，需要自己手动维护plugins和ZSH_THEME</p></blockquote><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></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> --depth=1 https://github.com/zsh-users/zsh-autosuggestions <span class="variable">$&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;</span>/plugins/zsh-autosuggestions &amp;&amp; \</span><br><span class="line">git <span class="built_in">clone</span> --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting.git <span class="variable">$&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;</span>/plugins/zsh-syntax-highlighting &amp;&amp; \</span><br><span class="line">git <span class="built_in">clone</span> --depth=1 https://github.com/paulirish/git-open.git <span class="variable">$ZSH_CUSTOM</span>/plugins/git-open &amp;&amp; \</span><br><span class="line">git <span class="built_in">clone</span> --depth=1 https://github.com/romkatv/powerlevel10k.git <span class="variable">$&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;</span>/themes/powerlevel10k &amp;&amp; \</span><br><span class="line">[ -f ~/.zshrc ] &amp;&amp; &#123; grep -q <span class="string">&#x27;^plugins=(git)$&#x27;</span> ~/.zshrc &amp;&amp; sed -i <span class="string">&#x27;&#x27;</span> <span class="string">&#x27;s/^plugins=(git)$/plugins=(git zsh-autosuggestions extract zsh-syntax-highlighting)/&#x27;</span> ~/.zshrc || <span class="built_in">echo</span> <span class="string">&quot;⚠️ 未找到 plugins=(git)，未进行替换。&quot;</span>; grep -q <span class="string">&#x27;^ZSH_THEME=&quot;robbyrussell&quot;$&#x27;</span> ~/.zshrc &amp;&amp; sed -i <span class="string">&#x27;&#x27;</span> <span class="string">&#x27;s/^ZSH_THEME=&quot;robbyrussell&quot;/ZSH_THEME=&quot;powerlevel10k\/powerlevel10k&quot;/&#x27;</span> ~/.zshrc || <span class="built_in">echo</span> <span class="string">&quot;⚠️ 未找到 ZSH_THEME=\&quot;robbyrussell\&quot;，未进行替换。&quot;</span>; <span class="built_in">echo</span> <span class="string">&quot;✅ 操作完成，如未生效请重新打开终端或执行 source ~/.zshrc&quot;</span>; &#125; || <span class="built_in">echo</span> <span class="string">&quot;❌ 文件 ~/.zshrc 不存在，无法进行修改。&quot;</span> &amp;&amp; \</span><br><span class="line"><span class="built_in">source</span> ~/.zshrc</span><br></pre></td></tr></table></figure><h2 id="zsh-autosuggestions"><a href="#zsh-autosuggestions" class="headerlink" title="zsh-autosuggestions"></a>zsh-autosuggestions</h2><p>介绍: 可以根据历史记录提供提示命令<br>github地址: <a href="https://github.com/zsh-users/zsh-autosuggestions">https://github.com/zsh-users/zsh-autosuggestions</a><br>安装教程: <a href="https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md">https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md</a></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions $&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;/plugins/zsh-autosuggestions</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">plugins=(zsh-autosuggestions)</span><br></pre></td></tr></table></figure><h2 id="extract"><a href="#extract" class="headerlink" title="extract"></a>extract</h2><p>介绍: 一键解压,不用再去记tar各种命令<br>github地址: <a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract">https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/extract</a></p><p>不需要安装直接在plugins中增加<code>extract</code>即可</p><h2 id="zsh-syntax-highlighting"><a href="#zsh-syntax-highlighting" class="headerlink" title="zsh-syntax-highlighting"></a>zsh-syntax-highlighting</h2><p>介绍: 命令高亮提示<br>github地址: <a href="https://github.com/zsh-users/zsh-syntax-highlighting">https://github.com/zsh-users/zsh-syntax-highlighting</a></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting.git $&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;/plugins/zsh-syntax-highlighting</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">plugins=( [plugins...] zsh-syntax-highlighting)</span><br></pre></td></tr></table></figure><h2 id="git-open"><a href="#git-open" class="headerlink" title="git-open"></a>git-open</h2><p>介绍：直接在终端界面打开对应仓库的页面信息</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone --depth=1 https://github.com/paulirish/git-open.git $ZSH_CUSTOM/plugins/git-open</span><br></pre></td></tr></table></figure><h1 id="主题"><a href="#主题" class="headerlink" title="主题"></a>主题</h1><p>powerlevel10k&#x2F;powerlevel10k</p><p>github地址: <a href="https://github.com/romkatv/powerlevel10k">https://github.com/romkatv/powerlevel10k</a></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone --depth=1 https://github.com/romkatv/powerlevel10k.git $&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;/themes/powerlevel10k</span><br></pre></td></tr></table></figure><p>然后修改<code>.zshrc</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ZSH_THEME=<span class="string">&quot;powerlevel10k/powerlevel10k&quot;</span></span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>周报-第4期：多线程读取HashMap会产生死循环？</title>
      <link>https://baofeidyz.com/2024/0367001e6dac/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408301724127.png"></p>
<ul>
<li><a href="https://baofeidyz.com/2024/0367001e6dac/#%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%BB%E5%8F%96HashMap%E4%BC%9A%E4%BA%A7%E7%94%9F%E6%AD%BB%E5%BE%AA%E7%8E%AF%EF%BC%9F">多线程读取HashMap会产生死循环？</a></li>
</ul>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/%E5%91%A8%E6%8A%A5/">周报</category>
      <pubDate>Thu, 29 Aug 2024 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408301724127.png"></p><ul><li><a href="https://baofeidyz.com/2024/0367001e6dac/#%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%BB%E5%8F%96HashMap%E4%BC%9A%E4%BA%A7%E7%94%9F%E6%AD%BB%E5%BE%AA%E7%8E%AF%EF%BC%9F">多线程读取HashMap会产生死循环？</a></li></ul><span id="more"></span><h1 id="多线程读取HashMap会产生死循环？"><a href="#多线程读取HashMap会产生死循环？" class="headerlink" title="多线程读取HashMap会产生死循环？"></a>多线程读取HashMap会产生死循环？</h1><p>最近在看廖雪峰大佬写的Spring Cloud开发博客，其中在<a href="https://liaoxuefeng.com/books/java/springcloud/engine/asset/index.html">设计资产系统</a>时，提到说</p><blockquote><p>为什么要使用<code>ConcurrentMap</code>？<br>使用<code>ConcurrentMap</code>并不是为了让多线程并发写入，因为<code>AssetService</code>中并没有任何同步锁。对<code>AssetService</code>进行写操作必须是单线程，不支持多线程调用<code>tryTransfer()</code>。</p></blockquote><blockquote><p>但是读取Asset支持多线程并发读取，这也是使用ConcurrentMap的原因。如果改成<code>HashMap</code>，根据不同JDK版本的实现不同，多线程读取<code>HashMap</code>可能造成死循环（注意这不是<code>HashMap</code>的bug），必须引入同步机制。</p></blockquote><p>我就很好奇，为什么<strong>多线程读取HashMap可能造成死循环</strong>，我自己没有遇到过，因为我从来不在多线程的环境下使用<code>HashMap</code>（当然不排除可能我用的一些缓存组件内部实现用了）</p><p>在Google上简单看了一些帖子的分析，基本是都是和<code>HashMap</code>扩容相关，大部分都是一边读一边写产生的问题，比如<a href="https://www.cnblogs.com/dxflqm/p/12082081.html">你是否听说过 HashMap 在多线程环境下操作可能会导致程序死循环?</a>。我在学习<code>HashMap</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> V <span class="title function_">get</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">    Node&lt;K,V&gt; e;</span><br><span class="line">    <span class="keyword">return</span> (e = getNode(hash(key), key)) == <span class="literal">null</span> ? <span class="literal">null</span> : e.value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> Node&lt;K,V&gt; <span class="title function_">getNode</span><span class="params">(<span class="type">int</span> hash, Object key)</span> &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; first, e; <span class="type">int</span> n; K k;</span><br><span class="line">    <span class="keyword">if</span> ((tab = table) != <span class="literal">null</span> &amp;&amp; (n = tab.length) &gt; <span class="number">0</span> &amp;&amp;</span><br><span class="line">        (first = tab[(n - <span class="number">1</span>) &amp; hash]) != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (first.hash == hash &amp;&amp; <span class="comment">// always check first node</span></span><br><span class="line">            ((k = first.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">            <span class="keyword">return</span> first;</span><br><span class="line">        <span class="keyword">if</span> ((e = first.next) != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (first <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line">                <span class="keyword">return</span> ((TreeNode&lt;K,V&gt;)first).getTreeNode(hash, key);</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                    ((k = e.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">                    <span class="keyword">return</span> e;</span><br><span class="line">            &#125; <span class="keyword">while</span> ((e = e.next) != <span class="literal">null</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从我理解来看，我认为只是多线程读是不会出现死循环的，这里只是描述的不严谨罢了。</p><p>果然看评论区给出了正确的描述：<br><img src="https://cdn.baofeidyz.com/img/202408231629225.png"></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>周报-第3期：地图选型、土耳其iCloud服务涨价、基于UnraidOS制作torrent文件并上传PT</title>
      <link>https://baofeidyz.com/2024/0139c0cf5db0/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408201105521.png"></p>
<ul>
<li><a href="https://baofeidyz.com/2024/0139c0cf5db0#%E5%9C%B0%E5%9B%BE%E9%80%89%E5%9E%8B">地图选型</a></li>
<li><a href="https://baofeidyz.com/2024/0139c0cf5db0#%E5%9C%9F%E8%80%B3%E5%85%B6iCloud%E6%9C%8D%E5%8A%A1%E6%B6%A8%E4%BB%B7">土耳其iCloud服务涨价：价格翻倍</a></li>
<li><a href="https://baofeidyz.com/2024/0139c0cf5db0#%E5%9F%BA%E4%BA%8EUnraidOS%E5%88%B6%E4%BD%9Ctorrent%E6%96%87%E4%BB%B6%E5%B9%B6%E4%B8%8A%E4%BC%A0%E5%88%B0PT">基于UnraidOS制作torrent文件并上传到PT</a></li>
</ul>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/%E6%8A%98%E8%85%BE/">折腾</category>
      <category domain="https://baofeidyz.com/tags/AppleMusic/">AppleMusic</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B/">技术选型</category>
      <category domain="https://baofeidyz.com/tags/%E5%91%A8%E6%8A%A5/">周报</category>
      <category domain="https://baofeidyz.com/tags/%E5%9C%B0%E5%9B%BE/">地图</category>
      <pubDate>Thu, 22 Aug 2024 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408201105521.png"></p><ul><li><a href="https://baofeidyz.com/2024/0139c0cf5db0#%E5%9C%B0%E5%9B%BE%E9%80%89%E5%9E%8B">地图选型</a></li><li><a href="https://baofeidyz.com/2024/0139c0cf5db0#%E5%9C%9F%E8%80%B3%E5%85%B6iCloud%E6%9C%8D%E5%8A%A1%E6%B6%A8%E4%BB%B7">土耳其iCloud服务涨价：价格翻倍</a></li><li><a href="https://baofeidyz.com/2024/0139c0cf5db0#%E5%9F%BA%E4%BA%8EUnraidOS%E5%88%B6%E4%BD%9Ctorrent%E6%96%87%E4%BB%B6%E5%B9%B6%E4%B8%8A%E4%BC%A0%E5%88%B0PT">基于UnraidOS制作torrent文件并上传到PT</a></li></ul><span id="more"></span><h1 id="地图选型"><a href="#地图选型" class="headerlink" title="地图选型"></a>地图选型</h1><p>常规来说在国内就是高德、百度、腾讯这三家，但是企业版授权还是比较贵的，如果只是一些小项目，对于地图只是一个展示的基础功能。目前这三家还没有提供细分产品和定价，对于此，可以考虑使用<a href="http://lbs.tianditu.gov.cn/">天地图</a></p><h1 id="土耳其iCloud服务涨价"><a href="#土耳其iCloud服务涨价" class="headerlink" title="土耳其iCloud服务涨价"></a>土耳其iCloud服务涨价</h1><p>去年土耳其iCloud服务已经翻倍一次了，今年又翻倍，目前算下来价格：</p><ul><li>50GB 24.99里拉 5.26元</li><li>200GB 79.99里拉 16.83元</li><li>2TB 249.99里拉 52.61元</li></ul><p>涨了一倍的价格，并且是所有人全部涨价，不管是否是新订阅。价格优势越来越小，可能只有一个非云上贵州提供服务能让大家感到满意了。</p><p>Apple官网价格查看：<a href="https://support.apple.com/en-us/108047">iCloud+ plans and pricing</a></p><h1 id="基于UnraidOS制作torrent文件并上传到PT"><a href="#基于UnraidOS制作torrent文件并上传到PT" class="headerlink" title="基于UnraidOS制作torrent文件并上传到PT"></a>基于UnraidOS制作torrent文件并上传到PT</h1><p>本质上需要用到两个docker服务，一个是<a href="https://hub.docker.com/r/linuxserver/transmission">transmission</a>，一个是<a href="https://hub.docker.com/r/jlesage/mediainfo">jlesage&#x2F;mediainfo</a>。</p><p>transmission主要用途是制作torrent文件，而jlesage&#x2F;mediainfo是因为PT站要求需要获取对应的解析数据。</p><p>制作种子文件非常简单，进入transmission容器的控制台，然后敲入以下命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">transmission-create 文件或文件绝对路径</span><br></pre></td></tr></table></figure><p>就可以了。</p><p>获取媒体信息则是直接通过jlesage&#x2F;mediainfo提供的WebUI，直接打开具体的文件，然后选择格式为TXT，复制即可。</p><p>最后我们就按照PT站要求将种子文件提交到PT站做审核即可。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>周报-第2期：序列化Kryo、Service Provider Interface、旁听川普和马斯克的twitter live talking</title>
      <link>https://baofeidyz.com/2024/7e11f68354a6/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408201102495.png"></p>
<ul>
<li><a href="https://baofeidyz.com/2024/7e11f68354a6/#%E5%BA%8F%E5%88%97%E5%8C%96">序列化（Kryo）</a></li>
<li>发布了新的博客：<a href="https://baofeidyz.com/2024/eb322648f18b/">Hashtable和HashMap的差别</a>、<a href="https://baofeidyz.com/2024/c6548447bec7/">个人影视库搭建方案分享</a></li>
<li><a href="https://baofeidyz.com/2024/7e11f68354a6/#SPI%EF%BC%9AService-Provider-Interface">SPI：Service Provider Interface</a></li>
<li><a href="https://baofeidyz.com/2024/7e11f68354a6/#SPI%EF%BC%9ASpring%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8">SPI：Spring中的应用</a></li>
<li><a href="https://baofeidyz.com/2024/7e11f68354a6/#%E6%97%81%E5%90%ACDonald-J-Trump%E5%92%8CElon-Musk%E7%9A%84live-talking">旁听Donald J.Trump和Elon Musk的live talking</a></li>
</ul>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/%E5%91%A8%E6%8A%A5/">周报</category>
      <category domain="https://baofeidyz.com/tags/%E5%BA%8F%E5%88%97%E5%8C%96/">序列化</category>
      <category domain="https://baofeidyz.com/tags/Kryo/">Kryo</category>
      <pubDate>Thu, 15 Aug 2024 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408201102495.png"></p><ul><li><a href="https://baofeidyz.com/2024/7e11f68354a6/#%E5%BA%8F%E5%88%97%E5%8C%96">序列化（Kryo）</a></li><li>发布了新的博客：<a href="https://baofeidyz.com/2024/eb322648f18b/">Hashtable和HashMap的差别</a>、<a href="https://baofeidyz.com/2024/c6548447bec7/">个人影视库搭建方案分享</a></li><li><a href="https://baofeidyz.com/2024/7e11f68354a6/#SPI%EF%BC%9AService-Provider-Interface">SPI：Service Provider Interface</a></li><li><a href="https://baofeidyz.com/2024/7e11f68354a6/#SPI%EF%BC%9ASpring%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8">SPI：Spring中的应用</a></li><li><a href="https://baofeidyz.com/2024/7e11f68354a6/#%E6%97%81%E5%90%ACDonald-J-Trump%E5%92%8CElon-Musk%E7%9A%84live-talking">旁听Donald J.Trump和Elon Musk的live talking</a></li></ul><span id="more"></span><h1 id="序列化"><a href="#序列化" class="headerlink" title="序列化"></a>序列化</h1><p>Java本身有提供序列化和反序列化实现，但是在实际开发中发现大家很少使用。对于一些有可读性要求，前后端交互的场景，使用JSON序列化已成默认。</p><p>我自己有维护一个任务组件，一开始也是采用JSON序列化，并且数据存到数据库以后还可以直接搜索和阅读。但后面随着需要支持的业务越来越复杂，JSON序列化没有办法支持类似<code>BufferedImage</code>这类对象的序列化和反序列化操作，后面就改用了Java序列化实现。</p><p>最近在考虑将这个任务组件重写并开源，所以考虑在原来的基础上增加一些更好的选型。</p><h2 id="Kryo序列化"><a href="#Kryo序列化" class="headerlink" title="Kryo序列化"></a>Kryo序列化</h2><p><a href="https://github.com/EsotericSoftware/kryo">Kryo</a> 是我关注到的一个序列化选型。Kryo使用的是BSD-3-Clause许可协议，还是比较宽松的，对于我这个开源组件来说没有任何问题。</p><p>性能和安全性方面因为有其他知名开源项目的使用，所以我也不担心这方面。相比之下，我更关心他所支持的序列化类型。</p><p>我在Kryo项目的readme中查看到了这个链接：<a href="https://github.com/EsotericSoftware/kryo/blob/master/src/com/esotericsoftware/kryo/Kryo.java#L179">https://github.com/EsotericSoftware/kryo/blob/master/src/com/esotericsoftware/kryo/Kryo.java#L179</a> 从这个代码来看：</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></pre></td><td class="code"><pre><span class="line">addDefaultSerializer(<span class="type">byte</span>[].class, ByteArraySerializer.class);</span><br><span class="line">addDefaultSerializer(<span class="type">char</span>[].class, CharArraySerializer.class);</span><br><span class="line">addDefaultSerializer(<span class="type">short</span>[].class, ShortArraySerializer.class);</span><br><span class="line">addDefaultSerializer(<span class="type">int</span>[].class, IntArraySerializer.class);</span><br><span class="line">addDefaultSerializer(<span class="type">long</span>[].class, LongArraySerializer.class);</span><br><span class="line">addDefaultSerializer(<span class="type">float</span>[].class, FloatArraySerializer.class);</span><br><span class="line">addDefaultSerializer(<span class="type">double</span>[].class, DoubleArraySerializer.class);</span><br><span class="line">addDefaultSerializer(<span class="type">boolean</span>[].class, BooleanArraySerializer.class);</span><br><span class="line">addDefaultSerializer(String[].class, StringArraySerializer.class);</span><br><span class="line">addDefaultSerializer(Object[].class, ObjectArraySerializer.class);</span><br><span class="line">addDefaultSerializer(BigInteger.class, BigIntegerSerializer.class);</span><br><span class="line">addDefaultSerializer(BigDecimal.class, BigDecimalSerializer.class);</span><br><span class="line">addDefaultSerializer(Class.class, ClassSerializer.class);</span><br><span class="line">addDefaultSerializer(Date.class, DateSerializer.class);</span><br><span class="line">addDefaultSerializer(Enum.class, EnumSerializer.class);</span><br><span class="line">addDefaultSerializer(EnumSet.class, EnumSetSerializer.class);</span><br><span class="line">addDefaultSerializer(Currency.class, CurrencySerializer.class);</span><br><span class="line">addDefaultSerializer(StringBuffer.class, StringBufferSerializer.class);</span><br><span class="line">addDefaultSerializer(StringBuilder.class, StringBuilderSerializer.class);</span><br><span class="line">addDefaultSerializer(Collections.EMPTY_LIST.getClass(), CollectionsEmptyListSerializer.class);</span><br><span class="line">addDefaultSerializer(Collections.EMPTY_MAP.getClass(), CollectionsEmptyMapSerializer.class);</span><br><span class="line">addDefaultSerializer(Collections.EMPTY_SET.getClass(), CollectionsEmptySetSerializer.class);</span><br><span class="line">addDefaultSerializer(Collections.singletonList(<span class="literal">null</span>).getClass(), CollectionsSingletonListSerializer.class);</span><br><span class="line">addDefaultSerializer(Collections.singletonMap(<span class="literal">null</span>, <span class="literal">null</span>).getClass(), CollectionsSingletonMapSerializer.class);</span><br><span class="line">addDefaultSerializer(Collections.singleton(<span class="literal">null</span>).getClass(), CollectionsSingletonSetSerializer.class);</span><br><span class="line">addDefaultSerializer(TreeSet.class, TreeSetSerializer.class);</span><br><span class="line">addDefaultSerializer(Collection.class, CollectionSerializer.class);</span><br><span class="line">addDefaultSerializer(ConcurrentSkipListMap.class, ConcurrentSkipListMapSerializer.class);</span><br><span class="line">addDefaultSerializer(TreeMap.class, TreeMapSerializer.class);</span><br><span class="line">addDefaultSerializer(Map.class, MapSerializer.class);</span><br><span class="line">addDefaultSerializer(TimeZone.class, TimeZoneSerializer.class);</span><br><span class="line">addDefaultSerializer(Calendar.class, CalendarSerializer.class);</span><br><span class="line">addDefaultSerializer(Locale.class, LocaleSerializer.class);</span><br><span class="line">addDefaultSerializer(Charset.class, CharsetSerializer.class);</span><br><span class="line">addDefaultSerializer(URL.class, URLSerializer.class);</span><br><span class="line">addDefaultSerializer(Arrays.asList().getClass(), ArraysAsListSerializer.class);</span><br><span class="line">addDefaultSerializer(<span class="keyword">void</span>.class, <span class="keyword">new</span> <span class="title class_">VoidSerializer</span>());</span><br><span class="line">addDefaultSerializer(PriorityQueue.class, <span class="keyword">new</span> <span class="title class_">PriorityQueueSerializer</span>());</span><br><span class="line">addDefaultSerializer(BitSet.class, <span class="keyword">new</span> <span class="title class_">BitSetSerializer</span>());</span><br><span class="line">addDefaultSerializer(KryoSerializable.class, KryoSerializableSerializer.class);</span><br></pre></td></tr></table></figure><p>我感觉有一些些担忧呀，毕竟我的任务组件是没有限制对象类型的。不过Kryo支持自定义序列化实现方式，如果真的遇到了不支持的对象类型，也许我可以自己写一个然后合并给Kryo。</p><p>这块如果要采用Kryo代替Java的序列化的话，我可能还得加一个单元测试来确认支持的对象范围。</p><h2 id="参考内容"><a href="#参考内容" class="headerlink" title="参考内容"></a>参考内容</h2><ul><li><a href="https://juejin.cn/post/6993647089431347237">深入浅出序列化（2）——Kryo序列化</a></li></ul><h1 id="SPI：Service-Provider-Interface"><a href="#SPI：Service-Provider-Interface" class="headerlink" title="SPI：Service Provider Interface"></a>SPI：Service Provider Interface</h1><p>从Java 1.6开始，引入了<code>java.util.ServiceLoader</code>类，我简单看了一下源码，有几个有意思的点分享一下：</p><h2 id="懒加载"><a href="#懒加载" class="headerlink" title="懒加载"></a>懒加载</h2><p>内部有一个<code>private</code>修饰的<code>LazyIterator</code>类</p><h2 id="扫描路径"><a href="#扫描路径" class="headerlink" title="扫描路径"></a>扫描路径</h2><p>默认扫描路径前缀是<code>META-INF/services/</code></p><h2 id="类加载的时候，有获取ExtClassLoader实例"><a href="#类加载的时候，有获取ExtClassLoader实例" class="headerlink" title="类加载的时候，有获取ExtClassLoader实例"></a>类加载的时候，有获取<code>ExtClassLoader</code>实例</h2><p>这个方法蛮有意思的，循环查找，直到<code>prev</code>为<code>ExtClassLoader</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates a new service loader for the given service type, using the</span></span><br><span class="line"><span class="comment"> * extension class loader.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt; This convenience method simply locates the extension class loader,</span></span><br><span class="line"><span class="comment"> * call it &lt;tt&gt;&lt;i&gt;extClassLoader&lt;/i&gt;&lt;/tt&gt;, and then returns</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;blockquote&gt;&lt;pre&gt;</span></span><br><span class="line"><span class="comment"> * ServiceLoader.load(&lt;i&gt;service&lt;/i&gt;, &lt;i&gt;extClassLoader&lt;/i&gt;)&lt;/pre&gt;&lt;/blockquote&gt;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt; If the extension class loader cannot be found then the system class</span></span><br><span class="line"><span class="comment"> * loader is used; if there is no system class loader then the bootstrap</span></span><br><span class="line"><span class="comment"> * class loader is used.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt; This method is intended for use when only installed providers are</span></span><br><span class="line"><span class="comment"> * desired.  The resulting service will only find and load providers that</span></span><br><span class="line"><span class="comment"> * have been installed into the current Java virtual machine; providers on</span></span><br><span class="line"><span class="comment"> * the application&#x27;s class path will be ignored.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  &lt;S&gt; the class of the service type</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  service</span></span><br><span class="line"><span class="comment"> *         The interface or abstract class representing the service</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> A new service loader</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;S&gt; ServiceLoader&lt;S&gt; <span class="title function_">loadInstalled</span><span class="params">(Class&lt;S&gt; service)</span> &#123;</span><br><span class="line">    <span class="type">ClassLoader</span> <span class="variable">cl</span> <span class="operator">=</span> ClassLoader.getSystemClassLoader();</span><br><span class="line">    <span class="type">ClassLoader</span> <span class="variable">prev</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">while</span> (cl != <span class="literal">null</span>) &#123;</span><br><span class="line">        prev = cl;</span><br><span class="line">        cl = cl.getParent();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ServiceLoader.load(service, prev);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我就很好奇，为什么他非要拿到<code>extClassLoader</code>，目前还没有找到答案。</p><h1 id="SPI：Spring中的应用"><a href="#SPI：Spring中的应用" class="headerlink" title="SPI：Spring中的应用"></a>SPI：Spring中的应用</h1><p>其实Spring框架中也有用到SPI，对应的路径是<code>META-INF/services/spring.factories</code>，如果你在引入别人的jar包时，你就会发现很多都有这个<code>spring.factories</code>文件，这个就是Spring用于管理自动加载的。</p><h2 id="与Configuration注解的差别"><a href="#与Configuration注解的差别" class="headerlink" title="与Configuration注解的差别"></a>与Configuration注解的差别</h2><p>有<code>@Configuration</code>注解的类路径需要是<code>@SpringBootApplication</code>声明的扫描路径（不声明如果就是当前类及所有子路径）。</p><p>所以我们如果要做一个组件，最好就是加上<code>META-INF/services/spring.factories</code>以便其他人集成</p><h2 id="文件内容模版"><a href="#文件内容模版" class="headerlink" title="文件内容模版"></a>文件内容模版</h2><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></pre></td><td class="code"><pre><span class="line">org.springframework.boot.autoconfigure.EnableAutoConfiguration=\</span><br><span class="line">com.baofeidyz.AbcdThreadPoolConfiguration,\</span><br><span class="line">com.baofeidyz.AbcdServiceConfiguration</span><br></pre></td></tr></table></figure><h1 id="旁听Donald-J-Trump和Elon-Musk的live-talking"><a href="#旁听Donald-J-Trump和Elon-Musk的live-talking" class="headerlink" title="旁听Donald J.Trump和Elon Musk的live talking"></a>旁听Donald J.Trump和Elon Musk的live talking</h1><p>你可以点这个<a href="https://x.com/i/spaces/1nAKEpNkLwoxL">链接</a>回听。</p><p>这件事发生在川普被枪击之后，也是被Twitter封禁之后第一次回归twitter平台，直接和Elon Musk开启talking。我听了一部分，可能就china、nuclear、develop fast这些地方有点记忆点，其他的也就是听了个寂寞。不过这么轻松就能直接参与历史事件，还是很值得记录一下。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>个人影视库搭建方案分享</title>
      <link>https://baofeidyz.com/2024/c6548447bec7/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408131554736.png"><br>最近得益于阿里云盘的收割计划，大家对于个人影视库的搭建方案开启了新的探索，在此我也蹭蹭热点，分享一下我的个人影视库搭建方案。</p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%98%E8%85%BE/">折腾</category>
      <category domain="https://baofeidyz.com/categories/%E7%94%9F%E6%B4%BB/">生活</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%98%E8%85%BE/">折腾</category>
      <category domain="https://baofeidyz.com/tags/%E7%94%9F%E6%B4%BB/">生活</category>
      <pubDate>Tue, 13 Aug 2024 06:51:05 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408131554736.png"><br>最近得益于阿里云盘的收割计划，大家对于个人影视库的搭建方案开启了新的探索，在此我也蹭蹭热点，分享一下我的个人影视库搭建方案。</p><span id="more"></span><h1 id="方案简介"><a href="#方案简介" class="headerlink" title="方案简介"></a>方案简介</h1><p>19年那会做的还比较简单，emby+zimuzu就可以了。那会也发过博客：<a href="https://blog.csdn.net/baofeidyz/article/details/86763918">【技术宅拯救世界】emby+zimuzu打造家庭影院</a></p><p>目前是自建的NAS存储，然后通过各种渠道收集媒体资源，最后再通过infuse扫库和观看。infuse不只是Apple TV端支持，iOS、iPadOS以及macOS也是支持的，所以在苹果全家桶的情况下，一个infuse就可以解决所有问题。</p><h2 id="自建NAS"><a href="#自建NAS" class="headerlink" title="自建NAS"></a>自建NAS</h2><table><thead><tr><th>项目</th><th>型号</th><th>链接</th><th>备注</th><th></th></tr></thead><tbody><tr><td>操作系统</td><td>Unraid OS Basic</td><td><a href="https://unraid.net/pricing">Unraid官网</a></td><td>购入价：33美元</td><td></td></tr><tr><td>U盘</td><td>金士顿 32G USB2.0 CZ33</td><td><a href="https://3.cn/23DkY3-h">京东自营</a></td><td>购入价：35元，好像下架了</td><td></td></tr><tr><td>机箱+电源</td><td>TANK</td><td><a href="https://m.tb.cn/h.gO7QMizetSFB5RX?tk=b9jg3ex3MzQ">淘宝</a></td><td>电源是店家搭配的，购入价：1070元</td><td></td></tr><tr><td>主板</td><td>ASUSTeK COMPUTER INC. PRIME H610M-E D4</td><td><a href="https://m.tb.cn/h.gl8HGig7OZjN2wN">淘宝</a></td><td>主板+CPU一起买的，购入价：1200</td><td></td></tr><tr><td>CPU</td><td>Intel® Core™ i3-12100</td><td></td><td></td><td></td></tr><tr><td>内存</td><td>威刚 16G DDR4 3200</td><td><a href="https://u.jd.com/mQvfaos">京东自营</a></td><td>购入价：230元</td><td></td></tr><tr><td>HDD存储硬盘</td><td>TOSHIBA 16T企业盘</td><td><a href="https://u.jd.com/muyIuDg">京东自营</a></td><td>购入价：2200元</td><td></td></tr><tr><td>SSD缓存盘</td><td>铨兴 C201 SATA 3.0 1TB</td><td><a href="https://u.jd.com/Kr3EOvH">京东自营</a></td><td>购入价：287元</td><td></td></tr></tbody></table><h3 id="踩坑一：主板和内存条不兼容"><a href="#踩坑一：主板和内存条不兼容" class="headerlink" title="踩坑一：主板和内存条不兼容"></a>踩坑一：主板和内存条不兼容</h3><p>我遇到过华硕主板和金士顿内存条不兼容的问题，会导致无法正常重启。解决办法只能是拔掉BIOS电池，然后进BIOS设置关闭快速启动后，再指定启动盘才可以启动。而拔BIOS电池需要拆掉所有的东西，真是非常痛苦的一段经历。</p><p>我升级过BIOS到最新的版本，问题依然存在。</p><p>我也寄修过华硕这块主板，华硕说主板是没有问题的，又给我寄回来了，说是检测一切正常，也没有复现我遇到的问题。中途也不给我打电话，直接就给我寄回来，我都是快递给我打电话才知道被原封不动寄回，结果就是问题依然存在，没有解决。</p><p>最后的最后是我带着碰运气的想法买了一根威刚的内存条才解决问题。但是目前重启以后华硕的BIOS还是会报错，需要手动按F8选择启动盘，但至少不用拔BIOS电池，不用拆机了。</p><p>我建议不要买<strong>华硕</strong>的主板。</p><h3 id="踩坑二：NAS系统选择"><a href="#踩坑二：NAS系统选择" class="headerlink" title="踩坑二：NAS系统选择"></a>踩坑二：NAS系统选择</h3><p>我一开始也没有计划买Unraid，我想着用个开源的系统就好了，比如TrueNAS，甚至直接装个debian也可以。</p><p>我是在配置home assistant的时候遇到的网络问题，折腾了我很长一段时间，断断续续可能有接近两周我都是折腾到半夜，后面实在是受不了了，就试用了一下Unraid，发现HA完全没问题，直接就可以用，然后发现所有我需要的都有，果断付费，多出来的时候我早点睡觉多好。</p><p>没想到，最近一段时间还成了理财产品😂。</p><p>听说U盘也可能存在兼容性问题，如果你也考虑Unraid，我是建议你直接抄我作业。</p><h2 id="媒体资源渠道"><a href="#媒体资源渠道" class="headerlink" title="媒体资源渠道"></a>媒体资源渠道</h2><h3 id="人人"><a href="#人人" class="headerlink" title="人人"></a>人人</h3><p>已死，怀念一下。</p><h3 id="BT站"><a href="#BT站" class="headerlink" title="BT站"></a>BT站</h3><p>RARBG，已关闭。目前我也没有在使用BT站了。</p><h3 id="PT站"><a href="#PT站" class="headerlink" title="PT站"></a>PT站</h3><p>目前主力。</p><p>PT站是相对于BT来说，划分出一个小圈子的种子分享站。多数都是邀请制，且有分享率要求（上传量&#x2F;下载量）。但也正是因为对于分享率有要求，使得种子的下载速度非常快，跑满带宽是一件很轻松的事。</p><p>PT的好处在于不会发生分享的链接被关闭的情况，且资源质量非常高，也不会出现恶心人的水印或者中间插播广告的情况。</p><p>PT站门槛比较高，首先注册就能卡掉绝大数人。我这篇博客也不会涉及具体的PT站介绍，因为大部分PT站有要求不得公开宣传，否则会被封号。对于邀请问题，其实你在一些相对技术宅的论坛发帖求邀，留个邮箱会有大佬乐意给你的。邀请发帖的时候要说明自己有无公网IP，带宽上传速度是多少，硬盘大小，是否可7x24h待机，是否知晓PT站的新人审核制度等等。</p><p>如果你不清楚审核制度则需要明确说明，一般就是给你再多发一个网址，自己看看就理解的事儿，千万不要不懂装懂，搞不好会连累邀请你的人。</p><h3 id="网盘资源"><a href="#网盘资源" class="headerlink" title="网盘资源"></a>网盘资源</h3><p>网盘分享的速度一般都比PT更快，所以我用网盘的时候也非常多。</p><p>我目前主力是使用TG频道、TG机器人等。</p><p>网盘资源经常遇到加水印广告，视频播到中间部分突然插播菠菜广告的，所以一般PT有资源，我就不会选择网盘。</p><h2 id="资源管理方案"><a href="#资源管理方案" class="headerlink" title="资源管理方案"></a>资源管理方案</h2><h3 id="PT资源管理"><a href="#PT资源管理" class="headerlink" title="PT资源管理"></a>PT资源管理</h3><p>Unraid OS本身是支持docker的，基于docker安装一个qbitorrent就可以完成对PT资源的管理，并且下载完成后会一直做种。</p><h3 id="网盘资源管理"><a href="#网盘资源管理" class="headerlink" title="网盘资源管理"></a>网盘资源管理</h3><p>一开始我是直接使用alist挂载，然后infuse通过webdav协议挂载。</p><p>后来infuse直接支持了阿里云盘，就直接挂载了，这个方案是最好的，尤其人在外面的时候，云盘保存一下马上就可以使用infuse播放（相对于阿里云盘自己的播放器可以免费观看更高的清晰度）。</p><p>最近阿里云盘已经全面限速了，1080P都没办法流畅在线播放。所以目前的方案是alist挂载阿里云盘，同时也挂载NAS的本地磁盘，通过alist直接将阿里云盘中的资源复制到NAS本地磁盘即可。</p><p>虽然速度只有500k，但是白天远程操作一下，晚上再观看并不影响。其他的网盘资源也是同样的方案，非常好用。</p><h2 id="播放器"><a href="#播放器" class="headerlink" title="播放器"></a>播放器</h2><p>infuse，苹果所有终端都适配了，独一家的杜比解码授权，我是早期土耳其区的年付订阅用户，目前还是99里拉每年，算下来大概一年25元人民币。</p><p>infuse使用<a href="https://www.themoviedb.org/">TMDB</a>服务做解析，PT站资源命名很规范没有任何问题，网盘资源需要自己修改调整。对于TMDB上没有的资源我会自己上去新建和编辑。</p><h2 id="远程观看解决方案"><a href="#远程观看解决方案" class="headerlink" title="远程观看解决方案"></a>远程观看解决方案</h2><p>Unraid OS中基于docker安装tailscale，然后配置上SSL证书，开启HTTPS访问即可解决，这块有规划详细一点的搭建博客，敬请期待。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>PT是主力，网盘打辅助，所有的网盘资源都通过alist直接复制到本地磁盘，通过tailscale+SSL在外也能安全访问家里的服务。</p><p>播放全靠infuse，大部分资源都是杜比的，有一个授权解码很重要，目前是按年订阅中。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Hashtable和HashMap的区别</title>
      <link>https://baofeidyz.com/2024/eb322648f18b/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408121627342.png"></p>
<ul>
<li><a href="https://baofeidyz.com/2024/eb322648f18b/#Hashtable%E4%B8%8D%E5%85%81%E8%AE%B8%E7%A9%BA%E5%80%BC">Hashtable不允许空值</a></li>
<li><a href="https://baofeidyz.com/2024/eb322648f18b/#Hashtable%E6%98%AF%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84">Hashtable是线程安全的</a></li>
<li><a href="https://baofeidyz.com/2024/eb322648f18b/#%E6%80%A7%E8%83%BD%E6%AF%94HashMap%E5%B7%AE">性能比HashMap差</a></li>
<li><a href="https://baofeidyz.com/2024/eb322648f18b/#%E7%BB%A7%E6%89%BF%E5%B7%AE%E5%BC%82">继承差异</a></li>
<li><a href="https://baofeidyz.com/2024/eb322648f18b/#%E9%81%8D%E5%8E%86%E5%B7%AE%E5%BC%82">遍历差异</a></li>
<li><a href="https://baofeidyz.com/2024/eb322648f18b/#%E4%B8%80%E4%BA%9B%E9%A2%98%E5%A4%96%E8%AF%9D">一些题外话</a></li>
</ul>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/Java%E5%9F%BA%E7%A1%80/">Java基础</category>
      <pubDate>Mon, 12 Aug 2024 06:24:08 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408121627342.png"></p><ul><li><a href="https://baofeidyz.com/2024/eb322648f18b/#Hashtable%E4%B8%8D%E5%85%81%E8%AE%B8%E7%A9%BA%E5%80%BC">Hashtable不允许空值</a></li><li><a href="https://baofeidyz.com/2024/eb322648f18b/#Hashtable%E6%98%AF%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84">Hashtable是线程安全的</a></li><li><a href="https://baofeidyz.com/2024/eb322648f18b/#%E6%80%A7%E8%83%BD%E6%AF%94HashMap%E5%B7%AE">性能比HashMap差</a></li><li><a href="https://baofeidyz.com/2024/eb322648f18b/#%E7%BB%A7%E6%89%BF%E5%B7%AE%E5%BC%82">继承差异</a></li><li><a href="https://baofeidyz.com/2024/eb322648f18b/#%E9%81%8D%E5%8E%86%E5%B7%AE%E5%BC%82">遍历差异</a></li><li><a href="https://baofeidyz.com/2024/eb322648f18b/#%E4%B8%80%E4%BA%9B%E9%A2%98%E5%A4%96%E8%AF%9D">一些题外话</a></li></ul><span id="more"></span><h1 id="HashTable不允许空值"><a href="#HashTable不允许空值" class="headerlink" title="HashTable不允许空值"></a>HashTable不允许空值</h1><p>这个是<code>Hashtable</code>的<code>put</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><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> V <span class="title function_">put</span><span class="params">(K key, V value)</span> &#123;  </span><br><span class="line">    <span class="comment">// Make sure the value is not null  </span></span><br><span class="line">    <span class="keyword">if</span> (value == <span class="literal">null</span>) &#123;  </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">// Makes sure the key is not already in the hashtable.  </span></span><br><span class="line">    Entry&lt;?,?&gt; tab[] = table;  </span><br><span class="line">    <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> key.hashCode();  </span><br><span class="line">    <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> (hash &amp; <span class="number">0x7FFFFFFF</span>) % tab.length;  </span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span>  </span><br><span class="line">    Entry&lt;K,V&gt; entry = (Entry&lt;K,V&gt;)tab[index];  </span><br><span class="line">    <span class="keyword">for</span>(; entry != <span class="literal">null</span> ; entry = entry.next) &#123;  </span><br><span class="line">        <span class="keyword">if</span> ((entry.hash == hash) &amp;&amp; entry.key.equals(key)) &#123;  </span><br><span class="line">            <span class="type">V</span> <span class="variable">old</span> <span class="operator">=</span> entry.value;  </span><br><span class="line">            entry.value = value;  </span><br><span class="line">            <span class="keyword">return</span> old;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    addEntry(hash, key, value, index);  </span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我简单查了一下校验的原因，可信度比较高的是因为<code>Hashtable</code>出现的比较早，是在Java 1.0引入的，发布于1996年1月，可能是为了确认<code>get(key)</code>方法返回<code>null</code>时，不是因为值为<code>null</code>而返回，而是因为真的没有这个<code>key</code>。</p><p>而<code>HashMap</code>是在Java 1.2引入的，发布于1998年12月。<code>Hashmap</code>继承于<code>AbstractMap</code></p><h1 id="Hashtable是线程安全的"><a href="#Hashtable是线程安全的" class="headerlink" title="Hashtable是线程安全的"></a>Hashtable是线程安全的</h1><p><code>Hashtable</code>主要的方法都增加了<code>synchronized</code>关键字，确保了线程安全，<code>HashMap</code>则没有。</p><h1 id="性能比HashMap差"><a href="#性能比HashMap差" class="headerlink" title="性能比HashMap差"></a>性能比HashMap差</h1><p>也是因为增加<code>synchronized</code>关键字</p><h1 id="继承差异"><a href="#继承差异" class="headerlink" title="继承差异"></a>继承差异</h1><p><code>HashMap</code>继承的是<code>AbstractMap</code>，而&#96;Hash</p><h1 id="遍历差异"><a href="#遍历差异" class="headerlink" title="遍历差异"></a>遍历差异</h1><p><code>HashMap</code>引入了<code>fail-fast</code>机制，如果迭代过程中被其他线程做了修改（除迭代器的remove方法外），会抛出<code>ConcurrentModificationException</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="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">forEach</span><span class="params">(Consumer&lt;? <span class="built_in">super</span> K&gt; action)</span> &#123;  </span><br><span class="line">    Node&lt;K,V&gt;[] tab;  </span><br><span class="line">    <span class="keyword">if</span> (action == <span class="literal">null</span>)  </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();  </span><br><span class="line">    <span class="keyword">if</span> (size &gt; <span class="number">0</span> &amp;&amp; (tab = table) != <span class="literal">null</span>) &#123;  </span><br><span class="line">        <span class="type">int</span> <span class="variable">mc</span> <span class="operator">=</span> modCount;  </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; tab.length; ++i) &#123;  </span><br><span class="line">            <span class="keyword">for</span> (Node&lt;K,V&gt; e = tab[i]; e != <span class="literal">null</span>; e = e.next)  </span><br><span class="line">                action.accept(e.key);  </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="keyword">if</span> (modCount != mc)  </span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ConcurrentModificationException</span>();  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Hashtable</code>因为用了同步机制，就不存在这个问题了</p><h1 id="一些题外话"><a href="#一些题外话" class="headerlink" title="一些题外话"></a>一些题外话</h1><p>由于<code>Hashtable</code>几乎所有的方法都是同步的，所以实际开发中很少使用，而我自己则是从来没用过。需要多线程使用时，建议选择<code>ConcurrentHashMap</code></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>周报-第1期：个人博客新增RSS</title>
      <link>https://baofeidyz.com/2024/8b4b824ee5c2/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408071010661.png"></p>
<ul>
<li>新增两篇shell脚本：<a href="https://baofeidyz.com/2024/7722782a22f5/">Git删除本地所有分支</a>、<a href="https://baofeidyz.com/2024/638ccbb78dca/">Maven多模块时仅构建单个模块</a></li>
<li><a href="https://baofeidyz.com/2024/8b4b824ee5c2/#%E7%BB%99%E5%8D%9A%E5%AE%A2%E5%A2%9E%E5%8A%A0%E4%BA%86RSS%E6%8F%92%E4%BB%B6">个人博客增加了RSS插件</a></li>
</ul>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/%E5%91%A8%E6%8A%A5/">周报</category>
      <category domain="https://baofeidyz.com/tags/hexo/">hexo</category>
      <category domain="https://baofeidyz.com/tags/hexo-next/">hexo-next</category>
      <pubDate>Thu, 08 Aug 2024 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408071010661.png"></p><ul><li>新增两篇shell脚本：<a href="https://baofeidyz.com/2024/7722782a22f5/">Git删除本地所有分支</a>、<a href="https://baofeidyz.com/2024/638ccbb78dca/">Maven多模块时仅构建单个模块</a></li><li><a href="https://baofeidyz.com/2024/8b4b824ee5c2/#%E7%BB%99%E5%8D%9A%E5%AE%A2%E5%A2%9E%E5%8A%A0%E4%BA%86RSS%E6%8F%92%E4%BB%B6">个人博客增加了RSS插件</a></li></ul><span id="more"></span><h1 id="给博客增加了RSS插件"><a href="#给博客增加了RSS插件" class="headerlink" title="给博客增加了RSS插件"></a>给博客增加了RSS插件</h1><ul><li>项目地址：<a href="https://github.com/hexojs/hexo-generator-feed">https://github.com/hexojs/hexo-generator-feed</a></li></ul><h2 id="安装及配置修改"><a href="#安装及配置修改" class="headerlink" title="安装及配置修改"></a>安装及配置修改</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-generator-feed --save</span><br></pre></td></tr></table></figure><p>在hexo的<code>_config.yml</code>文件最末尾加上</p><figure class="highlight yml"><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></pre></td><td class="code"><pre><span class="line"><span class="attr">feed:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">atom</span></span><br><span class="line">  <span class="attr">path:</span> <span class="string">atom.xml</span></span><br><span class="line">  <span class="attr">limit:</span> <span class="number">20</span></span><br><span class="line">  <span class="attr">hub:</span></span><br><span class="line">  <span class="attr">content:</span></span><br><span class="line">  <span class="attr">content_limit:</span> <span class="number">140</span></span><br><span class="line">  <span class="attr">content_limit_delim:</span> <span class="string">&#x27; &#x27;</span></span><br><span class="line">  <span class="attr">order_by:</span> <span class="string">-date</span></span><br><span class="line">  <span class="attr">icon:</span> <span class="string">icon.png</span></span><br><span class="line">  <span class="attr">autodiscovery:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">template:</span></span><br></pre></td></tr></table></figure><p>此时你的RSS订阅已经部署成功，对应地址是<code>你的域名/atom.xml</code>，比如我的RSS订阅地址是<a href="https://baofeidyz.com/atom.xml">https://baofeidyz.com/atom.xml</a></p><h2 id="修改hexo-next配置增加按钮"><a href="#修改hexo-next配置增加按钮" class="headerlink" title="修改hexo next配置增加按钮"></a>修改hexo next配置增加按钮</h2><blockquote><p>我的博客使用的是hexo next主题，对应项目地址是：<a href="https://theme-next.js.org/">https://theme-next.js.org/</a></p></blockquote><p>hexo next在两个位置提供了RSS订阅按钮的位置，分别是菜单和文章底部。</p><ul><li>菜单：</li></ul><figure class="highlight yml"><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"><span class="attr">menu:</span></span><br><span class="line">  <span class="attr">RSS:</span> <span class="string">/atom.xml</span> <span class="string">||</span> <span class="string">fa</span> <span class="string">fa-rss</span></span><br></pre></td></tr></table></figure><p>即可加上菜单位置的RSS订阅按钮，如下图所示：<br><img src="https://cdn.baofeidyz.com/img/202408071001695.png"></p><ul><li>文章底部</li></ul><figure class="highlight yml"><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"><span class="attr">follow_me:</span></span><br><span class="line">  <span class="attr">RSS:</span> <span class="string">/atom.xml</span> <span class="string">||</span> <span class="string">fa</span> <span class="string">fa-rss</span></span><br></pre></td></tr></table></figure><p>我觉得没有太大必要，所以我并没有开启这个文章底部的订阅按钮，就不截图展示效果了，大家可以自行尝试。</p><h2 id="RSS订阅效果展示"><a href="#RSS订阅效果展示" class="headerlink" title="RSS订阅效果展示"></a>RSS订阅效果展示</h2><p>RSS订阅软件为<a href="https://www.thunderbird.net/zh-CN/">Thunderbird</a></p><p><img src="https://cdn.baofeidyz.com/img/202408071007941.png"></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Maven多模块时仅构建单个模块</title>
      <link>https://baofeidyz.com/2024/638ccbb78dca/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408061430341.png"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Maven/">Maven</category>
      <category domain="https://baofeidyz.com/tags/Shell/">Shell</category>
      <pubDate>Tue, 06 Aug 2024 06:28:32 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408061430341.png"></p><span id="more"></span><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mvn clean package -U -Dmaven.test.skip=<span class="literal">true</span> -pl 子模块1的相对路径,子模块2的相对路径 -am -T 4 --settings ~/.m2/settings.xml</span><br></pre></td></tr></table></figure><blockquote><p>如果你只有一个子模块，需要去掉间隔符号</p></blockquote><ul><li><code>-U</code>：表示会强制更新带有<code>SNAPSHOT</code>标记的快照依赖</li><li><code>-Dmaven.test.skip=true</code>：表示会跳过代码测试代码的编译和运行</li><li><code>-pl</code>：意为project list，用于指定你需要构建的子模块</li><li><code>-am</code>：意为also make，用于表示需要将子模块相关的依赖模块一起构建</li><li><code>-T</code>：并发构建</li><li><code>--settings</code>：指定具体的maven settings文件，可选参数</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Git删除本地所有分支</title>
      <link>https://baofeidyz.com/2024/7722782a22f5/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408061412848.png"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Git/">Git</category>
      <category domain="https://baofeidyz.com/tags/Shell/">Shell</category>
      <pubDate>Tue, 06 Aug 2024 06:09:58 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408061412848.png"></p><span id="more"></span><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch | grep -v $(git branch --show-current) | xargs git branch -D</span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>Oceanbase（Oracle）踩坑记录</title>
      <link>https://baofeidyz.com/2024/938f9eb4796d/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408021024669.png"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/">数据库</category>
      <category domain="https://baofeidyz.com/tags/Oceanbase/">Oceanbase</category>
      <pubDate>Fri, 02 Aug 2024 02:25:48 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408021024669.png"></p><span id="more"></span><h1 id="ORA-01843-not-a-valid-month"><a href="#ORA-01843-not-a-valid-month" class="headerlink" title="ORA-01843: not a valid month"></a>ORA-01843: not a valid month</h1><h2 id="错误SQL"><a href="#错误SQL" class="headerlink" title="错误SQL"></a>错误SQL</h2><figure class="highlight sql"><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="keyword">SELECT</span></span><br><span class="line"><span class="operator">*</span></span><br><span class="line"><span class="keyword">FROM</span></span><br><span class="line">mytable</span><br><span class="line"><span class="keyword">WHERE</span></span><br><span class="line">mytable.xxx <span class="keyword">BETWEEN</span> <span class="string">&#x27;2024-01-10 00:00:00&#x27;</span> <span class="keyword">AND</span> <span class="string">&#x27;2025-01-10 23:59:59&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="错误原因"><a href="#错误原因" class="headerlink" title="错误原因"></a>错误原因</h2><p>没有使用<code>TO_DATE</code>函数</p><h1 id="ORA-01810-format-code-appears-twice"><a href="#ORA-01810-format-code-appears-twice" class="headerlink" title="ORA-01810: format code appears twice"></a>ORA-01810: format code appears twice</h1><h2 id="错误SQL-1"><a href="#错误SQL-1" class="headerlink" title="错误SQL"></a>错误SQL</h2><figure class="highlight sql"><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="keyword">SELECT</span></span><br><span class="line"><span class="operator">*</span></span><br><span class="line"><span class="keyword">FROM</span></span><br><span class="line">mytable</span><br><span class="line"><span class="keyword">WHERE</span></span><br><span class="line">mytable.xxx <span class="keyword">BETWEEN</span> TO_DATE(<span class="string">&#x27;2024-01-10 00:00:00&#x27;</span>, <span class="string">&#x27;yyyy-MM-dd HH:mm:ss&#x27;</span>) <span class="keyword">AND</span> TO_DATE(<span class="string">&#x27;2025-01-10 23:59:59&#x27;</span>, <span class="string">&#x27;yyyy-MM-dd HH:mm:ss&#x27;</span>)</span><br></pre></td></tr></table></figure><h2 id="错误原因-1"><a href="#错误原因-1" class="headerlink" title="错误原因"></a>错误原因</h2><p>Oracle不区分大小写，所以这里不能用<code>yyyy-MM-dd HH:mm:ss</code>，日期里面有<code>mm</code>，时间中也有<code>mm</code>就导致了这个错误。<br>改成<code>yyyy-mm-dd hh24:mi:ss</code>即可</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://stackoverflow.com/questions/34213502/ora-01810-format-code-appears-twice">ORA-01810: format code appears twice</a></li><li><a href="https://www.cnblogs.com/techyc/p/3708777.html">Oracle转换时间出现的问题：ORA-01810: format code appears twice</a></li></ul><h1 id="执行计划查看方式"><a href="#执行计划查看方式" class="headerlink" title="执行计划查看方式"></a>执行计划查看方式</h1><p>这块官网文档有写：<a href="https://www.oceanbase.com/docs/common-oceanbase-database-cn-10000000001700611">查看执行计划</a></p><p>简单来说主要就是在SQL上加上<code>EXPLAIN</code>即可</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>手把手教你将自己的项目发布到maven中央仓库（2024年最新版）</title>
      <link>https://baofeidyz.com/2024/e342b7febf8f/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202407180044201.png"></p>
<blockquote>
<p>前言：最近我在将自己写的一些组件开源，想要发布到maven中央仓库，结果发现网上很多的教程都已经过时，而官网的教程也有一定程度上的混沌，主要是部分文章已经过时导致的。给我这样的小白选手带来了一些麻烦，在我自己成功发布后决定总结一下整个流程以及中间遇到的问题分享出来。</p>
</blockquote>
<h1 id="maven中央仓库的限制"><a href="#maven中央仓库的限制" class="headerlink" title="maven中央仓库的限制"></a>maven中央仓库的限制</h1><blockquote>
<p>官网地址：<a href="https://central.sonatype.org/publish/requirements/">https://central.sonatype.org/publish/requirements/</a></p>
</blockquote>
<ol>
<li>不允许发布快照；</li>
<li>必须有javadoc包；</li>
<li>必须有souce包：也就是你的代码是开源的，这也是为什么一些不开源jar包不选择中央仓库的主要原因，比如itext和aspose等</li>
<li>至少有<code>.md5</code>和<code>.sha</code>校验结果；</li>
<li>必须要使用GPG或者gpg签名；</li>
<li>必须有<code>pom.xml</code>文件：同时需要有正确的<code>groupId</code>、<code>artifactId</code>、<code>version</code>、<code>name</code>、<code>description</code>、<code>url</code>、<code>licenses</code>、<code>developers</code>、<code>scm</code></li>
</ol>
<p>那么接下来我会详细讲解一下关于这些条件的。</p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/maven/">maven</category>
      <pubDate>Wed, 17 Jul 2024 09:32:24 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202407180044201.png"></p><blockquote><p>前言：最近我在将自己写的一些组件开源，想要发布到maven中央仓库，结果发现网上很多的教程都已经过时，而官网的教程也有一定程度上的混沌，主要是部分文章已经过时导致的。给我这样的小白选手带来了一些麻烦，在我自己成功发布后决定总结一下整个流程以及中间遇到的问题分享出来。</p></blockquote><h1 id="maven中央仓库的限制"><a href="#maven中央仓库的限制" class="headerlink" title="maven中央仓库的限制"></a>maven中央仓库的限制</h1><blockquote><p>官网地址：<a href="https://central.sonatype.org/publish/requirements/">https://central.sonatype.org/publish/requirements/</a></p></blockquote><ol><li>不允许发布快照；</li><li>必须有javadoc包；</li><li>必须有souce包：也就是你的代码是开源的，这也是为什么一些不开源jar包不选择中央仓库的主要原因，比如itext和aspose等</li><li>至少有<code>.md5</code>和<code>.sha</code>校验结果；</li><li>必须要使用GPG或者gpg签名；</li><li>必须有<code>pom.xml</code>文件：同时需要有正确的<code>groupId</code>、<code>artifactId</code>、<code>version</code>、<code>name</code>、<code>description</code>、<code>url</code>、<code>licenses</code>、<code>developers</code>、<code>scm</code></li></ol><p>那么接下来我会详细讲解一下关于这些条件的。</p><span id="more"></span><h2 id="不允许发布快照"><a href="#不允许发布快照" class="headerlink" title="不允许发布快照"></a>不允许发布快照</h2><p>快照即<code>snapshot</code>，也就是说你<code>pom.xml</code>文件中的<code>version</code>中不能包含<code>SNAPSHOT</code>关键字</p><h2 id="必须有javadoc包"><a href="#必须有javadoc包" class="headerlink" title="必须有javadoc包"></a>必须有javadoc包</h2><p>在你的<code>pom.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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-javadoc-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.9.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">id</span>&gt;</span>attach-javadocs<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">goal</span>&gt;</span>jar<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>其中插件的版本最好是直接使用最新的，你可以在这里查看最新的插件版本：<a href="https://central.sonatype.com/artifact/org.apache.maven.plugins/maven-javadoc-plugin">maven-javadoc-plugin</a></p><p>这样在最后使用maven来发布的时候，这个插件会自动帮你处理好javadoc的部分。当然，如果你的代码中不符合javadoc规范，则需要通过错误信息自行处理掉。</p><h2 id="必须有souce包"><a href="#必须有souce包" class="headerlink" title="必须有souce包"></a>必须有souce包</h2><p>和<a href="#%E5%BF%85%E9%A1%BB%E6%9C%89javadoc%E5%8C%85">必须有javadoc包</a>类似，也是通过插件来处理。</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-source-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.2.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">id</span>&gt;</span>attach-sources<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">goal</span>&gt;</span>jar-no-fork<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>插件最新版本见：<a href="https://central.sonatype.com/artifact/org.apache.maven.plugins/maven-source-plugin">maven-source-plugin</a></p><h2 id="至少有-md5和-sha校验结果"><a href="#至少有-md5和-sha校验结果" class="headerlink" title="至少有.md5和.sha校验结果"></a>至少有<code>.md5</code>和<code>.sha</code>校验结果</h2><p>和<a href="#%E5%BF%85%E9%A1%BB%E6%9C%89javadoc%E5%8C%85">必须有javadoc包</a>类似，也是通过插件来处理。</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.sonatype.central<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>central-publishing-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.5.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">extensions</span>&gt;</span>true<span class="tag">&lt;/<span class="name">extensions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">publishingServerId</span>&gt;</span>central<span class="tag">&lt;/<span class="name">publishingServerId</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">checksums</span>&gt;</span>required<span class="tag">&lt;/<span class="name">checksums</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">deploymentName</span>&gt;</span>记得修改这里，是你在中央仓库任务中的发布任务名称没有特别重要的意思<span class="tag">&lt;/<span class="name">deploymentName</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>插件最新版本见：<a href="https://central.sonatype.com/artifact/org.sonatype.central/central-publishing-maven-plugin">central-publishing-maven-plugin</a></p><p>同时这个插件也包含了直接发布到中央仓库的作用，后面会单独再讲到的。</p><h2 id="必须要使用GPG或者PGP签名"><a href="#必须要使用GPG或者PGP签名" class="headerlink" title="必须要使用GPG或者PGP签名"></a>必须要使用GPG或者PGP签名</h2><p>和<a href="#%E5%BF%85%E9%A1%BB%E6%9C%89javadoc%E5%8C%85">必须有javadoc包</a>类似，也是通过插件来处理。</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-gpg-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.2.4<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">id</span>&gt;</span>sign-artifacts<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">phase</span>&gt;</span>verify<span class="tag">&lt;/<span class="name">phase</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">goal</span>&gt;</span>sign<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>插件最新版本见：<a href="https://central.sonatype.com/artifact/org.apache.maven.plugins/maven-gpg-plugin">maven-gpg-plugin</a></p><h3 id="安装GPG"><a href="#安装GPG" class="headerlink" title="安装GPG"></a>安装GPG</h3><p>除了配置maven插件之外，你还需要在本地安装<code>gpg</code>以及初始化自己的私钥、公钥，并且还需要将公钥推送到三方服务器上。<br>这个是官网对应的介绍：<a href="https://central.sonatype.org/publish/requirements/gpg/">https://central.sonatype.org/publish/requirements/gpg/</a></p><p>我使用的是macOS + <a href="https://brew.sh/">Homebrew</a>的组合：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install gpg</span><br></pre></td></tr></table></figure><h3 id="生成密钥"><a href="#生成密钥" class="headerlink" title="生成密钥"></a>生成密钥</h3><p>安装好<code>gpg</code>后，我们需要生成自己的私钥和公钥：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --gen-key</span><br></pre></td></tr></table></figure><p>需要输入自己的姓名、电子邮件地址和密码。同时此密钥的有效期默认为 3 年（官网文档没更新，现在默认已经有效期是3年）。</p><p>当密钥过期以后用到这个密码来延长有效期。</p><h3 id="分发公钥"><a href="#分发公钥" class="headerlink" title="分发公钥"></a>分发公钥</h3><p>使用命令查看自己的密钥key</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --list-keys</span><br></pre></td></tr></table></figure><p>会拿到类似的结果：</p><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></pre></td><td class="code"><pre><span class="line">~ ❯ gpg --list-keys                                                    16:06:40</span><br><span class="line">[keyboxd]</span><br><span class="line">---------</span><br><span class="line">pub   xxx 2024-07-10 [SC] [有效至：2027-07-10] ABA2A06BB49B04E3B47616698B90791C18BFEF1E</span><br><span class="line">uid             [ 绝对 ] baofeidyz &lt;baofeidyz@gmail.com&gt;</span><br><span class="line">sub   xxx 2024-07-10 [E] [有效至：2027-07-10]</span><br></pre></td></tr></table></figure><p>可以看到这个pub一行最后有一串类似于UUID的内容，比如我的就是：<code>ABA2A06BB49B04E3B47616698B90791C18BFEF1E</code>，这个就是你的密钥key。</p><p>再使用命令分发即可：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --keyserver keyserver.ubuntu.com --send-keys ABA2A06BB49B04E3B47616698B90791C18BFEF1E</span><br></pre></td></tr></table></figure><p>其中<code>keyserver.ubuntu.com</code>有几个替代品：</p><ul><li><code>keyserver.ubuntu.com</code></li><li><code>keys.openpgp.org</code></li><li><code>pgp.mit.edu</code></li></ul><p>我自己用的<code>keys.openpgp.org</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --keyserver keys.openpgp.org --send-keys ABA2A06BB49B04E3B47616698B90791C18BFEF1E</span><br></pre></td></tr></table></figure><p>这是我的分发链接：<a href="https://keys.openpgp.org/vks/v1/by-fingerprint/ABA2A06BB49B04E3B47616698B90791C18BFEF1E">https://keys.openpgp.org/vks/v1/by-fingerprint/ABA2A06BB49B04E3B47616698B90791C18BFEF1E</a></p><p>分发后，你也可以在这个网页搜索自己的key：<a href="https://keys.openpgp.org/">https://keys.openpgp.org/</a></p><h2 id="必须有pom-xml文件"><a href="#必须有pom-xml文件" class="headerlink" title="必须有pom.xml文件"></a>必须有<code>pom.xml</code>文件</h2><p>实际在发布的时候，必须有单独的<code>pom.xml</code>文件。<br>但是我是用插件<a href="https://central.sonatype.com/artifact/org.sonatype.central/central-publishing-maven-plugin">central-publishing-maven-plugin</a>发布，所以我们只需要按照maven规范在项目中维护好<code>pom.xml</code>文件即可，打包的时候会由插件将<code>pom.xml</code>挪到正确的位置。</p><p>关于<code>pom.xml</code>文件内容的维护涉及了很多内容，我把<code>groupId</code>、<code>artifactId</code>、<code>version</code>、<code>name</code>、<code>description</code>、<code>url</code>合并为常规内容，其他的则单独描述。</p><blockquote><p>我这里也把我自己的<a href="https://github.com/baofeidyz/PageHelperEnhance/blob/main/pom.xml">pom.xml</a>文件链接放这里供大家参考。<br>需要注意的是官网给出的示例已经过时了（截止2024年7月10日的信息）。</p></blockquote><h3 id="常规部分"><a href="#常规部分" class="headerlink" title="常规部分"></a>常规部分</h3><p>开始这部分之前，你需要在中央仓库完成注册&#x2F;登录并且需要验证属于自己的域。<br>访问<a href="https://central.sonatype.com/">官网</a>，在最右侧有一个<code>Sign In</code>按钮<br><img src="https://cdn.baofeidyz.com/img/202407171616135.png"><br>我建议是直接使用GitHub授权登录。有两个理由：</p><ol><li>后续关于<code>scm</code>的声明时需要用到GitHub开源代码托管服务；</li><li>如果你没有自己的<a href="https://zh.wikipedia.org/zh-sg/%E5%9F%9F%E5%90%8D">域名</a>，你可以直接使用GitHub提供的；</li></ol><p>如果你像我一样，希望用自己的域名，则在登录完成后点击自己的名称，然后选择“View Namespaces”<br><img src="https://cdn.baofeidyz.com/img/202407171620545.png"><br>然后点击“Add Namespace”按照网页提示添加，然后去修改dns解析即可。需要注意的是，这里的Namespcae和域名是反的，比如我的域名是<a href="https://baofeidyz.com/">baofeidyz.com</a>，则这里需要输入的是<code>com.baofeidyz</code></p><ul><li><code>groupId</code>：这里则对应刚才的<code>Namespcae</code>即可</li><li><code>artifactId</code>：组件ID，任意即可，比如我最近上传的组件<a href="https://central.sonatype.com/artifact/com.baofeidyz/PageHelperEnhance">PageHelperEnhance</a></li><li><code>version</code>：版本号，比如<code>1.0.0</code>（记得不要带-SNAPSHOT，maven中央仓库<a href="#%E4%B8%8D%E5%85%81%E8%AE%B8%E5%8F%91%E5%B8%83%E5%BF%AB%E7%85%A7">不允许发布快照</a>）</li><li><code>name</code>：组件名称，比如<code>PageHelperEnhance</code></li><li><code>description</code>：描述，比如“Mybatis PageHelper组件的功能加强，封装了分页查询全部数据以及边查询边操作的生产者消费者模式。”</li><li><code>url</code>：开源地址，比如：<a href="https://github.com/baofeidyz/PageHelperEnhance">https://github.com/baofeidyz/PageHelperEnhance</a></li></ul><h3 id="licenses"><a href="#licenses" class="headerlink" title="licenses"></a>licenses</h3><p>开源协议，关于选择的问题，建议去看看GitHub提供的资料：<a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository#choosing-the-right-license">choosing-the-right-license</a></p><p>另外<a href="https://central.sonatype.org/publish/requirements/#license-information">官网</a>有提到两个授权，分别是The Apache License, Version 2.0和MIT License<br>但是我个人觉得<a href="https://zh.wikipedia.org/wiki/MIT%E8%A8%B1%E5%8F%AF%E8%AD%89">MIT</a>和<a href="https://zh.wikipedia.org/wiki/GNU%E9%80%9A%E7%94%A8%E5%85%AC%E5%85%B1%E8%AE%B8%E5%8F%AF%E8%AF%81#GPLv3">GPL3.0</a>更重要些。</p><h4 id="MIT"><a href="#MIT" class="headerlink" title="MIT"></a>MIT</h4><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">licenses</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">license</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">name</span>&gt;</span>MIT License<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">url</span>&gt;</span>http://www.opensource.org/licenses/mit-license.php<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">license</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">licenses</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="developers"><a href="#developers" class="headerlink" title="developers"></a>developers</h3><p>这个相对来说简单很多，就是注明一下开发者信息即可，你可以直接参考我的内容进行修改。</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">developers</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">developer</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">name</span>&gt;</span>baofeidyz<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">email</span>&gt;</span>baofeidyz@gmail.com<span class="tag">&lt;/<span class="name">email</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">url</span>&gt;</span>https://baofeidyz.com<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">developer</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">developers</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>name写你自己的名称</li><li>email写你自己的电子邮箱地址</li><li>url则是你个人主页的地址，如果没有可以写你自己的GitHub主页，比如 <a href="https://github.com/baofeidyz/">https://github.com/baofeidyz</a></li></ul><p>如果有多个开发者，则多加几个<code>developer</code>节点即可</p><h3 id="scm"><a href="#scm" class="headerlink" title="scm"></a>scm</h3><p>官网介绍：<a href="https://central.sonatype.org/publish/requirements/#scm-information">https://central.sonatype.org/publish/requirements/#scm-information</a><br>如果用的也是GitHub管理，那直接根据我的示例修改成你自己的信息即可：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scm</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">connection</span>&gt;</span>scm:git:git://github.com/baofeidyz/PageHelperEnhance.git<span class="tag">&lt;/<span class="name">connection</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">developerConnection</span>&gt;</span>scm:git:ssh://github.com:baofeidyz/PageHelperEnhance.git<span class="tag">&lt;/<span class="name">developerConnection</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">url</span>&gt;</span>https://github.com/baofeidyz/PageHelperEnhance/tree/main<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">scm</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>connection是git地址，一般在GitHub项目有一个绿色的code按钮，点击以后即可找到</li></ul><p><img src="https://cdn.baofeidyz.com/img/202407171647621.png"></p><h1 id="准备发布到maven中央仓库"><a href="#准备发布到maven中央仓库" class="headerlink" title="准备发布到maven中央仓库"></a>准备发布到maven中央仓库</h1><p>如果你已经全部满足了上述所有的要求，此时你就可以开始做最后的准备了。<br>在<a href="https://central.sonatype.com/">官网</a>登录自己的账号以后，选择“View Account”<br><img src="https://cdn.baofeidyz.com/img/202407171650324.png"><br>再点击“Generate User Token”<br><img src="https://cdn.baofeidyz.com/img/202407171652839.png"><br>此时你会拿到<code>Username</code>和<code>Password</code>两个参数，以及一个<code>settings.xml</code>示例<br><img src="https://cdn.baofeidyz.com/img/202407171653263.png"></p><blockquote><p>如果你之前曾经发布过到自建maven仓库，相信后面要如何调整你已经非常清楚了。</p></blockquote><p>首先你的本地需要安装<a href="https://maven.apache.org/">maven</a>，下载解压以后会看到一个<code>settings.xml</code>文件，你需要做的就是将官网获取的<code>Username</code>和<code>Password</code>放到这个<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></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">settings</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/SETTINGS/1.2.0&quot;</span></span></span><br><span class="line"><span class="tag"><span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag"><span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">servers</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">server</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;<span class="name">id</span>&gt;</span>central<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;<span class="name">username</span>&gt;</span>xxxxx<span class="tag">&lt;/<span class="name">username</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;<span class="name">password</span>&gt;</span>xxxx<span class="tag">&lt;/<span class="name">password</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">server</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;/<span class="name">servers</span>&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><p>注意这里的<code>id</code>为<code>central</code>，实际上是对应着我在<a href="#%E8%87%B3%E5%B0%91%E6%9C%89%60.md5%60%E5%92%8C%60.sha%60%E6%A0%A1%E9%AA%8C%E7%BB%93%E6%9E%9C">至少有<code>.md5</code>和<code>.sha</code>校验结果</a>提到的内容：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.sonatype.central<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>central-publishing-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.5.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">extensions</span>&gt;</span>true<span class="tag">&lt;/<span class="name">extensions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">publishingServerId</span>&gt;</span>central<span class="tag">&lt;/<span class="name">publishingServerId</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">checksums</span>&gt;</span>required<span class="tag">&lt;/<span class="name">checksums</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">deploymentName</span>&gt;</span>记得修改这里，是你在中央仓库任务中的发布任务名称没有特别重要的意思<span class="tag">&lt;/<span class="name">deploymentName</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>和这个<code>publishingServerId</code>是保持一致的。</p><p>至此，所有的准备都OK了。</p><h1 id="发布到maven中央仓库"><a href="#发布到maven中央仓库" class="headerlink" title="发布到maven中央仓库"></a>发布到maven中央仓库</h1><p>在项目的<code>pom.xml</code>文件的同级目录中，执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mvn deploy</span><br></pre></td></tr></table></figure><p>即可，当然我更推荐指定<code>settings.xml</code>文件以避免一些没必要处理的问题</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mvn deploy --settings ~/.m2/settings.xml</span><br></pre></td></tr></table></figure><p>成功推送后，你还需要在官网 <a href="https://central.sonatype.com/publishing/deployments">https://central.sonatype.com/publishing/deployments</a>点击“Publish”正式发布。然后就可以在中央仓库搜索到自己的组件了。其他镜像库，比如<a href="https://developer.aliyun.com/mvn/search">阿里</a>、<a href="http://mvnrepository.com/">mvnrepository.com</a>等大概四个小时左右就会同步。<br><img src="https://cdn.baofeidyz.com/img/202407171709385.png"><br><img src="https://cdn.baofeidyz.com/img/202407172042241.png"><br><img src="https://cdn.baofeidyz.com/img/202407172044013.png"></p><p>也许你可能还会遇到一些问题，我简单例举几个以及处理方式：</p><h2 id="javadoc不通过"><a href="#javadoc不通过" class="headerlink" title="javadoc不通过"></a>javadoc不通过</h2><p>如果没有长期使用javadoc规范，很可能你的注释内容会因为各种不标准导致javadoc不通过的，此时你只需要耐心的查看报错信息然后根据信息去排查解决问题就可以。</p><h2 id="推送到maven仓库以后被拒绝"><a href="#推送到maven仓库以后被拒绝" class="headerlink" title="推送到maven仓库以后被拒绝"></a>推送到maven仓库以后被拒绝</h2><p><a href="https://central.sonatype.com/artifact/org.sonatype.central/central-publishing-maven-plugin">central-publishing-maven-plugin</a> 这个插件默认是推送到maven仓库后，会将结果信息返回给你（此外你还可以在官网通知中查看），会明确的告知你缺少什么。<br>你如果按照<a href="#maven%E4%B8%AD%E5%A4%AE%E4%BB%93%E5%BA%93%E7%9A%84%E9%99%90%E5%88%B6">maven中央仓库的限制</a>做了调整，相信这一步会很顺利。</p><h1 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h1><p>如果文章内容有不正确的地方，还请评论告知。如果你成功发布了项目也欢迎在评论区留下你的项目链接，非常欢迎大家宣传自己的开源项目！</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>PDF元数据解析</title>
      <link>https://baofeidyz.com/2024/f2f4221aa1fd/</link>
      <description>
        <![CDATA[<h1 id="查看PDF文件元数据的方法"><a href="#查看PDF文件元数据的方法" class="headerlink" title="查看PDF文件元数据的方法"></a>查看PDF文件元数据的方法</h1>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/PDF/">PDF</category>
      <pubDate>Mon, 17 Jun 2024 12:31:43 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="查看PDF文件元数据的方法"><a href="#查看PDF文件元数据的方法" class="headerlink" title="查看PDF文件元数据的方法"></a>查看PDF文件元数据的方法</h1><span id="more"></span><h2 id="方法一：直接修改后缀名"><a href="#方法一：直接修改后缀名" class="headerlink" title="方法一：直接修改后缀名"></a>方法一：直接修改后缀名</h2><p>这是简单的办法，将文件后缀名改成<code>txt</code>，然后用文本阅读器打开即可</p><h2 id="方法二：使用itext-rups"><a href="#方法二：使用itext-rups" class="headerlink" title="方法二：使用itext-rups"></a>方法二：使用itext-rups</h2><p>工具类则是推荐<a href="https://github.com/itext/i7j-rups">itext7-rups</a>，这是itext自己开源的阅读器</p><h2 id="方法三：写代码"><a href="#方法三：写代码" class="headerlink" title="方法三：写代码"></a>方法三：写代码</h2><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="type">String</span> <span class="variable">filePath</span> <span class="operator">=</span> <span class="string">&quot;xxx.pdf&quot;</span>;  </span><br><span class="line"></span><br><span class="line"><span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(filePath);  </span><br><span class="line"><span class="type">byte</span>[] content = <span class="keyword">new</span> <span class="title class_">byte</span>[fis.available()];  </span><br><span class="line">fis.read(content);  </span><br><span class="line">fis.close();  </span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">text</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(content);  </span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">System.out.println(text);</span><br></pre></td></tr></table></figure><h1 id="PDF元数据解析"><a href="#PDF元数据解析" class="headerlink" title="PDF元数据解析"></a>PDF元数据解析</h1><blockquote><p>2024年12月更新：</p></blockquote><ul><li><a href="https://baofeidyz.com/2024/8e8edebf287d/">PDF元数据解析：流对象和过滤器</a></li></ul><p>挖坑，待填，需要的可以先看相关资料</p><h1 id="相关资料"><a href="#相关资料" class="headerlink" title="相关资料"></a>相关资料</h1><ul><li><a href="https://www.iso.org/obp/ui/en/#iso:std:75839:en">ISO 32000-2:2020</a> <blockquote><p>2024年12月9日更新：发现原链接打不开，然后这个PDF文档有版权保护，不允许互联网分享，所以自求多福吧。</p></blockquote></li></ul><h1 id="关联博客"><a href="#关联博客" class="headerlink" title="关联博客"></a>关联博客</h1><ul><li><a href="https://baofeidyz.com/tags/PDF/">PDF专栏</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>技术选型-浏览器在线预览word</title>
      <link>https://baofeidyz.com/2024/af2009bc0c4b/</link>
      <description>
        <![CDATA[<p>项目中原本的方案中是将word保存了所有的修改并转为pdf做在线预览，但是在转换的过程中丢失了修改记录和批注信息。</p>
<h1 id="❌-方案一：使用aspose做word转pdf且同时保留修改记录-收费"><a href="#❌-方案一：使用aspose做word转pdf且同时保留修改记录-收费" class="headerlink" title="❌ 方案一：使用aspose做word转pdf且同时保留修改记录 -收费"></a>❌ 方案一：使用aspose做word转pdf且同时保留修改记录 -收费</h1><p>我司目前只采购了aspose 20.11版本，基于这个版本简单尝试了一下，缺点如下：</p>
<ol>
<li>只能完成删除记录的显示；</li>
<li>无法声明出操作人和操作时间；</li>
<li>单页word有概率被转成了两页；</li>
</ol>
<blockquote>
<p>高版本的aspose有可能是支持的，我在官网有找到一份文档，需要的朋友可以去看看： <a href="https://forum.aspose.com/t/aspose-words-export-comment-and-revision-author/236618">Aspose Words export Comment and Revision author  Aspose Words 导出注释和修订作者</a>，但我司没有采购高版本aspose的计划，所以方案一我这里是放弃了。</p>
</blockquote>
<p>另外我附上我自己写的<a href="https://github.com/baofeidyz/blog-word-online-view">demo</a>代码以供大家尝试：</p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B/">技术选型</category>
      <pubDate>Sun, 16 Jun 2024 11:57:28 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>项目中原本的方案中是将word保存了所有的修改并转为pdf做在线预览，但是在转换的过程中丢失了修改记录和批注信息。</p><h1 id="❌-方案一：使用aspose做word转pdf且同时保留修改记录-收费"><a href="#❌-方案一：使用aspose做word转pdf且同时保留修改记录-收费" class="headerlink" title="❌ 方案一：使用aspose做word转pdf且同时保留修改记录 -收费"></a>❌ 方案一：使用aspose做word转pdf且同时保留修改记录 -收费</h1><p>我司目前只采购了aspose 20.11版本，基于这个版本简单尝试了一下，缺点如下：</p><ol><li>只能完成删除记录的显示；</li><li>无法声明出操作人和操作时间；</li><li>单页word有概率被转成了两页；</li></ol><blockquote><p>高版本的aspose有可能是支持的，我在官网有找到一份文档，需要的朋友可以去看看： <a href="https://forum.aspose.com/t/aspose-words-export-comment-and-revision-author/236618">Aspose Words export Comment and Revision author  Aspose Words 导出注释和修订作者</a>，但我司没有采购高版本aspose的计划，所以方案一我这里是放弃了。</p></blockquote><p>另外我附上我自己写的<a href="https://github.com/baofeidyz/blog-word-online-view">demo</a>代码以供大家尝试：</p><span id="more"></span><p>maven依赖:</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span>  </span><br><span class="line">  <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.aspose<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span>  </span><br><span class="line">  <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>aspose-pdf<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span>  </span><br><span class="line">  <span class="tag">&lt;<span class="name">version</span>&gt;</span>20.11<span class="tag">&lt;/<span class="name">version</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span>  </span><br><span class="line">  <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.aspose<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span>  </span><br><span class="line">  <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>aspose-words<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span>  </span><br><span class="line">  <span class="tag">&lt;<span class="name">version</span>&gt;</span>20.11<span class="tag">&lt;/<span class="name">version</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这个jar包不在apache的maven仓库中，你可以在这里检查：<a href="https://repo.maven.apache.org/maven2/com/aspose/">https://repo.maven.apache.org/maven2/com/aspose/</a> 是没有aspose的任何包的，你需要在你的maven pom.xml文件中加上以下信息（位于<code>project</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">repositories</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">id</span>&gt;</span>AsposeJavaAPI<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>Aspose Java API<span class="tag">&lt;/<span class="name">name</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="name">url</span>&gt;</span>https://releases.aspose.com/java/repo/<span class="tag">&lt;/<span class="name">url</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></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.baofeidyz.blog;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.Comment;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.Document;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.EditingLanguage;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.NodeCollection;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.NodeType;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.PdfLoadOptions;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.PdfPageMode;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.PdfSaveOptions;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.Revision;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.RevisionOptions;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.RevisionsView;  </span><br><span class="line"><span class="keyword">import</span> com.aspose.words.ShowInBalloons;  </span><br><span class="line"><span class="keyword">import</span> java.io.InputStream;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">App</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;  </span><br><span class="line">        <span class="type">PdfLoadOptions</span> <span class="variable">loadOptions</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PdfLoadOptions</span>();  </span><br><span class="line">        loadOptions.getLanguagePreferences().setDefaultEditingLanguage(EditingLanguage.CHINESE_PRC);  </span><br><span class="line">        <span class="keyword">try</span> (<span class="type">InputStream</span> <span class="variable">inputStream</span> <span class="operator">=</span> App.class.getClassLoader().getResourceAsStream(<span class="string">&quot;test.docx&quot;</span>)) &#123;  </span><br><span class="line">            <span class="type">Document</span> <span class="variable">document</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Document</span>(inputStream, loadOptions);  </span><br><span class="line">            <span class="keyword">for</span> (Revision revision : document.getRevisions()) &#123;  </span><br><span class="line">                revision.setAuthor(<span class="string">&quot;author&quot;</span>);  </span><br><span class="line">            &#125;  </span><br><span class="line">            <span class="type">NodeCollection</span> <span class="variable">comments</span> <span class="operator">=</span> document.getChildNodes(NodeType.COMMENT, <span class="literal">true</span>);  </span><br><span class="line">  </span><br><span class="line">            <span class="keyword">for</span> (Comment comment : (Iterable&lt;Comment&gt;) comments) &#123;  </span><br><span class="line">                System.out.println(<span class="string">&quot;1= &quot;</span> + comment.getAuthor());  </span><br><span class="line">                comment.setInitial(comment.getAuthor());  </span><br><span class="line">            &#125;  </span><br><span class="line">            document.getLayoutOptions().setShowParagraphMarks(<span class="literal">true</span>);  </span><br><span class="line">            document.getRevisions().get(<span class="number">0</span>).getAuthor();  </span><br><span class="line">            document.setRevisionsView(RevisionsView.ORIGINAL);  </span><br><span class="line">            document.getLayoutOptions().setShowComments(<span class="literal">true</span>);  </span><br><span class="line">            document.setTrackRevisions(<span class="literal">true</span>);  </span><br><span class="line">            <span class="type">RevisionOptions</span> <span class="variable">revisionOptions</span> <span class="operator">=</span> document.getLayoutOptions().getRevisionOptions();  </span><br><span class="line">            revisionOptions.setShowOriginalRevision(<span class="literal">true</span>);  </span><br><span class="line">            revisionOptions.setShowInBalloons(ShowInBalloons.FORMAT_AND_DELETE);  </span><br><span class="line">            revisionOptions.setShowRevisionBars(<span class="literal">true</span>);  </span><br><span class="line">            revisionOptions.setShowRevisionMarks(<span class="literal">true</span>);  </span><br><span class="line">            <span class="type">PdfSaveOptions</span> <span class="variable">saveOptions</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PdfSaveOptions</span>();  </span><br><span class="line">  </span><br><span class="line">            saveOptions.setPreserveFormFields(<span class="literal">true</span>);  </span><br><span class="line">  </span><br><span class="line">            saveOptions.setAdditionalTextPositioning(<span class="literal">true</span>);  </span><br><span class="line">            saveOptions.setCreateNoteHyperlinks(<span class="literal">true</span>);  </span><br><span class="line">            saveOptions.setDisplayDocTitle(<span class="literal">true</span>);  </span><br><span class="line">            saveOptions.setExportDocumentStructure(<span class="literal">true</span>);  </span><br><span class="line">            saveOptions.setOpenHyperlinksInNewWindow(<span class="literal">true</span>);  </span><br><span class="line">            saveOptions.setPageMode(PdfPageMode.USE_OUTLINES);  </span><br><span class="line">            saveOptions.setPrettyFormat(<span class="literal">true</span>);  </span><br><span class="line">            document.save(<span class="string">&quot;target/result.pdf&quot;</span>, saveOptions);  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="❌-方案二：采用kkFileView-免费，使用前注意开源协议"><a href="#❌-方案二：采用kkFileView-免费，使用前注意开源协议" class="headerlink" title="❌ 方案二：采用kkFileView  -免费，使用前注意开源协议"></a>❌ 方案二：采用<a href="https://github.com/kekingcn/kkFileView">kkFileView</a>  -免费，使用前注意开源协议</h1><p>kkFileView在国内还算是被用得比较多，且做的比较简单的开源服务，基本上部署一个docker就可以快速搞定。</p><p>但经过研究后发现，kkFileView在针对word预览这点上，还是基于word转pdf实现的，且自身没有提供更多的参数，默认转换结果是没有批注和修订信息的。</p><h1 id="✅-方案三：基于NTKO和WPS-NPAPI-方案直接预览word-收费"><a href="#✅-方案三：基于NTKO和WPS-NPAPI-方案直接预览word-收费" class="headerlink" title="✅ 方案三：基于NTKO和WPS(NPAPI)方案直接预览word -收费"></a>✅ 方案三：基于NTKO和WPS(NPAPI)方案直接预览word -收费</h1><h2 id="NTKO-收费"><a href="#NTKO-收费" class="headerlink" title="NTKO -收费"></a>NTKO -收费</h2><p><code>NTKO</code>是重庆软航科技有限公司提供的文档插件，其主要是提供web在线编辑word功能。老版本只能支持IE浏览器，在新版本（平台版Plus-2019）中已经支持了Chrome（<a href="http://www.ntko.com/demo/faq/questions/question2.html">⚠️ 只支持了32位的Chrome</a>）等浏览器。</p><p><code>NTKO</code>和<code>aspose</code>一样，也是需要采购的，采购完成以后NTKO会给出详细的开发文档。其实现原理就是利用插件在IE中直接打开一个word编辑器，操作界面和WPS或者Office的word一致（取决于你本地电脑上有WPS还是word）</p><p>我这里就不贴具体的开发教程了，你能知晓大概情况就行，如果确定要用<code>NTKO</code>建议直接联系对方商务索要试用版再决定是否采购。</p><h2 id="WPS（NPAPI）-接口免费但需要专业版WPS"><a href="#WPS（NPAPI）-接口免费但需要专业版WPS" class="headerlink" title="WPS（NPAPI）-接口免费但需要专业版WPS"></a>WPS（NPAPI）-接口免费但需要专业版WPS</h2><blockquote><p>贴一下维基百科的介绍：<br><a href="https://zh.wikipedia.org/wiki/NPAPI">NPAPI</a>：Netscape Plugin Application Programming Interface， 是一个跨平台的通用浏览器插件应用程序接口。1995年由<a href="https://zh.wikipedia.org/wiki/%E7%B6%B2%E6%99%AF" title="网景">网景</a>公司发布，应用于<a href="https://zh.wikipedia.org/wiki/%E7%BD%91%E6%99%AF%E5%AF%BC%E8%88%AA%E8%80%85" title="网景导航者">网景导航者</a>2.0版本，但其他浏览器很快也跟进支持，成为一个共通的插件标准，与微软的<a href="https://zh.wikipedia.org/wiki/ActiveX" title="ActiveX">ActiveX</a>形成竞争关系。<br>2014年11月，Google宣布Chrome将于2015年1月默认屏蔽NPAPI插件，9月份会完全移除支持，以鼓励开发者和用户转用HTML5、Chrome API或Google Native Client等新技术取代NPAPI。虽然Google曾经提出了名为<a href="https://zh.wikipedia.org/w/index.php?title=PPAPI&action=edit&redlink=1">PPAPI</a>（Pepper Plugin API）的解决方案，但没有得到其它厂商的普遍支持。</p></blockquote><p><code>WPS(NPAPI)</code>是wps专业版提供的接口，web应用可以通过<code>NPAPI</code>与<code>WPS</code>应用进行交互。但<code>NPAPI</code>在高版本的浏览器中已经被移除。庆幸的是信创环境中的浏览器基本保留了<code>NPAPI</code></p><p>关于这个模式是可以在WPS官网找到的资料的，地址：<a href="https://open.wps.cn/previous/docs/client/wpsLoad">https://open.wps.cn/previous/docs/client/wpsLoad</a></p><p><img src="https://cdn.baofeidyz.com/img/202406141710256.png"><br>官网也给出了代码示例</p><figure class="highlight html"><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="meta">&lt;!DOCTYPE   <span class="keyword">html</span> &gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>  <span class="attr">lang</span> = <span class="string">&quot;en&quot;</span> &gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">meta</span>  <span class="attr">charset</span> = <span class="string">&quot;UTF-8&quot;</span> &gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">title</span>&gt;</span> wps二次开发演示  <span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">object</span>  <span class="attr">type</span> = <span class="string">&quot;application/x-wps&quot;</span>  <span class="attr">width</span> = <span class="string">&quot;1024&quot;</span>  <span class="attr">height</span> = <span class="string">&quot;768&quot;</span>  <span class="attr">id</span> = <span class="string">&quot;wps&quot;</span> &gt;</span><span class="tag">&lt;/<span class="name">object</span>&gt;</span></span><br><span class="line">     <span class="comment">&lt;!--通过类型 “application/x-wps” 来加载WPS浏览器NPAPI插件，并传递宽高值--&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">script</span>  <span class="attr">type</span> = <span class="string">&quot;text/javascript&quot;</span> &gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">         <span class="comment">//来获取wps的插件对象</span></span></span><br><span class="line"><span class="language-javascript">         <span class="keyword">var</span> obj  = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>( <span class="string">&quot;wps&quot;</span> );</span></span><br><span class="line"><span class="language-javascript">         <span class="comment">//获取WPS对象树的根节点，后续功能调用都是根据 Application 逐步得到的</span></span></span><br><span class="line"><span class="language-javascript">         <span class="keyword">var</span> <span class="title class_">Application</span>  = obj.<span class="property">Application</span>;</span></span><br><span class="line"><span class="language-javascript">         <span class="comment">//创建一个新的空白文档</span></span></span><br><span class="line"><span class="language-javascript">         <span class="title class_">Application</span>.<span class="title function_">createDocument</span>( <span class="string">&quot;wps&quot;</span> );</span></span><br><span class="line"><span class="language-javascript">         <span class="comment">//获取文档内容区域</span></span></span><br><span class="line"><span class="language-javascript">         <span class="keyword">var</span> range   = <span class="title class_">Application</span>.<span class="property">ActiveDocument</span>.<span class="property">Content</span> ;</span></span><br><span class="line"><span class="language-javascript">         <span class="comment">// 在文档内容区域插入一个3行5列的表格，设置表格显示边框</span></span></span><br><span class="line"><span class="language-javascript">         <span class="title class_">Application</span>.<span class="property">ActiveDocument</span>.<span class="property">Tables</span>.<span class="title class_">Add</span>(range, <span class="number">3</span>, <span class="number">5</span>).<span class="property">Borders</span>.<span class="property">Enable</span> = <span class="number">1</span> ;</span></span><br><span class="line"><span class="language-javascript">     &lt; /script&gt;</span></span><br><span class="line"><span class="language-javascript">&lt;/body&gt;</span></span><br><span class="line"><span class="language-javascript">&lt;/html&gt;</span></span><br></pre></td></tr></table></figure><p>我有使用奇安信可信浏览器（基于麒麟OS）验证：<br><img src="https://cdn.baofeidyz.com/img/202406141714129.png"><br>需要强调的是必须要使用专业版的wps，另外这个方案还有一个缺点就是能用的API几乎没有，也没有找到什么有效的文档，纯靠自己摸索</p><figure class="highlight js"><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"><span class="variable language_">this</span>.<span class="property">wpsEditor</span> = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;wps&#x27;</span>).<span class="property">Application</span>;  </span><br><span class="line"><span class="variable language_">this</span>.<span class="property">wpsEditor</span>.<span class="title function_">openDocumentRemote</span>(url, readonly);</span><br></pre></td></tr></table></figure><p>如果你知道更多的资料，还请在这篇博客下方评论补充，感谢。</p><h1 id="其他选型"><a href="#其他选型" class="headerlink" title="其他选型"></a>其他选型</h1><ul><li><a href="https://solution.wps.cn/">WPS WebOffice</a></li><li><a href="https://www.onlyoffice.com/">OnlyOffice</a></li><li><a href="https://www.microsoft.com/en-us/microsoft-365/free-office-online-for-the-web">Office 365 online</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>0成本搭建属于自己的摄影plog</title>
      <link>https://baofeidyz.com/2024/a6609d7716b6/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408051028958.png"></p>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%98%E8%85%BE/">折腾</category>
      <category domain="https://baofeidyz.com/categories/%E6%91%84%E5%BD%B1/">摄影</category>
      <category domain="https://baofeidyz.com/categories/%E7%94%9F%E6%B4%BB/">生活</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%98%E8%85%BE/">折腾</category>
      <category domain="https://baofeidyz.com/tags/%E6%91%84%E5%BD%B1/">摄影</category>
      <category domain="https://baofeidyz.com/tags/%E7%94%9F%E6%B4%BB/">生活</category>
      <pubDate>Fri, 31 May 2024 11:37:10 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.baofeidyz.com/img/202408051028958.png"></p><span id="more"></span><p>在v站上看到一篇帖子分享了开源项目<a href="https://github.com/linyuxuanlin/Gallery-Portfolio">Gallery-Portfolio</a>，看了一下demo感觉蛮不错的，可以展示快门、光圈和ISO信息。<br>这个项目部署需要用到 <a href="https://vercel.com/">vercel</a> 、<a href="https://www.cloudflare.com/developer-platform/r2/">Cloudflare R2</a>，当然如果你有自己的域名会更好。</p><h1 id="第一步，项目调整"><a href="#第一步，项目调整" class="headerlink" title="第一步，项目调整"></a>第一步，项目调整</h1><p>因为项目本身有一些文字描述、页面标题等不支持动态设置，所以你觉得介意的话就得自己调整，这需要你有一定的开发知识。</p><p>我自己也有简单调整一些文字，默认主题也改成了dark模式，你可以在这里看看效果 <a href="https://gallery.baofeidyz.com/">baofeidyz’s gallery</a></p><p>项目本身的readme写的很详细，建议先看看readme，然后我这里补充几点：</p><ol><li>nodejs安装方法：<a href="https://nodejs.org/en/download/package-manager">https://nodejs.org/en/download/package-manager</a></li><li>新建的<code>.env</code>文件不要提交。默认fork操作后的权限都是public的，所以不要提交这个文件以避免Cloudflare R2的密钥被盗用</li></ol><p>另外，如果你没有安装node.js你也可以不用拉取，善于利用GitHub提供的code space是更好的办法，关于code spaces可以看看<a href="https://docs.github.com/en/codespaces">https://docs.github.com/en/codespaces</a></p><h1 id="第二步，申请免费的Cloudflare-R2"><a href="#第二步，申请免费的Cloudflare-R2" class="headerlink" title="第二步，申请免费的Cloudflare R2"></a>第二步，申请免费的Cloudflare R2</h1><p>你需要自己注册Cloudflare账号，如果已经有那么就直接复用就可以。</p><p>创建一个单独的桶，然后我建议是上传包含照片的文件夹，任意上传一张先试试，自己知道咋回事以后再尝试继续上传即可。</p><p>桶创建好以后，还需要创建“R2 API令牌”，需要注意的是，Gallery-Portfolio有一个压缩照片生成缩略图的功能，所以你在创建R2 API令牌的时候，需要选择“对象读和写”的权限，“指定存储桶”为刚才创建的那个桶，TTL选择永久。</p><p>在本地填好<code>.env</code>文件，按照readme给出的方法测试一下。</p><h1 id="第三步，部署到vercel"><a href="#第三步，部署到vercel" class="headerlink" title="第三步，部署到vercel"></a>第三步，部署到vercel</h1><p>简单描述一下吧，你需要注册一个账号，然后绑定你的GitHub账号，然后就可以一键部署了。<br>部署的时候会有一个地方填写环境变量，你不用一个一个拷贝，你把整个<code>.env</code>文件的内容粘贴一下就可以了。</p><h1 id="可选一，去掉多余的exif信息"><a href="#可选一，去掉多余的exif信息" class="headerlink" title="可选一，去掉多余的exif信息"></a>可选一，去掉多余的exif信息</h1><p>安装<code>exiftool</code>工具，官网地址：<a href="https://exiftool.org/">https://exiftool.org</a></p><p>macOS可以使用<code>homebrew</code>安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install exiftool</span><br></pre></td></tr></table></figure><p>安装完成以后，可以使用以下脚本去除extif信息以保证隐私安全</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">exiftool -all= -tagsFromFile @ -ExposureTime -ISO -ApertureValue -FNumber *.jpg</span><br></pre></td></tr></table></figure><p>同时我自己还加上了一个版权信息，完整的脚本如下</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">exiftool -all= -tagsFromFile @ -ExposureTime -ISO -ApertureValue -FNumber -copyright=<span class="string">&quot;© 2024 baofeidyz@gmail.com&quot;</span> *.jpg</span><br></pre></td></tr></table></figure><p>每个相机厂商的exif信息的关键字可能有所不同，对于你想要保留的快门时间、ISO、光圈等信息可能与我给出的关键字是不同的，你可以通过</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">exiftool -s xxx.jpg</span><br></pre></td></tr></table></figure><p>来获取详细的关键字，然后再调整为你自己的脚本即可</p><h1 id="可选二，增加水印"><a href="#可选二，增加水印" class="headerlink" title="可选二，增加水印"></a>可选二，增加水印</h1><p>为了避免有人不经过我的允许拿去商用，我在每张照片的正中增加了水印，用的是Lightroom批量处理。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>新注册土耳其Apple ID订阅Apple Music详细操作步骤，新手一定要看！</title>
      <link>https://baofeidyz.com/apple-music-qa2/</link>
      <description>
        <![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697116537829/af2cfa8f-7bcf-4ae5-bb0c-b5d74085ed4a.png"></p>
<h1]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/Apple-Music/">Apple Music</category>
      <category domain="https://baofeidyz.com/tags/AppleMusic/">AppleMusic</category>
      <pubDate>Thu, 12 Oct 2023 05:24:52 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697116537829/af2cfa8f-7bcf-4ae5-bb0c-b5d74085ed4a.png"></p><h1 id="操作步骤"><a href="#操作步骤" class="headerlink" title="操作步骤"></a>操作步骤</h1><blockquote><p>⚠️ 警告：不要先绑定礼品卡，在下面的步骤中我有说明何时绑定</p><p>🔔 提示：刚开始使用时，不建议登录iCloud。只需要登录app store就可以订阅Apple Music，后期稳定以后再考虑使用iCloud。这一步骤不是必须，但有可能会因为你登录了iCloud导致封控等级变高，所以请先不要直接登录iCloud</p></blockquote><p><img src="https://cdn.baofeidyz.com/img/202308202107128.png"></p><p>首先看第一张图，我们打开Apple Music，在登录了土耳其Apple ID帐号后能看到激活页面有一个订阅按钮（<strong>Redeem Now</strong>）。</p><p>在我们点了订阅按钮以后，由于我们的账户没有激活iTunes，所以我们会看到一个弹窗提示。这里大概的意思是“你的Apple ID还没有在iTunes商店中使用过，请点击review按钮去登记”，所以我们直接点击弹窗中的Review按钮跳转去登记即可。</p><p><img src="https://cdn.baofeidyz.com/img/202308202110523.png"></p><p>点击<strong>订阅</strong>按钮以后，需要输入密码</p><p><img src="https://cdn.baofeidyz.com/img/202308202129671.png"></p><p>在新的页面中，我们会看到一个苹果媒体协议的内容，我们默认勾选<strong>Agree to Terms and Conditions</strong>就行了</p><p><img src="https://cdn.baofeidyz.com/img/202308202114464.png"></p><p>关于这个最上面这个信用卡和手机号两个选项，经过我个人的实测是可以不填的，所以不建议大家去选中。选中以后就会变成必填。</p><blockquote><p>如果不小心点到了，有两种解决方案：</p><ol><li><p>点击左上角的“Back”，返回到第一张图的状态，点击<strong>Redeem Now</strong>按钮重新走流程即可；</p></li><li><p><del>选择<strong>Mobile Phone</strong>，使用国内手机号也是可行的（我没有实测，是网友反馈给我的）</del> 之前确实可以，现在是完全不行了</p></li></ol></blockquote><p>要在这个弹窗中点击按钮，然后随便输入一个土耳其的地址和手机号即可（在之前的文章中，我有给出一个特定的值，已确认那个地址信息会被苹果封控，请勿再用）。 请使用三方服务网站提供的土耳其地址：<a href="https://www.meiguodizhi.com/tr-address">https://www.meiguodizhi.com/tr-address</a> 或者自行在Google地图中查找一个地址信息进行填写。</p><p><strong>这个时候就可以绑定礼品卡重新操作去订阅Apple Music服务了。</strong></p><p>如果你此时发现你必须要输入手机号或者信用卡，这意味着你已经被风控了。<br>你可以选择：</p><ol><li>打电话给中国大陆的Apple支持（只能电话），就直说你无法购买App Store中的收费应用即可，一般等待24小时后就可以了</li><li>更复杂的解决方案，这个方案我就不分享了，不然会很快被和谐（实在是需要的话，你可以通过我的闲鱼账号与我取得联系，但这会存在一些费用）</li></ol><blockquote><p><a href="https://m.tb.cn/h.hNFGTvG?tk=YpNh4ft34E3">点我跳转到闲鱼</a><br>如果闲鱼链接失效，可以直接搜索<strong>用户</strong>“土耳其礼品卡”，会员名x开头，7结尾，粉丝数量大于150的就是我了</p></blockquote><h1 id="背景（原因）"><a href="#背景（原因）" class="headerlink" title="背景（原因）"></a>背景（原因）</h1><blockquote><p>这一章节是故意放到后面的，便于大家直接根据前文操作。 对于一些比较感兴趣的朋友，则可以继续阅读</p></blockquote><p>今日（2023年10月12日）有朋友遇到了需要绑定信用卡或者土耳其手机号的问题，在尝试付费订阅Apple Music时，苹果弹出需要绑定信用卡或手机号的弹窗，且此时为必选，无法跳过。如下图，“Credit&#x2F;debit card”是默认勾选上的，无法取消。</p><p><img src="https://cdn.baofeidyz.com/img/202310122033168.jpeg"></p><p>这与我之前发布的文章<a href="https://baofeidyz.com/apple-music-qa1/">《Apple Music激活时遇iTunes注册弹窗的解决方案》</a>是不同的，当时这个页面甚至还可以绑定+86的手机号，而现在只能绑定+90（土耳其）的手机号了。</p><p>当然最后我还是给这位朋友解决的，但是操作方法过于复杂，就不多讲了，说出来可能一会儿就死了。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Apple Music激活时遇iTunes注册弹窗的解决方案</title>
      <link>https://baofeidyz.com/apple-music-qa1/</link>
      <description>
        <![CDATA[<blockquote>
<p>2023年10月12日更新：文章内容已过期，请移步到 <a href="https://baofeidyz.com/apple-music-qa2/">《新注册土耳其Apple ID订阅Apple]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/Apple-Music/">Apple Music</category>
      <category domain="https://baofeidyz.com/tags/AppleMusic/">AppleMusic</category>
      <pubDate>Sun, 20 Aug 2023 05:42:09 GMT</pubDate>
      <content:encoded>
        <![CDATA[<blockquote><p>2023年10月12日更新：文章内容已过期，请移步到 <a href="https://baofeidyz.com/apple-music-qa2/">《新注册土耳其Apple ID订阅Apple Music详细操作步骤，新手一定要看！》</a></p></blockquote><p>最近有朋友在激活Apple Music服务时候遇到因新注册Apple ID没有激活iTunes帐户产生了一些问题，这篇文章主要是更详细的解决方案。</p><p><img src="https://cdn.baofeidyz.com/img/202308202107128.png"></p><p>首先看第一张图，我们打开Apple Music，在登录了土耳其Apple ID帐号后能看到激活页面有一个订阅按钮（<strong>Redeem Now</strong>）。</p><p>在我们点了订阅按钮以后，由于我们的账户没有激活iTunes，所以我们会看到一个弹窗提示。这里大概的意思是“你的Apple ID还没有在iTunes商店中使用过，请点击review按钮去登记”，所以我们直接点击弹窗中的Review按钮跳转去登记即可。</p><p><img src="https://cdn.baofeidyz.com/img/202308202110523.png"></p><p>点击<strong>订阅</strong>按钮以后，需要输入密码</p><p><img src="https://cdn.baofeidyz.com/img/202308202129671.png"></p><p>在新的页面中，我们会看到一个苹果媒体协议的内容，我们默认勾选<strong>Agree to Terms and Conditions</strong>就行了</p><p><img src="https://cdn.baofeidyz.com/img/202308202114464.png"></p><p><img src="https://cdn.baofeidyz.com/img/202308202130652.png"></p><p>关于这个最上面这个信用卡和手机号两个选项，经过我个人的实测是可以不填的，所以不建议大家去选中。选中以后就会变成必填。</p><blockquote><p>如果不小心点到了，有两种解决方案：</p><ol><li><p>点击左上角的“Back”，返回到第一张图的状态，点击<strong>Redeem Now</strong>按钮重新走流程即可；</p></li><li><p>选择<strong>Mobile Phone</strong>，使用国内手机号也是可行的（我没有实测，是网友反馈给我的）</p></li></ol></blockquote><p>要在这个弹窗中点击按钮，然后随便输入一个土耳其的地址和手机号即可，我这里提供了一个参考数据：</p><figure class="highlight yaml"><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 class="attr">Street:</span> <span class="string">Gevhernesibe</span></span><br><span class="line"><span class="attr">Postcode:</span> <span class="number">38010</span></span><br><span class="line"><span class="attr">City:</span> <span class="string">İstasyon</span></span><br><span class="line"><span class="attr">Phone:</span> <span class="number">262</span> <span class="number">6560404</span></span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>macOS借助vmware隔离运行aTrust，实现宿主机“干净”连入局域网</title>
      <link>https://baofeidyz.com/2023/9e27261d88ca/</link>
      <description>
        <![CDATA[<blockquote>
<p>2024年12月4日更新：这个方案最终被抛弃了，建议大家直接使用 <a]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/macOS/">macOS</category>
      <category domain="https://baofeidyz.com/tags/aTrust/">aTrust</category>
      <category domain="https://baofeidyz.com/tags/easyconnect/">easyconnect</category>
      <pubDate>Sun, 28 May 2023 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<blockquote><p>2024年12月4日更新：这个方案最终被抛弃了，建议大家直接使用 <a href="https://github.com/docker-easyconnect/docker-easyconnect">https://github.com/docker-easyconnect/docker-easyconnect</a> 即可</p></blockquote><p>aTrust是深信服原easyconnect的升级产品，重点打造了一个“零信任”的概念，就是这个概念让我头皮发麻，其在官网直接挂着</p><blockquote><p>终端检测深入：支持进程级检测，可发现和阻止终端上非可信应用进程；在登录时、每一次访问业务时，对终端环境持续进行检测和认证，确保终端合规。</p></blockquote><p><img src="https://cdn.baofeidyz.com/img/202303281454270.png"></p><p>这和病毒有什么差别么？所以我当然不能让这种糟粕运行在我的macOS宿主机上，于是我开始了折腾之旅。</p><h1 id="第一步，申请个人免费的vmware"><a href="#第一步，申请个人免费的vmware" class="headerlink" title="第一步，申请个人免费的vmware"></a>第一步，申请个人免费的vmware</h1><p>这个申请的方法大家可以在网上搜一下，有很多详细的图文教程，我后面会考虑补充一篇博客，但现在没时间搞。</p><p>当然你也可以试试免费的<a href="https://www.virtualbox.org/">Oracle VM VirtualBox</a>，理论上来说也是一样可用的。</p><h1 id="第二步，下载debian-11的镜像文件"><a href="#第二步，下载debian-11的镜像文件" class="headerlink" title="第二步，下载debian 11的镜像文件"></a>第二步，下载debian 11的镜像文件</h1><p>我要特别讲一下为什么要下载debian 11，这是因为atrust官方提供的linux安装包特别强调了自己支持的是国产化的uos和麒麟os，但是咱们下载下来以后是一个deb文件，经过我自己测试，将deb包转为rpm包以后，在centos 7&#x2F;centos 7.5&#x2F;centos 8上都无法安装。</p><blockquote><p>当然我不排除是我自己操作有问题，大家如果特别喜欢用centos的，可以考虑自己折腾一下。但是centos已经不维护了，在使用过程中会有一些问题。</p></blockquote><p>经过我自己的测试，debian 11是可以使用的。</p><p>debian 11的镜像推荐大家用BT种子的方式，实测比直接通过镜像站下载的速度更快，<strong>但请不要使用迅雷这类只下载不上传的“吸血”软件。</strong><br>BT种子官网下载地址：<a href="https://cdimage.debian.org/debian-cd/current/amd64/bt-dvd/debian-11.6.0-amd64-DVD-1.iso.torrent">debian-11.6.0-amd64-DVD-1.iso</a> 其他版本的镜像大家可以自行在<a href="https://www.debian.org/download">官网</a>查找</p><blockquote><p>补充信息：BT下载软件推荐<a href="https://www.qbittorrent.org/download">qbittorrent</a>，请在下载成功以后保持至少24小时运行时间以供其他人从您本地获取文件。如果您有NAS服务器那就更好<br><strong>请不要使用迅雷这类只下载不上传的“吸血”软件</strong></p></blockquote><h1 id="第三步，使用vmware引导安装debian-11"><a href="#第三步，使用vmware引导安装debian-11" class="headerlink" title="第三步，使用vmware引导安装debian 11"></a>第三步，使用vmware引导安装debian 11</h1><p>这里我就不写具体的操作步骤了，请注意： </p><ol><li>虚拟机网络要选择<strong>桥接</strong>（这非常重要）</li><li>你需要安装桌面才能打开atrust登录你的账号</li><li>语言选择的时候直接选择中文，否则时区在启动后修改比较麻烦，很可能导致无法使用apt-get update更新</li></ol><h2 id="可能会遇到的问题"><a href="#可能会遇到的问题" class="headerlink" title="可能会遇到的问题"></a>可能会遇到的问题</h2><h3 id="无法执行apt-get-update或者是速度较慢"><a href="#无法执行apt-get-update或者是速度较慢" class="headerlink" title="无法执行apt-get update或者是速度较慢"></a>无法执行apt-get update或者是速度较慢</h3><p>可以改用镜像源，修改<code>/etc/apt/sources.list</code>文件的内容，这里用aliyun举例：</p><figure class="highlight plaintext"><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">deb https://mirrors.aliyun.com/debian/ bullseye main non-free contrib</span><br><span class="line">deb-src https://mirrors.aliyun.com/debian/ bullseye main non-free contrib</span><br><span class="line">deb https://mirrors.aliyun.com/debian-security/ bullseye-security main</span><br><span class="line">deb-src https://mirrors.aliyun.com/debian-security/ bullseye-security main</span><br><span class="line">deb https://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib</span><br><span class="line">deb-src https://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib</span><br><span class="line">deb https://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib</span><br><span class="line">deb-src https://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib</span><br></pre></td></tr></table></figure><p>然后再执行<code>apt-get update</code>即可</p><blockquote><p>如果你一直遇到更新失败，你可以按照这个检查顺序：</p><ol><li>ping mirrors.aliyun.com 看看能否ping通网络</li><li>检查 ping mirrors.aliyun.com 返回的IP是否是宿主机可以ping通的IP，以此确认是不是dns解析存在问题。存在问题可以通过修改debian 11上的&#x2F;etc&#x2F;hosts文件来解决</li><li>检查时区&#x2F;时间是否正确</li></ol></blockquote><h1 id="第四步，在debian-11中安装atrust"><a href="#第四步，在debian-11中安装atrust" class="headerlink" title="第四步，在debian 11中安装atrust"></a>第四步，在debian 11中安装atrust</h1><p>atrust下载客户端时，选择amd 64 麒麟Kylin即可，官网似乎没有找到下载介绍页，这个地址应该是有各个团队&#x2F;企业的VPN登录页提供的<br><img src="https://cdn.baofeidyz.com/img/202303281810191.png"></p><p>下载完的deb安装包，可以通过<code>scp</code>命令直接传输到debian中，当然也可以使用一些<code>sftp</code>客户端</p><p>安装deb时，需要带上相关的依赖，你可以执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get -f install atrust.deb</span><br></pre></td></tr></table></figure><blockquote><p>debian 11默认安装过程中没有开启root账号的ssh远程登录配置，你得去把这个打开。可参考：<a href="debian%20%E5%BC%80%E5%90%AFroot%E8%B4%A6%E5%8F%B7%E8%BF%9C%E7%A8%8Bssh%E7%99%BB%E5%BD%95.md">debian 开启root账号远程ssh登录</a><br>或者你直接在虚拟机页面中执行</p></blockquote><h1 id="第五步，debian开启路由转发"><a href="#第五步，debian开启路由转发" class="headerlink" title="第五步，debian开启路由转发"></a>第五步，debian开启路由转发</h1><p>通过修改<code>/etc/sysctl.conf</code>文件中的<code>net.ipv4.ip_forward</code>的值改为<code>1</code>，并执行<code>sysctl -p</code>使得配置生效即可<br>再把debian的防火墙关闭即可</p><h1 id="第六步，macOS宿主机修改路由a"><a href="#第六步，macOS宿主机修改路由a" class="headerlink" title="第六步，macOS宿主机修改路由a"></a>第六步，macOS宿主机修改路由a</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> route add 1.0.0.0/8 【这里写上debian虚拟机的IP地址】</span><br></pre></td></tr></table></figure><p>这样就可以了</p><p><code>1.0.0.0/8</code>这个是我随便写的，如果你们用的是192，或者172的开头的IP地址，那么就是<code>192.0.0.0/8</code>或者<code>172.0.0.0/8</code>。<br>当然如果你本地的IP地址就是192开头的，那么你可能需要拆得更细致一点。假设你本地的IP地址是192.168.1.100，你的内网地址是192.168.31开头，那么你的shell脚本就应该是</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> route add 192.168.32.0/24 【这里写上debian虚拟机的IP地址】</span><br></pre></td></tr></table></figure><p>这个地方需要你对网络有一定的了解，添加成功以后可以使用<code>telent</code>命令确认一下是否能通特定的端口</p><p>此外你还需要注意两点：</p><ol><li>这个debian虚拟机用的是桥接模式，所以每次连接新的路由器可能会拿到不同的IP，这时候你需要删掉之前的路由，然后加上新的路由<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> route delete 1.0/8</span><br></pre></td></tr></table></figure></li><li>对于内部使用域名访问的服务，则需要修改macOS宿主机的hosts文件，或者你本地自建一个dns服务来处理</li></ol><p>这样做了以后，不管你是<code>ping</code>还是 <code>telnet</code> 还是 <code>curl</code> 或者是<code>socket</code>连接都可以无感体验，debian 11推荐1c1g的配置即可，肯定会比你直接在macOS宿主机上登录atrust会耗电一些。如果有条件的话，也可以把debian 11部署到其他的电脑上。</p><h2 id="其他你可能需要了解的"><a href="#其他你可能需要了解的" class="headerlink" title="其他你可能需要了解的"></a>其他你可能需要了解的</h2><h3 id="macOS宿主机更换网络以后如何处理？"><a href="#macOS宿主机更换网络以后如何处理？" class="headerlink" title="macOS宿主机更换网络以后如何处理？"></a>macOS宿主机更换网络以后如何处理？</h3><p>我尝试过单独重启网络</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl restart networking</span><br></pre></td></tr></table></figure><p>但实际没有效果，具体原因我暂时也没时间去搞了，我目前是通过重启debian 11来解决的</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl reboot -i</span><br></pre></td></tr></table></figure><h3 id="为什么不使用docker而选择虚拟机？"><a href="#为什么不使用docker而选择虚拟机？" class="headerlink" title="为什么不使用docker而选择虚拟机？"></a>为什么不使用docker而选择虚拟机？</h3><p>macOS上的docker本质上也是一个运行在虚拟机中的，并且docker在<a href="https://dockerdocs.cn/docker-for-mac/networking/">官网</a>已经明确说明macOS上的docker服务无法实现从宿主机到docker容器的通信<br><img src="https://cdn.baofeidyz.com/img/202303290932363.png"></p><blockquote><p>GitHub上也有相关的解决方案（docker-connector），如果你感兴趣的话，你可以动手尝试一下。docker安装debian 11以后，安装一个vnc server即可解决atrust客户端登录的问题，其他操作应该和我这篇博客的方法相差不大</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>Jetcache踩坑合集</title>
      <link>https://baofeidyz.com/2023/69cd795b27a2/</link>
      <description>
        <![CDATA[<h1 id="Jetcache踩坑合集"><a href="#Jetcache踩坑合集" class="headerlink" title="Jetcache踩坑合集"></a>Jetcache踩坑合集</h1><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>本文主要是记录个人在使用jetcache时遇到的一些问题以及相应的解决方案，次之是将这些问题和解决方案发布到互联网中希望能帮助到一些人，如果觉得文章写得还不错，可以点赞收藏以鼓励我继续更新博客，这将对我非常重要。</p>
<p>流水账子标题中重复带上jetcache关键字主要是为了提高搜索的准确性，本身文章上下文已足够表达含义，但是为了优化搜索情况特意加上的。</p>
<h2 id="踩坑流水账"><a href="#踩坑流水账" class="headerlink" title="踩坑流水账"></a>踩坑流水账</h2><h3 id="jetcache默认返回值为null时候，不会缓存"><a href="#jetcache默认返回值为null时候，不会缓存" class="headerlink" title="jetcache默认返回值为null时候，不会缓存"></a>jetcache默认返回值为null时候，不会缓存</h3><p>这个是jetcache的默认策略，我之所以遇到这个问题是我自己写的测试代码时直接返回了null，我误认为是我spring aop配置有问题，导致缓存没有生效，实则是jetcache默认策略。</p>
<p>在<code>com.alicp.jetcache.anno.Cached</code>注解中有一个属性<code>cacheNullValue</code>，默认值为false，表示不缓存空值。</p>
<h3 id="jetcache不支持模糊清空缓存"><a href="#jetcache不支持模糊清空缓存" class="headerlink" title="jetcache不支持模糊清空缓存"></a>jetcache不支持模糊清空缓存</h3><p>这点其实在GitHub issue清单中有好几个<a href="https://github.com/alibaba/jetcache/issues?q=is:issue+is:open+%E6%A8%A1%E7%B3%8A">issue</a> 都是与这个问题相关的，如果你也是从Spring Cache转到jetcache，由于缺少模糊清空缓存，会导致整个系统原有的实现都必须调整。</p>
<p>这个特性主要影响到缓存的数据是一个列表或者是一整颗树的情况，当列表或树中部分值更新以后，这个列表的缓存或树的缓存没有办法更新。</p>
<h3 id="jetcache多节点缓存清空需要先有-Cached注册"><a href="#jetcache多节点缓存清空需要先有-Cached注册" class="headerlink" title="jetcache多节点缓存清空需要先有@Cached注册"></a>jetcache多节点缓存清空需要先有@Cached注册</h3>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/jetcache/">jetcache</category>
      <pubDate>Sun, 28 May 2023 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="Jetcache踩坑合集"><a href="#Jetcache踩坑合集" class="headerlink" title="Jetcache踩坑合集"></a>Jetcache踩坑合集</h1><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>本文主要是记录个人在使用jetcache时遇到的一些问题以及相应的解决方案，次之是将这些问题和解决方案发布到互联网中希望能帮助到一些人，如果觉得文章写得还不错，可以点赞收藏以鼓励我继续更新博客，这将对我非常重要。</p><p>流水账子标题中重复带上jetcache关键字主要是为了提高搜索的准确性，本身文章上下文已足够表达含义，但是为了优化搜索情况特意加上的。</p><h2 id="踩坑流水账"><a href="#踩坑流水账" class="headerlink" title="踩坑流水账"></a>踩坑流水账</h2><h3 id="jetcache默认返回值为null时候，不会缓存"><a href="#jetcache默认返回值为null时候，不会缓存" class="headerlink" title="jetcache默认返回值为null时候，不会缓存"></a>jetcache默认返回值为null时候，不会缓存</h3><p>这个是jetcache的默认策略，我之所以遇到这个问题是我自己写的测试代码时直接返回了null，我误认为是我spring aop配置有问题，导致缓存没有生效，实则是jetcache默认策略。</p><p>在<code>com.alicp.jetcache.anno.Cached</code>注解中有一个属性<code>cacheNullValue</code>，默认值为false，表示不缓存空值。</p><h3 id="jetcache不支持模糊清空缓存"><a href="#jetcache不支持模糊清空缓存" class="headerlink" title="jetcache不支持模糊清空缓存"></a>jetcache不支持模糊清空缓存</h3><p>这点其实在GitHub issue清单中有好几个<a href="https://github.com/alibaba/jetcache/issues?q=is:issue+is:open+%E6%A8%A1%E7%B3%8A">issue</a> 都是与这个问题相关的，如果你也是从Spring Cache转到jetcache，由于缺少模糊清空缓存，会导致整个系统原有的实现都必须调整。</p><p>这个特性主要影响到缓存的数据是一个列表或者是一整颗树的情况，当列表或树中部分值更新以后，这个列表的缓存或树的缓存没有办法更新。</p><h3 id="jetcache多节点缓存清空需要先有-Cached注册"><a href="#jetcache多节点缓存清空需要先有-Cached注册" class="headerlink" title="jetcache多节点缓存清空需要先有@Cached注册"></a>jetcache多节点缓存清空需要先有@Cached注册</h3><span id="more"></span><p>这个是jetcache比较新的特性了，利用了<code>CacheMonitor</code>接口，并且在这个的基础上抽象了一个<code>com.alicp.jetcache.support.BroadcastManager</code>支持了更多的中间件，并且对于循环通知的问题有一个很好的解决方案。</p><p>但是jetcache只考虑到了同一份代码多节点部署的情况，我的实际应用场景比这个更复杂一些。</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">private</span> <span class="keyword">void</span> <span class="title function_">processCacheMessage</span><span class="params">(CacheMessage cacheMessage)</span> &#123;  </span><br><span class="line">    <span class="keyword">if</span> (sourceId.equals(cacheMessage.getSourceId())) &#123;  </span><br><span class="line">        <span class="keyword">return</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="type">Cache</span> <span class="variable">cache</span> <span class="operator">=</span> cacheManager.getCache(cacheMessage.getArea(), cacheMessage.getCacheName());  </span><br><span class="line">    <span class="keyword">if</span> (cache == <span class="literal">null</span>) &#123;  </span><br><span class="line">        logger.warn(<span class="string">&quot;Cache instance not exists: &#123;&#125;,&#123;&#125;&quot;</span>, cacheMessage.getArea(), cacheMessage.getCacheName());  </span><br><span class="line">        <span class="keyword">return</span>;    &#125;  </span><br><span class="line">    <span class="type">Cache</span> <span class="variable">absCache</span> <span class="operator">=</span> CacheUtil.getAbstractCache(cache);  </span><br><span class="line">    <span class="keyword">if</span> (!(absCache <span class="keyword">instanceof</span> MultiLevelCache)) &#123;  </span><br><span class="line">        logger.warn(<span class="string">&quot;Cache instance is not MultiLevelCache: &#123;&#125;,&#123;&#125;&quot;</span>, cacheMessage.getArea(), cacheMessage.getCacheName());  </span><br><span class="line">        <span class="keyword">return</span>;    &#125;  </span><br><span class="line">    Cache[] caches = ((MultiLevelCache) absCache).caches();  </span><br><span class="line">    Set&lt;Object&gt; keys = Stream.of(cacheMessage.getKeys()).collect(Collectors.toSet());  </span><br><span class="line">    <span class="keyword">for</span> (Cache c : caches) &#123;  </span><br><span class="line">        <span class="type">Cache</span> <span class="variable">localCache</span> <span class="operator">=</span> CacheUtil.getAbstractCache(c);  </span><br><span class="line">        <span class="keyword">if</span> (localCache <span class="keyword">instanceof</span> AbstractEmbeddedCache) &#123;  </span><br><span class="line">            ((AbstractEmbeddedCache) localCache).__removeAll(keys);  </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></pre></td></tr></table></figure><p>在这个方法中，是直接通过<code>cacheManager</code>实例去获取的，</p><p>当然这个问题也很好解决，你可以把这个模块的代码抽取出来，新建maven的module即可解决。</p><p>又或是直接增加<code>@Cached</code>注解声明即可。</p><h3 id="jetcache的key必须是一个EL表达式的key，不能是固定字符串"><a href="#jetcache的key必须是一个EL表达式的key，不能是固定字符串" class="headerlink" title="jetcache的key必须是一个EL表达式的key，不能是固定字符串"></a>jetcache的key必须是一个EL表达式的key，不能是固定字符串</h3><p>不太确定是不是jetcache特意为之，但是在使用过程中稍微注意一下即可</p><h3 id="jetcache在遇到参数是集合的时候，key不可以是EL表达式"><a href="#jetcache在遇到参数是集合的时候，key不可以是EL表达式" class="headerlink" title="jetcache在遇到参数是集合的时候，key不可以是EL表达式"></a>jetcache在遇到参数是集合的时候，key不可以是EL表达式</h3><p>在这个<a href="https://github.com/alibaba/jetcache/blob/cc5e9407202855dd2391d6f6dd0e319b3a9a01ad/jetcache-anno/src/main/java/com/alicp/jetcache/anno/method/CacheHandler.java#L183-L229">方法</a> 中，你可以看到当<code>@CacheInvalidate</code>中的<code>multi</code>属性声明为true时，可以看到调用了一个<code>toIterable</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Iterable <span class="title function_">toIterable</span><span class="params">(Object obj)</span> &#123;  </span><br><span class="line">    <span class="keyword">if</span> (obj.getClass().isArray()) &#123;  </span><br><span class="line">        <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> Object[]) &#123;  </span><br><span class="line">            <span class="keyword">return</span> Arrays.asList((Object[]) obj);  </span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;  </span><br><span class="line">            <span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();  </span><br><span class="line">            <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> Array.getLength(obj);  </span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; len; i++) &#123;  </span><br><span class="line">                list.add(Array.get(obj, i));  </span><br><span class="line">            &#125;  </span><br><span class="line">            <span class="keyword">return</span> list;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> Iterable) &#123;  </span><br><span class="line">        <span class="keyword">return</span> (Iterable) obj;  </span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>只能说jetcache支持了EL表达式，但是又没有完全支持。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>土耳其Apple Music注册使用全攻略</title>
      <link>https://baofeidyz.com/apple-music/</link>
      <description>
        <![CDATA[<p><a href="https://coverview.baofeidyz.com/"><img src="https://cdn.baofeidyz.com/img/202407052058449.png"></a></p>
<blockquote>
<p>2025年7月29日更新：</p>
<ol>
<li>去掉了家庭组的信息，我目前没有家庭组提供，建议大家自己成组</li>
<li>更新了部分已过期的信息，比如土耳其里拉的价格</li>
<li>删除了已失效的百度手机助手提供的Apple Music安卓版链接</li>
<li>新增了Apple Music安卓旧版本客户端123盘的分享链接</li>
<li>迁移了博客《Apple Music激活时遇iTunes注册弹窗的解决方案》和《新注册土耳其Apple ID订阅Apple Music详细操作步骤，新手一定要看！》</li>
<li>更新了闲鱼链接</li>
</ol>
<p>2024年7月5日更新：</p>
<ol>
<li>原帖在这里：<a href="https://baofeidyz.hashnode.dev/apple-music">https://baofeidyz.hashnode.dev/apple-music</a></li>
<li>调整部分过时的内容，新增了iCloud云盘链接（阿里云盘不让分享apk文件，所以先暂时拿iCloud云盘用一下）</li>
</ol>
<p>2023年10月12日更新：<br>很抱歉，就目前来看苹果已经在逐步收紧了，如果还没上车的朋友请尽快上车了，后期可能注册和订阅都将变的越来越困难，详细操作步骤我已经单独更新了一篇博客，请务必查看！链接：<a href="https://baofeidyz.com/apple-music-qa2/">《新注册土耳其Apple ID订阅Apple Music详细操作步骤，新手一定要看！》</a></p>
<p>2023年7月23日更新：<br>很抱歉，就我今天测试结果来看，至少从今天（也许很早之前）苹果已经改为免费一个月了，博客原标题中的“-免费六个月”部分已修改为“每月最低不到2元”（目前家庭订阅是29.99里拉，约等于8元，家庭订阅最多可以六人，平均算下来人均不到1.4元）。此外，部分平台由于文章无法再编辑，已尽量通过评论区的方式告知，希望您理解。</p>
</blockquote>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/Apple-Music/">Apple Music</category>
      <category domain="https://baofeidyz.com/tags/AppleMusic/">AppleMusic</category>
      <pubDate>Fri, 19 May 2023 03:41:52 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://coverview.baofeidyz.com/"><img src="https://cdn.baofeidyz.com/img/202407052058449.png"></a></p><blockquote><p>2025年7月29日更新：</p><ol><li>去掉了家庭组的信息，我目前没有家庭组提供，建议大家自己成组</li><li>更新了部分已过期的信息，比如土耳其里拉的价格</li><li>删除了已失效的百度手机助手提供的Apple Music安卓版链接</li><li>新增了Apple Music安卓旧版本客户端123盘的分享链接</li><li>迁移了博客《Apple Music激活时遇iTunes注册弹窗的解决方案》和《新注册土耳其Apple ID订阅Apple Music详细操作步骤，新手一定要看！》</li><li>更新了闲鱼链接</li></ol><p>2024年7月5日更新：</p><ol><li>原帖在这里：<a href="https://baofeidyz.hashnode.dev/apple-music">https://baofeidyz.hashnode.dev/apple-music</a></li><li>调整部分过时的内容，新增了iCloud云盘链接（阿里云盘不让分享apk文件，所以先暂时拿iCloud云盘用一下）</li></ol><p>2023年10月12日更新：<br>很抱歉，就目前来看苹果已经在逐步收紧了，如果还没上车的朋友请尽快上车了，后期可能注册和订阅都将变的越来越困难，详细操作步骤我已经单独更新了一篇博客，请务必查看！链接：<a href="https://baofeidyz.com/apple-music-qa2/">《新注册土耳其Apple ID订阅Apple Music详细操作步骤，新手一定要看！》</a></p><p>2023年7月23日更新：<br>很抱歉，就我今天测试结果来看，至少从今天（也许很早之前）苹果已经改为免费一个月了，博客原标题中的“-免费六个月”部分已修改为“每月最低不到2元”（目前家庭订阅是29.99里拉，约等于8元，家庭订阅最多可以六人，平均算下来人均不到1.4元）。此外，部分平台由于文章无法再编辑，已尽量通过评论区的方式告知，希望您理解。</p></blockquote><span id="more"></span><hr><blockquote><p><strong>写在前面的话：</strong> 本文为了照顾更多的人，写了很多的内容，如果你只是想要确切的方法请滑到相应的章节即可。</p></blockquote><h1 id="为什么要使用Apple-Music？"><a href="#为什么要使用Apple-Music？" class="headerlink" title="为什么要使用Apple Music？"></a>为什么要使用Apple Music？</h1><ol><li>界面简洁</li><li>一次订阅可收听曲库中所有的歌曲，不存在二次收费</li><li>交互体验非常棒</li><li>较于Spotify价格更低</li></ol><h1 id="一定要用iPhone才能使用Apple-Music吗？"><a href="#一定要用iPhone才能使用Apple-Music吗？" class="headerlink" title="一定要用iPhone才能使用Apple Music吗？"></a>一定要用iPhone才能使用Apple Music吗？</h1><p>当然不是，Apple Music目前除了苹果全家桶的支持以外，还支持安卓手机、网页、智能电视、游戏主机（PS5和XBOX）、Roku电视棒以及Windows PC</p><p>你可以在这个<a href="https://www.apple.com.cn/apple-music/">Apple Music</a>官网的最下方找到所有设备支持的清单，特别说明的是，即便你的安卓手机没有GMS（Google套件）也可以使用Apple Music，你可以直接请求以下地址安装：</p><blockquote><p>对于只有安卓&#x2F;Android设备的朋友需要注意一点是新版本的客户端移除了礼品卡绑定入口，需要使用低版本的客户端才可以。绑定完了以后再换回新版或者Google play版即可。<br>同时也建议大家在绑定礼品卡的时候最好是一次性搞定一年左右的比较合适。</p></blockquote><ul><li>iCloud云盘：<a href="https://www.icloud.com/iclouddrive/033fLafCwUkfaMMQJSFYRakSQ#Apple_Music">iCloud_Apple_Music</a></li><li>123盘：<a href="https://www.123pan.com/s/AnMWTd-UNzQH">AppleMusic-3.6.0-2021-07-27.apk</a></li></ul><p><img src="https://s2.loli.net/2023/03/19/O4nhTIPFWBpq3Js.png"></p><h1 id="为什么要使用土耳其区的Apple-music？"><a href="#为什么要使用土耳其区的Apple-music？" class="headerlink" title="为什么要使用土耳其区的Apple music？"></a>为什么要使用土耳其区的Apple music？</h1><p>这一章节的内容主要是向你阐述为什么要选择土耳其区的Apple Music而不是使用中国大陆区</p><h2 id="No-1-费用低廉"><a href="#No-1-费用低廉" class="headerlink" title="No.1 费用低廉"></a>No.1 费用低廉</h2><p>这是我推荐使用土耳其区最主要的原因。</p><p>土耳其Apple Music费用低。目前1新土耳其里拉等于0.36元，土耳其Apple Music个人订阅是19.99新土耳其里拉，约等于7.2元对于中国大陆的Apple Music 10元&#x2F;月就已经便宜了。 如果你选择开通家庭订阅，价格是29.99新土耳其里拉，并且能找到五个人一起拼，那么均摊下来是1.8元！一个月不到2块钱！</p><p>目前我的Apple Music家庭组还有三个位置，有需要的也可以直接联系我，评论留言或者<a href="https://m.tb.cn/h.g6zTivU?tk=1nOz302w2uJ">闲鱼</a>私聊即可。</p><h2 id="No-2-土耳其音乐曲库远大于中国大陆区"><a href="#No-2-土耳其音乐曲库远大于中国大陆区" class="headerlink" title="No.2 土耳其音乐曲库远大于中国大陆区"></a>No.2 土耳其音乐曲库远大于中国大陆区</h2><ol><li>Apple Music在不同地区的曲库是不一样的，中国大陆的曲库应该是最小的（之一），美区应该是最大的，土耳其属于二者之间</li><li>Apple Music对音乐也做了分级，音乐中带儿童色情的“脏标”在中国大陆的曲库中是没有的，土耳其则有</li><li>2022年周杰伦发布的新专辑《最伟大的作品》，其他地区均是同步更新，而中国大陆则是延期接近三个月才更新</li><li>封面图有所差异 第一张图中国大陆区域在展示Beyonce的《RENAISSANCE》，第二张图是土耳其区</li><li>部分歌手的歌曲版权存在差异化，比如华晨宇，TFBOYS的歌曲在中国大陆区的Apple Music几乎没有 左侧是中国大陆区域搜索华晨宇，右侧是在土耳其搜索华晨宇，结果差距很大</li></ol><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/202303191835744.jpeg"><br><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/202303191836915.PNG"></p><ol start="6"><li>部分不能讲的原因导致无法上架的，比如歌手李志，还有一些台湾的组合以及马来西亚的歌手等等，但在土耳其区则无此问题</li></ol><h2 id="No-3-土耳其区功能更多"><a href="#No-3-土耳其区功能更多" class="headerlink" title="No.3 土耳其区功能更多"></a>No.3 土耳其区功能更多</h2><ol><li><p>Apple Music土耳其区可以解锁真正的电台功能</p></li><li><p>中国大陆订阅的Apple Music不支持AppleTV使用。当然目前有部分网友通过卡网络bug的形式在AppleTV上使用Apple Music，但如果可以直接使用岂不是更好？</p></li><li><p><del>土耳其区可以直接观看Apple Music live，比如2022年歌手Billie Eilish在英国伦敦举办的live演唱会录屏就可以直接在Apple Music观看。就算你没有AppleTV，你还可以通过AirPlay（隔空投放）到电视上观看</del>：发稿前发现中国大陆区也可以看</p></li></ol><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/202303191939928.png"></p><p>当然如果你没有AppleTV，手机上也是可以观看的</p><h2 id="No-4-土耳其免费赠送时间长"><a href="#No-4-土耳其免费赠送时间长" class="headerlink" title="No.4 土耳其免费赠送时间长"></a>No.4 土耳其免费赠送时间长</h2><p><del>在中国大陆区，官方的赠送时间是一个月，而在土耳其区是整整六个月！</del>：这个已经没有了，目前都是免费一个月，我之前确实白嫖了半年😂</p><h1 id="如何注册与开通土耳其区Apple-Music？"><a href="#如何注册与开通土耳其区Apple-Music？" class="headerlink" title="如何注册与开通土耳其区Apple Music？"></a>如何注册与开通土耳其区Apple Music？</h1><ol><li><p>访问苹果用于注册appleid的网址：<a href="https://appleid.apple.com/account">https://appleid.apple.com/account</a></p><blockquote><p>如果你存在无法打开此网站的问题，可以考虑重启自己的设备（手机）或者是由Wi-Fi切换到数据流量（蜂窝移动网络）试试。</p></blockquote></li><li><p>访问以后，我们根据网页提示的信息进行填写即可</p><p> <img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/202303192003243.png"></p><p> 这里有几点信息你可能需要知道的：</p><ol><li><p>姓氏和名字不需要填写真实的，你可以填写任意内容</p></li><li><p>国家或地区请记得选择土耳其，这里的顺序是按照拼音首字母来的，大概是在这个位置前后</p><p> <img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/202303192006698.png"></p></li></ol></li><li><p><code>name@example.com</code>这里就是你想要创建的Apple ID账号，你需要输入一个你已经存在的邮箱账号，注意不能是icloud.com的邮箱账号 如果你没有邮箱账号的话，你可能需要单独为此注册一个，推荐使用腾讯邮箱或者网易163邮箱（或者outlook、gmail邮箱）</p><blockquote><p><a href="mailto:&#37;&#x45;&#57;&#x25;&#x39;&#67;&#x25;&#x38;&#x30;&#x25;&#x45;&#x38;&#37;&#x41;&#x36;&#x25;&#56;&#49;&#x25;&#69;&#54;&#x25;&#66;&#51;&#37;&#x41;&#x38;&#37;&#x45;&#x36;&#37;&#x38;&#x34;&#x25;&#x38;&#70;&#37;&#x45;&#x37;&#x25;&#57;&#x41;&#37;&#x38;&#52;&#x25;&#x45;&#x36;&#37;&#x39;&#56;&#x25;&#65;&#x46;&#37;&#x45;&#70;&#x25;&#66;&#67;&#x25;&#x38;&#67;&#37;&#x45;&#52;&#x25;&#x42;&#x44;&#37;&#65;&#48;&#x25;&#x45;&#x35;&#x25;&#65;&#54;&#37;&#x38;&#x32;&#x25;&#69;&#x36;&#37;&#57;&#69;&#37;&#x39;&#67;&#x25;&#69;&#x36;&#x25;&#x39;&#67;&#37;&#x38;&#57;&#x51;&#x51;&#37;&#69;&#53;&#37;&#56;&#70;&#x25;&#x42;&#55;&#x25;&#69;&#x46;&#37;&#66;&#x43;&#x25;&#56;&#x43;&#37;&#x45;&#57;&#37;&#x38;&#x32;&#37;&#65;&#x33;&#37;&#69;&#52;&#x25;&#x42;&#57;&#x25;&#x38;&#x38;&#37;&#x45;&#57;&#x25;&#x42;&#x42;&#37;&#57;&#x38;&#37;&#69;&#x38;&#37;&#65;&#69;&#x25;&#65;&#52;&#37;&#x45;&#x34;&#x25;&#66;&#x44;&#37;&#x41;&#x30;&#37;&#x45;&#53;&#37;&#66;&#48;&#37;&#66;&#x31;&#x25;&#x45;&#54;&#37;&#57;&#56;&#37;&#x41;&#70;&#x25;&#x45;&#x36;&#37;&#x39;&#x43;&#x25;&#x38;&#x39;&#x51;&#x51;&#37;&#69;&#57;&#37;&#56;&#50;&#x25;&#x41;&#69;&#x25;&#x45;&#55;&#37;&#x41;&#69;&#37;&#x42;&#x31;&#37;&#69;&#55;&#37;&#57;&#65;&#x25;&#x38;&#52;&#37;&#69;&#x46;&#37;&#x42;&#x43;&#37;&#x38;&#67;&#37;&#x45;&#x38;&#37;&#66;&#x34;&#x25;&#x41;&#54;&#x25;&#69;&#53;&#37;&#x38;&#70;&#x25;&#x42;&#x37;&#x25;&#69;&#x36;&#37;&#x39;&#56;&#37;&#x41;&#x46;&#x25;&#69;&#x34;&#x25;&#66;&#68;&#x25;&#x41;&#48;&#x25;&#x45;&#55;&#x25;&#x39;&#x41;&#37;&#x38;&#52;&#x51;&#x51;&#x25;&#69;&#x35;&#x25;&#56;&#x46;&#x25;&#x42;&#55;&#x25;&#x45;&#x37;&#37;&#x41;&#48;&#x25;&#x38;&#x31;&#64;&#x71;&#x71;&#46;&#99;&#x6f;&#x6d;">需要注意的是，你如果有QQ号，那么默认你就是有QQ邮箱的，账号是你的QQ号码@qq.com</a>。 <a href="mailto:&#37;&#x45;&#x34;&#x25;&#66;&#x45;&#x25;&#56;&#66;&#37;&#69;&#53;&#37;&#x41;&#54;&#37;&#56;&#x32;&#37;&#x45;&#x34;&#37;&#66;&#68;&#37;&#65;&#x30;&#x25;&#x45;&#55;&#x25;&#x39;&#x41;&#x25;&#56;&#52;&#x51;&#x51;&#x25;&#x45;&#53;&#37;&#56;&#70;&#37;&#x42;&#x37;&#x25;&#69;&#55;&#37;&#x41;&#48;&#37;&#x38;&#49;&#37;&#69;&#54;&#x25;&#57;&#56;&#x25;&#65;&#70;&#x31;&#50;&#x33;&#x34;&#53;&#x36;&#x37;&#x38;&#x39;&#37;&#x45;&#70;&#x25;&#66;&#x43;&#x25;&#56;&#67;&#37;&#69;&#57;&#37;&#x38;&#x32;&#37;&#x41;&#51;&#x25;&#x45;&#x34;&#x25;&#x42;&#x39;&#37;&#56;&#x38;&#37;&#69;&#x34;&#37;&#x42;&#x44;&#37;&#x41;&#x30;&#x25;&#x45;&#55;&#37;&#57;&#x41;&#37;&#x38;&#x34;&#x51;&#81;&#37;&#69;&#x39;&#37;&#56;&#x32;&#x25;&#x41;&#x45;&#x25;&#x45;&#x37;&#x25;&#65;&#x45;&#37;&#x42;&#49;&#x25;&#69;&#53;&#37;&#66;&#48;&#x25;&#66;&#49;&#x25;&#69;&#x36;&#x25;&#x39;&#56;&#x25;&#65;&#x46;&#49;&#x32;&#x33;&#x34;&#53;&#54;&#55;&#x38;&#x39;&#64;&#113;&#x71;&#46;&#99;&#111;&#109;">例如你的QQ号码是<code>123456789</code>，那么你的QQ邮箱就是<code>123456789@qq.com</code></a></p></blockquote><ul><li><p>手机注册腾讯QQ邮箱请访问：<a href="https://ssl.zc.qq.com/phone/index.html?from=pt">QQ注册</a></p></li><li><p>电脑注册腾讯QQ邮箱请访问：<a href="https://ssl.zc.qq.com/v3/index-chs.html?from=pt">QQ注册</a></p></li></ul></li><li><p>最后输入密码、验证手机号即可注册成功</p></li></ol><blockquote><p>我最近一段时间也在注册新的Apple ID，我发现苹果的限制是越来越多了，经常遇到无法创建账号的问题，这个就是苹果自己的风控。如果你也遇到了这个问题（实际上很多人都遇到过，闲鱼上经常有人来问）。</p><ol><li>切换网络：失败的时候如果用的蜂窝数据就换成Wi-Fi再提交，如果失败的时候是Wi-Fi那就换成蜂窝数据。特别注意一点就是不要使用公共Wi-Fi，比如咖啡店、奶茶店、车站、酒店这种人流性特别大的就不要尝试了。如果确实找不到合适的Wi-Fi可以试试换个地方，然后开关飞行模式。因为基站切换的时候有概率会刷新你的公网IP。</li><li>浏览器使用隐私模式：Windows设备快捷键是ctrl+shift+n，macOS设备快捷键是command+shift+n。移动设备则需要自己在浏览器设置中找一下或者装一个duckduckgo浏览器有“一键焚烧”功能。主要是为了避免苹果利用cookie之类的缓存来判断你的设备。</li><li>换设备：如果你用的设备已经注册过多个Apple ID，那么你提交失败的概率就非常大。我现在就是这种状态，第一种和第二种方法对我来说已经没有太多用处了。</li><li>换邮箱地址：比较有名的邮箱成功率会高一些，比如qq.com、163.com、gmail.com、outlook.com等等。如果你用的自己的域名邮箱，提交失败也是常事。</li></ol></blockquote><h2 id="如何订阅土耳其区Apple-Music？"><a href="#如何订阅土耳其区Apple-Music？" class="headerlink" title="如何订阅土耳其区Apple Music？"></a>如何订阅土耳其区Apple Music？</h2><p>首先你要知道你是不能直接使用微信、支付宝、银行卡（银联）等渠道直接订阅土耳其区Apple Music的，目前土耳其区也不支持类似于美区可用的Paypal方案。</p><p>常见的方案有两种：</p><ol><li><p>你搞到一张土耳其的银行卡。相信大多数人都做不到这点</p></li><li><p>通过购买土耳其区App Store 礼品卡。这也是目前最为主流的解决方案</p><p> 主要有两种方式来购买土耳其App Store礼品卡：</p></li></ol><h3 id="方式1-通过oyunfor直接购买App-Store礼品卡"><a href="#方式1-通过oyunfor直接购买App-Store礼品卡" class="headerlink" title="方式1:通过oyunfor直接购买App Store礼品卡"></a>方式1:通过oyunfor直接购买App Store礼品卡</h3><ol><li><p>在 <a href="https://www.oyunfor.com/">https://www.oyunfor.com/</a> 中注册账号</p></li><li><p>选择你需要的额度 <a href="https://www.oyunfor.com/apple-store/apple-store-itunes-gift-card">https://www.oyunfor.com/apple-store/apple-store-itunes-gift-card</a>，先加入购物车</p></li><li><p>确定好金额后提交订单</p></li><li><p>可通过国内的visa或master信用卡付款，支付平台会收取3%左右的手续费，然后银行方面还可能收取货币转换费（一般使用单币卡可以免货币转换费，具体请咨询银行客服）</p></li><li><p>购买成功以后无法直接拿到礼品卡，你需要先绑定自己的手机号，+86目前可用</p></li></ol><h3 id="可能出现的问题以及解决方案："><a href="#可能出现的问题以及解决方案：" class="headerlink" title="可能出现的问题以及解决方案："></a>可能出现的问题以及解决方案：</h3><ol><li><p>官网默认是土耳其语，你需要切换到English或者自己使用网页翻译服务</p></li><li><p>目前没有被墙，但直连速度很慢很慢，所以还是需要有比较稳定的上网方案，且节点不能支持IPv6，否则在付款阶段会被拒绝</p></li><li><p>手机号在网页上绑定以后还是无效，你需要在官网上咨询客服，不支持中文，需要使用英文交流</p></li><li><p>客服大概率会让你使用WhatsApp来验证手机号的有效性，对于大部分用户来说可能注册WhatsApp又是一个很麻烦的事儿</p></li></ol><h3 id="方式2：通过我卖的土耳其App-Store-礼品卡直接激活"><a href="#方式2：通过我卖的土耳其App-Store-礼品卡直接激活" class="headerlink" title="方式2：通过我卖的土耳其App Store 礼品卡直接激活"></a>方式2：通过我卖的土耳其App Store 礼品卡直接激活</h3><p>目前我已经在闲鱼上架了多种面额的 App Store 礼品卡，均是通过oyunfor购买所得。</p><p>拍下付款以后，会<strong>自动发货，自动答复的消息中有相应的激活方法</strong></p><p>其他问题可以评论区留言，或者闲鱼私聊我</p><p><a href="https://m.tb.cn/h.hNFGTvG?tk=YpNh4ft34E3">点我跳转到闲鱼</a></p><blockquote><p>如果闲鱼链接失效，可以直接搜索<strong>用户</strong>“土耳其礼品卡”，会员名x开头，7结尾，粉丝数量大于150的就是我了</p></blockquote><h1 id="如何迁移Apple-Music中国大陆区的资料库？"><a href="#如何迁移Apple-Music中国大陆区的资料库？" class="headerlink" title="如何迁移Apple Music中国大陆区的资料库？"></a>如何迁移Apple Music中国大陆区的资料库？</h1><p>首先是不推荐直接将自己目前的Apple ID改为土耳其区，那么这种情况下已经有了中国大陆区的Apple Music要如何迁移呢？</p><p>非常简单，Apple Music有一个播放列表共享功能，虽然中国大陆区的共享功能收到了限制（不能搜索其他人的），但是可以通过链接直接分享。</p><p>所以我们只要把资料库中所有的歌曲都新建成一个播放列表并生成共享链接，然后土耳其区的账号访问这个播放列表，添加并勾选到资料库即可完成。</p><h2 id="如何将中国大陆区的播放列表批量迁移？"><a href="#如何将中国大陆区的播放列表批量迁移？" class="headerlink" title="如何将中国大陆区的播放列表批量迁移？"></a>如何将中国大陆区的播放列表批量迁移？</h2><p>很抱歉，这个暂时没有特别好的方法，建议是利用Apple Music列表共享功能将列表一个一个的挪动</p><h3 id="如何将中国大陆区自己上传的音乐文件批量迁移？"><a href="#如何将中国大陆区自己上传的音乐文件批量迁移？" class="headerlink" title="如何将中国大陆区自己上传的音乐文件批量迁移？"></a>如何将中国大陆区自己上传的音乐文件批量迁移？</h3><p>对于自己主动上传的这部分音乐文件，Apple Music是可以直接下载的，只需要用一台电脑先下载源文件，然后切换账号重新上传即可完成</p><h1 id="写到最后"><a href="#写到最后" class="headerlink" title="写到最后"></a>写到最后</h1><p>其实，当你有一个外区Apple ID以及App Store礼品卡以后，你可以订阅很多其他的服务或者购买其他的软件，而且土耳其区的价格都比较低。比如Affinity Photo2</p><p>此外，目前国行的App Store已经全面实行了备案制，很多app其实在国区都没了，有个土耳其区的Apple ID还是不错的。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>解决macOS IntelliJ IDEA 卡顿问题</title>
      <link>https://baofeidyz.com/solve-macos-intellij-idea-high-cpu-usage/</link>
      <description>
        <![CDATA[<h1 id="解决macOS-IntelliJ-IDEA-卡顿问题"><a href="#解决macOS-IntelliJ-IDEA-卡顿问题" class="headerlink" title="解决macOS IntelliJ IDEA 卡顿问题"></a>解决macOS]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/macOS/">macOS</category>
      <category domain="https://baofeidyz.com/tags/IDEA/">IDEA</category>
      <pubDate>Mon, 10 Apr 2023 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="解决macOS-IntelliJ-IDEA-卡顿问题"><a href="#解决macOS-IntelliJ-IDEA-卡顿问题" class="headerlink" title="解决macOS IntelliJ IDEA 卡顿问题"></a>解决macOS IntelliJ IDEA 卡顿问题</h1><blockquote><p>写在前面的话1：我在撰写这篇博客时候，所用的IntelliJ IDEA版本是IntelliJ IDEA 2022.3.3 (Ultimate Edition)，你需要知道可能对于不同的IntelliJ IDEA版本会有一定的差异<br/><br>写在前面的话2：如果我这篇博客可以帮助到你，请给我一个免费的赞和收藏，谢谢</p></blockquote><h1 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h1><p>我遇到的卡顿问题主要体现在编辑代码时，输入中文时，cpu使用率飙升到100%，并且中文已经在键盘中打完了，但是展示到IntelliJ IDEA页面上还需要5秒以上，已经卡到没办法做研发工作了。</p><h2 id="一些相关的因素"><a href="#一些相关的因素" class="headerlink" title="一些相关的因素"></a>一些相关的因素</h2><p>在特别卡的时候，我有更换过一次项目。我在做这个项目之前，虽然也卡，但是不会类似于这个项目这么卡。<br>但同时我在这期间有升级过macOS的系统版本和IntelliJ IDEA的版本，变量因素很多，加大了问题排查定位的难度。</p><h1 id="排查思路"><a href="#排查思路" class="headerlink" title="排查思路"></a>排查思路</h1><blockquote><p>我以前很喜欢写流水账，但是这次来来回回花了我好几个月才解决，所以我已经回忆不起来我到底是怎么一步一步排查的了，我尝试写一些我还能想起来的作为排查思路</p></blockquote><h2 id="检查内存、硬盘、cpu使用率"><a href="#检查内存、硬盘、cpu使用率" class="headerlink" title="检查内存、硬盘、cpu使用率"></a>检查内存、硬盘、cpu使用率</h2><p>这是我最开始的考虑，macOS上有一个系统应用，叫“活动监视器“，这里面可以比较清晰的看到IntelliJ IDEA的cpu、内存、硬盘、网络的使用情况，当然你也可以在终端中使用<code>top</code>或者<code>htop</code>来确认。<br>通过我的观察，我发现我输入中文的时候，我的八个很容易就达到了100%，CPU使用率轻轻松松超过400%</p><p>如果你使用IntelliJ IDEA开发程序，那么Java应用的卡顿问题如何处理，你应该是比较熟悉的，对吧？但是如果没有源码，我们直接去查IntelliJ IDEA的线程信息其实帮助也不是很大，我稍微花了一点时间在网络上翻找了一些帖子。</p><p>IntelliJ IDEA其实是有自己的分析工具，其位置在于【顶栏-&gt;Help-&gt;Diagnostic Tools-&gt;Activity Monitor】</p><blockquote><p>说个题外话，不建议安装IntelliJ IDEA的中文语言包，因为互联网上大量文档主要还是用英文来写的，我在解决这个IntelliJ IDEA卡顿问题的时候也在IntelliJ IDEA自己的论坛看了很多帖子，如果你长期使用中文语言包，对于一些词汇太陌生会导致自己处理问题比较麻烦。</p></blockquote><p><img src="https://cdn.baofeidyz.com/img/202303311658360.png"></p><p>从这个工具里面咱们就可以看到具体什么进程导致CPU的高占用，对于进程名不熟悉的，借助于Google也可以很快了解具体是做什么的。</p><p>不同的使用场景下，会存在不同的原因，并且因为IntelliJ IDEA还支持了插件，所以导致卡顿的原因更是不计其数，但只要你懂得如何去排查问题，这个事儿就变得没有那么困难了。</p><h2 id="一个一个的禁用插件来排查"><a href="#一个一个的禁用插件来排查" class="headerlink" title="一个一个的禁用插件来排查"></a>一个一个的禁用插件来排查</h2><p>这个方法是很多人都有提到的，并且在实际应用上是真的有效果！如果你安装的插件比较多，那么请一个一个的disable掉，利用排查法找到具体是哪个插件所导致的，插件越多理论上时间会比较长，你也可以选择以五个为单位快速定位一个大致的范围。</p><h1 id="常见的一些解决方案"><a href="#常见的一些解决方案" class="headerlink" title="常见的一些解决方案"></a>常见的一些解决方案</h1><h2 id="修改IntelliJ-IDEA的内存"><a href="#修改IntelliJ-IDEA的内存" class="headerlink" title="修改IntelliJ IDEA的内存"></a>修改IntelliJ IDEA的内存</h2><p>放到第一位肯定是相当重要的，但IntelliJ IDEA在扫描、编译、处理各类问题的时候，确实是需要很大内存的。我个人目前是给到了6G。</p><p>网上有各种各样教你修改配置文件的方法，但是IntelliJ IDEA自己就提供了这样的功能，你直接需要点击【顶栏-&gt;Help-&gt;Edit Custom VM Options】就可以在IntelliJ IDEA编辑框打开一个以<code>.vmoptions</code>结尾的文件。如果之前已经有<code>-Xmx</code>属性那么就改一下，如果没有就新起一行加上即可，如果你想和我一样配置成6G，那么修改属性值应该是<code>-Xmx6144m</code>即可，这表示最小堆内存位6G</p><h2 id="使用更高版本的jdk以及IntelliJ-IDEA的新界面"><a href="#使用更高版本的jdk以及IntelliJ-IDEA的新界面" class="headerlink" title="使用更高版本的jdk以及IntelliJ IDEA的新界面"></a>使用更高版本的jdk以及IntelliJ IDEA的新界面</h2><p>java 原来使用的是OpenGL，但是macOS从Mojave开始启用了新的Metal框架，jetbrains对此也是早早布局，我这里放两个链接供你了解更多的内容：</p><ol><li><a href="https://blog.jetbrains.com/platform/2020/11/metal-for-intellij-platform/">Metal for IntelliJ Platform | The JetBrains Platform Blog</a></li><li><a href="https://youtrack.jetbrains.com/issue/JBR-745/Improve-java2d-rendering-performance-on-macOS-by-using-Metal-framework">https://youtrack.jetbrains.com/issue/JBR-745/Improve-java2d-rendering-performance-on-macOS-by-using-Metal-framework</a></li></ol><p>当你的分辨率非常高，或者是显示器非常的多时，渲染的压力就会越大，cpu就会飙升，IntelliJ IDEA也会变卡。截止目前IntelliJ IDEA已经发布了<code>New UI</code>，虽然被标记为<code>Beta</code>，但从我个人使用情况来说，我还没遇到过问题。反而新版本的UI更让我聚焦我的开发工作，用了几天很快就适应了。<br>简单来说，你需要jre 17及以上版本且IntelliJ IDEA版本保持为最新版即可。</p><h2 id="插件导致的卡顿"><a href="#插件导致的卡顿" class="headerlink" title="插件导致的卡顿"></a>插件导致的卡顿</h2><h3 id="git插件"><a href="#git插件" class="headerlink" title="git插件"></a>git插件</h3><p>排查思路很简单，就是我在上一个章节中提到的一个一个的禁用插件来排查，如果你已经确定是IntelliJ IDEA的git插件导致的卡顿，但是你又想像我一样继续使用这个git插件，那么应该怎么解决呢？</p><p>这个问题我是偶然一次浏览到的，<a href="https://www.zhihu.com/question/35203203">macOS 下 Intellij IDEA 变得特别卡该如何解决？ - 知乎</a> 其中第一个高赞回答就是解决方法，其实就是<code>.gitignore</code>文件设置不合理，导致git插件索引了大量无意义的文件，我目前维护的这个项目git下同时存放了多个子系统的代码，文件数量和层级都非常的夸张。</p><p>我在我本地修改了<code>.gitignore</code>文件的内容，去掉了一些我不需要管的文件夹后，git插件导致的IntelliJ IDEA卡顿问题就迎刃而解了。</p><h3 id="其他插件"><a href="#其他插件" class="headerlink" title="其他插件"></a>其他插件</h3><ul><li>如果你确定你不需要，比如alibaba的p3c检查，或者是一些其他的检查扫描类插件，可以disable的都尽量卸载即可。</li><li>对于一些比较小众的插件，你可以尝试通过GitHub直接联系作者以寻求解决方案。</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>mat用小内存解析超大堆快照的可行方法</title>
      <link>https://baofeidyz.com/2023/da2a256aff47/</link>
      <description>
        <![CDATA[<h1 id="mat用小内存解析超大堆快照的可行方法"><a href="#mat用小内存解析超大堆快照的可行方法" class="headerlink" title="mat用小内存解析超大堆快照的可行方法"></a>mat用小内存解析超大堆快照的可行方法</h1><blockquote>
<p>写在前面的话1：服务器上的堆大小已经远超过我开发机的内存大小了，如果直接使用mat客户端来分析很快就会出现OOM的问题，这篇博客一定程度上可以解决这个问题<br/><br>写在前面的话2：大部分同学一直都在使用mat的gui来做分析，但其实mat的gui只是增加来一个html预览功能，我们可以利用mat命令直接生成html，甚至我们还可以挂载成在线服务，以供其他人浏览，非常赞</p>
</blockquote>
<h1 id="命令"><a href="#命令" class="headerlink" title="命令"></a>命令</h1><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></pre></td><td class="code"><pre><span class="line">/Applications/mat.app/Contents/Eclipse/ParseHeapDump.sh ~/Downloads/java_pidxxx.hprof org.eclipse.mat.api:suspects</span><br><span class="line">/Applications/mat.app/Contents/Eclipse/ParseHeapDump.sh ~/Downloads/java_pidxxx.hprof org.eclipse.mat.api:overview</span><br><span class="line">/Applications/mat.app/Contents/Eclipse/ParseHeapDump.sh ~/Downloads/java_pidxxx.hprof org.eclipse.mat.api:top_components</span><br></pre></td></tr></table></figure>]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/memory-analyzer/">memory-analyzer</category>
      <pubDate>Mon, 10 Apr 2023 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="mat用小内存解析超大堆快照的可行方法"><a href="#mat用小内存解析超大堆快照的可行方法" class="headerlink" title="mat用小内存解析超大堆快照的可行方法"></a>mat用小内存解析超大堆快照的可行方法</h1><blockquote><p>写在前面的话1：服务器上的堆大小已经远超过我开发机的内存大小了，如果直接使用mat客户端来分析很快就会出现OOM的问题，这篇博客一定程度上可以解决这个问题<br/><br>写在前面的话2：大部分同学一直都在使用mat的gui来做分析，但其实mat的gui只是增加来一个html预览功能，我们可以利用mat命令直接生成html，甚至我们还可以挂载成在线服务，以供其他人浏览，非常赞</p></blockquote><h1 id="命令"><a href="#命令" class="headerlink" title="命令"></a>命令</h1><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></pre></td><td class="code"><pre><span class="line">/Applications/mat.app/Contents/Eclipse/ParseHeapDump.sh ~/Downloads/java_pidxxx.hprof org.eclipse.mat.api:suspects</span><br><span class="line">/Applications/mat.app/Contents/Eclipse/ParseHeapDump.sh ~/Downloads/java_pidxxx.hprof org.eclipse.mat.api:overview</span><br><span class="line">/Applications/mat.app/Contents/Eclipse/ParseHeapDump.sh ~/Downloads/java_pidxxx.hprof org.eclipse.mat.api:top_components</span><br></pre></td></tr></table></figure><span id="more"></span><p>这个是基于macOS客户端来使用的，其他平台的mat命令位置得对应修改一下，当然如果你觉得分三次解析比较麻烦，你也可以直接一行完成</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/Applications/mat.app/Contents/Eclipse/ParseHeapDump.sh ~/Downloads/java_pidxxx.hprof org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components</span><br></pre></td></tr></table></figure><p>从我实际的使用体验来说，建议是分开解析，其实很多时候你只需要一个<code>suspects</code>视图就足够了。</p><p>你需要注意的是mat在第一次解析的时候会生成很多的index索引类的文件，你得保证你的硬盘空间足够保存这部分的文件。<br>但mat完成以后，其实就是生成了多个html文件，你可以选择借助于nginx，挂载为在线地址，以供其他人在线预览，实际体验还是很不错的。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>macOS软件推荐</title>
      <link>https://baofeidyz.com/2023/ffff4831c7af/</link>
      <description>
        <![CDATA[<blockquote>
<p>与此同时，这还是一份我个人重装macOS系统的备忘录😁</p>
</blockquote>
<h2 id="工具类"><a href="#工具类" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/tags/macOS/">macOS</category>
      <pubDate>Wed, 04 Jan 2023 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<blockquote><p>与此同时，这还是一份我个人重装macOS系统的备忘录😁</p></blockquote><h2 id="工具类"><a href="#工具类" class="headerlink" title="工具类"></a>工具类</h2><table><thead><tr><th>软件名称</th><th>软件用途</th><th>价格</th><th>推荐下载方式</th></tr></thead><tbody><tr><td>homebrew</td><td>macOS包管理工具</td><td>免费</td><td>终端执行：&#x2F;bin&#x2F;bash -c “$(curl -fsSL <a href="https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh">https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh</a>)”</td></tr><tr><td>mos</td><td>用于鼠标切换上下滚动的顺序，以及鼠标平滑滚动</td><td>免费</td><td>brew install mos</td></tr><tr><td>AppCleaner</td><td>卸载app，并且删掉相关联的文件</td><td>免费</td><td>brew install appcleaner</td></tr><tr><td>numbers</td><td>替代Excel</td><td>免费</td><td>app store</td></tr><tr><td>keynote</td><td>替代PowerPoint</td><td>免费</td><td>app store</td></tr><tr><td>pages</td><td>替代word</td><td>免费</td><td>app store</td></tr><tr><td>chrome浏览器</td><td>上网、开发调试</td><td>免费</td><td><a href="https://google.cn/chrome">https://google.cn/chrome</a></td></tr><tr><td>sinology drive client</td><td>群晖文件同步工具</td><td>免费</td><td><a href="https://www.synology.com/en-global/dsm/feature/drive">Synology Drive</a></td></tr><tr><td>snipaste</td><td>截图、贴图工具</td><td>基本版免费（高级版macOS还没上）</td><td>brew install snipaste</td></tr><tr><td>mircsoft remote</td><td>windows操作系统远程桌面工具</td><td>免费</td><td><a href="https://apps.apple.com/us/app/microsoft-remote-desktop/id1295203466?l=zh&mt=12">美区app store</a></td></tr><tr><td>infuse</td><td>一个跨iOS iPadOS macOS tvos，带资源刮削的播放器</td><td>x刀&#x2F;年</td><td><a href="https://apps.apple.com/us/app/infuse-%E6%99%BA%E8%83%BD%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E5%99%A8/id1136220934?l=zh">美区app store</a></td></tr><tr><td>Obisidian</td><td>markdown笔记软件</td><td>免费（同步服务收费）</td><td>brew install –cask obsidian</td></tr><tr><td>marktext</td><td>markdown编辑软件，操作表格的时候会比Obisidian好用很多很多，可以搭配使用</td><td>免费</td><td>brew install mark-text</td></tr><tr><td>ForkLift</td><td>ftp、sftp软件</td><td>收费，买断制</td><td>brew install –cask forklift</td></tr><tr><td>Maccy</td><td>剪贴板记录软件</td><td>免费</td><td>brew install maccy</td></tr><tr><td>MonitorContror</td><td>显示器亮度控制软件</td><td>免费</td><td>brew install –cask monitorcontrol</td></tr><tr><td>Itsycal</td><td>菜单栏中的日期选起控件</td><td>免费</td><td>brew install Itsycal</td></tr><tr><td>omnidisksweeper</td><td>磁盘清理工具</td><td>免费</td><td>brew install –cask omnidisksweeper</td></tr><tr><td>vmware-fusion</td><td>vmware虚拟机免费版</td><td>个人免费（需要自己在官网申请license）</td><td>brew install –cask vmware-fusion</td></tr><tr><td>hiddenbar</td><td>管理macOS菜单栏图标</td><td>免费</td><td>brew install –cask hiddenbar</td></tr></tbody></table><p>一键安装：</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><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">brew install mos &amp;&amp; \</span><br><span class="line">brew install appcleaner &amp;&amp; \</span><br><span class="line">brew install snipaste &amp;&amp; \</span><br><span class="line">brew install --cask forklift &amp;&amp; \</span><br><span class="line">brew install --cask obsidian &amp;&amp; \</span><br><span class="line">brew install mark-text &amp;&amp; \</span><br><span class="line">brew install maccy &amp;&amp; \</span><br><span class="line">brew install Itsycal &amp;&amp; \</span><br><span class="line">brew install --cask omnidisksweeper &amp;&amp; \</span><br><span class="line">brew install --cask hiddenbar</span><br></pre></td></tr></table></figure><h2 id="通讯软件"><a href="#通讯软件" class="headerlink" title="通讯软件"></a>通讯软件</h2><table><thead><tr><th>软件名称</th><th>软件用途</th><th>价格</th><th>推荐安装方式</th></tr></thead><tbody><tr><td>telegram</td><td>聊天</td><td>免费（高级会员需订阅）</td><td>app store（有沙盒机制）</td></tr><tr><td><del>foxmail</del></td><td><del>管理企业邮件</del></td><td><del>免费</del></td><td><del>brew install –cask foxmail</del></td></tr><tr><td>thunderbird</td><td>邮件管理</td><td>开源免费</td><td>brew install –cask thunderbird</td></tr></tbody></table><p>一键安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install --cask thunderbird</span><br></pre></td></tr></table></figure><h2 id="编程软件"><a href="#编程软件" class="headerlink" title="编程软件"></a>编程软件</h2><table><thead><tr><th>软件名称</th><th>软件用途</th><th>价格</th><th>推荐安装方式</th></tr></thead><tbody><tr><td>drawio</td><td>UML绘图工具</td><td>免费</td><td>brew install –cask drawio</td></tr><tr><td>vscode</td><td>文本编译器（也可以是IDE）</td><td>免费</td><td>brew install –cask visual-studio-code</td></tr><tr><td><del>docker</del></td><td><del>容器化工具</del></td><td><del>免费</del></td><td><del>brew install –cask docker</del></td></tr><tr><td>orbstack</td><td>容器化工具</td><td>免费</td><td>brew install –cask orbstack</td></tr><tr><td>nginx</td><td>web、代理服务等</td><td>免费</td><td>brew install nginx</td></tr><tr><td>mat</td><td>java堆内存分析工具</td><td>免费</td><td>brew install mat</td></tr><tr><td>jmeter</td><td>并发测试工具</td><td>免费</td><td>brew install jmeter</td></tr><tr><td>node</td><td>开发环境</td><td>免费</td><td>brew install node</td></tr><tr><td>git</td><td>代码分支管理工具</td><td>免费</td><td>brew install git</td></tr><tr><td>sdkman</td><td>各类SDK管理工具</td><td>免费</td><td>curl -s “<a href="https://get.sdkman.io/">https://get.sdkman.io</a>“</td></tr></tbody></table><p>一键安装：</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">brew install --cask drawio &amp;&amp; \</span><br><span class="line">brew install --cask visual-studio-code &amp;&amp; \</span><br><span class="line">brew install --cask orbstack &amp;&amp; \</span><br><span class="line">brew install nginx &amp;&amp; \</span><br><span class="line">brew install mat &amp;&amp; \</span><br><span class="line">brew install jmeter &amp;&amp; \</span><br><span class="line">brew install node &amp;&amp; \</span><br><span class="line">brew install git &amp;&amp; \</span><br><span class="line">curl -s <span class="string">&quot;[https://get.sdkman.io](https://get.sdkman.io)&quot;</span></span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>Java云原生开发初体验报告</title>
      <link>https://baofeidyz.com/2022/7a54c64670b4/</link>
      <description>
        <![CDATA[<h1 id="Java云原生开发初体验报告"><a href="#Java云原生开发初体验报告" class="headerlink" title="Java云原生开发初体验报告"></a>Java云原生开发初体验报告</h1><h1 id="背景"><a]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/Java-Native/">Java Native</category>
      <pubDate>Tue, 09 Aug 2022 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="Java云原生开发初体验报告"><a href="#Java云原生开发初体验报告" class="headerlink" title="Java云原生开发初体验报告"></a>Java云原生开发初体验报告</h1><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>前段时间在考虑做一款小工具，功能非常简单，调用多个HTTP接口，分析处理返回的数据，生成Excel文件即可。 为了尽量的让这个工具的实用性更高，我首先想到Java的云原生开发方案，直接构建为可执行文件，不需要使用的人再去安装jre运行环境，或者是带着庞大的jre文件发出。再者，我也想试试Java的云原生方案到底好不好用。</p><h1 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h1><p>因为一直在使用Spring开发业务，所以我这次直接使用了Spring Native</p><h1 id="开发过程"><a href="#开发过程" class="headerlink" title="开发过程"></a>开发过程</h1><blockquote><p>注：均基于 macOS</p></blockquote><h2 id="开发环境安装"><a href="#开发环境安装" class="headerlink" title="开发环境安装"></a>开发环境安装</h2><p>Spring官网的文章写的非常好，直接参考官文就可以了。<a href="https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/">Spring官方文档</a> 我选择安装了sdk man，然后</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sdk install java 22.1.r11-nik</span><br></pre></td></tr></table></figure><p>剩下的就是直接用spring native创建一个项目就可以了，这个非常简单。</p><h2 id="编译构建"><a href="#编译构建" class="headerlink" title="编译构建"></a>编译构建</h2><ol><li><p>如果你用了阿里云的maven仓库，请记得切换成apache的原库</p></li><li><p>macOS默认mvn命令指定的jdk版本可能不是你新安装的GraalVM（像我本地就有七八个不同的JDK版本），会出现一些编译错误，那么你需要创建<code>~/.mavenrc</code>文件</p></li></ol><figure class="highlight plaintext"><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">// 路径记得自己改成正确的</span><br><span class="line">export JAVA_HOME=/Users/baofeidyz/.sdkman/candidates/java/current</span><br></pre></td></tr></table></figure><h2 id="报错集锦"><a href="#报错集锦" class="headerlink" title="报错集锦"></a>报错集锦</h2><h3 id="报错1"><a href="#报错1" class="headerlink" title="报错1"></a>报错1</h3><figure class="highlight plaintext"><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">gu install native-image</span><br><span class="line">Downloading: Component catalog from download.bell-sw.com</span><br><span class="line">Error: Unknown component: native-image</span><br></pre></td></tr></table></figure><p>在高版本里面，这个<code>native-image</code>好像不需要手动安装了（也有可能是我之前安装过）<a href="https://github.com/oracle/graal/issues/1665">Install fails in 19.2.0 with gu · Issue #1665 · oracle&#x2F;graal · GitHub</a> 然后我直接运行了一下<code>native-image</code>发现已经有了</p><h3 id="报错2-反射错误"><a href="#报错2-反射错误" class="headerlink" title="报错2 反射错误"></a>报错2 反射错误</h3><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><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.baofeidyz.XXXDTO` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)</span><br><span class="line">at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: java.util.ArrayList[0])</span><br><span class="line">at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1904) </span><br><span class="line">at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)</span><br><span class="line">at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1349)</span><br><span class="line">at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1415)</span><br><span class="line">at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:351)</span><br><span class="line">at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:184) </span><br><span class="line">at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:355) </span><br><span class="line">at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244) </span><br><span class="line">at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28) </span><br><span class="line">at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)</span><br><span class="line">at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:4650) </span><br><span class="line">at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2856) </span><br><span class="line">有省略部分堆栈</span><br></pre></td></tr></table></figure><p>这个报错只有在<code>GraalVM</code>下运行时才会出现，我最开始以为是因为我用了<code>lombok</code>然后导致的编译问题，同时我也有注意到，<code>Spring Native</code>本身是直接带了<code>lombok</code>的，我觉得不太可能吧。 但是我还是尝试去掉了<code>lombok</code>，当然并没有什么用。</p><p>然后我又开始在网上寻找一些蛛丝马迹，看看有没有其他人也遇到了类似的问题。但是Java Native问题的搜索真的是非常难找，因为这类问题有可能因为其他的原因也会出现，并且由于Spring Native使用人非常的少，你很难在Google的结果中找到真正的答案。我也尝试使用<code>site:github.com</code>再去Google搜索，仍没有找到答案。 我甚至都想去提issue了 😂 <a href="https://github.com/graalvm/native-build-tools">GitHub - graalvm&#x2F;native-build-tools: Native-image plugins for various build tools</a></p><p>我后来咨询了一位做过Spring Native开发的朋友，他告诉我，可能是因为反射导致的，需要增加配置。 我顺着这位朋友的思路，去看了一下反射配置的问题，正好在<a href="https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#_annotated_hints">这篇文章</a>中给出了一个示例</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="meta">@TypeHint(types = Data.class, typeNames = &quot;com.example.webclient.Data$SuperHero&quot;)</span> </span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SampleApplication</span> &#123; <span class="comment">// ... &#125;</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"><span class="meta">@TypeHint(types = XXXDTO.class, typeNames = &quot;com.baofeidyz.XXXDTO&quot;)</span>  </span><br><span class="line"><span class="meta">@SpringBootApplication</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MySpringNativeApplication</span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>确实解决了这个<code>jackson</code>反序列化导致的错误，但我又遇到了新的错误。</p><h3 id="报错3-缺少字符集"><a href="#报错3-缺少字符集" class="headerlink" title="报错3 缺少字符集"></a>报错3 缺少字符集</h3><figure class="highlight plaintext"><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">java.nio.charset.UnsupportedCharsetException: CP1252</span><br><span class="line">        at java.nio.charset.Charset.forName(Charset.java:529)</span><br><span class="line">        at org.apache.poi.util.LocaleUtil.&lt;clinit&gt;(LocaleUtil.java:56)</span><br><span class="line">        at org.apache.poi.ss.usermodel.DataFormatter.&lt;init&gt;(DataFormatter.java:242)</span><br><span class="line">        at org.apache.poi.ss.usermodel.DataFormatter.&lt;init&gt;(DataFormatter.java:233) </span><br><span class="line">        at org.apache.poi.ss.formula.functions.TextFunction.&lt;clinit&gt;(TextFunction.java:33) </span><br><span class="line">        at org.apache.poi.ss.formula.atp.AnalysisToolPak.createFunctionsMap(AnalysisToolPak.java:82)</span><br><span class="line">        at org.apache.poi.ss.formula.atp.AnalysisToolPak.&lt;init&gt;(AnalysisToolPak.java:47)</span><br><span class="line">        at org.apache.poi.ss.formula.atp.AnalysisToolPak.&lt;clinit&gt;(AnalysisToolPak.java:33) </span><br><span class="line">        at org.apache.poi.ss.formula.udf.AggregatingUDFFinder.&lt;clinit&gt;(AggregatingUDFFinder.java:35)</span><br><span class="line">        at java.lang.Class.ensureInitialized(DynamicHub.java:518) </span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.&lt;init&gt;(XSSFWorkbook.java:155) ~[na:na]</span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.&lt;init&gt;(XSSFWorkbook.java:226) ~[na:na]</span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.&lt;init&gt;(XSSFWorkbook.java:214) ~[na:na]</span><br><span class="line">        // 有省略堆栈信息</span><br></pre></td></tr></table></figure><p>这个看起来像是poi在操作的时候，遇到了一个<code>chartset</code>问题，spring有一个专门的<a href="https://github.com/spring-projects-experimental/spring-native">仓库</a>我在这个仓库里面找到了这个<a href="https://github.com/spring-projects-experimental/spring-native/issues/966">issue</a>，我看到一个人也遇到了这个<code>charset</code>的问题，但是他是<code>mysql</code>的时候遇到的，他提到加了一个参数用于解决这个问题。 紧接着，我又找到了几个issue：</p><ul><li><p><a href="https://github.com/oracle/graal/issues/3191">I tried to build a native image program with POI, but it failed · Issue #3191 · oracle&#x2F;graal · GitHub</a></p></li><li><p><a href="https://github.com/oracle/graal/issues/1294">issue with okhttp with graalvm-ce-19.0.0 · Issue #1294 · oracle&#x2F;graal · GitHub</a></p></li><li><p><a href="https://github.com/oracle/graal/issues/1370">UnsupportedEncodingException when running native-image generated binary · Issue #1370 · oracle&#x2F;graal · GitHub</a></p></li></ul><p>了解到说，需要在<code>native-image</code>编译的时候，增加一个<code>AddAllCharsets</code>参数 但是我是使用的spring native的maven插件，我的构建命令是<code>mvn -Pnative</code>，我并没有直接使用<code>native-image</code>，我不知道怎么增加这个参数才是对的。 此外，我还看了两份文档：</p><ul><li><p>关于这个如何查看堆信息之类的：<a href="https://www.graalvm.org/dashboard/?ojr=help;topic=getting-started.md">GraalVM Dashboard</a></p></li><li><p>关于<code>native-image</code>这个命令如何使用的：<a href="https://www.graalvm.org/uploads/quick-references/native-image-quick-reference-v2_A4.pdf">https://www.graalvm.org/uploads/quick-references/native-image-quick-reference-v2_A4.pdf</a> 然后我又去查了一下Spring Native自己维护的一个hints项目：<a href="https://github.com/spring-projects-experimental/spring-native/tree/main/spring-native-configuration/src/main/java">spring-native&#x2F;spring-native-configuration&#x2F;src&#x2F;main&#x2F;java at main · spring-projects-experimental&#x2F;spring-native · GitHub</a> 然后又去研究了一下Spring Native的脚本：<a href="https://github.com/spring-projects-experimental/spring-native/blob/main/scripts/compileWithMaven.sh">spring-native&#x2F;compileWithMaven.sh at main · spring-projects-experimental&#x2F;spring-native · GitHub</a></p></li></ul><p>终于，我知道如何在Spring Native中增加这个<code>AddAllCharsets</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@TypeHint(types = XXXDTO.class, typeNames = &quot;com.baofeidyz.XXXDTO&quot;)</span>  </span><br><span class="line"><span class="comment">// 👇 下面这行代码就解决了我的问题</span></span><br><span class="line"><span class="meta">@NativeHint(options = &quot;-H:+AddAllCharsets&quot;)</span>  </span><br><span class="line"><span class="meta">@SpringBootApplication</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MySpringNativeApplication</span> &#123;  </span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最佳实践就是加一个<code>@NativeHint(options = &quot;-H:+AddAllCharsets&quot;)</code> 当然不可能这么顺利的，很快我又遇到了新的报错</p><h3 id="错误4-POI的resource文件未加载"><a href="#错误4-POI的resource文件未加载" class="headerlink" title="错误4 POI的resource文件未加载"></a>错误4 POI的resource文件未加载</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></pre></td><td class="code"><pre><span class="line">org.apache.xmlbeans.SchemaTypeLoaderException: XML-BEANS compiled schema: Could not locate compiled schema resource org/apache/poi/schemas/ooxml/system/ooxml/index.xsb (org.apache.poi.schemas.ooxml.system.ooxml.index) - code <span class="number">0</span></span><br><span class="line">        at org.apache.xmlbeans.impl.schema.XsbReader.&lt;init&gt;(XsbReader.java:<span class="number">63</span>) </span><br><span class="line">        at org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl.initFromHeader(SchemaTypeSystemImpl.java:<span class="number">235</span>) </span><br><span class="line">        at org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl.&lt;init&gt;(SchemaTypeSystemImpl.java:<span class="number">201</span>) </span><br><span class="line">        at org.apache.poi.schemas.ooxml.system.ooxml.TypeSystemHolder.&lt;init&gt;(TypeSystemHolder.java:<span class="number">9</span>) </span><br><span class="line">        at org.apache.poi.schemas.ooxml.system.ooxml.TypeSystemHolder.&lt;clinit&gt;(TypeSystemHolder.java:<span class="number">6</span>) </span><br><span class="line">        at org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook.&lt;clinit&gt;(CTWorkbook.java:<span class="number">22</span>) </span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.onWorkbookCreate(XSSFWorkbook.java:<span class="number">475</span>)</span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.&lt;init&gt;(XSSFWorkbook.java:<span class="number">232</span>) </span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.&lt;init&gt;(XSSFWorkbook.java:<span class="number">226</span>) </span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.&lt;init&gt;(XSSFWorkbook.java:<span class="number">214</span>)</span><br><span class="line">        <span class="comment">// 有省略</span></span><br></pre></td></tr></table></figure><p>我猜测应该是因为资源没有引入导致的，所以我理解应该还是应该用spring native提供的hint去实现。我参考这个<a href="https://github.com/GoodforGod/graalvm-hint#resourcehint">代码块</a> 增加了一个<code>@ResourceHint</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@TypeHint(types = XXXDTO.class, typeNames = &quot;com.baofeidyz.XXXDTO&quot;)</span>  </span><br><span class="line"><span class="meta">@ResourceHint(patterns = &#123;&quot;(^/|[a-zA-Z])*/.+(/$)?.xsb&quot;&#125;)</span>  </span><br><span class="line"><span class="meta">@NativeHint(options = &quot;-H:+AddAllCharsets&quot;)</span>  </span><br><span class="line"><span class="meta">@SpringBootApplication</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MySpringNativeApplication</span> &#123;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="报错5"><a href="#报错5" class="headerlink" title="报错5"></a>报错5</h3><figure class="highlight plaintext"><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">java.lang.ClassCastException: org.apache.xmlbeans.impl.values.XmlComplexContentImpl cannot be cast to org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook</span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.onWorkbookCreate(XSSFWorkbook.java:475)</span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.&lt;init&gt;(XSSFWorkbook.java:232) </span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.&lt;init&gt;(XSSFWorkbook.java:226) </span><br><span class="line">        at org.apache.poi.xssf.usermodel.XSSFWorkbook.&lt;init&gt;(XSSFWorkbook.java:214) </span><br><span class="line">        // 有省略</span><br></pre></td></tr></table></figure><p>然后我在GraalVM的issue清单中找到了这个issue：<a href="https://github.com/oracle/graal/issues/1929">Getting following error while generating excel file using native image. · Issue #1929 · oracle&#x2F;graal · GitHub</a> 基本上错误信息就是一致的。 我试了一下，不行。</p><p>我感觉POI和GraalVM配合坑有点多，我不打算一个一个找了，我尝试去找一些大而全的文章，于是，我找到了这篇文章：<a href="http://lokie.wang/article/66">Graalvm使用采坑 - 行万里路才能回到内心深处，读万卷书才能看得清皓月繁星</a></p><p>可惜还是失败了，我感觉算了吧，POI和GraalVM的路还很长哦…</p><h1 id="一些思考"><a href="#一些思考" class="headerlink" title="一些思考"></a>一些思考</h1><ol><li><p>对于这种POI组件的问题，处理起来确实很难。我查了GraalVM的ISSUE清单，5.0之前的POI确实是有一些问题，但与此同时GraalVM可能需要大量的构建参数去配置，然后Spring Native又在其中转了一次。也就是说，我需要知道这个问题不是POI导致的，然后去看native-image对应的解决方案（配置参数）是什么，然后再转成Spring Native的hints。如果我不能确定这个问题是不是POI导致的，我连提ISSUE给谁都不清楚，如果自己尝试去排查编译问题，这就会进入一个更深的坑。</p></li><li><p>我建议如果<a href="https://github.com/spring-projects-experimental/spring-native/tree/main/samples">Spring Native example</a>下找不到对应的例子之前，不要去趟这个浑水，整个开发过程非常阻塞，问题很难说真正的解决完。</p></li><li><p>Java云原生的路确实很难走，不管是GraalVM去适配组件，还是组件去适配GraalVM，整个适配时间会拉得很长很长。</p></li><li><p>GraalVM编译时间很长，我的项目并不大，4核8线程，16G内存，但是每次都需要大概3分钟左右，并且编译的时候，CPU使用率一直都很高。</p></li></ol><h1 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h1><p>我后来用GO重写了整个程序，这是我第一次用GO，从0开始学，大概花了20个小时就写完了，并且我还用了协程并发，处理了一些线程安全的问题。相比之下，Spring Native至少花了我四倍的时间，并且没有做完。 GO编译速度非常快，我交叉编译linux或windows也才10s，CPU使用率也不高，相比之下，GO做这类小工具的云原生开发真的是非常好用。</p><p>所以我在怀疑，Java云原生是否真的有意义？希望十年后，能被打脸。</p><p>以上就是我Java云原生的初步体验报告，如果对你有一点点帮助，希望可以给我的文章点个赞和收藏。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>NanoPi R2S 软路由折腾记</title>
      <link>https://baofeidyz.com/2022/ec3fcefc534b/</link>
      <description>
        <![CDATA[<h1 id="NanoPi-R2S-软路由折腾记"><a href="#NanoPi-R2S-软路由折腾记" class="headerlink" title="NanoPi R2S 软路由折腾记"></a>NanoPi R2S 软路由折腾记</h1><h1]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%98%E8%85%BE/">折腾</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%98%E8%85%BE/">折腾</category>
      <category domain="https://baofeidyz.com/tags/NanoPi-R2S/">NanoPi R2S</category>
      <pubDate>Fri, 17 Jun 2022 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="NanoPi-R2S-软路由折腾记"><a href="#NanoPi-R2S-软路由折腾记" class="headerlink" title="NanoPi R2S 软路由折腾记"></a>NanoPi R2S 软路由折腾记</h1><h1 id="😩-问题多多"><a href="#😩-问题多多" class="headerlink" title="😩 问题多多"></a>😩 问题多多</h1><p>最开始用的是<a href="https://www.youtube.com/channel/UCA0gaB71yl2p_g5WlO5zljw">YouTuber 洋葱</a>提供的固件，但是存在一些问题：</p><ol><li>TF卡的空间没有挂载完（8G的卡，只用到了1G左右），自己用fdisk挂载逻辑分区报错；</li><li>修改docker默认存储位置后，镜像依赖的一些程序无法使用，如<code>curl</code>等；</li><li>直接<code>opkg install</code>安装固件总会出一些莫名其妙的依赖问题，<code>opkg update</code>也好，还是直接ipk安装，都会遇到很多奇奇怪怪的问题；</li></ol><h1 id="🧐-有机会？"><a href="#🧐-有机会？" class="headerlink" title="🧐 有机会？"></a>🧐 有机会？</h1><p>接着，我就去openwrt官网找了镜像，但无论是用工具<code>balenaEther</code>刷入，还是使用<code>dd</code>命令写入。当在TF卡插回R2S时，SYS灯一直红色长亮，wan和lan的灯无任何响应。<br>由于R2S本身还有挑卡的毛病，不太确定是不是镜像本身存在问题；</p><h1 id="😋-解决啦～"><a href="#😋-解决啦～" class="headerlink" title="😋 解决啦～"></a>😋 解决啦～</h1><p>后面意外发现其实友善也有自己的官网，在其<a href="https://wiki.friendlyelec.com/wiki/index.php/NanoPi_R2S/zh#.E5.AE.89.E8.A3.85.E7.B3.BB.E7.BB.9F">wiki</a>上找到了<a href="https://download.friendlyelec.com/NanoPiR2S">镜像下载地址</a>，<br>下载刷入以后，正常启动。</p><blockquote><p>我用的是<code>balenaEther</code>，但使用<code>dd</code>应该也是可行的</p></blockquote><p>docker也可正常使用。当然还是遇到了一些小问题。</p><h1 id="🐛-一些小问题"><a href="#🐛-一些小问题" class="headerlink" title="🐛 一些小问题"></a>🐛 一些小问题</h1><h2 id="openclash订阅更新一直失败"><a href="#openclash订阅更新一直失败" class="headerlink" title="openclash订阅更新一直失败"></a>openclash订阅更新一直失败</h2><p>安装好openclash插件以后，在更新订阅时，一直提示失败。</p><h3 id="DNS无法解析"><a href="#DNS无法解析" class="headerlink" title="DNS无法解析"></a>DNS无法解析</h3><p>常见DNS无法解析，直接写到<code>etc/hosts</code>，使用<code>ping</code>或者<code>nslookup</code>命令验证解决</p><h3 id="curl命令缺少依赖文件"><a href="#curl命令缺少依赖文件" class="headerlink" title="curl命令缺少依赖文件"></a>curl命令缺少依赖文件</h3><p>openclash下载文件也是依赖于<code>wget</code>或者<code>curl</code>之类命令，我<code>ssh</code>连接到路由器以后，测试了一下这两个命令是否可正常下载文件，结果到<code>curl</code>时，发现缺少了部分so依赖文件，使用<code>opkg install</code>安装对应的包就解决了。<br>参考：<a href="https://openwrt.pkgs.org/21.02/openwrt-base-x86_64/libwolfssl5.2.0.99a5b54a_5.2.0-stable-2_x86_64.ipk.html">libwolfssl5.2.0.99a5b54a_5.2.0-stable-2_x86_64.ipk OpenWrt 21.02 Download</a></p><h1 id="🥶-继续折腾HASS"><a href="#🥶-继续折腾HASS" class="headerlink" title="🥶 继续折腾HASS"></a>🥶 继续折腾HASS</h1><p>未完待续…</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Docker容器一启动就挂，要怎么排查？</title>
      <link>https://baofeidyz.com/2022/889ffcfbf52a/</link>
      <description>
        <![CDATA[<h1 id="Docker容器一启动就挂，要怎么排查？"><a href="#Docker容器一启动就挂，要怎么排查？" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/%E8%B8%A9%E5%9D%91/">踩坑</category>
      <category domain="https://baofeidyz.com/tags/Docker/">Docker</category>
      <pubDate>Fri, 10 Jun 2022 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="Docker容器一启动就挂，要怎么排查？"><a href="#Docker容器一启动就挂，要怎么排查？" class="headerlink" title="Docker容器一启动就挂，要怎么排查？"></a>Docker容器一启动就挂，要怎么排查？</h1><h1 id="1-分析镜像信息"><a href="#1-分析镜像信息" class="headerlink" title="1. 分析镜像信息"></a>1. 分析镜像信息</h1><p>通过</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker inspect 镜像ID</span><br></pre></td></tr></table></figure><p>可以查看当前镜像的属性信息，那么我们重点需要关注的是<code>Entrypoint</code>这个属性的信息。<br>关于entrypoint是什么，你可以参考<a href="https://docs.docker.com/engine/reference/builder/#entrypoint">Dockerfile reference | Docker Documentation</a></p><p>我这里获取到的<code>Entrypoint</code>信息如下：</p><figure class="highlight json"><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="attr">&quot;Entrypoint&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                <span class="string">&quot;/docker-entrypoint.sh&quot;</span></span><br><span class="line">            <span class="punctuation">]</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>这个<code>Entrypoint</code>可以说是非常的简单，但是又非常的复杂。<br>简单在于就一行，是直接去执行<code>/docker-entrypoint.sh</code>这个脚本，但是复杂在于我们并不能直接看到这个<code>docker-entrypoint.sh</code>的内容。</p><h1 id="2-分析Entrypoint"><a href="#2-分析Entrypoint" class="headerlink" title="2. 分析Entrypoint"></a>2. 分析Entrypoint</h1><p>一般来说，我们有几种方法可以看到这个<code>/docker-entrypoint.sh</code>文件：</p><ol><li>挂载：通过<code>docker run -v</code>将这个目录挂载到宿主机上，就算这个容器一启动就死，我们也不用担心拿不到文件；</li><li>交互命令（这个词是我自己想的，可能不是很准确）：通过<code>docker run -it 镜像ID sh -c &quot;cat /docker-entrypoint.sh&quot;</code>去查看内容。这个方法其实还是需要容器启动的，如果是启动立刻挂的情况，这个方法应该是会失效的；</li><li>跳过entrypoint：通过&#96;docker run –entrypoint sh 镜像ID -c “cat &#x2F;docker-entrypoint.sh”，即可覆盖镜像中的entrypoint，覆盖以后，容器并没有去运行entrypoint脚本，所以也就不会挂了。</li></ol><h1 id="3-覆盖entrypoint"><a href="#3-覆盖entrypoint" class="headerlink" title="3. 覆盖entrypoint"></a>3. 覆盖entrypoint</h1><p>既然都可以覆盖entrypoint了，那我们为啥要局限于通过交互命令去查看文件呢？</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -d --entrypoint sh 镜像ID -c &quot;ping 127.0.0.1&quot;</span><br></pre></td></tr></table></figure><p>通过这个命令，给容器一个不可完成的任务，那么这个容器将一直存活。然后再通过</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker exec -it 容器ID sh</span><br></pre></td></tr></table></figure><p>进入容器，岂不是美哉？</p><blockquote><p>如果你的镜像比较简单，可能会没有<code>ping</code>命令，那么你可能需要<code>tail</code>或者其他命令来保证容器可长期存活</p></blockquote><h1 id="4-手动执行entrypoint"><a href="#4-手动执行entrypoint" class="headerlink" title="4. 手动执行entrypoint"></a>4. 手动执行entrypoint</h1><p>容器不会被自动杀掉以后，我们就可以一步一步的去分析这个entrypoint到底是死在哪儿了？</p><p>这里就没有办法展开讲了，因为每个人的情况都是不同的，只要有点耐心，我相信一定是可以解决的。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Docker（K8S）环境下开启JMX远程监控</title>
      <link>https://baofeidyz.com/2022/ee902c708a62/</link>
      <description>
        <![CDATA[<h1 id="Docker（K8S）环境下开启JMX远程监控"><a href="#Docker（K8S）环境下开启JMX远程监控" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/%E8%B8%A9%E5%9D%91/">踩坑</category>
      <category domain="https://baofeidyz.com/tags/Docker/">Docker</category>
      <category domain="https://baofeidyz.com/tags/K8s/">K8s</category>
      <category domain="https://baofeidyz.com/tags/JMX/">JMX</category>
      <pubDate>Fri, 03 Jun 2022 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="Docker（K8S）环境下开启JMX远程监控"><a href="#Docker（K8S）环境下开启JMX远程监控" class="headerlink" title="Docker（K8S）环境下开启JMX远程监控"></a>Docker（K8S）环境下开启JMX远程监控</h1><h1 id="问题引入"><a href="#问题引入" class="headerlink" title="问题引入"></a>问题引入</h1><p>JMX（即Java Management Extensions），如果你在网上搜索如何配置JMX，你就会看到这样的一堆配置</p><figure class="highlight plaintext"><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">-Djava.rmi.server.hostname=</span><br><span class="line">-Dcom.sun.management.jmxremote</span><br><span class="line">-Dcom.sun.management.jmxremote.rmi.port=</span><br><span class="line">-Dcom.sun.management.jmxremote.port=</span><br><span class="line">-Dcom.sun.management.jmxremote.authenticate=false</span><br><span class="line">-Dcom.sun.management.jmxremote.ssl=false</span><br></pre></td></tr></table></figure><p>然后你就会发现在docker下，怎么弄都不对。</p><h1 id="JMX分析"><a href="#JMX分析" class="headerlink" title="JMX分析"></a>JMX分析</h1><p>JMX其实需要注册三个端口，其作用为：</p><ul><li><p>端口1: 接收注册请求，JMX客户端（如<code>jvisualvm</code>）在连接时，需要填写的端口号</p></li><li><p>端口2: 用于远程连接，可以与端口1使用同一个端口号</p></li><li><p>端口3: 用于本地连接，随机（这个端口在实际业务场景中，不需要去指定，我暂时没研究如何指定这个端口）</p></li></ul><h1 id="排坑"><a href="#排坑" class="headerlink" title="排坑"></a>排坑</h1><h2 id="坑点1-JMX端口仅暴露1个"><a href="#坑点1-JMX端口仅暴露1个" class="headerlink" title="坑点1 JMX端口仅暴露1个"></a>坑点1 JMX端口仅暴露1个</h2><p>由于JMX是在远程监控的情况下是需要两个端口的，所以在Docker环境下很可能就会出现因为端口没映射全而导致的失败</p><p>解决办法也很简单，就是把端口1和端口2设置为同一个端口即可，这样也可以减少端口映射过多而产生的端口冲突</p><figure class="highlight plaintext"><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">-Dcom.sun.management.jmxremote.rmi.port=8888</span><br><span class="line">-Dcom.sun.management.jmxremote.port=8888</span><br></pre></td></tr></table></figure><blockquote><p>注：<code>8888</code>这个端口是我随便写的，在合法范围内都可以</p></blockquote><h2 id="坑点2-java-rmi-server-hostname配置导致JMX连接失败"><a href="#坑点2-java-rmi-server-hostname配置导致JMX连接失败" class="headerlink" title="坑点2 java.rmi.server.hostname配置导致JMX连接失败"></a>坑点2 java.rmi.server.hostname配置导致JMX连接失败</h2><p>坑点1其实在网络上已经有大把大把现成的文章了，而对于坑点2，则在于<code>java.rmi.server.hostname</code>这个配置。我个人认为这个是因为JMX诞生比较早，所以JMX并没有适配容器化，这就导致了第二个坑点。</p><p><code>java.rmi.server.hostname</code>配置失败的体现在于，你的JMX客户端，会在连接中卡顿很久，这其实就是JMX尝试连接，但连接不上导致的。</p><p>JMX客户端根据你填写的IP和PORT去查找JMX服务，这时候你的服务端会返回这个<code>java.rmi.server.hostname</code>和<code>com.sun.management.jmxremote.rmi.port</code>，JMX客户端再根据这两个值去连接。</p><p>那么在docker环境下，你配置的<code>java.rmi.server.hostname</code>很可能就配置错了，你需要保证这个<code>java.rmi.server.hostname</code>是客户端直接可连的，并且这个<code>com.sun.management.jmxremote.rmi.port</code>也是可以直接访问的。</p><p>我使用的环境是我用我的笔记本去监控运行在服务器K8S集群中的服务，这种情况下，我需要把<code>java.rmi.server.hostname</code>配置为这个集群所使用的映射服务IP，</p><blockquote><p>如果你的映射服务IP过多，这就会很麻烦，因为每次修改<code>java.rmi.server.hostname</code>需要重启，而重启等同于重新部署，这就导致有可能不匹配，这个问题可能需要想办法绑定，或者自己手动用<code>kubectl</code>做映射</p></blockquote><p>并且你还需要保证<code>com.sun.management.jmxremote.rmi.port</code>是映射服务所使用的端口。</p><blockquote><p>创建端口映射的时候，就保证你的容器端口和映射端口得是一致的，不然JMX服务返回给JMX客户端的端口，没办法通过端口映射连接了</p></blockquote><p>![在这里插入图片描述](<a href="https://img-blog.csdnimg.cn/129b48695cbd44c5a6c6d0f346c23666.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmFvZmVpZHl6,size_20,color_FFFFFF,t_70,g_se,x_16">https://img-blog.csdnimg.cn/129b48695cbd44c5a6c6d0f346c23666.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmFvZmVpZHl6,size_20,color_FFFFFF,t_70,g_se,x_16</a> align&#x3D;”left”)</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://stackoverflow.com/questions/35466461/how-to-connect-with-jmx-from-host-to-docker-container-in-docker-machine">how-to-connect-with-jmx-from-host-to-docker-container-in-docker-machine</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>不要直接复用其他业务的线程池</title>
      <link>https://baofeidyz.com/2022/6fd6c11c6446/</link>
      <description>
        <![CDATA[<h1 id="不要直接复用其他业务的线程池"><a href="#不要直接复用其他业务的线程池" class="headerlink" title="不要直接复用其他业务的线程池"></a>不要直接复用其他业务的线程池</h1><h1 id="前言"><a]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/%E8%B8%A9%E5%9D%91/">踩坑</category>
      <pubDate>Fri, 03 Jun 2022 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="不要直接复用其他业务的线程池"><a href="#不要直接复用其他业务的线程池" class="headerlink" title="不要直接复用其他业务的线程池"></a>不要直接复用其他业务的线程池</h1><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近在复查团队小伙伴的代码时发现，错误复用了一个定时触发信息同步的线程池。但他开发的代码所对应的业务场景是响应前端页面的请求。而这次的线程池复用将可能会导致系统页面“卡死”。</p><p>信息同步的线程池，其主要配置信息为：</p><ul><li>corePoolSize：4</li><li>maximumPoolSize：8</li><li>keepAliveTime：30L</li><li>unit：TimeUnit.SECONDS</li><li>workQueue：<code>new ArrayBlockingQueue&lt;&gt;(1000)</code></li><li>threadFactory：<code>new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat(&quot;xxx-pool-%d&quot;).build()</code></li><li>handler：<code>new ThreadPoolExecutor.CallerRunsPolicy()</code></li></ul><p>这个定时任务设定为每半个小时执行一次，当定时任务开始执行后，这个队列任务很快就会装满。</p><p>如果这个时候响应前端页面请求的线程进入，就会进行等待队列，此时可能会发生：</p><ol><li>任务队列满，触发丢弃策略，前端页面请求被丢弃，用户拿不到的正确的数据；</li><li>任务较多，前端页面请求等待，可能会出现前端页面请求超时，系统“卡死”；</li></ol><h1 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h1><p>存在任务优先级存在问题。页面请求的优先级应大于信息同步任务的优先级，但如果复用同一个线程池，那么在任务执行顺序上，就是先进先执行。</p><p>如果此前已经有多个信息同步任务正在等待，页面请求也必须要等到信息同步任务执行完成以后才可以去与其他线程抢占系统资源。</p><h1 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h1><p>对于一些优先级较高的任务，应当独立维护线程池。虽然在JVM中还存在线程调度问题，但至少不会一直阻塞去等待其他任务的执行。</p><p>对于一些优先级较低的定时任务，可以考虑适当复用，与此同时也需要考虑好核心线程数、最大线程数、等待队列、丢弃策略等。</p><p>很多技术解决方案都是一把双刃剑，用得好，事半功倍，用不好，系统宕机😂</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>解决macOS Big Sur升级后部分java应用无法打开的问题JavaVM: Failed to load JVM: libserver.dylib</title>
      <link>https://baofeidyz.com/2022/8f9d0cbeb525/</link>
      <description>
        <![CDATA[<h1 id="解决macOS-Big-Sur升级后部分java应用无法打开的问题JavaVM-Failed-to-load-JVM-libserver-dylib"><a]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/%E8%B8%A9%E5%9D%91/">踩坑</category>
      <category domain="https://baofeidyz.com/tags/macOS/">macOS</category>
      <pubDate>Fri, 03 Jun 2022 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="解决macOS-Big-Sur升级后部分java应用无法打开的问题JavaVM-Failed-to-load-JVM-libserver-dylib"><a href="#解决macOS-Big-Sur升级后部分java应用无法打开的问题JavaVM-Failed-to-load-JVM-libserver-dylib" class="headerlink" title="解决macOS Big Sur升级后部分java应用无法打开的问题JavaVM: Failed to load JVM: libserver.dylib"></a>解决macOS Big Sur升级后部分java应用无法打开的问题JavaVM: Failed to load JVM: libserver.dylib</h1><p>升级到macOS Big Sur以后，之前安装的dbeaver和mat都无法打开了，点击报错都是同一个问题。</p><p>实际上oracle jdk在安装完成以后是没有 libserver.dylib 这个文件的，但是dbeaver和mat还是在查找这个文件，应该是出兼容性bug了。</p><p>解决的方案很简单，就是要找到这个 libserver.dylib 对应应该是什么文件就可以了。几番折腾之下，我在<a href="https://support.excentis.com/index.php?/Knowledgebase/Article/View/84">这里</a>找到了答案，实际的地址应该是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/server/libjvm.dylib</span><br></pre></td></tr></table></figure><p>使用<code>ln -s</code>创建一个链接就可以解决了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo ln -s /Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/lib/server/libjvm.dylib /Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/lib/libserver.dylib</span><br></pre></td></tr></table></figure><p>如果你的jdk文件夹和我的命令不一样，请记得修改命令。</p><p>升级之前为啥没有这个问题，我就不知道了，很有可能我之前创建过链接被macOS给删掉？（可能性不大吧）</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>git仓库从30G压缩到600M-git文档仓库过大的优化方案</title>
      <link>https://baofeidyz.com/2021/44e6c6da4a97/</link>
      <description>
        <![CDATA[<h1 id="git仓库从30G压缩到600M-git文档仓库过大的优化方案"><a href="#git仓库从30G压缩到600M-git文档仓库过大的优化方案" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Git/">Git</category>
      <pubDate>Mon, 23 Aug 2021 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="git仓库从30G压缩到600M-git文档仓库过大的优化方案"><a href="#git仓库从30G压缩到600M-git文档仓库过大的优化方案" class="headerlink" title="git仓库从30G压缩到600M-git文档仓库过大的优化方案"></a>git仓库从30G压缩到600M-git文档仓库过大的优化方案</h1><p>项目上使用git管理相关文档，因维护多年，导致其文档仓库已经逼近30G的大小，对于我这台256G的MBP来说，无疑是占用了巨大的空间，介于此，我计划减少这个仓库的体积。</p><h2 id="git-lfs"><a href="#git-lfs" class="headerlink" title="git-lfs"></a>git-lfs</h2><p><a href="https://git-lfs.github.com/">git-lfs</a>是<a href="https://www.atlassian.com/git/tutorials/git-lfs">atlassian</a>维护的开源项目，意图解决因反复修改大文件而导致git仓库变大，以及影响初次下载的体验；</p><p>在我尝试使用git-lfs后，占用空间被压缩到了24G左右，但效果并不明显，这主要是因为这个git仓库中的大部分文档都不存在反复修改的情况，大部分文件都是上传后再也没有改过。</p><p>可以说git-lfs解决了我一部分的问题，但并没有全部解决。</p><h2 id="GVFS"><a href="#GVFS" class="headerlink" title="GVFS"></a>GVFS</h2><p>GVFS是微软的开源项目，意图解决git对于超大型仓库的维护问题，但很可惜，GVFS依赖于window操作系统，我手上主力机还是macOS，所以GVFS就不考虑了。</p><h2 id="git-sparse-checkout"><a href="#git-sparse-checkout" class="headerlink" title="git sparse checkout"></a>git sparse checkout</h2><p><a href="https://git-scm.com/docs/git-sparse-checkout">sparse checkout</a>是git自己维护的功能，其提供的功能是告诉git相关的命令再拉取时仅拉取部分文件，或排除掉部分文件。</p><p>很现实，sparse checkout就是解决我这个问题的最佳方案</p><h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><ol><li>新建仓库并设置远端仓库地址</li></ol><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">git init</span><br><span class="line">git remote add -f origin &lt;url&gt;</span><br></pre></td></tr></table></figure><ol><li>开启配置</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git config core.sparsecheckout true</span><br></pre></td></tr></table></figure><ol><li>配置想要拉取的文件路径</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xxx/xxx/**</span><br></pre></td></tr></table></figure><p>或者是不想要拉取的文件路径</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">!yyy/**</span><br></pre></td></tr></table></figure><p>然后正常使用<code>git pull</code>拉取即可</p><blockquote><ol><li><p>注意使用较新版本的git；</p></li><li><p>使用方法也可以参考[官网链接](git config core.sparsecheckout true)</p></li></ol></blockquote><h1 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h1><p>最终我使用的是<code>git-lfs</code>搭配<code>git parse checkout</code>搭配使用，一方面降低大文件重复修改所产生的占用，一方面只需要拉取我自己需要的文件到本地即可。</p><p>这样操作以后，原30G的git仓库，我现在只需要600M就可以了～</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Java应用被k8s认定为oom杀掉</title>
      <link>https://baofeidyz.com/2020/e94cc37e0655/</link>
      <description>
        <![CDATA[<h1 id="Java应用被k8s认定为oom杀掉"><a href="#Java应用被k8s认定为oom杀掉" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/%E6%95%85%E9%9A%9C%E6%8E%92%E6%9F%A5%E8%AE%B0%E5%BD%95/">故障排查记录</category>
      <pubDate>Tue, 15 Dec 2020 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="Java应用被k8s认定为oom杀掉"><a href="#Java应用被k8s认定为oom杀掉" class="headerlink" title="Java应用被k8s认定为oom杀掉"></a>Java应用被k8s认定为oom杀掉</h1><p>前不久现场反馈说服务运行一段时间就重启了，希望我介入排查一下。</p><p>先说结论</p><p>jvm堆大小与k8s pod设置的大小一致，均为4g。因jvm还存在其他的内存占用，pod服务总体的内存占用会超过4g，k8s认定为oom，将其杀掉。以及jvm较低版本没有支持容器namespace的资源隔离。</p><h1 id="处理过程"><a href="#处理过程" class="headerlink" title="处理过程"></a>处理过程</h1><ol><li><p>检查日志</p><p>看服务重启前后日志有无抛出一些较为严重的错误，是否存在因为个别异常导致的服务重启；</p></li><li><p>修改jvm堆的大小，增加oom时候自动dump等参数</p><ol><li>怀疑是系统内部有一些“不良”业务导致的堆的大量占用，试想是否可以通过增加堆的大小，再对堆进行快照分析，确定具体占用较大堆内存的业务代码，对其进行定向优化。</li><li>增加<code>-XX:+HeapDumpOnOutOfMemoryError</code>以及<code>-XX:HeapDumpPath=/myPath/heapdump.hprof</code>参数，让jvm在下一次oom时自动导出堆的快照，便于分析。</li></ol><p>通过不停的分析堆的镜像快照，确实是没有任何业务代码过多的或者过量的导致了堆的增长，增加的<code>-XX:+HeapDumpOnOutOfMemoryError</code>也没有生效。</p></li><li><p>检查k8s pod信息</p><p>在k8s<br>pod信息查到，服务是以oom的原因导致被kill的。经过了解，k8s认定pod的内存占用达到了pod所配置的limit值时，就会判定为oom，然后杀掉，从侧面增加<code>-XX:+HeapDumpOnOutOfMemoryError</code><br>无效的原因。</p><p>检查现场配置时发现，jvm堆的大小和pod的大小限制一致。已知java 8除了堆以外还有其他的内存占用，猜测是不是这部分导致了pod实际内存使用大小超过了pod限制导致。</p><p>修改pod限制参数为6g后，问题得以解决。</p></li></ol><h1 id="后续思考"><a href="#后续思考" class="headerlink" title="后续思考"></a>后续思考</h1><p>虽然修改为6g以后，服务不再被k8s以oom的原因kill了，但于此同时，还有一个问题一直萦绕着我。</p><p><strong>为什么jvm没有回收内存？</strong></p><p>在一番了解后，我了解到Java 1.8有一个更新，在Java 1.8<br>191这个版本中，Java更新了对于namespace的支持，地址是：<a href="https://www.oracle.com/java/technologies/javase/8u191-relnotes.html%EF%BC%8C%E5%AF%B9%E5%BA%94%E5%85%B7%E4%BD%93%E7%9A%84bug%E6%8F%8F%E8%BF%B0%E6%98%AFhttps://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146115">https://www.oracle.com/java/technologies/javase/8u191-relnotes.html，对应具体的bug描述是https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146115</a></p><p>容器是通过linux的namespace做资源隔离，通过pid下的cgroup做资源限制，但是在早先版本中，jvm并没有支持这一特性，从而导致了jvm内存不回收的问题。</p><p><strong>jvm指定的堆大小到底</strong></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>类的加载、链接和初始化（基于Java 1.8）</title>
      <link>https://baofeidyz.com/2020/4fcfc7a47487/</link>
      <description>
        <![CDATA[<h2 id="类的加载、链接和初始化（基于Java-1-8）"><a href="#类的加载、链接和初始化（基于Java-1-8）" class="headerlink" title="类的加载、链接和初始化（基于Java]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/Java%E4%B8%AD%E7%BA%A7/">Java中级</category>
      <pubDate>Thu, 10 Dec 2020 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="类的加载、链接和初始化（基于Java-1-8）"><a href="#类的加载、链接和初始化（基于Java-1-8）" class="headerlink" title="类的加载、链接和初始化（基于Java 1.8）"></a>类的加载、链接和初始化（基于Java 1.8）</h2><p>Java的数据类型（Data type）主要是有两种：</p><ol><li><a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.3">基本类型 primitive types</a></li><li><a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.4">引用类型 reference types</a></li></ol><p>其中引用类型又被细分为：</p><ol><li>类 class types</li><li>数组 array types</li><li>接口 interface types</li></ol><p>基本类型和数组类型是由Java虚拟机直接生成的，其他（类 class types、接口 interface types）则需要Java虚拟机对其进行链接和初始化。</p><h1 id="1-Java虚拟机启动-Java-Virtual-Machine-Startup"><a href="#1-Java虚拟机启动-Java-Virtual-Machine-Startup" class="headerlink" title="1. Java虚拟机启动 Java Virtual Machine Startup"></a>1. Java虚拟机启动 Java Virtual Machine Startup</h1><ul><li>通过引导类加载器创建一个初始类来启动，并执行这个<code>public class</code>中的<code>void main(String[])</code>方法。</li><li>初始类可以作为命令行参数提供。或者，该实现可以提供一个初始类，该初始类设置一个类加载器，该类加载器进而加载。</li></ul><h1 id="2-创建和加载-Creation-and-Loading"><a href="#2-创建和加载-Creation-and-Loading" class="headerlink" title="2. 创建和加载 Creation and Loading"></a>2. 创建和加载 Creation and Loading</h1><ul><li>指查找字节流，并据此创建类的过程。</li><li>其中数组（array types）是没有字节流的，由Java虚拟机直接生成，对于其他的类来说，Java虚拟机需要借助于类加载器来完成查找字节流的工程。</li></ul><h2 id="2-1-类加载器-ClassLoader"><a href="#2-1-类加载器-ClassLoader" class="headerlink" title="2.1 类加载器 ClassLoader"></a>2.1 类加载器 ClassLoader</h2><p>在Java虚拟机规范中，类加载器被分为两种：</p><ol><li>Java虚拟机提供的引导类加载器（bootstrap class loader)</li><li>用户定义的类加载器(user-defind class loaders)</li></ol><h3 id="2-1-1-Java虚拟机提供的引导类加载器-bootstrap-class-loader"><a href="#2-1-1-Java虚拟机提供的引导类加载器-bootstrap-class-loader" class="headerlink" title="2.1.1 Java虚拟机提供的引导类加载器 bootstrap class loader"></a>2.1.1 Java虚拟机提供的引导类加载器 bootstrap class loader</h3><ul><li>bootstrap class loader 由Java虚拟机提供的</li><li>这个类加载器是使用C++实现的，没有对应的Java对象。</li></ul><h3 id="2-1-2-用户定义的类加载器-user-defind-class-loaders"><a href="#2-1-2-用户定义的类加载器-user-defind-class-loaders" class="headerlink" title="2.1.2 用户定义的类加载器 user-defind class loaders"></a>2.1.2 用户定义的类加载器 user-defind class loaders</h3><ul><li>user-defind class loaders 是Java虚拟机规范中对于类加载器的分类划分，是一个统称，实际上并没有这个类加载器</li><li>用户定义的类加载器都是<code>java.lang.ClassLoader</code>类的子类</li><li>在Java虚拟机规范中提到，用户定义的类加载器可以实现通过网络下载类，动态生成类或从加密文件中提取类</li><li>用户定义的类加载器需要由bootstrap class loader去加载</li><li>在Java1.8的核心类库中，提供了两个类加载器，分别是：<ol><li>扩展类加载器 extention class loader<br>  <code>sun.misc.Launcher.ExtClassLoader</code></li><li>应用类加载器 application class loader<br>  <code>sun.misc.Launcher.AppClassLoader</code></li></ol></li></ul><h4 id="2-1-2-1-扩展类加载器-extention-class-loader"><a href="#2-1-2-1-扩展类加载器-extention-class-loader" class="headerlink" title="2.1.2.1 扩展类加载器 extention class loader"></a>2.1.2.1 扩展类加载器 extention class loader</h4><ul><li>扩展类加载器的父是启动类加载器（bootstrap class loader)</li><li>负责加载相对次要、但又通用的类，如JRE的lib&#x2F;ext目录下jar包中的类（以及java.ext.dirs指定的类, 这个可以通过查看<code>sun.misc.Launcher.ExtClassLoader.getExtDirs()</code>方法确认）</li></ul><h4 id="2-1-2-2-应用类加载器-application-class-loader"><a href="#2-1-2-2-应用类加载器-application-class-loader" class="headerlink" title="2.1.2.2 应用类加载器 application class loader"></a>2.1.2.2 应用类加载器 application class loader</h4><ul><li>应用类加载器的父则是扩展类加载器</li><li>负责加载应用程序路径下的类（应用程序指虚拟机参数-cp&#x2F;-classpath、系统变量java.class.path或环境变量CLASSPATH所指定的路径。这个可以通过查看<code>sun.misc.Launcher.AppClassLoader</code>确认）</li><li>默认情况下，应用程序中包含的类便是通过应用类加载器加载的。</li></ul><h3 id="2-1-3-双亲委派模型"><a href="#2-1-3-双亲委派模型" class="headerlink" title="2.1.3 双亲委派模型"></a>2.1.3 双亲委派模型</h3><ul><li><p>指的是一个类加载器接收到加载请求时，会先将请求转发给父类加载器，在父类加载器没有找到所请求的类的情况下，该类加载器才会去尝试加载。</p></li><li><p>双亲委派模型可以避免类的重复加载，以及java的核心api被篡改的问题。</p></li></ul><h1 id="3-链接-Linking"><a href="#3-链接-Linking" class="headerlink" title="3 链接 Linking"></a>3 链接 Linking</h1><ul><li>指将创建的类合并至Java虚拟机中，使之能够执行的过程。可分为验证(Verification)、准备(Preparation)以及解析(Resolution)三个阶段</li></ul><h2 id="3-1-验证-Verification"><a href="#3-1-验证-Verification" class="headerlink" title="3.1 验证 Verification"></a>3.1 验证 Verification</h2><p>验证是为了确保被加载的类满足Java虚拟机的约束条件。</p><h2 id="3-2-准备-Preparation"><a href="#3-2-准备-Preparation" class="headerlink" title="3.2 准备 Preparation"></a>3.2 准备 Preparation</h2><ul><li>准备是为被加载的类的静态字段分配内容。</li><li>构造其他跟类层次相关的数据结构：如用来实现虚方法的动态绑定的方法表。</li></ul><h2 id="3-3-解析-Resolution"><a href="#3-3-解析-Resolution" class="headerlink" title="3.3 解析 Resolution"></a>3.3 解析 Resolution</h2><blockquote><p>在开始解析之前，需要知道：<br>class文件被加载到Java虚拟机之前，这个类无法知道其他类及其方法、字段所对应的具体地址，甚至不知道自己方法、字段的地址。因此，每当需要引用这些成员时，Java编译器会生成一个符号引用。在运行阶段，这个符号引用一般都能无歧义地定位到具体目标上。</p></blockquote><ul><li>解析的目标是将符号引用解析成为实际应用： 如果符号引用指向一个未被加载的类，或者未被加载类的字段或方法，那么解析就触发这个类的加载。（但未必会出发这个类的链接和初始化）</li></ul><p>此外，在Java虚拟机规范中并没有要求在链接过程中完成解析。仅规定了：如果某些字节码使用了符号引用，那么在执行这些字节码之前，需要完成对这些符号引用的解析。</p><h1 id="4-初始化-Initialization"><a href="#4-初始化-Initialization" class="headerlink" title="4. 初始化 Initialization"></a>4. 初始化 Initialization</h1><ul><li>为标记为常量值的字段赋值，以及执行<code>&lt;clinit&gt;</code>方法的过程</li><li>Java虚拟机会通过加锁来确保类的<code>&lt;clinit&gt;</code>方法仅被执行一次</li></ul><blockquote><p>常量值解释：<br>Java代码中，如果要初始化一个静态字段，可以在声明时直接赋值，或者在静态代码块中对其进行赋值<br>如果直接赋值的静态字段被final所修饰，并且它的类型是基本类型或字符串时，该字段便会被Java编译器标记为常量值（ConstantValue）</p></blockquote><h2 id="4-1-初始化的触发条件"><a href="#4-1-初始化的触发条件" class="headerlink" title="4.1 初始化的触发条件"></a>4.1 初始化的触发条件</h2><p>在Java虚拟机规范中明确枚举了以下情况：</p><ul><li><p>The execution of any one of the Java Virtual Machine instructions new, getstatic, putstatic, or invokestatic that references C (§new, §getstatic, §putstatic, §invokestatic). These instructions reference a class or interface directly or indirectly through either a field reference or a method reference.</p></li><li><p>Upon execution of a new instruction, the referenced class is initialized if it has not been initialized already.</p></li><li><p>Upon execution of a getstatic, putstatic, or invokestatic instruction, the class or interface that declared the resolved field or method is initialized if it has not been initialized already.</p></li><li><p>The first invocation of a java.lang.invoke.MethodHandle instance which was the result of method handle resolution (§5.4.3.5) for a method handle of kind 2 (REF_getStatic), 4 (REF_putStatic), 6 (REF_invokeStatic), or 8 (REF_newInvokeSpecial).</p></li><li><p>This implies that the class of a bootstrap method is initialized when the bootstrap method is invoked for an invokedynamic instruction (§invokedynamic), as part of the continuing resolution of the call site specifier.</p></li><li><p>Invocation of certain reflective methods in the class library (§2.12), for example, in class Class or in package java.lang.reflect.</p></li><li><p>If C is a class, the initialization of one of its subclasses.</p></li><li><p>If C is an interface that declares a non-abstract, non-static method, the initialization of a class that implements C directly or indirectly.</p></li><li><p>If C is a class, its designation as the initial class at Java Virtual Machine startup (§5.2).</p></li></ul><p>Java虚拟机规范中有部分是依赖于Java虚拟机指令了，我对此了解并不多，以下摘抄于《极客时间-深入拆解Java虚拟机-郑雨迪》的分享，相较而言更通俗易懂些。</p><ol><li>当虚拟机启动时，初始化用户指定的主类；</li><li>当遇到用以新建目标类实例的new指令时，初始化new指令的目标类；</li><li>当遇到调用静态方法的指令时，初始化该静态方法所在的类；</li><li>当遇到访问静态字段的指令时，初始化该静态字段所在的类；</li><li>子类的初始化会触发父类的初始化；</li><li>如果一个接口定义了default方法，那么直接实现或者间接实现该接口的类的初始化，会触发该接口的初始化；</li><li>使用反射API对某个类进行反射调用时，初始化这个类；</li><li>当初次调用MethodHandle实例时，初始化该MethodHandle指向的方法所在的类。</li></ol><h1 id="5-绑定本机方法实现"><a href="#5-绑定本机方法实现" class="headerlink" title="5. 绑定本机方法实现"></a>5. 绑定本机方法实现</h1><p>指的是将Java编程语言以为的其他语言编写的功能和实现native方法的功能集成到Java虚拟机中以便可以执行的过程。</p><p>传统上来说，此过程可称为链接，但Java虚拟机规范中指出，使用绑定是为了避免于Java虚拟机对类和接口的链接产生混淆。</p><h1 id="6-Java虚拟机退出"><a href="#6-Java虚拟机退出" class="headerlink" title="6. Java虚拟机退出"></a>6. Java虚拟机退出</h1><p>当调用<code>Runtime.exit()</code>、<code>Runtime.halt()</code>、<code>System.exit</code>，并且<code>SecurityManager</code>安全管理其允许exit或halt时候，Java虚拟机就会被关闭。</p><p>同时，JNI（Java Native Interface）规范描述了对于JNI调用相关的java虚拟机的终止信息。</p><h1 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h1><p><a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html">https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html</a><br><a href="https://time.geekbang.org/column/article/11523">https://time.geekbang.org/column/article/11523</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>HashMap源码实现解析</title>
      <link>https://baofeidyz.com/2020/135d626f9fdb/</link>
      <description>
        <![CDATA[<h1 id="HashMap源码实现解析"><a href="#HashMap源码实现解析" class="headerlink" title="HashMap源码实现解析"></a>HashMap源码实现解析</h1><figure class="highlight]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/Java%E4%B8%AD%E7%BA%A7/">Java中级</category>
      <pubDate>Sat, 08 Aug 2020 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="HashMap源码实现解析"><a href="#HashMap源码实现解析" class="headerlink" title="HashMap源码实现解析"></a>HashMap源码实现解析</h1><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></pre></td><td class="code"><pre><span class="line">java version &quot;1.8.0_251&quot;</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_251-b08)</span><br><span class="line">Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)</span><br></pre></td></tr></table></figure><p>基于数组+链表实现，通过<code>&amp;</code>与运算，计算数组下标。在JDK8中，加入红黑树实现，使其时间复杂度保持在O(1)到O(logn)</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The table, initialized on first use, and resized as</span></span><br><span class="line"><span class="comment"> * necessary. When allocated, length is always a power of two.</span></span><br><span class="line"><span class="comment"> * (We also tolerate length zero in some operations to allow</span></span><br><span class="line"><span class="comment"> * bootstrapping mechanics that are currently not needed.)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">transient</span> Node&lt;K,V&gt;[] table;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Node</span>&lt;K,V&gt; <span class="keyword">implements</span> <span class="title class_">Map</span>.Entry&lt;K,V&gt; &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> hash;</span><br><span class="line">    <span class="keyword">final</span> K key;</span><br><span class="line">    V value;</span><br><span class="line">    Node&lt;K,V&gt; next;</span><br><span class="line"></span><br><span class="line">    Node(<span class="type">int</span> hash, K key, V value, Node&lt;K,V&gt; next) &#123;</span><br><span class="line">        <span class="built_in">this</span>.hash = hash;</span><br><span class="line">        <span class="built_in">this</span>.key = key;</span><br><span class="line">        <span class="built_in">this</span>.value = value;</span><br><span class="line">        <span class="built_in">this</span>.next = next;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> K <span class="title function_">getKey</span><span class="params">()</span>        &#123; <span class="keyword">return</span> key; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> V <span class="title function_">getValue</span><span class="params">()</span>      &#123; <span class="keyword">return</span> value; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> String <span class="title function_">toString</span><span class="params">()</span> &#123; <span class="keyword">return</span> key + <span class="string">&quot;=&quot;</span> + value; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects.hashCode(key) ^ Objects.hashCode(value);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> V <span class="title function_">setValue</span><span class="params">(V newValue)</span> &#123;</span><br><span class="line">        <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> value;</span><br><span class="line">        value = newValue;</span><br><span class="line">        <span class="keyword">return</span> oldValue;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (o == <span class="built_in">this</span>)</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (o <span class="keyword">instanceof</span> Map.Entry) &#123;</span><br><span class="line">            Map.Entry&lt;?,?&gt; e = (Map.Entry&lt;?,?&gt;)o;</span><br><span class="line">            <span class="keyword">if</span> (Objects.equals(key, e.getKey()) &amp;&amp;</span><br><span class="line">                Objects.equals(value, e.getValue()))</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>HashMap 静态内部类Node，实现链表，通过Node[]这个数组属性存放所有的节点。</p><h2 id="put-K-V"><a href="#put-K-V" class="headerlink" title="put(K,V)"></a>put(K,V)</h2><p>应该直接看<code>final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">boolean</span> onlyIfAbsent, <span class="type">boolean</span> evict)</span> &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; <span class="type">int</span> n, i;</span><br><span class="line">    <span class="keyword">if</span> ((tab = table) == <span class="literal">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line">        n = (tab = resize()).length;</span><br><span class="line">    <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) &amp; hash]) == <span class="literal">null</span>)</span><br><span class="line">        tab[i] = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        Node&lt;K,V&gt; e; K k;</span><br><span class="line">        <span class="keyword">if</span> (p.hash == hash &amp;&amp;</span><br><span class="line">            ((k = p.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">            e = p;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line">            e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(<span class="built_in">this</span>, tab, hash, key, value);</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">binCount</span> <span class="operator">=</span> <span class="number">0</span>; ; ++binCount) &#123;</span><br><span class="line">                <span class="keyword">if</span> ((e = p.next) == <span class="literal">null</span>) &#123;</span><br><span class="line">                    p.next = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line">                    <span class="keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></span><br><span class="line">                        treeifyBin(tab, hash);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                    ((k = e.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                p = e;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (e != <span class="literal">null</span>) &#123; <span class="comment">// existing mapping for key</span></span><br><span class="line">            <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e.value;</span><br><span class="line">            <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="literal">null</span>)</span><br><span class="line">                e.value = value;</span><br><span class="line">            afterNodeAccess(e);</span><br><span class="line">            <span class="keyword">return</span> oldValue;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ++modCount;</span><br><span class="line">    <span class="keyword">if</span> (++size &gt; threshold)</span><br><span class="line">        resize();</span><br><span class="line">    afterNodeInsertion(evict);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果当前想要存放的这个节点的hash值暂时没有存在的节点，则直接在数组中添加。</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"><span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) &amp; hash]) == <span class="literal">null</span>)</span><br><span class="line">            tab[i] = newNode(hash, key, value, <span class="literal">null</span>);</span><br></pre></td></tr></table></figure><p>通过<code>&amp;</code>与运算，</p><p>如果当前节点的hash值存在了，则在这个节点下增加链表。</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="keyword">else</span> &#123;</span><br><span class="line">    Node&lt;K,V&gt; e; K k;</span><br><span class="line">    <span class="keyword">if</span> (p.hash == hash &amp;&amp;</span><br><span class="line">        ((k = p.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">        e = p;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line">        e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(<span class="built_in">this</span>, tab, hash, key, value);</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">binCount</span> <span class="operator">=</span> <span class="number">0</span>; ; ++binCount) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((e = p.next) == <span class="literal">null</span>) &#123;</span><br><span class="line">                p.next = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line">                <span class="keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></span><br><span class="line">                    treeifyBin(tab, hash);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                ((k = e.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            p = e;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (e != <span class="literal">null</span>) &#123; <span class="comment">// existing mapping for key</span></span><br><span class="line">        <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e.value;</span><br><span class="line">        <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="literal">null</span>)</span><br><span class="line">            e.value = value;</span><br><span class="line">        afterNodeAccess(e);</span><br><span class="line">        <span class="keyword">return</span> oldValue;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从JDK8 开始，当链表中的子节点超过八个时，将转为红黑树。关于红黑树的数据结构特点，我现在也不是特别的理解，先给自己挖个坑，改天填。</p><h3 id="红黑树"><a href="#红黑树" class="headerlink" title="红黑树"></a>红黑树</h3><h3 id="HashMap扩容"><a href="#HashMap扩容" class="headerlink" title="HashMap扩容"></a>HashMap扩容</h3><p>HashMap中有一个属性：<code>threshold</code> ，这个主要是根据阀值和当前HashMap的大小计算而来，可通过查看</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="title function_">HashMap</span><span class="params">(<span class="type">int</span> initialCapacity, <span class="type">float</span> loadFactor)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (initialCapacity &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Illegal initial capacity: &quot;</span> +</span><br><span class="line">                                           initialCapacity);</span><br><span class="line">    <span class="keyword">if</span> (initialCapacity &gt; MAXIMUM_CAPACITY)</span><br><span class="line">        initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line">    <span class="keyword">if</span> (loadFactor &lt;= <span class="number">0</span> || Float.isNaN(loadFactor))</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Illegal load factor: &quot;</span> +</span><br><span class="line">                                           loadFactor);</span><br><span class="line">    <span class="built_in">this</span>.loadFactor = loadFactor;</span><br><span class="line">    <span class="built_in">this</span>.threshold = tableSizeFor(initialCapacity);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在初始化HashMap的最后，会根据当前的阀值和实际的大小进行计算<code>threshold</code>的值，同时在每一次操作元素的时候，都会去比较当前HashMap的实际大小与<code>threshold</code>的值，如果当前实际大小已经大于了这个限定的阀值，此时将会对HashMap进行扩容。</p><p><code>resize()</code>方法主要是两个步骤：</p><ol><li>计算大小；</li><li>将原HashMap中的元素进行移动</li></ol><blockquote><p>挖坑，以后填</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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">oldCap</span> <span class="operator">=</span> (oldTab == <span class="literal">null</span>) ? <span class="number">0</span> : oldTab.length;</span><br><span class="line"><span class="type">int</span> <span class="variable">oldThr</span> <span class="operator">=</span> threshold;</span><br><span class="line"><span class="type">int</span> newCap, newThr = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span> (oldCap &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (oldCap &gt;= MAXIMUM_CAPACITY) &#123;</span><br><span class="line">        threshold = Integer.MAX_VALUE;</span><br><span class="line">        <span class="keyword">return</span> oldTab;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> ((newCap = oldCap &lt;&lt; <span class="number">1</span>) &lt; MAXIMUM_CAPACITY &amp;&amp;</span><br><span class="line">             oldCap &gt;= DEFAULT_INITIAL_CAPACITY)</span><br><span class="line">        newThr = oldThr &lt;&lt; <span class="number">1</span>; <span class="comment">// double threshold</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (oldThr &gt; <span class="number">0</span>) <span class="comment">// initial capacity was placed in threshold</span></span><br><span class="line">    newCap = oldThr;</span><br><span class="line"><span class="keyword">else</span> &#123;               <span class="comment">// zero initial threshold signifies using defaults</span></span><br><span class="line">    newCap = DEFAULT_INITIAL_CAPACITY;</span><br><span class="line">    newThr = (<span class="type">int</span>)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (newThr == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="type">float</span> <span class="variable">ft</span> <span class="operator">=</span> (<span class="type">float</span>)newCap * loadFactor;</span><br><span class="line">    newThr = (newCap &lt; MAXIMUM_CAPACITY &amp;&amp; ft &lt; (<span class="type">float</span>)MAXIMUM_CAPACITY ?</span><br><span class="line">              (<span class="type">int</span>)ft : Integer.MAX_VALUE);</span><br><span class="line">&#125;</span><br><span class="line">threshold = newThr;</span><br></pre></td></tr></table></figure><h2 id="get-Object-key"><a href="#get-Object-key" class="headerlink" title="get(Object key)"></a>get(Object key)</h2><p>get方法就更好理解了，首先还是通过hash值找到数组下标，然后通过数组下标获取的实际的元素。然后判断一下当前节点key的hash值是否与第一个节点相同，相同则直接返回结果。</p><p>如果不同，这个时候，就得看第一个节点后的下一个节点是采用的红黑树还是使用的链表。然后再根据key的hash去取值即可。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> Node&lt;K,V&gt; <span class="title function_">getNode</span><span class="params">(<span class="type">int</span> hash, Object key)</span> &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; first, e; <span class="type">int</span> n; K k;</span><br><span class="line">    <span class="keyword">if</span> ((tab = table) != <span class="literal">null</span> &amp;&amp; (n = tab.length) &gt; <span class="number">0</span> &amp;&amp;</span><br><span class="line">        (first = tab[(n - <span class="number">1</span>) &amp; hash]) != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (first.hash == hash &amp;&amp; <span class="comment">// always check first node</span></span><br><span class="line">            ((k = first.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">            <span class="keyword">return</span> first;</span><br><span class="line">        <span class="keyword">if</span> ((e = first.next) != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (first <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line">                <span class="keyword">return</span> ((TreeNode&lt;K,V&gt;)first).getTreeNode(hash, key);</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                    ((k = e.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">                    <span class="keyword">return</span> e;</span><br><span class="line">            &#125; <span class="keyword">while</span> ((e = e.next) != <span class="literal">null</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>ArrayList源码解析</title>
      <link>https://baofeidyz.com/2020/ca30dff3752f/</link>
      <description>
        <![CDATA[<h1 id="ArrayList源码解析"><a href="#ArrayList源码解析" class="headerlink" title="ArrayList源码解析"></a>ArrayList源码解析</h1><figure class="highlight]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <pubDate>Wed, 05 Aug 2020 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="ArrayList源码解析"><a href="#ArrayList源码解析" class="headerlink" title="ArrayList源码解析"></a>ArrayList源码解析</h1><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></pre></td><td class="code"><pre><span class="line">java version &quot;1.8.0_251&quot;</span><br><span class="line">Java(TM) SE Runtime Environment (build 1.8.0_251-b08)</span><br><span class="line">Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)</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"><span class="keyword">transient</span> Object[] elementData;</span><br></pre></td></tr></table></figure><h2 id="add-E-e"><a href="#add-E-e" class="headerlink" title="add(E e)"></a>add(E e)</h2><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="comment">/**</span></span><br><span class="line"><span class="comment">  * Appends the specified element to the end of this list.</span></span><br><span class="line"><span class="comment">  *</span></span><br><span class="line"><span class="comment">  * <span class="doctag">@param</span> e element to be appended to this list</span></span><br><span class="line"><span class="comment">  * <span class="doctag">@return</span> &lt;tt&gt;true&lt;/tt&gt; (as specified by &#123;<span class="doctag">@link</span> Collection#add&#125;)</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line">    ensureCapacityInternal(size + <span class="number">1</span>);  <span class="comment">// Increments modCount!!</span></span><br><span class="line">    elementData[size++] = e;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</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">void</span> <span class="title function_">ensureCapacityInternal</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line">    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">ensureExplicitCapacity</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line">    modCount++;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// overflow-conscious code</span></span><br><span class="line">    <span class="keyword">if</span> (minCapacity - elementData.length &gt; <span class="number">0</span>)</span><br><span class="line">        grow(minCapacity);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>size</code>是当前数组实际使用的大小，如果当前所需要的数组地址已经大于了当前数组的容量，则对数组进行扩容操作，即调用<code>grow</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">private</span> <span class="keyword">void</span> <span class="title function_">grow</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line">    <span class="comment">// overflow-conscious code</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">oldCapacity</span> <span class="operator">=</span> elementData.length;</span><br><span class="line">    <span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> oldCapacity + (oldCapacity &gt;&gt; <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (newCapacity - minCapacity &lt; <span class="number">0</span>)</span><br><span class="line">        newCapacity = minCapacity;</span><br><span class="line">    <span class="keyword">if</span> (newCapacity - MAX_ARRAY_SIZE &gt; <span class="number">0</span>)</span><br><span class="line">        newCapacity = hugeCapacity(minCapacity);</span><br><span class="line">    <span class="comment">// minCapacity is usually close to size, so this is a win:</span></span><br><span class="line">    elementData = Arrays.copyOf(elementData, newCapacity);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>得到所需要扩容的大小以后，调用<code>navite</code>方法对集合进行扩容。<br>扩容结束以后，将<code>size</code>（代表着实际占用的变量）进行自增，同时将这个数组下标进行赋值。</p><h2 id="remove-Object-o"><a href="#remove-Object-o" class="headerlink" title="remove(Object o)"></a>remove(Object o)</h2><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="type">boolean</span> <span class="title function_">remove</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (o == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> <span class="number">0</span>; index &lt; size; index++)</span><br><span class="line">            <span class="keyword">if</span> (elementData[index] == <span class="literal">null</span>) &#123;</span><br><span class="line">                fastRemove(index);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> <span class="number">0</span>; index &lt; size; index++)</span><br><span class="line">            <span class="keyword">if</span> (o.equals(elementData[index])) &#123;</span><br><span class="line">                fastRemove(index);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>remove</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="comment">/*</span></span><br><span class="line"><span class="comment">  * Private remove method that skips bounds checking and does not</span></span><br><span class="line"><span class="comment">  * return the value removed.</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">fastRemove</span><span class="params">(<span class="type">int</span> index)</span> &#123;</span><br><span class="line">    modCount++;</span><br><span class="line">    <span class="type">int</span> <span class="variable">numMoved</span> <span class="operator">=</span> size - index - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">if</span> (numMoved &gt; <span class="number">0</span>)</span><br><span class="line">        System.arraycopy(elementData, index+<span class="number">1</span>, elementData, index,</span><br><span class="line">                          numMoved);</span><br><span class="line">    elementData[--size] = <span class="literal">null</span>; <span class="comment">// clear to let GC do its work</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>定位到具体需要删除的数组下标以后，将下标后的数据往前移动，并将最后一个元素设为<code>null</code>，便于GC回收内存。</p><h2 id="迭代器"><a href="#迭代器" class="headerlink" title="迭代器"></a>迭代器</h2><p>私有内部类，通过“游标”去操作数组</p><h2 id="为什么不能在循环里面增加或删除元素"><a href="#为什么不能在循环里面增加或删除元素" class="headerlink" title="为什么不能在循环里面增加或删除元素"></a>为什么不能在循环里面增加或删除元素</h2><h3 id="foreach"><a href="#foreach" class="headerlink" title="foreach"></a>foreach</h3><p>foreach本质上就是迭代器的实现，但在删除的时候，使用的是集合的<code>remove</code>方法，而不是迭代器提供的<code>remove</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line"><span class="keyword">public</span> E <span class="title function_">next</span><span class="params">()</span> &#123;</span><br><span class="line">    checkForComodification();</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> cursor;</span><br><span class="line">    <span class="keyword">if</span> (i &gt;= size)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NoSuchElementException</span>();</span><br><span class="line">    Object[] elementData = ArrayList.<span class="built_in">this</span>.elementData;</span><br><span class="line">    <span class="keyword">if</span> (i &gt;= elementData.length)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ConcurrentModificationException</span>();</span><br><span class="line">    cursor = i + <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">return</span> (E) elementData[lastRet = i];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// modCount是ArrayList中的属性值，是集合添加元素、删除元素的次数，expectedModCount是迭代器中的属性值，是预期的修改次数。实际修改值与期望值不同</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">checkForComodification</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (modCount != expectedModCount)</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ConcurrentModificationException</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="for循环"><a href="#for循环" class="headerlink" title="for循环"></a>for循环</h3><p>for循环本质上是使用数组下标遍历数组，通过前文中提到的<code>remove(Object o)</code>方法的实现，可以了解到，实际上是将需要删除的元素后的数组向前移动，并将最后一个元素设为null，便于GC回收。</p><p>那么当我们使用for循环操作数组，并对其进行删除操作的时候</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"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; list.size(); i++)&#123;</span><br><span class="line">  <span class="keyword">if</span>(list[i] == <span class="string">&#x27;xxx&#x27;</span>)&#123;</span><br><span class="line">    list.remove(i);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>假设数组中，第X个元素满足条件，并将其进行删除。 此时X后的数据元素全部向前移动，那么第X个元素，已经是移动前X+1，如果此时i++自增，那么你取到的是未移动前的X+2个元素。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; list.size(); i++)&#123;</span><br><span class="line">  <span class="keyword">if</span>(list[i] == <span class="string">&#x27;xxx&#x27;</span>)&#123;</span><br><span class="line">    list.remove(i);</span><br><span class="line">    i--;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>部分参考： <a href="https://blog.csdn.net/wangjun5159/article/details/61415358">https://blog.csdn.net/wangjun5159/article/details/61415358</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>解决私有证书导致Maven无法更新的问题</title>
      <link>https://baofeidyz.com/2020/0b6497dc2639/</link>
      <description>
        <![CDATA[<h1 id="解决私有证书导致Maven无法更新的问题"><a href="#解决私有证书导致Maven无法更新的问题" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/%E8%B8%A9%E5%9D%91/">踩坑</category>
      <category domain="https://baofeidyz.com/tags/Maven/">Maven</category>
      <pubDate>Sun, 05 Jul 2020 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="解决私有证书导致Maven无法更新的问题"><a href="#解决私有证书导致Maven无法更新的问题" class="headerlink" title="解决私有证书导致Maven无法更新的问题"></a>解决私有证书导致Maven无法更新的问题</h1><p>最近公司更换了maven的私有化仓库，一般来说，其实也没啥大问题，就是修改一下setting文件就好了。但麻烦的是，他们搞了一个自签的证书，强制使用了https。</p><h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h2><h2 id="全局代理"><a href="#全局代理" class="headerlink" title="全局代理"></a>全局代理</h2><p>这个属于自己把自己坑了，怪自己科学上网管理不当。</p><ol><li><p>终端检查是否有全局代理</p><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></pre></td><td class="code"><pre><span class="line">echo $http_proxy</span><br><span class="line">echo $https_proxy</span><br><span class="line">echo $all_proxy</span><br></pre></td></tr></table></figure></li><li><p>检查IDEA代理设置</p><p>Preferences-&gt;Appearance &amp; Behaivor-&gt;System Settings-&gt;HTTP Proxy</p></li><li><p>You have JVM property https.proxyHost set</p></li></ol><p>   在IDEA 的 HTTP Proxy页面看到了这个警告，通过一番搜索，发现可以在IDEA的配置文件vmoptions里面加上-Djava.net.useSystemProxies&#x3D;true解决掉</p><h2 id="证书问题"><a href="#证书问题" class="headerlink" title="证书问题"></a>证书问题</h2><p>由于公司用的是自签的证书，所以还得配置自签的证书</p><p>其实最开始我是没有确认原因的，我是通过IDEA help-&gt;Show log in finder找到了IDEA的详细日志，才确认到因为证书问题导致无法更新的。</p><p>由于maven是依赖于java的，即便是我给macOS安装了根证书也无效，所以还得给java运行环境安装根证书才可以。</p><p>需要注意的是，idea默认启动maven的jdk环境可能并不是系统安装的jdk，需要到idea配置面板中确认一下即可。</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654188687563/ydGGTNEXR.png"></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>记因缓存返回引用对象导致的线程安全问题</title>
      <link>https://baofeidyz.com/2020/4f7ec9837351/</link>
      <description>
        <![CDATA[<h1 id="记因缓存返回引用对象导致的线程安全问题"><a href="#记因缓存返回引用对象导致的线程安全问题" class="headerlink" title="记因缓存返回引用对象导致的线程安全问题"></a>记因缓存返回引用对象导致的线程安全问题</h1><h2]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/%E8%B8%A9%E5%9D%91/">踩坑</category>
      <category domain="https://baofeidyz.com/tags/%E6%95%85%E9%9A%9C%E6%8E%92%E6%9F%A5%E8%AE%B0%E5%BD%95/">故障排查记录</category>
      <pubDate>Thu, 11 Jun 2020 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="记因缓存返回引用对象导致的线程安全问题"><a href="#记因缓存返回引用对象导致的线程安全问题" class="headerlink" title="记因缓存返回引用对象导致的线程安全问题"></a>记因缓存返回引用对象导致的线程安全问题</h1><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>前几天处理生产环境问题的时候，遇到一个因为缓存写的不太好，直接返回了引用对象，导致的线程安全问题。</p><h2 id="日志信息"><a href="#日志信息" class="headerlink" title="日志信息"></a>日志信息</h2><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></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">06</span>-<span class="number">01</span> <span class="number">14</span>:<span class="number">37</span>:<span class="number">49</span> [ERROR] [task-<span class="number">3</span>] xxx -失败！</span><br><span class="line">java.util.ConcurrentModificationException: <span class="literal">null</span></span><br><span class="line">  at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:<span class="number">859</span>) ~[na:<span class="number">1.7</span><span class="number">.0_80</span>]</span><br><span class="line">  at java.util.ArrayList$Itr.next(ArrayList.java:<span class="number">831</span>) ~[na:<span class="number">1.7</span><span class="number">.0_80</span>]</span><br><span class="line"><span class="number">2020</span>-<span class="number">06</span>-<span class="number">01</span> <span class="number">14</span>:<span class="number">37</span>:<span class="number">49</span> [ERROR] [qtp1073067421-<span class="number">33398</span>] xxx -失败！</span><br><span class="line">java.util.ConcurrentModificationException: <span class="literal">null</span></span><br><span class="line">  at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:<span class="number">859</span>) ~[na:<span class="number">1.7</span><span class="number">.0_80</span>]</span><br><span class="line">  at java.util.ArrayList$Itr.next(ArrayList.java:<span class="number">831</span>) ~[na:<span class="number">1.7</span><span class="number">.0_80</span>]</span><br></pre></td></tr></table></figure><h2 id="排查过程"><a href="#排查过程" class="headerlink" title="排查过程"></a>排查过程</h2><p>两个线程同时抛出一个异常点，在被我删掉的日志中显示这两个线程正在执行同一行代码。</p><p>两个线程执行的同一行代码其实是一个<code>foreach</code>遍历，且经我检查，并没有对集合中的对象进行<code>remove</code>的操作。</p><p>继续排查发现，这个<code>foreach</code>操作的对象是从一个缓存中获取到的，于是顺着这个缓存开始继续排查。跟着代码调用逻辑发现，有直接往这个缓存返回的list中做add的操作。</p><p>检查后发现这个我们系统自己使用ConcurrentHashMap封装的缓存直接返回了引用对象<code>ArrayList</code>，且可读可写。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>最后其实结论就是缓存使用不当，导致多线程操作ArrayList对象，一边遍历一边插入新的元素，导致迭代器在做check的时候抛出了一场，出现了线程安全的问题。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><ol><li>返回结果的时候拷贝，不要直接返回引用对象</li><li>使用线程安全的List实现</li><li>还有啥？记一个TODO</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>Java线程安全初识</title>
      <link>https://baofeidyz.com/2020/dc3b4841ef39/</link>
      <description>
        <![CDATA[<h1 id="线程安全的概念"><a href="#线程安全的概念" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/Java%E4%B8%AD%E7%BA%A7/">Java中级</category>
      <pubDate>Fri, 29 May 2020 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="线程安全的概念"><a href="#线程安全的概念" class="headerlink" title="线程安全的概念"></a>线程安全的概念</h1><p>我的理解是由于程序使用多线程的方式运行，导致程序无法正确的得出我们期望的结果。</p><p>什么会导致线程安全问题，主要是可见性、原子性、有序性问题。详细可见：<a href="https://leanote.baofeidyz.com/blog/post/baofeidyz/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B-%E5%8F%AF%E8%A7%81%E6%80%A7%E3%80%81%E5%8E%9F%E5%AD%90%E6%80%A7%E3%80%81%E6%9C%89%E5%BA%8F%E6%80%A7%E9%97%AE%E9%A2%98%E5%BC%95%E5%85%A5">Java并发编程-可见性、原子性、有序性问题引入</a></p><h1 id="线程安全的实现方式"><a href="#线程安全的实现方式" class="headerlink" title="线程安全的实现方式"></a>线程安全的实现方式</h1><h2 id="可见性和有序性问题"><a href="#可见性和有序性问题" class="headerlink" title="可见性和有序性问题"></a>可见性和有序性问题</h2><p>主要是通过volatile、Happens-Before规则以及final关键字解决。详细可见<a href="https://leanote.baofeidyz.com/blog/post/baofeidyz/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B-%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%8F%AF%E8%A7%81%E6%95%88%E3%80%81%E5%8E%9F%E5%AD%90%E6%80%A7%E3%80%81%E6%9C%89%E5%BA%8F%E6%80%A7%E9%97%AE%E9%A2%98">Java并发编程-如何解决可见性和有序性问题</a></p><h2 id="原子性问题"><a href="#原子性问题" class="headerlink" title="原子性问题"></a>原子性问题</h2><h3 id="互斥锁"><a href="#互斥锁" class="headerlink" title="互斥锁"></a>互斥锁</h3><h4 id="sychronized"><a href="#sychronized" class="headerlink" title="sychronized"></a>sychronized</h4><h1 id="死锁问题"><a href="#死锁问题" class="headerlink" title="死锁问题"></a>死锁问题</h1><p>产生死锁需要同时满足四个条件，即：</p><ol><li>互斥：共享资源X和Y只能被一个线程占用</li><li>占有且等待：线程1已经取得共享资源x，在等待共享资源y时，不释放共享资源x</li><li>不可抢占：其他线程不可抢占线程1占有的资源</li><li>循环等待：线程1等待线程2占有的资源，线程2等待线程1占有的资源</li></ol><p>解决死锁问题，就是解决上面四个条件的任一一个。</p><ol><li>对于“占用且等待”条件，我们可一次性申请所有资源。</li><li>对于“不可抢占”条件，占用部分资源的线程进一步申请其他资源时，如果申请不到，可主动释放它占有的资源。</li><li>对于“循环等待”条件，按序申请，即资源是有线性顺序的，申请的时候可以先申请资源序号小的，再申请序号大的。</li></ol><blockquote><p>针对于“不可抢占”条件，sychronized申请不到资源时会进入阻塞状态，无法释放已占有的资源，我们应使用JUC提供的Lock解决</p></blockquote><h1 id="sychronized关键字的使用场景、作用范围"><a href="#sychronized关键字的使用场景、作用范围" class="headerlink" title="sychronized关键字的使用场景、作用范围"></a>sychronized关键字的使用场景、作用范围</h1><h2 id="实现方法"><a href="#实现方法" class="headerlink" title="实现方法"></a>实现方法</h2><p>利用Monitor，在使用sychronized关键字修饰的代码块，编译后自动生成相关加锁和解锁的代码，但仅支持一个条件变量，通过monitorenter和monitorexit实现。</p><h2 id="修饰非静态方法"><a href="#修饰非静态方法" class="headerlink" title="修饰非静态方法"></a>修饰非静态方法</h2><p>默认对当前实例对象<code>this</code>加锁</p><h2 id="修饰静态方法"><a href="#修饰静态方法" class="headerlink" title="修饰静态方法"></a>修饰静态方法</h2><p>默认对当前类的Class对象加锁</p><h1 id="管程Monitor"><a href="#管程Monitor" class="headerlink" title="管程Monitor"></a>管程Monitor</h1><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160736.png" alt="极客时间-Java并发编程实战-javaMESA管程模型"></p><h1 id="为什么局部变量是线程安全的？"><a href="#为什么局部变量是线程安全的？" class="headerlink" title="为什么局部变量是线程安全的？"></a>为什么局部变量是线程安全的？</h1><h2 id="调用栈和栈帧"><a href="#调用栈和栈帧" class="headerlink" title="调用栈和栈帧"></a>调用栈和栈帧</h2><p>CPU支持栈结构，这个栈与方法调用相关，被称为调用栈。<br>每个方法在调用栈中都有自己的独立空间，被称为栈帧，每个栈帧都有对应方法需要的参数和返回地址。</p><p>当调用方法时，会创建新的栈帧，并压入调用栈；当方法返回时，对应的栈帧会自动弹出，即栈帧和方法是同生共死的。<br><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160757.png" alt="极客时间-Java并发编程实现-调用栈结构"></p><h2 id="局部变量存储位置"><a href="#局部变量存储位置" class="headerlink" title="局部变量存储位置"></a>局部变量存储位置</h2><p>局部变量放到了调用栈里，如下图所示：<br><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160818.png" alt="极客时间-Java并发编程实战-保护局部变量的调用栈结构"></p><h2 id="调用栈与线程"><a href="#调用栈与线程" class="headerlink" title="调用栈与线程"></a>调用栈与线程</h2><p>每个线程都有自己独立的调用栈<br><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160838.png" alt="极客时间-Java并发编程实战-线程和调用栈的关系图"></p><h1 id="JUC中锁的分类和用途"><a href="#JUC中锁的分类和用途" class="headerlink" title="JUC中锁的分类和用途"></a>JUC中锁的分类和用途</h1><h2 id="公平锁、非公平锁"><a href="#公平锁、非公平锁" class="headerlink" title="公平锁、非公平锁"></a>公平锁、非公平锁</h2><p>公平锁，多个线程按照申请锁的顺序来获取锁。会判断当前线程是否处于等待队列的头部，即链表的头部，如果是的话就直接获取锁。<br>非公平锁，没有顺序。不会判断当前线程处于等待队列的具体位置。CAS操作成功则认定为获取到锁。<br>synchronized是非公平锁，ReentrantLock可通过构造函数决定是公平锁还是非公平锁。</p><h2 id="可重入锁"><a href="#可重入锁" class="headerlink" title="可重入锁"></a>可重入锁</h2><p>线程可重复获取同一把锁。<br>ReentrantLock在获取锁时，判断当前线程是否是之前已获取锁的线程，如果是，则直接返回true表示锁获取成功。</p><h2 id="互斥锁（独享锁）、读写锁（共享锁）"><a href="#互斥锁（独享锁）、读写锁（共享锁）" class="headerlink" title="互斥锁（独享锁）、读写锁（共享锁）"></a>互斥锁（独享锁）、读写锁（共享锁）</h2><p>互斥锁（独享锁）指锁一次只能被一个线程持有，读写锁（共享锁）指该锁可被多个线程持有。<br>synchronized和ReentrantLock都是互斥锁（独享锁），ReadWriteLock的读锁是共享锁，写锁是独占锁。</p><h2 id="乐观锁、悲观锁"><a href="#乐观锁、悲观锁" class="headerlink" title="乐观锁、悲观锁"></a>乐观锁、悲观锁</h2><p>乐观锁在更新数据时会不断尝试更新，认为不加锁的并发操作是没问题的。基于CAS实现。<br>悲观锁认为对一个共享变量的并发操作，这个共享变量是一定会发生修改的，采取加锁方式。</p><p>乐观锁适合读操作远远大于写操作的情景，悲观锁适合写操作非常多的场景。</p><h2 id="分段锁"><a href="#分段锁" class="headerlink" title="分段锁"></a>分段锁</h2><p>对于ConcurrentHashMap来说，在put操作时，通过hashcode判断将要put的元素需要放到哪个分段，然后对分段进行加锁。当put操作不同的分段时，就可以实现并发操作。</p><h2 id="无锁、偏向锁、轻量级锁、重量级锁"><a href="#无锁、偏向锁、轻量级锁、重量级锁" class="headerlink" title="无锁、偏向锁、轻量级锁、重量级锁"></a>无锁、偏向锁、轻量级锁、重量级锁</h2><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160902.png"></p><p>自1.6以后，java对synchronized进行了优化，当第一个线程获得了锁，锁状态变更新为偏向锁状态。</p><h3 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a>偏向锁</h3><p>获取锁：当之前的线程再次获取锁时，无需再执行获取锁的过程。<br>锁撤销：原持有偏向锁的线程状态是非活动状态时，偏向锁撤销，锁状态更新为无锁状态。</p><h3 id="轻量级锁"><a href="#轻量级锁" class="headerlink" title="轻量级锁"></a>轻量级锁</h3><p>获取锁：如果每次申请锁的线程都是不相同的，则锁会升级为轻量级锁，指向栈中锁记录的指针。轻量级锁适用于线程交替执行同步块的场景。<br>释放锁：通过CAS操作，尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word，如果成功则完成解锁操作。如果失败则表明有其他线程获取该锁，此时锁膨胀为重量级锁。释放锁的同时，唤醒被挂起的线程。</p><h3 id="重量级"><a href="#重量级" class="headerlink" title="重量级"></a>重量级</h3><p>当多个线程同时竞争锁，则轻量级锁会膨胀为重量级锁。指向互斥量的指针。未获取到锁的线程会阻塞。</p><h2 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a>自旋锁</h2><p>循环检测锁标志位</p><h2 id="可中断锁"><a href="#可中断锁" class="headerlink" title="可中断锁"></a>可中断锁</h2><p>Lock</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 支持中断的 API</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">lockInterruptibly</span><span class="params">()</span> </span><br><span class="line">  <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"><span class="comment">// 支持超时的 API</span></span><br><span class="line"><span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">(<span class="type">long</span> time, TimeUnit unit)</span> </span><br><span class="line">  <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"><span class="comment">// 支持非阻塞获取锁的 API</span></span><br><span class="line"><span class="type">boolean</span> <span class="title function_">tryLock</span><span class="params">()</span>;</span><br></pre></td></tr></table></figure><h1 id="线程安全的集合"><a href="#线程安全的集合" class="headerlink" title="线程安全的集合"></a>线程安全的集合</h1><h2 id="List"><a href="#List" class="headerlink" title="List"></a>List</h2><h3 id="CopyOnWriteArrayList"><a href="#CopyOnWriteArrayList" class="headerlink" title="CopyOnWriteArrayList"></a>CopyOnWriteArrayList</h3><p>在写操作的时候，会将共享变量复制一份出来，当写操作完成以后，再修改共享变量的内存引用地址。<br>不能使用迭代器删除数据，因为操作的是一个副本，不会修改到实际的共享变量。</p><h2 id="Map"><a href="#Map" class="headerlink" title="Map"></a>Map</h2><h3 id="ConcurrentHashMap"><a href="#ConcurrentHashMap" class="headerlink" title="ConcurrentHashMap"></a>ConcurrentHashMap</h3><ul><li>KEY和VALUE不允许为空</li><li>KEY是无序的</li></ul><h3 id="ConcurrentSkipListMap"><a href="#ConcurrentSkipListMap" class="headerlink" title="ConcurrentSkipListMap"></a>ConcurrentSkipListMap</h3><ul><li>KEY和VALUE不允许为空</li><li>KEY是有序的</li><li>实现方案是使用SkipList（跳表）数据结构</li></ul><h2 id="Set"><a href="#Set" class="headerlink" title="Set"></a>Set</h2><h3 id="CopyOnWriteArraySet"><a href="#CopyOnWriteArraySet" class="headerlink" title="CopyOnWriteArraySet"></a>CopyOnWriteArraySet</h3><p>类似于<a href="#CopyOnWriteArrayList">CopyOnWriteArrayList</a></p><h3 id="ConcurrentSkipListSet"><a href="#ConcurrentSkipListSet" class="headerlink" title="ConcurrentSkipListSet"></a>ConcurrentSkipListSet</h3><p>类似于<a href="#ConcurrentSkipListMap">ConcurrentSkipListMap</a></p><h2 id="Queue"><a href="#Queue" class="headerlink" title="Queue"></a>Queue</h2><h3 id="单端阻塞队列"><a href="#单端阻塞队列" class="headerlink" title="单端阻塞队列"></a>单端阻塞队列</h3><h4 id="ArrayBlockingQueue"><a href="#ArrayBlockingQueue" class="headerlink" title="ArrayBlockingQueue"></a>ArrayBlockingQueue</h4><p>使用数组实现</p><h4 id="LinkedBlockingQueue"><a href="#LinkedBlockingQueue" class="headerlink" title="LinkedBlockingQueue"></a>LinkedBlockingQueue</h4><p>使用链表实现</p><h4 id="SynchronousQueue"><a href="#SynchronousQueue" class="headerlink" title="SynchronousQueue"></a>SynchronousQueue</h4><p>不持有队列，入队操作必须要等到消费者线程的出队操作</p><h4 id="LinkedTransferQueue"><a href="#LinkedTransferQueue" class="headerlink" title="LinkedTransferQueue"></a>LinkedTransferQueue</h4><p>链表实现，入队操作必须要等到消费者线程的出队操作</p><h4 id="PriorityBlockingQueue"><a href="#PriorityBlockingQueue" class="headerlink" title="PriorityBlockingQueue"></a>PriorityBlockingQueue</h4><p>支持按照优先级出队</p><h4 id="DelayQueue"><a href="#DelayQueue" class="headerlink" title="DelayQueue"></a>DelayQueue</h4><p>支持延时出队</p><h3 id="双端阻塞队列"><a href="#双端阻塞队列" class="headerlink" title="双端阻塞队列"></a>双端阻塞队列</h3><h4 id="LinkedBlockingDeque"><a href="#LinkedBlockingDeque" class="headerlink" title="LinkedBlockingDeque"></a>LinkedBlockingDeque</h4><h3 id="单端非阻塞队列"><a href="#单端非阻塞队列" class="headerlink" title="单端非阻塞队列"></a>单端非阻塞队列</h3><h4 id="ConcurrentLinkedQueue"><a href="#ConcurrentLinkedQueue" class="headerlink" title="ConcurrentLinkedQueue"></a>ConcurrentLinkedQueue</h4><h3 id="双端非阻塞队列"><a href="#双端非阻塞队列" class="headerlink" title="双端非阻塞队列"></a>双端非阻塞队列</h3><h4 id="ConcurrentLinkedDeque"><a href="#ConcurrentLinkedDeque" class="headerlink" title="ConcurrentLinkedDeque"></a>ConcurrentLinkedDeque</h4><h2 id="原子类"><a href="#原子类" class="headerlink" title="原子类"></a>原子类</h2><h3 id="原子化的基本数据类型"><a href="#原子化的基本数据类型" class="headerlink" title="原子化的基本数据类型"></a>原子化的基本数据类型</h3><h4 id="AtomicBoolean"><a href="#AtomicBoolean" class="headerlink" title="AtomicBoolean"></a>AtomicBoolean</h4><h4 id="AtomicInteger"><a href="#AtomicInteger" class="headerlink" title="AtomicInteger"></a>AtomicInteger</h4><h4 id="AtomicLong"><a href="#AtomicLong" class="headerlink" title="AtomicLong"></a>AtomicLong</h4><h3 id="原子化的对象引用类型"><a href="#原子化的对象引用类型" class="headerlink" title="原子化的对象引用类型"></a>原子化的对象引用类型</h3><h4 id="AtomicReference"><a href="#AtomicReference" class="headerlink" title="AtomicReference"></a>AtomicReference</h4><h4 id="AtomicStampedReference"><a href="#AtomicStampedReference" class="headerlink" title="AtomicStampedReference"></a>AtomicStampedReference</h4><h4 id="AtomicMarkableReference"><a href="#AtomicMarkableReference" class="headerlink" title="AtomicMarkableReference"></a>AtomicMarkableReference</h4><h1 id="用锁的最佳实践"><a href="#用锁的最佳实践" class="headerlink" title="用锁的最佳实践"></a>用锁的最佳实践</h1><ol><li>永远只在更新对象的成员变量时加锁</li><li>永远只在访问可变的成员变量时加锁</li><li>永远不在调用其他对象的方法时加锁</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>Gitlab Runner的安装与注册</title>
      <link>https://baofeidyz.com/2020/9f200d0fd6f4/</link>
      <description>
        <![CDATA[<h1 id="Gitlab-Runner的安装与注册"><a href="#Gitlab-Runner的安装与注册" class="headerlink" title="Gitlab Runner的安装与注册"></a>Gitlab Runner的安装与注册</h1><h2]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/Git/">Git</category>
      <category domain="https://baofeidyz.com/tags/Gitlab-CICD/">Gitlab-CICD</category>
      <pubDate>Thu, 28 May 2020 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="Gitlab-Runner的安装与注册"><a href="#Gitlab-Runner的安装与注册" class="headerlink" title="Gitlab Runner的安装与注册"></a>Gitlab Runner的安装与注册</h1><h2 id="1-访问项目的gitlab页面获取token"><a href="#1-访问项目的gitlab页面获取token" class="headerlink" title="1.访问项目的gitlab页面获取token"></a>1.访问项目的gitlab页面获取token</h2><blockquote><p><code>Settings</code> -&gt; <code>CI/CD</code> -&gt; <code>Runners</code></p></blockquote><p>获取到当前项目的key，如下图所示：</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654188769130/uH4gWoT3D.png"></p><p>此时我们就可以拿到对应的token了</p><h2 id="2-安装gitlab-runner"><a href="#2-安装gitlab-runner" class="headerlink" title="2. 安装gitlab runner"></a>2. 安装gitlab runner</h2><blockquote><p>官网安装教程：<a href="https://docs.gitlab.com/runner/install/">https://docs.gitlab.com/runner/install/</a></p></blockquote><p>建议是安装到CentOS服务器上</p><ol><li>添加源</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash</span><br></pre></td></tr></table></figure><ol start="2"><li>通过包管理工具安装</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo yum install gitlab-runner</span><br></pre></td></tr></table></figure><p>默认会安装最新的版本，如果需要安装特定版本则可以通过以下命令实现</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">yum list gitlab-runner --showduplicates | sort -r</span><br><span class="line">sudo yum install gitlab-runner-10.0.0-1</span><br></pre></td></tr></table></figure><h2 id="3-注册gitlab-runner"><a href="#3-注册gitlab-runner" class="headerlink" title="3. 注册gitlab runner"></a>3. 注册gitlab runner</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo gitlab-runner register</span><br></pre></td></tr></table></figure><p>需要输入项目所在的gitlab地址，类似于<code>https://gitlab.xxx.com</code><br>回车过后需要输入项目的token，也就是我们在第一步中获取到的<br>后面会要求输入一些描述，紧接着是<code>tag</code>，这个可以用于<code>.gitlab-ci.yml</code>文件中指定具体的<code>runner</code>，可以认为<code>tag</code>就是这个当前注册runner的身份ID。最后是选择执行器，初级选手，只会使用<code>shell</code>,其他相关的请看<a href="#EXECUTORS%E6%89%A7%E8%A1%8C%E5%99%A8%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D">EXECUTORS执行器简单介绍</a></p><p>另，一台服务器可以注册多个<code>gitlab runner</code></p><h1 id="EXECUTORS执行器简单介绍"><a href="#EXECUTORS执行器简单介绍" class="headerlink" title="EXECUTORS执行器简单介绍"></a>EXECUTORS执行器简单介绍</h1><table><thead><tr><th>执行者</th><th>描述</th></tr></thead><tbody><tr><td>shell</td><td>在本地运行构建，默认</td></tr><tr><td>docker</td><td>使用Docker容器运行构建 - 这需要在Runner运行的系统上安装[runners.docker]和Docker Engine</td></tr><tr><td>docker-ssh</td><td>运行生成使用泊坞容器，但用SSH连接到它-这需要的存在[runners.docker]，[runners.ssh]以及码头工人引擎的亚军运行的系统上安装。注意：这将在本地计算机上运行docker容器，它只是更改命令在该容器内运行的方式。如果要在外部计算机上运行docker命令，则应更改host该runners.docker部分中的参数。</td></tr><tr><td>ssh</td><td>使用SSH远程运行构建 - 这需要存在 [runners.ssh]</td></tr><tr><td>parallels</td><td>使用Parallels VM运行构建，但使用SSH连接到它 - 这需要存在[runners.parallels]和[runners.ssh]</td></tr><tr><td>virtualbox</td><td>使用VirtualBox VM运行构建，但使用SSH连接到它 - 这需要存在[runners.virtualbox]和[runners.ssh]</td></tr><tr><td>docker+machine</td><td>喜欢docker，但使用自动缩放的Docker机器 - 这需要存在[runners.docker]和[runners.machine]</td></tr><tr><td>docker-ssh+machine</td><td>喜欢docker-ssh，但使用自动缩放的Docker机器 - 这需要存在[runners.docker]和[runners.machine]</td></tr><tr><td>kubernetes</td><td>使用Kubernetes Pods运行构建 - 这需要存在 [runners.kubernetes]</td></tr></tbody></table><h2 id="shell执行器补充"><a href="#shell执行器补充" class="headerlink" title="shell执行器补充"></a>shell执行器补充</h2><p>shell执行器是根据当前gitlab runner所安装的操作系统来决定的</p><table><thead><tr><th>shell</th><th>描述</th></tr></thead><tbody><tr><td>bash</td><td>生成Bash（Bourne-shell）脚本。在Bash上下文中执行的所有命令（所有Unix系统的默认值）</td></tr><tr><td>sh</td><td>生成Sh（Bourne-shell）脚本。Sh上下文中执行的所有命令（适用bash于所有Unix系统的后备）</td></tr><tr><td>cmd</td><td>生成Windows批处理脚本。所有命令都在批处理上下文中执行（Windows的默认值）</td></tr><tr><td>powershell</td><td>生成Windows PowerShell脚本。所有命令都在PowerShell上下文中执行</td></tr></tbody></table><h1 id="gitlab-ci-yml基本语法"><a href="#gitlab-ci-yml基本语法" class="headerlink" title=".gitlab-ci.yml基本语法"></a>.gitlab-ci.yml基本语法</h1><p>以下内容暂时只针对shell执行器</p><h1 id="config-toml基本配置"><a href="#config-toml基本配置" class="headerlink" title="config.toml基本配置"></a>config.toml基本配置</h1><blockquote><p>官方文档介绍：<a href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html">https://docs.gitlab.com/runner/configuration/advanced-configuration.html</a></p></blockquote><h2 id="全局配置"><a href="#全局配置" class="headerlink" title="全局配置"></a>全局配置</h2><table><thead><tr><th>关键字</th><th>描述</th></tr></thead><tbody><tr><td><code>concurrent</code></td><td>限制全局可以同时运行的作业数。使用所有已定义的运行者的作业的最大上限。0并不是指无限制，如果服务器性能还不错，可以尝试给个5</td></tr><tr><td><code>log_level</code></td><td>日志级别（选项：调试，信息，警告，错误，致命，恐慌）。请注意，此设置的优先级低于命令行参数设置的级别<code>--debug</code>，<code>-l</code>或<code>--log-level</code></td></tr><tr><td><code>log_format</code></td><td>日志格式（选项：runner，text，json）。请注意，此设置的优先级低于命令行参数设置的格式<code>--log-format</code></td></tr><tr><td><code>check_interval</code></td><td>定义新作业检查之间的间隔长度（以秒为单位）。默认值为3; 如果设置为0或更低，将使用默认值。</td></tr><tr><td><code>sentry_dsn</code></td><td>启用跟踪哨兵的所有系统级错误</td></tr><tr><td><code>listen_address</code></td><td>地址（<code>&lt;host&gt;:&lt;port&gt;</code>），Prometheus指标HTTP服务器应该在其上监听</td></tr></tbody></table><h2 id="session-server-介绍"><a href="#session-server-介绍" class="headerlink" title="[session_server]介绍"></a><code>[session_server]</code>介绍</h2><p>此项配置应在<code>[[runners]]</code>外部指定，主要包含以下参数</p><table><thead><tr><th>设置</th><th>描述</th></tr></thead><tbody><tr><td><code>listen_address</code></td><td>用于会话服务器的内部URL。</td></tr><tr><td><code>advertise_address</code></td><td>Runner将向GitLab公开的URL，用于访问会话服务器。<code>listen_address</code>如果没有定义，则回退到。</td></tr><tr><td><code>session_timeout</code></td><td>作业完成后会话可以保持活动状态的多长时间（这将阻止作业完成），默认为1800（30分钟）。</td></tr></tbody></table><blockquote><p>其中<code>listen_address</code>和<code>advertise_address</code>需要以<code>host:port</code>形式提供，其中<code>host</code>可以是IP地址，也可以是域名。</p></blockquote><p>示例：</p><figure class="highlight toml"><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 class="section">[session_server]</span></span><br><span class="line">  <span class="attr">listen_address</span> = <span class="string">&quot;0.0.0.0:8093&quot;</span> <span class="comment">#  listen on all available interfaces on port 8093</span></span><br><span class="line">  <span class="attr">advertise_address</span> = <span class="string">&quot;runner-host-name.tld:8093&quot;</span></span><br><span class="line">  <span class="attr">session_timeout</span> = <span class="number">1800</span></span><br></pre></td></tr></table></figure><h2 id="runners-介绍"><a href="#runners-介绍" class="headerlink" title="[[runners]]介绍"></a><code>[[runners]]</code>介绍</h2><p>一个<code>config.toml</code>文件允许存在多个<code>[[runners]]</code>，也就对应了上文中提到的一个服务器允许注册多个gitlab runner<br>相关配置参数介绍如下表所示：</p><table><thead><tr><th>设置</th><th>描述</th></tr></thead><tbody><tr><td><code>name</code></td><td>Runner的描述，只是提供信息</td></tr><tr><td><code>url</code></td><td>GitLab URL</td></tr><tr><td><code>token</code></td><td>Runner的特殊令牌（不要与注册令牌混淆）</td></tr><tr><td><code>tls-ca-file</code></td><td>包含证书的文件，用于在使用HTTPS时验证对等方</td></tr><tr><td><code>tls-cert-file</code></td><td>包含证书的文件，以便在使用HTTPS时与对等方进行身份验证</td></tr><tr><td><code>tls-key-file</code></td><td>包含私钥的文件，用于在使用HTTPS时与对等方进行身份验证</td></tr><tr><td><code>limit</code></td><td>限制此令牌可同时处理的作业数。0（默认）仅表示不限制</td></tr><tr><td><code>executor</code></td><td>选择应如何构建项目</td></tr><tr><td><code>shell</code></td><td>用于生成脚本的shell的名称。默认值取决于平台。</td></tr><tr><td><code>builds_dir</code></td><td>构建将存储在所选执行程序的上下文中的目录（本地，Docker，SSH）</td></tr><tr><td><code>cache_dir</code></td><td>构建缓存的目录将存储在所选执行程序（本地，Docker，SSH）的上下文中。如果使用docker执行程序，则此目录需要包含在其volumes参数中。</td></tr><tr><td><code>environment</code></td><td>附加或覆盖环境变量</td></tr><tr><td><code>request_concurrency</code></td><td>限制GitLab新作业的并发请求数（默认值为1）</td></tr><tr><td><code>output_limit</code></td><td>设置最大构建日志大小（以KB为单位），默认设置为4096（4MB）</td></tr><tr><td><code>pre_clone_script</code></td><td>在克隆Git存储库之前要在Runner上执行的命令。例如，这可以用于首先调整Git客户端配置。要插入多个命令，请使用（三引号）多行字符串或“\ n”字符。</td></tr><tr><td><code>pre_build_script</code></td><td>在克隆Git存储库之后但在执行构建之前要在Runner上执行的命令。要插入多个命令，请使用（三引号）多行字符串或“\ n”字符。</td></tr><tr><td><code>post_build_script</code></td><td>在执行构建之后但在执行之前要在Runner上执行的命令after_script。要插入多个命令，请使用（三引号）多行字符串或“\ n”字符。</td></tr><tr><td><code>clone_url</code></td><td>覆盖GitLab实例的URL。如果Runner无法连接到GitLab上的GitLab暴露自己，则使用。</td></tr><tr><td><code>debug_trace_disabled</code></td><td>禁用该CI_DEBUG_TRACE功能。设置为true时，即使用户CI_DEBUG_TRACE将设置为调试跟踪，也将保持禁用状态true。</td></tr></tbody></table><p>示例：</p><figure class="highlight toml"><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"><span class="section">[[runners]]</span></span><br><span class="line">  <span class="attr">name</span> = <span class="string">&quot;ruby-2.1-docker&quot;</span></span><br><span class="line">  <span class="attr">url</span> = <span class="string">&quot;https://CI/&quot;</span></span><br><span class="line">  <span class="attr">token</span> = <span class="string">&quot;TOKEN&quot;</span></span><br><span class="line">  <span class="attr">limit</span> = <span class="number">0</span></span><br><span class="line">  <span class="attr">executor</span> = <span class="string">&quot;docker&quot;</span></span><br><span class="line">  <span class="attr">builds_dir</span> = <span class="string">&quot;&quot;</span></span><br><span class="line">  <span class="attr">shell</span> = <span class="string">&quot;&quot;</span></span><br><span class="line">  <span class="attr">environment</span> = [<span class="string">&quot;ENV=value&quot;</span>, <span class="string">&quot;LC_ALL=en_US.UTF-8&quot;</span>]</span><br><span class="line">  <span class="attr">clone_url</span> = <span class="string">&quot;http://gitlab.example.local&quot;</span></span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>Java线程基础</title>
      <link>https://baofeidyz.com/2019/0e68c9260224/</link>
      <description>
        <![CDATA[<h1 id="Java线程基础"><a href="#Java线程基础" class="headerlink" title="Java线程基础"></a>Java线程基础</h1><h2 id="线程状态"><a href="#线程状态" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">多线程</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <pubDate>Fri, 26 Jul 2019 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="Java线程基础"><a href="#Java线程基础" class="headerlink" title="Java线程基础"></a>Java线程基础</h1><h2 id="线程状态"><a href="#线程状态" class="headerlink" title="线程状态"></a>线程状态</h2><blockquote><p>关于java的线程状态，实际上你只要看<code>Thread.java</code>源码就可以了，网上很多资料都不全，而且往往你看了资料以后根本记不住，反而自己去花时间看看源码，思考每次位运算的结果，才能让你真的理解和掌握线程的状态转换~当然，我也不建议你看我的这篇文章，毕竟我是写给我自己看的。</p></blockquote><h3 id="NEW"><a href="#NEW" class="headerlink" title="NEW"></a>NEW</h3><blockquote><p>Thread state for a thread which has not yet started.</p></blockquote><p>还没有开始的状态，就是new</p><h3 id="RUNNABLE"><a href="#RUNNABLE" class="headerlink" title="RUNNABLE"></a>RUNNABLE</h3><blockquote><p>Thread state for a runnable thread.  A thread in the runnable state is executing in the Java virtual  achine but it may be waiting for other resources from the operating system such as processor.</p></blockquote><p>正在jvm虚拟机中运行的状态，但是可能还需要等待系统资源，类似于cpu这种资源。</p><h3 id="BLOCKED"><a href="#BLOCKED" class="headerlink" title="BLOCKED"></a>BLOCKED</h3><blockquote><p>Thread state for a thread blocked waiting for a monitor lock. A thread in the blocked state is waiting for a monitor lock to enter a synchronized block&#x2F;method or reenter a synchronized block&#x2F;method after calling</p></blockquote><p>在monitor管程阻塞等待获取资源（锁）的状态，主要表现在synchronized block&#x2F;method</p><h3 id="WAITING"><a href="#WAITING" class="headerlink" title="WAITING"></a>WAITING</h3><blockquote><p>Thread state for a waiting thread. A thread is in the waiting state due to calling one of the following methods:</p><p>Object.wait with no timeout</p><p>Thread.join with no timeout</p><p>LockSupport.park</p><p>A thread in the waiting state is waiting for another thread to perform a particular action.</p></blockquote><p>当调用<code>Object.wait()</code>、<code>Thread.join()</code>、<code>LockSupport.park()</code>方法的时候，调用线程就会进入waiting状态，只有当线程调用<code>Object.notify()</code>或者<code>Object.notifyAll()</code>方法的时候才会终止。另外，一个线程调用另外一个线程的<code>join()</code>方法，则该线程将等待另外一个线程执行结束。</p><h3 id="TIMED-WAITING"><a href="#TIMED-WAITING" class="headerlink" title="TIMED_WAITING"></a>TIMED_WAITING</h3><blockquote><p>Thread state for a waiting thread with a specified waiting time. A thread is in the timed waiting state due to calling one of the following methods with a specified positive waiting time.</p></blockquote><h3 id="TERMINATED"><a href="#TERMINATED" class="headerlink" title="TERMINATED"></a>TERMINATED</h3><blockquote><p>Thread state for a terminated thread.The thread has completed execution.</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>从源码看ThreadPoolExecutor和BlockQueue</title>
      <link>https://baofeidyz.com/2019/19058a7e9208/</link>
      <description>
        <![CDATA[<h1 id="从源码看ThreadPoolExecutor和BlockQueue"><a href="#从源码看ThreadPoolExecutor和BlockQueue" class="headerlink"]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">多线程</category>
      <category domain="https://baofeidyz.com/tags/Java/">Java</category>
      <category domain="https://baofeidyz.com/tags/Java%E4%B8%AD%E7%BA%A7/">Java中级</category>
      <pubDate>Sun, 21 Jul 2019 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="从源码看ThreadPoolExecutor和BlockQueue"><a href="#从源码看ThreadPoolExecutor和BlockQueue" class="headerlink" title="从源码看ThreadPoolExecutor和BlockQueue"></a>从源码看ThreadPoolExecutor和BlockQueue</h1><h2 id="JDK版本"><a href="#JDK版本" class="headerlink" title="JDK版本"></a>JDK版本</h2><p>此文章基于jdk 1.8.0_191</p><h2 id="需要用到的知识点"><a href="#需要用到的知识点" class="headerlink" title="需要用到的知识点"></a>需要用到的知识点</h2><h3 id="位运算"><a href="#位运算" class="headerlink" title="位运算"></a>位运算</h3><table><thead><tr><th align="left">操作符</th><th align="left">描述</th><th align="left">例子</th></tr></thead><tbody><tr><td align="left">＆</td><td align="left">如果相对应位都是1，则结果为1，否则为0</td><td align="left">（A＆B），得到12，即0000 1100</td></tr><tr><td align="left">|</td><td align="left">如果相对应位都是0，则结果为0，否则为1</td><td align="left">（A | B）得到61，即 0011 1101</td></tr><tr><td align="left">^</td><td align="left">如果相对应位值相同，则结果为0，否则为1</td><td align="left">（A ^ B）得到49，即 0011 0001</td></tr><tr><td align="left">〜</td><td align="left">按位取反运算符翻转操作数的每一位，即0变成1，1变成0。</td><td align="left">（〜A）得到-61，即1100 0011</td></tr><tr><td align="left">&lt;&lt;</td><td align="left">按位左移运算符。左操作数按位左移右操作数指定的位数。</td><td align="left">A &lt;&lt; 2得到240，即 1111 0000</td></tr><tr><td align="left"><code>&gt;&gt;</code></td><td align="left">按位右移运算符。左操作数按位右移右操作数指定的位数。</td><td align="left">A &gt;&gt; 2得到15即 1111</td></tr><tr><td align="left"><code>&gt;&gt;&gt;</code></td><td align="left">按位右移补零操作符。左操作数的值按右操作数指定的位数右移，移动得到的空位以零填充。</td><td align="left">A&gt;&gt;&gt;2得到15即0000 1111</td></tr></tbody></table><h2 id="线程池原理"><a href="#线程池原理" class="headerlink" title="线程池原理"></a>线程池原理</h2><blockquote><p>摘自 汪文君. Java高并发编程详解：多线程与架构设计 (Java核心技术系列) (Kindle 位置 2508-2512). 北京华章图文信息有限公司. Kindle 版本. </p></blockquote><p>所谓线程池通俗的理解就是有一个池子，里面存放着已经创建好的线程，当有任务提交给线程池执行时，池子中的 某个线程会主动执行该任务。如果池子中的线程数量不够应付数量众多的任务时，则需要自动扩充新的线程到池子 中，但是该数量是有限的，就好比池塘的水界线一样。当任务比较少的时候，池子中的线程能够自动回收，释放 资源。为了能够异步地提交任务和缓存未被处理的任务，需要有一个任务队列。</p><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721102700.png" alt="线程池原理图"></p><p>一个完整的线程池应该具备如下要素：</p><ol><li>任务队列：用于缓存提交的任务</li><li>线程数量管理功能：一个线程池必须能够很好地管理和控制线程数量，可通过如下三个参数来实现，比如创建 线程池时初始的线程数量 init；线程池自动扩充时最大的线程数量max；在线程池空闲时需要释放线程但是也要维护一定数量的活跃数量或者核心数量core。有了这三个参数，就能够很好地控制线程池中的线程数量，将其维护在一个合理的范围之内，三者之间的关系是 init&lt;&#x3D; core&lt;&#x3D; max</li><li>任务拒绝策略：如果线程数量已达到上限且任务队列已满，则需要有相应的拒绝策略来通知任务提交者</li><li>线程工厂：主要用于个性化定制线程，比如线程设置为守护线程以及设置线程名称等</li><li>QueueSize：任务队列主要存放提交的Runnable，但是为了防止内存溢出，需要有limit数量对其进行控制</li><li>Keepedalive 时间：该时间主要决定线程各个重要参数自动维护的时间间隔</li></ol><h2 id="线程池的五种状态"><a href="#线程池的五种状态" class="headerlink" title="线程池的五种状态"></a>线程池的五种状态</h2><blockquote><p>线程池状态示意图以及五种状态的说明摘自CSDN<a href="https://blog.csdn.net/L_kanglin/article/details/57411851">一只逗比的程序猿</a></p></blockquote><p>一共有五种，分别是RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED</p><p>线程池状态切换示意图</p><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160003.png" alt="线程池状态切换示意图"></p><h3 id="RUNNING"><a href="#RUNNING" class="headerlink" title="RUNNING"></a>RUNNING</h3><p>状态说明：线程池处在RUNNING状态时，能够接收新任务，以及对已添加的任务进行处理</p><p>状态切换：线程池的初始化状态是RUNNING。换句话说，线程池被一旦被创建，就处于RUNNING状态，并且线程池中的任务数为0</p><h3 id="SHUTDOWN"><a href="#SHUTDOWN" class="headerlink" title="SHUTDOWN"></a>SHUTDOWN</h3><p>状态说明：线程池处在SHUTDOWN状态时，不接收新任务，但能处理已添加的任务</p><p>状态切换：调用线程池的shutdown()接口时，线程池由RUNNING -&gt; SHUTDOWN</p><blockquote><p>注：虽然状态已经不是RUNNING了，但是如果任务队列中还有任务的时候，线程池仍然会继续执行，具体分析请见ThreadPoolExecutor.execute()方法解析</p></blockquote><h3 id="STOP"><a href="#STOP" class="headerlink" title="STOP"></a>STOP</h3><p>状态说明：线程池处在STOP状态时，不接收新任务，不处理已添加的任务，并且会中断正在处理的任务</p><p>状态切换：调用线程池的shutdownNow()接口时，线程池由(RUNNING or SHUTDOWN ) -&gt; STOP</p><h3 id="TIDYING"><a href="#TIDYING" class="headerlink" title="TIDYING"></a>TIDYING</h3><p>状态说明：当所有的任务已终止，ctl记录的”任务数量”为0，线程池会变为TIDYING状态。当线程池变为TIDYING状态时，会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的，若用户想在线程池变为TIDYING时，进行相应的处理；可以通过重载terminated()函数来实现</p><p>状态切换：当线程池在SHUTDOWN状态下，阻塞队列为空并且线程池中执行的任务也为空时，就会由 SHUTDOWN -&gt; TIDYING。<br>当线程池在STOP状态下，线程池中执行的任务为空时，就会由STOP -&gt; TIDYING</p><h3 id="TERMINATED"><a href="#TERMINATED" class="headerlink" title="TERMINATED"></a>TERMINATED</h3><p>状态说明：线程池彻底终止，就变成TERMINATED状态</p><p>状态切换：线程池处在TIDYING状态时，执行完terminated()之后，就会由 TIDYING -&gt; TERMINATED</p><h2 id="线程池五种状态的二进制表示"><a href="#线程池五种状态的二进制表示" class="headerlink" title="线程池五种状态的二进制表示"></a>线程池五种状态的二进制表示</h2><table><thead><tr><th>线程池状态</th><th>二进制</th></tr></thead><tbody><tr><td>RUNNING</td><td>111</td></tr><tr><td>SHUTDOWN</td><td>000</td></tr><tr><td>STOP</td><td>001</td></tr><tr><td>TIDYING</td><td>010</td></tr><tr><td>TERMINATED</td><td>011</td></tr></tbody></table><figure class="highlight json"><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">COUNT_BITS <span class="punctuation">:</span><span class="number">29</span></span><br><span class="line">RUNNING    <span class="punctuation">:</span><span class="number">11100000</span> <span class="number">00000000</span> <span class="number">00000000</span> <span class="number">00000000</span></span><br><span class="line">SHUTDOWN   <span class="punctuation">:</span><span class="number">00000000</span> <span class="number">00000000</span> <span class="number">00000000</span> <span class="number">00000000</span></span><br><span class="line">STOP       <span class="punctuation">:</span><span class="number">00100000</span> <span class="number">00000000</span> <span class="number">00000000</span> <span class="number">00000000</span></span><br><span class="line">TIDYING    <span class="punctuation">:</span><span class="number">01000000</span> <span class="number">00000000</span> <span class="number">00000000</span> <span class="number">00000000</span></span><br><span class="line">TERMINATED <span class="punctuation">:</span><span class="number">01100000</span> <span class="number">00000000</span> <span class="number">00000000</span> <span class="number">00000000</span></span><br><span class="line">RUNNING    <span class="punctuation">:</span><span class="number">-536870912</span></span><br><span class="line">SHUTDOWN   <span class="punctuation">:</span><span class="number">0</span></span><br><span class="line">STOP       <span class="punctuation">:</span><span class="number">536870912</span></span><br><span class="line">TIDYING    <span class="punctuation">:</span><span class="number">1073741824</span></span><br><span class="line">TERMINATED <span class="punctuation">:</span><span class="number">1610612736</span></span><br></pre></td></tr></table></figure><h2 id="ThreadPoolExecutor解读"><a href="#ThreadPoolExecutor解读" class="headerlink" title="ThreadPoolExecutor解读"></a>ThreadPoolExecutor解读</h2><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">ThreadPoolExecutor</span><span class="params">(<span class="type">int</span> corePoolSize,</span></span><br><span class="line"><span class="params">                          <span class="type">int</span> maximumPoolSize,</span></span><br><span class="line"><span class="params">                          <span class="type">long</span> keepAliveTime,</span></span><br><span class="line"><span class="params">                          TimeUnit unit,</span></span><br><span class="line"><span class="params">                          BlockingQueue&lt;Runnable&gt; workQueue,</span></span><br><span class="line"><span class="params">                          ThreadFactory threadFactory,</span></span><br><span class="line"><span class="params">                          RejectedExecutionHandler handler)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (corePoolSize &lt; <span class="number">0</span> ||</span><br><span class="line">        maximumPoolSize &lt;= <span class="number">0</span> ||</span><br><span class="line">        maximumPoolSize &lt; corePoolSize ||</span><br><span class="line">        keepAliveTime &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>();</span><br><span class="line">    <span class="keyword">if</span> (workQueue == <span class="literal">null</span> || threadFactory == <span class="literal">null</span> || handler == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="built_in">this</span>.acc = System.getSecurityManager() == <span class="literal">null</span> ?</span><br><span class="line">        <span class="literal">null</span> :</span><br><span class="line">    AccessController.getContext();</span><br><span class="line">    <span class="built_in">this</span>.corePoolSize = corePoolSize;</span><br><span class="line">    <span class="built_in">this</span>.maximumPoolSize = maximumPoolSize;</span><br><span class="line">    <span class="built_in">this</span>.workQueue = workQueue;</span><br><span class="line">    <span class="built_in">this</span>.keepAliveTime = unit.toNanos(keepAliveTime);</span><br><span class="line">    <span class="built_in">this</span>.threadFactory = threadFactory;</span><br><span class="line">    <span class="built_in">this</span>.handler = handler;</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="type">int</span> corePoolSize,</span><br><span class="line"><span class="type">int</span> maximumPoolSize,</span><br><span class="line"><span class="type">long</span> keepAliveTime,</span><br><span class="line">TimeUnit unit,</span><br><span class="line">BlockingQueue&lt;Runnable&gt; workQueue,</span><br><span class="line">ThreadFactory threadFactory,</span><br><span class="line">RejectedExecutionHandler handler</span><br></pre></td></tr></table></figure><p>对应含义关系如下</p><table><thead><tr><th>参数名</th><th>类型</th><th>备注</th></tr></thead><tbody><tr><td>corePoolSize</td><td><code>int</code></td><td>核心线程数（如果<code>allowCoreThreadTimeOut</code>为true，核心线程将一直存活）</td></tr><tr><td>maximumPoolSize</td><td><code>int</code></td><td>允许创建的最大线程数<br>（如果使用了无界队列LinkedBlockingQueue，这个值会失效，原因在讲解execute方法中提及）</td></tr><tr><td>keepAliveTime</td><td><code>long</code></td><td>非核心线程闲置时的超时时长（如果<code>allowCoreThreadTimeOut</code>为true，这个时长也会用于核心线程）</td></tr><tr><td>unit</td><td><code>TimeUnit</code></td><td>参数<code>keepAliveTime</code>的单位</td></tr><tr><td>workQueue</td><td><code>BlockingQueue&lt;Runnable&gt;</code></td><td>任务队列，可选的<a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/BlockingQueue.html">子类</a>有<br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ArrayBlockingQueue.html">ArrayBlockingQueue</a><br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/DelayQueue.html">DelayQueue</a><br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/LinkedBlockingDeque.html">LinkedBlockingDeque</a><br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/LinkedBlockingQueue.html">LinkedBlockingQueue</a><br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/LinkedTransferQueue.html">LinkedTransferQueue</a><br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/PriorityBlockingQueue.html">PriorityBlockingQueue</a><br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/SynchronousQueue.html">SynchronousQueue</a></td></tr><tr><td>threadFactory</td><td><code>ThreadFactory</code></td><td>线程工厂，为线程池提供创建新线程的功能（其他构造函数中默认传<code>Executors.defaultThreadFactory()</code></td></tr><tr><td>handler</td><td><code>RejectedExecutionHandler</code></td><td>拒绝策略，当队列和线程池都满了就才会根据这个策略进行处理。（默认为AbortPolicy，直接抛出异常），可选<a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/RejectedExecutionHandler.html">子类</a>有<a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.AbortPolicy.html">ThreadPoolExecutor.AbortPolicy</a><br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.CallerRunsPolicy.html">ThreadPoolExecutor.CallerRunsPolicy</a><br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.DiscardOldestPolicy.html">ThreadPoolExecutor.DiscardOldestPolicy</a><br><a href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.DiscardPolicy.html">ThreadPoolExecutor.DiscardPolicy</a></td></tr></tbody></table><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(Runnable command)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (command == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">         * Proceed in 3 steps:</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * 1. If fewer than corePoolSize threads are running, try to</span></span><br><span class="line"><span class="comment">         * start a new thread with the given command as its first</span></span><br><span class="line"><span class="comment">         * task.  The call to addWorker atomically checks runState and</span></span><br><span class="line"><span class="comment">         * workerCount, and so prevents false alarms that would add</span></span><br><span class="line"><span class="comment">         * threads when it shouldn&#x27;t, by returning false.</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * 2. If a task can be successfully queued, then we still need</span></span><br><span class="line"><span class="comment">         * to double-check whether we should have added a thread</span></span><br><span class="line"><span class="comment">         * (because existing ones died since last checking) or that</span></span><br><span class="line"><span class="comment">         * the pool shut down since entry into this method. So we</span></span><br><span class="line"><span class="comment">         * recheck state and if necessary roll back the enqueuing if</span></span><br><span class="line"><span class="comment">         * stopped, or start a new thread if there are none.</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * 3. If we cannot queue task, then we try to add a new</span></span><br><span class="line"><span class="comment">         * thread.  If it fails, we know we are shut down or saturated</span></span><br><span class="line"><span class="comment">         * and so reject the task.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">    <span class="keyword">if</span> (workerCountOf(c) &lt; corePoolSize) &#123;</span><br><span class="line">        <span class="keyword">if</span> (addWorker(command, <span class="literal">true</span>))</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        c = ctl.get();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (isRunning(c) &amp;&amp; workQueue.offer(command)) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">recheck</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">        <span class="keyword">if</span> (! isRunning(recheck) &amp;&amp; remove(command))</span><br><span class="line">            reject(command);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (workerCountOf(recheck) == <span class="number">0</span>)</span><br><span class="line">            addWorker(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (!addWorker(command, <span class="literal">false</span>))</span><br><span class="line">        reject(command);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里涉及到<code>runState</code>和<code>workerCount</code>两个概念，线程池中是利用32位的int变量来表示。</p><p>因为线程池的状态总共有五种，2^2 &#x3D; 4, 2^3 &#x3D; 8，所以需要占用三位，实际采用的就是<strong>高三位</strong>表示，具体可见<a href="#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E4%BA%94%E7%A7%8D%E7%8A%B6%E6%80%81%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%A1%A8%E7%A4%BA">线程池五种状态的二进制表示</a></p><p>剩下的部分全部都用于记录有效线程数，所以代码中也就规定有效线程数不可大于29位，也就是最大为2^29-1，详见execute()方法解析。</p><p>然后我们再来一起看看<code>execute(Runnable command)</code>方法是如何运行的。</p><h4 id="execute-整体逻辑概览"><a href="#execute-整体逻辑概览" class="headerlink" title="execute()整体逻辑概览"></a>execute()整体逻辑概览</h4><ol><li>最开始是一个基本的判空逻辑，如果传入的任务是空的则抛出异常</li><li>第一个判断：检查当前核心线程数，如果当前线程数小于核心线程则调用<code>addWorker()</code>方法创建线程，创建成功返回true</li><li>第二个判断：当核心线程池中的所有线程都在运行，此时将线程放到任务中</li><li>第三个判断：如果核心线程数已经满了，队列也添加失败了，那么这里就会调用上面提到的拒绝策略，如果我们没有在创建线程池时给出特定的拒绝策略，那么默认的实现就是抛出异常。</li></ol><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">throw</span> <span class="keyword">new</span> <span class="title class_">RejectedExecutionException</span>(<span class="string">&quot;Task &quot;</span> + r.toString() +</span><br><span class="line">                                                 <span class="string">&quot; rejected from &quot;</span> +</span><br><span class="line">                                                 e.toString());</span><br></pre></td></tr></table></figure><h4 id="细节实现"><a href="#细节实现" class="headerlink" title="细节实现"></a>细节实现</h4><h4 id="第一个判断：检查当前核心线程数"><a href="#第一个判断：检查当前核心线程数" class="headerlink" title="第一个判断：检查当前核心线程数"></a>第一个判断：检查当前核心线程数</h4><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="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line"><span class="keyword">if</span> (workerCountOf(c) &lt; corePoolSize) &#123;</span><br><span class="line">    <span class="keyword">if</span> (addWorker(command, <span class="literal">true</span>))</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    c = ctl.get();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="获取workerCount"><a href="#获取workerCount" class="headerlink" title="获取workerCount"></a>获取workerCount</h5><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"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">ctl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(ctlOf(RUNNING, <span class="number">0</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">ctlOf</span><span class="params">(<span class="type">int</span> rs, <span class="type">int</span> wc)</span> &#123; <span class="keyword">return</span> rs | wc; &#125;</span><br></pre></td></tr></table></figure><p>这里的<code>ctl</code>变量是一个初始值为<code>RUNNING</code>的<code>AtomicInteger</code>对象，拿到的变量<code>c</code>中既存储了当前线程池的状态，又保存了当前线程池中的有效线程数量。</p><blockquote><p>画外音：这里的AtomicInteger对象为什么是线程安全的，是因为使用了CAS，具体不谈</p></blockquote><h5 id="判断当前有效线程数量是否大于核心线程数量"><a href="#判断当前有效线程数量是否大于核心线程数量" class="headerlink" title="判断当前有效线程数量是否大于核心线程数量"></a>判断当前有效线程数量是否大于核心线程数量</h5><p>然后我们再看<code>workerCountOf(c)</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">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">workerCountOf</span><span class="params">(<span class="type">int</span> c)</span>  &#123; </span><br><span class="line">    <span class="keyword">return</span> c &amp; CAPACITY; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>CAPACITY</code>的值为：</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"><span class="number">00011111</span> <span class="number">11111111</span> <span class="number">11111111</span> <span class="number">11111111</span></span><br></pre></td></tr></table></figure><p>结合<a href="#%E4%BD%8D%E8%BF%90%E7%AE%97">位运算</a>法则，这里<code>c &amp; CAPACITY</code>的结果集就是实际的有效线程数量。</p><h5 id="创建核心线程数量"><a href="#创建核心线程数量" class="headerlink" title="创建核心线程数量"></a>创建核心线程数量</h5><p>当有效线程数量小于核心线程数量的时候，我们需要调用<code>addWorker(command, true)</code>的方法去创建线程，这里的参数<code>ture</code>就用于标识当前想要创建的线程是核心线程。</p><p>然后我们再来看看<code>addWorker(command, true)</code>方法的具体实现逻辑。</p><h5 id="addWork实现逻辑"><a href="#addWork实现逻辑" class="headerlink" title="addWork实现逻辑"></a>addWork实现逻辑</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">addWorker</span><span class="params">(Runnable firstTask, <span class="type">boolean</span> core)</span> &#123;</span><br><span class="line">    retry:</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">        <span class="type">int</span> <span class="variable">rs</span> <span class="operator">=</span> runStateOf(c);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Check if queue empty only if necessary.</span></span><br><span class="line">        <span class="keyword">if</span> (rs &gt;= SHUTDOWN &amp;&amp;</span><br><span class="line">            ! (rs == SHUTDOWN &amp;&amp;</span><br><span class="line">               firstTask == <span class="literal">null</span> &amp;&amp;</span><br><span class="line">               ! workQueue.isEmpty()))</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">wc</span> <span class="operator">=</span> workerCountOf(c);</span><br><span class="line">            <span class="keyword">if</span> (wc &gt;= CAPACITY ||</span><br><span class="line">                wc &gt;= (core ? corePoolSize : maximumPoolSize))</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">if</span> (compareAndIncrementWorkerCount(c))</span><br><span class="line">                <span class="keyword">break</span> retry;</span><br><span class="line">            c = ctl.get();  <span class="comment">// Re-read ctl</span></span><br><span class="line">            <span class="keyword">if</span> (runStateOf(c) != rs)</span><br><span class="line">                <span class="keyword">continue</span> retry;</span><br><span class="line">            <span class="comment">// else CAS failed due to workerCount change; retry inner loop</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="type">boolean</span> <span class="variable">workerStarted</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">workerAdded</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">Worker</span> <span class="variable">w</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        w = <span class="keyword">new</span> <span class="title class_">Worker</span>(firstTask);</span><br><span class="line">        <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> w.thread;</span><br><span class="line">        <span class="keyword">if</span> (t != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">mainLock</span> <span class="operator">=</span> <span class="built_in">this</span>.mainLock;</span><br><span class="line">            mainLock.lock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// Recheck while holding lock.</span></span><br><span class="line">                <span class="comment">// Back out on ThreadFactory failure or if</span></span><br><span class="line">                <span class="comment">// shut down before lock acquired.</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">rs</span> <span class="operator">=</span> runStateOf(ctl.get());</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (rs &lt; SHUTDOWN ||</span><br><span class="line">                    (rs == SHUTDOWN &amp;&amp; firstTask == <span class="literal">null</span>)) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (t.isAlive()) <span class="comment">// precheck that t is startable</span></span><br><span class="line">                        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalThreadStateException</span>();</span><br><span class="line">                    workers.add(w);</span><br><span class="line">                    <span class="type">int</span> <span class="variable">s</span> <span class="operator">=</span> workers.size();</span><br><span class="line">                    <span class="keyword">if</span> (s &gt; largestPoolSize)</span><br><span class="line">                        largestPoolSize = s;</span><br><span class="line">                    workerAdded = <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                mainLock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (workerAdded) &#123;</span><br><span class="line">                t.start();</span><br><span class="line">                workerStarted = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (! workerStarted)</span><br><span class="line">            addWorkerFailed(w);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> workerStarted;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h6 id="rs为什么可以表示runstate？"><a href="#rs为什么可以表示runstate？" class="headerlink" title="rs为什么可以表示runstate？"></a><code>rs</code>为什么可以表示runstate？</h6><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"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">runStateOf</span><span class="params">(<span class="type">int</span> c)</span>     &#123; <span class="keyword">return</span> c &amp; ~CAPACITY; &#125;</span><br></pre></td></tr></table></figure><p><code>~CAPACITY</code>的值为：</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"><span class="number">11100000</span> <span class="number">00000000</span> <span class="number">00000000</span> <span class="number">00000000</span></span><br></pre></td></tr></table></figure><p>所以后面的29位不论怎么样都会变成0，也就是最后的结果集中只会有高三位用于表示runstate的参数</p><h6 id="什么时候才会创建线程？"><a href="#什么时候才会创建线程？" class="headerlink" title="什么时候才会创建线程？"></a>什么时候才会创建线程？</h6><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"><span class="keyword">if</span> (rs &gt;= SHUTDOWN &amp;&amp;</span><br><span class="line">    ! (rs == SHUTDOWN &amp;&amp;</span><br><span class="line">       firstTask == <span class="literal">null</span> &amp;&amp;</span><br><span class="line">       ! workQueue.isEmpty()))</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</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">rs &gt;= SHUTDOW</span><br></pre></td></tr></table></figure><p>代表当前runstate是SHUTDOWN  STOP TIDYING TERMINATED 任意一个，关于五种状态的值可见<a href="#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E4%BA%94%E7%A7%8D%E7%8A%B6%E6%80%81%E7%9A%84%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%A1%A8%E7%A4%BA">线程池五种状态的二进制表示</a></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">! (rs == SHUTDOWN &amp;&amp; firstTask == <span class="literal">null</span> &amp;&amp; ! workQueue.isEmpty())</span><br></pre></td></tr></table></figure><p>A：在<code>rs &gt;= SHUTDOW</code>成立的前提下，如果是<code>rs != SHUTDOWN</code>，则整个判断成立。</p><p>B：在<code>rs &gt;= SHUTDOW</code>成立的前提下，如果是<code>firstTask != null</code>，则整个判断成立。</p><p>C：在<code>rs &gt;= SHUTDOW</code>成立的前提下，如果是<code> workQueue.isEmpty()</code>，则整个判断成立。</p><p>并且A B C三个是有先后顺序的</p><p>再总结一下就是在第一个判断成立的前提下，第二个判断中，只要有一个不成立就会返回false，线程创建失败。</p><p>转成白话文就是</p><blockquote><p>A 当线程池状态为STOP TIDYING TERMINATED时，不会创建线程</p><p>B 当线程池为SHUTDOWN时，不允许新建任务</p><p>C 当线程池为SHUTDOWN时，且没有新的任务，此时如果任务队列也经没有任务，同样不会创建线程</p></blockquote><p>此时再回头看看<a href="#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E4%BA%94%E7%A7%8D%E7%8A%B6%E6%80%81">线程池的五种状态</a></p><hr><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (;;) &#123;</span><br><span class="line">    <span class="comment">// 第一步，拿到当前的有效线程数</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">wc</span> <span class="operator">=</span> workerCountOf(c);</span><br><span class="line">    <span class="comment">// 如果当前有效线程数已经大于等于允许的最大线程数，不允许创建</span></span><br><span class="line">    <span class="comment">// 如果正准备创建的线程是core的线程且大于线程池初始化时设定的线程数，不允许创建</span></span><br><span class="line">    <span class="comment">// 如果正准备创建的线程不是core但大于线程池初始化时设定的最大线程数，不允许创建</span></span><br><span class="line">    <span class="keyword">if</span> (wc &gt;= CAPACITY ||</span><br><span class="line">        wc &gt;= (core ? corePoolSize : maximumPoolSize))</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// 利用CAS做自增操作，如果成功了，就跳出循环体开始下一步操作，如果失败则重试</span></span><br><span class="line">    <span class="keyword">if</span> (compareAndIncrementWorkerCount(c))</span><br><span class="line">        <span class="keyword">break</span> retry;</span><br><span class="line">    <span class="comment">// 重新读取当前的runstate，如果当前的runstate和循环体中的runstate有变化，则重新去判断是否需要创建线程</span></span><br><span class="line">    c = ctl.get();  <span class="comment">// Re-read ctl</span></span><br><span class="line">    <span class="keyword">if</span> (runStateOf(c) != rs)</span><br><span class="line">        <span class="keyword">continue</span> retry;</span><br><span class="line">    <span class="comment">// else CAS failed due to workerCount change; retry inner loop</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h6 id="线程到底是怎么创建的呢？"><a href="#线程到底是怎么创建的呢？" class="headerlink" title="线程到底是怎么创建的呢？"></a>线程到底是怎么创建的呢？</h6><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></pre></td><td class="code"><pre><span class="line">   <span class="comment">// 标记线程是否启动</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">workerStarted</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"><span class="comment">// 标记线程是否被添加</span></span><br><span class="line">   <span class="type">boolean</span> <span class="variable">workerAdded</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">   <span class="type">Worker</span> <span class="variable">w</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">   <span class="keyword">try</span> &#123;</span><br><span class="line">       w = <span class="keyword">new</span> <span class="title class_">Worker</span>(firstTask);</span><br><span class="line">       <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> w.thread;</span><br><span class="line">       <span class="keyword">if</span> (t != <span class="literal">null</span>) &#123;</span><br><span class="line">           <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">mainLock</span> <span class="operator">=</span> <span class="built_in">this</span>.mainLock;</span><br><span class="line">           <span class="comment">// 获取锁</span></span><br><span class="line">           mainLock.lock();</span><br><span class="line">           <span class="keyword">try</span> &#123;</span><br><span class="line">               <span class="comment">// Recheck while holding lock.</span></span><br><span class="line">               <span class="comment">// Back out on ThreadFactory failure or if</span></span><br><span class="line">               <span class="comment">// shut down before lock acquired.</span></span><br><span class="line">               <span class="type">int</span> <span class="variable">rs</span> <span class="operator">=</span> runStateOf(ctl.get());</span><br><span class="line">      <span class="comment">// 再检查一下当前线程池的状态</span></span><br><span class="line">               <span class="comment">// rs &lt; SHUTDOWN 表示线程池的状态为RUNNING就直接创建线程</span></span><br><span class="line">               <span class="comment">// rs == SHUTDOWN &amp;&amp; firstTask == null 当前线程已经处于SHUTDOWN且当前没有新建的任务（也就是为了把任务队列执行完）</span></span><br><span class="line">               <span class="keyword">if</span> (rs &lt; SHUTDOWN ||</span><br><span class="line">                   (rs == SHUTDOWN &amp;&amp; firstTask == <span class="literal">null</span>)) &#123;</span><br><span class="line">                   <span class="comment">// 如果正准备创建的线程已经处于alive状态，则抛出异常</span></span><br><span class="line">                   <span class="keyword">if</span> (t.isAlive()) <span class="comment">// precheck that t is startable</span></span><br><span class="line">                       <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalThreadStateException</span>();</span><br><span class="line">                   <span class="comment">// 否则放到workers中（包含线程池中所有的工作线程，将新构造的工作线程加入到工作线程集合中）</span></span><br><span class="line">                   workers.add(w);</span><br><span class="line">                   <span class="type">int</span> <span class="variable">s</span> <span class="operator">=</span> workers.size();</span><br><span class="line">                   <span class="keyword">if</span> (s &gt; largestPoolSize)</span><br><span class="line">                       largestPoolSize = s;</span><br><span class="line">                   workerAdded = <span class="literal">true</span>;</span><br><span class="line">               &#125;</span><br><span class="line">           &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">               mainLock.unlock();</span><br><span class="line">           &#125;</span><br><span class="line">           <span class="keyword">if</span> (workerAdded) &#123;</span><br><span class="line">               t.start();</span><br><span class="line">               workerStarted = <span class="literal">true</span>;</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">   &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">       <span class="keyword">if</span> (! workerStarted)</span><br><span class="line">           addWorkerFailed(w);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><h5 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h5><h6 id="创建线程的条件："><a href="#创建线程的条件：" class="headerlink" title="创建线程的条件："></a>创建线程的条件：</h6><blockquote><p>当前线程池状态为RUNNING，当前线程数小于核心线程数或当前线程线程数小于最大线程数。其中最大线程数又分为创建线程池时给定的数量以及程序允许的最大值。</p></blockquote><h6 id="不创建线程的条件："><a href="#不创建线程的条件：" class="headerlink" title="不创建线程的条件："></a>不创建线程的条件：</h6><blockquote><p>当线程池状态不为RUNNING时，不会接受新的任务，此时如果任务队列还有任务，会把这部分处理完。</p></blockquote><h4 id="第二个判断：当核心线程池中的所有线程都在运行，此时将线程放到任务中"><a href="#第二个判断：当核心线程池中的所有线程都在运行，此时将线程放到任务中" class="headerlink" title="第二个判断：当核心线程池中的所有线程都在运行，此时将线程放到任务中"></a>第二个判断：当核心线程池中的所有线程都在运行，此时将线程放到任务中</h4><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">   <span class="comment">// 检查当前线程池状态，如果为RUNNING状态，则尝试将任务放到任务队列中  </span></span><br><span class="line"><span class="keyword">if</span> (isRunning(c) &amp;&amp; workQueue.offer(command)) &#123;</span><br><span class="line">       <span class="type">int</span> <span class="variable">recheck</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">       <span class="comment">// 放置成功以后，再次检查当前的线程池状态，如果当前线程池状态非RUNNING，则尝试将刚刚放入的任务从任务队列中移除</span></span><br><span class="line">       <span class="keyword">if</span> (! isRunning(recheck) &amp;&amp; remove(command))</span><br><span class="line">           reject(command);</span><br><span class="line">       <span class="comment">// 如果当前线程数状态为RUNNING，但是workerCount的值又等于0则传入空任务结束此次创建</span></span><br><span class="line">       <span class="keyword">else</span> <span class="keyword">if</span> (workerCountOf(recheck) == <span class="number">0</span>)</span><br><span class="line">           addWorker(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><h4 id="第三个判断：什么时候执行拒绝策略？"><a href="#第三个判断：什么时候执行拒绝策略？" class="headerlink" title="第三个判断：什么时候执行拒绝策略？"></a>第三个判断：什么时候执行拒绝策略？</h4><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"><span class="keyword">else</span> <span class="keyword">if</span> (!addWorker(command, <span class="literal">false</span>))</span><br><span class="line">    reject(command);</span><br></pre></td></tr></table></figure><p>当第二个判断不成立，也就是当前线程池状态非RUNNING状态或尝试将任务放到任务队列中失败时，尝试再次创建一个非核心线程，此时线程数需要小于创建线程池时给定的最大值。</p><blockquote><p>总结</p><p>结合第二个判断和第三个判断，就可以明白为什么当任务队列是无界的时候，最大线程数不会产生作用了。</p></blockquote><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><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="keyword">final</span> <span class="keyword">void</span> <span class="title function_">runWorker</span><span class="params">(Worker w)</span> &#123;</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">wt</span> <span class="operator">=</span> Thread.currentThread(); <span class="comment">// 得到当前线程</span></span><br><span class="line">    <span class="type">Runnable</span> <span class="variable">task</span> <span class="operator">=</span> w.firstTask; <span class="comment">// 得到Worker中的任务task，也就是用户传入的task</span></span><br><span class="line">    w.firstTask = <span class="literal">null</span>; <span class="comment">// 将Worker中的任务置空</span></span><br><span class="line">    w.unlock(); <span class="comment">// allow interrupts。 </span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">completedAbruptly</span> <span class="operator">=</span> <span class="literal">true</span>; <span class="comment">// 标识当前Worker异常结束，默认是异常结束</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 如果worker中的任务不为空，执行执行任务</span></span><br><span class="line">        <span class="comment">// 否则使用getTask获得任务。一直循环，除非得到的任务为空才退出</span></span><br><span class="line">        <span class="keyword">while</span> (task != <span class="literal">null</span> || (task = getTask()) != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 如果拿到了任务，给自己上锁，表示当前Worker已经要开始执行任务了，</span></span><br><span class="line">            <span class="comment">// 已经不是处于闲置Worker(闲置Worker的解释请看下面的线程池关闭)</span></span><br><span class="line">            w.lock();  </span><br><span class="line">            <span class="comment">// 在执行任务之前先做一些处理。 </span></span><br><span class="line">            <span class="comment">// 1. 如果线程池已经处于STOP状态并且当前线程没有被中断，中断线程 </span></span><br><span class="line">            <span class="comment">// 2. 如果线程池还处于RUNNING或SHUTDOWN状态，并且当前线程已经被中断了，</span></span><br><span class="line">            <span class="comment">// 重新检查一下线程池状态，如果处于STOP状态并且没有被中断，那么中断线程</span></span><br><span class="line">            <span class="keyword">if</span> ((runStateAtLeast(ctl.get(), STOP) ||</span><br><span class="line">                 (Thread.interrupted() &amp;&amp;</span><br><span class="line">                  runStateAtLeast(ctl.get(), STOP))) &amp;&amp;</span><br><span class="line">                !wt.isInterrupted())</span><br><span class="line">                wt.interrupt();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 任务执行前需要做什么，ThreadPoolExecutor是个空实现，子类可以自行扩展</span></span><br><span class="line">                beforeExecute(wt, task); </span><br><span class="line">                <span class="type">Throwable</span> <span class="variable">thrown</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// 真正的开始执行任务，这里run的时候可能会被中断，比如线程池调用了shutdownNow方法</span></span><br><span class="line">                    task.run(); </span><br><span class="line">                &#125; <span class="keyword">catch</span> (RuntimeException x) &#123; <span class="comment">// 任务执行发生的异常全部抛出，不在runWorker中处理</span></span><br><span class="line">                    thrown = x; <span class="keyword">throw</span> x;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Error x) &#123;</span><br><span class="line">                    thrown = x; <span class="keyword">throw</span> x;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Throwable x) &#123;</span><br><span class="line">                    thrown = x; <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(x);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    <span class="comment">// 任务执行结束需要做什么，ThreadPoolExecutor是个空实现，子类可以自行扩展</span></span><br><span class="line">                    afterExecute(task, thrown); </span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                task = <span class="literal">null</span>;</span><br><span class="line">                w.completedTasks++; <span class="comment">// 记录执行任务的个数</span></span><br><span class="line">                w.unlock(); <span class="comment">// 执行完任务之后，解锁，Worker变成闲置Worker，等待执行下一个任务</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        completedAbruptly = <span class="literal">false</span>; <span class="comment">// 正常结束</span></span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        processWorkerExit(w, completedAbruptly); <span class="comment">// Worker退出时执行</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="BlockQueue"><a href="#BlockQueue" class="headerlink" title="BlockQueue"></a>BlockQueue</h2><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160225.png"></p><p>三个添加元素的方法：</p><ul><li>add:把e加到队列里,添加成功返回true，容量如果满了添加失败会抛出IllegalStateException异常</li><li>offer:表示如果可能的话,将e加到队列里，成功返回true,否则返回false</li><li>put:把e加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.</li></ul><p>三个删除元素的方法：</p><ul><li>poll:取走队列头部的对象,若不能立即取出,则可以等待timeout参数规定的时间,取不到时返回null</li><li>remove:基于对象找到对应的元素并删除，删除成功返回true，否则返回false</li><li>take:取走队列中排在首位的对象,若队列为空,一直阻塞到队列有元素并删除</li></ul><blockquote><p>其中：</p><p>队列不接受null 元素。试图add、put 或offer 一个null 元素时，某些实现会抛出NullPointerException。</p><p>null 被用作指示poll 操作失败的警戒值。 </p></blockquote><table><thead><tr><th align="left"></th><th align="left">抛出异常</th><th align="left">特殊值</th><th align="left">阻塞</th><th>超时</th></tr></thead><tbody><tr><td align="left"><strong>插入</strong></td><td align="left"><code>add(e)</code></td><td align="left"><code>offer(e)</code></td><td align="left"><code>put(e)</code></td><td><code>offer(e, time, unit)</code></td></tr><tr><td align="left"><strong>移除</strong></td><td align="left"><code>remove（Object o）</code></td><td align="left"><code>poll()</code></td><td align="left"><code>take()</code></td><td><code>poll(time, unit)</code></td></tr><tr><td align="left"><strong>检查</strong></td><td align="left"><code>element()</code></td><td align="left"><code>peek()</code></td><td align="left"><em>不可用</em></td><td><em>不可用</em></td></tr></tbody></table><table><thead><tr><th>类型</th><th>含义</th></tr></thead><tbody><tr><td>抛出异常</td><td>如果试图的操作无法立即执行，抛一个异常</td></tr><tr><td>特殊值</td><td>如果试图的操作无法立即执行，返回一个特定的值(常常是 true &#x2F; false)</td></tr><tr><td>阻塞</td><td>如果试图的操作无法立即执行，该方法调用将会发生阻塞，直到能够执行</td></tr><tr><td>超时</td><td>如果试图的操作无法立即执行，该方法调用将会发生阻塞，直到能够执行，<br>但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true &#x2F; false)</td></tr></tbody></table><p>常见的四个实现：</p><h3 id="ArrayBlockingQueue"><a href="#ArrayBlockingQueue" class="headerlink" title="ArrayBlockingQueue"></a>ArrayBlockingQueue</h3><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160411.png"></p><p>一个由数组支持的有界阻塞队列。此队列按 **FIFO（先进先出）**原则对元素进行排序。队列的头部是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部，队列获取操作则是从队列头部开始获得元素。</p><p>ArrayBlockingQueue的原理就是使用一个可重入锁和这个锁生成的两个条件对象进行并发控制(classic two-condition algorithm)</p><p>ArrayBlockingQueue是一个带有长度的阻塞队列，初始化的时候必须要指定队列长度，且指定长度之后不允许进行修改</p><h4 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性</h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 存储队列元素的数组，是个循环数组</span></span><br><span class="line"><span class="keyword">final</span> Object[] items;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 拿数据的索引，用于take，poll，peek，remove方法</span></span><br><span class="line"><span class="type">int</span> takeIndex;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 放数据的索引，用于put，offer，add方法</span></span><br><span class="line"><span class="type">int</span> putIndex;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 元素个数</span></span><br><span class="line"><span class="type">int</span> count;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 可重入锁</span></span><br><span class="line"><span class="keyword">final</span> ReentrantLock lock;</span><br><span class="line"><span class="comment">// notEmpty条件对象，由lock创建</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Condition notEmpty;</span><br><span class="line"><span class="comment">// notFull条件对象，由lock创建</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Condition notFull;</span><br></pre></td></tr></table></figure><h4 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 构造函数要求指定队列大小capacity</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* capacity和fair，capacity同第一个构造方法，代表队列大小。fair代表该队列的访问策略是否公平。如果为     </span></span><br><span class="line"><span class="comment">* true，则按照 FIFO 顺序访问插入或移除时受阻塞线程的队列；如果为 false，则访问顺序是不确定的。这里fair参</span></span><br><span class="line"><span class="comment">* 数被设置为ReentrantLock的入参，就可以通过ReentrantLock来保证线程访问是否公平。而此构造方法创建了两个</span></span><br><span class="line"><span class="comment">* Condition，也就是条件，分别是notEmpty和notFull，Condition可以调用wait()和signal()来控制当前现</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">// 默认fair为false</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ArrayBlockingQueue</span><span class="params">(<span class="type">int</span> capacity, <span class="type">boolean</span> fair,</span></span><br><span class="line"><span class="params">                             Collection&lt;? extends E&gt; c)</span> &#123;</span><br><span class="line">       <span class="built_in">this</span>(capacity, fair);</span><br><span class="line">       <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">       lock.lock(); <span class="comment">// Lock only for visibility, not mutual exclusion</span></span><br><span class="line">       <span class="keyword">try</span> &#123;</span><br><span class="line">           <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">           <span class="keyword">try</span> &#123;</span><br><span class="line">               <span class="keyword">for</span> (E e : c) &#123;</span><br><span class="line">                   checkNotNull(e);</span><br><span class="line">                   items[i++] = e;</span><br><span class="line">               &#125;</span><br><span class="line">           &#125; <span class="keyword">catch</span> (ArrayIndexOutOfBoundsException ex) &#123;</span><br><span class="line">               <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>();</span><br><span class="line">           &#125;</span><br><span class="line">           count = i;</span><br><span class="line">           putIndex = (i == capacity) ? <span class="number">0</span> : i;</span><br><span class="line">       &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">           lock.unlock();</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>内部调用的<code>this(capacity, fair)</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">ArrayBlockingQueue</span><span class="params">(<span class="type">int</span> capacity, <span class="type">boolean</span> fair)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (capacity &lt;= <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>();</span><br><span class="line">    <span class="built_in">this</span>.items = <span class="keyword">new</span> <span class="title class_">Object</span>[capacity];</span><br><span class="line">    lock = <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>(fair);</span><br><span class="line">    notEmpty = lock.newCondition();</span><br><span class="line">    notFull =  lock.newCondition();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="数据的添加"><a href="#数据的添加" class="headerlink" title="数据的添加"></a>数据的添加</h4><h5 id="add"><a href="#add" class="headerlink" title="add"></a>add</h5><p>ArrayBlockingQueue自己并没有实现add方法，而直接调用父类AbstractQueue的add方法</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="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">super</span>.add(e);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>内部调用的super.add</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="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (offer(e))</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">throw</span>  IllegalStateException(<span class="string">&quot;Queue full&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实际上最后调用的就是ArrayBlockingQueue自己的offer方法，但是如果offer方法返回结果为false，则抛出IllegalStateException</p><h5 id="offer"><a href="#offer" class="headerlink" title="offer"></a>offer</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">offer</span><span class="params">(E e)</span> &#123;</span><br><span class="line">       <span class="comment">// 不允许元素为空，为空会抛出NullPointerException异常</span></span><br><span class="line">       checkNotNull(e);</span><br><span class="line">       <span class="comment">// 获取锁</span></span><br><span class="line">       <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">       lock.lock();</span><br><span class="line">       <span class="keyword">try</span> &#123;</span><br><span class="line">           <span class="comment">// 元素个数和当前存储队列元素的数组大小相等，就不会再加了，所以会返回false</span></span><br><span class="line">           <span class="keyword">if</span> (count == items.length)</span><br><span class="line">               <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">           <span class="keyword">else</span> &#123;</span><br><span class="line">               <span class="comment">// 入队操作</span></span><br><span class="line">               enqueue(e);</span><br><span class="line">               <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">           &#125;</span><br><span class="line">       &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">           <span class="comment">// 释放锁</span></span><br><span class="line">           lock.unlock();</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>内部调用的enqueue方法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">enqueue</span><span class="params">(E x)</span> &#123;</span><br><span class="line">       <span class="comment">// assert lock.getHoldCount() == 1;</span></span><br><span class="line">       <span class="comment">// assert items[putIndex] == null;</span></span><br><span class="line">       <span class="keyword">final</span> Object[] items = <span class="built_in">this</span>.items;</span><br><span class="line">       items[putIndex] = x;</span><br><span class="line">       <span class="comment">// 放数据索引+1，如果数据索引已经与存储队列的元素数量相同则变为0</span></span><br><span class="line">       <span class="keyword">if</span> (++putIndex == items.length)</span><br><span class="line">           putIndex = <span class="number">0</span>;</span><br><span class="line">       <span class="comment">// 元素个数+1</span></span><br><span class="line">       count++;</span><br><span class="line">       <span class="comment">// 使用条件对象notEmpty通知，比如使用take方法的时，队列中没有数据被阻塞。这个时候队列中新增了一条数据，需要调用signal通知</span></span><br><span class="line">       notEmpty.signal();</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><h5 id="put"><a href="#put" class="headerlink" title="put"></a>put</h5><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="keyword">void</span> <span class="title function_">put</span><span class="params">(E e)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">       <span class="comment">// 元素为空抛出NPE异常</span></span><br><span class="line">       checkNotNull(e);</span><br><span class="line">       <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">       <span class="comment">// 加锁，保证调用put方法的时候只有一个线程</span></span><br><span class="line">       lock.lockInterruptibly();</span><br><span class="line">       <span class="keyword">try</span> &#123;</span><br><span class="line">           <span class="comment">// 如果队列满了，阻塞当前线程并加入到条件对象notFull的等待队列里</span></span><br><span class="line">           <span class="keyword">while</span> (count == items.length)</span><br><span class="line">               <span class="comment">// 线程阻塞并被挂起，同时释放锁</span></span><br><span class="line">               notFull.await();</span><br><span class="line">           <span class="comment">// 入队</span></span><br><span class="line">           enqueue(e);</span><br><span class="line">       &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">           lock.unlock();</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">insert</span><span class="params">(E x)</span> &#123;</span><br><span class="line">    items[putIndex] = x;</span><br><span class="line">    putIndex = inc(putIndex);</span><br><span class="line">    ++count;</span><br><span class="line">    notEmpty.signal(); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为什么改了？改之前和改之后有什么区别？</p><h4 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h4><blockquote><p>ArrayBlockingQueue总共有三个添加数据的方法，分别是add、put、offer</p><ul><li><p>add方法：内部实际调用的是offer方法，如果队列已满则抛出IllegalStateException一场，否则返回true</p></li><li><p>offer方法：如果队列满了返回false，成功返回true</p></li><li><p>put方法：如果队列满了会阻塞线程，直到有线程消费了队列中的元素</p></li></ul><p>三个方法内部都使用可重入锁保证原子性</p></blockquote><h4 id="数据的删除"><a href="#数据的删除" class="headerlink" title="数据的删除"></a>数据的删除</h4><h5 id="poll"><a href="#poll" class="headerlink" title="poll"></a>poll</h5><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> E <span class="title function_">poll</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 加锁，保证调用poll方法的时候只有一个线程</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">    lock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 如果count=0，也就是队列中没有元素了，这时候会返回null，否则调用dequeue取元素</span></span><br><span class="line">        <span class="keyword">return</span> (count == <span class="number">0</span>) ? <span class="literal">null</span> : dequeue();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>内部调用的dequeue方法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> E <span class="title function_">dequeue</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// assert lock.getHoldCount() == 1;</span></span><br><span class="line">    <span class="comment">// assert items[takeIndex] != null;</span></span><br><span class="line">    <span class="keyword">final</span> Object[] items = <span class="built_in">this</span>.items;</span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">    <span class="comment">// takeIndex是用于拿数据的索引</span></span><br><span class="line">    <span class="type">E</span> <span class="variable">x</span> <span class="operator">=</span> (E) items[takeIndex];</span><br><span class="line">    <span class="comment">// 取出数据以后，这个元素位置为null</span></span><br><span class="line">    items[takeIndex] = <span class="literal">null</span>;</span><br><span class="line">    <span class="comment">// 如果当前拿数据的索引大小已经和元素数组大小相等则置为0，这里的takeIndex之所以自增是因为FIFO原则</span></span><br><span class="line">    <span class="keyword">if</span> (++takeIndex == items.length)</span><br><span class="line">        takeIndex = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 一个数据被取出，此时元素个数-1</span></span><br><span class="line">    count--;</span><br><span class="line">    <span class="comment">// TODO</span></span><br><span class="line">    <span class="keyword">if</span> (itrs != <span class="literal">null</span>)</span><br><span class="line">        itrs.elementDequeued();</span><br><span class="line">    <span class="comment">// 使用对象notFull通知，如使用put方法放置数据的时候队列满了，被阻塞，这个时候dequeue取出一条数据，队列没满，则可以继续放入</span></span><br><span class="line">    notFull.signal();</span><br><span class="line">    <span class="keyword">return</span> x;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="take"><a href="#take" class="headerlink" title="take"></a>take</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> E <span class="title function_">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">    lock.lockInterruptibly();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 如果队列为空，阻塞当前线程，并加入到条件对象notEmpty的等待队列里</span></span><br><span class="line">        <span class="keyword">while</span> (count == <span class="number">0</span>)</span><br><span class="line">            <span class="comment">// 线程阻塞并被挂起，同时释放锁</span></span><br><span class="line">            notEmpty.await();</span><br><span class="line">        <span class="keyword">return</span> dequeue();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="remove"><a href="#remove" class="headerlink" title="remove"></a>remove</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">remove</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (o == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">final</span> Object[] items = <span class="built_in">this</span>.items;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">    lock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 放数据索引</span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">putIndex</span> <span class="operator">=</span> <span class="built_in">this</span>.putIndex;</span><br><span class="line">            <span class="comment">// 取数据索引</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> takeIndex;</span><br><span class="line">            <span class="comment">// 通过元素类型遍历,找到类型相同的索引位置，i&lt;=元素总大小</span></span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (o.equals(items[i])) &#123;</span><br><span class="line">                    removeAt(i);</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (++i == items.length)</span><br><span class="line">                    i = <span class="number">0</span>;</span><br><span class="line">              <span class="comment">// 因为putIndex就是最后一个放数据的位置，所以拿数据的索引不能等于它</span></span><br><span class="line">              <span class="comment">// 但是有没有想过为什么不可以写&lt;=呢？</span></span><br><span class="line">            &#125; <span class="keyword">while</span> (i != putIndex);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>内部调用的removeAt方法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">removeAt</span><span class="params">(<span class="keyword">final</span> <span class="type">int</span> removeIndex)</span> &#123;</span><br><span class="line">    <span class="comment">// assert lock.getHoldCount() == 1;</span></span><br><span class="line">    <span class="comment">// assert items[removeIndex] != null;</span></span><br><span class="line">    <span class="comment">// assert removeIndex &gt;= 0 &amp;&amp; removeIndex &lt; items.length;</span></span><br><span class="line">    <span class="keyword">final</span> Object[] items = <span class="built_in">this</span>.items;</span><br><span class="line">    <span class="comment">// 如果要删除数据的索引位置就是拿数据索引位置，直接将takeIndex索引位置上的数据，然后takeIndex+1</span></span><br><span class="line">    <span class="keyword">if</span> (removeIndex == takeIndex) &#123;</span><br><span class="line">        <span class="comment">// removing front item; just advance</span></span><br><span class="line">        items[takeIndex] = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">if</span> (++takeIndex == items.length)</span><br><span class="line">            takeIndex = <span class="number">0</span>;</span><br><span class="line">        count--;</span><br><span class="line">        <span class="keyword">if</span> (itrs != <span class="literal">null</span>)</span><br><span class="line">            itrs.elementDequeued();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// an &quot;interior&quot; remove</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// slide over all others up through putIndex.</span></span><br><span class="line">        <span class="comment">// 如果要删除数据的索引位置不是takeIndex，则需要移动元素位置，更新putIndex</span></span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">putIndex</span> <span class="operator">=</span> <span class="built_in">this</span>.putIndex;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> removeIndex;;) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">next</span> <span class="operator">=</span> i + <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">if</span> (next == items.length)</span><br><span class="line">                next = <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">if</span> (next != putIndex) &#123;</span><br><span class="line">                items[i] = items[next];</span><br><span class="line">                i = next;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                items[i] = <span class="literal">null</span>;</span><br><span class="line">                <span class="built_in">this</span>.putIndex = i;</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">        count--;</span><br><span class="line">        <span class="keyword">if</span> (itrs != <span class="literal">null</span>)</span><br><span class="line">            itrs.removedAt(removeIndex);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 删除以后通知阻塞线程，比如put方法</span></span><br><span class="line">    notFull.signal();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h4><blockquote><p>三个删除方法，分别是poll，take，remove</p><ul><li>poll方法对于队列为空的情况，返回null，否则返回队列头部元素</li><li>remove方法取的元素是基于对象的下标值，删除成功返回true，否则返回false</li><li>poll方法和remove方法不会阻塞线程</li><li>take方法对于队列为空的情况，会阻塞并挂起当前线程，直到有数据加入到队列中</li><li>三个删除方法内部都会调用notFull.signal方法通知正在等待队列满情况下的阻塞线程</li></ul></blockquote><h3 id="LinkedBlockingQueue"><a href="#LinkedBlockingQueue" class="headerlink" title="LinkedBlockingQueue"></a>LinkedBlockingQueue</h3><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160443.png"></p><p>内部以一个链式结构(链接节点)对其元素进行存储，链表是单向链表，满足FIFO(先进先出)原则。</p><h4 id="属性-1"><a href="#属性-1" class="headerlink" title="属性"></a>属性</h4><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></pre></td><td class="code"><pre><span class="line">  <span class="comment">/** The capacity bound, or Integer.MAX_VALUE if none */</span></span><br><span class="line"><span class="comment">// 容量大小</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> capacity;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/** Current number of elements */</span></span><br><span class="line"><span class="comment">// 元素个数</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Head of linked list.</span></span><br><span class="line"><span class="comment">   * Invariant: head.item == null</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="keyword">transient</span> Node&lt;E&gt; head;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Tail of linked list.</span></span><br><span class="line"><span class="comment">   * Invariant: last.next == null</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="keyword">private</span> <span class="keyword">transient</span> Node&lt;E&gt; last;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/** Lock held by take, poll, etc */</span></span><br><span class="line"><span class="comment">// 读锁</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">takeLock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">/** Wait queue for waiting takes */</span></span><br><span class="line"><span class="comment">// 读锁的条件对象</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Condition</span> <span class="variable">notEmpty</span> <span class="operator">=</span> takeLock.newCondition();</span><br><span class="line"></span><br><span class="line">  <span class="comment">/** Lock held by put, offer, etc */</span></span><br><span class="line"><span class="comment">// 写锁</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">putLock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">/** Wait queue for waiting puts */</span></span><br><span class="line"><span class="comment">// 写锁的条件对象</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Condition</span> <span class="variable">notFull</span> <span class="operator">=</span> putLock.newCondition();</span><br></pre></td></tr></table></figure><p>ArrayBlockingQueue只有1个锁，添加数据和删除数据的时候只有1个可以被执行，不允许并行操作</p><p>但LinkedBlockingQueue有2个锁，读和写各有一把，添加数据和删除数据两个操作可以并行。</p><h4 id="创建-1"><a href="#创建-1" class="headerlink" title="创建"></a>创建</h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">LinkedBlockingQueue</span><span class="params">(Collection&lt;? extends E&gt; c)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>(Integer.MAX_VALUE);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">putLock</span> <span class="operator">=</span> <span class="built_in">this</span>.putLock;</span><br><span class="line">    putLock.lock(); <span class="comment">// Never contended, but necessary for visibility</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (E e : c) &#123;</span><br><span class="line">            <span class="keyword">if</span> (e == <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">            <span class="keyword">if</span> (n == capacity)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Queue full&quot;</span>);</span><br><span class="line">            enqueue(<span class="keyword">new</span> <span class="title class_">Node</span>&lt;E&gt;(e));</span><br><span class="line">            ++n;</span><br><span class="line">        &#125;</span><br><span class="line">        count.set(n);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        putLock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>内部调用的this和enqueue方法</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="keyword">public</span> <span class="title function_">LinkedBlockingQueue</span><span class="params">(<span class="type">int</span> capacity)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (capacity &lt;= <span class="number">0</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>();</span><br><span class="line">    <span class="built_in">this</span>.capacity = capacity;</span><br><span class="line">    <span class="comment">// last和head节点都是null</span></span><br><span class="line">    last = head = <span class="keyword">new</span> <span class="title class_">Node</span>&lt;E&gt;(<span class="literal">null</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">enqueue</span><span class="params">(Node&lt;E&gt; node)</span> &#123;</span><br><span class="line">    <span class="comment">// assert putLock.isHeldByCurrentThread();</span></span><br><span class="line">    <span class="comment">// assert last.next == null;</span></span><br><span class="line">    last = last.next = node;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Node</span>&lt;E&gt; &#123;</span><br><span class="line">    E item;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * One of:</span></span><br><span class="line"><span class="comment">     * - the real successor Node</span></span><br><span class="line"><span class="comment">     * - this Node, meaning the successor is head.next</span></span><br><span class="line"><span class="comment">     * - null, meaning there is no successor (this is the last node)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Node&lt;E&gt; next;</span><br><span class="line"></span><br><span class="line">    Node(E x) &#123; item = x; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>enqueue的class方法</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">private</span> <span class="keyword">void</span> <span class="title function_">enqueue</span><span class="params">(Node&lt;E&gt; paramNode)</span>&#123;</span><br><span class="line">     <span class="built_in">this</span>.last = (<span class="built_in">this</span>.last.next = paramNode);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>另外还画了一个图</p><p><img src="http://qiniu.losergzr.cn/picgo/20190401180533.png"></p><h4 id="数据的添加-1"><a href="#数据的添加-1" class="headerlink" title="数据的添加"></a>数据的添加</h4><p>三个方法，分别是add offer put</p><h5 id="add-1"><a href="#add-1" class="headerlink" title="add"></a>add</h5><p>LinkedBlockingQueue和ArrayBlockingQueue一样，同样没有实现add方法，所以会直接调用父类AbstractQueue的add方法</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="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (offer(e))</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">      <span class="keyword">else</span></span><br><span class="line">          <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Queue full&quot;</span>);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>实际上也就是调用LinkedBlockingQueue的offer方法，失败则抛出异常</p><h5 id="offer-1"><a href="#offer-1" class="headerlink" title="offer"></a>offer</h5><blockquote><p>关于这里使用到的锁实际上涉及到java的monitor MESA模型，这里就不展开讲了，有兴趣的小伙伴戳<a href="https://time.geekbang.org/column/article/7db3be62eae600db0d947f428185c06a/share?code=4e5ChyziCCisKCETno4QJHdW0BXcHigtqXBCNDlJuIE=&from=singlemessage&isappinstalled=0&oss_token=">极客时间-管程：并发编程的万能钥匙</a>，有二十个小伙伴可以免费读哦～打不开的话复制地址用微信打开哦😊</p><p>可重入锁指的是线程可以重复获取同一把锁，ReentrantLock有一个带布尔值fair的构造函数，true表示公平锁，反之则是非公平锁。指的是条件变量的等待队列唤醒策略，公平锁是唤醒等待时间最长的，非公平锁则不会保证</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">offer</span><span class="params">(E e)</span> &#123;</span><br><span class="line">    <span class="comment">// 不允许空元素</span></span><br><span class="line">    <span class="keyword">if</span> (e == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="built_in">this</span>.count;</span><br><span class="line">    <span class="comment">// 如果满了就返回false，but这个大小可是2^31-1=2147483647</span></span><br><span class="line">    <span class="keyword">if</span> (count.get() == capacity)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">    Node&lt;E&gt; node = <span class="keyword">new</span> <span class="title class_">Node</span>&lt;E&gt;(e);</span><br><span class="line">    <span class="comment">// 拿到写锁</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">putLock</span> <span class="operator">=</span> <span class="built_in">this</span>.putLock;</span><br><span class="line">    <span class="comment">// 对写操作加锁</span></span><br><span class="line">    putLock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (count.get() &lt; capacity) &#123;</span><br><span class="line">            enqueue(node);</span><br><span class="line">            <span class="comment">// 元素个数+1</span></span><br><span class="line">            c = count.getAndIncrement();</span><br><span class="line">            <span class="keyword">if</span> (c + <span class="number">1</span> &lt; capacity)</span><br><span class="line">                <span class="comment">// 如果容量还没满，在对象notFull唤醒正在等待的线程，表示可以再往队列里加数据了</span></span><br><span class="line">                notFull.signal();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        putLock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>)</span><br><span class="line">        signalNotEmpty();</span><br><span class="line">  <span class="comment">// todo</span></span><br><span class="line">    <span class="keyword">return</span> c &gt;= <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="put-1"><a href="#put-1" class="headerlink" title="put"></a>put</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">put</span><span class="params">(E e)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="keyword">if</span> (e == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="comment">// Note: convention in all put/take/etc is to preset local var</span></span><br><span class="line">    <span class="comment">// holding count negative to indicate failure unless set.</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">    Node&lt;E&gt; node = <span class="keyword">new</span> <span class="title class_">Node</span>&lt;E&gt;(e);</span><br><span class="line">    <span class="comment">// 拿到写锁</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">putLock</span> <span class="operator">=</span> <span class="built_in">this</span>.putLock;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="built_in">this</span>.count;</span><br><span class="line">    <span class="comment">// 对写操作加锁</span></span><br><span class="line">    putLock.lockInterruptibly();</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"><span class="comment">         * Note that count is used in wait guard even though it is</span></span><br><span class="line"><span class="comment">         * not protected by lock. This works because count can</span></span><br><span class="line"><span class="comment">         * only decrease at this point (all other puts are shut</span></span><br><span class="line"><span class="comment">         * out by lock), and we (or some other waiting put) are</span></span><br><span class="line"><span class="comment">         * signalled if it ever changes from capacity. Similarly</span></span><br><span class="line"><span class="comment">         * for all other uses of count in other wait guards.</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="keyword">while</span> (count.get() == capacity) &#123;</span><br><span class="line">            notFull.await();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 入队操作</span></span><br><span class="line">        enqueue(node);</span><br><span class="line">        <span class="comment">// 元素+1</span></span><br><span class="line">        c = count.getAndIncrement();</span><br><span class="line">        <span class="keyword">if</span> (c + <span class="number">1</span> &lt; capacity)</span><br><span class="line">            <span class="comment">// 如果容量还没满，在放锁的条件对象notFull唤醒正在等待的线程</span></span><br><span class="line">            notFull.signal();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        putLock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="number">0</span>)</span><br><span class="line">        signalNotEmpty();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="总结-3"><a href="#总结-3" class="headerlink" title="总结"></a>总结</h4><blockquote><p>add offer put方法与ArrayBlockingQueue特性一致，只是底层实现不同</p><ul><li><p>add方法：内部实际调用的是offer方法，如果队列已满则抛出IllegalStateException一场，否则返回true</p></li><li><p>offer方法：如果队列已满则返回false，成功返回true</p></li><li><p>put方法：如果队列满了会阻塞线程，直到有线程消费了队列中的元素</p></li></ul><p>ArrayBlockingQueue中放入数据阻塞的时候，需要消费数据才能唤醒，而LinkedBlockingQueue中放入数据阻塞的时候，因为它内部有2个锁，可以并行执行放入数据和消费数据，不仅在消费数据的时候进行唤醒插入阻塞的线程，同时在插入的时候如果容量还没满，也会唤醒插入阻塞的线程</p></blockquote><h4 id="数据的删除-1"><a href="#数据的删除-1" class="headerlink" title="数据的删除"></a>数据的删除</h4><h5 id="poll-1"><a href="#poll-1" class="headerlink" title="poll"></a>poll</h5><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> E <span class="title function_">poll</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="built_in">this</span>.count;</span><br><span class="line">    <span class="keyword">if</span> (count.get() == <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">E</span> <span class="variable">x</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">takeLock</span> <span class="operator">=</span> <span class="built_in">this</span>.takeLock;</span><br><span class="line">    takeLock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (count.get() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            x = dequeue();</span><br><span class="line">            c = count.getAndDecrement();</span><br><span class="line">            <span class="keyword">if</span> (c &gt; <span class="number">1</span>)</span><br><span class="line">                notEmpty.signal();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        takeLock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (c == capacity)</span><br><span class="line">        signalNotFull();</span><br><span class="line">    <span class="comment">// 成功就会返回实际的元素，否则返回null</span></span><br><span class="line">    <span class="keyword">return</span> x;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>内部调用的dequeue方法</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">private</span> E <span class="title function_">dequeue</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// assert takeLock.isHeldByCurrentThread();</span></span><br><span class="line">    <span class="comment">// assert head.item == null;</span></span><br><span class="line">    Node&lt;E&gt; h = head;</span><br><span class="line">    Node&lt;E&gt; first = h.next;</span><br><span class="line">    h.next = h; <span class="comment">// help GC</span></span><br><span class="line">    head = first;</span><br><span class="line">    <span class="type">E</span> <span class="variable">x</span> <span class="operator">=</span> first.item;</span><br><span class="line">    first.item = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">return</span> x;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不是特别好理解的话，就看图吧 </p><p><img src="https://raw.githubusercontent.com/baofeidyz/images/master/img/20190721160516.png"></p><h5 id="take-1"><a href="#take-1" class="headerlink" title="take"></a>take</h5><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">public</span> E <span class="title function_">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    E x;</span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="built_in">this</span>.count;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">takeLock</span> <span class="operator">=</span> <span class="built_in">this</span>.takeLock;</span><br><span class="line">    takeLock.lockInterruptibly();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (count.get() == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 阻塞等待</span></span><br><span class="line">            notEmpty.await();</span><br><span class="line">        &#125;</span><br><span class="line">        x = dequeue();</span><br><span class="line">        c = count.getAndDecrement();</span><br><span class="line">        <span class="keyword">if</span> (c &gt; <span class="number">1</span>)</span><br><span class="line">            notEmpty.signal();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        takeLock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (c == capacity)</span><br><span class="line">        signalNotFull();</span><br><span class="line">    <span class="keyword">return</span> x;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="remove-1"><a href="#remove-1" class="headerlink" title="remove"></a>remove</h5><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">remove</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (o == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// remove操作的位置不固定，所以需要对两个锁都进行加锁</span></span><br><span class="line">    fullyLock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (Node&lt;E&gt; trail = head, p = trail.next;</span><br><span class="line">             p != <span class="literal">null</span>;</span><br><span class="line">             trail = p, p = p.next) &#123;</span><br><span class="line">            <span class="comment">// 判断是否找到对象</span></span><br><span class="line">            <span class="keyword">if</span> (o.equals(p.item)) &#123;</span><br><span class="line">                <span class="comment">// 修改节点的链接信息，同时调用notFull的signal方法</span></span><br><span class="line">                unlink(p, trail);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        fullyUnlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>内部调用的fullyLock方法 unlink方法以及fullyUnlock方法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">fullyLock</span><span class="params">()</span> &#123;</span><br><span class="line">    putLock.lock();</span><br><span class="line">    takeLock.lock();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">unlink</span><span class="params">(Node&lt;E&gt; p, Node&lt;E&gt; trail)</span> &#123;</span><br><span class="line">  <span class="comment">// assert isFullyLocked();</span></span><br><span class="line">  <span class="comment">// p.next is not changed, to allow iterators that are</span></span><br><span class="line">  <span class="comment">// traversing p to maintain their weak-consistency guarantee.</span></span><br><span class="line">  p.item = <span class="literal">null</span>;</span><br><span class="line">  trail.next = p.next;</span><br><span class="line">  <span class="keyword">if</span> (last == p)</span><br><span class="line">    last = trail;</span><br><span class="line">  <span class="comment">// 判断当前元素总数是否等于最大值</span></span><br><span class="line">  <span class="keyword">if</span> (count.getAndDecrement() == capacity)</span><br><span class="line">    notFull.signal();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">fullyUnlock</span><span class="params">()</span> &#123;</span><br><span class="line">  takeLock.unlock();</span><br><span class="line">  putLock.unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="总结-4"><a href="#总结-4" class="headerlink" title="总结"></a>总结</h4><blockquote><p>take poll方法的特性与ArrayBlockingQueue一致，唯一不同的是remove方法中会同时对取 拿两个锁进行加锁</p></blockquote><p>ArrayBlockingQueue因为内部实现是通过数组实现，所以其在初始化的时候必须要指定大小，且不可变，在删除操作的时候会移动元素。</p><p>LinkedBlockingQueue内部实现是通过单向链表实现，本身没有边界，但默认最大值为2^31-1。可同时操作读和写，在删除操作时需要给读写同时加锁。</p><h3 id="DelayQueue"><a href="#DelayQueue" class="headerlink" title="DelayQueue"></a>DelayQueue</h3><p>Delayed 元素的一个无界阻塞队列，只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满，则队 列没有头部，并且 poll 将返回 null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于等于 0 的值时，将发生到期。即使无法使用 take 或 poll 移除未到期的元素，也不会将这些元素作为正常元素对待</p><h3 id="SynchronousQueue"><a href="#SynchronousQueue" class="headerlink" title="SynchronousQueue"></a>SynchronousQueue</h3><p>SynchronousQueue 是一个特殊的队列，它的内部同时只能够容纳单个元素。如果该队列已有一元素的话，试图向队列中插入一个新元素的线程将会阻塞，直到另一个线程将该元素从队列中抽走。同样，如果该队列为空，试图向队列中抽取一个元素的线程将会阻塞，直到另一个线程向队列中插入了一条新的元素。</p><h2 id="拒绝策略"><a href="#拒绝策略" class="headerlink" title="拒绝策略"></a>拒绝策略</h2><p>主要有四种</p><h3 id="ThreadPoolExecutor-AbortPolicy"><a href="#ThreadPoolExecutor-AbortPolicy" class="headerlink" title="ThreadPoolExecutor.AbortPolicy"></a>ThreadPoolExecutor.AbortPolicy</h3><p>默认的ThreadPoolExecutor.AbortPolicy   处理程序遭到拒绝将抛出运行时RejectedExecutionException</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">AbortPolicy</span> <span class="keyword">implements</span> <span class="title class_">RejectedExecutionHandler</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Creates an &#123;<span class="doctag">@code</span> AbortPolicy&#125;.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AbortPolicy</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Always throws RejectedExecutionException.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> r the runnable task requested to be executed</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> e the executor attempting to execute this task</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> RejectedExecutionException always</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">rejectedExecution</span><span class="params">(Runnable r, ThreadPoolExecutor e)</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RejectedExecutionException</span>(<span class="string">&quot;Task &quot;</span> + r.toString() +</span><br><span class="line">                                             <span class="string">&quot; rejected from &quot;</span> +</span><br><span class="line">                                             e.toString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ThreadPoolExecutor-CallerRunsPolicy"><a href="#ThreadPoolExecutor-CallerRunsPolicy" class="headerlink" title="ThreadPoolExecutor.CallerRunsPolicy"></a>ThreadPoolExecutor.CallerRunsPolicy</h3><p>线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制，能够减缓新任务的提交速度</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">CallerRunsPolicy</span> <span class="keyword">implements</span> <span class="title class_">RejectedExecutionHandler</span> &#123;</span><br><span class="line">       <span class="comment">/**</span></span><br><span class="line"><span class="comment">        * Creates a &#123;<span class="doctag">@code</span> CallerRunsPolicy&#125;.</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">       <span class="keyword">public</span> <span class="title function_">CallerRunsPolicy</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">       <span class="comment">/**</span></span><br><span class="line"><span class="comment">        * Executes task r in the caller&#x27;s thread, unless the executor</span></span><br><span class="line"><span class="comment">        * has been shut down, in which case the task is discarded.</span></span><br><span class="line"><span class="comment">        *</span></span><br><span class="line"><span class="comment">        * <span class="doctag">@param</span> r the runnable task requested to be executed</span></span><br><span class="line"><span class="comment">        * <span class="doctag">@param</span> e the executor attempting to execute this task</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">       <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">rejectedExecution</span><span class="params">(Runnable r, ThreadPoolExecutor e)</span> &#123;</span><br><span class="line">           <span class="keyword">if</span> (!e.isShutdown()) &#123;</span><br><span class="line">               r.run();</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><h3 id="ThreadPoolExecutor-DiscardPolicy"><a href="#ThreadPoolExecutor-DiscardPolicy" class="headerlink" title="ThreadPoolExecutor.DiscardPolicy"></a>ThreadPoolExecutor.DiscardPolicy</h3><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="keyword">static</span> <span class="keyword">class</span> <span class="title class_">DiscardPolicy</span> <span class="keyword">implements</span> <span class="title class_">RejectedExecutionHandler</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Creates a &#123;<span class="doctag">@code</span> DiscardPolicy&#125;.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">DiscardPolicy</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Does nothing, which has the effect of discarding task r.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> r the runnable task requested to be executed</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> e the executor attempting to execute this task</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">rejectedExecution</span><span class="params">(Runnable r, ThreadPoolExecutor e)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ThreadPoolExecutor-DiscardOldestPolicy"><a href="#ThreadPoolExecutor-DiscardOldestPolicy" class="headerlink" title="ThreadPoolExecutor.DiscardOldestPolicy"></a>ThreadPoolExecutor.DiscardOldestPolicy</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">DiscardOldestPolicy</span> <span class="keyword">implements</span> <span class="title class_">RejectedExecutionHandler</span> &#123;</span><br><span class="line">       <span class="comment">/**</span></span><br><span class="line"><span class="comment">        * Creates a &#123;<span class="doctag">@code</span> DiscardOldestPolicy&#125; for the given executor.</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">       <span class="keyword">public</span> <span class="title function_">DiscardOldestPolicy</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">       <span class="comment">/**</span></span><br><span class="line"><span class="comment">        * Obtains and ignores the next task that the executor</span></span><br><span class="line"><span class="comment">        * would otherwise execute, if one is immediately available,</span></span><br><span class="line"><span class="comment">        * and then retries execution of task r, unless the executor</span></span><br><span class="line"><span class="comment">        * is shut down, in which case task r is instead discarded.</span></span><br><span class="line"><span class="comment">        *</span></span><br><span class="line"><span class="comment">        * <span class="doctag">@param</span> r the runnable task requested to be executed</span></span><br><span class="line"><span class="comment">        * <span class="doctag">@param</span> e the executor attempting to execute this task</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">       <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">rejectedExecution</span><span class="params">(Runnable r, ThreadPoolExecutor e)</span> &#123;</span><br><span class="line">           <span class="keyword">if</span> (!e.isShutdown()) &#123;</span><br><span class="line">               e.getQueue().poll();</span><br><span class="line">               e.execute(r);</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><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="https://time.geekbang.org/column/intro/159?code=4e5ChyziCCisKCETno4QJHdW0BXcHigtqXBCNDlJuIE=&from=singlemessage&isappinstalled=0">极客时间-Java并发编程实战</a></p><p><a href="https://www.amazon.cn/dp/B07D9KBGR6/ref=sr_1_1?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&keywords=%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8E%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1&qid=1554224223&s=gateway&sr=8-1">《Java高并发编程详解：多线程与架构设计 (Java核心技术系列) 》</a></p><p><a href="https://fangjian0423.github.io/2016/05/10/java-arrayblockingqueue-linkedblockingqueue-analysis/">Java阻塞队列ArrayBlockingQueue和LinkedBlockingQueue实现原理分析</a></p><p><a href="https://www.cnblogs.com/java-zhao/p/5135958.html">LinkedBlockingQueue源码解析</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>使用dnsproxy自建DNS服务</title>
      <link>https://baofeidyz.com/2019/4ddc630f7b39/</link>
      <description>
        <![CDATA[<h1 id="使用dnsproxy自建DNS服务"><a href="#使用dnsproxy自建DNS服务" class="headerlink" title="使用dnsproxy自建DNS服务"></a>使用dnsproxy自建DNS服务</h1><h2]]>
      </description>
      <author>baofeidyz's blog</author>
      <category domain="https://baofeidyz.com/categories/%E6%8A%80%E6%9C%AF/">技术</category>
      <category domain="https://baofeidyz.com/tags/%E6%8A%98%E8%85%BE/">折腾</category>
      <pubDate>Fri, 19 Jul 2019 01:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="使用dnsproxy自建DNS服务"><a href="#使用dnsproxy自建DNS服务" class="headerlink" title="使用dnsproxy自建DNS服务"></a>使用dnsproxy自建DNS服务</h1><h2 id="为什么我想要自建DNS服务？"><a href="#为什么我想要自建DNS服务？" class="headerlink" title="为什么我想要自建DNS服务？"></a>为什么我想要自建DNS服务？</h2><p>因为我们公司内网有基于Windows的DNS服务。主要因为部分域名是仅限内网解析的，所以我不得不使用公司内网提供的DNS。如果这个DNS服务好用也就没啥了，但问题在于，他总是能解析出一些无法访问的IP，上网体验糟糕。</p><p>我有尝试修改我本地的DNS，或者是尝试将内网的域名写入hosts文件中，最终都无法满足我的需求。</p><p>经人安利，我找到了这个开源项目：<a href="https://github.com/AdguardTeam/dnsproxy">https://github.com/AdguardTeam/dnsproxy</a> </p><h2 id="如何使用dnsproxy？"><a href="#如何使用dnsproxy？" class="headerlink" title="如何使用dnsproxy？"></a>如何使用dnsproxy？</h2><blockquote><p>其实我觉得我有点废话了，大家可以直接看GitHub的上readme</p></blockquote><p>这个项目，本质上就是将我访问的域名去访问多个DNS服务，并将最快的IP返回作为解析的返回结果。要想把这个服务跑起来十分容易。</p><p>这个是项目的发布地址，有各个平台已经编译好的版本：<a href="https://github.com/AdguardTeam/dnsproxy/releases">https://github.com/AdguardTeam/dnsproxy/releases</a></p><p>紧接着，打开所在目录，找可执行的文件即可。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnsproxy -u x.x.x.x -u y.y.y.y -u z.z.z.z -u 114.114.114.114 -u 1.1.1.1 -u 8.8.8.8</span><br></pre></td></tr></table></figure><p>需要提醒的是 <code>x.x.x.x</code>、<code>y.y.y.y</code>、<code>z.z.z.z</code>这三个DNS服务是我给我们公司内网DNS服务打的马赛克，请不要直接复用哦。</p><p>如果需要在后台运行，可以使用<code>nohup</code></p>]]>
      </content:encoded>
    </item>
  </channel>
</rss>
