<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://blog.81vm3.xyz/</id>
  <title type="text">Blume&apos;s Skylog</title>
  <subtitle type="text">全栈开发 · AI 应用</subtitle>
  <updated>2026-05-06T00:00:00.000Z</updated>
  <author><name>Blume</name></author>
  <link rel="alternate" href="https://blog.81vm3.xyz/"/>
  <link rel="self" href="https://blog.81vm3.xyz/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://blog.81vm3.xyz/posts/grafana/</id>
      <title type="text">Grafana 可视化与监控</title>
      <published>2026-05-06T00:00:00.000Z</published>
      <updated>2026-05-06T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/grafana/"/>
      <summary type="text">Grafana 的定位、常见数据源与 Grafana + Prometheus 经典监控链路。</summary>
      <content type="html"><![CDATA[<p>Grafana 是一个开源的数据可视化和监控平台，主要用于把各种数据源中的指标（Metrics）、日志（Logs）和追踪（Traces）展示成直观的仪表盘（Dashboard），方便运维、开发和业务人员监控系统状态。</p>
<section><h3>Grafana 能做什么？<a href="#grafana-能做什么"><span>#</span></a></h3><section><h4>1. 数据可视化<a href="#1-数据可视化"><span>#</span></a></h4><p>将数据绘制成：</p><ul>
<li>折线图</li>
<li>柱状图</li>
<li>饼图</li>
<li>仪表盘</li>
<li>热力图</li>
<li>表格</li>
</ul><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>CPU使用率</span></div></div><div><div><div>2</div></div><div><span>│</span></div></div><div><div><div>3</div></div><div><span>│      ╱╲</span></div></div><div><div><div>4</div></div><div><span>│    ╱╱  ╲</span></div></div><div><div><div>5</div></div><div><span>│  ╱╱     ╲</span></div></div><div><div><div>6</div></div><div><span>└───────────── 时间</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>2. 系统监控<a href="#2-系统监控"><span>#</span></a></h4><p>常用于监控：</p><ul>
<li>服务器 CPU、内存、磁盘</li>
<li>Kubernetes 集群</li>
<li>数据库性能</li>
<li>应用程序运行状态</li>
<li>网络流量</li>
</ul></section><section><h4>3. 告警通知<a href="#3-告警通知"><span>#</span></a></h4><p>当指标异常时自动发送：</p><ul>
<li>邮件</li>
<li>钉钉</li>
<li>企业微信</li>
<li>Slack</li>
<li>Webhook</li>
</ul><p>例如：</p><blockquote><p>CPU 使用率连续 5 分钟超过 90%，自动报警。</p></blockquote><hr /></section></section>
<section><h3>Grafana 的数据来源<a href="#grafana-的数据来源"><span>#</span></a></h3><p>Grafana 本身不存储监控数据，它负责展示数据。</p><p>常见搭配：</p>

<table><thead><tr><th>数据源</th><th>用途</th></tr></thead><tbody><tr><td>Prometheus</td><td>监控指标（最常见）</td></tr><tr><td>InfluxDB</td><td>时序数据库</td></tr><tr><td>Elasticsearch</td><td>日志分析</td></tr><tr><td>MySQL</td><td>业务数据</td></tr><tr><td>PostgreSQL</td><td>业务数据</td></tr><tr><td>Loki</td><td>日志监控</td></tr><tr><td>Tempo</td><td>链路追踪</td></tr></tbody></table><hr /></section>
<section><h3>Grafana + Prometheus 是最经典组合<a href="#grafana--prometheus-是最经典组合"><span>#</span></a></h3><p>架构通常如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>服务器</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span>Node Exporter</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>5</div></div><div><span>Prometheus</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>7</div></div><div><span>Grafana</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>9</div></div><div><span>Dashboard</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>工作流程：</p><ol>
<li>Node Exporter 收集服务器指标</li>
<li>Prometheus 定时拉取指标</li>
<li>Grafana 从 Prometheus 查询数据</li>
<li>Grafana 展示监控图表</li>
</ol><hr /></section>
<section><h3>一个典型场景<a href="#一个典型场景"><span>#</span></a></h3><p>比如你有一个 Java 网站：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用户访问网站</span></div></div><div><div><div>2</div></div><div><span><span>       </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span>Spring Boot</span></div></div><div><div><div>4</div></div><div><span><span>       </span></span><span>│</span></div></div><div><div><div>5</div></div><div><span>Prometheus</span></div></div><div><div><div>6</div></div><div><span><span>       </span></span><span>│</span></div></div><div><div><div>7</div></div><div><span>Grafana</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Grafana 可以实时展示：</p><ul>
<li>QPS（每秒请求数）</li>
<li>接口响应时间</li>
<li>错误率</li>
<li>JVM 内存</li>
<li>GC 次数</li>
<li>数据库连接数</li>
</ul><hr /></section>
<section><h3>为什么 Grafana 这么流行？<a href="#为什么-grafana-这么流行"><span>#</span></a></h3><p>优点：</p><ul>
<li>免费开源</li>
<li>界面美观</li>
<li>支持大量数据源</li>
<li>Dashboard 丰富</li>
<li>Kubernetes/云原生生态事实标准</li>
<li>插件生态完善</li>
</ul><p>很多公司会使用：</p><ul>
<li>Grafana + Prometheus（监控）</li>
<li>Grafana + Loki（日志）</li>
<li>Grafana + Tempo（链路追踪）</li>
</ul><p>形成完整的可观测性（Observability）平台。</p></section>
<section><h3>一句话理解<a href="#一句话理解"><span>#</span></a></h3><p><strong>Prometheus 负责“采集和存储监控数据”，Grafana 负责“把数据画成好看的图表并进行告警”。</strong></p><p>如果你是在学习 DevOps、运维、云原生或 Kubernetes，那么 Grafana 几乎是必学工具。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/trace-propagation/</id>
      <title type="text">微服务之间如何传递 trace</title>
      <published>2026-04-22T00:00:00.000Z</published>
      <updated>2026-04-22T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/trace-propagation/"/>
      <summary type="text">跨服务传递的是 trace 上下文而非 tracer：HTTP header、gRPC metadata 与 MQ 消息头的标准做法。</summary>
      <content type="html"><![CDATA[<p>跨微服务时，靠的是“上下文传播”，不是接口 body 显式保留 trace id。</p>
<p>先区分 3 个东西：</p>
<ul>
<li><strong>tracer</strong>：代码里 <code>otel.Tracer(...)</code> 的对象，表示哪段代码在打点</li>
<li><strong>trace id</strong>：一整条链路的 ID</li>
<li><strong>span id</strong>：链路里某个节点的 ID</li>
</ul>
<p>微服务之间传递的是 trace id/span 上下文，不是 tracer。</p>
<section><h2>HTTP 场景<a href="#http-场景"><span>#</span></a></h2><p>标准做法是放在请求头里，通常是 W3C Trace Context：</p><ul>
<li><code>traceparent</code></li>
<li><code>tracestate</code></li>
<li>可选 <code>baggage</code></li>
</ul><p>流程是：</p><ol>
<li>服务 A 收到请求，创建或继续一个 trace</li>
<li>服务 A 调服务 B 前，把当前 trace context 注入到 HTTP header</li>
<li>服务 B 收到请求后，从 header 提取 trace context</li>
<li>服务 B 基于这个 context 创建自己的 span</li>
</ol><p>这样 A 和 B 的 span 就会落到同一个 trace 里。</p><p>所以一般不需要你在接口 JSON 里专门加：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"traceId"</span><span>: </span><span>"..."</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>除非你有业务上的特殊需求。对 tracing 本身来说，不该这么做，原因是：</p><ul>
<li>污染业务接口契约</li>
<li>容易和真实 OTel 上下文不一致</li>
<li>标准库/中间件已经能自动处理 header</li>
</ul><p>HTTP 里通常像这样：</p><ul>
<li>服务端：<strong>Extract</strong>（从入站 header 恢复上下文）</li>
<li>客户端：<strong>Inject</strong>（把上下文写入出站 header）</li>
</ul><p>Go + OpenTelemetry 的服务端提取示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 服务端：从入站请求 header 中提取上下文，再基于它创建 span</span></div></div><div><div><div>2</div></div><div><span>ctx</span><span><span> </span><span>:=</span><span> </span></span><span>otel</span><span>.</span><span>GetTextMapPropagator</span><span>().</span><span>Extract</span><span>(</span></div></div><div><div><div>3</div></div><div><span>  </span><span>r</span><span>.</span><span>Context</span><span>(),</span></div></div><div><div><div>4</div></div><div><span>  </span><span>propagation</span><span>.</span><span>HeaderCarrier</span><span>(</span><span>r</span><span>.</span><span>Header</span><span>),</span></div></div><div><div><div>5</div></div><div><span>)</span></div></div><div><div><div>6</div></div><div><span>ctx</span><span>, </span><span>span</span><span><span> </span><span>:=</span><span> </span></span><span>tracer</span><span>.</span><span>Start</span><span>(</span><span>ctx</span><span>, </span><span>"handle-request"</span><span>)</span></div></div><div><div><div>7</div></div><div><span>defer</span><span> </span><span>span</span><span>.</span><span>End</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>客户端注入示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 客户端：把当前上下文注入到出站请求 header</span></div></div><div><div><div>2</div></div><div><span>req</span><span><span> </span><span>:=</span><span> </span></span><span>httptest</span><span>.</span><span>NewRequest</span><span>(</span><span>"GET"</span><span>, </span><span>"http://service-b/api"</span><span>, </span><span>nil</span><span>)</span></div></div><div><div><div>3</div></div><div><span>otel</span><span>.</span><span>GetTextMapPropagator</span><span>().</span><span>Inject</span><span>(</span></div></div><div><div><div>4</div></div><div><span>  </span><span>ctx</span><span>,</span></div></div><div><div><div>5</div></div><div><span>  </span><span>propagation</span><span>.</span><span>HeaderCarrier</span><span>(</span><span>req</span><span>.</span><span>Header</span><span>),</span></div></div><div><div><div>6</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>gRPC 与消息队列<a href="#grpc-与消息队列"><span>#</span></a></h2><p>gRPC 也是一样，只是不是 HTTP header，而是 gRPC metadata。原理完全相同：</p><ul>
<li>client interceptor 注入</li>
<li>server interceptor 提取</li>
</ul><p>消息队列也类似，通常放在消息 header / metadata 里，不放消息 body。</p></section>
<section><h2>实务建议<a href="#实务建议"><span>#</span></a></h2><ul>
<li>HTTP：用 <code>traceparent</code> 这些标准 header</li>
<li>gRPC：用 metadata</li>
<li>MQ/Kafka：用 message headers</li>
<li>不要把 trace id 做成业务接口字段，除非只是为了调试展示</li>
<li>可以额外返回一个 <code>X-Trace-ID</code> 给前端或调用方排查问题，但这只是便于观察，不是主链路传播的标准方式</li>
</ul><p>相关概念见 OpenTelemetry 与链路追踪笔记。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/k8s-port-forward/</id>
      <title type="text">kubectl port-forward 端口转发</title>
      <published>2026-04-08T00:00:00.000Z</published>
      <updated>2026-04-08T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/k8s-port-forward/"/>
      <summary type="text">kubectl port-forward 转发 Pod/Service、让局域网访问以及常用排查命令。</summary>
      <content type="html"><![CDATA[<section><h1>k8s 端口转发<a href="#k8s-端口转发"><span>#</span></a></h1><p>最常用的是 <code>kubectl port-forward</code>。</p><section><h2>转发 Pod<a href="#转发-pod"><span>#</span></a></h2><p>把某个 Pod 的 <code>8080</code> 转到本机 <code>18080</code>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>port-forward</span><span> </span><span>pod/&lt;pod-name&gt;</span><span> </span><span>18080:8080</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果 Pod 在指定 namespace：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>-n</span><span> </span><span>&lt;namespace&gt;</span><span> </span><span>port-forward</span><span> </span><span>pod/&lt;pod-name&gt;</span><span> </span><span>18080:8080</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>访问方式：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>http://127.0.0.1:18080</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>转发 Service<a href="#转发-service"><span>#</span></a></h2><p>通常转发 Service 更稳：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>-n</span><span> </span><span>&lt;namespace&gt;</span><span> </span><span>port-forward</span><span> </span><span>svc/&lt;service-name&gt;</span><span> </span><span>18080:8080</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注意这里后面的 <code>8080</code> 是 Service 的端口，不一定是容器端口。</p></section><section><h2>让局域网其他机器也能访问<a href="#让局域网其他机器也能访问"><span>#</span></a></h2><p>默认只监听本机 <code>127.0.0.1</code>。如果要让其他机器访问你这台主机上的转发端口：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>-n</span><span> </span><span>&lt;namespace&gt;</span><span> </span><span>port-forward</span><span> </span><span>--address</span><span> </span><span>0.0.0.0</span><span> </span><span>pod/&lt;pod-name&gt;</span><span> </span><span>18080:8080</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后其他机器访问：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>http://你的主机IP:18080</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>常用排查命令<a href="#常用排查命令"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>pods</span><span> </span><span>-n</span><span> </span><span>&lt;namespace&gt;</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>svc</span><span> </span><span>-n</span><span> </span><span>&lt;namespace&gt;</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>describe</span><span> </span><span>pod</span><span> </span><span>&lt;pod-name&gt;</span><span> </span><span>-n</span><span> </span><span>&lt;namespace&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>注意事项<a href="#注意事项"><span>#</span></a></h2><ul>
<li><code>kubectl port-forward</code> 是前台命令，终端关闭后转发会断开。</li>
<li>目标端口必须是 Pod 或 Service 实际监听的端口。</li>
<li>如果是长期暴露服务，不建议靠 port-forward，应该考虑 <code>NodePort</code>、<code>LoadBalancer</code>、Ingress 或反向代理。</li>
</ul><p>Kubernetes</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/kubectl-commands/</id>
      <title type="text">kubectl 常用命令速查</title>
      <published>2026-03-20T00:00:00.000Z</published>
      <updated>2026-03-20T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/kubectl-commands/"/>
      <summary type="text">kubectl 查看集群/资源/日志、进入容器、本地访问服务与部署删除的常用命令。</summary>
      <content type="html"><![CDATA[<section><h1>kubectl 常用命令<a href="#kubectl-常用命令"><span>#</span></a></h1><section><h2>查看当前集群<a href="#查看当前集群"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>config</span><span> </span><span>current-context</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>nodes</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>ns</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>查看资源<a href="#查看资源"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>all</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>pods</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>deploy</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>4</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>svc</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>5</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>configmap</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>6</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>secret</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>7</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>job</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>查看详情<a href="#查看详情"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>describe</span><span> </span><span>pod</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>&lt;pod-name&gt;</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>describe</span><span> </span><span>deploy</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>user-api-deployment</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>describe</span><span> </span><span>svc</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>user-api</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>看日志<a href="#看日志"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>logs</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>&lt;pod-name&gt;</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>logs</span><span> </span><span>-f</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>deploy/user-api-deployment</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>logs</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>job/ai-migrate-job</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>进入容器<a href="#进入容器"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>exec</span><span> </span><span>-it</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>&lt;pod-name&gt;</span><span> </span><span>--</span><span> </span><span>sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>本地访问服务<a href="#本地访问服务"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>port-forward</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>svc/user-api</span><span> </span><span>18000:80</span></div></div><div><div><div>2</div></div><div><span>curl</span><span> </span><span>http://localhost:18000/readyz</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>部署和删除<a href="#部署和删除"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>kustomize</span><span> </span><span>deploy/overlay/dev</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>apply</span><span> </span><span>-k</span><span> </span><span>deploy/overlay/dev</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>delete</span><span> </span><span>-k</span><span> </span><span>deploy/overlay/dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>重启和发布状态<a href="#重启和发布状态"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>restart</span><span> </span><span>deployment/user-api-deployment</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>status</span><span> </span><span>deployment/user-api-deployment</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>history</span><span> </span><span>deployment/user-api-deployment</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>看事件<a href="#看事件"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>events</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>--sort-by=.lastTimestamp</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>相关笔记：Kubernetes 排障手册、K8s 本地实战</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/k8s-service/</id>
      <title type="text">k8s Service：给一组 Pod 稳定入口</title>
      <published>2026-03-03T00:00:00.000Z</published>
      <updated>2026-03-03T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/k8s-service/"/>
      <summary type="text">Service 解决的问题、关键字段、四种类型与排障要点。</summary>
      <content type="html"><![CDATA[<section><h1>k8s Service<a href="#k8s-service"><span>#</span></a></h1><p>Service 给一组 Pod 提供稳定访问入口。Pod 会销毁、重建、换 IP，但 Service 的名字和虚拟 IP 相对稳定。</p><p></p><figure><img alt="Pasted image 20260616204746.png" loading="lazy" width="441" height="247" src="/_astro/pasted-image-20260616204746.3ppGTFOi_Z1cQe5d.webp" /><figcaption>Pasted image 20260616204746.png</figcaption></figure><p></p><section><h2>它解决什么问题<a href="#它解决什么问题"><span>#</span></a></h2><p>Deployment 后面可能有多个 Pod：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>user-api Pod A: 10.1.0.11</span></div></div><div><div><div>2</div></div><div><span>user-api Pod B: 10.1.0.12</span></div></div><div><div><div>3</div></div><div><span>user-api Pod C: 10.1.0.13</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>客户端不应该记住这些 Pod IP，因为 Pod 随时会变化。Service 通过 label selector 找到这些 Pod，对外提供一个稳定入口。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>client -&gt; Service -&gt; matching Pods</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>最小示例<a href="#最小示例"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apiVersion</span><span>: </span><span>v1</span></div></div><div><div><div>2</div></div><div><span>kind</span><span>: </span><span>Service</span></div></div><div><div><div>3</div></div><div><span>metadata</span><span>:</span></div></div><div><div><div>4</div></div><div><span>  </span><span>name</span><span>: </span><span>user-service</span></div></div><div><div><div>5</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>6</div></div><div><span>  </span><span>type</span><span>: </span><span>ClusterIP</span></div></div><div><div><div>7</div></div><div><span>  </span><span>selector</span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>app</span><span>: </span><span>user-service</span></div></div><div><div><div>9</div></div><div><span>  </span><span>ports</span><span>:</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>- </span><span>name</span><span>: </span><span>http</span></div></div><div><div><div>11</div></div><div><span>      </span><span>port</span><span>: </span><span>80</span></div></div><div><div><div>12</div></div><div><span>      </span><span>targetPort</span><span>: </span><span>8000</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>关键字段：</p>

<table><thead><tr><th>字段</th><th>作用</th></tr></thead><tbody><tr><td><code>selector</code></td><td>找到带有对应 label 的 Pod</td></tr><tr><td><code>port</code></td><td>Service 暴露的端口</td></tr><tr><td><code>targetPort</code></td><td>Pod 容器里的端口</td></tr><tr><td><code>type</code></td><td>Service 暴露方式</td></tr></tbody></table></section><section><h2>Service 类型<a href="#service-类型"><span>#</span></a></h2>

<table><thead><tr><th>类型</th><th>用途</th></tr></thead><tbody><tr><td><code>ClusterIP</code></td><td>只在集群内部访问，默认类型</td></tr><tr><td><code>NodePort</code></td><td>通过每台 Node 的端口访问</td></tr><tr><td><code>LoadBalancer</code></td><td>云厂商创建负载均衡器</td></tr><tr><td><code>ExternalName</code></td><td>映射到外部 DNS 名称</td></tr></tbody></table></section><section><h2>实际项目例子<a href="#实际项目例子"><span>#</span></a></h2><p>项目里有：</p><ul>
<li><code>user-api</code>：把流量转发到 <code>app: user-api</code> 的 Pod。</li>
<li><code>admin-api</code>：把流量转发到 <code>app: admin-api</code> 的 Pod。</li>
</ul><p>本地调试时常用 <code>port-forward</code>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>port-forward</span><span> </span><span>-n</span><span> </span><span>demo</span><span> </span><span>svc/user-api</span><span> </span><span>18000:80</span></div></div><div><div><div>2</div></div><div><span>curl</span><span> </span><span>http://localhost:18000/readyz</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>常用命令<a href="#常用命令"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>svc</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>describe</span><span> </span><span>svc</span><span> </span><span>user-api</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>endpoints</span><span> </span><span>-n</span><span> </span><span>demo</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果 Service 没有转发到 Pod，优先检查：</p><ul>
<li>Service 的 <code>selector</code> 是否匹配 Pod 的 <code>labels</code>。</li>
<li>Pod 是否处于 Ready 状态。</li>
<li><code>targetPort</code> 是否等于容器实际监听端口。</li>
</ul><p>相关笔记：pod、k8s deployment、Kubernetes 排障手册</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/k8s-architecture/</id>
      <title type="text">Kubernetes 集群架构总览</title>
      <published>2026-02-12T00:00:00.000Z</published>
      <updated>2026-02-12T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/k8s-architecture/"/>
      <summary type="text">K8s 集群的控制平面组件（apiserver/etcd/scheduler/controller-manager）与节点组件（kubelet/kube-proxy）。</summary>
      <content type="html"><![CDATA[<p>Kubernetes 背后的架构概念。</p>
<p>Kubernetes 集群由一个控制平面和一组用于运行容器化应用的工作机器组成， 这些工作机器称作节点（Node）。每个集群至少需要一个工作节点来运行 Pod。</p>
<p>工作节点托管着组成应用负载的 Pod。控制平面管理集群中的工作节点和 Pod。 在生产环境中，控制平面通常跨多台计算机运行，而一个集群通常运行多个节点，以提供容错和高可用。</p>
<p>本文概述了构建一个完整且可运行的 Kubernetes 集群所需的各种组件。</p>
<p></p><figure><img src="https://kubernetes.io/images/docs/kubernetes-cluster-architecture.svg" alt="控制平面（kube-apiserver、etcd、kube-controller-manager、kube-scheduler）和多个节点。每个节点运行 kubelet 和 kube-proxy。" /><figcaption>控制平面（kube-apiserver、etcd、kube-controller-manager、kube-scheduler）和多个节点。每个节点运行 kubelet 和 kube-proxy。</figcaption></figure><p></p>
<p>图 1. Kubernetes 集群组件。</p>
<p>关于此架构</p>
<p><a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#kube-proxy" target="_blank"></a><a href="https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/" target="_blank"></a></p>
<section><h2>控制平面组件 <a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#control-plane-components" target="_blank"></a><a href="#控制平面组件"><span>#</span></a></h2><p>控制平面组件会为集群做出全局决策，比如资源的调度。 以及检测和响应集群事件，例如当不满足 Deployment 的 <code>[replicas](https://kubernetes.io/zh-cn/docs/reference/glossary/?all=true#term-replica)</code> 字段时，要启动新的 <a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/" target="_blank">Pod</a>）。</p><p>控制平面组件可以在集群中的任何节点上运行。 然而，为了简单起见，安装脚本通常会在同一个计算机上启动所有控制平面组件， 并且不会在此计算机上运行用户容器。 请参阅<a href="https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/high-availability/" target="_blank">使用 kubeadm 构建高可用性集群</a>中关于跨多机器安装控制平面的示例。</p><section><h3>kube-apiserver<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#kube-apiserver" target="_blank"></a><a href="#kube-apiserver"><span>#</span></a></h3><p>API 服务器是 Kubernetes <a href="https://kubernetes.io/zh-cn/docs/reference/glossary/?all=true#term-control-plane" target="_blank">控制平面</a>的组件， 该组件负责公开了 Kubernetes API，负责处理接受请求的工作。 API 服务器是 Kubernetes 控制平面的前端。</p><p>Kubernetes API 服务器的主要实现是 <a href="https://kubernetes.io/zh-cn/docs/reference/command-line-tools-reference/kube-apiserver/" target="_blank">kube-apiserver</a>。 <code>kube-apiserver</code> 设计上考虑了水平扩缩，也就是说，它可通过部署多个实例来进行扩缩。 你可以运行 <code>kube-apiserver</code> 的多个实例，并在这些实例之间平衡流量。</p></section><section><h3>etcd<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#etcd" target="_blank"></a><a href="#etcd"><span>#</span></a></h3><p>一致且高可用的键值存储，用作 Kubernetes 所有集群数据的后台数据库。</p><p>如果你的 Kubernetes 集群使用 etcd 作为其后台数据库， 请确保你针对这些数据有一份 <a href="https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/configure-upgrade-etcd/#backing-up-an-etcd-cluster" target="_blank">备份</a>计划。</p><p>你可以在官方<a href="https://etcd.io/docs/" target="_blank">文档</a>中找到有关 etcd 的深入知识。</p></section><section><h3>kube-scheduler<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#kube-scheduler" target="_blank"></a><a href="#kube-scheduler"><span>#</span></a></h3><p><code>kube-scheduler</code> 是<a href="https://kubernetes.io/zh-cn/docs/reference/glossary/?all=true#term-control-plane" target="_blank">控制平面</a>的组件， 负责监视新创建的、未指定运行<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/nodes/" target="_blank">节点</a>的 <a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/" target="_blank">Pod</a>，并选择节点来让 Pod 在上面运行。</p><p>调度决策考虑的因素包括单个 Pod 及多个 Pod 集合的<a href="https://kubernetes.io/zh-cn/docs/reference/glossary/?all=true#term-infrastructure-resource" target="_blank">资源</a>需求、 软硬件及策略约束、亲和性及反亲和性规范、数据位置、工作负载间的干扰及最后时限。</p></section><section><h3>kube-controller-manager<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#kube-controller-manager" target="_blank"></a><a href="#kube-controller-manager"><span>#</span></a></h3><p>kube-controller-manager 是运行<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/controller/" target="_blank">控制器</a>进程的控制平面组件。</p><p>从逻辑上讲，每个<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/controller/" target="_blank">控制器</a>都是一个单独的进程， 但是为了降低复杂性，它们都被编译到同一个可执行文件，并在同一个进程中运行。</p><p>控制器有许多不同类型。以下是一些例子：</p><ul>
<li>Node 控制器：负责在节点出现故障时进行通知和响应</li>
<li>Job 控制器：监测代表一次性任务的 Job 对象，然后创建 Pod 来运行这些任务直至完成</li>
<li>EndpointSlice 控制器：填充 EndpointSlice 对象（以提供 Service 和 Pod 之间的链接）。</li>
<li>ServiceAccount 控制器：为新的命名空间创建默认的 ServiceAccount。</li>
</ul><p>以上并不是一个详尽的列表。</p></section><section><h3>cloud-controller-manager<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#cloud-controller-manager" target="_blank"></a><a href="#cloud-controller-manager"><span>#</span></a></h3><p>一个 Kubernetes <a href="https://kubernetes.io/zh-cn/docs/reference/glossary/?all=true#term-control-plane" target="_blank">控制平面</a>组件， 嵌入了特定于云平台的控制逻辑。 云控制器管理器（Cloud Controller Manager）允许将你的集群连接到云提供商的 API 之上， 并将与该云平台交互的组件同与你的集群交互的组件分离开来。</p><p><code>cloud-controller-manager</code> 仅运行特定于云平台的控制器。 因此如果你在自己的环境中运行 Kubernetes，或者在本地计算机中运行学习环境， 所部署的集群不包含云控制器管理器。</p><p>与 <code>kube-controller-manager</code> 类似，<code>cloud-controller-manager</code> 将若干逻辑上独立的控制回路组合到同一个可执行文件中，以同一进程的方式供你运行。 你可以对其执行水平扩容（运行不止一个副本）以提升性能或者增强容错能力。</p><p>下面的控制器都包含对云平台驱动的依赖：</p><ul>
<li>Node 控制器：用于在节点终止响应后检查云平台以确定节点是否已被删除</li>
<li>Route 控制器：用于在底层云基础架构中设置路由</li>
<li>Service 控制器：用于创建、更新和删除云平台上的负载均衡器</li>
</ul><hr /></section></section>
<section><h2>节点组件 <a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#node-components" target="_blank"></a><a href="#节点组件"><span>#</span></a></h2><p>节点组件会在每个节点上运行，负责维护运行的 Pod 并提供 Kubernetes 运行时环境。</p><section><h3>kubelet<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#kubelet" target="_blank"></a><a href="#kubelet"><span>#</span></a></h3><p><code>kubelet</code> 会在集群中每个<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/nodes/" target="_blank">节点（node）</a>上运行。 它保证<a href="https://kubernetes.io/zh-cn/docs/concepts/containers/" target="_blank">容器（containers）</a>都运行在 <a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/" target="_blank">Pod</a> 中。</p><p><a href="https://kubernetes.io/zh-cn/docs/reference/command-line-tools-reference/kubelet/" target="_blank">kubelet</a> 接收一组通过各类机制提供给它的 PodSpec，确保这些 PodSpec 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器。</p></section><section><h3>kube-proxy（可选） <a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#kube-proxy" target="_blank"></a><a href="#kube-proxy可选"><span>#</span></a></h3><p>根据 Service 和它对应的后端 Pod 列表，在每个节点上配置转发规则，让发往 Service 的流量能被送到某个后端
Pod。</p><p><a href="https://kubernetes.io/zh-cn/docs/reference/command-line-tools-reference/kube-proxy/" target="_blank">kube-proxy</a> 是集群中每个<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/nodes/" target="_blank">节点（node）</a>上所运行的网络代理， 实现 Kubernetes <a href="https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/" target="_blank">服务（Service）</a> 概念的一部分。</p><p>kube-proxy 维护节点上的一些网络规则， 这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。</p><p>如果操作系统提供了可用的数据包过滤层，则 kube-proxy 会通过它来实现网络规则。 否则，kube-proxy 仅做流量转发。</p><p>如果你使用<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#network-plugins" target="_blank">网络插件</a>为 Service 实现本身的数据包转发， 并提供与 kube-proxy 等效的行为，那么你不需要在集群中的节点上运行 kube-proxy。</p></section><section><h3>容器运行时 <a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#container-runtime" target="_blank"></a><a href="#容器运行时"><span>#</span></a></h3><p>这个基础组件使 Kubernetes 能够有效运行容器。 它负责管理 Kubernetes 环境中容器的执行和生命周期。</p><p>Kubernetes 支持许多容器运行环境，例如 <a href="https://containerd.io/docs/" target="_blank">containerd</a>、 <a href="https://cri-o.io/#what-is-cri-o" target="_blank">CRI-O</a> 以及 <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-node/container-runtime-interface.md" target="_blank">Kubernetes CRI (容器运行环境接口)</a> 的其他任何实现。</p></section></section>
<section><h2>插件（Addons） <a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#addons" target="_blank"></a><a href="#插件addons"><span>#</span></a></h2><p>插件使用 Kubernetes 资源（<a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/daemonset/" target="_blank">DaemonSet</a>、 <a href="https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/" target="_blank">Deployment</a> 等）实现集群功能。 因为这些插件提供集群级别的功能，插件中命名空间域的资源属于 <code>kube-system</code> 命名空间。</p><p>下面描述众多插件中的几种。有关可用插件的完整列表， 请参见<a href="https://kubernetes.io/zh-cn/docs/concepts/cluster-administration/addons/" target="_blank">插件（Addons）</a>。</p><section><h3>DNS<a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#dns" target="_blank"></a><a href="#dns"><span>#</span></a></h3><p>尽管其他插件都并非严格意义上的必需组件，但几乎所有 Kubernetes 集群都应该有<a href="https://kubernetes.io/zh-cn/docs/concepts/services-networking/dns-pod-service/" target="_blank">集群 DNS</a>， 因为很多示例都需要 DNS 服务。</p><p>集群 DNS 是一个 DNS 服务器，和环境中的其他 DNS 服务器一起工作，它为 Kubernetes 服务提供 DNS 记录。</p><p>Kubernetes 启动的容器自动将此 DNS 服务器包含在其 DNS 搜索列表中。</p></section><section><h3>Web 界面（仪表盘） <a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#web-ui-dashboard" target="_blank"></a><a href="#web-界面仪表盘"><span>#</span></a></h3><p><a href="https://kubernetes.io/zh-cn/docs/tasks/access-application-cluster/web-ui-dashboard/" target="_blank">Dashboard</a> 是 Kubernetes 集群的通用的、基于 Web 的用户界面。 它使用户可以管理集群中运行的应用程序以及集群本身，并进行故障排除。</p></section><section><h3>容器资源监控 <a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#container-resource-monitoring" target="_blank"></a><a href="#容器资源监控"><span>#</span></a></h3><p><a href="https://kubernetes.io/zh-cn/docs/tasks/debug/debug-cluster/resource-usage-monitoring/" target="_blank">容器资源监控</a> 将关于容器的一些常见的时序度量值保存到一个集中的数据库中，并提供浏览这些数据的界面。</p></section><section><h3>集群层面日志 <a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#cluster-level-logging" target="_blank"></a><a href="#集群层面日志"><span>#</span></a></h3><p><a href="https://kubernetes.io/zh-cn/docs/concepts/cluster-administration/logging/" target="_blank">集群层面日志</a>机制负责将容器的日志数据保存到一个集中的日志存储中， 这种集中日志存储提供搜索和浏览接口。</p></section><section><h3>网络插件 <a href="https://kubernetes.io/zh-cn/docs/concepts/architecture/#network-plugins" target="_blank"></a><a href="#网络插件"><span>#</span></a></h3><p><a href="https://kubernetes.io/zh-cn/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins" target="_blank">网络插件</a> 是实现容器网络接口（CNI）规范的软件组件。它们负责为 Pod 分配 IP 地址， 并使这些 Pod 能在集群内部相互通信</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/consistent-hashing/</id>
      <title type="text">一致性哈希：consistent 与 rendezvous 的对比</title>
      <published>2026-02-06T00:00:00.000Z</published>
      <updated>2026-02-06T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/consistent-hashing/"/>
      <summary type="text">两种分布式哈希的均匀度与耗时对比结论：节点少选 rendezvous，节点多且时间敏感选 consistent。</summary>
      <content type="html"><![CDATA[<p>一致性哈希要解决的问题：在节点集合会变化的分布式系统里，把 key 稳定地映射到节点上，使得增删节点时只影响尽量少的 key。常见实现有两类：</p>
<ul>
<li><strong>Consistent Hashing（环 + 虚拟节点）</strong>：把节点和 key 都哈希到一个环上，key 沿环顺时针找到第一个节点；用虚拟节点改善均匀度。</li>
<li><strong>Rendezvous Hashing（HRW，Highest Random Weight）</strong>：对每个 key，计算它与所有节点的哈希分数，取分数最高的节点。没有环、没有虚拟节点，纯计算。</li>
</ul>
<section><h2>对比结论<a href="#对比结论"><span>#</span></a></h2><p>参考资料：</p><ul>
<li><a href="https://developer.huawei.com/consumer/cn/forum/topic/0203810951415790238" target="_blank">16 张图解带你掌握一致性哈希算法 - 华为开发者话题</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/1924514679499237285" target="_blank">Consistent 和 Rendezvous 两种分布式 Hash 的比较 - 知乎</a></li>
<li><a href="https://pdai.tech/md/algorithm/alg-domain-distribute-x-consistency-hash.html" target="_blank">分布式算法 - 一致性 Hash 算法 - Java 全栈知识体系</a></li>
</ul><p>实测的两条核心结论：</p><ul>
<li><strong>rendezvous 的均匀度更好</strong>，所有场景的均值偏差都在 10% 以内。但当集群节点数较多时，rendezvous 更耗时：100 个节点时，rendezvous 的耗时是 consistent 的一倍（因为每个 key 都要和全部节点算一遍分数，O(n)）。</li>
<li><strong>consistent 随虚拟节点数量增加，均匀性越来越好</strong>，且虚拟节点数量基本不影响调度用时（哈希环 + 二分查找，O(log n)）。但均匀度和 rendezvous 比始终有差距。</li>
</ul></section>
<section><h2>怎么选<a href="#怎么选"><span>#</span></a></h2><ul>
<li>节点数较少：直接选 rendezvous hash，简单且均匀。</li>
<li>节点数较多且时间敏感：选 consistent hash。</li>
<li>节点数较多但对均匀性要求更高、能接受耗时：依然选 rendezvous hash 来确保均匀性。</li>
</ul><p>一句话：rendezvous 用计算时间换均匀度；consistent 用虚拟节点改善均匀度，查询则是哈希环上 O(log n) 的二分。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/gateway-auth/</id>
      <title type="text">微服务鉴权与网关职责</title>
      <published>2026-01-27T00:00:00.000Z</published>
      <updated>2026-01-27T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/gateway-auth/"/>
      <summary type="text">Gateway 负责认证、Service 负责授权：身份上下文透传、反例风险与文件服务落地场景。</summary>
      <content type="html"><![CDATA[<section><h1>微服务鉴权与网关职责<a href="#微服务鉴权与网关职责"><span>#</span></a></h1><section><h2>核心结论<a href="#核心结论"><span>#</span></a></h2><ul>
<li>Gateway 负责 Authentication（你是谁）</li>
<li>Microservice 负责 Authorization（你能干什么）</li>
</ul></section><section><h2>Gateway 应该做什么<a href="#gateway-应该做什么"><span>#</span></a></h2><ul>
<li>验证登录态：JWT / OAuth2 / Session / API Key</li>
<li>提取并传递身份上下文：<code>x-user-id</code>、<code>x-user-role</code>、<code>x-tenant-id</code></li>
<li>公共安全能力：限流、黑名单、IP 风控、CORS、TLS、WAF、审计日志、Tracing</li>
</ul></section><section><h2>Service 必须做什么<a href="#service-必须做什么"><span>#</span></a></h2><ul>
<li>校验资源归属（是否本人数据）</li>
<li>校验角色权限（RBAC）</li>
<li>校验属性条件（ABAC，可选）</li>
<li>校验租户边界（tenant）</li>
</ul></section><section><h2>反例<a href="#反例"><span>#</span></a></h2><p>只在 Gateway 校验 JWT，service 完全信任 header。</p><p>风险：</p><ul>
<li>内网横向攻击</li>
<li>内部请求伪造</li>
<li>SSRF 间接越权</li>
<li>资源 ID 枚举导致越权操作</li>
</ul></section><section><h2>gRPC 透传身份（Go）<a href="#grpc-透传身份go"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>md</span><span><span> </span><span>:=</span><span> </span></span><span>metadata</span><span>.</span><span>Pairs</span><span>(</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"x-user-id"</span><span>, </span><span>"123"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"x-user-role"</span><span>, </span><span>"user"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>)</span></div></div><div><div><div>5</div></div><div><span>ctx</span><span><span> </span><span>:=</span><span> </span></span><span>metadata</span><span>.</span><span>NewOutgoingContext</span><span>(</span><span>ctx</span><span>, </span><span>md</span><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>// server side</span></div></div><div><div><div>8</div></div><div><span>md</span><span>, </span><span>_</span><span><span> </span><span>=</span><span> </span></span><span>metadata</span><span>.</span><span>FromIncomingContext</span><span>(</span><span>ctx</span><span>)</span></div></div><div><div><div>9</div></div><div><span>uid</span><span><span> </span><span>:=</span><span> </span></span><span>md</span><span>.</span><span>Get</span><span>(</span><span>"x-user-id"</span><span>)</span></div></div><div><div><div>10</div></div><div><span>role</span><span><span> </span><span>:=</span><span> </span></span><span>md</span><span>.</span><span>Get</span><span>(</span><span>"x-user-role"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>说明：</p><ul>
<li>metadata 适合内部身份上下文透传</li>
<li>不能只“读到 uid 就执行业务”，必须继续做业务授权校验</li>
</ul></section><section><h2>更成熟的内部安全<a href="#更成熟的内部安全"><span>#</span></a></h2><ul>
<li>内部签名头（例如：<code>x-auth-signature</code> + <code>x-request-ts</code>）防伪造</li>
<li>Service Mesh（Istio/Linkerd）做服务间 mTLS 身份认证</li>
</ul></section><section><h2>文件服务场景落地<a href="#文件服务场景落地"><span>#</span></a></h2><p>外部：<code>HTTP/JSON -&gt; Gateway</code><br />
内部：<code>Gateway -&gt; gRPC filesystem-service</code></p><p>filesystem-service 关注：</p><ul>
<li>元数据、分片、hash、配额、权限</li>
<li>对象存储交互</li>
<li>presigned URL 签发</li>
</ul><p>大文件推荐：</p><ul>
<li>Gateway 只签发 URL</li>
<li>客户端直传对象存储（S3/OSS/MinIO）</li>
</ul></section><section><h2>快速检查清单<a href="#快速检查清单"><span>#</span></a></h2><ul>
<li>是否明确区分认证与授权</li>
<li>service 是否做了资源归属校验</li>
<li>是否有 role/tenant 维度校验</li>
<li>是否限制了内部伪造请求风险</li>
<li>上传链路是否避免 Gateway 传大文件</li>
</ul></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/minikube/</id>
      <title type="text">Minikube 本地集群</title>
      <published>2026-01-22T00:00:00.000Z</published>
      <updated>2026-01-22T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/minikube/"/>
      <summary type="text">Minikube 的启停、复用其内部 Docker 构建镜像以及常用调试命令。</summary>
      <content type="html"><![CDATA[<section><h1>Minikube<a href="#minikube"><span>#</span></a></h1><p>Minikube 是本地单节点 Kubernetes 集群，适合学习和本地验证部署 YAML。</p><section><h2>启动和停止<a href="#启动和停止"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>minikube</span><span> </span><span>start</span></div></div><div><div><div>2</div></div><div><span>minikube</span><span> </span><span>status</span></div></div><div><div><div>3</div></div><div><span>minikube</span><span> </span><span>stop</span></div></div><div><div><div>4</div></div><div><span>minikube</span><span> </span><span>delete</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>确认 kubectl 连到 minikube<a href="#确认-kubectl-连到-minikube"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>config</span><span> </span><span>current-context</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>nodes</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>正常情况下会看到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>minikube</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>使用 minikube 内部 Docker<a href="#使用-minikube-内部-docker"><span>#</span></a></h2><p>本地构建镜像后，如果希望 Kubernetes 直接使用这个镜像，需要让当前 shell 的 Docker 命令指向 minikube 内部 Docker：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>eval</span><span> $(</span><span>minikube</span><span> </span><span>docker-env</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>之后构建的镜像会进入 minikube，可以配合：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>imagePullPolicy</span><span>: </span><span>IfNotPresent</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>避免 Kubernetes 去远程镜像仓库拉取。</p></section><section><h2>退出 minikube Docker 环境<a href="#退出-minikube-docker-环境"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>eval</span><span> $(</span><span>minikube</span><span> </span><span>docker-env</span><span> </span><span>-u</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>常用调试命令<a href="#常用调试命令"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>minikube</span><span> </span><span>dashboard</span></div></div><div><div><div>2</div></div><div><span>minikube</span><span> </span><span>ssh</span></div></div><div><div><div>3</div></div><div><span>minikube</span><span> </span><span>image</span><span> </span><span>ls</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>本地练习<a href="#本地练习"><span>#</span></a></h2><p>推荐流程：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>minikube</span><span> </span><span>start</span></div></div><div><div><div>2</div></div><div><span>eval</span><span> $(</span><span>minikube</span><span> </span><span>docker-env</span><span>)</span></div></div><div><div><div>3</div></div><div><span>make</span><span> </span><span>build-base-image</span></div></div><div><div><div>4</div></div><div><span>APP_VERSION</span><span>=</span><span>dev</span><span> </span><span>docker</span><span> </span><span>compose</span><span> </span><span>-f</span><span> </span><span>docker-compose.yaml</span><span> </span><span>build</span><span> </span><span>user-api</span><span> </span><span>admin-api</span><span> </span><span>worker</span><span> </span><span>image-worker</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>tag</span><span> </span><span>user-api:dev</span><span> </span><span>localhost/user-api:dev</span></div></div><div><div><div>6</div></div><div><span>kubectl</span><span> </span><span>apply</span><span> </span><span>-k</span><span> </span><span>deploy/overlay/dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>相关笔记：kubectl 常用命令</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/docker-build-context/</id>
      <title type="text">Docker 镜像构建上下文</title>
      <published>2026-01-15T00:00:00.000Z</published>
      <updated>2026-01-15T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/docker-build-context/"/>
      <summary type="text">docker build 末尾那个点是什么：构建上下文的作用、传输机制与控制范围。</summary>
      <content type="html"><![CDATA[<p>如果注意,会看到 docker build命令最后有一个 <code>.</code></p>
<p>docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?</p>
<p>这就引入了上下文的概念</p>
<ul>
<li><strong>提供构建时所需文件</strong>
<ul>
<li><code>COPY</code>、<code>ADD</code> 指令引用的文件路径都必须在上下文内。</li>
</ul>
</li>
<li><strong>传输给 Docker daemon</strong>
<ul>
<li>在 <code>docker build</code> 时，CLI 会把上下文打包成一个 tar 包，发送给后端守护进程。</li>
</ul>
</li>
<li><strong>控制构建范围</strong>
<ul>
<li>若不小心把整个大目录（如 <code>/</code> 或 <code>~</code>）作为上下文，会严重拖慢构建。</li>
</ul>
</li>
</ul>
<section><h2>一个具体例子<a href="#一个具体例子"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 上下文 = 当前目录（末尾的 .），Dockerfile 默认为 ./Dockerfile</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>-t</span><span> </span><span>my-app</span><span> </span><span>.</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 上下文 = 当前目录，但指定别的 Dockerfile</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>-f</span><span> </span><span>microservice.Dockerfile</span><span> </span><span>-t</span><span> </span><span>my-app</span><span> </span><span>.</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 上下文 = 指定目录（构建时路径都相对它解析）</span></div></div><div><div><div>8</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>-t</span><span> </span><span>my-app</span><span> </span><span>./backend</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行 <code>docker build .</code> 时终端里那一行 <code>Sending build context to Docker daemon  xx MB</code>，就是把整个上下文目录打包成 tar 传给 daemon 的过程——这也解释了为什么 <code>COPY</code> 不能引用上下文之外的路径（如 <code>COPY ../foo</code> 是非法的）。</p></section>
<section><h2>用 .dockerignore 收窄上下文<a href="#用-dockerignore-收窄上下文"><span>#</span></a></h2><p>上下文太大不仅拖慢构建，还会把不该进镜像的文件传进 daemon。在上下文根目录放 <code>.dockerignore</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>.git</span></div></div><div><div><div>2</div></div><div><span>node_modules</span></div></div><div><div><div>3</div></div><div><span>dist</span></div></div><div><div><div>4</div></div><div><span>*.log</span></div></div><div><div><div>5</div></div><div><span>.env</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>语法类似 <code>.gitignore</code>。被排除的文件不会进入 tar 包，<code>COPY .</code> 也就拿不到它们，同时避免密钥、构建产物意外进镜像。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/kafka/</id>
      <title type="text">Kafka 的分区与副本机制</title>
      <published>2026-01-09T00:00:00.000Z</published>
      <updated>2026-01-09T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/kafka/"/>
      <summary type="text">Kafka Broker/Topic/分区的关系，以及 leader-follower 副本的读写与冗余备份机制。</summary>
      <content type="html"><![CDATA[<p>在 Kafka 中，每个 Kafka 实例称为 Broker，每个 Broker 中可以保存多个 Topic。每个 Topic 可以划分为多个分区，**每个分区保存的数据是不一样的，**这些分区可以在同一个 Broker 中，也可以在散布在不同的 Broker 中。</p>
<p>一个 Broker 可以存储不同 Topic 的不同分区，也可以存储同一个 Topic 的不同分区。</p>
<p></p><figure><img alt="Pasted image 20260701172445.png" loading="lazy" width="725" height="359" src="/_astro/pasted-image-20260701172445.B6CfFkqE_Z2qfDRX.webp" /><figcaption>Pasted image 20260701172445.png</figcaption></figure><p></p>
<section><h2>分区与复制<a href="#分区与复制"><span>#</span></a></h2><p>主题的每个分区都有至少一个副本，也就是 <code>--replication-factor</code> 参数必须大于 1。副本分为 leader 和 follower 两种，每个副本都需要消耗一个存储空间，leader 对外提供读写消息，而 follower 提供冗余备份，leader 会及时将消息增量同步到所有 follower 中。</p><p>在 <code>hello-topic</code> 主题中，当分区只有一个副本时，或只关注 leader 副本时，leader 副本对应的 Broker 节点位置如下：</p><p></p><figure><img alt="Pasted image 20260701173450.png" loading="lazy" width="827" height="250" src="/_astro/pasted-image-20260701173450.4bYNsgkA_Mv0H6.webp" /><figcaption>Pasted image 20260701173450.png</figcaption></figure><p></p><p>如果设置了多个副本( <code>--replication-factor=3</code> ) 时，leader 副本和 follower 副本的位置如下所示：</p><p></p><figure><img alt="Pasted image 20260701173500.png" loading="lazy" width="822" height="256" src="/_astro/pasted-image-20260701173500.C4N6LXIe_73geE.webp" /><figcaption>Pasted image 20260701173500.png</figcaption></figure><p></p><p>在不同的副本中，只有 leader 副本能够进行读写，follower 接收从 leader 推送过来的数据，做好冗余备份。</p><p></p><figure><img alt="Pasted image 20260701173509.png" loading="lazy" width="766" height="548" src="/_astro/pasted-image-20260701173509.CFCDmKv3_Z29bIFu.webp" /><figcaption>Pasted image 20260701173509.png</figcaption></figure>
<figure><img alt="Pasted image 20260701173520.png" loading="lazy" width="939" height="493" src="/_astro/pasted-image-20260701173520.C5DaTBdd_Z64ej1.webp" /><figcaption>Pasted image 20260701173520.png</figcaption></figure><p></p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/docker-image/</id>
      <title type="text">Docker 镜像的分层与构建</title>
      <published>2026-01-05T00:00:00.000Z</published>
      <updated>2026-01-05T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/docker-image/"/>
      <summary type="text">Docker 镜像的分层结构、UnionFS 与构建缓存机制，以及 docker build 的多种用法。</summary>
      <content type="html"><![CDATA[<section><h3>🧱 镜像（Image）<a href="#-镜像image"><span>#</span></a></h3><ul>
<li>是一个 <strong>只读模板</strong>，包含了运行应用所需的一切：操作系统、程序代码、依赖、配置等。</li>
<li>它就像一份快照，可以重复使用。</li>
<li>构建一次，可以启动很多个容器。</li>
</ul><p>Docker 镜像是由多层构成的，每一层通常是只读的。这些层通过 UnionFS (联合文件系统) 组合在一起形成一个完整的文件系统，最终在容器运行时添加一个最上面的 <strong>可写层（writeable layer）</strong>。</p><p><strong>如果你改变了 Dockerfile 中的一层指令（如 <code>RUN</code>、<code>COPY</code>、<code>ADD</code>），那么它之后的所有层都会重新构建</strong>。这是 Docker 的构建缓存（build cache）机制所决定的。</p><p>如果多个命令都指向同一个目的，那么应该用一个run来进行，而不是叠加多层</p><p>用一个RUN：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>FROM</span><span> debian:jessie</span></div></div><div><div><div>2</div></div><div><span>RUN</span><span> buildDeps=</span><span>'gcc libc6-dev make'</span><span> \</span></div></div><div><div><div>3</div></div><div><span>&amp;&amp; apt-get update \</span></div></div><div><div><div>4</div></div><div><span>&amp;&amp; apt-get install -y $buildDeps \</span></div></div><div><div><div>5</div></div><div><span>&amp;&amp; wget -O redis.tar.gz </span><span>"http://download.redis.io/releases/redis-3.2.5.tar.gz"</span><span> \</span></div></div><div><div><div>6</div></div><div><span>&amp;&amp; mkdir -p /usr/src/redis \</span></div></div><div><div><div>7</div></div><div><span>&amp;&amp; tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \</span></div></div><div><div><div>8</div></div><div><span>&amp;&amp; make -C /usr/src/redis \</span></div></div><div><div><div>9</div></div><div><span>&amp;&amp; make -C /usr/src/redis install \</span></div></div><div><div><div>10</div></div><div><span>&amp;&amp; rm -rf /var/lib/apt/lists/* \</span></div></div><div><div><div>11</div></div><div><span>&amp;&amp; rm redis.tar.gz \</span></div></div><div><div><div>12</div></div><div><span>&amp;&amp; rm -r /usr/src/redis \</span></div></div><div><div><div>13</div></div><div><span>&amp;&amp; apt-get purge -y --auto-remove $buildDeps</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>docker build 各种用法<a href="#docker-build-各种用法"><span>#</span></a></h2><section><h3>从dockerfile<a href="#从dockerfile"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>docker</span><span> </span><span>build</span><span> </span><span>-f</span><span> </span><span>microservice.Dockerfile</span><span> </span><span>-t</span><span> </span><span>docker-jar</span><span> </span><span>.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>docker</span><span> </span><span>build</span><span> </span><span>-t</span><span> </span><span>minecraft-modpack-server</span><span> </span><span>.</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>从标准输入<a href="#从标准输入"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>Dockerfile</span><span> | </span><span>docker</span><span> </span><span>build</span><span> </span><span>-</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>从Git repo<a href="#从git-repo"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>https://github.com/twang2218/gitlab-ce-zh.git#:8.14</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Docker 指令</p></section></section>
<section><h2>删除镜像<a href="#删除镜像"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 删除单个镜像（可以用镜像名:tag 或镜像 ID）</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>rmi</span><span> </span><span>nginx:latest</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 强制删除（即使有基于它的容器存在）</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>rmi</span><span> </span><span>-f</span><span> </span><span>&lt;image-id&gt;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 清理所有悬空镜像（dangling，即没有 tag 引用的中间层）</span></div></div><div><div><div>8</div></div><div><span>docker</span><span> </span><span>image</span><span> </span><span>prune</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 清理所有未被任何容器使用的镜像</span></div></div><div><div><div>11</div></div><div><span>docker</span><span> </span><span>image</span><span> </span><span>prune</span><span> </span><span>-a</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/local-negative-cache/</id>
      <title type="text">本地负缓存</title>
      <published>2025-12-03T00:00:00.000Z</published>
      <updated>2025-12-03T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/local-negative-cache/"/>
      <summary type="text">把不存在/失败的查询结果缓存在本地内存中，短时间内直接返回，避免重复打到下游系统。</summary>
      <content type="html"><![CDATA[<p>把不存在/失败结果缓存在本地内存里，短时间内直接返回，避免重复访问下游系统。</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>id=999999 不存在 → 缓存 60 秒</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>后面 60 秒：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>直接返回不存在，不用查 DB</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<section><h2>为什么需要<a href="#为什么需要"><span>#</span></a></h2><p>典型的缓存只缓存「查到了」的结果，但线上更耗资源的往往是<strong>查不到</strong>的请求：</p><ul>
<li>缓存穿透：不存在的 key 每次都要打到数据库，负面结果不缓存，等于每次都白查一遍。</li>
<li>下游故障放大：某个依赖超时/报错后，上层不断重试同样的失败请求，把故障放大。</li>
<li>恶意或爬虫流量：大量轮询不存在的资源 ID。</li>
</ul><p>负缓存（negative caching）就是把「不存在 / 失败」也当成一种结果缓存起来，短时间内对同一 key 直接复用这个结论。</p></section>
<section><h2>实现要点<a href="#实现要点"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 极简示意：本地 map + 过期时间</span></div></div><div><div><div>2</div></div><div><span>var</span><span> </span><span>negativeCache</span><span> </span><span>sync</span><span>.</span><span>Map</span><span> </span><span>// key -&gt; expireAt</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>func</span><span> </span><span>GetProfile</span><span><span>(</span><span>ctx</span><span> </span></span><span>context</span><span>.</span><span>Context</span><span><span>, </span><span>id</span><span> </span></span><span>string</span><span><span>) (</span><span>*</span></span><span>Profile</span><span>, </span><span>error</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>if</span><span> </span><span>exp</span><span>, </span><span>ok</span><span><span> </span><span>:=</span><span> </span></span><span>negativeCache</span><span>.</span><span>Load</span><span>(</span><span>id</span><span>); </span><span>ok</span><span><span> </span><span>&amp;&amp;</span><span> </span></span><span>time</span><span>.</span><span>Now</span><span>().</span><span>Before</span><span>(</span><span>exp</span><span>.(</span><span>time</span><span>.</span><span>Time</span><span>)) {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>return</span><span> </span><span>nil</span><span>, </span><span>ErrNotFound</span><span> </span><span>// 命中负缓存，直接返回</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>  </span><span>p</span><span>, </span><span>err</span><span><span> </span><span>:=</span><span> </span></span><span>repo</span><span>.</span><span>Find</span><span>(</span><span>ctx</span><span>, </span><span>id</span><span>)</span></div></div><div><div><div>9</div></div><div><span>  </span><span>if</span><span> </span><span>errors</span><span>.</span><span>Is</span><span>(</span><span>err</span><span>, </span><span>ErrNotFound</span><span>) {</span></div></div><div><div><div>10</div></div><div><span>    </span><span>negativeCache</span><span>.</span><span>Store</span><span>(</span><span>id</span><span>, </span><span>time</span><span>.</span><span>Now</span><span>().</span><span>Add</span><span>(</span><span>60</span><span>*</span><span>time</span><span>.</span><span>Second</span><span>))</span></div></div><div><div><div>11</div></div><div><span>    </span><span>return</span><span> </span><span>nil</span><span>, </span><span>err</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>  </span><span>return</span><span> </span><span>p</span><span>, </span><span>err</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>TTL 要短</strong>：负缓存是「暂时的结论」，实体可能随后就被创建。一般秒级到一分钟，宁短勿长。</li>
<li><strong>只缓存确定性失败</strong>：<code>不存在</code> 可以缓存；<code>超时/网络错误</code> 不应该固化为负缓存，否则下游恢复后仍被挡住。</li>
<li><strong>要有容量上限与淘汰</strong>：本地内存是无底洞风险，配合 LRU 或分片 + 定期清理，防止被不存在的 key 撑爆。</li>
<li><strong>命中打点</strong>：监控负缓存命中率，异常升高往往意味着上游调用方在犯错或有攻击流量。</li>
</ul><p>与「缓存空对象」是同一个思路，只是把空值判断从 Redis 前移到进程内，省一次网络往返。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/mysql-index-optimization/</id>
      <title type="text">MySQL 索引与慢查询优化</title>
      <published>2025-11-14T00:00:00.000Z</published>
      <updated>2025-11-14T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/mysql-index-optimization/"/>
      <summary type="text">慢查询 200ms 阈值、JOIN 对数据量的影响、IN 列表过大丢索引与分批查询。</summary>
      <content type="html"><![CDATA[<p>每条查询的时间应该控制在200ms内
否则认为是慢查询</p>
<p>慢查询带来的后果是：IO开销过大，锁持有时间增加，导致数据库吞吐量降低</p>
<section><h2>排查与定位<a href="#排查与定位"><span>#</span></a></h2><ul>
<li>开启慢查询日志：<code>slow_query_log=ON</code>，<code>long_query_time=0.2</code>，配合 <code>pt-query-digest</code> 或云监控聚合分析。</li>
<li>用 <code>EXPLAIN</code> 看执行计划，重点看 <code>type</code>（至少到 <code>range</code>，避免 <code>ALL</code> 全表扫描）、<code>key</code>（实际用到的索引）、<code>rows</code>（预估扫描行数）、<code>Extra</code>（<code>Using filesort</code> / <code>Using temporary</code> 都是危险信号）。</li>
</ul></section>
<section><h2>JOIN 与数据量<a href="#join-与数据量"><span>#</span></a></h2><p>内联JOIN是求交集，数据变小</p><p>外JOIN，会导致数据量过大</p><ul>
<li>JOIN 的驱动表尽量选过滤后结果集小的那张，被驱动表的关联列必须有索引。</li>
<li>避免在 JOIN 条件列上做函数运算或类型转换（如字符串列和数字比较），否则索引失效。</li>
</ul></section>
<section><h2>索引失效的常见场景<a href="#索引失效的常见场景"><span>#</span></a></h2><ul>
<li>对索引列使用函数或运算：<code>WHERE DATE(created_at) = ...</code>、<code>WHERE id + 1 = ...</code>。</li>
<li>隐式类型转换：varchar 列用数字比较。</li>
<li>前导模糊匹配：<code>LIKE '%keyword'</code>。</li>
<li>联合索引不满足最左前缀。</li>
<li><strong>IN 列表过大</strong>：如果 IN 关键字包含的id过多，数据库有可能抛弃索引直接进行全表扫描（优化器认为回表成本高于全表扫描）。</li>
</ul></section>
<section><h2>分批查询<a href="#分批查询"><span>#</span></a></h2><p>如果单次查询的数据量大于1000，则最好需要分批次进行查询
每批次500条</p><p>分批不只为了保护索引，还能控制单次网络包大小、减少锁持有时间与内存占用。配合覆盖索引（只 SELECT 需要的列）效果更好。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/redis-persistence/</id>
      <title type="text">Redis 数据持久化（RDB）</title>
      <published>2025-10-28T00:00:00.000Z</published>
      <updated>2025-10-28T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/redis-persistence/"/>
      <summary type="text">Redis RDB 快照机制、save/bgsave 的阻塞差异、自动触发时机与 flushall 的覆盖语义。</summary>
      <content type="html"><![CDATA[<p>Redis 提供 2 种持久化方案：</p>
<ul>
<li>RDB   将在内存中的数据库记录定期生成快照存入磁盘中</li>
<li>AOF   将 Redis 的操作日志以追加的方式写入文件，</li>
</ul>
<section><h2>RDB<a href="#rdb"><span>#</span></a></h2><p>RedisDatabase</p><p>将Redis某一时刻的内存数据写入到RDB文件中，并保存到磁盘上。
该文件是一个<strong>压缩</strong>过的<strong>二进制文件</strong>，默认文件名为<code>dump.rdb</code>，通过该文件可以还原redis数据库的数据。在Redis服务器启动时，会重新加载dump.rdb文件的数据到内存当中，达到恢复数据目的。</p><p><strong>可根据</strong> redis.conf <strong>配置文件中设置rdb文件名 和 保存目录：</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 指定本地数据库文件名（rdb文件的名称），默认值为dump.rdb</span></div></div><div><div><div>2</div></div><div><span>dbfilename dump.rdb</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 指定本地数据库存放目录（dump.rdb文件存放目录），rdb、aof文件也会写在这个目录dir /usr/local/var/db/redis/</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>save<a href="#save"><span>#</span></a></h2><p>当某个客户端发送 save 命令后，此时会阻塞当前Redis服务器，在RDB文件创建完成之前是不能处理其他客户端发送的任何命令请求，如果数据量太大会造成长时间阻塞，期间redis无法处理其他请求，<strong>线上环境不建议使用</strong>。</p></section>
<section><h2>bgsave<a href="#bgsave"><span>#</span></a></h2><p>当某个客户端发送 bgsave 命令后，Redis服务器会执行fork() 函数创建一个子进程，由子进程负责rdb文件创建和写入，期间服务器不会阻塞，可以接受其他客户端的命令请求，**但在fork执行阶段，服务器还会阻塞，无法接受其他客户端命令（一般时间很短）</p></section>
<section><h2>自动触发<a href="#自动触发"><span>#</span></a></h2><p>在以下几种情况时会自动触发生成 RDB 文件：</p><ul>
<li><code>redis.conf</code> 配置文件中达到 <code>save</code> 参数条件</li>
<li>主从复制时，从节点要从主节点进行全量复制，此时会触发 bgsave，生成快照发送到从节点</li>
<li>执行 shutdown，如果没有开启 AOF 持久化，会触发 bgsave</li>
<li>执行 flushall（生成空的 dump.rdb！）</li>
</ul></section>
<section><h2>flushall<a href="#flushall"><span>#</span></a></h2><blockquote><p>flushall 生成的是空dump.rdb文件，目的是覆盖并删除上次备份的快照，防止redis重启时，自动载入并恢复到上一份备份数据！</p></blockquote></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/ddd/</id>
      <title type="text">DDD 领域驱动设计入门</title>
      <published>2025-10-11T00:00:00.000Z</published>
      <updated>2025-10-11T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/ddd/"/>
      <summary type="text">DDD 的由来与核心概念：实体、值对象、聚合、仓储、分层架构与 Go 项目目录落地。</summary>
      <content type="html"><![CDATA[<p><strong>DDD（Domain-Driven Design，领域驱动设计）</strong> 不是一种框架，而是一种<strong>面向复杂业务系统的软件设计方法论</strong>。</p>
<p>它的核心思想是：</p>
<blockquote><p>软件结构应该围绕业务领域（Domain）组织，而不是围绕数据库、接口、框架组织。</p></blockquote>
<hr />
<section><h1>为什么会有 DDD<a href="#为什么会有-ddd"><span>#</span></a></h1><p>很多项目一开始都是这样：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>controller</span></div></div><div><div><div>2</div></div><div><span>service</span></div></div><div><div><div>3</div></div><div><span>repository</span></div></div><div><div><div>4</div></div><div><span>model</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>user_controller.go</span></div></div><div><div><div>2</div></div><div><span>user_service.go</span></div></div><div><div><div>3</div></div><div><span>user_repo.go</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当业务简单时没问题。</p><p>但随着需求增加：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用户</span></div></div><div><div><div>2</div></div><div><span>订单</span></div></div><div><div><div>3</div></div><div><span>支付</span></div></div><div><div><div>4</div></div><div><span>优惠券</span></div></div><div><div><div>5</div></div><div><span>库存</span></div></div><div><div><div>6</div></div><div><span>物流</span></div></div><div><div><div>7</div></div><div><span>积分</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Service 会越来越大：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>CreateOrder</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>检查库存</span></div></div><div><div><div>3</div></div><div><span>    </span><span>检查优惠券</span></div></div><div><div><div>4</div></div><div><span>    </span><span>扣减库存</span></div></div><div><div><div>5</div></div><div><span>    </span><span>创建订单</span></div></div><div><div><div>6</div></div><div><span>    </span><span>创建支付单</span></div></div><div><div><div>7</div></div><div><span>    </span><span>发MQ</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>最后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>OrderService</span></div></div><div><div><div>2</div></div><div><span>3000行</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>业务规则散落在：</p><ul>
<li>Controller</li>
<li>Service</li>
<li>SQL</li>
<li>MQ Consumer</li>
</ul><p>维护起来很痛苦。</p><p>DDD 就是为了解决：</p><blockquote><p>复杂业务逻辑如何长期演进。</p></blockquote><hr /></section>
<section><h1>DDD 的核心概念<a href="#ddd-的核心概念"><span>#</span></a></h1><section><h2>1. Domain（领域）<a href="#1-domain领域"><span>#</span></a></h2><p>领域就是业务范围。</p><p>例如电商：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>电商系统</span></div></div><div><div><div>2</div></div><div><span>├── 用户领域</span></div></div><div><div><div>3</div></div><div><span>├── 订单领域</span></div></div><div><div><div>4</div></div><div><span>├── 支付领域</span></div></div><div><div><div>5</div></div><div><span>├── 库存领域</span></div></div><div><div><div>6</div></div><div><span>└── 物流领域</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>每个领域独立思考。</p><p>“这个东西如果脱离 HTTP、数据库、缓存、消息队列，仍然有业务意义吗？”</p><ul>
<li>如果有，可能属于 domain</li>
<li>如果没有，多半不属于 domain</li>
</ul><hr /></section><section><h2>2. Entity（实体）<a href="#2-entity实体"><span>#</span></a></h2><p>有唯一身份的对象。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>User</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>ID</span><span> </span><span>int64</span></div></div><div><div><div>3</div></div><div><span>    </span><span>Name</span><span> </span><span>string</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>即使名字变了：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>张三 → 李四</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>还是同一个用户。</p><p>因为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ID没变</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h2>3. Value Object（值对象）<a href="#3-value-object值对象"><span>#</span></a></h2><p>没有身份。</p><p>只关心值。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>Money</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>Amount</span><span> </span><span>int64</span></div></div><div><div><div>3</div></div><div><span>    </span><span>Currency</span><span> </span><span>string</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>两个：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>100 CNY</span></div></div><div><div><div>2</div></div><div><span>100 CNY</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>就是相等的。</p><hr /></section><section><h2>4. Aggregate（聚合）<a href="#4-aggregate聚合"><span>#</span></a></h2><p>DDD最重要概念之一。</p><p>例如订单：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Order</span></div></div><div><div><div>2</div></div><div><span>├── OrderItem</span></div></div><div><div><div>3</div></div><div><span>├── Address</span></div></div><div><div><div>4</div></div><div><span>└── PaymentInfo</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这些对象一起维护一致性。</p><p>形成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Order Aggregate</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><section><h3>聚合根（Aggregate Root）<a href="#聚合根aggregate-root"><span>#</span></a></h3><p>外部只能访问：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Order</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>不能直接改：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>OrderItem</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>order</span><span>.</span><span>AddItem</span><span>(...)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而不是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>order</span><span>.</span><span>Items</span><span>[</span><span>0</span><span>].</span><span>Count</span><span>++</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样业务规则集中。</p><hr /></section></section></section>
<section><h1>Repository<a href="#repository"><span>#</span></a></h1><p>Repository 不是 DAO。</p><p>DAO：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>db</span><span>.</span><span>Create</span><span>(...)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Repository：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>OrderRepository</span><span> </span><span>interface</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>Save</span><span><span>(</span><span>order</span><span> </span><span>*</span></span><span>Order</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>FindByID</span><span><span>(</span><span>id</span><span> </span></span><span>string</span><span>)</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>领域层不知道：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>MySQL</span></div></div><div><div><div>2</div></div><div><span>PostgreSQL</span></div></div><div><div><div>3</div></div><div><span>Redis</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h1>Domain Service<a href="#domain-service"><span>#</span></a></h1><p>有些业务不属于某个实体。</p><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>订单创建</span></div></div><div><div><div>2</div></div><div><span>需要：</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>库存</span></div></div><div><div><div>5</div></div><div><span>优惠券</span></div></div><div><div><div>6</div></div><div><span>支付</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>放进 Order 也不合适。</p><p>这时：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>type</span><span> </span><span>OrderDomainService</span><span> </span><span>struct</span><span> {}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>负责跨聚合逻辑。</p><hr /></section>
<section><h1>Application Service<a href="#application-service"><span>#</span></a></h1><p>很多人容易混淆。</p><p>DDD里通常：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Controller</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>Application Service</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>Domain</span></div></div><div><div><div>6</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>Repository</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Application Service：</p><p>负责：</p><ul>
<li>编排流程</li>
<li>事务</li>
<li>调用领域对象</li>
</ul><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>func</span><span> </span><span>CreateOrder</span><span><span>(</span><span>cmd</span><span> </span></span><span>CreateOrderCommand</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>order</span><span><span> </span><span>:=</span><span> </span></span><span>domain</span><span>.</span><span>NewOrder</span><span>()</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>order</span><span>.</span><span>AddItem</span><span>(...)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>repo</span><span>.</span><span>Save</span><span>(</span><span>order</span><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>mq</span><span>.</span><span>Publish</span><span>(...)</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h1>分层架构<a href="#分层架构"><span>#</span></a></h1><p>经典 DDD：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>┌──────────────────┐</span></div></div><div><div><div>2</div></div><div><span>│ Presentation     │</span></div></div><div><div><div>3</div></div><div><span>│ Controller/API   │</span></div></div><div><div><div>4</div></div><div><span>└────────┬─────────┘</span></div></div><div><div><div>5</div></div><div><span><span>         </span></span><span>▼</span></div></div><div><div><div>6</div></div><div><span>┌──────────────────┐</span></div></div><div><div><div>7</div></div><div><span>│ Application      │</span></div></div><div><div><div>8</div></div><div><span>│ Use Case         │</span></div></div><div><div><div>9</div></div><div><span>└────────┬─────────┘</span></div></div><div><div><div>10</div></div><div><span><span>         </span></span><span>▼</span></div></div><div><div><div>11</div></div><div><span>┌──────────────────┐</span></div></div><div><div><div>12</div></div><div><span>│ Domain           │</span></div></div><div><div><div>13</div></div><div><span>│ Entity           │</span></div></div><div><div><div>14</div></div><div><span>│ Aggregate        │</span></div></div><div><div><div>15</div></div><div><span>│ Domain Service   │</span></div></div><div><div><div>16</div></div><div><span>└────────┬─────────┘</span></div></div><div><div><div>17</div></div><div><span><span>         </span></span><span>▼</span></div></div><div><div><div>18</div></div><div><span>┌──────────────────┐</span></div></div><div><div><div>19</div></div><div><span>│ Infrastructure   │</span></div></div><div><div><div>20</div></div><div><span>│ DB MQ Redis      │</span></div></div><div><div><div>21</div></div><div><span>└──────────────────┘</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section>
<section><h1>Go 项目常见目录<a href="#go-项目常见目录"><span>#</span></a></h1><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>internal/</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>├── application</span></div></div><div><div><div>4</div></div><div><span>│   └── order</span></div></div><div><div><div>5</div></div><div><span>│       └── service.go</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>├── domain</span></div></div><div><div><div>8</div></div><div><span>│   └── order</span></div></div><div><div><div>9</div></div><div><span>│       ├── entity.go</span></div></div><div><div><div>10</div></div><div><span>│       ├── repository.go</span></div></div><div><div><div>11</div></div><div><span>│       └── service.go</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>├── infrastructure</span></div></div><div><div><div>14</div></div><div><span>│   └── repository</span></div></div><div><div><div>15</div></div><div><span>│       └── order_repo.go</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>└── interfaces</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>└── http</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>└── order_handler.go</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><hr /></section>
<section><h1>在微服务里怎么用<a href="#在微服务里怎么用"><span>#</span></a></h1><p>假设你之前说的在线判题系统：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>gateway</span></div></div><div><div><div>2</div></div><div><span>user</span></div></div><div><div><div>3</div></div><div><span>problem</span></div></div><div><div><div>4</div></div><div><span>judge</span></div></div><div><div><div>5</div></div><div><span>sandbox</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>每个微服务都可以有自己的领域。</p><p>例如：</p><section><h3>problem-service<a href="#problem-service"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Problem</span></div></div><div><div><div>2</div></div><div><span>Tag</span></div></div><div><div><div>3</div></div><div><span>SubmissionRule</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3>judge-service<a href="#judge-service"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Submission</span></div></div><div><div><div>2</div></div><div><span>JudgeTask</span></div></div><div><div><div>3</div></div><div><span>JudgeResult</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section><section><h3>user-service<a href="#user-service"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>User</span></div></div><div><div><div>2</div></div><div><span>Role</span></div></div><div><div><div>3</div></div><div><span>Permission</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h1>DDD 和 MVC 的区别<a href="#ddd-和-mvc-的区别"><span>#</span></a></h1><p>MVC：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Controller</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>Service</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>DAO</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>关注：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>代码分层</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /><p>DDD：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Domain</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>Aggregate</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>Business Rules</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>关注：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>业务建模</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以：</p><blockquote><p>MVC 是技术视角，DDD 是业务视角。</p></blockquote><p>两者并不冲突。</p><hr /></section>
<section><h1>什么时候值得引入 DDD<a href="#什么时候值得引入-ddd"><span>#</span></a></h1><p>如果是学习性质的：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Gateway</span></div></div><div><div><div>2</div></div><div><span>User</span></div></div><div><div><div>3</div></div><div><span>Problem</span></div></div><div><div><div>4</div></div><div><span>Judge</span></div></div><div><div><div>5</div></div><div><span>Sandbox</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我的建议是：</p><section><h3>第一阶段<a href="#第一阶段"><span>#</span></a></h3><p>普通三层：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>handler</span></div></div><div><div><div>2</div></div><div><span>service</span></div></div><div><div><div>3</div></div><div><span>repo</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>先把功能跑通。</p><hr /></section><section><h3>第二阶段<a href="#第二阶段"><span>#</span></a></h3><p>当出现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>提交代码</span></div></div><div><div><div>2</div></div><div><span>↓</span></div></div><div><div><div>3</div></div><div><span>创建Submission</span></div></div><div><div><div>4</div></div><div><span>↓</span></div></div><div><div><div>5</div></div><div><span>发MQ</span></div></div><div><div><div>6</div></div><div><span>↓</span></div></div><div><div><div>7</div></div><div><span>Judge消费</span></div></div><div><div><div>8</div></div><div><span>↓</span></div></div><div><div><div>9</div></div><div><span>更新结果</span></div></div><div><div><div>10</div></div><div><span>↓</span></div></div><div><div><div>11</div></div><div><span>更新排行榜</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这种复杂业务流时，</p><p>再引入：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Aggregate</span></div></div><div><div><div>2</div></div><div><span>Domain Service</span></div></div><div><div><div>3</div></div><div><span>Repository</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>会更容易体会 DDD 的价值。</p><p>否则很容易变成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>为了DDD而DDD</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>写了很多目录和接口，但业务复杂度还没到那个程度。对于一个在线判题系统这类项目，比较合适的路线通常是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>三层架构</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>理解微服务</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>5</div></div><div><span>事件驱动(MQ)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>7</div></div><div><span>DDD Lite</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>完整DDD</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样学习曲线会平滑很多，也更符合实际项目的演进过程。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/python-strenum-literal/</id>
      <title type="text">Python StrEnum 与 Literal 的区别</title>
      <published>2025-09-10T00:00:00.000Z</published>
      <updated>2025-09-10T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/python-strenum-literal/"/>
      <summary type="text">StrEnum（运行时枚举）与 Literal（纯类型约束）的差异、对比表与 FastAPI/Pydantic 选型建议。</summary>
      <content type="html"><![CDATA[<p><code>StrEnum</code> 和 <code>Literal</code> 虽然都能限制字符串取值，但用途完全不同。</p>
<section><h2>1. StrEnum：运行时的枚举类型<a href="#1-strenum运行时的枚举类型"><span>#</span></a></h2><p>Python 3.11+</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> enum </span><span>import</span><span> StrEnum</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>Status</span><span>(</span><span>StrEnum</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>PENDING</span><span> </span><span>=</span><span> </span><span>"pending"</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>SUCCESS</span><span> </span><span>=</span><span> </span><span>"success"</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>FAILED</span><span> </span><span>=</span><span> </span><span>"failed"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>status </span><span>=</span><span> Status.</span><span>PENDING</span></span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>print</span><span>(status)          </span><span># pending</span></div></div><div><div><div>4</div></div><div><span>print</span><span>(status.value)    </span><span># pending</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>特点：</p><ul>
<li>运行时真实存在的对象</li>
<li><strong>可以遍历</strong></li>
<li>可以比较</li>
<li>可以作为配置、数据库字段、API参数等统一定义</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> s </span><span>in</span><span> Status:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>print</span><span>(s)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span><span> status </span><span>==</span><span> Status.</span><span>PENDING</span><span>:</span></span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h2>2. Literal：类型提示<a href="#2-literal类型提示"><span>#</span></a></h2><p>来自 <code>typing</code></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> typing </span><span>import</span><span> Literal</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>Status </span><span>=</span><span> Literal[</span></span></div></div><div><div><div>4</div></div><div><span>    </span><span>"pending"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"success"</span><span>,</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"failed"</span></div></div><div><div><div>7</div></div><div><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>update_status</span><span>(</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>status</span><span>:</span><span> Status</span></div></div><div><div><div>3</div></div><div><span>):</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>IDE 会提示：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>update_status</span><span>(</span></span><span>"pending"</span><span>)  </span><span># ✅</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>update_status</span><span>(</span></span><span>"abc"</span><span>)      </span><span># ❌ 类型检查报错</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>特点：</p><ul>
<li>只存在于类型系统</li>
<li>运行时没有枚举对象</li>
<li>不能遍历</li>
<li>不能写 <code>Status.PENDING</code></li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>print</span><span>(Status)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>typing.Literal[</span><span>'pending'</span><span>, </span><span>'success'</span><span>, </span><span>'failed'</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h2>对比<a href="#对比"><span>#</span></a></h2>

<table><thead><tr><th>特性</th><th>StrEnum</th><th>Literal</th></tr></thead><tbody><tr><td>运行时存在</td><td>✅</td><td>❌</td></tr><tr><td>类型检查</td><td>✅</td><td>✅</td></tr><tr><td>可遍历</td><td>✅</td><td>❌</td></tr><tr><td>可作为常量</td><td>✅</td><td>❌</td></tr><tr><td>IDE 自动补全</td><td>✅</td><td>一般</td></tr><tr><td>JSON/API 参数约束</td><td>✅</td><td>✅</td></tr><tr><td>数据库存储映射</td><td>✅</td><td>❌</td></tr></tbody></table><hr /></section>
<section><h2>FastAPI/Pydantic 场景<a href="#fastapipydantic-场景"><span>#</span></a></h2><p>通常推荐：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> enum </span><span>import</span><span> StrEnum</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>ModelProvider</span><span>(</span><span>StrEnum</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>OPENAI</span><span> </span><span>=</span><span> </span><span>"openai"</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>ANTHROPIC</span><span> </span><span>=</span><span> </span><span>"anthropic"</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>GEMINI</span><span> </span><span>=</span><span> </span><span>"gemini"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> pydantic </span><span>import</span><span> BaseModel</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>Request</span><span>(</span><span>BaseModel</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>provider: ModelProvider</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>自动生成 OpenAPI：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"enum"</span><span>: [</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"openai"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"anthropic"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"gemini"</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>]</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>同时还能：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span><span> req.provider </span><span>==</span><span> ModelProvider.</span><span>OPENAI</span><span>:</span></span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h2>什么时候用 Literal？<a href="#什么时候用-literal"><span>#</span></a></h2><p>当你只是想约束几个字符串参数，而且不会在别处复用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>create_message</span><span>(</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>role</span><span>:</span><span> Literal[</span></div></div><div><div><div>3</div></div><div><span>        </span><span>"user"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>        </span><span>"assistant"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>        </span><span>"system"</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>]</span></div></div><div><div><div>7</div></div><div><span>):</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这种场景用 <code>Literal</code> 很轻量。</p><hr /></section>
<section><h2>实际项目建议<a href="#实际项目建议"><span>#</span></a></h2><ul>
<li><strong>业务领域概念（状态、模型提供商、订单类型等）</strong> → <code>StrEnum</code></li>
<li><strong>函数参数的简单约束</strong> → <code>Literal</code></li>
<li><strong>FastAPI/Pydantic Schema</strong> → 优先 <code>StrEnum</code></li>
</ul><p>很多大型 Python 项目（如 Pydantic、FastAPI、LangGraph 等）最终都会把重要的字符串常量抽成 <code>StrEnum</code>，因为它既有类型安全，又有运行时能力。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/gin-model-binding/</id>
      <title type="text">Gin 模型绑定与验证</title>
      <published>2025-09-02T00:00:00.000Z</published>
      <updated>2025-09-02T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/gin-model-binding/"/>
      <summary type="text">Gin 的两类绑定方法（Must/Should）与 binding:&apos;required&apos; 字段校验用法。</summary>
      <content type="html"><![CDATA[<p>使用时，需要在要绑定的所有字段上，设置相应的tag。 例如，使用 JSON 绑定时，设置字段标签为 <code>json:"fieldname"</code>。</p>
<p>Gin提供了两类绑定方法：</p>
<ul>
<li><strong>Type</strong> - Must bind
<ul>
<li><strong>Methods</strong> - <code>Bind</code>, <code>BindJSON</code>, <code>BindXML</code>, <code>BindQuery</code>, <code>BindYAML</code></li>
<li><strong>Behavior</strong> - 这些方法属于 <code>MustBindWith</code> 的具体调用。 如果发生绑定错误，则请求终止，并触发 <code>c.AbortWithError(400, err).SetType(ErrorTypeBind)</code>。响应状态码被设置为 400 并且 <code>Content-Type</code> 被设置为 <code>text/plain; charset=utf-8</code>。 如果您在此之后尝试设置响应状态码，Gin会输出日志 <code>[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422</code>。 如果您希望更好地控制绑定，考虑使用 <code>ShouldBind</code> 等效方法。</li>
</ul>
</li>
<li><strong>Type</strong> - Should bind
<ul>
<li><strong>Methods</strong> - <code>ShouldBind</code>, <code>ShouldBindJSON</code>, <code>ShouldBindXML</code>, <code>ShouldBindQuery</code>, <code>ShouldBindYAML</code></li>
<li><strong>Behavior</strong> - 这些方法属于 <code>ShouldBindWith</code> 的具体调用。 如果发生绑定错误，Gin 会返回错误并由开发者处理错误和请求。</li>
</ul>
</li>
</ul>
<p>使用 Bind 方法时，Gin 会尝试根据 Content-Type 推断如何绑定。 如果你明确知道要绑定什么，可以使用 <code>MustBindWith</code> 或 <code>ShouldBindWith</code>。</p>
<p>你也可以指定必须绑定的字段。 如果一个字段的 tag 加上了 <code>binding:"required"</code>，但绑定时是空值, Gin 会报错。</p>
<p>如果结构体的某个字段本身也是一个结构体（嵌套结构体），为了正确验证，该结构体的字段也需要添加 <code>binding:"required"</code> 标签。</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 绑定 JSON</span></div></div><div><div><div>2</div></div><div><span>type</span><span> </span><span>Login</span><span> </span><span>struct</span><span> {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>User</span><span>     </span><span>string</span><span> </span><span>`form:"user" json:"user" xml:"user"  binding:"required"`</span></div></div><div><div><div>4</div></div><div><span>  </span><span>Password</span><span> </span><span>string</span><span> </span><span>`form:"password" json:"password" xml:"password" binding:"required"`</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>8</div></div><div><span>  </span><span>router</span><span><span> </span><span>:=</span><span> </span></span><span>gin</span><span>.</span><span>Default</span><span>()</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>  </span><span>// 绑定 JSON ({"user": "manu", "password": "123"})</span></div></div><div><div><div>11</div></div><div><span>  </span><span>router</span><span>.</span><span>POST</span><span>(</span><span>"/loginJSON"</span><span>, </span><span>func</span><span><span>(</span><span>c</span><span> </span><span>*</span></span><span>gin</span><span>.</span><span>Context</span><span>) {</span></div></div><div><div><div>12</div></div><div><span>    </span><span>var</span><span> </span><span>json</span><span> </span><span>Login</span></div></div><div><div><div>13</div></div><div><span>    </span><span>if</span><span> </span><span>err</span><span><span> </span><span>:=</span><span> </span></span><span>c</span><span>.</span><span>ShouldBindJSON</span><span><span>(</span><span>&amp;</span></span><span>json</span><span>); </span><span>err</span><span><span> </span><span>!=</span><span> </span></span><span>nil</span><span> {</span></div></div><div><div><div>14</div></div><div><span>      </span><span>c</span><span>.</span><span>JSON</span><span>(</span><span>http</span><span>.</span><span>StatusBadRequest</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"error"</span><span>: </span><span>err</span><span>.</span><span>Error</span><span>()})</span></div></div><div><div><div>15</div></div><div><span>      </span><span>return</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>if</span><span> </span><span>json</span><span>.</span><span>User</span><span><span> </span><span>!=</span><span> </span></span><span>"manu"</span><span><span> </span><span>||</span><span> </span></span><span>json</span><span>.</span><span>Password</span><span><span> </span><span>!=</span><span> </span></span><span>"123"</span><span> {</span></div></div><div><div><div>19</div></div><div><span>      </span><span>c</span><span>.</span><span>JSON</span><span>(</span><span>http</span><span>.</span><span>StatusUnauthorized</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"status"</span><span>: </span><span>"unauthorized"</span><span>})</span></div></div><div><div><div>20</div></div><div><span>      </span><span>return</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>    </span><span>c</span><span>.</span><span>JSON</span><span>(</span><span>http</span><span>.</span><span>StatusOK</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"status"</span><span>: </span><span>"you are logged in"</span><span>})</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>})</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>  </span><span>// 绑定 XML (</span></div></div><div><div><div>27</div></div><div><span>  </span><span>//  &lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></div></div><div><div><div>28</div></div><div><span>  </span><span>//  &lt;root&gt;</span></div></div><div><div><div>29</div></div><div><span>  </span><span>//    &lt;user&gt;manu&lt;/user&gt;</span></div></div><div><div><div>30</div></div><div><span>  </span><span>//    &lt;password&gt;123&lt;/password&gt;</span></div></div><div><div><div>31</div></div><div><span>  </span><span>//  &lt;/root&gt;)</span></div></div><div><div><div>32</div></div><div><span>  </span><span>router</span><span>.</span><span>POST</span><span>(</span><span>"/loginXML"</span><span>, </span><span>func</span><span><span>(</span><span>c</span><span> </span><span>*</span></span><span>gin</span><span>.</span><span>Context</span><span>) {</span></div></div><div><div><div>33</div></div><div><span>    </span><span>var</span><span> </span><span>xml</span><span> </span><span>Login</span></div></div><div><div><div>34</div></div><div><span>    </span><span>if</span><span> </span><span>err</span><span><span> </span><span>:=</span><span> </span></span><span>c</span><span>.</span><span>ShouldBindXML</span><span><span>(</span><span>&amp;</span></span><span>xml</span><span>); </span><span>err</span><span><span> </span><span>!=</span><span> </span></span><span>nil</span><span> {</span></div></div><div><div><div>35</div></div><div><span>      </span><span>c</span><span>.</span><span>JSON</span><span>(</span><span>http</span><span>.</span><span>StatusBadRequest</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"error"</span><span>: </span><span>err</span><span>.</span><span>Error</span><span>()})</span></div></div><div><div><div>36</div></div><div><span>      </span><span>return</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span>    </span><span>if</span><span> </span><span>xml</span><span>.</span><span>User</span><span><span> </span><span>!=</span><span> </span></span><span>"manu"</span><span><span> </span><span>||</span><span> </span></span><span>xml</span><span>.</span><span>Password</span><span><span> </span><span>!=</span><span> </span></span><span>"123"</span><span> {</span></div></div><div><div><div>40</div></div><div><span>      </span><span>c</span><span>.</span><span>JSON</span><span>(</span><span>http</span><span>.</span><span>StatusUnauthorized</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"status"</span><span>: </span><span>"unauthorized"</span><span>})</span></div></div><div><div><div>41</div></div><div><span>      </span><span>return</span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>43</div></div><div>
</div></div><div><div><div>44</div></div><div><span>    </span><span>c</span><span>.</span><span>JSON</span><span>(</span><span>http</span><span>.</span><span>StatusOK</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"status"</span><span>: </span><span>"you are logged in"</span><span>})</span></div></div><div><div><div>45</div></div><div><span><span>  </span></span><span>})</span></div></div><div><div><div>46</div></div><div>
</div></div><div><div><div>47</div></div><div><span>  </span><span>// 绑定 HTML 表单 (user=manu&amp;password=123)</span></div></div><div><div><div>48</div></div><div><span>  </span><span>router</span><span>.</span><span>POST</span><span>(</span><span>"/loginForm"</span><span>, </span><span>func</span><span><span>(</span><span>c</span><span> </span><span>*</span></span><span>gin</span><span>.</span><span>Context</span><span>) {</span></div></div><div><div><div>49</div></div><div><span>    </span><span>var</span><span> </span><span>form</span><span> </span><span>Login</span></div></div><div><div><div>50</div></div><div><span>    </span><span>// 根据 Content-Type Header 推断使用哪个绑定器。</span></div></div><div><div><div>51</div></div><div><span>    </span><span>if</span><span> </span><span>err</span><span><span> </span><span>:=</span><span> </span></span><span>c</span><span>.</span><span>ShouldBind</span><span><span>(</span><span>&amp;</span></span><span>form</span><span>); </span><span>err</span><span><span> </span><span>!=</span><span> </span></span><span>nil</span><span> {</span></div></div><div><div><div>52</div></div><div><span>      </span><span>c</span><span>.</span><span>JSON</span><span>(</span><span>http</span><span>.</span><span>StatusBadRequest</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"error"</span><span>: </span><span>err</span><span>.</span><span>Error</span><span>()})</span></div></div><div><div><div>53</div></div><div><span>      </span><span>return</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>    </span><span>if</span><span> </span><span>form</span><span>.</span><span>User</span><span><span> </span><span>!=</span><span> </span></span><span>"manu"</span><span><span> </span><span>||</span><span> </span></span><span>form</span><span>.</span><span>Password</span><span><span> </span><span>!=</span><span> </span></span><span>"123"</span><span> {</span></div></div><div><div><div>57</div></div><div><span>      </span><span>c</span><span>.</span><span>JSON</span><span>(</span><span>http</span><span>.</span><span>StatusUnauthorized</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"status"</span><span>: </span><span>"unauthorized"</span><span>})</span></div></div><div><div><div>58</div></div><div><span>      </span><span>return</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>60</div></div><div>
</div></div><div><div><div>61</div></div><div><span>    </span><span>c</span><span>.</span><span>JSON</span><span>(</span><span>http</span><span>.</span><span>StatusOK</span><span>, </span><span>gin</span><span>.</span><span>H</span><span>{</span><span>"status"</span><span>: </span><span>"you are logged in"</span><span>})</span></div></div><div><div><div>62</div></div><div><span><span>  </span></span><span>})</span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span>  </span><span>// 监听并在 0.0.0.0:8080 上启动服务</span></div></div><div><div><div>65</div></div><div><span>  </span><span>router</span><span>.</span><span>Run</span><span>(</span><span>":8080"</span><span>)</span></div></div><div><div><div>66</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/go-small-interface/</id>
      <title type="text">Go 的 Small Interface 设计理念</title>
      <published>2025-08-05T00:00:00.000Z</published>
      <updated>2025-08-05T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/go-small-interface/"/>
      <summary type="text">从编译模型看 Go 与 C++ 契约方式的差异：消费者定义小接口与隐式实现的哲学。</summary>
      <content type="html"><![CDATA[<p>C++ 使用时必须依赖头文件声明接口，而 Go 却可以在消费端就地定义 interface，由类型隐式满足契约。<strong>这个差异的核心在于语言的设计哲学、实现机制和编译模型的不同。</strong> Go 和 C++ 在“契约”（contract）定义方式上的对比，体现了两种完全不同的编程范式。</p>
<section><h3>1. C++ 的方式：生产者提供全局 Header 作为契约（显式、中心化）<a href="#1-c-的方式生产者提供全局-header-作为契约显式中心化"><span>#</span></a></h3><ul>
<li><strong>历史和编译模型驱动</strong>：C++ 继承自 C 的<strong>分离编译（separate compilation）<strong>模型。源文件（.cpp）分开编译成对象文件，再链接。编译器在编译一个 .cpp 时，需要知道其他模块的</strong>声明</strong>（类型、函数签名、类布局等），否则无法生成正确的代码（尤其是虚函数表、内存布局等）。
<ul>
<li>Header 文件（.h/.hpp）就是这个“声明”的载体。它定义了类、函数、虚方法等，作为<strong>全局可见的契约</strong>。</li>
<li>生产者（库作者）必须在 header 中把接口写好，消费者（用户代码）通过 <code>#include</code> 来使用。</li>
</ul>
</li>
<li><strong>显式实现</strong>：类需要显式继承抽象基类（带纯虚函数），或使用模板。改变 header 往往影响 ABI（二进制兼容性），容易导致“header hell”、编译时间爆炸和耦合。</li>
<li><strong>优势</strong>：适合大型系统，需要精确控制内存布局、性能和二进制接口（比如游戏引擎、系统库）。header 像一份“公开规格书”。</li>
</ul><p>这是<strong>生产者驱动</strong>（producer-driven）的契约：生产者决定“我的东西长什么样，你们都得按这个来”。</p></section>
<section><h3>2. Go 的方式：消费者在消费端定义 interface（隐式、分散化）<a href="#2-go-的方式消费者在消费端定义-interface隐式分散化"><span>#</span></a></h3><ul>
<li><strong>编译模型不同</strong>：Go <strong>没有 header</strong>，整个 package 一起编译（快速构建是 Go 的核心目标之一）。编译器能直接看到所有源码，不需要单独的声明文件。</li>
<li><strong>隐式满足（Implicit Implementation）</strong>：这是 Go 接口最关键的特性。
<ul>
<li>一个类型只要实现了 interface 声明的所有方法，就<strong>自动满足</strong>该 interface，不需要 <code>implements</code> 关键字。</li>
<li>接口本身只是<strong>一组方法签名</strong>的集合（往往很小，比如 <code>io.Reader</code> 只一个 <code>Read</code> 方法）。</li>
</ul>
</li>
<li><strong>消费者定义</strong>：推荐在<strong>使用接口的地方</strong>（消费者包）定义 interface，而不是在提供具体实现的包里。</li>
</ul><p><strong>为什么 Go 这样设计？</strong></p><ul>
<li><strong>解耦和灵活性</strong>：生产者只需提供<strong>具体类型</strong>（struct + 方法），不需要预知所有可能的用法。消费者根据自己<strong>当前需求</strong>定义最小化的 interface（“我只需要这些行为”）。这符合 Go “小接口、大组合”的哲学（Small Interfaces）。
<ul>
<li>例子：标准库的 <code>io.Reader</code>、<code>io.Writer</code>、<code>fmt.Stringer</code> 等，都是极小的接口，定义在需要它们的地方。</li>
</ul>
</li>
<li><strong>减少依赖</strong>：消费者定义 interface 时，不需要导入生产者包里的接口定义（避免循环依赖或不必要的包依赖）。测试时特别方便——你可以轻松 mock 一个只满足你自定义 interface 的假对象。</li>
<li><strong>Duck Typing 的静态检查版</strong>：像动态语言的鸭子类型，但编译期就检查。生产者代码不需要“知道”任何 interface 的存在。</li>
<li><strong>设计哲学</strong>：Go 强调<strong>简单、可维护、少预判</strong>。生产者不用在设计时“预见所有用例”（这往往会导致胖接口、违背接口隔离原则）。消费者只依赖自己真正需要的行为，代码更松耦合、更易演化。</li>
</ul></section>
<section><h3>总结对比<a href="#总结对比"><span>#</span></a></h3>

<table><thead><tr><th>方面</th><th>C++ (Header)</th><th>Go (Interface)</th></tr></thead><tbody><tr><td><strong>谁定义契约</strong></td><td>生产者（全局 header）</td><td>消费者（使用处定义）</td></tr><tr><td><strong>实现方式</strong></td><td>显式（继承、声明）</td><td>隐式（只要有方法就行）</td></tr><tr><td><strong>接口大小</strong></td><td>倾向较大、完整类接口</td><td>极小、单一职责（one-method 常见）</td></tr><tr><td><strong>耦合度</strong></td><td>较高（header 变更影响大）</td><td>极低</td></tr><tr><td><strong>适用场景</strong></td><td>性能敏感、大型系统、ABI 稳定</td><td>微服务、快速迭代、测试友好</td></tr><tr><td><strong>根本原因</strong></td><td>分离编译 + 历史传统</td><td>统一编译 + 现代简约哲学</td></tr></tbody></table><p><strong>Go 的这种设计不是“随意”，而是深思熟虑的结果</strong>：它让接口真正成为“行为契约”而非“类型层次结构”。很多从 Java/C++ 转 Go 的人初期会不适应（总想在生产者端定义大接口），但用熟后会发现测试、扩展和维护都轻松很多。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/cmake-add-subdirectory-vs-add-library/</id>
      <title type="text">CMake：add_subdirectory 与 add_library 的区别</title>
      <published>2025-07-15T00:00:00.000Z</published>
      <updated>2025-07-15T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/cmake-add-subdirectory-vs-add-library/"/>
      <summary type="text">add_subdirectory、add_library 与 target_link_libraries 各自的职责边界。</summary>
      <content type="html"><![CDATA[<p>If you want to compile the code into your project directly, that’s not a library, that’s just code.</p>
<p>A library is a distinct compiled and linked object. A static library is a <code>.a</code>/<code>.lib</code> file, a shared library is a <code>.so</code>/<code>.dll</code> file.</p>
<p>If you just want to add source code files to your current target, you do so with <code>target_sources()</code> and that’s it. No need to create a library at all.</p>
<p>So with that in mind, we can now talk about the commands:</p>
<p><code>add_subdirectory()</code>: This has nothing to do with libraries, this just runs the CML of a subdirectory. The code in that CML can do anything, maybe it creates a library, maybe it adds source code files to your current project, maybe it plays a little song and prints messages to your build log</p>
<p><code>add_library()</code>: This creates a new library target, a new <code>.lib</code> or <code>.dll</code> file to be compiled and linked. It is the sister command to <code>add_executable()</code>, which creates a new binary executable target (<code>.exe</code> on Windows)</p>
<p><code>target_link_libraries()</code>: This links a library, maybe created in your current project or maybe an imported library from <code>find_package()</code>/<code>find_library()</code>, into a target</p>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/cmake-target-include-directories/</id>
      <title type="text">CMake target_include_directories 的三种作用域</title>
      <published>2025-07-08T00:00:00.000Z</published>
      <updated>2025-07-08T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/cmake-target-include-directories/"/>
      <summary type="text">target_include_directories 的 PRIVATE/INTERFACE/PUBLIC 语义、适用场景与对照表。</summary>
      <content type="html"><![CDATA[<section><h2><code>target_include_directories()</code><a href="#target_include_directories"><span>#</span></a></h2><p>命令可以为目标指定包含目录，并且可以使用 <strong><code>PRIVATE</code></strong>、<strong><code>INTERFACE</code></strong> 或 <strong><code>PUBLIC</code></strong> 来控制这些包含目录的作用范围以及它们是否会传播给其他依赖该目标的目标。</p><section><h3>1. <code>PRIVATE</code><a href="#1-private"><span>#</span></a></h3><p><code>PRIVATE</code> 意味着包含目录<strong>仅对该目标自身</strong>可见，它不会传播给链接该目标的其他目标。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>target_include_directories</span><span>(MyTarget PRIVATE include/)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>使用场景</strong>：目标在自身编译时需要该包含目录，但不希望其他链接到这个目标的目标使用该目录。</li>
</ul><hr /></section><section><h3>2. <code>INTERFACE</code><a href="#2-interface"><span>#</span></a></h3><p><code>INTERFACE</code> 表示该目标本身<strong>不需要</strong>这个包含目录，但是<strong>链接到这个目标的其他目标</strong>会需要它。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>target_include_directories</span><span>(MyTarget INTERFACE include/)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>使用场景</strong>：该目标是一个头文件库（例如header-only library），它自身不需要包含目录，但任何链接到它的目标都需要这个包含目录来编译。</li>
</ul><hr /></section><section><h3>3. <code>PUBLIC</code><a href="#3-public"><span>#</span></a></h3><p><code>PUBLIC</code> 意味着包含目录对该目标<strong>自身</strong>和<strong>链接到该目标的其他目标</strong>都可见。它既会用于编译这个目标，也会传播给任何依赖它的其他目标。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>target_include_directories</span><span>(MyTarget PUBLIC include/)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>使用场景</strong>：该目标在编译时需要包含目录，同时任何依赖这个目标的其他目标也需要该包含目录。</li>
</ul><hr /></section></section>
<section><h2>总结：<a href="#总结"><span>#</span></a></h2>

<table><thead><tr><th><strong>可见性</strong></th><th><strong>目标本身使用</strong></th><th><strong>链接到该目标的其他目标使用</strong></th></tr></thead><tbody><tr><td><code>PRIVATE</code></td><td>是</td><td>否</td></tr><tr><td><code>INTERFACE</code></td><td>否</td><td>是</td></tr><tr><td><code>PUBLIC</code></td><td>是</td><td>是</td></tr></tbody></table></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/go-map/</id>
      <title type="text">Go 的 map</title>
      <published>2025-06-20T00:00:00.000Z</published>
      <updated>2025-06-20T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/go-map/"/>
      <summary type="text">Go map 的引用类型本质、key 的约束、nil 行为与初始化示例。</summary>
      <content type="html"><![CDATA[<p><code>map</code> 是引用类型，可以使用如下<strong>声明</strong>：</p>
<p><strong>map 不是”简单结构”，是复杂的引用类型，必须 <code>make</code> 分配底层哈希表内存。</strong></p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span> </span><span>map1</span><span> </span><span>map</span><span>[</span><span>keytype</span><span>]</span><span>valuetype</span></div></div><div><div><div>2</div></div><div><span>var</span><span> </span><span>map1</span><span> </span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>在声明的时候不需要知道 <code>map</code> 的长度，<code>map</code> 是可以动态增长的。</p>
<p>未初始化的 <code>map</code> 的值是 <code>nil</code>。</p>
<p>key 可以是任意可以用 <code>==</code> 或者 <code>!=</code> 操作符比较的类型，比如 <code>string</code>、<code>int</code>、<code>float32(64)</code>。所以数组、切片和结构体不能作为 key (译者注：含有数组切片的结构体不能作为 key，只包含内建类型的 <code>struct</code> 是可以作为 key 的），但是指针和接口类型可以。如果要用结构体作为 key 可以提供 <code>Key()</code> 和 <code>Hash()</code> 方法，这样可以通过结构体的域计算出唯一的数字或者字符串的 key。</p>
<p>value 可以是任意类型的；通过使用空接口类型（详见<a href="https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/11.9.md" target="_blank">第 11.9 节</a>），我们可以存储任意值，但是使用这种类型作为值时需要先做一次类型断言（详见<a href="https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/11.3.md" target="_blank">第 11.3 节</a>）。</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>package</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div><span>import</span><span> </span><span>"fmt"</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>func</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>var</span><span> </span><span>mapLit</span><span> </span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span></div></div><div><div><div>6</div></div><div><span>  </span><span>//var mapCreated map[string]float32</span></div></div><div><div><div>7</div></div><div><span>  </span><span>var</span><span> </span><span>mapAssigned</span><span> </span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>  </span><span>mapLit</span><span><span> </span><span>=</span><span> </span></span><span>map</span><span>[</span><span>string</span><span>]</span><span>int</span><span>{</span><span>"one"</span><span>: </span><span>1</span><span>, </span><span>"two"</span><span>: </span><span>2</span><span>}</span></div></div><div><div><div>10</div></div><div><span>  </span><span>mapCreated</span><span><span> </span><span>:=</span><span> </span></span><span>make</span><span>(</span><span>map</span><span>[</span><span>string</span><span>]</span><span>float32</span><span>)</span></div></div><div><div><div>11</div></div><div><span>  </span><span>mapAssigned</span><span><span> </span><span>=</span><span> </span></span><span>mapLit</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>  </span><span>mapCreated</span><span>[</span><span>"key1"</span><span><span>] </span><span>=</span><span> </span></span><span>4.5</span></div></div><div><div><div>14</div></div><div><span>  </span><span>mapCreated</span><span>[</span><span>"key2"</span><span><span>] </span><span>=</span><span> </span></span><span>3.14159</span></div></div><div><div><div>15</div></div><div><span>  </span><span>mapAssigned</span><span>[</span><span>"two"</span><span><span>] </span><span>=</span><span> </span></span><span>3</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>  </span><span>fmt</span><span>.</span><span>Printf</span><span>(</span><span>"Map literal at </span><span>\"</span><span>one</span><span>\"</span><span> is: </span><span>%d</span><span>\n</span><span>"</span><span>, </span><span>mapLit</span><span>[</span><span>"one"</span><span>])</span></div></div><div><div><div>18</div></div><div><span>  </span><span>fmt</span><span>.</span><span>Printf</span><span>(</span><span>"Map created at </span><span>\"</span><span>key2</span><span>\"</span><span> is: </span><span>%f</span><span>\n</span><span>"</span><span>, </span><span>mapCreated</span><span>[</span><span>"key2"</span><span>])</span></div></div><div><div><div>19</div></div><div><span>  </span><span>fmt</span><span>.</span><span>Printf</span><span>(</span><span>"Map assigned at </span><span>\"</span><span>two</span><span>\"</span><span> is: </span><span>%d</span><span>\n</span><span>"</span><span>, </span><span>mapLit</span><span>[</span><span>"two"</span><span>])</span></div></div><div><div><div>20</div></div><div><span>  </span><span>fmt</span><span>.</span><span>Printf</span><span>(</span><span>"Map literal at </span><span>\"</span><span>ten</span><span>\"</span><span> is: </span><span>%d</span><span>\n</span><span>"</span><span>, </span><span>mapLit</span><span>[</span><span>"ten"</span><span>])</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/wooden-toy-festival/</id>
      <title type="text">Wooden Toy Festival 题解：二分答案与贪心验证</title>
      <published>2025-05-25T00:00:00.000Z</published>
      <updated>2025-05-25T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/wooden-toy-festival/"/>
      <summary type="text">三个雕刻师制作 n 个玩具的最小等待时间：二分答案 + 排序后贪心分配验证，附完整 C++ 代码。</summary>
      <content type="html"><![CDATA[<section><h2>题意：<a href="#题意"><span>#</span></a></h2><p>有三个雕刻师，他们需要制作n个玩具，对于n个玩具，每个玩具都有一个图案值a[i]，</p><p>如果雕刻师选择的图案是 x ，那么为他制作一个图案为 y 的玩具需要 |x−y| 的时间，因为玩具越像他能立即制作的玩具，雕刻师的工作速度就越快。</p><p>雕刻师们都是非常熟练的人，可以同时处理不同玩具
雕刻师们希望在选择准备工作的样式时，尽可能减少完成制作所有玩具所需要的等待时间</p><p>要求你求出制作出所有玩家所需要的最短等待时间</p></section>
<section><h2>分析<a href="#分析"><span>#</span></a></h2><p>当需要求某个数是最大值/最小值的时候，会考虑到问题是否可以通过二分搜索来解决。</p><p>能用二分搜索解决的问题通常具有单调性，而对于本题，很明显的一个事实是如果等待时间越短，那么三个雕刻师一开始所选择的图案就与越多的玩具的a[i]相似.。</p><p>而且，还注意到，如果3个雕刻师能够在 t 的时间内制作所有的玩具，那么3个雕刻师必定也能在 d (d &gt; t) 的时间内也制作所有的玩具</p><p>因此我们可以尝试二分搜索可行的时间（即三个雕刻师是否可以在这个时间内把所有的玩具制作完），一旦查到可行的时间，我们便试着不断缩小搜索的区间去把mid值逼近最优解。</p></section>
<section><h2>条件判断<a href="#条件判断"><span>#</span></a></h2><p>那么现在的问题是该如何判断三者可以在时间 t 内制作所有玩具？</p><p>想象一下，如果我们给三个雕刻师分别分配任务，时间 t 实际上取决于三者之间最晚完成任务的人。
考虑到任务分配的顺序是没有影响的，那么我们最好对图案数组先排序一次，因为排序后的数组中对于 任意的 (i, j)，他们的 |a[i] - a[j]| 的值总要比未排序的要小。</p><p>也就是因为这个排序后的性质，如果我们在排序后的数组里面按顺序分配任务，<strong>选定 i，某个雕刻师在 t 时间内能制作完成的玩具数量即为在 [a[i], a[i] + 2*x] 值区间内的 a[i] 的数量</strong></p><p>所以剩下来的部分就只有模拟了，我们模拟分配工作给雕刻师，记录当前已完成制作的玩具的数量 cur，每名雕刻师的工作完成后cur都会增加，而这个cur后面的玩具又会交给下一个雕刻师，最后检查cur是否到达n个数量即可</p></section>
<section><h2>详细代码<a href="#详细代码"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span><span> </span><span>std</span><span>;</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>int</span><span> </span><span>mod</span><span> </span><span>=</span><span> </span><span><span>1</span><span>e</span><span>6</span></span><span>+</span><span>10</span><span>;</span></div></div><div><div><div>5</div></div><div><span>typedef</span><span> </span><span>long</span><span> </span><span>long</span><span> </span><span>ll</span><span>;</span></div></div><div><div><div>6</div></div><div><span>#define</span><span> </span><span>x</span><span> </span><span>first</span></div></div><div><div><div>7</div></div><div><span>#define</span><span> </span><span>y</span><span> </span><span>second</span></div></div><div><div><div>8</div></div><div><span>typedef</span><span> </span><span>pair</span><span>&lt;int</span><span>, </span><span>int&gt;</span><span> </span><span>P</span><span>;</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>ll</span><span> </span><span>v</span><span>[</span><span>mod</span><span>];</span></div></div><div><div><div>11</div></div><div><span>ll</span><span> </span><span>n</span><span>;</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>bool</span><span> </span><span>check</span><span>(</span><span>ll</span><span><span> </span><span>x</span><span>) {</span></span></div></div><div><div><div>14</div></div><div><span>  </span><span>int</span><span> </span><span>cur</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>15</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>3</span><span>; </span><span>++</span><span>i</span><span>) {</span></div></div><div><div><div>16</div></div><div><span>        </span><span>int</span><span> </span><span>start</span><span> </span><span>=</span><span> </span><span>v</span><span>[</span><span>cur</span><span>];</span></div></div><div><div><div>17</div></div><div><span>        </span><span>int</span><span> </span><span>end</span><span> </span><span>=</span><span> </span><span>start</span><span> </span><span>+</span><span> </span><span>2</span><span> </span><span>*</span><span> </span><span>x</span><span>;</span></div></div><div><div><div>18</div></div><div><span>        </span><span>while</span><span> (</span><span>cur</span><span> </span><span>&lt;</span><span> </span><span>n</span><span> </span><span>&amp;&amp;</span><span> </span><span>v</span><span>[</span><span>cur</span><span>] </span><span>&lt;=</span><span> </span><span>end</span><span>) {</span></div></div><div><div><div>19</div></div><div><span>            </span><span>++</span><span>cur</span><span>;</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>        </span><span>if</span><span> (</span><span>cur</span><span> </span><span>&gt;=</span><span> </span><span>n</span><span>) {</span></div></div><div><div><div>22</div></div><div><span>            </span><span>return</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>    </span><span>return</span><span> </span><span>cur</span><span> </span><span>&gt;=</span><span> </span><span>n</span><span>;</span></div></div><div><div><div>27</div></div><div><span>}</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>30</div></div><div><span>  </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>n</span><span>;</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>n</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>33</div></div><div><span>    </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>v</span><span>[</span><span>i</span><span>];</span></div></div><div><div><div>34</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>  </span><span>sort</span><span>(</span><span>v</span><span>, </span><span>v</span><span>+</span><span>n</span><span>);</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>  </span><span>ll</span><span> </span><span>l</span><span> </span><span>=</span><span> </span><span>0</span><span>, </span><span>r</span><span> </span><span>=</span><span> </span><span><span>1</span><span>e</span><span>9</span></span><span>;</span></div></div><div><div><div>39</div></div><div><span>  </span><span>ll</span><span> </span><span>mid</span><span>,</span><span>ans</span><span>=</span><span><span>1</span><span>e</span><span>9</span></span><span>;</span></div></div><div><div><div>40</div></div><div><span><span>  </span></span><span>//mid为三者雕刻所需要的最大时间</span></div></div><div><div><div>41</div></div><div><span><span>  </span></span><span>//check的时候，尽可能多的在mid时间内为雕刻师安排多的v</span></div></div><div><div><div>42</div></div><div><span><span>  </span></span><span>//显然这个问题具有单调性，如果用时越久，模具的制作偏差就越大</span></div></div><div><div><div>43</div></div><div><span>  </span><span>while</span><span> (</span><span>l</span><span> </span><span>&lt;=</span><span> </span><span>r</span><span>) {</span></div></div><div><div><div>44</div></div><div><span>    </span><span>mid</span><span>=</span><span>(</span><span>l</span><span>+</span><span>r</span><span>)</span><span>&gt;&gt;</span><span>1</span><span>;</span></div></div><div><div><div>45</div></div><div><span>    </span><span>if</span><span> (</span><span>check</span><span>(</span><span>mid</span><span>)) {</span></div></div><div><div><div>46</div></div><div><span>      </span><span>r</span><span> </span><span>=</span><span> </span><span>mid</span><span>-</span><span>1</span><span>;</span></div></div><div><div><div>47</div></div><div><span>      </span><span>ans</span><span> </span><span>=</span><span> </span><span>mid</span><span>;</span></div></div><div><div><div>48</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>49</div></div><div><span>      </span><span>l</span><span> </span><span>=</span><span> </span><span>mid</span><span>+</span><span>1</span><span>;</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>51</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>52</div></div><div><span>  </span><span>cout</span><span> </span><span>&lt;&lt;</span><span> </span><span>ans</span><span> </span><span>&lt;&lt;</span><span> </span><span>endl</span><span>;</span></div></div><div><div><div>53</div></div><div><span>}</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>int</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>ios_base::</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>57</div></div><div><span>    </span><span>cin</span><span>.</span><span>tie</span><span>(</span><span>NULL</span><span>);</span></div></div><div><div><div>58</div></div><div><span>    </span><span>cout</span><span>.</span><span>tie</span><span>(</span><span>NULL</span><span>);</span></div></div><div><div><div>59</div></div><div>
</div></div><div><div><div>60</div></div><div><span>  </span><span>int</span><span> </span><span>t</span><span>;</span></div></div><div><div><div>61</div></div><div><span>  </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>t</span><span>;</span></div></div><div><div><div>62</div></div><div><span>  </span><span>while</span><span>(</span><span>t</span><span>--</span><span>) </span><span>solve</span><span>();</span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span>  </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>65</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/cf957e-novices-mistake/</id>
      <title type="text">CF 957E Novice&apos;s Mistake 题解</title>
      <published>2025-03-09T00:00:00.000Z</published>
      <updated>2025-03-09T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/cf957e-novices-mistake/"/>
      <summary type="text">字符串乘法与数字乘法结果相同的 (a,b) 计数：通过收紧枚举上下界让暴力可过。</summary>
      <content type="html"><![CDATA[<section><h2>原题<a href="#原题"><span>#</span></a></h2><p>K1o0n 的第一个编程问题是这样的”Noobish_Monk有 n 个朋友。 (1≤n≤100) 个朋友。每个朋友都给了他 a (1≤a≤10000) 个苹果。 (1≤a≤10000) 个苹果作为生日礼物。收到这样的礼物后，诺比什/蒙克很高兴，他回赠了 b (1≤b≤min(10000,a⋅n)) 个苹果。 (1≤b≤min(10000,a⋅n)) 个苹果给他的朋友们。诺比西和尚还剩下多少个苹果？”</p><p>K1o0n 写了一个解决方案，但不小心把 n 的值看成了字符串，所以 n⋅a−b 的值的计算方法不同。具体来说</p><p>当用字符串 n 乘以整数 a 时，他将得到字符串
s=n+n+⋯+n+n (a times)
从字符串 s 中减去整数 b 时，将删除最后的 b 个字符。如果 b 大于或等于字符串 s 的长度，则字符串 s将变为空。
了解到这一点后，ErnKor 开始关注在给定的 n 中，有多少对 (a,b) 满足问题的约束条件，而 K1o0n 的解给出了正确答案。</p><p>“解法给出了正确答案 “意味着它输出了一个非空字符串，这个字符串转换成整数后等于正确答案，即 n⋅a−b 的值。</p><section><h3>输入<a href="#输入"><span>#</span></a></h3><p>第一行包含一个整数 t ( 1≤t≤100) - 测试用例数。
对于每个测试用例，单行输入包含一个整数 n ( 1≤n≤100)。
保证在所有测试用例中， n 都是不同的。</p></section></section>
<section><h2>输出<a href="#输出"><span>#</span></a></h2><p>对于每个测试用例，按以下格式输出答案：
在第一行，输出整数 x - 给定 n 的坏测试次数。
在接下来的 x 行中，输出两个整数 ai 和 bi - K1o0n 在测试” n “中的解决方案的整数。 ai bi” 得到正确答案。</p><p>Input</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>3</span></div></div><div><div><div>2</div></div><div><span>2</span></div></div><div><div><div>3</div></div><div><span>3</span></div></div><div><div><div>4</div></div><div><span>10</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Output</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>3</span></div></div><div><div><div>2</div></div><div><span>20</span><span> </span><span>18</span></div></div><div><div><div>3</div></div><div><span>219</span><span> </span><span>216</span></div></div><div><div><div>4</div></div><div><span>2218</span><span> </span><span>2214</span></div></div><div><div><div>5</div></div><div><span>1</span></div></div><div><div><div>6</div></div><div><span>165</span><span> </span><span>162</span></div></div><div><div><div>7</div></div><div><span>1</span></div></div><div><div><div>8</div></div><div><span>1262</span><span> </span><span>2519</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>题目大意：<a href="#题目大意"><span>#</span></a></h2><p>给你一个整数n，现在对于n*a - b有两种运算：</p><ol>
<li>n作为一个字符往后延伸至a个，然后从最后减去b个字符</li>
<li>n作为一个数字，进行运算n*a - b</li>
</ol><p>需要你求出 (a, b) 不同的组合数，使得两种运算后的结果相同。</p></section>
<section><h2>思考<a href="#思考"><span>#</span></a></h2><p>此题的数据范围不大，使用暴力枚举再调整合适的枚举范围是可以通过的。</p><p>对于此题，我们容易想到O(n^2)的暴力解法，即从010000枚举a再从 0a*n 去枚举b
但是这样子做必定会超时，我们不可能直接按题目给的范围去直接枚举b</p><section><h4>调整上界<a href="#调整上界"><span>#</span></a></h4><p>由于在第一种操作中，减去b会使得n<em>a减去b个字符，结果的字符串字符数不可能为负数
因此b的上界即是n</em>a的字符串的最长长度，即 |n|*a，即 数字n变成字符串后的长度再乘每次枚举的a</p><p>所以我们确定b的上界为  |n|*a</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> </span><span>len</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>2</div></div><div><span>while</span><span> (</span><span>tmp</span><span>[</span><span>len</span><span>]) {</span></div></div><div><div><div>3</div></div><div><span>  </span><span>arr</span><span>[</span><span>len</span><span>] </span><span>=</span><span> </span><span>tmp</span><span>[</span><span>len</span><span>]</span><span>-</span><span>'0'</span><span>;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>len</span><span>++</span><span>;</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div><span>for</span><span> (</span><span>int</span><span> </span><span>a</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>a</span><span> </span><span>&lt;=</span><span> </span><span>10000</span><span>; </span><span>a</span><span>++</span><span>)</span></div></div><div><div><div>7</div></div><div><span>  </span><span>for</span><span> (</span><span>int</span><span> </span><span>b</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>b</span><span> </span><span>&lt;=</span><span> </span><span>a</span><span>*</span><span>len</span><span>; </span><span>b</span><span>++</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>调整下界<a href="#调整下界"><span>#</span></a></h4><p>回到题目观察数据范围，我们发现n*a - b的范围是严格小于 10^6的，也就是说，按照第一种操作，字符串的长度最多不会超过6个字符，
于是下界minB的计算：|n|∗a−minB≤6
minB=|n|∗a−6</p></section><section><h4>比较两种计算的值<a href="#比较两种计算的值"><span>#</span></a></h4><p>十进制逐位加，注意一个小技巧：这里对arr下标取余运算，我们设arr是一个大小为3的数组，下标存的是 n 的每一位数字</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>val</span><span> </span><span>=</span><span> (</span><span>n</span><span>*</span><span>a</span><span>) </span><span>-</span><span> </span><span>b</span><span>;</span><span> //具体值</span></div></div><div><div><div>2</div></div><div><span>rep</span><span> </span><span>=</span><span> (</span><span>a</span><span>*</span><span>len</span><span> </span><span>-</span><span> </span><span>b</span><span>);</span><span> //目标的字符串有多长</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>if</span><span> (</span><span>!</span><span>val</span><span> </span><span>||</span><span> </span><span>!</span><span>rep</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>break</span><span>;</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>int</span><span> </span><span>r</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>9</div></div><div><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>rep</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>10</div></div><div><span>  </span><span>r</span><span> </span><span>=</span><span> </span><span>r</span><span> </span><span>*</span><span> </span><span>10</span><span> </span><span>+</span><span> </span><span>arr</span><span>[</span><span>i</span><span>%</span><span>len</span><span>];</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div><div><div><div>12</div></div><div><span>if</span><span> (</span><span>r</span><span> </span><span>==</span><span> </span><span>val</span><span>) {</span></div></div><div><div><div>13</div></div><div><span>  </span><span>ans</span><span>.</span><span>push_back</span><span>({</span><span>a</span><span>, </span><span>b</span><span>});</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>题解代码<a href="#题解代码"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span><span> </span><span>std</span><span>;</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>int</span><span> </span><span>mod</span><span> </span><span>=</span><span> </span><span><span>1</span><span>e</span><span>6</span></span><span>+</span><span>10</span><span>;</span></div></div><div><div><div>5</div></div><div><span>typedef</span><span> </span><span>long</span><span> </span><span>long</span><span> </span><span>ll</span><span>;</span></div></div><div><div><div>6</div></div><div><span>#define</span><span> </span><span>x</span><span> </span><span>first</span></div></div><div><div><div>7</div></div><div><span>#define</span><span> </span><span>y</span><span> </span><span>second</span></div></div><div><div><div>8</div></div><div><span>typedef</span><span> </span><span>pair</span><span>&lt;int</span><span>, </span><span>int&gt;</span><span> </span><span>P</span><span>;</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>11</div></div><div><span>    </span><span>int</span><span> </span><span>n</span><span>;</span></div></div><div><div><div>12</div></div><div><span>    </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>n</span><span>;</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>P</span><span>&gt; </span><span>ans</span><span>;</span></div></div><div><div><div>15</div></div><div><span>    </span><span>int</span><span> </span><span>val</span><span>;</span></div></div><div><div><div>16</div></div><div><span>    </span><span>int</span><span> </span><span>rep</span><span>;</span></div></div><div><div><div>17</div></div><div><span>    </span><span>int</span><span> </span><span>d</span><span>;</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>    </span><span>int</span><span> </span><span>arr</span><span>[</span><span>3</span><span>];</span></div></div><div><div><div>20</div></div><div><span>    </span><span>char</span><span> </span><span>tmp</span><span>[</span><span>256</span><span>] {};</span></div></div><div><div><div>21</div></div><div><span>    </span><span>itoa</span><span>(</span><span>n</span><span>, </span><span>tmp</span><span>, </span><span>10</span><span>);</span></div></div><div><div><div>22</div></div><div><span>    </span><span>int</span><span> </span><span>len</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span>while</span><span> (</span><span>tmp</span><span>[</span><span>len</span><span>]) {</span></div></div><div><div><div>25</div></div><div><span>        </span><span>arr</span><span>[</span><span>len</span><span>] </span><span>=</span><span> </span><span>tmp</span><span>[</span><span>len</span><span>]</span><span>-</span><span>'0'</span><span>;</span></div></div><div><div><div>26</div></div><div><span>        </span><span>len</span><span>++</span><span>;</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>a</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>a</span><span> </span><span>&lt;=</span><span> </span><span>10000</span><span>; </span><span>a</span><span>++</span><span>) {</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>//对于B:  上界: a*len-1，下界: a*len-6 (在区间内最多不会超过6个字符)</span></div></div><div><div><div>31</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> </span><span>b</span><span> </span><span>=</span><span> </span><span>max</span><span>(</span><span>1</span><span>, </span><span>len</span><span>*</span><span>a</span><span> </span><span>-</span><span>5</span><span>); </span><span>b</span><span> </span><span>&lt;=</span><span> </span><span>a</span><span>*</span><span>len</span><span>; </span><span>b</span><span>++</span><span>) {</span></div></div><div><div><div>32</div></div><div><span>            </span><span>val</span><span> </span><span>=</span><span> (</span><span>n</span><span>*</span><span>a</span><span>) </span><span>-</span><span> </span><span>b</span><span>;</span><span> //具体值</span></div></div><div><div><div>33</div></div><div><span>            </span><span>rep</span><span> </span><span>=</span><span> (</span><span>a</span><span>*</span><span>len</span><span> </span><span>-</span><span> </span><span>b</span><span>);</span><span> //目标的字符串有多长</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>            </span><span>if</span><span> (</span><span>!</span><span>val</span><span> </span><span>||</span><span> </span><span>!</span><span>rep</span><span>) {</span></div></div><div><div><div>36</div></div><div><span>                </span><span>break</span><span>;</span></div></div><div><div><div>37</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span>            </span><span>int</span><span> </span><span>r</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>40</div></div><div><span>            </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>rep</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>41</div></div><div><span>                </span><span>r</span><span> </span><span>=</span><span> </span><span>r</span><span> </span><span>*</span><span> </span><span>10</span><span> </span><span>+</span><span> </span><span>arr</span><span>[</span><span>i</span><span>%</span><span>len</span><span>];</span></div></div><div><div><div>42</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span>            </span><span>if</span><span> (</span><span>r</span><span> </span><span>==</span><span> </span><span>val</span><span>) {</span></div></div><div><div><div>44</div></div><div><span>                </span><span>ans</span><span>.</span><span>push_back</span><span>({</span><span>a</span><span>, </span><span>b</span><span>});</span></div></div><div><div><div>45</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>46</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span>    </span><span>cout</span><span> </span><span>&lt;&lt;</span><span> </span><span>ans</span><span>.</span><span>size</span><span>() </span><span>&lt;&lt;</span><span> </span><span>endl</span><span>;</span></div></div><div><div><div>50</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>ans</span><span>.</span><span>size</span><span>(); </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>51</div></div><div><span>        </span><span>cout</span><span> </span><span>&lt;&lt;</span><span> </span><span>ans</span><span>[</span><span>i</span><span>].</span><span>x</span><span> </span><span>&lt;&lt;</span><span> </span><span>' '</span><span> </span><span>&lt;&lt;</span><span> </span><span>ans</span><span>[</span><span>i</span><span>].</span><span>y</span><span> </span><span>&lt;&lt;</span><span> </span><span>endl</span><span>;</span></div></div><div><div><div>52</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>53</div></div><div><span>}</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>int</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>ios_base::</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>57</div></div><div><span>    </span><span>cin</span><span>.</span><span>tie</span><span>(</span><span>NULL</span><span>);</span></div></div><div><div><div>58</div></div><div><span>    </span><span>cout</span><span>.</span><span>tie</span><span>(</span><span>NULL</span><span>);</span></div></div><div><div><div>59</div></div><div>
</div></div><div><div><div>60</div></div><div><span>    </span><span>int</span><span> </span><span>t</span><span>;</span></div></div><div><div><div>61</div></div><div><span>    </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>t</span><span>;</span></div></div><div><div><div>62</div></div><div><span>    </span><span>while</span><span> (</span><span>t</span><span>--</span><span>)</span></div></div><div><div><div>63</div></div><div><span>        </span><span>solve</span><span>();</span></div></div><div><div><div>64</div></div><div>
</div></div><div><div><div>65</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>66</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/tarjan/</id>
      <title type="text">Tarjan 算法求强连通分量</title>
      <published>2025-01-12T00:00:00.000Z</published>
      <updated>2025-01-12T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/tarjan/"/>
      <summary type="text">Tarjan 算法求强连通分量（SCC）的核心思路、dfn/low 更新规则、代码框架与缩点等常见应用。</summary>
      <content type="html"><![CDATA[<p>Tarjan 算法是一种用于 <strong>求强连通分量（SCC, Strongly Connected Components）</strong> 的高效算法，时间复杂度 <strong>O(n + m)</strong>，其中 <span><span>nn</span><span><span><span></span><span>n</span></span></span></span> 是顶点数，<span><span>mm</span><span><span><span></span><span>m</span></span></span></span> 是边数。它基于 <strong>深度优先搜索（DFS）</strong>，使用<strong>回溯</strong>的思想来判断一个点是否属于同一个 SCC。</p>
<section><h2><strong>1. Tarjan 算法核心思路</strong><a href="#1-tarjan-算法核心思路"><span>#</span></a></h2><ol>
<li><strong>时间戳（dfn）</strong>：记录每个节点的 <strong>DFS 访问次序</strong>，每个点的 dfn[u] 值是其访问顺序（从 1 开始递增）。</li>
<li><strong>最小可达值（low）</strong>：记录 <strong>当前节点能通过 DFS 访问到的最早的节点</strong>。</li>
</ol><ul>
<li>如果 low[u] == dfn[u]，说明 u 是一个 SCC 的根，栈中 u 及其上方的所有点属于同一 SCC。</li>
</ul><ol>
<li><strong>栈（stack）</strong>：维护 SCC，确保 SCC 内的点按拓扑顺序排列。</li>
<li><strong>入栈标记（in_stack）</strong>：用于判断当前节点是否在栈中，避免重复计算。</li>
</ol></section>
<section><h2><strong>2. Tarjan 代码框架</strong><a href="#2-tarjan-代码框架"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span><span> </span><span>std</span><span>;</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>int</span><span> </span><span>N</span><span> </span><span>=</span><span> </span><span><span>1</span><span>e</span><span>5</span></span><span> </span><span>+</span><span> </span><span>5</span><span>;</span><span>  // 最大节点数</span></div></div><div><div><div>5</div></div><div><span>vector</span><span>&lt;int&gt;</span><span> </span><span>graph</span><span>[</span><span>N</span><span>];</span><span>   // 邻接表存储图</span></div></div><div><div><div>6</div></div><div><span>int</span><span> </span><span>dfn</span><span>[</span><span>N</span><span>], </span><span>low</span><span>[</span><span>N</span><span>], </span><span>scc_id</span><span>[</span><span>N</span><span>], </span><span>ts</span><span> </span><span>=</span><span> </span><span>0</span><span>, </span><span>scc_cnt</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>7</div></div><div><span>bool</span><span> </span><span>in_stack</span><span>[</span><span>N</span><span>];</span></div></div><div><div><div>8</div></div><div><span>stack</span><span>&lt;int&gt;</span><span> </span><span>stk</span><span>;</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>void</span><span> </span><span>tarjan</span><span>(</span><span>int</span><span><span> </span><span>u</span><span>) {</span></span></div></div><div><div><div>11</div></div><div><span>    </span><span>dfn</span><span>[</span><span>u</span><span>] </span><span>=</span><span> </span><span>low</span><span>[</span><span>u</span><span>] </span><span>=</span><span> </span><span>++</span><span>ts</span><span>;</span><span>  // 设置访问次序</span></div></div><div><div><div>12</div></div><div><span>    </span><span>stk</span><span>.</span><span>push</span><span>(</span><span>u</span><span>);</span></div></div><div><div><div>13</div></div><div><span>    </span><span>in_stack</span><span>[</span><span>u</span><span>] </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>v</span><span> : </span><span>graph</span><span>[</span><span>u</span><span>]) {</span></div></div><div><div><div>16</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span>dfn</span><span>[</span><span>v</span><span>]) {</span><span>  // 递归访问</span></div></div><div><div><div>17</div></div><div><span>            </span><span>tarjan</span><span>(</span><span>v</span><span>);</span></div></div><div><div><div>18</div></div><div><span>            </span><span>low</span><span>[</span><span>u</span><span>] </span><span>=</span><span> </span><span>min</span><span>(</span><span>low</span><span>[</span><span>u</span><span>], </span><span>low</span><span>[</span><span>v</span><span>]);</span><span>  // 回溯更新 low 值</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>} </span><span>else</span><span> </span><span>if</span><span> (</span><span>in_stack</span><span>[</span><span>v</span><span>]) {</span><span>  // 发现回边</span></div></div><div><div><div>20</div></div><div><span>            </span><span>low</span><span>[</span><span>u</span><span>] </span><span>=</span><span> </span><span>min</span><span>(</span><span>low</span><span>[</span><span>u</span><span>], </span><span>dfn</span><span>[</span><span>v</span><span>]);</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span>if</span><span> (</span><span>dfn</span><span>[</span><span>u</span><span>] </span><span>==</span><span> </span><span>low</span><span>[</span><span>u</span><span>]) {</span><span>  // 发现 SCC 根</span></div></div><div><div><div>25</div></div><div><span>        </span><span>++</span><span>scc_cnt</span><span>;</span></div></div><div><div><div>26</div></div><div><span>        </span><span>while</span><span> (</span><span>true</span><span>) {</span></div></div><div><div><div>27</div></div><div><span>            </span><span>int</span><span> </span><span>v</span><span> </span><span>=</span><span> </span><span>stk</span><span>.</span><span>top</span><span>();</span></div></div><div><div><div>28</div></div><div><span>            </span><span>stk</span><span>.</span><span>pop</span><span>();</span></div></div><div><div><div>29</div></div><div><span>            </span><span>in_stack</span><span>[</span><span>v</span><span>] </span><span>=</span><span> </span><span>false</span><span>;</span></div></div><div><div><div>30</div></div><div><span>            </span><span>scc_id</span><span>[</span><span>v</span><span>] </span><span>=</span><span> </span><span>scc_cnt</span><span>;</span><span>  // 给 SCC 赋编号</span></div></div><div><div><div>31</div></div><div><span>            </span><span>if</span><span> (</span><span>v</span><span> </span><span>==</span><span> </span><span>u</span><span>) </span><span>break</span><span>;</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>34</div></div><div><span>}</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>void</span><span> </span><span>find_scc</span><span>(</span><span>int</span><span><span> </span><span>n</span><span>) {</span></span></div></div><div><div><div>37</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>i</span><span> </span><span>&lt;=</span><span> </span><span>n</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>38</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span>dfn</span><span>[</span><span>i</span><span>]) </span><span>tarjan</span><span>(</span><span>i</span><span>);</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>40</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h2><strong>3. Tarjan SCC 代码详解</strong><a href="#3-tarjan-scc-代码详解"><span>#</span></a></h2><section><h3><strong>(1) dfn[u] 和 low[u]</strong><a href="#1-dfnu-和-lowu"><span>#</span></a></h3><ul>
<li>dfn[u]：表示 <strong>访问次序</strong>，用递增的时间戳标记。</li>
<li>low[u]：表示 <strong>回溯能到的最早访问点</strong>。</li>
<li><strong>更新规则</strong>：</li>
</ul><p>low[u] = min(low[u], low[v])（如果 v 是 u 的子节点）。
low[u] = min(low[u], dfn[v])（如果 v 已访问，且 v 在栈中）。</p></section><section><h3><strong>(2) SCC 识别</strong><a href="#2-scc-识别"><span>#</span></a></h3><ul>
<li>如果 dfn[u] == low[u]，说明 u 是 SCC 根，栈中 u 及其上方的所有点属于同一 SCC。</li>
<li><strong>SCC 出栈的顺序即拓扑序</strong>，可以用于 <strong>缩点（Condensation Graph）</strong>。</li>
</ul></section></section>
<section><h2><strong>4. SCC 相关应用</strong><a href="#4-scc-相关应用"><span>#</span></a></h2><section><h3><strong>(1) 求缩点 DAG</strong><a href="#1-求缩点-dag"><span>#</span></a></h3><p>SCC 形成后，我们可以 <strong>建立一个新的 DAG</strong>（有向无环图），以 SCC 为点，SCC 之间有向边：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vector</span><span>&lt;int&gt;</span><span> </span><span>scc_graph</span><span>[</span><span>N</span><span>];</span><span>  // SCC 缩点后的图</span></div></div><div><div><div>2</div></div><div><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>scc_in_degree</span><span>(</span><span>N</span><span>, </span><span>0</span><span>);</span><span>  // SCC 入度</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>void</span><span> </span><span>build_scc_graph</span><span>(</span><span>int</span><span><span> </span><span>n</span><span>) {</span></span></div></div><div><div><div>5</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>u</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>u</span><span> </span><span>&lt;=</span><span> </span><span>n</span><span>; </span><span>u</span><span>++</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> </span><span>v</span><span> : </span><span>graph</span><span>[</span><span>u</span><span>]) {</span></div></div><div><div><div>7</div></div><div><span>            </span><span>if</span><span> (</span><span>scc_id</span><span>[</span><span>u</span><span>] </span><span>!=</span><span> </span><span>scc_id</span><span>[</span><span>v</span><span>]) {</span><span>  // 仅当 u, v 属于不同 SCC 时建边</span></div></div><div><div><div>8</div></div><div><span>                </span><span>scc_graph</span><span>[</span><span>scc_id</span><span>[</span><span>u</span><span>]].</span><span>push_back</span><span>(</span><span>scc_id</span><span>[</span><span>v</span><span>]);</span></div></div><div><div><div>9</div></div><div><span>                </span><span>scc_in_degree</span><span>[</span><span>scc_id</span><span>[</span><span>v</span><span>]]</span><span>++</span><span>;</span></div></div><div><div><div>10</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3><strong>(2) 统计有向图中的「强连通块」</strong><a href="#2-统计有向图中的强连通块"><span>#</span></a></h3><p>Tarjan 算法可以用于求解 <strong>一个图最多能划分成多少个强连通块</strong>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cout</span><span> </span><span>&lt;&lt;</span><span> </span><span>"图的强连通分量个数: "</span><span> </span><span>&lt;&lt;</span><span> </span><span>scc_cnt</span><span> </span><span>&lt;&lt;</span><span> </span><span>endl</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3><strong>(3) 计算入度为 0 的 SCC 数量</strong><a href="#3-计算入度为-0-的-scc-数量"><span>#</span></a></h3><p>入度为 0 的 SCC <strong>在缩点 DAG 中没有前驱</strong>，可用于拓扑排序：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>int</span><span> </span><span>zero_in_degree_scc</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>2</div></div><div><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>i</span><span> </span><span>&lt;=</span><span> </span><span>scc_cnt</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> (</span><span>scc_in_degree</span><span>[</span><span>i</span><span>] </span><span>==</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>4</div></div><div><span>        </span><span>zero_in_degree_scc</span><span>++</span><span>;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3><strong>(4) 计算强连通分量中的最小代价</strong><a href="#4-计算强连通分量中的最小代价"><span>#</span></a></h3><p>有时 SCC 内有一些数值（如贿赂代价、最小值等），可以用 <strong>SCC 合并后取最小值</strong>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>scc_min_cost</span><span>(</span><span>scc_cnt</span><span> </span><span>+</span><span> </span><span>1</span><span>, </span><span>INF</span><span>);</span></div></div><div><div><div>2</div></div><div><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>i</span><span> </span><span>&lt;=</span><span> </span><span>n</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>3</div></div><div><span>    </span><span>scc_min_cost</span><span>[</span><span>scc_id</span><span>[</span><span>i</span><span>]] </span><span>=</span><span> </span><span>min</span><span>(</span><span>scc_min_cost</span><span>[</span><span>scc_id</span><span>[</span><span>i</span><span>]], </span><span>cost</span><span>[</span><span>i</span><span>]);</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2><strong>5. 复杂度分析</strong><a href="#5-复杂度分析"><span>#</span></a></h2><ul>
<li><strong>Tarjan 的时间复杂度</strong>：</li>
</ul><p>每个点 <strong>被访问 1 次</strong>（O(n)）。
每条边 <strong>被遍历 1 次</strong>（O(m)）。
<strong>总时间复杂度 O(n + m)</strong>，适用于 <strong>大规模数据</strong>（如 <span><span>n,m≤105n, m \leq 10^5</span><span><span><span></span><span>n</span><span>,</span><span></span><span>m</span><span></span><span>≤</span><span></span></span><span><span></span><span>1</span><span><span>0</span><span><span><span><span><span><span></span><span><span>5</span></span></span></span></span></span></span></span></span></span></span>）。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/knapsack-01/</id>
      <title type="text">0-1 背包问题</title>
      <published>2024-12-15T00:00:00.000Z</published>
      <updated>2024-12-15T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/knapsack-01/"/>
      <summary type="text">0-1 背包的状态定义、二维 DP 转移方程与一维滚动数组空间优化。</summary>
      <content type="html"><![CDATA[<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>题意概要：有 n 个物品和一个容量为 W 的背包，每个物品有重量 w_{i} 和价值 v_{i} 两种属性，要求选若干物品放入背包使背包中物品的总价值最大且背包中物品的总重量不超过背包的容量。</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>在上述例题中，由于每个物体只有两种可能的状态（取与不取），对应二进制中的 <span><span>00</span><span><span><span></span><span>0</span></span></span></span> 和 <span><span>11</span><span><span><span></span><span>1</span></span></span></span>，这类问题便被称为「0-1 背包问题」。</p>
<p>例题中已知条件有第 i 个物品的重量 w_{i}，价值 v_{i}，以及背包的总容量 W。
设 DP 状态 f_{i,j} 为在只能放前 i 个物品的情况下，容量为 j 的背包所能达到的最大总价值。</p>
<p>考虑转移。假设当前已经处理好了前 i-1 个物品的所有状态，那么对于第 i 个物品，当其不放入背包时，背包的剩余容量不变，背包中物品的总价值也不变，故这种情况的最大价值为 f_{i-1,j}；当其放入背包时，背包的剩余容量会减小 w_{i}，背包中物品的总价值会增大 v_{i}，故这种情况的最大价值为 f_{i-1,j-w_{i}}+v_{i}。 由此可以得出状态转移方程：</p>
<p>物品放入背包后，总重量增加 <span><span>wiw_i</span><span><span><span></span><span><span>w</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，背包剩余容量减少，所以容量是 <span><span>j−wij-w_i</span><span><span><span></span><span>j</span><span></span><span>−</span><span></span></span><span><span></span><span><span>w</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></p>
<p></p><figure><img alt="Pasted image 20240314225745.png" loading="lazy" width="1363" height="601" src="/_astro/pasted-image-20240314225745.BcgMsJt7_5Arpw.webp" /><figcaption>Pasted image 20240314225745.png</figcaption></figure><p></p>
<section><h3>1. 二维动态规划解法<a href="#1-二维动态规划解法"><span>#</span></a></h3><p>最直接的思路是使用二维数组来记录状态。定义<code>dp[i][j]</code>表示前<code>i</code>个物品在背包容量为<code>j</code>时所能达到的最大价值。</p><p><strong>状态转移方程</strong>：</p><ul>
<li>如果不选第<code>i</code>个物品，<code>dp[i][j] = dp[i-1][j]</code>。</li>
<li>如果选第<code>i</code>个物品，<code>dp[i][j] = dp[i-1][j-wi] + vi</code>，其中<code>wi</code>是第<code>i</code>个物品的重量，<code>vi</code>是其价值。</li>
</ul><p>综合以上两种情况：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>dp[i][j]=max(dp[i−1][j],dp[i−1][j−wi]+vi)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>时间复杂度</strong>：<code>O(n * W)</code>，空间复杂度也是<code>O(n * W)</code>。</p></section>
<section><h3>2. 一维动态规划优化<a href="#2-一维动态规划优化"><span>#</span></a></h3><p>通过观察上述二维动态规划方程，可以发现<code>dp[i][j]</code>只依赖于<code>dp[i-1][j]</code>和<code>dp[i-1][j-wi]</code>。因此，<code>dp</code>数组当前行的值只与前一行有关，所以我们可以通过一维数组来保存这些状态，从而降低空间复杂度。</p><p><strong>优化思路</strong>：</p><ul>
<li>使用一维数组<code>dp[j]</code>，表示在背包容量为<code>j</code>时，能得到的最大价值。</li>
<li>按照从大到小的顺序更新<code>dp[j]</code>，这样可以确保在计算<code>dp[j]</code>时，<code>dp[j-wi]</code>使用的是上一轮的值，即“前一行”的状态。</li>
</ul><p><strong>状态转移方程</strong>（与二维数组时类似）</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>dp[j]=max(dp[j],dp[j−wi]+vi)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/max-increasing-triples/</id>
      <title type="text">最大上升三元组数量</title>
      <published>2024-12-01T00:00:00.000Z</published>
      <updated>2024-12-01T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/max-increasing-triples/"/>
      <summary type="text">用两棵树状数组统计数组中最大上升三元组数量的 C++ 实现。</summary>
      <content type="html"><![CDATA[<p>数组离散化
树状数组
前缀和</p>
<section><h2>思路<a href="#思路"><span>#</span></a></h2><p>要求统计满足 <span><span>i&lt;j&lt;ki &lt; j &lt; k</span><span><span><span></span><span>i</span><span></span><span>&lt;</span><span></span></span><span><span></span><span>j</span><span></span><span>&lt;</span><span></span></span><span><span></span><span>k</span></span></span></span> 且 <span><span>ai&lt;aj&lt;aka_i &lt; a_j &lt; a_k</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>&lt;</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>&lt;</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>k</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 的上升三元组数量。把每个位置 <span><span>jj</span><span><span><span></span><span>j</span></span></span></span> 当作三元组的中间元素，问题拆成两步：</p><ul>
<li><strong>第一层（树状数组 f1）</strong>：从左往右扫描，<code>f1.add(idx, 1)</code> 记录值 <code>idx</code> 出现次数。对当前值 <code>idx</code>，<code>f1.query(idx - 1)</code> 就是它左边比它小的数的个数，记作 <span><span>smalljsmall_j</span><span><span><span></span><span>s</span><span>ma</span><span>l</span><span><span>l</span><span><span><span><span><span><span></span><span><span>j</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>。</li>
<li><strong>第二层（树状数组 f2）</strong>：<code>f2.add(idx, small_j)</code>，即在位置 <code>idx</code> 上累加「以某个值结尾、且比它小的左边元素数」。这样 <code>f2.query(idx - 1)</code> 累计的就是所有「两两上升对」中，右端值比 <code>idx</code> 小的对数。</li>
</ul><p>扫到 <code>i</code> 时执行 <code>ans += f2.query(idx - 1)</code>，即以 <span><span>aia_i</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 作为第三个元素，能接上的上升对数量。三层递推：单个数 → 上升对 → 上升三元组。</p><p>值域可能很大（long long），所以先对数组<strong>离散化</strong>（排序副本 + <code>lower_bound</code> 映射），再把离散化后的排名当作树状数组下标。</p><p>复杂度 <span><span>O(nlog⁡n)O(n \log n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span></span><span>lo<span>g</span></span><span></span><span>n</span><span>)</span></span></span></span>，空间 <span><span>O(n)O(n)</span><span><span><span></span><span>O</span><span>(</span><span>n</span><span>)</span></span></span></span>。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;bits/stdc++.h&gt;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>namespace</span><span><span> </span><span>std</span><span>;</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>const</span><span> </span><span>int</span><span> </span><span>MAXN</span><span> </span><span>=</span><span> </span><span><span>3</span><span>e</span><span>4</span></span><span>+</span><span>10</span><span>;</span></div></div><div><div><div>5</div></div><div><span>typedef</span><span> </span><span>long</span><span> </span><span>long</span><span> </span><span>ll</span><span>;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>class</span><span> </span><span>MyTree</span><span> {</span></div></div><div><div><div>8</div></div><div><span>public:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>v</span><span>;</span></div></div><div><div><div>10</div></div><div><span>    </span><span>int</span><span> </span><span>sz</span><span>;</span></div></div><div><div><div>11</div></div><div><span>    </span><span>MyTree</span><span>(</span><span>int</span><span><span> </span><span>sz</span><span>) {</span></span></div></div><div><div><div>12</div></div><div><span>        </span><span>this</span><span>-&gt;</span><span>sz</span><span> </span><span>=</span><span> </span><span>sz</span><span>;</span></div></div><div><div><div>13</div></div><div><span>        </span><span>v</span><span>.</span><span>resize</span><span>(</span><span>sz</span><span> </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>    </span><span>int</span><span> </span><span>lowbit</span><span>(</span><span>int</span><span><span> </span><span>x</span><span>) {</span></span></div></div><div><div><div>17</div></div><div><span>        </span><span>return</span><span> </span><span>x</span><span> </span><span>&amp;</span><span> (</span><span>-</span><span>x</span><span>);</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>    </span><span>void</span><span> </span><span>add</span><span>(</span><span>int</span><span><span> </span><span>x</span><span>, </span></span><span>int</span><span><span> </span><span>value</span><span>) {</span></span></div></div><div><div><div>21</div></div><div><span>        </span><span>while</span><span> (</span><span>x</span><span> </span><span>&lt;=</span><span> </span><span>sz</span><span>) {</span></div></div><div><div><div>22</div></div><div><span>            </span><span>v</span><span>[</span><span>x</span><span>] </span><span>+=</span><span> </span><span>value</span><span>;</span></div></div><div><div><div>23</div></div><div><span>            </span><span>x</span><span> </span><span>+=</span><span> </span><span>lowbit</span><span>(</span><span>x</span><span>);</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>    </span><span>int</span><span> </span><span>query</span><span>(</span><span>int</span><span><span> </span><span>x</span><span>) {</span></span></div></div><div><div><div>28</div></div><div><span>        </span><span>int</span><span> </span><span>sum</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>29</div></div><div><span>        </span><span>while</span><span> (</span><span>x</span><span> </span><span>&gt;</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>30</div></div><div><span>            </span><span>sum</span><span> </span><span>+=</span><span> </span><span>v</span><span>[</span><span>x</span><span>];</span></div></div><div><div><div>31</div></div><div><span>            </span><span>x</span><span> </span><span>-=</span><span> </span><span>lowbit</span><span>(</span><span>x</span><span>);</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>33</div></div><div><span>        </span><span>return</span><span> </span><span>sum</span><span>;</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>35</div></div><div><span>};</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>MyTree</span><span> </span><span>f1</span><span>(</span><span>MAXN</span><span>), </span><span>f2</span><span>(</span><span>MAXN</span><span>);</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>40</div></div><div><span>    </span><span>int</span><span> </span><span>n</span><span>;</span></div></div><div><div><div>41</div></div><div><span>    </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>n</span><span>;</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>ll</span><span>&gt; </span><span>arr</span><span>(</span><span>n</span><span> </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>44</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>ll</span><span>&gt; </span><span>sorted_arr</span><span>(</span><span>n</span><span> </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>i</span><span> </span><span>&lt;=</span><span> </span><span>n</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>47</div></div><div><span>        </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>arr</span><span>[</span><span>i</span><span>];</span></div></div><div><div><div>48</div></div><div><span>        </span><span>sorted_arr</span><span>[</span><span>i</span><span>] </span><span>=</span><span> </span><span>arr</span><span>[</span><span>i</span><span>];</span><span>  // 复制数组用于排序</span></div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div><span>    </span><span>sort</span><span>(</span><span>next</span><span>(</span><span>begin</span><span>(</span><span>sorted_arr</span><span>)), </span><span>end</span><span>(</span><span>sorted_arr</span><span>));</span><span> // 仅排序副本</span></div></div><div><div><div>52</div></div><div><span>    </span><span>auto</span><span> </span><span>f</span><span> </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>ll</span><span><span> </span><span>x</span><span>) {</span></span></div></div><div><div><div>53</div></div><div><span>        </span><span>return</span><span> </span><span>lower_bound</span><span>(</span><span>next</span><span>(</span><span>begin</span><span>(</span><span>sorted_arr</span><span>)), </span><span>end</span><span>(</span><span>sorted_arr</span><span>), </span><span>x</span><span>) </span><span>-</span><span> </span><span>sorted_arr</span><span>.</span><span>begin</span><span>();</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>    </span><span>ll</span><span> </span><span>ans</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>57</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>i</span><span> </span><span>&lt;=</span><span> </span><span>n</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>58</div></div><div><span>        </span><span>int</span><span> </span><span>idx</span><span> </span><span>=</span><span> </span><span>f</span><span>(</span><span>arr</span><span>[</span><span>i</span><span>]);</span></div></div><div><div><div>59</div></div><div><span>        </span><span>f1</span><span>.</span><span>add</span><span>(</span><span>idx</span><span>, </span><span>1</span><span>);</span></div></div><div><div><div>60</div></div><div><span>        </span><span>f2</span><span>.</span><span>add</span><span>(</span><span>idx</span><span>, </span><span>f1</span><span>.</span><span>query</span><span>(</span><span>idx</span><span> </span><span>-</span><span> </span><span>1</span><span>));</span><span>  // 确保索引合法</span></div></div><div><div><div>61</div></div><div><span>        </span><span>ans</span><span> </span><span>+=</span><span> </span><span>f2</span><span>.</span><span>query</span><span>(</span><span>idx</span><span> </span><span>-</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>62</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>63</div></div><div><span>    </span><span>cout</span><span> </span><span>&lt;&lt;</span><span> </span><span>ans</span><span> </span><span>&lt;&lt;</span><span> </span><span>endl</span><span>;</span></div></div><div><div><div>64</div></div><div><span>}</span></div></div><div><div><div>65</div></div><div>
</div></div><div><div><div>66</div></div><div><span>int</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>ios_base::</span><span>sync_with_stdio</span><span>(</span><span>false</span><span>);</span></div></div><div><div><div>68</div></div><div><span>    </span><span>cin</span><span>.</span><span>tie</span><span>(</span><span>NULL</span><span>);</span></div></div><div><div><div>69</div></div><div><span>    </span><span>cout</span><span>.</span><span>tie</span><span>(</span><span>NULL</span><span>);</span></div></div><div><div><div>70</div></div><div>
</div></div><div><div><div>71</div></div><div><span>    </span><span>solve</span><span>();</span></div></div><div><div><div>72</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>73</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/weighted-path-length/</id>
      <title type="text">带权路径长度（WPL）</title>
      <published>2024-11-10T00:00:00.000Z</published>
      <updated>2024-11-10T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/weighted-path-length/"/>
      <summary type="text">带权路径长度 WPL 的定义、计算公式与哈夫曼树示例。</summary>
      <content type="html"><![CDATA[<p>在讨论树结构，特别是在哈夫曼树等数据压缩和编码算法中，一个关键的概念是“带权路径长度”（Weighted Path Length，WPL）。这个概念对于理解树的效率和优化有重要作用。</p>
<section><h3>带权路径长度（WPL）<a href="#带权路径长度wpl"><span>#</span></a></h3><p>带权路径长度是树中 <strong>所有叶节点</strong> 的路径长度与其权值的乘积之和。具体来说：</p><ul>
<li><strong>路径长度</strong>：从树的根节点到任一节点的边的数量（或者说从该节点到根节点的边的数量）。</li>
<li><strong>权值</strong>：分配给树中每个节点的数值，通常反映了某种度量（如在哈夫曼树中，一个字符的频率）。</li>
</ul><p>对于哈夫曼树，WPL 最小化是其设计的关键目的，意味着常见的数据（高权值）具有较短的路径长度，从而实现整体存储和传输的效率。</p></section>
<section><h3>WPL 的计算<a href="#wpl-的计算"><span>#</span></a></h3><p>假设一个树结构中，每个叶节点 <span><span>nin_i</span><span><span><span></span><span><span>n</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 有一个权值 <span><span>wiw_i</span><span><span><span></span><span><span>w</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 和一个从根到该节点的路径长度 <span><span>lil_i</span><span><span><span></span><span><span>l</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>，WPL 计算公式为：</p><p><span><span>WPL=∑i=1kwi×li\text{WPL} = \sum_{i=1}^{k} w_i \times l_i</span><span><span><span></span><span><span>WPL</span></span><span></span><span>=</span><span></span></span><span><span></span><span><span>∑</span><span><span><span><span><span><span></span><span><span><span>i</span><span>=</span><span>1</span></span></span></span><span><span></span><span><span><span>k</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span><span>w</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>×</span><span></span></span><span><span></span><span><span>l</span><span><span><span><span><span><span></span><span><span>i</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span></p><p>其中 <span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 是叶节点的数量。</p></section>
<section><h3>示例<a href="#示例"><span>#</span></a></h3><p>考虑一个简单的哈夫曼树：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>    </span></span><span>*</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>/ \</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>5   *</span></div></div><div><div><div>4</div></div><div><span><span>     </span></span><span>/ \</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>2   3</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在这个例子中，数字代表权值，星号 (*) 表示内部节点（其权值是子节点权值的和）：</p><ul>
<li>权值为 5 的节点路径长度为 1。</li>
<li>权值为 2 的节点路径长度为 2。</li>
<li>权值为 3 的节点路径长度为 2。</li>
</ul><p>计算 WPL:</p><p><span><span>WPL=(5×1)+(2×2)+(3×2)=5+4+6=15WPL = (5 \times 1) + (2 \times 2) + (3 \times 2) = 5 + 4 + 6 = 15</span><span><span><span></span><span>W</span><span>P</span><span>L</span><span></span><span>=</span><span></span></span><span><span></span><span>(</span><span>5</span><span></span><span>×</span><span></span></span><span><span></span><span>1</span><span>)</span><span></span><span>+</span><span></span></span><span><span></span><span>(</span><span>2</span><span></span><span>×</span><span></span></span><span><span></span><span>2</span><span>)</span><span></span><span>+</span><span></span></span><span><span></span><span>(</span><span>3</span><span></span><span>×</span><span></span></span><span><span></span><span>2</span><span>)</span><span></span><span>=</span><span></span></span><span><span></span><span>5</span><span></span><span>+</span><span></span></span><span><span></span><span>4</span><span></span><span>+</span><span></span></span><span><span></span><span>6</span><span></span><span>=</span><span></span></span><span><span></span><span>15</span></span></span></span></p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/mex/</id>
      <title type="text">MEX：最小非负整数</title>
      <published>2024-10-20T00:00:00.000Z</published>
      <updated>2024-10-20T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/mex/"/>
      <summary type="text">MEX 的三个性质，以及用 set + 计数数组支持插入删除后 O(log n) 查询 MEX 的实现。</summary>
      <content type="html"><![CDATA[<p>MEX 即一个数组中的最小非负整数，它满足下面三个性质：</p>
<p>只有在添加的数等于当前序列的mex时，mex的值才会变化。
添加操作只会使得mex变大
删除操作会使得mex的值变小。</p>
<p><a href="https://codeforces.com/contest/1629/problem/C" target="_blank">Problem - C - Codeforces</a></p>
<p>对于一个随时插入，删除元素的数组，想高效的查询mex可使用 set 和 一个计数数组</p>
<p>用一个set&lt;int&gt; st 来记录没有出现过的数，然后直接取最小值就可以了，当然需要一个cnt来计数。</p>
<p>最小值用begin取得</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct</span><span> </span><span>Mex</span><span> {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>static</span><span> </span><span>const</span><span> </span><span>int</span><span> </span><span>N</span><span> </span><span>=</span><span> </span><span><span>1</span><span>e</span><span>5</span></span><span> </span><span>+</span><span> </span><span>5</span><span>;</span></div></div><div><div><div>3</div></div><div><span>    </span><span>int</span><span> </span><span>cnt</span><span>[</span><span>N</span><span>] </span><span>=</span><span> {};</span></div></div><div><div><div>4</div></div><div><span>    </span><span>set</span><span>&lt;</span><span>int</span><span>&gt; </span><span>s</span><span>;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>Mex</span><span>() { </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>N</span><span>; </span><span>++</span><span>i</span><span>) </span><span>s</span><span>.</span><span>insert</span><span>(</span><span>i</span><span>); }</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>void</span><span> </span><span>add</span><span>(</span><span>int</span><span><span> </span><span>x</span><span>) {</span></span></div></div><div><div><div>8</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span>cnt</span><span>[</span><span>x</span><span>]) </span><span>s</span><span>.</span><span>erase</span><span>(</span><span>x</span><span>);</span></div></div><div><div><div>9</div></div><div><span>        </span><span>++</span><span>cnt</span><span>[</span><span>x</span><span>];</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>    </span><span>void</span><span> </span><span>del</span><span>(</span><span>int</span><span><span> </span><span>x</span><span>) {</span></span></div></div><div><div><div>13</div></div><div><span>        </span><span>if</span><span> (</span><span>cnt</span><span>[</span><span>x</span><span>] </span><span>==</span><span> </span><span>1</span><span>) </span><span>s</span><span>.</span><span>insert</span><span>(</span><span>x</span><span>);</span></div></div><div><div><div>14</div></div><div><span>        </span><span>if</span><span> (</span><span>cnt</span><span>[</span><span>x</span><span>]) </span><span>--</span><span>cnt</span><span>[</span><span>x</span><span>];</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>    </span><span>int</span><span> </span><span>get</span><span>() { </span><span>return</span><span> </span><span>*</span><span>s</span><span>.</span><span>begin</span><span>(); }</span></div></div><div><div><div>18</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/cpp-priority-queue/</id>
      <title type="text">C++ priority_queue 优先队列</title>
      <published>2024-10-06T00:00:00.000Z</published>
      <updated>2024-10-06T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/cpp-priority-queue/"/>
      <summary type="text">C++ priority_queue 的基本用法与通过自定义比较实现最小堆。</summary>
      <content type="html"><![CDATA[<p>C++中的优先队列（priority queue）是一种用于维护一组元素构成的集合的容器适配器，其中每个元素都有一个优先级。添加到优先队列中的元素将按照其优先级排序，优先级最高的元素将首先被移除。优先队列通常使用堆（heap）数据结构来实现，以支持高效的元素插入和移除操作。</p>
<p>在C++标准库中，优先队列通过<code>priority_queue</code>模板类提供，该模板类定义在头文件<code>&lt;queue&gt;</code>中。</p>
<section><h3>基本用法<a href="#基本用法"><span>#</span></a></h3><p>以下是<code>priority_queue</code>的基本用法示例：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;iostream&gt;</span></div></div><div><div><div>2</div></div><div><span>#include</span><span> </span><span>&lt;queue&gt;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>int</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>// 创建一个int类型的优先队列，默认使用std::less&lt;int&gt;，</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>// 即最大值优先</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>std::</span><span>priority_queue</span><span>&lt;</span><span>int</span><span>&gt; </span><span>pq</span><span>;</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>// 向优先队列中插入元素</span></div></div><div><div><div>10</div></div><div><span>    </span><span>pq</span><span>.</span><span>push</span><span>(</span><span>30</span><span>);</span></div></div><div><div><div>11</div></div><div><span>    </span><span>pq</span><span>.</span><span>push</span><span>(</span><span>10</span><span>);</span></div></div><div><div><div>12</div></div><div><span>    </span><span>pq</span><span>.</span><span>push</span><span>(</span><span>20</span><span>);</span></div></div><div><div><div>13</div></div><div><span>    </span><span>pq</span><span>.</span><span>push</span><span>(</span><span>5</span><span>);</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>// 遍历优先队列（这不是最好的方法，因为会改变队列内容）</span></div></div><div><div><div>16</div></div><div><span>    </span><span>while</span><span> (</span><span>!</span><span>pq</span><span>.</span><span>empty</span><span>()) {</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>// 访问队头元素</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>std::</span><span>cout</span><span> </span><span>&lt;&lt;</span><span> </span><span>pq</span><span>.</span><span>top</span><span>() </span><span>&lt;&lt;</span><span> </span><span>" "</span><span>;</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>// 移除队头元素</span></div></div><div><div><div>20</div></div><div><span>        </span><span>pq</span><span>.</span><span>pop</span><span>();</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>std::</span><span>cout</span><span> </span><span>&lt;&lt;</span><span> std::</span><span>endl</span><span>;</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>25</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出结果将是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>30 20 10 5</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h3>自定义优先级<a href="#自定义优先级"><span>#</span></a></h3><p>你可以通过定义自己的比较函数或者重载<code>operator&lt;</code>来实现自定义类型的优先级排序。比如，使用结构体并自定义优先级规则：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#include</span><span> </span><span>&lt;iostream&gt;</span></div></div><div><div><div>2</div></div><div><span>#include</span><span> </span><span>&lt;queue&gt;</span></div></div><div><div><div>3</div></div><div><span>#include</span><span> </span><span>&lt;vector&gt;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>struct</span><span> </span><span>MyStruct</span><span> {</span></div></div><div><div><div>6</div></div><div><span>    </span><span>int</span><span> </span><span>value</span><span>;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>// 自定义比较函数</span></div></div><div><div><div>8</div></div><div><span>    </span><span>bool</span><span> </span><span>operator</span><span>&lt;(</span><span>const</span><span> </span><span>MyStruct</span><span>&amp;</span><span><span> </span><span>other</span><span>) </span></span><span>const</span><span> {</span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>// 这里定义的是最小值优先</span></div></div><div><div><div>10</div></div><div><span>        </span><span>return</span><span> </span><span>value</span><span> </span><span>&gt;</span><span> </span><span>other</span><span>.</span><span>value</span><span>;</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>};</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>int</span><span> </span><span>main</span><span>() {</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>std::</span><span>priority_queue</span><span>&lt;</span><span>MyStruct</span><span>&gt; </span><span>pq</span><span>;</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>// 向优先队列中插入元素</span></div></div><div><div><div>18</div></div><div><span>    </span><span>pq</span><span>.</span><span>push</span><span>({</span><span>10</span><span>});</span></div></div><div><div><div>19</div></div><div><span>    </span><span>pq</span><span>.</span><span>push</span><span>({</span><span>30</span><span>});</span></div></div><div><div><div>20</div></div><div><span>    </span><span>pq</span><span>.</span><span>push</span><span>({</span><span>20</span><span>});</span></div></div><div><div><div>21</div></div><div><span>    </span><span>pq</span><span>.</span><span>push</span><span>({</span><span>5</span><span>});</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>// 遍历优先队列</span></div></div><div><div><div>24</div></div><div><span>    </span><span>while</span><span> (</span><span>!</span><span>pq</span><span>.</span><span>empty</span><span>()) {</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>std::</span><span>cout</span><span> </span><span>&lt;&lt;</span><span> </span><span>pq</span><span>.</span><span>top</span><span>().</span><span>value</span><span> </span><span>&lt;&lt;</span><span> </span><span>" "</span><span>;</span></div></div><div><div><div>26</div></div><div><span>        </span><span>pq</span><span>.</span><span>pop</span><span>();</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>std::</span><span>cout</span><span> </span><span>&lt;&lt;</span><span> std::</span><span>endl</span><span>;</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>    </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>31</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>输出结果将是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>5 10 20 30</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在这个例子中，自定义的比较运算符让我们能够实现最小值优先的排序逻辑。注意，当使用自定义类型时，必须确保提供用于比较元素的机制，要么通过重载<code>operator&lt;</code>，要么通过提供自定义的比较函数对象。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/trie/</id>
      <title type="text">字典树</title>
      <published>2024-09-22T00:00:00.000Z</published>
      <updated>2024-09-22T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/trie/"/>
      <summary type="text">字典树的结构特点与插入/查找模板，以及用 01-trie 维护异或极值的方法与例题。</summary>
      <content type="html"><![CDATA[<p>数据结构</p>
<p><strong>特点</strong>：字典树支持高效的前缀查找。其次，通过共享前缀来节省空间，多个以相同前缀开头的字符串只需存储一份前缀。再次，字典树的节点不直接存储完整的字符串，而是通过从根节点到该节点的路径构成字符串
最后，字典树支持插入、查找、删除和前缀匹配等多种操作
<strong>插入和查找操作的时间复杂度均为 O(m)，其中 m 是字符串的长度</strong></p>
<p><strong>结构定义</strong>：字典树通常由两个主要结构组成。首先是TrieNode（节点），每个节点包含一个子节点的映射（通常使用哈希表或数组）和一个布尔值 end，指示该节点是否为某个字符串的结尾。其次是Trie（字典树），它包含一个指向根节点的指针，所有字符串的插入和查找操作都从根节点开始</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct</span><span> </span><span>trie</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>int</span><span> </span><span>nex</span><span>[</span><span>100000</span><span>][</span><span>26</span><span>], </span><span>cnt</span><span>;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>bool</span><span> </span><span>exist</span><span>[</span><span>100000</span><span>];</span><span>  // 该结点结尾的字符串是否存在</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>  </span><span>void</span><span> </span><span>insert</span><span>(</span><span>char</span><span> </span><span>*</span><span><span>s</span><span>, </span></span><span>int</span><span><span> </span><span>l</span><span>) {</span></span><span>  // 插入字符串</span></div></div><div><div><div>6</div></div><div><span>    </span><span>int</span><span> </span><span>p</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>7</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>l</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>8</div></div><div><span>      </span><span>int</span><span> </span><span>c</span><span> </span><span>=</span><span> </span><span>s</span><span>[</span><span>i</span><span>] </span><span>-</span><span> </span><span>'a'</span><span>;</span></div></div><div><div><div>9</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span>nex</span><span>[</span><span>p</span><span>][</span><span>c</span><span>]) </span><span>nex</span><span>[</span><span>p</span><span>][</span><span>c</span><span>] </span><span>=</span><span> </span><span>++</span><span>cnt</span><span>;</span><span>  // 如果没有，就添加结点</span></div></div><div><div><div>10</div></div><div><span>      </span><span>p</span><span> </span><span>=</span><span> </span><span>nex</span><span>[</span><span>p</span><span>][</span><span>c</span><span>];</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>    </span><span>exist</span><span>[</span><span>p</span><span>] </span><span>=</span><span> </span><span>true</span><span>;</span></div></div><div><div><div>13</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>  </span><span>bool</span><span> </span><span>find</span><span>(</span><span>char</span><span> </span><span>*</span><span><span>s</span><span>, </span></span><span>int</span><span><span> </span><span>l</span><span>) {</span></span><span>  // 查找字符串</span></div></div><div><div><div>16</div></div><div><span>    </span><span>int</span><span> </span><span>p</span><span> </span><span>=</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>17</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>l</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>18</div></div><div><span>      </span><span>int</span><span> </span><span>c</span><span> </span><span>=</span><span> </span><span>s</span><span>[</span><span>i</span><span>] </span><span>-</span><span> </span><span>'a'</span><span>;</span></div></div><div><div><div>19</div></div><div><span>      </span><span>if</span><span> (</span><span>!</span><span>nex</span><span>[</span><span>p</span><span>][</span><span>c</span><span>]) </span><span>return</span><span> </span><span>0</span><span>;</span></div></div><div><div><div>20</div></div><div><span>      </span><span>p</span><span> </span><span>=</span><span> </span><span>nex</span><span>[</span><span>p</span><span>][</span><span>c</span><span>];</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>22</div></div><div><span>    </span><span>return</span><span> </span><span>exist</span><span>[</span><span>p</span><span>];</span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
<section><h3>维护异或极值<a href="#维护异或极值"><span>#</span></a></h3><p>XOR 异或
将数的二进制表示看做一个字符串，就可以建出字符集为 <span><span>{0,1}\{0,1\}</span><span><span><span></span><span>{</span><span>0</span><span>,</span><span></span><span>1</span><span>}</span></span></span></span> 的 trie 树。</p><p><a href="https://codeforces.com/problemset/problem/706/D" target="_blank">Problem - 706D - Codeforces</a></p><p>我们考虑在一个<strong>01字典树</strong>（每个节点是0或1）里插入所有数的二进制表示。比如要插入一个32位整数，就从高位到低位一位一位插。</p><p><strong>插入过程：</strong></p><ul>
<li>从最高位（比如31位）往最低位0位走。</li>
<li>每一位如果是0，就走/建0儿子，否则走/建1儿子。</li>
</ul><p><strong>查询最大异或值的过程：</strong>
假设现在要查一个数x，想找集合中一个数，使得异或值最大。异或要大，意味着尽量在每一位上异或出1。怎么做？</p><ul>
<li>从高位到低位一位一位走。</li>
<li>每一位，<strong>优先找跟x当前位不同的方向</strong>（因为1异或0 = 1，能让结果高位出现1）。</li>
<li>如果能走，就走异位；不能走，就只能走同位。</li>
<li>边走边计算异或值。</li>
</ul><p><strong>查询最小异或值的过程（比如如果需要）</strong>：</p><ul>
<li>每一位，<strong>优先走跟x当前位相同的方向</strong>（因为0异或0=0，1异或1=0，结果小）。</li>
<li>走不了再走异位。</li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/xor/</id>
      <title type="text">XOR 异或的性质与可差分性</title>
      <published>2024-09-08T00:00:00.000Z</published>
      <updated>2024-09-08T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/xor/"/>
      <summary type="text">XOR 的交换律、结合律与可差分性：用前缀异和处理区间异或和的两道例题。</summary>
      <content type="html"><![CDATA[<p>位运算</p>
<p>XOR 满足<strong>交换律和结合律</strong></p>
<ol>
<li>结论1: 如果奇数位和偶数位的 XOR 结果相等，则整个数组的 XOR 结果为 0</li>
<li>结论2:  a xor b = 0 当且仅当 a == b</li>
<li>结论3:  a xor 0 = 0</li>
</ol>
<p><a href="https://codeforces.com/problemset/problem/1872/E" target="_blank">Problem - 1872E - Codeforces</a>
巧妙异或前缀和</p>
<p><a href="https://codeforces.com/problemset/problem/1516/B" target="_blank">Problem - 1516B - Codeforces</a>
此题中由于异或运算满足交换律，所以相邻谁先异或谁是没有关系的，
然后就可以把问题转化成求分界点，两边的异或和相等 (或者三块相等)。
然后就暴力枚举两个分界点L，R</p>
<p>枚举 “隔板点”</p>
<p><strong>一个数组中的异或和是可差分的信息</strong></p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>void</span><span> </span><span>solve</span><span>() {</span></div></div><div><div><div>2</div></div><div><span>    </span><span>int</span><span> </span><span>n</span><span>;</span></div></div><div><div><div>3</div></div><div><span>    </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>n</span><span>;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>vector</span><span>&lt;</span><span>ll</span><span>&gt; </span><span>v</span><span>(</span><span>n</span><span>), </span><span>pa</span><span>(</span><span>n</span><span>);</span></div></div><div><div><div>5</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>n</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>        </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>v</span><span>[</span><span>i</span><span>];</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>pa</span><span>[</span><span>0</span><span>] </span><span>=</span><span> </span><span>v</span><span>[</span><span>0</span><span>];</span></div></div><div><div><div>10</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>n</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>11</div></div><div><span>        </span><span>pa</span><span>[</span><span>i</span><span>] </span><span>=</span><span> </span><span>pa</span><span>[</span><span>i</span><span> </span><span>-</span><span> </span><span>1</span><span>] </span><span>^</span><span> </span><span>v</span><span>[</span><span>i</span><span>];</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>    </span><span>if</span><span> (</span><span>pa</span><span>[</span><span>n</span><span> </span><span>-</span><span> </span><span>1</span><span>] </span><span>==</span><span> </span><span>0</span><span>) {</span></div></div><div><div><div>15</div></div><div><span>        </span><span>YES</span><span>;</span></div></div><div><div><div>16</div></div><div><span>        </span><span>return</span><span>;</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>    </span><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> </span><span>n</span><span> </span><span>-</span><span> </span><span>1</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>20</div></div><div><span>        </span><span>for</span><span> (</span><span>int</span><span> </span><span>j</span><span> </span><span>=</span><span> </span><span>i</span><span> </span><span>+</span><span> </span><span>1</span><span>; </span><span>j</span><span> </span><span>&lt;</span><span> </span><span>n</span><span> </span><span>-</span><span> </span><span>1</span><span>; </span><span>j</span><span>++</span><span>) {</span></div></div><div><div><div>21</div></div><div><span>            </span><span>ll</span><span> </span><span>a</span><span> </span><span>=</span><span> </span><span>pa</span><span>[</span><span>i</span><span>];</span></div></div><div><div><div>22</div></div><div><span>            </span><span>ll</span><span> </span><span>b</span><span> </span><span>=</span><span> </span><span>pa</span><span>[</span><span>j</span><span>] </span><span>^</span><span> </span><span>pa</span><span>[</span><span>i</span><span>];</span></div></div><div><div><div>23</div></div><div><span>            </span><span>ll</span><span> </span><span>c</span><span> </span><span>=</span><span> </span><span>pa</span><span>[</span><span>n</span><span> </span><span>-</span><span> </span><span>1</span><span>] </span><span>^</span><span> </span><span>pa</span><span>[</span><span>j</span><span>];</span></div></div><div><div><div>24</div></div><div><span>            </span><span>if</span><span> (</span><span>a</span><span> </span><span>==</span><span> </span><span>b</span><span> </span><span>&amp;&amp;</span><span> </span><span>b</span><span> </span><span>==</span><span> </span><span>c</span><span>) {</span></div></div><div><div><div>25</div></div><div><span>                </span><span>YES</span><span>;</span></div></div><div><div><div>26</div></div><div><span>                </span><span>return</span><span>;</span></div></div><div><div><div>27</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>    </span><span>NO</span><span>;</span></div></div><div><div><div>32</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/dsu/</id>
      <title type="text">并查集</title>
      <published>2024-08-25T00:00:00.000Z</published>
      <updated>2024-08-25T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/dsu/"/>
      <summary type="text">并查集的基本概念（Union/Find）与路径压缩 C++ 模板。</summary>
      <content type="html"><![CDATA[<p>并查集是一种用于管理元素所属集合的数据结构，实现为一个森林，其中<strong>每棵树表示一个集合</strong>，树中的节点表示对应集合中的元素。</p>
<p>顾名思义，并查集支持两种操作：</p>
<ul>
<li>合并（Union）：合并两个元素所属集合（合并对应的树）</li>
<li>查询（Find）：查询某个元素所属集合（查询对应的树的根节点），这可以用于判断两个元素是否属于同一集合</li>
</ul>
<p>并查集在经过修改后可以支持单个元素的删除、移动；使用动态开点线段树还可以实现可持久化并查集。
</p><figure><img alt="Pasted image 20240310165444.png" loading="lazy" width="604" height="521" src="/_astro/pasted-image-20240310165444.BuMka81X_Z7yXpB.webp" /><figcaption>Pasted image 20240310165444.png</figcaption></figure><p></p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>struct</span><span> </span><span>dsu</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>std::</span><span>vector</span><span>&lt;</span><span>int</span><span>&gt; </span><span>pa</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>explicit</span><span> </span><span>dsu</span><span>(</span><span>int</span><span><span> </span><span>size</span><span>) : </span></span><span>pa</span><span>(</span><span>size</span><span>) {</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>std::</span><span>iota</span><span>(</span><span>pa</span><span>.</span><span>begin</span><span>(), </span><span>pa</span><span>.</span><span>end</span><span>(), </span><span>0</span><span>);</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>int</span><span> </span><span>find</span><span>(</span><span>int</span><span><span> </span><span>x</span><span>) {</span></span></div></div><div><div><div>9</div></div><div><span>        </span><span>return</span><span> </span><span>pa</span><span>[</span><span>x</span><span>] </span><span>==</span><span> </span><span>x</span><span> </span><span>?</span><span> </span><span>x</span><span> </span><span>:</span><span> (</span><span>pa</span><span>[</span><span>x</span><span>] </span><span>=</span><span> </span><span>find</span><span>(</span><span>pa</span><span>[</span><span>x</span><span>]));</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>    </span><span>void</span><span> </span><span>unite</span><span>(</span><span>int</span><span><span> </span><span>x</span><span>, </span></span><span>int</span><span><span> </span><span>y</span><span>) {</span></span></div></div><div><div><div>13</div></div><div><span>        </span><span>pa</span><span>[</span><span>find</span><span>(</span><span>x</span><span>)] </span><span>=</span><span> </span><span>find</span><span>(</span><span>y</span><span>);</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>15</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/array-discretization/</id>
      <title type="text">数组离散化</title>
      <published>2024-08-12T00:00:00.000Z</published>
      <updated>2024-08-12T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/array-discretization/"/>
      <summary type="text">数组离散化的三种 C++ 写法：sort+unique、lower_bound 映射与宏封装。</summary>
      <content type="html"><![CDATA[<p>当你需要比较元素的大小，或者节省空间的情况:</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 离散化</span></div></div><div><div><div>2</div></div><div><span>sort</span><span>(</span><span>begin</span><span>(</span><span>v_sorted</span><span>), </span><span>end</span><span>(</span><span>v_sorted</span><span>));</span></div></div><div><div><div>3</div></div><div><span>v_sorted</span><span>.</span><span>erase</span><span>(</span><span>unique</span><span>(</span><span>begin</span><span>(</span><span>v_sorted</span><span>), </span><span>end</span><span>(</span><span>v_sorted</span><span>)), </span><span>end</span><span>(</span><span>v_sorted</span><span>));</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>// 映射离散化索引（1-based index）</span></div></div><div><div><div>6</div></div><div><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>0</span><span>; </span><span>i</span><span> </span><span>&lt;</span><span> (</span><span>int</span><span>)</span><span>v_sorted</span><span>.</span><span>size</span><span>(); </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>7</div></div><div><span>  </span><span>index_map</span><span>[</span><span>v_sorted</span><span>[</span><span>i</span><span>]] </span><span>=</span><span> </span><span>i</span><span> </span><span>+</span><span> </span><span>1</span><span>;</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vector</span><span>&lt;</span><span>ll</span><span>&gt; </span><span>arr</span><span>(</span><span>n</span><span> </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>2</div></div><div><span>vector</span><span>&lt;</span><span>ll</span><span>&gt; </span><span>sorted_arr</span><span>(</span><span>n</span><span> </span><span>+</span><span> </span><span>1</span><span>);</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>for</span><span> (</span><span>int</span><span> </span><span>i</span><span> </span><span>=</span><span> </span><span>1</span><span>; </span><span>i</span><span> </span><span>&lt;=</span><span> </span><span>n</span><span>; </span><span>i</span><span>++</span><span>) {</span></div></div><div><div><div>5</div></div><div><span>  </span><span>cin</span><span> </span><span>&gt;&gt;</span><span> </span><span>arr</span><span>[</span><span>i</span><span>];</span></div></div><div><div><div>6</div></div><div><span>  </span><span>sorted_arr</span><span>[</span><span>i</span><span>] </span><span>=</span><span> </span><span>arr</span><span>[</span><span>i</span><span>];</span><span>  // 复制数组用于排序</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>sort</span><span>(</span><span>next</span><span>(</span><span>begin</span><span>(</span><span>sorted_arr</span><span>)), </span><span>end</span><span>(</span><span>sorted_arr</span><span>));</span><span> // 仅排序副本</span></div></div><div><div><div>10</div></div><div><span>auto</span><span> </span><span>f</span><span> </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>ll</span><span><span> </span><span>x</span><span>) {</span></span></div></div><div><div><div>11</div></div><div><span>  </span><span>return</span><span> </span><span>lower_bound</span><span>(</span><span>next</span><span>(</span><span>begin</span><span>(</span><span>sorted_arr</span><span>)), </span><span>end</span><span>(</span><span>sorted_arr</span><span>), </span><span>x</span><span>) </span><span>-</span><span> </span><span>sorted_arr</span><span>.</span><span>begin</span><span>();</span></div></div><div><div><div>12</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#define</span><span> </span><span>all</span><span><span>(</span><span>x</span><span>) </span></span><span>x</span><span>.</span><span>begin</span><span>(), </span><span>x</span><span>.</span><span>end</span><span>()</span></div></div><div><div><div>2</div></div><div><span>#define</span><span> </span><span>unq_all</span><span><span>(</span><span>x</span><span>) </span></span><span>x</span><span>.</span><span>erase</span><span>(</span><span>unique</span><span>(</span><span>all</span><span>(</span><span>x</span><span>)), </span><span>x</span><span>.</span><span>end</span><span>())</span></div></div><div><div><div>3</div></div><div><span>#define</span><span> </span><span>sort_all</span><span><span>(</span><span>x</span><span>) </span></span><span>sort</span><span>(</span><span>all</span><span>(</span><span>x</span><span>))</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>vector</span><span>&lt;</span><span>ll</span><span>&gt;</span><span> </span><span>g</span><span> </span><span>=</span><span> </span><span>v</span><span>;</span></div></div><div><div><div>6</div></div><div><span>sort_all</span><span>(</span><span>v</span><span>);</span></div></div><div><div><div>7</div></div><div><span>unq_all</span><span>(</span><span>v</span><span>);</span></div></div><div><div><div>8</div></div><div><span>auto</span><span> </span><span>get_id</span><span> </span><span>=</span><span> [</span><span>&amp;</span><span>](</span><span>ll</span><span><span> </span><span>x</span><span>) {</span></span></div></div><div><div><div>9</div></div><div><span>  </span><span>return</span><span> </span><span>lower_bound</span><span>(</span><span>g</span><span>.</span><span>begin</span><span>(), </span><span>g</span><span>.</span><span>end</span><span>(), </span><span>x</span><span>) </span><span>-</span><span> </span><span>g</span><span>.</span><span>begin</span><span>() </span><span>+</span><span> </span><span>1</span><span>;</span><span> // 下标从1开始</span></div></div><div><div><div>10</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/vue-component-props/</id>
      <title type="text">Vue 组件属性 Props</title>
      <published>2024-04-15T00:00:00.000Z</published>
      <updated>2024-04-15T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/vue-component-props/"/>
      <summary type="text">Props 父子传值的基础用法、类型校验、默认值与必填约束。</summary>
      <content type="html"><![CDATA[<p>在 Vue 组件中，属性（Props）用于在父组件和子组件之间传递数据。以下是 Vue 组件属性的关键概念：</p>
<section><h3>1. <strong>基础使用</strong><a href="#1-基础使用"><span>#</span></a></h3><p>子组件模板（单文件组件的 <code>&lt;template&gt;</code> 块）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>template</span><span>&gt;</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>&lt;</span><span>div</span><span>&gt;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>&lt;</span><span>p</span><span>&gt;Message: {{ message }}&lt;/</span><span>p</span><span>&gt;</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>5</div></div><div><span>&lt;/</span><span>template</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对应的脚本（Options API）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>default</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>props</span><span>:</span><span> [</span><span>"message"</span><span>], </span><span>// 简单定义 props</span></div></div><div><div><div>3</div></div><div><span>};</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>父组件使用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>ChildComponent</span><span> </span><span>message</span><span>=</span><span>"Hello Vue!"</span><span>/&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h3>2. <strong>类型验证</strong><a href="#2-类型验证"><span>#</span></a></h3><p>Vue 允许为 props 指定数据类型：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>props: {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>message: String,</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>count: Number,</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>isActive: Boolean,</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>items: Array,</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>user: Object,</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>callback: Function</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h3>3. <strong>默认值</strong><a href="#3-默认值"><span>#</span></a></h3><p>如果父组件未传递 prop，可以提供默认值：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>props: {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>count: {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>type: Number,</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>default: 0</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h3>4. <strong>必填</strong><a href="#4-必填"><span>#</span></a></h3><p>如果某个 prop 必须提供：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>props: {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>title: {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>type: String,</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>required: true</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h3>5. <strong>自定义验证</strong><a href="#5-自定义验证"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>props: {</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>rating: {</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>type: Number,</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>validator: value =&gt; value &gt;= 0 &amp;&amp; value &lt;= </span><span>5</span></div></div><div><div><div>5</div></div><div><span>  </span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h3>6. <strong>动态绑定</strong><a href="#6-动态绑定"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>ChildComponent</span><span> :</span><span>message</span><span>=</span><span>"</span><span>parentMessage</span><span>"</span><span> :</span><span>count</span><span>=</span><span>"</span><span>42</span><span>"</span><span>/&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>:message</code> 表示 message 由父组件的数据 <code>parentMessage</code> 绑定，而不是字符串。</p><hr /></section>
<section><h3>7. <strong>非 Prop Attribute</strong><a href="#7-非-prop-attribute"><span>#</span></a></h3><p>如果子组件没有声明某个 prop，Vue 会把它当作普通 HTML 特性传递：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>ChildComponent</span><span> </span><span>class</span><span>=</span><span>"custom-style"</span><span>/&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果 <code>ChildComponent</code> 没有 <code>props: ["class"]</code>，这个 <code>class</code> 会应用到子组件的根元素。</p><hr /></section>
<section><h3>8. <strong>单向数据流</strong><a href="#8-单向数据流"><span>#</span></a></h3><p>Prop 只能在子组件内部 <strong>读取</strong>，不能修改。如果要修改，推荐使用：</p><ul>
<li>
<p><strong>局部数据拷贝</strong>：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>props: ["count"],</span></div></div><div><div><div>2</div></div><div><span>data() {</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>return { localCount: this.count };</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong>$emit 事件通知父组件</strong>：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>button</span><span> @</span><span>click</span><span>=</span><span>"</span><span>$emit</span><span>(</span><span>'update-count'</span><span><span>, </span><span>count</span><span> </span></span><span>+</span><span> </span><span>1</span><span>)</span><span>"</span><span>&gt;Increase&lt;/</span><span>button</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul><hr /></section>
<section><h3>9. <strong>使用 <code>defineProps</code>（Vue 3 Composition API）</strong><a href="#9-使用-definepropsvue-3-composition-api"><span>#</span></a></h3><p>单文件组件 <code>&lt;script setup&gt;</code> 块中：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>defineProps</span><span>({</span></div></div><div><div><div>2</div></div><div><span>  </span><span>message</span><span>:</span><span><span> </span><span>String</span><span>,</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>count</span><span>:</span><span><span> </span><span>Number</span></span></div></div><div><div><div>4</div></div><div><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Vue 组件的属性机制可以帮助父子组件高效通信，同时保持组件的封装性和可复用性。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/vue-lifecycle/</id>
      <title type="text">Vue 生命周期钩子</title>
      <published>2024-04-02T00:00:00.000Z</published>
      <updated>2024-04-02T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/vue-lifecycle/"/>
      <summary type="text">Vue 组件创建、挂载、更新各阶段的生命周期钩子与可操作范围对照。</summary>
      <content type="html"><![CDATA[<section><h3><strong>（1）创建阶段</strong><a href="#1创建阶段"><span>#</span></a></h3><p>组件实例被初始化，但 <strong>DOM 还未渲染</strong>，可以在这阶段进行数据初始化、调用 API 获取数据等操作。</p>

<table><thead><tr><th>钩子</th><th>说明</th></tr></thead><tbody><tr><td><code>beforeCreate()</code></td><td><strong>数据和事件都未初始化</strong>，只能访问 <code>methods</code> 但无法访问 <code>data</code>、<code>computed</code>。</td></tr><tr><td><code>created()</code></td><td><strong>实例创建完成，data 已初始化</strong>，可以进行 <strong>异步请求</strong>，但 <strong>DOM 还未渲染</strong>。</td></tr></tbody></table><hr /></section>
<section><h3><strong>（2）挂载阶段</strong><a href="#2挂载阶段"><span>#</span></a></h3><p>组件被挂载到 <strong>DOM</strong>，此时可以进行 <strong>DOM 操作</strong> 或者 <strong>获取 DOM 元素</strong>。</p>

<table><thead><tr><th>钩子</th><th>说明</th></tr></thead><tbody><tr><td><code>beforeMount()</code></td><td>模板已编译，<strong>但虚拟 DOM 还未挂载到真实 DOM</strong>，可以操作 <code>data</code>，但不能操作 <code>DOM</code>。</td></tr><tr><td><code>mounted()</code></td><td>组件 <strong>已挂载到真实 DOM</strong>，<strong>可操作 DOM、进行 AJAX 请求</strong>。</td></tr></tbody></table><hr /></section>
<section><h3><strong>（3）更新阶段</strong><a href="#3更新阶段"><span>#</span></a></h3><p>当 <code>data</code> 发生变化时，Vue 组件会重新渲染。</p>

<table><thead><tr><th>钩子</th><th>说明</th></tr></thead><tbody><tr><td><code>beforeUpdate()</code></td><td>组件 <strong>更新前</strong> 触发，数据已经改变 <strong>但 DOM 还未更新</strong>。</td></tr><tr><td><code>updated()</code></td><td>组件 <strong>更新后</strong> 触发，<strong>DOM 已更新</strong>，可以执行依赖更新 DOM 的操作。</td></tr></tbody></table></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/vue-v-model/</id>
      <title type="text">Vue v-model 双向数据绑定</title>
      <published>2024-03-18T00:00:00.000Z</published>
      <updated>2024-03-18T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/vue-v-model/"/>
      <summary type="text">v-model 指令在表单控件与组件间同步数据的作用、特点与 Vue 3 自定义绑定。</summary>
      <content type="html"><![CDATA[<section><h3><strong><code>v-model</code> 在 Vue 中的作用</strong><a href="#v-model-在-vue-中的作用"><span>#</span></a></h3><p><code>v-model</code> 是 Vue 提供的<strong>双向数据绑定</strong>指令，用于在表单控件（如 <code>input</code>、<code>textarea</code>、<code>select</code>）和组件之间同步数据。</p><p><strong>特点</strong>：</p><ul>
<li>绑定的数据会随着用户输入自动更新。</li>
<li>适用于 <code>input</code>、<code>textarea</code>、<code>select</code> 等表单控件。</li>
<li>Vue 3 中可以自定义 <code>v-model</code> 绑定的属性。</li>
</ul></section>
<section><h2>本质是语法糖<a href="#本质是语法糖"><span>#</span></a></h2><p>在原生表单元素上，<code>v-model</code> 约等于「value 绑定 + input 事件回写」：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>input</span><span> </span><span>v-model</span><span>=</span><span>"text"</span><span> /&gt;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>&lt;!-- 等价于 --&gt;</span></div></div><div><div><div>4</div></div><div><span>&lt;</span><span>input</span><span> </span><span>:value</span><span>=</span><span>"text"</span><span> </span><span>@input</span><span>=</span><span>"text = $event.target.value"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>修饰符<a href="#修饰符"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!-- lazy：改为 change 事件触发（失焦才同步） --&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>input</span><span> </span><span>v-model.lazy</span><span>=</span><span>"text"</span><span> /&gt;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>&lt;!-- trim：自动去掉首尾空格 --&gt;</span></div></div><div><div><div>5</div></div><div><span>&lt;</span><span>input</span><span> </span><span>v-model.trim</span><span>=</span><span>"text"</span><span> /&gt;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>&lt;!-- number：把输入转为数字 --&gt;</span></div></div><div><div><div>8</div></div><div><span>&lt;</span><span>input</span><span> </span><span>v-model.number</span><span>=</span><span>"age"</span><span> </span><span>type</span><span>=</span><span>"number"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>在组件上使用（Vue 3）<a href="#在组件上使用vue-3"><span>#</span></a></h2><p>组件上的 <code>v-model</code> 默认展开为 <code>modelValue</code> prop 和 <code>update:modelValue</code> 事件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!-- 父组件 --&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>MyInput</span><span> </span><span>v-model</span><span>=</span><span>"keyword"</span><span> /&gt;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>&lt;!-- 等价于 --&gt;</span></div></div><div><div><div>5</div></div><div><span>&lt;</span><span>MyInput</span><span> </span><span>:model-value</span><span>=</span><span>"keyword"</span><span> </span><span>@update:model-value</span><span>=</span><span>"keyword = $event"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 子组件 MyInput</span></div></div><div><div><div>2</div></div><div><span>const</span><span> </span><span>props</span><span> </span><span>=</span><span> </span><span>defineProps</span><span>([</span><span>"modelValue"</span><span>]);</span></div></div><div><div><div>3</div></div><div><span>const</span><span> </span><span>emit</span><span> </span><span>=</span><span> </span><span>defineEmits</span><span>([</span><span>"update:modelValue"</span><span>]);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Vue 3 还支持多个双向绑定，通过参数区分：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>MyForm</span><span> </span><span>v-model:title</span><span>=</span><span>"bookTitle"</span><span> </span><span>v-model:author</span><span>=</span><span>"bookAuthor"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>自定义组件也可以声明 <code>v-model</code> 的 arg、事件名与修饰符（<code>defineModel()</code> 更简洁）。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.81vm3.xyz/posts/vue-v-bind/</id>
      <title type="text">Vue v-bind 动态属性绑定</title>
      <published>2024-03-05T00:00:00.000Z</published>
      <updated>2024-03-05T00:00:00.000Z</updated>
      <author><name>Blume</name></author>
      <link rel="alternate" href="https://blog.81vm3.xyz/posts/vue-v-bind/"/>
      <summary type="text">v-bind 将 JavaScript 表达式绑定到 HTML attribute 的用法。</summary>
      <content type="html"><![CDATA[<p><code>v-bind</code> 是 Vue.js 中用于动态绑定 HTML attribute（属性）的指令。它允许你在模板中将 JavaScript 表达式的值绑定到 HTML 元素的属性上，使页面能够根据数据的变化自动更新。</p>
<section><h2>基本语法<a href="#基本语法"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!-- 完整写法 --&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>a</span><span> </span><span>v-bind:href</span><span>=</span><span>"url"</span><span>&gt;链接&lt;/</span><span>a</span><span>&gt;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>&lt;!-- 缩写：冒号 --&gt;</span></div></div><div><div><div>5</div></div><div><span>&lt;</span><span>a</span><span> </span><span>:href</span><span>=</span><span>"url"</span><span>&gt;链接&lt;/</span><span>a</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>绑定的值可以是任意 JavaScript 表达式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>img</span><span> </span><span>:src</span><span>=</span><span>"baseUrl + '/logo.png'"</span><span> </span><span>:alt</span><span>=</span><span>"title"</span><span> </span><span>:width</span><span>=</span><span>"size * 2"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>绑定 class 与 style<a href="#绑定-class-与-style"><span>#</span></a></h2><p>这是 <code>v-bind</code> 最常用的场景之一，支持对象语法和数组语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!-- 对象语法：条件为真时应用该 class --&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>div</span><span> </span><span>:class</span><span>=</span><span>"{ active: isActive, 'text-red': hasError }"</span><span>&gt;&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>&lt;!-- 数组语法 --&gt;</span></div></div><div><div><div>5</div></div><div><span>&lt;</span><span>div</span><span> </span><span>:class</span><span>=</span><span>"[baseClass, isActive ? 'active' : '']"</span><span>&gt;&lt;/</span><span>div</span><span>&gt;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>&lt;!-- style 对象语法 --&gt;</span></div></div><div><div><div>8</div></div><div><span>&lt;</span><span>div</span><span> </span><span>:style</span><span>=</span><span>"{ color: textColor, fontSize: fontSize + 'px' }"</span><span>&gt;&lt;/</span><span>div</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>动态 attribute 名<a href="#动态-attribute-名"><span>#</span></a></h2><p>Vue 3 支持用方括号把 attribute 名也变成表达式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>a</span><span> </span><span>:[attributeName]</span><span>=</span><span>"url"</span><span>&gt;这里的 attributeName 可以是动态的&lt;/</span><span>a</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>与组件 props 的关系<a href="#与组件-props-的关系"><span>#</span></a></h2><p>在组件上使用 <code>:prop-name="value"</code> 时，<code>v-bind</code> 就是把父组件的数据传给子组件的 prop。配合 <code>v-bind="obj"</code> 还可以一次性把整个对象展开为多个 props：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>UserCard</span><span> </span><span>v-bind</span><span>=</span><span>"userFields"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>一句话总结：插值表达式负责把值写进文本，<code>v-bind</code> 负责把值写进 attribute。</p></section>]]></content>
    </entry>
</feed>
