<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Alignm-ent</title>
  
  <subtitle>Regret permeates every stage of life.</subtitle>
  <link href="https://yourdomain.com/atom.xml" rel="self"/>
  
  <link href="https://yourdomain.com/"/>
  <updated>2026-07-26T10:29:40.000Z</updated>
  <id>https://yourdomain.com/</id>
  
  <author>
    <name>Alignm-ent</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>AgentSphere 个人项目的分享和理解</title>
    <link href="https://yourdomain.com/2026/agentsphere-sharing.html"/>
    <id>https://yourdomain.com/2026/agentsphere-sharing.html</id>
    <published>2026-07-26T10:24:04.000Z</published>
    <updated>2026-07-26T10:29:40.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、为什么还要造轮子？"><a href="#一、为什么还要造轮子？" class="headerlink" title="一、为什么还要造轮子？"></a>一、为什么还要造轮子？</h2><p>2024~2025 被称为 Agent 元年，LangChain、AutoGPT、CrewAI、Dify、Coze……各种框架层出不穷，教程铺天盖地。但真正动手做一个 RAG 知识库 Agent 时，最难的不是”加什么”，而是”不加什么”。</p><p>大多数 RAG 方案在做同一件事：把用户问题切碎，和文档的关键词做匹配。但<strong>知识不是关键词的集合，知识是关联、是结构、是上下文</strong>。</p><p>举个例子：一份企业年报写着”A 公司收购了 B 公司”，一份行业研报写着”B 公司的核心技术团队来自 C 实验室”。真正够用的系统，应该能回答”C 实验室的技术最终去了哪里？”——这是一个跨文档推理问题，不是一个关键词匹配问题。</p><p>AgentSphere 的核心哲学：<strong>不做关键词匹配器，做知识推理器</strong>。</p><h2 id="二、第一个分岔口：OCR-到底要不要？"><a href="#二、第一个分岔口：OCR-到底要不要？" class="headerlink" title="二、第一个分岔口：OCR 到底要不要？"></a>二、第一个分岔口：OCR 到底要不要？</h2><p>大多数 RAG 教程教你用 PyPDFLoader &#x2F; PyMuPDFLoader 直接读取 PDF。如果是数字原生 PDF，当然没问题。但如果是扫描件——合同、发票、老旧档案——这些工具读出来就是乱码，乱码进向量库，检索质量直接崩。</p><p>PaddleOCR 很重——几百 MB 的下载量，推理吃显存。</p><p><strong>AgentSphere 的做法</strong>：先检查 PDF 是否有可提取的文本层——有就直接解析，跳过 OCR；没有才拉起 PaddleOCR。</p><p>核心理念：<strong>每个重组件都应该有”跳过”的权利</strong>。</p><h2 id="三、GraphRAG：最容易被过度工程化的模块"><a href="#三、GraphRAG：最容易被过度工程化的模块" class="headerlink" title="三、GraphRAG：最容易被过度工程化的模块"></a>三、GraphRAG：最容易被过度工程化的模块</h2><p>GraphRAG（Microsoft，2024）用知识图谱增强 RAG——把孤立的数据库点连成关系网络，让 AI “理解”数据之间的关联，而不是仅仅检索单个事实。</p><p>但三个问题很明显：</p><ol><li><strong>实体提取很贵</strong>——每个 chunk 一次 LLM 调用，1000 个 chunk &#x3D; 1000 次 API 调用</li><li><strong>图谱质量取决于 LLM 质量</strong>——小模型做实体提取，结果一塌糊涂</li><li><strong>大部分查询不需要图谱</strong>——“这份合同的甲方是谁？”这类简单问题，关键词搜索就能解决</li></ol><p><strong>AgentSphere 的设计</strong>：GraphRAG 是可选增强，不是默认管线。用一个 Router Agent 做问题分类——事实类查询走 BM25 + 向量检索，关系推理类（”A 和 B 之间有什么关联？”）才触发图谱多跳推理。</p><blockquote><p>GraphRAG 是锤子，但不是所有问题都是钉子。</p></blockquote><h2 id="四、Reranker：最值的-100ms-延迟"><a href="#四、Reranker：最值的-100ms-延迟" class="headerlink" title="四、Reranker：最值的 100ms 延迟"></a>四、Reranker：最值的 100ms 延迟</h2><p>混合检索——BM25 关键词 + BGE 向量——之后，RRF 融合出的 Top-10 排序往往有偏差：BM25 偏向给短文本打高分，向量检索偏向长文本。</p><p>BGE-Reranker 是 Cross-Encoder，把 query 和 document 拼在一起编码，计算真正的语义匹配。代价：多 100<del>200ms 延迟。收益：**Top-5 精度通常提升 15%</del>25%**。</p><blockquote><p>如果只选一个优化策略，选 Reranker。</p></blockquote><h2 id="五、Agent-不是越多越好"><a href="#五、Agent-不是越多越好" class="headerlink" title="五、Agent 不是越多越好"></a>五、Agent 不是越多越好</h2><p>CrewAI、AutoGen 这些多 Agent 框架给人一种错觉——Agent 越多越智能。</p><p>现实是：<strong>Agent 的复杂度是指数级的</strong>。每多一个 Agent，就多一次 LLM 调用（成本 + 延迟），多一个故障点（A 输出烂 → B 崩 → C 幻觉），调试难度翻倍（到底哪个 Agent 的 prompt 出了问题？）。</p><p><strong>AgentSphere 的 5 个 Agent</strong>：</p><table><thead><tr><th>Agent</th><th>职责</th><th>是否必须</th></tr></thead><tbody><tr><td>Router</td><td>问题分类</td><td>必须（决定走哪条路）</td></tr><tr><td>Planner</td><td>执行规划</td><td>必须（复杂问题分步编排）</td></tr><tr><td>Query Rewrite</td><td>查询改写</td><td>可选（简单问题跳过）</td></tr><tr><td>Answer Verify</td><td>事实校验</td><td>可选（内部测试时开启）</td></tr><tr><td>Orchestrator</td><td>协调调度</td><td>必须（调度以上所有 Agent）</td></tr></tbody></table><p>设计准则：<strong>每个 Agent 只做一件事，每个 Agent 都可以被跳过</strong>。</p><h2 id="六、Token-消耗：你必须关心的事"><a href="#六、Token-消耗：你必须关心的事" class="headerlink" title="六、Token 消耗：你必须关心的事"></a>六、Token 消耗：你必须关心的事</h2><p>一次 QA 流程的 Token 消耗示例：</p><ul><li>Query Rewrite：500 tokens</li><li>实体提取（GraphRAG）：2,000 tokens × 10 chunks &#x3D; 20,000 tokens</li><li>LLM 生成：3,000 tokens</li><li>Answer Verify：2,000 tokens</li><li><strong>合计：约 25,500 tokens &#x2F; 次查询</strong></li></ul><p>AgentSphere 在代码层面做了 <strong>Token 监控</strong>——每次 LLM 调用都记入 LLM Monitor，按 Provider 和模型维度分析成本。看不见的成本才是最大的成本。</p><h2 id="七、最后的话"><a href="#七、最后的话" class="headerlink" title="七、最后的话"></a>七、最后的话</h2><p>AI 肉眼可见地在引领一个新的时代，但创造的本质不是学会调用 API——是<strong>形成了一套判断力</strong>：</p><ul><li>什么时候该上 OCR，什么时候不该</li><li>什么时候 GraphRAG 值它的成本，什么时候是过度工程</li><li>多一个 Agent 带来的价值，值不值它引入的复杂度</li><li>每一行代码是在解决问题，还是在制造问题</li></ul><p>AgentSphere 是我个人的学习项目，<strong>每个模块都保留了当初决策的分岔口——你完全可以选择另一条路</strong>。</p><blockquote><p>希望 AI 不是一个终点，而是形成自己判断的起点。</p></blockquote><p>项目地址：<a href="https://github.com/Alignm-ent/AgentSphere">github.com&#x2F;Alignm-ent&#x2F;AgentSphere</a></p>]]></content>
    
    
    <summary type="html">一个 RAG 知识库 Agent 的实践思考——不做关键词匹配器，做知识推理器</summary>
    
    
    
    <category term="文档" scheme="https://yourdomain.com/categories/%E6%96%87%E6%A1%A3/"/>
    
    
    <category term="RAG" scheme="https://yourdomain.com/tags/RAG/"/>
    
    <category term="AI" scheme="https://yourdomain.com/tags/AI/"/>
    
    <category term="Agent" scheme="https://yourdomain.com/tags/Agent/"/>
    
  </entry>
  
  <entry>
    <title>Python 输入输出与常用标准库使用手册</title>
    <link href="https://yourdomain.com/2026/python-input-output-notes.html"/>
    <id>https://yourdomain.com/2026/python-input-output-notes.html</id>
    <published>2026-07-23T07:30:00.000Z</published>
    <updated>2026-07-23T07:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="一、输入获取（核心两类场景）"><a href="#一、输入获取（核心两类场景）" class="headerlink" title="一、输入获取（核心两类场景）"></a>一、输入获取（核心两类场景）</h1><h2 id="1-基础-input"><a href="#1-基础-input" class="headerlink" title="1. 基础 input()"></a>1. 基础 input()</h2><p><code>input()</code> 读取<strong>一整行</strong>，返回字符串，末尾换行自动剔除。</p><h3 id="1-1-读取单个数字（数字单独一行）"><a href="#1-1-读取单个数字（数字单独一行）" class="headerlink" title="1.1 读取单个数字（数字单独一行）"></a>1.1 读取单个数字（数字单独一行）</h3><figure class="highlight python"><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="comment"># 输入每行仅一个数字</span></span><br><span class="line">a = <span class="built_in">int</span>(<span class="built_in">input</span>())</span><br><span class="line">f = <span class="built_in">float</span>(<span class="built_in">input</span>())</span><br><span class="line">s = <span class="built_in">input</span>()  <span class="comment"># 读取字符串，原样保留空格</span></span><br></pre></td></tr></table></figure><p><strong>报错场景</strong>：一行输入多个数字 <code>2 3</code>，直接 <code>int(input())</code> 会报错 <code>invalid literal</code>。</p><h3 id="1-2-一行多个数字（OJ高频）"><a href="#1-2-一行多个数字（OJ高频）" class="headerlink" title="1.2 一行多个数字（OJ高频）"></a>1.2 一行多个数字（OJ高频）</h3><figure class="highlight python"><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"># 一行两个整数，空格分隔</span></span><br><span class="line">a, b = <span class="built_in">map</span>(<span class="built_in">int</span>, <span class="built_in">input</span>().split())</span><br><span class="line"></span><br><span class="line"><span class="comment"># 一行任意数量数字，存入列表</span></span><br><span class="line">nums = <span class="built_in">list</span>(<span class="built_in">map</span>(<span class="built_in">int</span>, <span class="built_in">input</span>().split()))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 浮点型多数字</span></span><br><span class="line">x, y = <span class="built_in">map</span>(<span class="built_in">float</span>, <span class="built_in">input</span>().split())</span><br></pre></td></tr></table></figure><p><code>split()</code> 特性：自动按<strong>任意空白</strong>（空格、Tab、多个连续空格）分割。</p><h3 id="1-3-分行读取多组数据"><a href="#1-3-分行读取多组数据" class="headerlink" title="1.3 分行读取多组数据"></a>1.3 分行读取多组数据</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 读取n行数字</span></span><br><span class="line">n = <span class="built_in">int</span>(<span class="built_in">input</span>())</span><br><span class="line">arr = []</span><br><span class="line"><span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(n):</span><br><span class="line">    val = <span class="built_in">int</span>(<span class="built_in">input</span>())</span><br><span class="line">    arr.append(val)</span><br></pre></td></tr></table></figure><h2 id="2-高速输入-sys-stdin（大数据量推荐）"><a href="#2-高速输入-sys-stdin（大数据量推荐）" class="headerlink" title="2. 高速输入 sys.stdin（大数据量推荐）"></a>2. 高速输入 sys.stdin（大数据量推荐）</h2><p>数据量大时 <code>input()</code> 速度慢，使用 sys 批量读取。</p><figure class="highlight python"><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">import</span> sys</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取所有输入，按行分割</span></span><br><span class="line">lines = sys.stdin.readlines()</span><br><span class="line">a = <span class="built_in">int</span>(lines[<span class="number">0</span>])</span><br><span class="line">b = <span class="built_in">int</span>(lines[<span class="number">1</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 一次性读取全部数字（无视换行/空格）</span></span><br><span class="line">data = <span class="built_in">list</span>(<span class="built_in">map</span>(<span class="built_in">int</span>, sys.stdin.read().split()))</span><br><span class="line">a = data[<span class="number">0</span>]</span><br><span class="line">b = data[<span class="number">1</span>]</span><br></pre></td></tr></table></figure><h2 id="3-输入格式冲突报错说明"><a href="#3-输入格式冲突报错说明" class="headerlink" title="3. 输入格式冲突报错说明"></a>3. 输入格式冲突报错说明</h2><table><thead><tr><th>代码写法</th><th>要求输入格式</th><th>错误输入</th><th>报错信息</th></tr></thead><tbody><tr><td><code>a=int(input())</code></td><td>一数一行</td><td><code>2 3</code></td><td><code>ValueError: invalid literal for int()</code></td></tr><tr><td><code>a,b=map(int,input().split())</code></td><td>一行两个数</td><td>仅输入1个数</td><td><code>ValueError: not enough values to unpack</code></td></tr></tbody></table><hr><h1 id="二、输出-print-用法"><a href="#二、输出-print-用法" class="headerlink" title="二、输出 print 用法"></a>二、输出 print 用法</h1><h2 id="2-1-基础打印"><a href="#2-1-基础打印" class="headerlink" title="2.1 基础打印"></a>2.1 基础打印</h2><figure class="highlight python"><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="built_in">print</span>(<span class="string">&quot;Hello&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="number">123</span>)</span><br><span class="line"><span class="comment"># 多参数自动空格分隔</span></span><br><span class="line"><span class="built_in">print</span>(a, b, <span class="string">&quot;result&quot;</span>)</span><br><span class="line"><span class="comment"># 不自动换行</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;NO&quot;</span>, end=<span class="string">&quot;&quot;</span>)</span><br><span class="line"><span class="comment"># 自定义分隔符</span></span><br><span class="line"><span class="built_in">print</span>(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>, sep=<span class="string">&quot;,&quot;</span>) <span class="comment"># 1,2,3</span></span><br></pre></td></tr></table></figure><h2 id="2-2-格式化输出"><a href="#2-2-格式化输出" class="headerlink" title="2.2 格式化输出"></a>2.2 格式化输出</h2><figure class="highlight python"><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"># f-string（推荐，Python3.6+）</span></span><br><span class="line">x = <span class="number">10</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;数字是<span class="subst">&#123;x&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># format 兼容旧版本</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;数字是&#123;&#125;&quot;</span>.<span class="built_in">format</span>(x))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 保留小数</span></span><br><span class="line">pi = <span class="number">3.14159</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;pi:<span class="number">.2</span>f&#125;</span>&quot;</span>) <span class="comment"># 保留2位小数</span></span><br></pre></td></tr></table></figure><h2 id="2-3-批量输出（大数据）"><a href="#2-3-批量输出（大数据）" class="headerlink" title="2.3 批量输出（大数据）"></a>2.3 批量输出（大数据）</h2><p>多次 print 效率低，拼接字符串一次性输出：</p><figure class="highlight python"><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">res = []</span><br><span class="line">res.append(<span class="string">&quot;YES&quot;</span>)</span><br><span class="line">res.append(<span class="built_in">str</span>(<span class="number">123</span>))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;\n&#x27;</span>.join(res))</span><br></pre></td></tr></table></figure><hr><h1 id="三、Python-容器（对标-C-STL）"><a href="#三、Python-容器（对标-C-STL）" class="headerlink" title="三、Python 容器（对标 C++ STL）"></a>三、Python 容器（对标 C++ STL）</h1><h2 id="1-list-数组-vector"><a href="#1-list-数组-vector" class="headerlink" title="1. list 数组 &#x2F; vector"></a>1. list 数组 &#x2F; vector</h2><p>对应C++ <code>vector&lt;int&gt;</code>，可变、有序、可重复。</p><figure class="highlight python"><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="comment"># 初始化</span></span><br><span class="line">arr = []</span><br><span class="line">arr = [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]</span><br><span class="line">arr = [<span class="number">0</span>]*<span class="number">10</span> <span class="comment"># 长度10，全0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 增删改查</span></span><br><span class="line">arr.append(<span class="number">4</span>)       <span class="comment"># 尾部添加 push_back</span></span><br><span class="line">arr.pop()            <span class="comment"># 删除尾部 pop_back</span></span><br><span class="line">arr.pop(<span class="number">0</span>)           <span class="comment"># 删除头部（效率低）</span></span><br><span class="line">arr.insert(<span class="number">1</span>, <span class="number">99</span>)   <span class="comment"># 指定位置插入</span></span><br><span class="line">arr.remove(<span class="number">2</span>)        <span class="comment"># 删除第一个值为2的元素</span></span><br><span class="line"><span class="built_in">print</span>(arr[<span class="number">0</span>])        <span class="comment"># 下标访问</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">len</span>(arr))      <span class="comment"># size()</span></span><br></pre></td></tr></table></figure><h2 id="2-deque-双端队列（queue-deque-STL）"><a href="#2-deque-双端队列（queue-deque-STL）" class="headerlink" title="2. deque 双端队列（queue &#x2F; deque STL）"></a>2. deque 双端队列（queue &#x2F; deque STL）</h2><p>需导入 <code>collections</code>，首尾增删O(1)，替代list做队列。</p><figure class="highlight python"><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">from</span> collections <span class="keyword">import</span> deque</span><br><span class="line">q = deque()</span><br><span class="line"></span><br><span class="line">q.append(<span class="number">1</span>)    <span class="comment"># 队尾入队 push_back</span></span><br><span class="line">q.appendleft(<span class="number">2</span>)<span class="comment"># 队头入队 push_front</span></span><br><span class="line">q.popleft()    <span class="comment"># 队头出队 pop_front</span></span><br><span class="line">q.pop()         <span class="comment"># 队尾出队 pop_back</span></span><br><span class="line"><span class="built_in">print</span>(q[<span class="number">0</span>])     <span class="comment"># 取队首 front()</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">len</span>(q))   <span class="comment"># size()</span></span><br></pre></td></tr></table></figure><h2 id="3-set-集合（std-unordered-set-set）"><a href="#3-set-集合（std-unordered-set-set）" class="headerlink" title="3. set 集合（std::unordered_set &#x2F; set）"></a>3. set 集合（std::unordered_set &#x2F; set）</h2><p>元素<strong>唯一、无序</strong>，去重、查找O(1)。</p><figure class="highlight python"><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">s = <span class="built_in">set</span>()</span><br><span class="line">s.add(<span class="number">1</span>)       <span class="comment"># insert</span></span><br><span class="line">s.add(<span class="number">1</span>)       <span class="comment"># 重复添加自动忽略</span></span><br><span class="line">s.remove(<span class="number">1</span>)    <span class="comment"># 删除元素</span></span><br><span class="line"><span class="built_in">print</span>(<span class="number">1</span> <span class="keyword">in</span> s)  <span class="comment"># 查询是否存在 count()</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">len</span>(s))  <span class="comment"># size()</span></span><br></pre></td></tr></table></figure><h2 id="4-dict-字典（std-unordered-map-map）"><a href="#4-dict-字典（std-unordered-map-map）" class="headerlink" title="4. dict 字典（std::unordered_map &#x2F; map）"></a>4. dict 字典（std::unordered_map &#x2F; map）</h2><p>键值对存储，key唯一，哈希查找。</p><figure class="highlight python"><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">mp = &#123;&#125;</span><br><span class="line">mp[<span class="string">&quot;a&quot;</span>] = <span class="number">10</span>    <span class="comment"># map[key] = val</span></span><br><span class="line">mp[<span class="number">5</span>] = <span class="number">20</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;a&quot;</span> <span class="keyword">in</span> mp) <span class="comment"># 判断key是否存在</span></span><br><span class="line"><span class="built_in">print</span>(mp.get(<span class="string">&quot;b&quot;</span>, <span class="number">0</span>)) <span class="comment"># 不存在返回默认值0</span></span><br><span class="line"><span class="keyword">del</span> mp[<span class="string">&quot;a&quot;</span>]       <span class="comment"># 删除键值对</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">len</span>(mp))    <span class="comment"># size()</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 遍历键、值</span></span><br><span class="line"><span class="keyword">for</span> k <span class="keyword">in</span> mp:</span><br><span class="line">    <span class="built_in">print</span>(k, mp[k])</span><br></pre></td></tr></table></figure><h2 id="5-tuple-元组"><a href="#5-tuple-元组" class="headerlink" title="5. tuple 元组"></a>5. tuple 元组</h2><p>不可变数组，对应const vector，不能增删改。</p><figure class="highlight python"><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">t = (<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>)</span><br><span class="line"><span class="built_in">print</span>(t[<span class="number">0</span>])</span><br><span class="line"><span class="comment"># t[0] = 99 报错，无法修改</span></span><br></pre></td></tr></table></figure><h2 id="容器功能对标简表"><a href="#容器功能对标简表" class="headerlink" title="容器功能对标简表"></a>容器功能对标简表</h2><table><thead><tr><th>Python容器</th><th>C++ STL</th><th>核心特性</th></tr></thead><tbody><tr><td>list</td><td>vector</td><td>可变有序数组，尾部操作快</td></tr><tr><td>deque</td><td>deque &#x2F; queue</td><td>双端快速增删，适合队列BFS</td></tr><tr><td>set</td><td>unordered_set</td><td>自动去重，快速查找</td></tr><tr><td>dict</td><td>unordered_map</td><td>key-value哈希映射</td></tr><tr><td>tuple</td><td>const vector</td><td>只读固定长度数组</td></tr></tbody></table><hr><h1 id="四、通用刷题模板（OJ专用，带Main类避免判题报错）"><a href="#四、通用刷题模板（OJ专用，带Main类避免判题报错）" class="headerlink" title="四、通用刷题模板（OJ专用，带Main类避免判题报错）"></a>四、通用刷题模板（OJ专用，带Main类避免判题报错）</h1><figure class="highlight python"><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="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> deque</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Main</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">main</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="comment"># 输入1：一行两个数字</span></span><br><span class="line">        a, b = <span class="built_in">map</span>(<span class="built_in">int</span>, <span class="built_in">input</span>().split())</span><br><span class="line">        <span class="comment"># 输入2：数字分行读取</span></span><br><span class="line">        <span class="comment"># a = int(input())</span></span><br><span class="line">        <span class="comment"># b = int(input())</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 逻辑处理示例</span></span><br><span class="line">        a %= <span class="number">2</span></span><br><span class="line">        b %= <span class="number">2</span></span><br><span class="line">        <span class="keyword">if</span> a == <span class="number">1</span> <span class="keyword">or</span> b == <span class="number">1</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;NO&quot;</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;YES&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    Main().main()</span><br></pre></td></tr></table></figure><hr><h1 id="五、补充注意事项"><a href="#五、补充注意事项" class="headerlink" title="五、补充注意事项"></a>五、补充注意事项</h1><ol><li><strong>编码声明</strong>：代码含中文注释时，首行添加 <code># -*- coding: utf-8 -*-</code>，消除 Non-ASCII 报错；</li><li><strong>输入格式严格匹配</strong>：判题系统输入固定，分行输入不能用 <code>split()</code>，单行多数字不能分次 <code>input()</code>；</li><li><strong>容器选择</strong>：BFS广度优先搜索优先用 <code>deque</code>，不要用 list 频繁 <code>pop(0)</code>（时间复杂度高）；</li><li><strong>大数据优化</strong>：数据量上万时，使用 <code>sys.stdin.read()</code> 一次性读取全部输入，减少IO耗时。</li></ol>]]></content>
    
    
    <summary type="html">Python 刷题必备 — input/sys.stdin 输入、print 输出格式化、list/dict/set/deque 容器对标 C++ STL、OJ 报错排查、通用刷题模板</summary>
    
    
    
    <category term="指令" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/"/>
    
    <category term="杂记" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/%E6%9D%82%E8%AE%B0/"/>
    
    
    <category term="Python" scheme="https://yourdomain.com/tags/Python/"/>
    
    <category term="算法" scheme="https://yourdomain.com/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="OJ" scheme="https://yourdomain.com/tags/OJ/"/>
    
  </entry>
  
  <entry>
    <title>Go 语言笔记 &amp; GORM 入门</title>
    <link href="https://yourdomain.com/2025/go-gorm-notes.html"/>
    <id>https://yourdomain.com/2025/go-gorm-notes.html</id>
    <published>2025-03-24T01:57:00.000Z</published>
    <updated>2025-03-24T01:57:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Go-基础语法"><a href="#Go-基础语法" class="headerlink" title="Go 基础语法"></a>Go 基础语法</h2><h3 id="导入原则"><a href="#导入原则" class="headerlink" title="导入原则"></a>导入原则</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;xxx&quot;</span></span><br></pre></td></tr></table></figure><p>导入的包或定义的变量若未使用会编译报错——强制减少无用开销。</p><h3 id="类型转换"><a href="#类型转换" class="headerlink" title="类型转换"></a>类型转换</h3><figure class="highlight go"><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">// 使用 strconv 包进行类型转换</span></span><br><span class="line"><span class="comment">// 函数通常返回两个值：结果值和 error</span></span><br></pre></td></tr></table></figure><h3 id="goto"><a href="#goto" class="headerlink" title="goto"></a>goto</h3><p>跳转关键字，可在函数内跳转到指定标签。</p><h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h3><p>Go 中<strong>不支持函数重载</strong>——同一作用域下函数名必须唯一。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 别名 ≠ 原类型，不能直接赋值，但可以显式转换后赋值</span></span><br><span class="line"><span class="keyword">type</span> MyInt <span class="type">int</span></span><br><span class="line"><span class="keyword">var</span> a MyInt = <span class="number">10</span></span><br><span class="line"><span class="keyword">var</span> b <span class="type">int</span> = <span class="type">int</span>(a) <span class="comment">// 需要显式转换</span></span><br></pre></td></tr></table></figure><h3 id="包"><a href="#包" class="headerlink" title="包"></a>包</h3><ul><li>方法能否被外部调用取决于<strong>首字母是否大写</strong>（<code>public</code> &#x2F; <code>private</code> 的体现）</li><li>同一个包下不能有重名函数</li><li>Go 有”声明必使用”的原则，别名声明后就不能再用原名调用</li></ul><h3 id="init-函数"><a href="#init-函数" class="headerlink" title="init() 函数"></a>init() 函数</h3><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">import → 全局变量定义 → init() → main()</span><br></pre></td></tr></table></figure><h3 id="匿名函数"><a href="#匿名函数" class="headerlink" title="匿名函数"></a>匿名函数</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 一次使用</span></span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; ... &#125;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 多次使用</span></span><br><span class="line">fn := <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; ... &#125;</span><br><span class="line">fn()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 全局（可外部引用）</span></span><br><span class="line"><span class="keyword">var</span> Fn = <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; ... &#125;</span><br></pre></td></tr></table></figure><h3 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h3><p>闭包引用了函数体之外的变量，函数+引用环境 &#x3D; 闭包。</p><h3 id="defer"><a href="#defer" class="headerlink" title="defer"></a>defer</h3><ul><li><code>defer</code> 压栈时相当于<strong>值拷贝</strong>，不会随后面变化而变化</li><li>通常用于资源关闭（文件、连接等），创建资源后紧随 <code>defer</code> 关闭</li></ul><figure class="highlight go"><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">f, _ := os.Open(<span class="string">&quot;file.txt&quot;</span>)</span><br><span class="line"><span class="keyword">defer</span> f.Close()</span><br></pre></td></tr></table></figure><h3 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h3><figure class="highlight go"><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">// Go 中一个汉字占 3 字节（UTF-8）</span></span><br><span class="line"><span class="comment">// 字符串遍历使用 strings 包</span></span><br></pre></td></tr></table></figure><h3 id="日期时间"><a href="#日期时间" class="headerlink" title="日期时间"></a>日期时间</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line">now := time.Now()</span><br><span class="line">now.Year()</span><br><span class="line">now.Month()</span><br><span class="line">now.Day()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 时间格式化（数字固定）</span></span><br><span class="line">now.Format(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>)</span><br></pre></td></tr></table></figure><blockquote><p>注意：Go 的时间格式化模板数字 <code>2006-01-02 15:04:05</code> 是固定的，不能随意更改。</p></blockquote><h3 id="错误捕获"><a href="#错误捕获" class="headerlink" title="错误捕获"></a>错误捕获</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// defer + recover 替代 try/catch</span></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">if</span> err := <span class="built_in">recover</span>(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">&quot;捕获异常:&quot;</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;()</span><br></pre></td></tr></table></figure><blockquote><p>Go 中空值写作 <code>nil</code>，而非 <code>null</code>。</p></blockquote><h3 id="遍历"><a href="#遍历" class="headerlink" title="遍历"></a>遍历</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// for range</span></span><br><span class="line"><span class="keyword">for</span> k, v := <span class="keyword">range</span> 数组名 &#123;</span><br><span class="line">    <span class="comment">// k 为下标，v 为对应值</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="GORM-入门"><a href="#GORM-入门" class="headerlink" title="GORM 入门"></a>GORM 入门</h2><h3 id="依赖管理"><a href="#依赖管理" class="headerlink" title="依赖管理"></a>依赖管理</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">go get          <span class="comment"># 下载并安装包</span></span><br><span class="line">go mod tidy     <span class="comment"># 整理依赖，移除未使用的包</span></span><br><span class="line">go mod vendor   <span class="comment"># 生成本地 vendor 目录</span></span><br></pre></td></tr></table></figure><h3 id="DSN-数据库连接"><a href="#DSN-数据库连接" class="headerlink" title="DSN 数据库连接"></a>DSN 数据库连接</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用户名:密码@tcp(IP)/数据库名?charset=utf8mb4&amp;parseTime=true&amp;loc=Local</span></span><br></pre></td></tr></table></figure><blockquote><p><code>&amp;parseTime=true</code> 中的 <code>&amp;</code> 会影响结构体是否同步更新。加 <code>&amp;</code> 时，插入记录后 <code>user.ID</code> 能获取到自增主键值；不加则不会同步，需要手动查询。</p></blockquote><h3 id="常用方法"><a href="#常用方法" class="headerlink" title="常用方法"></a>常用方法</h3><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>Create(&amp;user)</code></td><td>创建记录</td></tr><tr><td><code>Save(&amp;user)</code></td><td>保存或更新，支持事务回滚等校验</td></tr><tr><td><code>First(&amp;user)</code></td><td>查询第一条记录（按主键升序）</td></tr><tr><td><code>Last(&amp;user)</code></td><td>查询最后一条记录（按主键降序）</td></tr><tr><td><code>Find(&amp;users)</code></td><td>查询多条记录</td></tr><tr><td><code>Where(...).Find(&amp;users)</code></td><td>条件查询</td></tr><tr><td><code>Model(&amp;User&#123;&#125;).Update(...)</code></td><td>更新字段</td></tr></tbody></table><p>示例：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">db.Where(<span class="string">&quot;name = ?&quot;</span>, <span class="string">&quot;Alice&quot;</span>).Find(&amp;users)</span><br><span class="line">db.Where(<span class="string">&quot;id &gt; ?&quot;</span>, <span class="number">20</span>).Find(&amp;users)</span><br><span class="line">db.Model(&amp;User&#123;&#125;).Update(<span class="string">&quot;name&quot;</span>, <span class="string">&quot;NewName&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="模型示例"><a href="#模型示例" class="headerlink" title="模型示例"></a>模型示例</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> models</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;gorm.io/gorm&quot;</span></span><br><span class="line">    <span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> UserBasic <span class="keyword">struct</span> &#123;</span><br><span class="line">    gorm.Model</span><br><span class="line">    Username      <span class="type">string</span></span><br><span class="line">    Password      <span class="type">string</span></span><br><span class="line">    Phone         <span class="type">string</span></span><br><span class="line">    Email         <span class="type">string</span></span><br><span class="line">    Identity      <span class="type">string</span></span><br><span class="line">    ClientIp      <span class="type">string</span></span><br><span class="line">    ClientPort    <span class="type">string</span></span><br><span class="line">    LoginTime     time.Time</span><br><span class="line">    HeartbeatTime time.Time</span><br><span class="line">    LoginOutTime  time.Time <span class="string">`gorm:&quot;column:login_out_time&quot; json:&quot;login_out_time&quot;`</span></span><br><span class="line">    IsLogout      <span class="type">bool</span></span><br><span class="line">    DeviceInfo    <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(table *UserBasic)</span></span> TableName() <span class="type">string</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;user_basic&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GetUserList</span><span class="params">()</span></span> []*UserBasic &#123;</span><br><span class="line">    data := <span class="built_in">make</span>([]*UserBasic, <span class="number">10</span>)</span><br><span class="line">    utils.DB.Find(&amp;data)</span><br><span class="line">    <span class="keyword">return</span> data</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Go 语言基础语法笔记（声明、函数、包、defer、闭包等）及 GORM 数据库交互入门</summary>
    
    
    
    <category term="指令" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/"/>
    
    <category term="杂记" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/%E6%9D%82%E8%AE%B0/"/>
    
    
    <category term="Go" scheme="https://yourdomain.com/tags/Go/"/>
    
    <category term="GORM" scheme="https://yourdomain.com/tags/GORM/"/>
    
    <category term="数据库" scheme="https://yourdomain.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>Java 接入 AI 接口</title>
    <link href="https://yourdomain.com/2025/java-ai-api-integration.html"/>
    <id>https://yourdomain.com/2025/java-ai-api-integration.html</id>
    <published>2025-02-26T09:49:06.000Z</published>
    <updated>2025-02-26T09:49:06.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>如果你的项目使用 JDK 17+，推荐学习 Spring AI 框架。本篇以 JDK 8 项目为例，手搓 AI 接口的接入。</p><h2 id="依赖引入"><a href="#依赖引入" class="headerlink" title="依赖引入"></a>依赖引入</h2><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="comment">&lt;!-- Gson for JSON processing --&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.google.code.gson<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>gson<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.8.9<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="comment">&lt;!-- HttpClient for making HTTP requests --&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>org.apache.httpcomponents.client5<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>httpclient5<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>5.1.3<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><h2 id="AI-配置类"><a href="#AI-配置类" class="headerlink" title="AI 配置类"></a>AI 配置类</h2><p>建议单独开一个配置类，后续可通过 Nacos 等方式动态更新：</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">public</span> <span class="keyword">class</span> <span class="title class_">AIConfig</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String apiKey;</span><br><span class="line">    <span class="keyword">private</span> String model;</span><br><span class="line">    <span class="keyword">private</span> String URL;</span><br><span class="line">    <span class="keyword">private</span> Double temperature;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AIConfig</span><span class="params">()</span> &#123;</span><br><span class="line">        URL = <span class="string">&quot;https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions&quot;</span>;</span><br><span class="line">        apiKey = <span class="string">&quot;sk-xxx&quot;</span>;</span><br><span class="line">        model = <span class="string">&quot;qwen-plus&quot;</span>;</span><br><span class="line">        temperature = <span class="number">0.8</span>;</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><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AIMessage</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String role;    <span class="comment">// system / user</span></span><br><span class="line">    <span class="keyword">private</span> String content;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AIRequst</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String model;</span><br><span class="line">    <span class="keyword">private</span> List&lt;AIMessage&gt; messages;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> temperature;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ResultBody <span class="title function_">AIImport</span><span class="params">(String userId, String issue)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="string">&quot;AI_&quot;</span> + userId;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">cacheObject</span> <span class="operator">=</span> redisService.getCacheObject(key);</span><br><span class="line">    <span class="type">String</span> <span class="variable">contextInfo</span> <span class="operator">=</span> (cacheObject != <span class="literal">null</span>) ? (String) cacheObject : <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">AIRequst</span> <span class="variable">openaiReq</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AIRequst</span>();</span><br><span class="line">    openaiReq.setModel(aiConfig.getModel());</span><br><span class="line"></span><br><span class="line">    List&lt;AIMessage&gt; messages = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="type">AIMessage</span> <span class="variable">systemMessage</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AIMessage</span>();</span><br><span class="line">    systemMessage.setRole(<span class="string">&quot;system&quot;</span>);</span><br><span class="line">    systemMessage.setContent(<span class="string">&quot;你是一个语音聊天助手，会讲冷笑话、鬼故事等。&quot;</span>);</span><br><span class="line">    messages.add(systemMessage);</span><br><span class="line"></span><br><span class="line">    <span class="type">AIMessage</span> <span class="variable">userMessage</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AIMessage</span>();</span><br><span class="line">    userMessage.setRole(<span class="string">&quot;user&quot;</span>);</span><br><span class="line">    userMessage.setContent(String.format(<span class="string">&quot;上下文信息：\n%s\n\n用户问题：%s&quot;</span>, contextInfo, issue));</span><br><span class="line">    messages.add(userMessage);</span><br><span class="line"></span><br><span class="line">    openaiReq.setMessages(messages);</span><br><span class="line">    openaiReq.setTemperature(aiConfig.getTemperature());</span><br><span class="line"></span><br><span class="line">    <span class="type">Gson</span> <span class="variable">gson</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Gson</span>();</span><br><span class="line">    <span class="type">String</span> <span class="variable">jsonRequest</span> <span class="operator">=</span> gson.toJson(openaiReq);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">URL</span>(aiConfig.getURL());</span><br><span class="line">        <span class="type">HttpURLConnection</span> <span class="variable">connection</span> <span class="operator">=</span> (HttpURLConnection) url.openConnection();</span><br><span class="line">        connection.setRequestMethod(<span class="string">&quot;POST&quot;</span>);</span><br><span class="line">        connection.setRequestProperty(<span class="string">&quot;Content-Type&quot;</span>, <span class="string">&quot;application/json&quot;</span>);</span><br><span class="line">        connection.setRequestProperty(<span class="string">&quot;Authorization&quot;</span>, <span class="string">&quot;Bearer &quot;</span> + aiConfig.getApiKey());</span><br><span class="line">        connection.setDoOutput(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> (<span class="type">OutputStream</span> <span class="variable">os</span> <span class="operator">=</span> connection.getOutputStream()) &#123;</span><br><span class="line">            <span class="type">byte</span>[] input = jsonRequest.getBytes(StandardCharsets.UTF_8);</span><br><span class="line">            os.write(input, <span class="number">0</span>, input.length);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">responseCode</span> <span class="operator">=</span> connection.getResponseCode();</span><br><span class="line">        <span class="keyword">if</span> (responseCode == <span class="number">200</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> (<span class="type">BufferedReader</span> <span class="variable">br</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(connection.getInputStream(), StandardCharsets.UTF_8))) &#123;</span><br><span class="line">                <span class="type">StringBuilder</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">                String responseLine;</span><br><span class="line">                <span class="keyword">while</span> ((responseLine = br.readLine()) != <span class="literal">null</span>) &#123;</span><br><span class="line">                    response.append(responseLine.trim());</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="type">JsonObject</span> <span class="variable">jsonResponse</span> <span class="operator">=</span> gson.fromJson(response.toString(), JsonObject.class);</span><br><span class="line">                <span class="keyword">if</span> (jsonResponse.getAsJsonArray(<span class="string">&quot;choices&quot;</span>).isEmpty()) &#123;</span><br><span class="line">                    <span class="keyword">return</span> ResultBody.error().message(<span class="string">&quot;AI 响应超时...&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="type">JsonObject</span> <span class="variable">choice</span> <span class="operator">=</span> jsonResponse.getAsJsonArray(<span class="string">&quot;choices&quot;</span>).get(<span class="number">0</span>).getAsJsonObject();</span><br><span class="line">                <span class="type">JsonObject</span> <span class="variable">message</span> <span class="operator">=</span> choice.getAsJsonObject(<span class="string">&quot;message&quot;</span>);</span><br><span class="line">                <span class="type">String</span> <span class="variable">answer</span> <span class="operator">=</span> message.get(<span class="string">&quot;content&quot;</span>).getAsString().trim();</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (answer.isEmpty()) &#123;</span><br><span class="line">                    <span class="keyword">return</span> ResultBody.error().message(<span class="string">&quot;AI 响应超时&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                redisService.setCacheObject(key, issue, <span class="number">300L</span>, TimeUnit.MINUTES);</span><br><span class="line">                <span class="keyword">return</span> ResultBody.ok().message(answer);</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">try</span> (<span class="type">BufferedReader</span> <span class="variable">br</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(connection.getErrorStream(), StandardCharsets.UTF_8))) &#123;</span><br><span class="line">                <span class="type">StringBuilder</span> <span class="variable">errorResponse</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">                String errorLine;</span><br><span class="line">                <span class="keyword">while</span> ((errorLine = br.readLine()) != <span class="literal">null</span>) &#123;</span><br><span class="line">                    errorResponse.append(errorLine.trim());</span><br><span class="line">                &#125;</span><br><span class="line">                System.err.println(<span class="string">&quot;错误响应: &quot;</span> + errorResponse);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> ResultBody.error().message(<span class="string">&quot;AI 响应超时...&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">        <span class="keyword">return</span> ResultBody.error().message(<span class="string">&quot;AI 响应超时&quot;</span>);</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><ul><li>JDK 8 项目可通过 HttpURLConnection 手搓 AI 接口，不需要额外框架</li><li>注意提取 AI 配置为独立配置类，方便后续热更新</li><li>结合 Redis 缓存上下文信息，实现简单的对话记忆</li></ul><blockquote><p>参考：<a href="https://blog.csdn.net/2301_79425914/article/details/145884338?spm=1001.2014.3001.5502">CSDN - Java接入AI接口</a></p></blockquote>]]></content>
    
    
    <summary type="html">JDK8 项目手搓 AI 接口接入，基于 HttpURLConnection 实现</summary>
    
    
    
    <category term="文档" scheme="https://yourdomain.com/categories/%E6%96%87%E6%A1%A3/"/>
    
    
    <category term="AI" scheme="https://yourdomain.com/tags/AI/"/>
    
    <category term="Java" scheme="https://yourdomain.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>P1113 杂物</title>
    <link href="https://yourdomain.com/2025/chores-p1113.html"/>
    <id>https://yourdomain.com/2025/chores-p1113.html</id>
    <published>2025-02-23T05:52:00.000Z</published>
    <updated>2025-02-23T05:52:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="考点"><a href="#考点" class="headerlink" title="考点"></a>考点</h2><p>拓扑排序、图论、递推 &#x2F; 记忆化搜索。</p><h2 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h2><p>题目给定 <code>n</code> 个任务，每个任务有完成耗时和前置任务。完成所有任务的最短时间，就是<strong>从起点到终点的最长路径</strong>（因为耗时最长的链决定了整体完成时间）。</p><p>本题用 DAG 建图 + 记忆化 DFS 解决：</p><ol><li>对每个任务，记录它的前序任务（即谁指向它）</li><li>用 DFS 从每个节点出发，递归计算到达该节点的最长路径</li><li>记忆化搜索避免重复计算</li></ol><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="type">int</span> <span class="title function_">dfs</span><span class="params">(<span class="type">int</span> x)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (f[x] != <span class="number">0</span>) <span class="keyword">return</span> f[x];</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; edge[x].size(); i++)</span><br><span class="line">        f[x] = Math.max(f[x], dfs(edge[x].get(i)));</span><br><span class="line">    f[x] += a[x];</span><br><span class="line">    <span class="keyword">return</span> f[x];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 建图时注意: edge[y].add(x) 表示 y 是 x 的前序</span></span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>“完成所有任务的最短时间” 在 DAG 中 &#x3D; 最长路径</li><li>记忆化搜索是 DAG 上 DP 的常用实现方式</li><li>建图时注意边的方向：前序指向后续</li></ul>]]></content>
    
    
    <summary type="html">洛谷 P1113，拓扑排序 + 记忆化搜索求 DAG 最长路径</summary>
    
    
    
    <category term="算法" scheme="https://yourdomain.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="图论" scheme="https://yourdomain.com/tags/%E5%9B%BE%E8%AE%BA/"/>
    
    <category term="拓扑排序" scheme="https://yourdomain.com/tags/%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F/"/>
    
    <category term="记忆化搜索" scheme="https://yourdomain.com/tags/%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2/"/>
    
    <category term="DAG" scheme="https://yourdomain.com/tags/DAG/"/>
    
  </entry>
  
  <entry>
    <title>Nginx 相关的经历</title>
    <link href="https://yourdomain.com/2025/nginx-experience.html"/>
    <id>https://yourdomain.com/2025/nginx-experience.html</id>
    <published>2025-02-19T12:03:09.000Z</published>
    <updated>2025-02-19T12:03:09.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>前后端分离的趋势下，Nginx 已经成为后端开发必须了解的技术点之一。</p><h2 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h2><p>修改或查看 Nginx 配置时，推荐先用 <code>nginx -t</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># Linux</span></span><br><span class="line"><span class="built_in">sudo</span> nginx -t 2&gt;&amp;1 | grep <span class="string">&quot;file&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Windows</span></span><br><span class="line">nginx -t 2&gt;&amp;1 | findstr <span class="string">&quot;file&quot;</span></span><br></pre></td></tr></table></figure><p><code>2&gt;&amp;1</code> 的作用是把标准错误（stderr）重定向到标准输出（stdout），因为 <code>nginx -t</code> 的提示信息通常走 stderr 输出。</p><h2 id="反向代理配置写法"><a href="#反向代理配置写法" class="headerlink" title="反向代理配置写法"></a>反向代理配置写法</h2><figure class="highlight nginx"><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="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line">    <span class="attribute">server_name</span> yourdomain.com;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> /api &#123;</span><br><span class="line">        <span class="attribute">proxy_pass</span> http://localhost:8080;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Forwarded-Proto <span class="variable">$scheme</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键说明：</p><table><thead><tr><th>字段</th><th>作用</th></tr></thead><tbody><tr><td><code>listen</code></td><td>监听的端口</td></tr><tr><td><code>location /api</code></td><td>路径匹配，<code>/</code> 匹配所有路径；多个服务可细化路径区分</td></tr><tr><td><code>proxy_pass</code></td><td>转发的目标地址</td></tr><tr><td><code>X-Real-IP</code></td><td>传递客户端真实 IP（否则后端看到的是代理 IP）</td></tr><tr><td><code>X-Forwarded-For</code></td><td>记录客户端及中间代理的 IP 链</td></tr><tr><td><code>X-Forwarded-Proto</code></td><td>传递原始请求协议（HTTP &#x2F; HTTPS）</td></tr></tbody></table><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>修改配置前先用 <code>nginx -t</code> 检查语法和定位文件</li><li>反向代理记得配置请求头透传客户端真实信息</li><li>多个服务可通过细化 <code>location</code> 路径做路由分发</li></ul><blockquote><p>参考：<a href="https://blog.csdn.net/2301_79425914/article/details/145714699?spm=1001.2014.3001.5502">CSDN - nginx相关的经历</a></p></blockquote>]]></content>
    
    
    <summary type="html">Nginx 常用命令、配置查找以及反向代理配置的写法</summary>
    
    
    
    <category term="文档" scheme="https://yourdomain.com/categories/%E6%96%87%E6%A1%A3/"/>
    
    
    <category term="Java" scheme="https://yourdomain.com/tags/Java/"/>
    
    <category term="Nginx" scheme="https://yourdomain.com/tags/Nginx/"/>
    
    <category term="运维" scheme="https://yourdomain.com/tags/%E8%BF%90%E7%BB%B4/"/>
    
  </entry>
  
  <entry>
    <title>行锁和表锁</title>
    <link href="https://yourdomain.com/2024/row-lock-vs-table-lock.html"/>
    <id>https://yourdomain.com/2024/row-lock-vs-table-lock.html</id>
    <published>2024-12-17T06:21:54.000Z</published>
    <updated>2024-12-17T06:21:54.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>后端开发中经常和数据库打交道，查询速率和耗时一直是需要关注的点。行锁和表锁是其中重要的知识点，而理解它们又绕不开索引。</p><h2 id="索引为什么能提升查询速度"><a href="#索引为什么能提升查询速度" class="headerlink" title="索引为什么能提升查询速度"></a>索引为什么能提升查询速度</h2><p>以 InnoDB 的 B+ 树索引为例。假设有一个数值类型的 <code>id</code> 字段，有 <code>1, 3, 4, 5</code> 四条记录：</p><ul><li><strong>无索引</strong>：数据按顺序排列，查询需要逐条遍历，时间复杂度 O(n)</li><li><strong>有索引</strong>：B+ 树结构类似平衡二叉树（左小右大），查询时间复杂度 O(log n)</li></ul><p>这就是加索引能大幅提升查询速率的核心原因。</p><h2 id="行锁与表锁"><a href="#行锁与表锁" class="headerlink" title="行锁与表锁"></a>行锁与表锁</h2><p>有了索引的基础，再看锁就清晰了：</p><ul><li><strong>行锁</strong>：基于索引字段的增删改语句，会锁定对应的索引行。其他事务可以操作表中其他索引行的数据，并发度高</li><li><strong>表锁</strong>：不基于索引字段的 SQL（如全表扫描），会锁住整张表，其他 SQL 需要等待，效率低</li></ul><p>所以在写业务 SQL 时，尽量基于索引字段操作，既能提升查询效率，又能利用行锁减少锁冲突。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>索引是行锁的前提，没有索引的 SQL 会退化到表锁</li><li>行锁粒度小，并发度高；表锁粒度大，适合批量操作但并发差</li><li>业务中尽量通过索引字段来操作数据，避免无意中触发表锁</li></ul><blockquote><p>参考：<a href="https://blog.csdn.net/2301_79425914/article/details/144519424?spm=1001.2014.3001.5502">CSDN - 行锁和表锁</a></p></blockquote>]]></content>
    
    
    <summary type="html">浅谈数据库行锁与表锁的区别，以及索引对锁粒度的影响</summary>
    
    
    
    <category term="文档" scheme="https://yourdomain.com/categories/%E6%96%87%E6%A1%A3/"/>
    
    
    <category term="Java" scheme="https://yourdomain.com/tags/Java/"/>
    
    <category term="数据库" scheme="https://yourdomain.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>Redis 的使用——排行榜实现</title>
    <link href="https://yourdomain.com/2024/redis-leaderboard.html"/>
    <id>https://yourdomain.com/2024/redis-leaderboard.html</id>
    <published>2024-12-13T14:49:20.000Z</published>
    <updated>2024-12-13T14:49:20.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="需求背景"><a href="#需求背景" class="headerlink" title="需求背景"></a>需求背景</h2><p>项目中常把持续使用的数据放到缓存中，以便更快响应、减少数据库操作。排行榜就是一个典型场景——如果每次用户查询都去查数据库，性能会很差。</p><p>以基于用户好友的排行榜为例，直接查库需要：获取好友 ID → 判断授权 → 获取数据 → 整合，中间还有各种异常处理，请求量上来后体验堪忧。</p><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>Redis 的 <code>ZSet</code>（有序集合）是实现排行榜的核心数据结构：</p><ul><li><strong>有序性</strong>：每个元素有一个分数（score），Redis 根据分数排序</li><li><strong>唯一性</strong>：每个元素唯一，不允许重复</li><li><strong>高效操作</strong>：支持 O(log N) 的插入、删除、查找，支持按分数范围分页查询</li></ul><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> R <span class="title function_">add</span><span class="params">(Double grades, String userId)</span> &#123;</span><br><span class="line">    redisTemplate.opsForZSet().add(<span class="string">&quot;leaderboard&quot;</span>, userId, grades);</span><br><span class="line">    <span class="keyword">return</span> R.ok();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> R <span class="title function_">getRanking</span><span class="params">(String userId)</span> &#123;</span><br><span class="line">    <span class="type">RankDTO</span> <span class="variable">rankDTO</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RankDTO</span>();</span><br><span class="line">    rankDTO.setUserId(userId);</span><br><span class="line">    <span class="comment">// reverseRank 从高到低排名，结果从 0 开始，+1 才是真实排名</span></span><br><span class="line">    rankDTO.setGrades(redisTemplate.opsForZSet().reverseRank(<span class="string">&quot;leaderboard&quot;</span>, userId) + <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> R.ok().data(<span class="string">&quot;rank&quot;</span>, rankDTO);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> R <span class="title function_">getLeaderboard</span><span class="params">()</span> &#123;</span><br><span class="line">    Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; leaderboardSet =</span><br><span class="line">        redisTemplate.opsForZSet().reverseRangeWithScores(<span class="string">&quot;leaderboard&quot;</span>, <span class="number">0</span>, -<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">return</span> R.ok().data(<span class="string">&quot;leaderboard&quot;</span>, leaderboardSet);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="踩坑与思考"><a href="#踩坑与思考" class="headerlink" title="踩坑与思考"></a>踩坑与思考</h2><p><strong>1. ZSet 的实现原理？</strong></p><p>ZSet 底层基于跳表（SkipList）和哈希表实现，兼顾有序性和高效查找。</p><p><strong>2. 用户独立排行榜如何更新？</strong></p><p>如果每个用户有自己的好友排行榜，好友数据变化后不方便逐个更新。建议对好友列表做缓存（增删好友时更新），再配合 ZSet 或数据库查询获取实时数据。ZSet 更适合平台维度的整体排行，如积分榜、月度爱心榜等。</p><p><strong>3. 排行榜过期时间怎么处理？</strong></p><p>ZSet 只能对整个集合设置过期时间，无法针对单条数据。可以用定时任务（如每天 0 点或每月 1 号 0 点）清空旧排行榜，统计当天或当月的有效记录。历史数据可单独落表用于归档。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>Redis ZSet 是实现排行榜的首选方案，简单高效</li><li>注意排名从 0 开始，取排名时 <code>reverseRank + 1</code></li><li>排行榜过期结合定时任务即可实现按天&#x2F;月轮换</li></ul><blockquote><p>参考：<a href="https://blog.csdn.net/2301_79425914/article/details/144460726?spm=1001.2014.3001.5502">CSDN - Redis的使用–排行榜实现</a></p></blockquote>]]></content>
    
    
    <summary type="html">基于 Redis ZSet 实现排行榜功能，涵盖排名查询、整体排行及常见问题</summary>
    
    
    
    <category term="文档" scheme="https://yourdomain.com/categories/%E6%96%87%E6%A1%A3/"/>
    
    
    <category term="Java" scheme="https://yourdomain.com/tags/Java/"/>
    
    <category term="数据库" scheme="https://yourdomain.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    <category term="Redis" scheme="https://yourdomain.com/tags/Redis/"/>
    
    <category term="缓存" scheme="https://yourdomain.com/tags/%E7%BC%93%E5%AD%98/"/>
    
  </entry>
  
  <entry>
    <title>JAVA-MQ 设置动态的过期队列</title>
    <link href="https://yourdomain.com/2024/java-mq-dynamic-expiration-queue.html"/>
    <id>https://yourdomain.com/2024/java-mq-dynamic-expiration-queue.html</id>
    <published>2024-12-11T03:03:11.000Z</published>
    <updated>2024-12-11T03:03:11.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="需求背景"><a href="#需求背景" class="headerlink" title="需求背景"></a>需求背景</h2><p>在开发场景中，如预约的签到，用户会有不同的预约签到时间，例如：7:00、7:30、8:00。</p><p>对于这种场景，使用定点轮询查表的方式，一方面时间颗粒度需要严格的控制，另一方面在数据库的记录一多就很浪费性能。所以可以考虑使用 MQ 的过期队列来实现功能。</p><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>基于 RabbitMQ 的死信队列：</p><ol><li>将消息的存活时间设置为”从当前时间到目标判断截止时间”的时间差</li><li>消息过期后自动转入死信队列</li><li>在项目中编写方法监听死信队列，触发业务逻辑</li></ol><p>这样就实现了动态的过期时间设置，无需轮询数据库。</p><h2 id="配置实现"><a href="#配置实现" class="headerlink" title="配置实现"></a>配置实现</h2><h3 id="1-定义队列与死信队列"><a href="#1-定义队列与死信队列" class="headerlink" title="1. 定义队列与死信队列"></a>1. 定义队列与死信队列</h3><p>配置一个正常队列，设置一个<strong>极大的默认过期时间</strong>（如 24h），并绑定死信交换机：</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="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> Queue <span class="title function_">queueLog</span><span class="params">()</span> &#123;</span><br><span class="line">    Map&lt;String, Object&gt; args = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    args.put(<span class="string">&quot;x-message-ttl&quot;</span>, TTL_MAX);            <span class="comment">// 消息最大存活时间 24h</span></span><br><span class="line">    args.put(<span class="string">&quot;x-dead-letter-exchange&quot;</span>, EXCHANGE_DLX_LOG);  <span class="comment">// 死信交换机</span></span><br><span class="line">    args.put(<span class="string">&quot;x-dead-letter-routing-key&quot;</span>, ROUTING_KEY_LOG); <span class="comment">// 死信路由键</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> QueueBuilder.durable(QUEUE_LOG).withArguments(args).build();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><code>TTL_MAX</code> 设置为 24h（注意单位是毫秒，<code>24 * 60 * 60 * 1000</code>）。</p></blockquote><p>配置流程：</p><ol><li>创建一个正常的交换机（Exchange）</li><li>创建一个正常的队列（Queue），绑定到正常交换机</li><li>定义一个死信交换机（DLX）和死信队列（DLQ）</li><li>将死信队列绑定到死信交换机</li><li><strong>将正常队列通过参数绑定到死信交换机</strong>（第 2 步的 <code>args</code>）</li></ol><h3 id="2-发送消息时动态设置过期时间"><a href="#2-发送消息时动态设置过期时间" class="headerlink" title="2. 发送消息时动态设置过期时间"></a>2. 发送消息时动态设置过期时间</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><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> AmqpTemplate rabbitTemplate;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendLogMessage</span><span class="params">(String message)</span> &#123;</span><br><span class="line">    QueryWrapper&lt;ReservationsDTO&gt; qw = <span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;&gt;();</span><br><span class="line">    qw.eq(<span class="string">&quot;reservation_id&quot;</span>, message);</span><br><span class="line">    <span class="type">ReservationsDTO</span> <span class="variable">dto</span> <span class="operator">=</span> reservationsMapper.selectOne(qw);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 计算当前时间到 startTime 的时间差</span></span><br><span class="line">    <span class="type">LocalDateTime</span> <span class="variable">startTime</span> <span class="operator">=</span> dto.getStartTime();</span><br><span class="line">    <span class="type">Duration</span> <span class="variable">duration</span> <span class="operator">=</span> Duration.between(LocalDateTime.now(), startTime);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (duration.isNegative()) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;签到时间已过: &#123;&#125;&quot;</span>, message);</span><br><span class="line">        duration = Duration.ZERO;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">expirationTimeMillis</span> <span class="operator">=</span> duration.toMillis();</span><br><span class="line"></span><br><span class="line">    rabbitTemplate.convertAndSend(RabbitMQ.EXCHANGE, message, msg -&gt; &#123;</span><br><span class="line">        msg.getMessageProperties().setExpiration(String.valueOf(expirationTimeMillis));</span><br><span class="line">        <span class="keyword">return</span> msg;</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></pre></td><td class="code"><pre><span class="line">rabbitTemplate.convertAndSend(RabbitMQ.EXCHANGE, <span class="string">&quot;路由键&quot;</span>, message, msg -&gt; &#123;</span><br><span class="line">    msg.getMessageProperties().setExpiration(String.valueOf(expirationTimeMillis));</span><br><span class="line">    <span class="keyword">return</span> msg;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="3-消费死信队列"><a href="#3-消费死信队列" class="headerlink" title="3. 消费死信队列"></a>3. 消费死信队列</h3><p>在消费者中监听对应的死信队列，正常编写业务逻辑即可。</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul><li>设置的单个消息过期时间**不要超过队列的 <code>x-message-ttl</code>**（即 24h），所以在设计时要考虑极端情况的最大间隔</li><li>时间差为负数时兜底处理为 <code>Duration.ZERO</code>，防止消息永远不过期</li><li><code>setExpiration</code> 接收的是字符串类型的毫秒数</li></ul><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>利用死信队列 + 动态 TTL，可以优雅地实现定时触发，避免轮询数据库</li><li>核心是三个配置：正常队列绑定死信交换机、设置队列最大 TTL、发送时动态计算过期时间</li><li>适用于预约签到、订单超时取消等场景</li></ul><blockquote><p>参考：<a href="https://blog.csdn.net/2301_79425914/article/details/144372955?spm=1001.2014.3001.5502">CSDN - JAVA-MQ设置动态的过期队列</a></p></blockquote>]]></content>
    
    
    <summary type="html">基于 RabbitMQ 死信队列实现动态过期时间，适用于预约签到等定时触发场景</summary>
    
    
    
    <category term="文档" scheme="https://yourdomain.com/categories/%E6%96%87%E6%A1%A3/"/>
    
    
    <category term="Java" scheme="https://yourdomain.com/tags/Java/"/>
    
    <category term="JAVA-MQ" scheme="https://yourdomain.com/tags/JAVA-MQ/"/>
    
    <category term="RabbitMQ" scheme="https://yourdomain.com/tags/RabbitMQ/"/>
    
  </entry>
  
  <entry>
    <title>Linux 常用命令 &amp; 高并发调优笔记</title>
    <link href="https://yourdomain.com/2024/linux-commands-notes.html"/>
    <id>https://yourdomain.com/2024/linux-commands-notes.html</id>
    <published>2024-11-21T12:34:00.000Z</published>
    <updated>2024-11-21T12:34:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Linux-命令通用性"><a href="#Linux-命令通用性" class="headerlink" title="Linux 命令通用性"></a>Linux 命令通用性</h2><p>大部分核心命令（<code>ls</code>, <code>cd</code>, <code>cat</code>, <code>grep</code>, <code>find</code> 等）在不同发行版中通用，遵循 POSIX 标准。</p><p>差异主要在包管理器：</p><table><thead><tr><th>发行版</th><th>包管理器</th></tr></thead><tbody><tr><td>Red Hat &#x2F; CentOS &#x2F; Fedora</td><td><code>yum</code> &#x2F; <code>dnf</code></td></tr><tr><td>Debian &#x2F; Ubuntu &#x2F; Mint</td><td><code>apt</code></td></tr><tr><td>Arch &#x2F; Manjaro</td><td><code>pacman</code></td></tr><tr><td>openSUSE</td><td><code>zypper</code></td></tr></tbody></table><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><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="built_in">cat</span> file             <span class="comment"># 显示文件内容</span></span><br><span class="line"><span class="built_in">tail</span> -n 10 file      <span class="comment"># 查看文件末尾 10 行</span></span><br><span class="line"><span class="built_in">tail</span> -n 10 file | grep <span class="string">&quot;error&quot;</span>  <span class="comment"># 结合 grep 过滤</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">mkdir</span> <span class="built_in">dir</span>            <span class="comment"># 创建文件夹</span></span><br><span class="line"><span class="built_in">mkdir</span> -p a/b/c       <span class="comment"># 创建多层级目录</span></span><br><span class="line"><span class="built_in">mkdir</span> -v <span class="built_in">dir</span>         <span class="comment"># 显示创建详情</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">rmdir</span> <span class="built_in">dir</span>            <span class="comment"># 删除空目录</span></span><br><span class="line"><span class="built_in">rm</span> -r <span class="built_in">dir</span>            <span class="comment"># 递归删除非空目录</span></span><br><span class="line"><span class="built_in">rm</span> -rf <span class="built_in">dir</span>           <span class="comment"># 强制删除（慎用！）</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">touch</span> file           <span class="comment"># 创建文件</span></span><br><span class="line"><span class="built_in">rm</span> file              <span class="comment"># 删除文件</span></span><br></pre></td></tr></table></figure><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><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="comment"># 覆盖写入</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Hello&quot;</span> &gt; file.txt</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;World&quot;</span> &gt;&gt; file.txt</span><br><span class="line"></span><br><span class="line"><span class="comment"># sudo 只能提升命令本身的权限，不能提升重定向(&gt;)的权限</span></span><br><span class="line"><span class="comment"># 正确做法：</span></span><br><span class="line"><span class="built_in">sudo</span> sh -c <span class="string">&#x27;echo &quot;config&quot; &gt; /etc/somefile&#x27;</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;./path/to/file&quot;</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="keyword">else</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="keyword">fi</span></span><br></pre></td></tr></table></figure><h2 id="高并发常见调优"><a href="#高并发常见调优" class="headerlink" title="高并发常见调优"></a>高并发常见调优</h2><h3 id="JVM-层面"><a href="#JVM-层面" class="headerlink" title="JVM 层面"></a>JVM 层面</h3><p><strong>垃圾回收（GC）</strong>：</p><ul><li>高并发下频繁 GC 会导致停顿</li><li>调优方向：使用 G1 &#x2F; ZGC、增大堆内存（<code>-Xms</code> &#x2F; <code>-Xmx</code>）、减少短生命周期对象</li></ul><p><strong>缓存区满</strong>：</p><ul><li>增加缓存区大小</li><li>优化缓存策略（如 LRU）</li></ul><h3 id="Nginx-层面"><a href="#Nginx-层面" class="headerlink" title="Nginx 层面"></a>Nginx 层面</h3><figure class="highlight nginx"><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="attribute">worker_processes</span> auto;           <span class="comment"># 增加工作进程数</span></span><br><span class="line"><span class="attribute">keepalive_timeout</span> <span class="number">65</span>;            <span class="comment"># 调整连接超时</span></span><br><span class="line"><span class="attribute">proxy_cache_path</span> /path levels=<span class="number">1</span>:<span class="number">2</span> keys_zone=my_cache:<span class="number">10m</span>;  <span class="comment"># 开启缓存</span></span><br></pre></td></tr></table></figure><h3 id="系统参数"><a href="#系统参数" class="headerlink" title="系统参数"></a>系统参数</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看当前值</span></span><br><span class="line">sysctl net.ipv4.tcp_max_syn_backlog</span><br><span class="line">sysctl net.core.somaxconn</span><br><span class="line"></span><br><span class="line"><span class="comment"># 临时修改（重启失效）</span></span><br><span class="line">sysctl -w net.ipv4.tcp_max_syn_backlog=2048</span><br><span class="line">sysctl -w net.core.somaxconn=256</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;net.ipv4.tcp_max_syn_backlog = 2048&quot;</span> &gt;&gt; /etc/sysctl.conf</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;net.core.somaxconn = 256&quot;</span> &gt;&gt; /etc/sysctl.conf</span><br><span class="line">sysctl -p</span><br></pre></td></tr></table></figure><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><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="built_in">df</span> -h                           <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="built_in">sudo</span> <span class="built_in">rm</span> -rf /tmp/*</span><br><span class="line"></span><br><span class="line"><span class="comment"># 清理包管理器缓存</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get clean              <span class="comment"># Debian 系</span></span><br><span class="line"><span class="built_in">sudo</span> yum clean all              <span class="comment"># Red Hat 系</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Linux 核心命令、Vim/Yum、高并发下 JVM/GC/Nginx/系统参数调优</summary>
    
    
    
    <category term="指令" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/"/>
    
    <category term="杂记" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/%E6%9D%82%E8%AE%B0/"/>
    
    
    <category term="Linux" scheme="https://yourdomain.com/tags/Linux/"/>
    
    <category term="Nginx" scheme="https://yourdomain.com/tags/Nginx/"/>
    
    <category term="JVM" scheme="https://yourdomain.com/tags/JVM/"/>
    
    <category term="运维" scheme="https://yourdomain.com/tags/%E8%BF%90%E7%BB%B4/"/>
    
  </entry>
  
  <entry>
    <title>01 背包：Knapsack 1</title>
    <link href="https://yourdomain.com/2024/knapsack-01-intro.html"/>
    <id>https://yourdomain.com/2024/knapsack-01-intro.html</id>
    <published>2024-10-25T13:21:00.000Z</published>
    <updated>2024-10-25T13:21:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>第一次在 AtCoder 上做到 01 背包，发现虽然是模板题，但自己写的时候还是卡了不少地方——主要是 Java 的数组类型和下标转换细节。</p><h2 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h2><p>给定 <code>N</code> 个物品，每个物品有重量 <code>w[i]</code> 和价值 <code>v[i]</code>。背包容量为 <code>W</code>，求能装入的最大总价值。</p><p>每个物品最多选一次，经典的 01 背包问题。</p><h2 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h2><p>01 背包的标准做法是用一维 DP 数组，容量从大到小遍历，保证每个物品只取一次。</p><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">f[j] = max(f[j - w[i]] + v[i], f[j])</span><br></pre></td></tr></table></figure><p>其中 <code>f[j]</code> 表示容量为 <code>j</code> 时能获得的最大价值。</p><p><strong>关键细节</strong>：</p><ul><li>数据范围较大，<code>f</code> 数组必须开 <code>long</code>，否则会溢出</li><li>Java 中数组下标必须是 <code>int</code>，而 <code>w[i]</code> 存为 <code>long</code> 时需要用 <code>(int)</code> 强转</li><li>空间上提前初始化一个足够大的数组（如 <code>maxn = 100005</code>），避免动态扩容</li></ul><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><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">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Y</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">maxn</span> <span class="operator">=</span> <span class="number">100005</span>;</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> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">scanner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> scanner.nextInt();</span><br><span class="line">        <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> scanner.nextInt();</span><br><span class="line">        <span class="type">long</span>[] f = <span class="keyword">new</span> <span class="title class_">long</span>[maxn];</span><br><span class="line">        <span class="type">long</span>[] w = <span class="keyword">new</span> <span class="title class_">long</span>[maxn];</span><br><span class="line">        <span class="type">long</span>[] v = <span class="keyword">new</span> <span class="title class_">long</span>[maxn];</span><br><span class="line"></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">1</span>; i &lt;= n; ++i) &#123;</span><br><span class="line">            w[i] = scanner.nextLong();</span><br><span class="line">            v[i] = scanner.nextLong();</span><br><span class="line">        &#125;</span><br><span class="line"></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">1</span>; i &lt;= n; ++i) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> m; j &gt;= w[i]; --j) &#123;</span><br><span class="line">                f[j] = Math.max(f[j - (<span class="type">int</span>) w[i]] + v[i], f[j]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        System.out.println(f[m]);</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><ul><li><strong>时间复杂度</strong>：O(N × W)，其中 N 为物品数，W 为背包容量</li><li><strong>空间复杂度</strong>：O(W)，使用一维滚动数组优化后的结果</li></ul><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>01 背包的一维写法要<strong>倒序遍历容量</strong>，这是核心套路</li><li>Java 做题时注意 <code>long</code> 数组和 <code>(int)</code> 类型转换，各语言的类型细节不同容易在这里耽误时间</li><li>初始化一个大数组而不是用 <code>ArrayList</code> 或动态结构，在竞赛中更省心</li></ul>]]></content>
    
    
    <summary type="html">AtCoder DP  Contest 第一题，01 背包模板题，记录 Java 实现时需要注意的数组类型和下标转换细节</summary>
    
    
    
    <category term="算法" scheme="https://yourdomain.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="Java" scheme="https://yourdomain.com/tags/Java/"/>
    
    <category term="动态规划" scheme="https://yourdomain.com/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
    <category term="背包" scheme="https://yourdomain.com/tags/%E8%83%8C%E5%8C%85/"/>
    
  </entry>
  
  <entry>
    <title>MySQL 连接管理常用命令</title>
    <link href="https://yourdomain.com/2024/mysql-connection-commands.html"/>
    <id>https://yourdomain.com/2024/mysql-connection-commands.html</id>
    <published>2024-10-15T15:33:00.000Z</published>
    <updated>2024-10-15T15:33:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="密码说明"><a href="#密码说明" class="headerlink" title="密码说明"></a>密码说明</h2><ul><li>桌面 SQL：常用密码</li><li>虚拟机中 MySQL：<code>root</code></li></ul><h2 id="查看连接信息"><a href="#查看连接信息" class="headerlink" title="查看连接信息"></a>查看连接信息</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查看当前的连接实例</span></span><br><span class="line"><span class="keyword">SHOW</span> <span class="keyword">FULL</span> PROCESSLIST;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查看连接总数</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">COUNT</span>(<span class="operator">*</span>) <span class="keyword">FROM</span> INFORMATION_SCHEMA.PROCESSLIST;</span><br></pre></td></tr></table></figure><h2 id="最大连接数"><a href="#最大连接数" class="headerlink" title="最大连接数"></a>最大连接数</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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 查看最大连接数</span></span><br><span class="line"><span class="keyword">SHOW</span> VARIABLES <span class="keyword">LIKE</span> <span class="string">&#x27;%max_connections%&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 临时设置（重启后失效）</span></span><br><span class="line"><span class="keyword">SET</span> <span class="keyword">GLOBAL</span> max_connections <span class="operator">=</span> <span class="number">200</span>;</span><br></pre></td></tr></table></figure><blockquote><p>配置文件方式修改需要重启数据库，但修改是永久的。</p></blockquote>]]></content>
    
    
    <summary type="html">MySQL 查看数据库连接、最大连接数及设置方法</summary>
    
    
    
    <category term="指令" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/"/>
    
    <category term="杂记" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/%E6%9D%82%E8%AE%B0/"/>
    
    
    <category term="数据库" scheme="https://yourdomain.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    <category term="MySQL" scheme="https://yourdomain.com/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>关于轮询和定时任务</title>
    <link href="https://yourdomain.com/2024/polling-vs-mq-delay.html"/>
    <id>https://yourdomain.com/2024/polling-vs-mq-delay.html</id>
    <published>2024-08-28T13:39:00.000Z</published>
    <updated>2024-08-28T13:39:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>对于过期状态修改（如订单超时取消），有两种常见处理方式：轮询和 MQ 延时消息。</p><h2 id="方案一：轮询"><a href="#方案一：轮询" class="headerlink" title="方案一：轮询"></a>方案一：轮询</h2><p>直接暴力扫表，对不合法状态进行修改。</p><p><strong>缺点</strong>：</p><ul><li>即使没有订单也会去扫表，浪费资源</li><li>数据量大时不够友好</li></ul><h2 id="方案二：MQ-延时消息"><a href="#方案二：MQ-延时消息" class="headerlink" title="方案二：MQ 延时消息"></a>方案二：MQ 延时消息</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><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">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> RabbitTemplate rabbitTemplate;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sendDelayedOrderMessage</span><span class="params">(Long orderId)</span> &#123;</span><br><span class="line">    <span class="type">MessageProperties</span> <span class="variable">messageProperties</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MessageProperties</span>();</span><br><span class="line">    messageProperties.setHeader(<span class="string">&quot;x-delay&quot;</span>, <span class="number">900000</span>); <span class="comment">// 15 分钟</span></span><br><span class="line">    <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> MessageBuilder.withBody(String.valueOf(orderId).getBytes())</span><br><span class="line">                                    .andProperties(messageProperties)</span><br><span class="line">                                    .build();</span><br><span class="line">    rabbitTemplate.convertAndSend(<span class="string">&quot;order.delay.queue&quot;</span>, message);</span><br><span class="line">    log.info(<span class="string">&quot;发送延时消息：&#123;&#125;&quot;</span>, orderId);</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><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="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> OrderMapper orderMapper;</span><br><span class="line"></span><br><span class="line"><span class="meta">@RabbitListener(queues = &quot;order.delay.queue&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleDelayedOrderMessage</span><span class="params">(Message message)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">orderIdStr</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(message.getBody());</span><br><span class="line">    <span class="type">Long</span> <span class="variable">orderId</span> <span class="operator">=</span> Long.parseLong(orderIdStr);</span><br><span class="line">    <span class="type">Orders</span> <span class="variable">order</span> <span class="operator">=</span> orderMapper.getById(orderId);</span><br><span class="line">    <span class="keyword">if</span> (order != <span class="literal">null</span> &amp;&amp; order.getStatus() == Orders.PENDING_PAYMENT) &#123;</span><br><span class="line">        order.setStatus(Orders.CANCELLED);</span><br><span class="line">        order.setCancelReason(<span class="string">&quot;订单超时，自动取消&quot;</span>);</span><br><span class="line">        order.setCancelTime(LocalDateTime.now());</span><br><span class="line">        orderMapper.update(order);</span><br><span class="line">        log.info(<span class="string">&quot;处理订单超时：&#123;&#125;&quot;</span>, orderId);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>优点</strong>：只在有订单时查询，每次处理的数据量可控。</p><p><strong>缺点</strong>：同一时刻数据量非常大时，部分数据可能在限定时间内得不到处理（可以通过增加消费者、使用死信队列等方式缓解，但也会引入消息幂等性等新问题）。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>两种方案各有利弊：</p><ul><li>轮询：实现简单，高峰时处理能力强，但空闲时浪费资源</li><li>MQ 延时消息：按需触发，但瞬时高峰吞吐有限</li></ul><p>没有银弹，所谓架构就是在各种不完美中找到当下最合适的妥协。</p>]]></content>
    
    
    <summary type="html">订单超时处理——轮询扫表 vs MQ 延时消息的对比与思考</summary>
    
    
    
    <category term="指令" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/"/>
    
    <category term="杂记" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/%E6%9D%82%E8%AE%B0/"/>
    
    
    <category term="RabbitMQ" scheme="https://yourdomain.com/tags/RabbitMQ/"/>
    
    <category term="延时消息" scheme="https://yourdomain.com/tags/%E5%BB%B6%E6%97%B6%E6%B6%88%E6%81%AF/"/>
    
    <category term="轮询" scheme="https://yourdomain.com/tags/%E8%BD%AE%E8%AF%A2/"/>
    
    <category term="订单" scheme="https://yourdomain.com/tags/%E8%AE%A2%E5%8D%95/"/>
    
  </entry>
  
  <entry>
    <title>Spring Boot 邮件发送</title>
    <link href="https://yourdomain.com/2024/email-smtp-qq.html"/>
    <id>https://yourdomain.com/2024/email-smtp-qq.html</id>
    <published>2024-08-28T13:21:00.000Z</published>
    <updated>2024-08-28T13:21:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前置准备"><a href="#前置准备" class="headerlink" title="前置准备"></a>前置准备</h2><p>使用 QQ 邮箱发送邮件需要先开启 POP3&#x2F;IMAP&#x2F;SMTP 服务，并在授权码管理页面生成授权码。</p><ul><li>开启位置：QQ 邮箱 → 设置 → 账户 → POP3&#x2F;IMAP&#x2F;SMTP&#x2F;Exchange&#x2F;CardDAV 服务</li><li>生成的授权码作为配置中的 <code>password</code></li></ul><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><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><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">mail:</span></span><br><span class="line">  <span class="attr">host:</span> <span class="string">smtp.qq.com</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">465</span></span><br><span class="line">  <span class="attr">username:</span> <span class="number">1271631984</span><span class="string">@qq.com</span></span><br><span class="line">  <span class="attr">password:</span> <span class="string">tysatqekxlsbfjig</span></span><br><span class="line">  <span class="attr">properties:</span></span><br><span class="line">    <span class="attr">mail:</span></span><br><span class="line">      <span class="attr">smtp:</span></span><br><span class="line">        <span class="attr">auth:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">socketFactory:</span></span><br><span class="line">          <span class="attr">class:</span> <span class="string">javax.net.ssl.SSLSocketFactory</span></span><br><span class="line">          <span class="attr">fallback:</span> <span class="literal">false</span></span><br><span class="line">          <span class="attr">port:</span> <span class="number">465</span></span><br></pre></td></tr></table></figure><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><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">void</span> <span class="title function_">email</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> <span class="string">&quot;1271631984@qq.com&quot;</span>;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">String</span> <span class="variable">password</span> <span class="operator">=</span> <span class="string">&quot;tysatqekxlsbfjig&quot;</span>;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">String</span> <span class="variable">to</span> <span class="operator">=</span> <span class="string">&quot;2166702512@qq.com&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">MimeMessage</span> <span class="variable">mimeMessage</span> <span class="operator">=</span> javaMailSender.createMimeMessage();</span><br><span class="line">    MimeMessageHelper mimeMessageHelper;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        mimeMessageHelper = <span class="keyword">new</span> <span class="title class_">MimeMessageHelper</span>(mimeMessage, <span class="literal">true</span>);</span><br><span class="line">        mimeMessageHelper.setTo(to);</span><br><span class="line">        mimeMessageHelper.setFrom(username);</span><br><span class="line">        mimeMessageHelper.setSubject(<span class="string">&quot;匹配成功！脱离单身系列&quot;</span>);</span><br><span class="line">        mimeMessageHelper.setText(<span class="string">&quot;周*浚 你好,&quot;</span></span><br><span class="line">                + <span class="string">&quot;\n\n介于你之前在网上对于单身现状的苦恼困惑 &quot;</span></span><br><span class="line">                + <span class="string">&quot;我司根据你之前所填写信息 现已为你匹配到合适的另一半 &quot;</span></span><br><span class="line">                + <span class="string">&quot;可通过添加对方qq好友进行进一步了解 QQ:2107886532&quot;</span>);</span><br><span class="line">        javaMailSender.send(mimeMessage);</span><br><span class="line">        System.out.println(<span class="string">&quot;发送成功&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (MessagingException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">        System.out.println(<span class="string">&quot;邮件发送失败&quot;</span>);</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><ul><li>密码使用 QQ 邮箱的<strong>授权码</strong>，不是登录密码</li><li>生产环境应将邮件发送封装成独立方法，接收发送对象和内容参数</li></ul>]]></content>
    
    
    <summary type="html">基于 Spring Boot JavaMailSender 实现 QQ 邮件发送，包含 SMTP 配置</summary>
    
    
    
    <category term="指令" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/"/>
    
    <category term="杂记" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/%E6%9D%82%E8%AE%B0/"/>
    
    
    <category term="Java" scheme="https://yourdomain.com/tags/Java/"/>
    
    <category term="Spring Boot" scheme="https://yourdomain.com/tags/Spring-Boot/"/>
    
    <category term="邮件" scheme="https://yourdomain.com/tags/%E9%82%AE%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>Vagrant &amp; Docker 常用命令</title>
    <link href="https://yourdomain.com/2024/vagrant-docker-commands.html"/>
    <id>https://yourdomain.com/2024/vagrant-docker-commands.html</id>
    <published>2024-08-12T12:10:00.000Z</published>
    <updated>2024-08-12T12:10:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Vagrant"><a href="#Vagrant" class="headerlink" title="Vagrant"></a>Vagrant</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">vagrant up          <span class="comment"># 启动虚拟机</span></span><br><span class="line">vagrant ssh         <span class="comment"># SSH 连接</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 默认 root 密码</span></span><br><span class="line">root password: vagrant</span><br></pre></td></tr></table></figure><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><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> -R 777 文件夹路径    <span class="comment"># 递归设置所有人可读写执行</span></span><br><span class="line"><span class="built_in">sudo</span>                      <span class="comment"># 以 root 权限执行</span></span><br></pre></td></tr></table></figure><h2 id="Docker"><a href="#Docker" class="headerlink" title="Docker"></a>Docker</h2><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置镜像源</span></span><br><span class="line"><span class="built_in">sudo</span> vi /etc/docker/daemon.json</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重载配置并重启</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl daemon-reload</span><br><span class="line"><span class="built_in">sudo</span> systemctl restart docker</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看 Docker 信息</span></span><br><span class="line"><span class="built_in">sudo</span> docker info</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看所有运行中的容器</span></span><br><span class="line">docker ps -a</span><br></pre></td></tr></table></figure><h2 id="SSH"><a href="#SSH" class="headerlink" title="SSH"></a>SSH</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">ssh qi@192.168.11.128    <span class="comment"># 界面化连接远程主机</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Vagrant 虚拟机管理和 Docker 常用操作命令</summary>
    
    
    
    <category term="指令" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/"/>
    
    <category term="杂记" scheme="https://yourdomain.com/categories/%E6%8C%87%E4%BB%A4/%E6%9D%82%E8%AE%B0/"/>
    
    
    <category term="Linux" scheme="https://yourdomain.com/tags/Linux/"/>
    
    <category term="运维" scheme="https://yourdomain.com/tags/%E8%BF%90%E7%BB%B4/"/>
    
    <category term="Vagrant" scheme="https://yourdomain.com/tags/Vagrant/"/>
    
    <category term="Docker" scheme="https://yourdomain.com/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>我的记录</title>
    <link href="https://yourdomain.com/2024/my-records.html"/>
    <id>https://yourdomain.com/2024/my-records.html</id>
    <published>2024-07-18T16:00:00.000Z</published>
    <updated>2024-07-18T16:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="CSDN"><a href="#CSDN" class="headerlink" title="CSDN"></a>CSDN</h2><p><img src="/img/csdn.png" alt="CSDN"></p><h2 id="Gitee-贡献"><a href="#Gitee-贡献" class="headerlink" title="Gitee 贡献"></a>Gitee 贡献</h2><h3 id="2026"><a href="#2026" class="headerlink" title="2026"></a>2026</h3><p><img src="/img/gitee_2026_contribution.png" alt="Gitee 2026"></p><h3 id="2025"><a href="#2025" class="headerlink" title="2025"></a>2025</h3><p><img src="/img/gitee_2025_contribution.png" alt="Gitee 2025"></p><h3 id="2024"><a href="#2024" class="headerlink" title="2024"></a>2024</h3><p><img src="/img/gitee_2024_contribution.png" alt="Gitee 2024"></p>]]></content>
    
    
    <summary type="html">CSDN 与 Gitee 数据记录</summary>
    
    
    
    <category term="我的记录" scheme="https://yourdomain.com/categories/%E6%88%91%E7%9A%84%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="记录" scheme="https://yourdomain.com/tags/%E8%AE%B0%E5%BD%95/"/>
    
  </entry>
  
  <entry>
    <title>DFS 入门：从汉诺塔到 n 的全排列</title>
    <link href="https://yourdomain.com/2024/dfs-permutation-intro.html"/>
    <id>https://yourdomain.com/2024/dfs-permutation-intro.html</id>
    <published>2024-06-29T09:53:00.000Z</published>
    <updated>2024-06-29T09:53:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>上学期在 JavaEE 学习中第一次接触 DFS 思想，是从汉诺塔问题开始的。汉诺塔很适合作为递归入门：把一个大问题拆成“先移动上面的盘子、再移动当前盘子、最后移动剩下的盘子”。</p><p>这学期补算法题时，又遇见了一个很典型的问题：<code>n</code> 的全排列。</p><h2 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h2><p>输入一个数字 <code>n</code>。</p><p>输出从 <code>1</code> 到 <code>n</code> 这 <code>n</code> 个数字组成的所有长度为 <code>n</code> 的排列。</p><p>例如输入 <code>3</code>，可以输出：</p><figure class="highlight text"><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">123</span><br><span class="line">132</span><br><span class="line">213</span><br><span class="line">231</span><br><span class="line">312</span><br><span class="line">321</span><br></pre></td></tr></table></figure><h2 id="DFS-思想"><a href="#DFS-思想" class="headerlink" title="DFS 思想"></a>DFS 思想</h2><p>这道题仍然是 DFS 的思想。</p><p>写递归时，可以先考虑“当前这一步做什么”，或者假设当前只有一步要完成。这一点很像分治：先把当前层的问题想清楚，再把剩下的问题交给下一层递归。</p><p>在全排列里，当前这一步就是：</p><blockquote><p>给当前位置 <code>temp</code> 选择一个还没有使用过的数字。</p></blockquote><p>比如第三个数字也可能成为第二个位置上的数字，所以每一层都要从 <code>1</code> 到 <code>n</code> 重新枚举。但为了避免数字重复使用，需要额外开一个数组 <code>vis</code> 记录每个数字是否已经被用过。</p><h2 id="状态记录与还原"><a href="#状态记录与还原" class="headerlink" title="状态记录与还原"></a>状态记录与还原</h2><p>和普通递归相比，这里的新技巧是多使用一个数组记录状态：</p><ul><li><code>nums[temp]</code>：记录当前位置选择了哪个数字</li><li><code>vis[i]</code>：记录数字 <code>i</code> 是否已经被使用</li></ul><p>每次选择一个数字后，先把它标记为已使用，然后进入下一层递归。</p><p>递归结束回来以后，还要把这个数字恢复为未使用，方便下一次枚举。这一步叫“回溯”，也就是把当前选择造成的状态影响撤销掉。</p><h2 id="Java-代码"><a href="#Java-代码" class="headerlink" title="Java 代码"></a>Java 代码</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">(<span class="type">int</span> temp)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (temp == n + <span class="number">1</span>) &#123;</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">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">            System.out.print(nums[i]);</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println();</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (vis[i] != <span class="number">1</span>) &#123;</span><br><span class="line">            nums[temp] = i;</span><br><span class="line">            vis[i] = <span class="number">1</span>;</span><br><span class="line">            test(temp + <span class="number">1</span>);</span><br><span class="line">            vis[i] = <span class="number">0</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><h2 id="递归过程理解"><a href="#递归过程理解" class="headerlink" title="递归过程理解"></a>递归过程理解</h2><p>可以把 <code>temp</code> 理解为“当前正在填写第几个位置”。</p><p>当 <code>temp == n + 1</code> 时，说明从第 <code>1</code> 个位置到第 <code>n</code> 个位置都已经填好了，这时输出一次答案。</p><p>否则，就枚举所有数字：</p><ol><li>如果数字 <code>i</code> 没有被使用，就把它放到当前位置。</li><li>标记 <code>vis[i] = 1</code>，表示这个数字已经被当前排列占用。</li><li>递归处理下一个位置，也就是 <code>test(temp + 1)</code>。</li><li>回来后执行 <code>vis[i] = 0</code>，恢复现场，让后面的排列还能继续使用这个数字。</li></ol><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>DFS 写法的关键不是一开始就想完整棵搜索树，而是先想清楚当前层要做什么。</p><p>全排列问题中，当前层要做的事情就是：给当前位置选择一个未使用过的数字。为了让每一层都能正确枚举，需要用 <code>vis</code> 数组记录状态，并在递归返回时还原状态。</p><p>这就是 DFS + 回溯最基础、也最常见的一种模板。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;上学期在 JavaEE 学习中第一次接触 DFS 思想，是从汉诺塔问题开始的。汉诺塔很适合作为递归入门：把一个大问题拆成“先移动上面的盘子、再移动当前盘子、最后移动剩下的盘子”。&lt;/p&gt;
&lt;p&gt;这学期补算法题时，又遇见了一个很典型的问题：&lt;code&gt;n&lt;/code&gt; 的全排</summary>
      
    
    
    
    <category term="算法" scheme="https://yourdomain.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="DFS" scheme="https://yourdomain.com/tags/DFS/"/>
    
    <category term="全排列" scheme="https://yourdomain.com/tags/%E5%85%A8%E6%8E%92%E5%88%97/"/>
    
    <category term="Java" scheme="https://yourdomain.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Frog 2</title>
    <link href="https://yourdomain.com/2024/frog-2-at-dp.html"/>
    <id>https://yourdomain.com/2024/frog-2-at-dp.html</id>
    <published>2024-06-29T09:52:00.000Z</published>
    <updated>2024-06-29T09:52:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>Frog 1 的进阶版，将固定跳跃步数改成了动态步数。核心思路不变，但多了初始化细节。</p><h2 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h2><p>定义 <code>dp[i]</code> 为跳到第 <code>i</code> 个石头的最小花费。</p><p>每次可以从第 <code>i</code> 个石头跳到 <code>i+1</code> 到 <code>i+k</code> 范围内的任意石头：</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">dp[i + j] = min(dp[i + j], dp[i] + |a[i] - a[i + j]|)</span><br></pre></td></tr></table></figure><p><strong>关键细节</strong>：因为取的是最小值，而 <code>int[]</code> 默认值为 0，需要先初始化成极大值，同时 <code>dp[1] = 0</code>（从起点开始，花费为 0）。</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">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>];</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">1</span>; i &lt;= n; i++) dp[i] = Integer.MAX_VALUE;</span><br><span class="line">dp[<span class="number">1</span>] = <span class="number">0</span>;</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">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= k; j++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (i + j &lt;= n)</span><br><span class="line">            dp[i + j] = Math.min(dp[i + j], dp[i] + Math.abs(a[i] - a[i + j]));</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">System.out.println(dp[n]);</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>线性 DP，但步数不固定，加一层循环即可</li><li><strong>注意初始化</strong>：取最小值时一定要把 dp 初始化为极大值，否则默认的 0 会干扰结果</li></ul>]]></content>
    
    
    <summary type="html">AtCoder DP Contest B - Frog 2，动态步数线性 DP，注意数组初始化</summary>
    
    
    
    <category term="算法" scheme="https://yourdomain.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="动态规划" scheme="https://yourdomain.com/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
    <category term="线性DP" scheme="https://yourdomain.com/tags/%E7%BA%BF%E6%80%A7DP/"/>
    
    <category term="AtCoder" scheme="https://yourdomain.com/tags/AtCoder/"/>
    
  </entry>
  
  <entry>
    <title>P1075 [NOIP2012 普及组] 质因数分解</title>
    <link href="https://yourdomain.com/2024/prime-factorization-p1075.html"/>
    <id>https://yourdomain.com/2024/prime-factorization-p1075.html</id>
    <published>2024-06-29T09:52:00.000Z</published>
    <updated>2024-06-29T09:52:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h2><p>从后往前枚举虽然直觉上更直接（要找大的那个质数），但当数据很大时，从 <code>n-1</code> 开始往前枚举效率极差，会 TLE。</p><p>反过来从前往后枚举，找到第一个能被 <code>n</code> 整除的数，它就是较小的那个质因数，那么 <code>n / i</code> 就是较大的那个质因数，直接输出。</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></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">n</span> <span class="operator">=</span> nextLong();</span><br><span class="line"><span class="type">long</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"><span class="keyword">while</span> (i &lt; Math.sqrt(n)) &#123;</span><br><span class="line">    <span class="keyword">if</span> (n % i == <span class="number">0</span>) &#123;</span><br><span class="line">        System.out.println(n / i);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    i++;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>从前往后枚举往往比从后往前快得多</li><li>枚举到 <code>sqrt(n)</code> 即可，因为因数成对出现</li></ul>]]></content>
    
    
    <summary type="html">NOIP2012 普及组，质因数分解，从后往前枚举会超时，改为从前往后枚举</summary>
    
    
    
    <category term="算法" scheme="https://yourdomain.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="数学" scheme="https://yourdomain.com/tags/%E6%95%B0%E5%AD%A6/"/>
    
    <category term="质数" scheme="https://yourdomain.com/tags/%E8%B4%A8%E6%95%B0/"/>
    
    <category term="NOIP" scheme="https://yourdomain.com/tags/NOIP/"/>
    
  </entry>
  
  <entry>
    <title>P3397 地毯</title>
    <link href="https://yourdomain.com/2024/carpet-p3397.html"/>
    <id>https://yourdomain.com/2024/carpet-p3397.html</id>
    <published>2024-04-07T13:26:00.000Z</published>
    <updated>2024-04-07T13:26:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="考点"><a href="#考点" class="headerlink" title="考点"></a>考点</h2><p>二维差分。</p><h2 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h2><p>暴力模拟会超时。</p><p><strong>一维差分的思路</strong>：对数组 <code>a</code> 构造差分数组 <code>b</code>，其中 <code>b[i] = a[i] - a[i-1]</code>。对区间 <code>[l, r]</code> 加上 <code>c</code> 时，只需 <code>b[l] += c</code>、<code>b[r+1] -= c</code>，最后前缀和还原。</p><p>对于二维，在 <code>(x1, y1)</code> 到 <code>(x2, y2)</code> 矩形区域加 <code>1</code>，差分操作是四个点：</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></pre></td><td class="code"><pre><span class="line">d[x1][y1]   += 1</span><br><span class="line">d[x1][y2+1] -= 1</span><br><span class="line">d[x2+1][y1] -= 1</span><br><span class="line">d[x2+1][y2+1] += 1</span><br></pre></td></tr></table></figure><p>最后对每行做前缀和即可得到最终的矩阵。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">l</span> <span class="operator">=</span> <span class="number">1</span>; l &lt;= n; l++) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">r</span> <span class="operator">=</span> <span class="number">1</span>; r &lt;= n; r++) &#123;</span><br><span class="line">        map[l][r] += map[l][r-<span class="number">1</span>];</span><br><span class="line">        System.out.print(map[l][r] + <span class="string">&quot; &quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    System.out.println();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><ul><li>差分的核心思想：区间修改转化为端点操作，避免暴力</li><li>二维差分可以拆成四个单点修改，再前缀和还原</li></ul>]]></content>
    
    
    <summary type="html">洛谷 P3397，二维差分模板，避免暴力超时</summary>
    
    
    
    <category term="算法" scheme="https://yourdomain.com/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="差分" scheme="https://yourdomain.com/tags/%E5%B7%AE%E5%88%86/"/>
    
    <category term="前缀和" scheme="https://yourdomain.com/tags/%E5%89%8D%E7%BC%80%E5%92%8C/"/>
    
  </entry>
  
</feed>
