<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Java on 小盒子的技术分享</title><link>https://xiaobox.github.io/tags/java/</link><description>Recent content in Java on 小盒子的技术分享</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 27 Feb 2026 23:00:00 +0000</lastBuildDate><atom:link href="https://xiaobox.github.io/tags/java/index.xml" rel="self" type="application/rss+xml"/><item><title>一文讲透 GoF 的 23 种设计模式之工厂方法</title><link>https://xiaobox.github.io/p/2026-02-27-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-gong-chan/</link><pubDate>Fri, 27 Feb 2026 23:00:00 +0000</pubDate><guid>https://xiaobox.github.io/p/2026-02-27-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-gong-chan/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-02-27-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-gong-chan/cover.jpg" alt="Featured image of post 一文讲透 GoF 的 23 种设计模式之工厂方法" /&gt;&lt;h1 id="一文讲透-gof-的-23-种设计模式之工厂方法"&gt;&lt;a href="#%e4%b8%80%e6%96%87%e8%ae%b2%e9%80%8f-gof-%e7%9a%84-23-%e7%a7%8d%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b9%8b%e5%b7%a5%e5%8e%82%e6%96%b9%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;一文讲透 GoF 的 23 种设计模式之工厂方法
&lt;/h1&gt;&lt;p&gt;工厂方法（Factory Method） 是创建型模式&lt;/p&gt;
&lt;h2 id="定义"&gt;&lt;a href="#%e5%ae%9a%e4%b9%89" class="header-anchor"&gt;&lt;/a&gt;定义
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;用一句话概括工厂方法模式：定义一个用于创建对象的接口，让子类决定实例化哪一个类。 它让类的实例化推迟到了子类。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-02-27-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-gong-chan/001-00e336ad.png"&gt;&lt;/p&gt;
&lt;h2 id="简单工厂"&gt;&lt;a href="#%e7%ae%80%e5%8d%95%e5%b7%a5%e5%8e%82" class="header-anchor"&gt;&lt;/a&gt;简单工厂
&lt;/h2&gt;&lt;p&gt;了解工厂方法模式前，我们先了解下简单工厂，既然叫简单工厂，那自然很 “简单”。&lt;/p&gt;
&lt;p&gt;它的核心思想非常直接：专门定义一个类（包揽大权），通过接收不同的参数，用 switch 或 if-else 来决定创建并返回哪一种具体的产品实例。&lt;/p&gt;
&lt;p&gt;假设我们在开发一个 AI 应用，需要根据不同场景创建不同类型的 AI Agent（比如负责对话的 Agent，和负责处理数据的 Agent）。&lt;/p&gt;
&lt;h3 id="第一步定义产品的共同接口和具体实现"&gt;&lt;a href="#%e7%ac%ac%e4%b8%80%e6%ad%a5%e5%ae%9a%e4%b9%89%e4%ba%a7%e5%93%81%e7%9a%84%e5%85%b1%e5%90%8c%e6%8e%a5%e5%8f%a3%e5%92%8c%e5%85%b7%e4%bd%93%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;第一步：定义产品的共同接口和具体实现
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;⚡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java片段&lt;/span&gt;&lt;span class="c1"&gt;// 1. 抽象产品&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;AIAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;voidexecuteTask&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 2. 具体产品 A：聊天助理&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;publicclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ChatAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AIAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publicvoidexecuteTask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ChatAgent: 正在与用户进行自然语言对话...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 2. 具体产品 B：数据分析助理&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;publicclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DataAnalysisAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AIAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publicvoidexecuteTask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DataAnalysisAgent: 正在提取并分析核心数据...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="第二步创建简单工厂类"&gt;&lt;a href="#%e7%ac%ac%e4%ba%8c%e6%ad%a5%e5%88%9b%e5%bb%ba%e7%ae%80%e5%8d%95%e5%b7%a5%e5%8e%82%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;第二步：创建“简单工厂”类
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;⚡ java片段// 3. 简单工厂类 (通常使用静态方法)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;publicclass AIAgentFactory {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; // 根据传入的类型参数，决定实例化哪个具体的 Agent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; publicstatic AIAgent createAgent(String agentType) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; if (&amp;#34;chat&amp;#34;.equalsIgnoreCase(agentType)) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; return new ChatAgent();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; } elseif (&amp;#34;data&amp;#34;.equalsIgnoreCase(agentType)) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; return new DataAnalysisAgent();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; } else {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; throw new IllegalArgumentException(&amp;#34;未知的 Agent 类型: &amp;#34; + agentType);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="第三步客户端调用"&gt;&lt;a href="#%e7%ac%ac%e4%b8%89%e6%ad%a5%e5%ae%a2%e6%88%b7%e7%ab%af%e8%b0%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;第三步：客户端调用
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;⚡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java片段public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 客户端不需要知道 ChatAgent 和 DataAnalysisAgent 是怎么被 new 出来的&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 只需要告诉工厂：“给我一个 chat 类型的 Agent”&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AIAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;agent1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AIAgentFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;chat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;agent1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeTask&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AIAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;agent2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AIAgentFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;agent2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeTask&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;结合代码，我们可以很直观地看到它的特点：&lt;/p&gt;
&lt;p&gt;●优点（省事、解耦）：客户端彻底和具体的实现类解耦了。你不需要在业务代码里到处写 new ChatAgent()，把“创建对象”的脏活累活全交给了工厂。&lt;/p&gt;
&lt;p&gt;●缺点（牵一发而动全身）：它严重违反了“开闭原则”（对扩展开放，对修改关闭）。假设我们现在要引入一个新的 CodingAgent（写代码助手），除了要新建产品类，你必须去修改 AIAgentFactory 里面的 if-else 代码。一旦产品种类极其庞大，这个工厂类就会变得非常臃肿且难以维护。&lt;/p&gt;
&lt;p&gt;正是为了解决简单工厂“违反开闭原则”的这个致命缺点，才演进出了工厂方法模式（把这一个大工厂，拆成了一个个不用改代码、只需新增的具体小工厂）。&lt;/p&gt;
&lt;h2 id="工厂方法模式的结构与角色"&gt;&lt;a href="#%e5%b7%a5%e5%8e%82%e6%96%b9%e6%b3%95%e6%a8%a1%e5%bc%8f%e7%9a%84%e7%bb%93%e6%9e%84%e4%b8%8e%e8%a7%92%e8%89%b2" class="header-anchor"&gt;&lt;/a&gt;工厂方法模式的结构与角色
&lt;/h2&gt;&lt;p&gt;工厂方法模式主要包含四个角色：&lt;/p&gt;
&lt;p&gt;●抽象产品 (Product)：定义产品的统一接口。&lt;/p&gt;
&lt;p&gt;●具体产品 (Concrete Product)：实现抽象产品接口的具体类。&lt;/p&gt;
&lt;p&gt;●抽象工厂 (Creator)：声明返回产品对象的工厂方法。&lt;/p&gt;
&lt;p&gt;●具体工厂 (Concrete Creator)：重写工厂方法，返回具体的实例化产品&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-02-27-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-gong-chan/002-5fb65f39.png"&gt;&lt;/p&gt;
&lt;h2 id="java-代码实现"&gt;&lt;a href="#java-%e4%bb%a3%e7%a0%81%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;Java 代码实现
&lt;/h2&gt;&lt;h3 id="1-定义产品大模型客户端"&gt;&lt;a href="#1-%e5%ae%9a%e4%b9%89%e4%ba%a7%e5%93%81%e5%a4%a7%e6%a8%a1%e5%9e%8b%e5%ae%a2%e6%88%b7%e7%ab%af" class="header-anchor"&gt;&lt;/a&gt;1. 定义产品（大模型客户端）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;⚡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java片段&lt;/span&gt;&lt;span class="c1"&gt;// 抽象产品：统一的大模型调用接口&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;LLMClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 具体产品 A：Claude 客户端&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;publicclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ClaudeClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLMClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;modelVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publicClaudeClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;modelVersion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;modelVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;modelVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;[Claude &amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;modelVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;] 思考并返回结果...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 具体产品 B：OpenAI 客户端&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;publicclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpenAIClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLMClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publicOpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;[OpenAI API] 处理输入并返回结果...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-定义创建者核心业务骨架--工厂方法"&gt;&lt;a href="#2-%e5%ae%9a%e4%b9%89%e5%88%9b%e5%bb%ba%e8%80%85%e6%a0%b8%e5%bf%83%e4%b8%9a%e5%8a%a1%e9%aa%a8%e6%9e%b6--%e5%b7%a5%e5%8e%82%e6%96%b9%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;2. 定义创建者（核心：业务骨架 + 工厂方法）
&lt;/h3&gt;&lt;p&gt;这里是关键：AgentWorkflow 不是一个纯粹的“工厂类”，它是业务类，工厂方法只是它的一部分。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;⚡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java片段&lt;/span&gt;&lt;span class="c1"&gt;// 抽象创建者：Agent 工作流骨架&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgentWorkflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 核心业务逻辑：定义了标准的处理流程（这其实也是个模板方法）&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publicvoidprocessTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;taskContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;=== 1. 解析任务上下文，提取关键信息 ===&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 【灵魂所在】：这里调用工厂方法，拿到一个产品对象。&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 父类在此刻完全不知道自己拿到的是 Claude 还是 OpenAI。&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLMClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;createLLMClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;=== 2. 请求大模型进行推理 ===&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taskContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;=== 3. 结果后处理并落库 ===\n&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 【工厂方法】：将实例化具体产品的职责，推迟到子类去实现&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLMClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createLLMClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-定义具体创建者子类重写工厂方法"&gt;&lt;a href="#3-%e5%ae%9a%e4%b9%89%e5%85%b7%e4%bd%93%e5%88%9b%e5%bb%ba%e8%80%85%e5%ad%90%e7%b1%bb%e9%87%8d%e5%86%99%e5%b7%a5%e5%8e%82%e6%96%b9%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;3. 定义具体创建者（子类重写工厂方法）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;⚡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java片段&lt;/span&gt;&lt;span class="c1"&gt;// 具体创建者 A：基于 Claude 的工作流&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;publicclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ClaudeAgentWorkflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AgentWorkflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLMClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createLLMClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 这里封装 Claude 特有的复杂初始化逻辑（比如加载凭证、设置代理等）&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34; -&amp;gt; [工厂方法] 正在初始化 Claude 客户端环境...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ClaudeClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;3.5-Sonnet&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 具体创建者 B：基于 OpenAI 的工作流&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;publicclass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpenAIAgentWorkflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AgentWorkflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;protected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LLMClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createLLMClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34; -&amp;gt; [工厂方法] 正在构建 OpenAI 客户端环境...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://api.openai.com/v1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-客户端调用"&gt;&lt;a href="#4-%e5%ae%a2%e6%88%b7%e7%ab%af%e8%b0%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;4. 客户端调用
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;⚡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java片段public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publicstaticvoidmain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;编写一段 Python Web 框架对比报告&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 场景 1：启动基于 Claude 的 Agent 工作流&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AgentWorkflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;claudeWorkflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ClaudeAgentWorkflow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;claudeWorkflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 场景 2：切换为基于 OpenAI 的 Agent 工作流&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AgentWorkflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;openaiWorkflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OpenAIAgentWorkflow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;openaiWorkflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果你回看之前的例子，你会发现这个 Demo 解决了一个架构设计上的核心痛点：控制反转 (IoC) 的雏形。&lt;/p&gt;
&lt;p&gt;在 AgentWorkflow 这个父类中，业务主流程已经被彻底固化并复用（processTask 方法）。如果在未来，业务需求要求你接入一个全新的本地开源模型（比如 DeepSeek），你不需要修改任何现有的主流程代码，只需要：&lt;/p&gt;
&lt;p&gt;●新建一个 DeepSeekClient（实现 LLMClient）。&lt;/p&gt;
&lt;p&gt;●新建一个 DeepSeekAgentWorkflow，重写 createLLMClient() 方法返回这个新 Client。&lt;/p&gt;
&lt;p&gt;这才是工厂方法模式真正强大的地方：它是为了让高层模块（业务骨架）能够独立于底层模块（具体产品）的创建而存在，从而支撑起大型框架的扩展性。 JDK 里的 Iterable 接口和它的 iterator() 方法，本质上就是这种工厂方法模式的经典体现。&lt;/p&gt;
&lt;h2 id="什么时候用"&gt;&lt;a href="#%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;什么时候用?
&lt;/h2&gt;&lt;p&gt;●你写的“父类流程”需要创建某种对象，但父类不该/不想知道具体类是谁（框架留扩展点的典型方式）。&lt;/p&gt;
&lt;p&gt;●你希望通过继承覆写来扩展“产物类型”，让调用方不动、流程不动。&lt;/p&gt;
&lt;p&gt;一些具体的场景：&lt;/p&gt;
&lt;p&gt;●框架扩展点：工厂方法很常见于“框架规定流程、业务方覆写创建”的场景（你写子类接入框架）。&lt;/p&gt;
&lt;p&gt;●Spring 的 FactoryBean：它的语义就是“这个 bean 不是普通 bean，而是用来生产另一个对象的”，并且暴露的是 getObject() 创建出来的对象。&lt;/p&gt;
&lt;p&gt;●Java ServiceLoader：通过 SPI 在运行时发现/加载实现类，属于“把具体实现延迟到运行时配置/部署”的一类机制，和“解耦创建与使用”的目标一致。&lt;/p&gt;
&lt;h2 id="注意模式的命名"&gt;&lt;a href="#%e6%b3%a8%e6%84%8f%e6%a8%a1%e5%bc%8f%e7%9a%84%e5%91%bd%e5%90%8d" class="header-anchor"&gt;&lt;/a&gt;注意模式的命名
&lt;/h2&gt;&lt;p&gt;我们回头看一下这个模式为什么叫 &lt;strong&gt;Factory Method&lt;/strong&gt;，而不是干脆叫 Factory ? 这个命名是有讲究的。&lt;/p&gt;
&lt;p&gt;核心原因在于：这个模式的灵魂是一个“方法”，而不是一个“类”。&lt;/p&gt;
&lt;p&gt;1.“工厂 (Factory)”是一个通俗的广义概念：&lt;/p&gt;
&lt;p&gt;在日常沟通中，只要一个类的主要职责是造对象，我们都叫它工厂（比如前面提过的“简单工厂”，它就是一个充斥着 if-else 的具体类）。&lt;/p&gt;
&lt;p&gt;2.“工厂方法 (Factory Method)”强调的是面向对象中的“多态”与“继承”：&lt;/p&gt;
&lt;p&gt;在 GoF 的定义中，创建对象的逻辑并不是封装在一个独立的、包揽大权的“工厂类”里，而是定义在了一个普通业务类（Creator）的内部，作为一个抽象方法存在。&lt;/p&gt;
&lt;p&gt;●这个模式的精髓是：父类定义业务骨架，把其中“需要实例化具体对象”的那一步，挖空成一个方法（也就是 Factory Method）。&lt;/p&gt;
&lt;p&gt;●具体的实例化工作，&lt;strong&gt;推迟（Defer）到了子类去重写这个方法&lt;/strong&gt;来实现&lt;/p&gt;</description></item><item><title>一文讲透 GoF 的 23 种设计模式之单例</title><link>https://xiaobox.github.io/p/2026-02-25-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-dan-li/</link><pubDate>Wed, 25 Feb 2026 10:13:49 +0000</pubDate><guid>https://xiaobox.github.io/p/2026-02-25-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-dan-li/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-02-25-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-dan-li/cover.jpg" alt="Featured image of post 一文讲透 GoF 的 23 种设计模式之单例" /&gt;&lt;h1 id="一文讲透-gof-的-23-种设计模式之单例"&gt;&lt;a href="#%e4%b8%80%e6%96%87%e8%ae%b2%e9%80%8f-gof-%e7%9a%84-23-%e7%a7%8d%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e4%b9%8b%e5%8d%95%e4%be%8b" class="header-anchor"&gt;&lt;/a&gt;一文讲透 GoF 的 23 种设计模式之单例
&lt;/h1&gt;&lt;p&gt;单例模式&amp;ndash;Singleton 是创建型模式&lt;/p&gt;
&lt;h2 id="定义"&gt;&lt;a href="#%e5%ae%9a%e4%b9%89" class="header-anchor"&gt;&lt;/a&gt;定义
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;确保一个类在一个 JVM 内只有一个实例，并提供全局访问点&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-02-25-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-dan-li/001-d2d7932d.png"&gt;&lt;/p&gt;
&lt;h2 id="什么时候用"&gt;&lt;a href="#%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;什么时候用?
&lt;/h2&gt;&lt;p&gt;●配置中心、缓存管理器、日志器（有时）&lt;/p&gt;
&lt;p&gt;●需要全局共享状态/资源&lt;/p&gt;
&lt;p&gt;对于 那些初始化很贵，重复创建又特别浪费资源的场景非常合适 。&lt;/p&gt;
&lt;h2 id="不要滥用"&gt;&lt;a href="#%e4%b8%8d%e8%a6%81%e6%bb%a5%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;不要滥用
&lt;/h2&gt;&lt;p&gt;单例本质是“全局变量 + 访问入口”，会增加耦合、影响测试&lt;/p&gt;
&lt;h2 id="实现方式"&gt;&lt;a href="#%e5%ae%9e%e7%8e%b0%e6%96%b9%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;实现方式
&lt;/h2&gt;&lt;p&gt;以下为常见的 5 种实现方式对比。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;实现方式&lt;/th&gt;
 &lt;th&gt;核心机制简述&lt;/th&gt;
 &lt;th&gt;并发安全性 (线程安全)&lt;/th&gt;
 &lt;th&gt;性能表现&lt;/th&gt;
 &lt;th&gt;核心易错点 / 致命缺陷&lt;/th&gt;
 &lt;th&gt;综合推荐度&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1. 饿汉式(Eager)&lt;/td&gt;
 &lt;td&gt;类加载时立即创建静态 final 实例。&lt;/td&gt;
 &lt;td&gt;安全(JVM类加载机制保证)&lt;/td&gt;
 &lt;td&gt;高 (运行时)获取实例无锁。但可能会拖慢系统启动速度，且如果不用会浪费内存。&lt;/td&gt;
 &lt;td&gt;低实现简单，不易出错。缺点是无法进行懒加载，且难以传递动态参数进行初始化。&lt;/td&gt;
 &lt;td&gt;⭐⭐⭐&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2. 懒汉式(同步方法)&lt;/td&gt;
 &lt;td&gt;在 getInstance 方法上加 synchronized 锁。&lt;/td&gt;
 &lt;td&gt;安全(粗粒度锁保证)&lt;/td&gt;
 &lt;td&gt;非常低每次调用 getInstance 都要发生线程竞争和锁获取，高并发下是严重的性能瓶颈。&lt;/td&gt;
 &lt;td&gt;低实现简单。主要的&amp;quot;错&amp;quot;是选择了这种低效的方案。&lt;/td&gt;
 &lt;td&gt;⭐&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3. 双重检查锁(DCL)&lt;/td&gt;
 &lt;td&gt;两次判空 + 同步代码块 + volatile 关键字。&lt;/td&gt;
 &lt;td&gt;安全 (有前提)必须在实例变量上加 volatile 禁止指令重排序。&lt;/td&gt;
 &lt;td&gt;高只在第一次初始化时加锁，后续调用无锁。实现了高性能的懒加载。&lt;/td&gt;
 &lt;td&gt;极高 (致命)最常见的错误是忘记加 volatile 关键字。这会导致多线程环境下，某个线程可能会拿到一个&amp;quot;半初始化&amp;quot;的对象，引发难以排查的 Bug。&lt;/td&gt;
 &lt;td&gt;⭐⭐⭐&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4. 静态内部类(Holder模式)&lt;/td&gt;
 &lt;td&gt;利用 JVM 加载外部类时不加载静态内部类的特性实现懒加载。&lt;/td&gt;
 &lt;td&gt;安全(JVM类加载机制保证)&lt;/td&gt;
 &lt;td&gt;高既实现了懒加载，又在获取实例时没有任何锁机制，性能优异。&lt;/td&gt;
 &lt;td&gt;低非常规整的写法。唯一需要注意的是要确保构造函数私有，防止外部意外实例化。&lt;/td&gt;
 &lt;td&gt;⭐⭐⭐⭐⭐ (手动实现首选)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5. 枚举(Enum)&lt;/td&gt;
 &lt;td&gt;利用 Java 枚举类型的特殊语法和底层实现。&lt;/td&gt;
 &lt;td&gt;安全 (天然)(JVM 层面保障，防御反射和序列化攻击)&lt;/td&gt;
 &lt;td&gt;高类似于饿汉式，类加载时完成初始化，运行时无锁。&lt;/td&gt;
 &lt;td&gt;极低代码最简洁，几乎不可能写错。缺点是无法继承其他类，且在语义上用来做复杂业务对象时显得突兀。&lt;/td&gt;
 &lt;td&gt;⭐⭐⭐⭐⭐ (最安全简洁)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;重点说明两种实现方式：枚举和静态内部类。&lt;/p&gt;
&lt;h3 id="枚举"&gt;&lt;a href="#%e6%9e%9a%e4%b8%be" class="header-anchor"&gt;&lt;/a&gt;枚举
&lt;/h3&gt;&lt;p&gt;这是 Java 最简洁实现。Java 的 Enum 在语言层面有一些特殊保证（例如不会被克隆），这也是它常被用来实现单例的原因之一。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;⚡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java片段public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INSTANCE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;prod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getEnv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;setEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INSTANCE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INSTANCE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEnv&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用枚举（enum）来实现单例模式，被《Effective Java》的作者 Joshua Bloch 称为 &lt;strong&gt;“实现单例模式的最佳方法”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它之所以备受推崇，是因为它用极其简洁的代码，完美解决了传统单例模式面临的线程安全、序列化破坏和反射破坏三大难题&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理一：利用 JVM 类加载机制保证“线程安全”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在传统的懒汉式单例中，为了保证多线程下只创建一个实例，我们需要写复杂的“双重检查锁（Double-Checked Locking）”并加上 volatile 关键字。&lt;/p&gt;
&lt;p&gt;而枚举怎么做的？&lt;/p&gt;
&lt;p&gt;当你定义 INSTANCE 时，编译器底层实际会把它转化为类似这样的代码：&lt;/p&gt;
&lt;p&gt;⚡ java片段&lt;code&gt;public static final AppConfig INSTANCE = new AppConfig();&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Java 虚拟机（JVM）在加载类的时候，会利用底层的类加载机制保证静态成员的初始化是绝对线程安全的。在这个类被加载到内存时，JVM 会自动实例化 INSTANCE 且只实例化一次，整个过程由 JVM 内部加锁保证同步，不需要你手动写任何并发控制代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理二：天生防御“反射攻击”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;传统的单例模式有一个致命弱点：恶意代码可以通过 Java 的反射机制（Reflection）把私有构造函数设置为可见（setAccessible(true)），从而强行 new 出新的实例，打破单例。&lt;/p&gt;
&lt;p&gt;而枚举怎么做的？&lt;/p&gt;
&lt;p&gt;Java 的反射 API 从源码级别就直接“封杀”了通过反射创建枚举实例的可能性。如果你去看 Constructor.newInstance() 的 Java 底层源码，会发现有一段明确的校验逻辑：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;⚡&lt;/span&gt; &lt;span class="n"&gt;java片段if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;clazz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getModifiers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;Modifier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENUM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="n"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Cannot reflectively create enum objects&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也就是说，&lt;strong&gt;一旦 JVM 发现你要用反射去创建枚举类的对象，就会直接抛出异常&lt;/strong&gt;，从根本上杜绝了反射攻击。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理三：天生防御“序列化破坏”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;传统的单例对象如果实现了 Serializable 接口，在进行网络传输或持久化到磁盘再反序列化读取回来时，默认会重新分配内存，生成一个全新的对象。传统做法是必须手动写一个 readResolve() 方法来返回原实例。&lt;/p&gt;
&lt;p&gt;而枚举怎么做的？&lt;/p&gt;
&lt;p&gt;Java 规范对枚举的序列化有特殊的规定。枚举在序列化的时候，仅仅是将枚举常量的名称（name）输出到了结果中；在反序列化的时候，Java 会调用 java.lang.Enum.valueOf() 方法，通过名字去查找并返回内存中已经存在的那个常量对象。&lt;/p&gt;
&lt;p&gt;因此，无论你反序列化多少次，拿到的永远是内存里的同一个 INSTANCE 对象。&lt;/p&gt;
&lt;p&gt;总结来说：枚举单例的核心原理就是 &lt;strong&gt;直接利用 Java 语言底层的机制&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;●用 JVM 类加载机制 搞定了线程安全。&lt;/p&gt;
&lt;p&gt;●用 反射 API 的硬编码拦截 搞定了反射破坏。&lt;/p&gt;
&lt;p&gt;●用 特殊的名称匹配机制 搞定了序列化破坏。&lt;/p&gt;
&lt;p&gt;在理论上，枚举单例确实是“最完美”的单例实现；但在实际的工程代码中，它的出场率确实不高。这并不是因为枚举本身有 bug，而是因为它在现代工程架构、面向对象设计理念以及测试友好度上，存在一些不可避免的局限性&lt;/p&gt;
&lt;p&gt;具体来说，有以下几个核心原因：&lt;/p&gt;
&lt;p&gt;1.&lt;strong&gt;现代框架（如 Spring）接管了单例的管理&lt;/strong&gt; 这是最根本的原因。在现代 Java 工程中（尤其是企业级开发），我们几乎不再手动编写任何单例模式了。 我们广泛使用 Spring/Spring Boot 这样的依赖注入（DI）框架。在 Spring 中，你只需要在一个普通的类上加上 @Service、@Component 或 @Configuration 注解，Spring 容器（IoC Container）就会默认将其作为一个单例来管理。框架不仅帮你保证了单例，还能帮你自动注入其他依赖（如数据库连接、其他服务），这比用枚举手写单例要强大、灵活得多。&lt;/p&gt;
&lt;p&gt;2.&lt;strong&gt;违反了“语义”和开发者的直觉&lt;/strong&gt; 代码不仅是给机器运行的，更是给人读的。 枚举的本来语义：代表一组固定的常量集合（如星期、颜色、订单状态）。单例的语义：通常是一个拥有复杂业务逻辑的管理类（如 UserManager、DatabaseConnectionPool）。&lt;/p&gt;
&lt;p&gt;如果把一个复杂的业务服务写成 enum，会让接手代码的其他开发者感到困惑，这违反了“最小惊讶原则（Principle of Least Astonishment）”。感觉就像是“为了用单例模式而强行用枚举”。&lt;/p&gt;
&lt;p&gt;3.&lt;strong&gt;面向对象特性的缺失（无法继承）&lt;/strong&gt; Java 规定，所有的枚举类都隐式继承了 java.lang.Enum。因为 Java 不支持多重继承，这意味着你的枚举单例不能再继承任何其他的父类。 如果你的架构需要 AppConfig 继承一个 BaseConfig 类来复用代码，枚举单例直接就做不到。 虽然枚举可以实现接口（implements Interface），但在需要共享基类代码的场景下，它的表现非常无力。&lt;/p&gt;
&lt;p&gt;4.&lt;strong&gt;传参初始化非常困难&lt;/strong&gt; 在工程实践中，单例对象在初始化时往往需要外部参数。比如，一个数据库连接池单例，在启动时需要读取配置文件里的 url 和 password。 普通的单例模式或 Spring 管理的 Bean，可以在运行时读取配置后，再进行初始化。 枚举常量的实例化是在类加载的最早期进行的，这个时候你很难把运行时的参数优雅地传递给枚举的构造函数。&lt;/p&gt;
&lt;p&gt;5.&lt;strong&gt;极难进行单元测试（Mock）&lt;/strong&gt; 在做单元测试时，我们经常需要把某些依赖的单例对象“Mock（模拟）”掉（比如使用 Mockito），以隔离测试环境。 普通类别的单例很容易被 Mock 框架替换。但是，枚举是静态的全局常量，它的生命周期和类加载器绑定。在测试中强行替换枚举实例极其困难，容易导致测试用例之间互相污染。&lt;/p&gt;
&lt;p&gt;在实际工程中：&lt;/p&gt;
&lt;p&gt;●如果你要写一个完全无状态、不需要继承、不依赖外部配置的纯工具类/简单配置类，用枚举单例确实不错。&lt;/p&gt;
&lt;p&gt;●但对于包含业务逻辑、需要依赖注入、需要被测试的类，交给 Spring 等框架去管理才是工业界的最佳实践。&lt;/p&gt;
&lt;h3 id="静态内部类"&gt;&lt;a href="#%e9%9d%99%e6%80%81%e5%86%85%e9%83%a8%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;静态内部类
&lt;/h3&gt;&lt;p&gt;如果你不想用枚举，又想要一个&lt;strong&gt;既能延迟加载（懒汉式），又绝对线程安全，还能完美避开繁琐的加锁（synchronized）&lt;/strong&gt; 的单例，静态内部类是最佳选择。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;⚡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java片段public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseConnectionPool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 1. 私有化构造函数，防止外部 new&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DatabaseConnectionPool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 可选：在这里加上防御反射攻击的代码&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SingletonHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INSTANCE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;不允许通过反射创建单例！&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 2. 核心：定义一个私有的静态内部类&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 这个类直到被调用时才会被 JVM 加载&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SingletonHolder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 由 JVM 保证这里的实例化是绝对线程安全的&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DatabaseConnectionPool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INSTANCE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DatabaseConnectionPool&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 3. 提供全局访问点&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DatabaseConnectionPool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 只有在调用这里时，SingletonHolder 才会被加载，从而实例化 INSTANCE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SingletonHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INSTANCE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;为什么它很巧妙？&lt;/p&gt;
&lt;p&gt;●懒加载（Lazy Loading）：当你加载 DatabaseConnectionPool 这个类时，内部类 SingletonHolder 并不会被立刻加载。只有当你真正调用 getInstance() 方法时，内部类才会被加载，对象才会被创建。这就节省了内存。&lt;/p&gt;
&lt;p&gt;●零并发负担：它没有使用任何 synchronized 或者 volatile 关键字。它完全将线程安全的控制权交给了 JVM 底层的类加载机制（JVM 在加载一个类时，会自动加锁保证全局唯一）。&lt;/p&gt;
&lt;h2 id="spring-是如何实现单例的"&gt;&lt;a href="#spring-%e6%98%af%e5%a6%82%e4%bd%95%e5%ae%9e%e7%8e%b0%e5%8d%95%e4%be%8b%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;Spring 是如何实现单例的？
&lt;/h2&gt;&lt;p&gt;Spring 里的单例（Singleton）和我们在《设计模式》书里学到的单例，在概念和实现思路上有很大的不同。&lt;/p&gt;
&lt;p&gt;●传统单例（GoF单例）：保证在一个 JVM（准确地说是类加载器）级别，某个类只有一个实例。类自己控制自己的实例化。&lt;/p&gt;
&lt;p&gt;●Spring 单例：保证在一个 Spring IoC 容器（ApplicationContext）内部，某个指定的 Bean 名称只有一个实例。它是由 Spring 框架来统一管理的。&lt;/p&gt;
&lt;p&gt;Spring 实现单例的核心原理可以概括为：&lt;strong&gt;单例注册表（Singleton Registry）&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="1-核心数据结构concurrenthashmap"&gt;&lt;a href="#1-%e6%a0%b8%e5%bf%83%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84concurrenthashmap" class="header-anchor"&gt;&lt;/a&gt;1. 核心数据结构：ConcurrentHashMap
&lt;/h3&gt;&lt;p&gt;如果你翻开 Spring 的底层源码（DefaultSingletonBeanRegistry 类），你会发现 Spring 管理单例的本质，就是一个大大的缓存 Map：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;⚡ java片段// Spring 源码中的 &amp;#34;一级缓存&amp;#34;，存放所有完全初始化好的单例 Bean
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;private final Map&amp;lt;String, Object&amp;gt; singletonObjects = new ConcurrentHashMap&amp;lt;&amp;gt;(256);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Spring 的单例其实就是把创建好的对象塞进了一个线程安全的 ConcurrentHashMap 里。Key 是 Bean 的名字（通常是类名首字母小写），Value 就是这个类的实例对象。&lt;/p&gt;
&lt;h3 id="2-spring-创建单例的流程"&gt;&lt;a href="#2-spring-%e5%88%9b%e5%bb%ba%e5%8d%95%e4%be%8b%e7%9a%84%e6%b5%81%e7%a8%8b" class="header-anchor"&gt;&lt;/a&gt;2. Spring 创建单例的流程
&lt;/h3&gt;&lt;p&gt;当你在代码里注入一个单例（比如通过 @Autowired），或者调用 context.getBean(&amp;ldquo;myService&amp;rdquo;) 时，Spring 大致会经历以下步骤：&lt;/p&gt;
&lt;p&gt;1.查缓存：Spring 首先会去 singletonObjects 这个 Map 里查，看看有没有叫 &amp;ldquo;myService&amp;rdquo; 的对象。&lt;/p&gt;
&lt;p&gt;2.有则返回：如果 Map 里有，说明已经创建过了，直接把这个对象返回给你。这就是单例的体现。&lt;/p&gt;
&lt;p&gt;3.无则创建并加锁：如果 Map 里没有，Spring 就会准备创建它。为了保证在多线程环境下只有一个线程能去创建这个 Bean，Spring 会对这个 Bean 的名字进行加锁（通常是通过对全局单例集合的锁或者特定的互斥锁来实现同步）。&lt;/p&gt;
&lt;p&gt;4.实例化与初始化：Spring 通过反射调用构造函数把对象 new 出来，然后进行属性填充（依赖注入），再调用 @PostConstruct 等初始化方法。&lt;/p&gt;
&lt;p&gt;5.放入 Map 并返回：最后，把完全准备好的对象放进 singletonObjects 这个 ConcurrentHashMap 里，然后返回给你。以后所有对这个 Bean 的请求，都直接从 Map 里拿。&lt;/p&gt;
&lt;h3 id="3-补充循环依赖的杀手锏三级缓存"&gt;&lt;a href="#3-%e8%a1%a5%e5%85%85%e5%be%aa%e7%8e%af%e4%be%9d%e8%b5%96%e7%9a%84%e6%9d%80%e6%89%8b%e9%94%8f%e4%b8%89%e7%ba%a7%e7%bc%93%e5%ad%98" class="header-anchor"&gt;&lt;/a&gt;3. 补充：循环依赖的杀手锏“三级缓存”
&lt;/h3&gt;&lt;p&gt;Spring 在管理单例时，还要解决一个传统单例很难解决的问题——循环依赖（比如 A 依赖 B，B 又依赖 A）。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，Spring 其实并没有只用一个 Map，而是用了三个 Map（传说中的三级缓存）：&lt;/p&gt;
&lt;p&gt;●一级缓存（singletonObjects）：存完整的、可用的单例对象。&lt;/p&gt;
&lt;p&gt;●二级缓存（earlySingletonObjects）：存半成品对象（刚 new 出来，但还没注入属性的对象），用于提前暴露自己，打破循环。&lt;/p&gt;
&lt;p&gt;●三级缓存（singletonFactories）：存对象工厂，用于在需要时生成代理对象（比如处理 AOP 切面）。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-02-25-yi-wen-jiang-tou-gof-de-23-zhong-she-ji-mo-shi-zhi-dan-li/002-5ae19680.svg"&gt;&lt;/p&gt;
&lt;p&gt;结合上面的图，核心过程如下：&lt;/p&gt;
&lt;p&gt;第一阶段：A 的创建与曝光&lt;/p&gt;
&lt;p&gt;1.调用 getBean(A)：Spring 容器开始创建 Bean A。&lt;/p&gt;
&lt;p&gt;2.实例化 A：调用构造函数，A 对象在内存中诞生，但属性（如 B）还是 null。&lt;/p&gt;
&lt;p&gt;3.暴露三级缓存：Spring 将 A 的工厂对象放入 三级缓存 (singletonFactories)。这是解决循环依赖的关键一步，意味着此时如果有其他对象引用 A，可以通过这个工厂拿到 A 的引用。&lt;/p&gt;
&lt;p&gt;第二阶段：A 填充属性，触发 B 的创建&lt;/p&gt;
&lt;p&gt;4.填充属性 B：A 发现自己依赖 B，于是暂停自己，转而去创建 B。&lt;/p&gt;
&lt;p&gt;第三阶段：B 的创建与获取 A&lt;/p&gt;
&lt;p&gt;5.实例化 B：B 对象诞生，属性（如 A）还是 null。&lt;/p&gt;
&lt;p&gt;6.暴露三级缓存：将 B 的工厂放入三级缓存。&lt;/p&gt;
&lt;p&gt;7.填充属性 A：B 发现自己依赖 A，于是尝试去缓存找 A。&lt;/p&gt;
&lt;p&gt;第四阶段：B 从缓存中找到 A (核心转折)&lt;/p&gt;
&lt;p&gt;8.查找缓存：&lt;/p&gt;
&lt;p&gt;●找一级缓存？没有（A 还没彻底完工）。&lt;/p&gt;
&lt;p&gt;●找二级缓存？没有（还没人提取过 A 的早期引用）。&lt;/p&gt;
&lt;p&gt;●找三级缓存？有了！&lt;/p&gt;
&lt;p&gt;9.升级缓存：&lt;/p&gt;
&lt;p&gt;●B 调用三级缓存中的工厂方法，拿到 A 的早期引用。&lt;/p&gt;
&lt;p&gt;●重点：如果 A 配置了 AOP（比如事务管理），这个工厂会提前生成 A 的代理对象。&lt;/p&gt;
&lt;p&gt;●将 A 的早期引用放入 二级缓存 (earlySingletonObjects)，并从三级缓存移除。&lt;/p&gt;
&lt;p&gt;10.B 完成：B 拿到了 A 的引用，完成属性填充和初始化，放入 一级缓存。&lt;/p&gt;
&lt;p&gt;第五阶段：A 完成&lt;/p&gt;
&lt;p&gt;11.A 获取 B：B 已经创建好了，A 顺利拿到 B 的引用。&lt;/p&gt;
&lt;p&gt;12.A 完成：A 完成属性填充和初始化，放入 一级缓存。&lt;/p&gt;</description></item><item><title>搞懂 ThreadLocal，其实就三件事：它是谁？它在哪？用完它咋办？</title><link>https://xiaobox.github.io/p/2025-08-21-gao-dong-threadlocal-qi-shi-jiu-san-jian-shi-ta-shi-shui-ta/</link><pubDate>Thu, 21 Aug 2025 10:52:24 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-08-21-gao-dong-threadlocal-qi-shi-jiu-san-jian-shi-ta-shi-shui-ta/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-08-21-gao-dong-threadlocal-qi-shi-jiu-san-jian-shi-ta-shi-shui-ta-/cover.jpg" alt="Featured image of post 搞懂 ThreadLocal，其实就三件事：它是谁？它在哪？用完它咋办？" /&gt;&lt;h1 id="缘起"&gt;&lt;a href="#%e7%bc%98%e8%b5%b7" class="header-anchor"&gt;&lt;/a&gt;缘起
&lt;/h1&gt;&lt;p&gt;这两天又用到了 &lt;code&gt;ThreadLocal&lt;/code&gt; ,时间一长，很多细节都想不起来了，现翻源码 😂&lt;/p&gt;
&lt;p&gt;想着干脆写个笔记记录一下，其实之前写过有关 &lt;code&gt;ThreadLocal&lt;/code&gt; 的文章，现在回过头看觉得一些细节写的交待的不好，那么就再写一遍吧。&lt;/p&gt;
&lt;p&gt;这一次的目标就是搞清楚 &lt;strong&gt;Threadlocal 它到底是怎么隔离线程数据的，好在哪？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当然也要挖一挖那个潜在的 &lt;strong&gt;内存泄漏风险&lt;/strong&gt;，看看它在 Java 不同版本里头有没有啥变化。&lt;/p&gt;
&lt;h1 id="threadlocal-它根本上是干嘛的"&gt;&lt;a href="#threadlocal-%e5%ae%83%e6%a0%b9%e6%9c%ac%e4%b8%8a%e6%98%af%e5%b9%b2%e5%98%9b%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;ThreadLocal 它根本上是干嘛的？
&lt;/h1&gt;&lt;p&gt;简单来说， ThreadLocal 就是给每个线程(注意是每个线程) 一个变量的独立副本。&lt;/p&gt;
&lt;p&gt;你可以想象一下，比如说用户 ID 或者一个事务 ID 这种需要跟某个线程绑定的数据，用它就特别合适。&lt;strong&gt;它解决的不是那种多线程怎么共享数据的问题，而是怎么管好单个线程自己用的数据。&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id="反向存储"&gt;&lt;a href="#%e5%8f%8d%e5%90%91%e5%ad%98%e5%82%a8" class="header-anchor"&gt;&lt;/a&gt;反向存储
&lt;/h1&gt;&lt;p&gt;它用了一个挺有意思的设计，有人叫它 “反向存储”&lt;/p&gt;
&lt;p&gt;我根据 ThreadLocal 的内部结构梳理了一个结构图，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;$ terminal┌─────────────────────────────────────────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;│ JVM 堆内存 - 全局共享区域 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;│ ┌─────────────────┐ ┌─────────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;│ │ ThreadLocal1 │ │ ThreadLocal2 │ ← 全局唯一实例 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;│ │ &lt;span class="o"&gt;(&lt;/span&gt;COUNTER&lt;span class="o"&gt;)&lt;/span&gt; │ │ &lt;span class="o"&gt;(&lt;/span&gt;NAME&lt;span class="o"&gt;)&lt;/span&gt; │ 所有线程共享 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;│ └─────────────────┘ └─────────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;└─────────────────────────────────────────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; ↑ ↑
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; │ 作为key引用 │ 作为key引用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;┌─────────────────────────────────────────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;│ 线程独立存储区域 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;│ ┌─────────────────┐ ┌─────────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;│ │ Thread-1 │ │ Thread-2 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;│ │ │ │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;│ │ threadLocals ──┼──┐ ┌──┼── threadLocals │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;│ └─────────────────┘ │ │ └─────────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;│ │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;│ ┌────────────────────▼──┐ ┌──▼────────────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;│ │ ThreadLocalMap-1 │ │ ThreadLocalMap-2 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;│ │ │ │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;│ │ Entry&lt;span class="o"&gt;[]&lt;/span&gt; table │ │ Entry&lt;span class="o"&gt;[]&lt;/span&gt; table │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;│ │ ┌─────────────────┐ │ │ ┌─────────────────┐ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;│ │ │ Entry&lt;span class="o"&gt;[&lt;/span&gt;0&lt;span class="o"&gt;]&lt;/span&gt; │ │ │ │ Entry&lt;span class="o"&gt;[&lt;/span&gt;0&lt;span class="o"&gt;]&lt;/span&gt; │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;│ │ │ key: COUNTER │ │ │ │ key: COUNTER │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;│ │ │ value: &lt;span class="m"&gt;100&lt;/span&gt; │ │ │ │ value: &lt;span class="m"&gt;200&lt;/span&gt; │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;│ │ └─────────────────┘ │ │ └─────────────────┘ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt;│ │ ┌─────────────────┐ │ │ ┌─────────────────┐ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt;│ │ │ Entry&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;]&lt;/span&gt; │ │ │ │ Entry&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;]&lt;/span&gt; │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt;│ │ │ key: NAME │ │ │ │ key: NAME │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt;│ │ │ value:&lt;span class="s2"&gt;&amp;#34;Thread1&amp;#34;&lt;/span&gt; │ │ │ │ value:&lt;span class="s2"&gt;&amp;#34;Thread2&amp;#34;&lt;/span&gt; │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt;│ │ └─────────────────┘ │ │ └─────────────────┘ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;34&lt;/span&gt;&lt;span class="cl"&gt;│ └───────────────────────┘ └───────────────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;35&lt;/span&gt;&lt;span class="cl"&gt;│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;36&lt;/span&gt;&lt;span class="cl"&gt;└─────────────────────────────────────────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从图上可以看到，实际上是每个 &lt;code&gt;Thread&lt;/code&gt; 对象它自己内部持有一个map，这个 map 就是 &lt;code&gt;ThreadLocalMap&lt;/code&gt;，每个线程都有一个。&lt;/p&gt;
&lt;p&gt;我们从源码中也能看到：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terminal&lt;/span&gt;&lt;span class="c1"&gt;// Thread 类的关键字段（简化版）&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;implements&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Runnable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 每个 Thread 实例都有自己独立的这个字段！&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ThreadLocal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ThreadLocalMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;threadLocals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ThreadLocal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ThreadLocalMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inheritableThreadLocals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 其他字段...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后那个 ThreadLocal 变量本身就是我们代码里定义的那个，通常是个 static final 的全局变量。它其实是充当了所有这些不同的 &lt;code&gt;ThreadLocalMap&lt;/code&gt; 里面的 entry 的 key。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;$ terminal// ThreadLocalMap 的关键实现
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;static class ThreadLocalMap &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; // Entry 继承 WeakReference，key 是 ThreadLocal 对象的弱引用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; static class Entry extends WeakReference&amp;lt;ThreadLocal&amp;lt;?&amp;gt;&amp;gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; Object value&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; Entry&lt;span class="o"&gt;(&lt;/span&gt;ThreadLocal&amp;lt;?&amp;gt; k, Object v&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; super&lt;span class="o"&gt;(&lt;/span&gt;k&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; // k 是全局共享的 ThreadLocal 对象
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; v&lt;span class="p"&gt;;&lt;/span&gt; // v 是线程独立的值
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; // 每个 ThreadLocalMap 都有自己独立的 Entry 数组
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; private Entry&lt;span class="o"&gt;[]&lt;/span&gt; table&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; // 构造函数：每个线程调用时都会创建新的实例
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; ThreadLocalMap&lt;span class="o"&gt;(&lt;/span&gt;ThreadLocal&amp;lt;?&amp;gt; firstKey, Object firstValue&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; new Entry&lt;span class="o"&gt;[&lt;/span&gt;INITIAL_CAPACITY&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; int &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; firstKey.threadLocalHashCode &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;INITIAL_CAPACITY - 1&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; table&lt;span class="o"&gt;[&lt;/span&gt;i&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; new Entry&lt;span class="o"&gt;(&lt;/span&gt;firstKey, firstValue&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 1&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; setThreshold&lt;span class="o"&gt;(&lt;/span&gt;INITIAL_CAPACITY&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;就是说 &lt;code&gt;Threadlocal&lt;/code&gt; 这个钥匙是大家都能看到的，是共享的，但是存东西的柜子也就是 &lt;code&gt;ThreadLocalMap&lt;/code&gt; 是每个线程自己的，是互相隔离的。&lt;/p&gt;
&lt;h2 id="内部存储"&gt;&lt;a href="#%e5%86%85%e9%83%a8%e5%ad%98%e5%82%a8" class="header-anchor"&gt;&lt;/a&gt;内部存储
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;这个 ThreadLocalMap 它怎么通过 ThreadLocal 这个键找到对应的值呢？它里面是咋存的？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;实际上它是用 ThreadLocal 实例本身的哈希码，这个哈希码会经过一个计算，然后确定在 map 内部数组里的一个位置。这个计算方法还挺讲究的，用了一个特殊的数字，就是为了让这些键能均匀地散开。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;$ terminal// 构造函数：每个线程调用时都会创建新的实例
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;ThreadLocalMap&lt;span class="o"&gt;(&lt;/span&gt;ThreadLocal&amp;lt;?&amp;gt; firstKey, Object firstValue&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; new Entry&lt;span class="o"&gt;[&lt;/span&gt;INITIAL_CAPACITY&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; int &lt;span class="nv"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; firstKey.threadLocalHashCode &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;INITIAL_CAPACITY - 1&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; table&lt;span class="o"&gt;[&lt;/span&gt;i&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; new Entry&lt;span class="o"&gt;(&lt;/span&gt;firstKey, firstValue&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 1&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt; setThreshold&lt;span class="o"&gt;(&lt;/span&gt;INITIAL_CAPACITY&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;ThreadLocal中有一个属性为HASH_INCREMENT = 0x61c88647。这个值很特殊，它是斐波那契数 也叫 黄金分割数。hash增量为 这个数字，带来的好处就是 hash 分布非常均匀。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要是算出来的位置已经被占了，就是所谓的哈希冲突，它就用一种叫&lt;strong&gt;线性探测&lt;/strong&gt;的方法来解决，简单说就是如果这个位置有人了，他就看下一个位置空不空，再不行就再看下一个，一直找到空位为止。&lt;/p&gt;
&lt;p&gt;因为这个 map 是线程私有的，不存在多个线程同时来抢位置的问题，所以这种简单的方法就够用了，效率也还行。&lt;/p&gt;
&lt;h2 id="隔离机制"&gt;&lt;a href="#%e9%9a%94%e7%a6%bb%e6%9c%ba%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;隔离机制
&lt;/h2&gt;&lt;p&gt;这个 “反向存储” ，有点儿反直觉：不是 ThreadLocal 对象持有多个线程的值，而是 每个 Thread 对象持有自己的 ThreadLocalMap，ThreadLocalMap 以 ThreadLocal 对象为 key，存储该线程的值。&lt;/p&gt;
&lt;p&gt;我们再具体总结一下 ThreadLocal 的隔离机制，实际上它是一个 &amp;ldquo;部分共享，部分独立&amp;rdquo; 的机制。&lt;/p&gt;
&lt;p&gt;1.ThreadLocal 对象全局共享：static final 修饰，JVM 中只有一个实例，所有线程都引用同一个 ThreadLocal 对象&lt;/p&gt;
&lt;p&gt;2.ThreadLocalMap 线程独立：存储在 Thread.threadLocals 字段中，每个 Thread 实例都有自己的 threadLocals 字段，调用 createMap() 时，给不同线程创建不同的 ThreadLocalMap 实例&lt;/p&gt;
&lt;p&gt;3.Entry 数组线程独立：每个 ThreadLocalMap 都有自己的 Entry[] table，虽然 Entry 的 key 都指向同一个 ThreadLocal 对象，但 Entry 对象本身和 value 都是线程独立的&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“ThreadLocal 就是一把“通用钥匙”，它不存东西，而是帮每个线程打开自己的“专属保险箱”。”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这就是 ThreadLocal 精妙设计的核心：通过线程对象的实例字段实现存储隔离，通过全局 ThreadLocal 对象实现访问统一。&lt;/p&gt;
&lt;h1 id="内存泄露风险"&gt;&lt;a href="#%e5%86%85%e5%ad%98%e6%b3%84%e9%9c%b2%e9%a3%8e%e9%99%a9" class="header-anchor"&gt;&lt;/a&gt;内存泄露风险
&lt;/h1&gt;&lt;p&gt;这个风险主要来自于 &lt;code&gt;ThreadLocalMap&lt;/code&gt; 存数据的方式。特别是它的键，这个 map 里面的键也就是咱们那个 &lt;code&gt;ThreadLocal&lt;/code&gt; 对象实例，它不是直接存的，它是被一个叫做 &lt;code&gt;weak reference&lt;/code&gt;，也就是弱引用的东西包了一层&lt;/p&gt;
&lt;h2 id="弱引用"&gt;&lt;a href="#%e5%bc%b1%e5%bc%95%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;弱引用
&lt;/h2&gt;&lt;p&gt;这个弱引用跟我们平时用的那种普通的引用就是强引用有啥不一样？&lt;/p&gt;
&lt;p&gt;咱们平时用的强引用，只要这个引用还在，垃圾回收器（GC） 就不会把那个对象收走，但弱引用不一样， GC 在扫描的时候如果发现一个对象只被弱引用指着，没有强引用指向它了，那 GC 就可以把它回收掉。所以在 &lt;code&gt;ThreadLocalMap&lt;/code&gt; 这里，如果你代码里别的地方不再持有那个 &lt;code&gt;Threadlocal&lt;/code&gt; 实例的强引用了,比如说那个类被卸载了，或者实例变量不再被访问了,类似这种情况，那 GC 就可能把这个 Threadlocal 对象本身回收掉，这时候 map 里面那个 entry 的键就变成了null。&lt;/p&gt;
&lt;h3 id="键没了变成-null-了那不是正好吗"&gt;&lt;a href="#%e9%94%ae%e6%b2%a1%e4%ba%86%e5%8f%98%e6%88%90-null-%e4%ba%86%e9%82%a3%e4%b8%8d%e6%98%af%e6%ad%a3%e5%a5%bd%e5%90%97" class="header-anchor"&gt;&lt;/a&gt;键没了，变成 null 了，那不是正好吗？
&lt;/h3&gt;&lt;p&gt;键没了，变成 null 了，说明这个条目没用了，可以清掉了。那不是正好吗？&lt;/p&gt;
&lt;p&gt;坑就在这，虽然键是弱引用， GC 可能回收它，但是和这个键关联的那个值（value），它是被强引用持有的。持有它就是 Threadlocalmap 里面的那个 entry 对象，这个 entry 对象本身强引用着那个 value。&lt;/p&gt;
&lt;p&gt;我们来捋一下：&lt;strong&gt;Threadlocal 键被弱引用包装，可能被 GC 回收变 null，但存的那个 value 被 entry 对象强引用。 然后这个 entry 对象又被 ThreadLocalMap 持有，ThreadLocalMap 又被那个 Thread 对象持有。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;整个引用链条如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-08-21-gao-dong-threadlocal-qi-shi-jiu-san-jian-shi-ta-shi-shui-ta-/001-9dc9dd97.png"&gt;&lt;/p&gt;
&lt;p&gt;只要这个线程还活着，比如线程池里的线程（它可能活很久，线程池里的线程会复用），然后如果这个线程后续一直没有再调用这个 Threadlocal 的 set、get 或者 remove 方法，这些方法在执行的时候会顺便检查一下清理掉那些键为 null 的entry，但如果一直没调用，那这个键虽然是 null 了，但那个 value 因为被 entry 强引用着，就一直没法被 GC 回收。&lt;strong&gt;这就泄露了。那个 value 对象就一直占着内存，明明逻辑上可能已经没用了。这就是典型的 Threadlocal 内存泄露场景&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="那为啥不干脆把那个-value-也用弱引用呢"&gt;&lt;a href="#%e9%82%a3%e4%b8%ba%e5%95%a5%e4%b8%8d%e5%b9%b2%e8%84%86%e6%8a%8a%e9%82%a3%e4%b8%aa-value-%e4%b9%9f%e7%94%a8%e5%bc%b1%e5%bc%95%e7%94%a8%e5%91%a2" class="header-anchor"&gt;&lt;/a&gt;那为啥不干脆把那个 value 也用弱引用呢？
&lt;/h3&gt;&lt;p&gt;那样的话 Threadlocal 可能就失去意义了。你想啊，那个 value 是线程真正需要的数据，比如一个数据库连接，如果它也是弱引用，那可能在你正用得好好的时候，突然就被 GC 给回收了。那下次去 get 的时候拿到了可能就是 null 了，即使我没 remove 它。那程序可能就出错了。&lt;strong&gt;所以用强引用是为了保证只要线程逻辑上还需要这个值，并且没显示的remove，它就应该一直在。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是在保证数据可用性和自动内存管理之间做了一个权衡，他选择了优先保证数据，但这个选择就把一部分清理的责任甩给了开发者。&lt;/p&gt;
&lt;h3 id="这对开发者意味着什么-"&gt;&lt;a href="#%e8%bf%99%e5%af%b9%e5%bc%80%e5%8f%91%e8%80%85%e6%84%8f%e5%91%b3%e7%9d%80%e4%bb%80%e4%b9%88-" class="header-anchor"&gt;&lt;/a&gt;这对开发者意味着什么 ？
&lt;/h3&gt;&lt;p&gt;意味着你必须养成一个习惯，非常非常重要的习惯，就是在使用完 Threadlocal 变量之后，一定要最好是在 finally 块里头调用那个 Threadlocal 的 remove 方法。&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terminalpublic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConnectionManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connectionHolder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SQLException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connectionHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isClosed&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DriverManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;jdbc:mysql://localhost:3306/test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;root&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;password&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connectionHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;closeConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connectionHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SQLException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connectionHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个 remove 方法会把当前线程的 ThreadLocalMap 里跟这个 Threadlocal 实例对应的那个 entry 整个都删掉，这样那个强引用的 value 自然也就没有引用指向它了，下次 GC 就能把它回收了。&lt;/p&gt;
&lt;p&gt;所以关键就是要手动清理，不能偷懒，不能指望它内部那个自动清理机制，&lt;strong&gt;尤其是在线程池这种线程生命周期可能很长的场景下，依赖自动清理风险很大。&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id="java-新版本"&gt;&lt;a href="#java-%e6%96%b0%e7%89%88%e6%9c%ac" class="header-anchor"&gt;&lt;/a&gt;java 新版本
&lt;/h1&gt;&lt;p&gt;既然 ThreadLocal 有内存泄露的风险，那么后面新的 Java 版本，比如 11、17、21 这些，有没有做些改进，或者提供一些替代方案呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;弱引用键、强引用值这个机制，在后面这些版本里基本没变，但是确实有一些改进和变化。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="java-17"&gt;&lt;a href="#java-17" class="header-anchor"&gt;&lt;/a&gt;java 17
&lt;/h2&gt;&lt;p&gt;Java 17 里针对那个公共的 ForkJoinPool，就是 ForkJoinPool 的 common pool，它增加了一个特性，当池里的任务执行完之后，会自动帮你清理掉那个任务线程用过的所有 Threadlocal 值，为这个特定的池缓解了一下风险。但注意，如果你自己创建的 ForkJoinPool 或者普通的线程池，或者直接创建了线程，那还得你自己负责 remove 。&lt;/p&gt;
&lt;h2 id="java-21"&gt;&lt;a href="#java-21" class="header-anchor"&gt;&lt;/a&gt;java 21
&lt;/h2&gt;&lt;p&gt;Java 21 提到的 scoped values ,目前还是预览特性，它提供了一种不同的方式来共享那些需要跟作用域绑定的数据（比如请求处理过程绑定的数据，特别是不可变数据），它的设计理念就是为了避免 Threadlocal 这种需要手动清理的麻烦，用一种结构化的方式来传递和管理，用完自然就没了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terminalimport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incubator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ScopedValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScopedValueExample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 声明一个 ScopedValue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ScopedValue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ScopedValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 使用 ScopedValue.where 绑定值，并在作用域内运行&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ScopedValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;在作用域内: &amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 作用域结束后，值自动消失，不存在泄漏&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// System.out.println(USER.get()); // 会抛 IllegalStateException&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;子方法依然能获取: &amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;至于虚拟线程，因为它们被设计成非常轻量级，而且通常生命周期很短，用完就丢了，线程没了它关联的 ThreadLocalMap 自然也就没了，所以长期泄露的风险窗口就大大缩短了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terminalpublic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VirtualThreadExample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ThreadLocal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withInitial&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;未设置&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InterruptedException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 使用虚拟线程（JDK 21 已经正式支持）&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vThread&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofVirtual&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;虚拟线程的数据&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentThread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34; -&amp;gt; &amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 不需要手动清理，虚拟线程生命周期很短，用完就结束&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vThread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 这里 vThread 已经结束，对应的 ThreadLocalMap 已自动销毁&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h1&gt;&lt;p&gt;我们来总结一下 ThreadLocal 这个东西。&lt;/p&gt;
&lt;p&gt;它通过每个线程自己私有的 ThreadLocalMap 实现了线程数据的隔离。挺强大的，在很多场景下很有用，但是它那个弱引用键加上强引用值的设计，就像一把双刃剑，带来了内存泄露的风险，特别是用线程池这种长生命周期的线程池，所以最重要的实践就是要记得用完之后一定在 finally 块里手动调用 remove 来清理。&lt;/p&gt;
&lt;p&gt;虽然新版 Java 针对特定场景，比如公共 ForkJoinPool 做了些自动清理，也提供了像 scoped values 这样的潜在替代方案，但总的来说，理解这个机制，并且承担起主动清理的责任，对开发者来说还是很重要的。&lt;/p&gt;</description></item><item><title>从初始化一个现代 python 项目中学习到的东西</title><link>https://xiaobox.github.io/p/2025-04-27-cong-chu-shi-hua-yi-ge-xian-dai-python-xiang-mu-zhong-xue-xi/</link><pubDate>Sun, 27 Apr 2025 06:57:05 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-04-27-cong-chu-shi-hua-yi-ge-xian-dai-python-xiang-mu-zhong-xue-xi/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-27-cong-chu-shi-hua-yi-ge-xian-dai-python-xiang-mu-zhong-xue-xi/cover.jpg" alt="Featured image of post 从初始化一个现代 python 项目中学习到的东西" /&gt;&lt;h2 id="uv"&gt;&lt;a href="#uv" class="header-anchor"&gt;&lt;/a&gt;uv
&lt;/h2&gt;&lt;p&gt;我准备用 uv 初始化一个 python 项目&lt;/p&gt;
&lt;h3 id="环境"&gt;&lt;a href="#%e7%8e%af%e5%a2%83" class="header-anchor"&gt;&lt;/a&gt;环境
&lt;/h3&gt;&lt;p&gt;我用的是苹果笔记本 MacBookPro ，具体的操作系统及硬件参数如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-27-cong-chu-shi-hua-yi-ge-xian-dai-python-xiang-mu-zhong-xue-xi/001-7b50b558.png"&gt;&lt;/p&gt;
&lt;h3 id="uv-的介绍与安装"&gt;&lt;a href="#uv-%e7%9a%84%e4%bb%8b%e7%bb%8d%e4%b8%8e%e5%ae%89%e8%a3%85" class="header-anchor"&gt;&lt;/a&gt;uv 的介绍与安装
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;uv 是一个使用 Rust 编写的工具，可以用来替代 pip、pipenv、pipx、poetry、virtualenv 等工具的使用，甚至还可以用来管理系统中所安装的 Python 发行版。uv 借鉴了很多现代语言中对于项目依赖的管理方式，项目中对于依赖的管理要远远优于使用 pip 和requirements.txt的方式。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;我之前用过 pip 、pipx 等工具，发现 uv 确实要快不少。具体有多快呢？ github 上有个图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-27-cong-chu-shi-hua-yi-ge-xian-dai-python-xiang-mu-zhong-xue-xi/002-23c27ef6.png"&gt;&lt;/p&gt;
&lt;p&gt;🚀速度比传统 pip 快 10-100倍。&lt;/p&gt;
&lt;p&gt;根据官网的介绍，uv 主要支持以下功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持版本锁定的项目依赖管理。&lt;/li&gt;
&lt;li&gt;支持直接运行 Python 脚本。&lt;/li&gt;
&lt;li&gt;支持对系统中安装的 Python 进行管理，支持多版本 Python 共存。&lt;/li&gt;
&lt;li&gt;支持 Python 包的发布和安装。&lt;/li&gt;
&lt;li&gt;支持兼容 pip 的应用接口。&lt;/li&gt;
&lt;li&gt;支持 Cargo 模式的项目工作区管理。&lt;/li&gt;
&lt;li&gt;更优化的全局支持库缓存。&lt;/li&gt;
&lt;li&gt;运行无需 Rust 或者 Python 支持。&lt;/li&gt;
&lt;li&gt;支持 Windows、macOS 和 Linux 系统&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;uv 对多 python 版本和环境的管理很不错，这样你就可以一个项目指定一个特定的 Python 版本，放心使用，想怎么折腾怎么折腾，不会影响全局。&lt;/p&gt;
&lt;p&gt;最近比较火的 MCP 很多也是用 uv 运行的，因为用 uv 命令可以直接运行 python 脚本。&lt;/p&gt;
&lt;p&gt;uv 的安装非常简单：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# macOS和Linux&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;curl -LsSf https://astral.sh/uv/install.sh &lt;span class="p"&gt;|&lt;/span&gt; sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Windows PowerShell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;powershell -ExecutionPolicy ByPass -c &lt;span class="s2"&gt;&amp;#34;irm https://astral.sh/uv/install.ps1 | iex&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="uv-对-python-的环境管理"&gt;&lt;a href="#uv-%e5%af%b9-python-%e7%9a%84%e7%8e%af%e5%a2%83%e7%ae%a1%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;uv 对 Python 的环境管理
&lt;/h3&gt;&lt;p&gt;首先用 uv 管理一下我们本机安装的 Python 环境。即到底安装了几个、哪些版本的 python。&lt;/p&gt;
&lt;p&gt;可以用 &lt;code&gt;uv python list&lt;/code&gt; 查看，像这样：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-27-cong-chu-shi-hua-yi-ge-xian-dai-python-xiang-mu-zhong-xue-xi/003-cd779d4c.png"&gt;&lt;/p&gt;
&lt;p&gt;可以看到我已经安装了多个版本的 python。 在后面建项目的时候，我选用 3.13 这个版本。当然你也可以根据你的情况下载新的需要使用的版本。这里给出一组相关命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;uv python install，安装指定版本的 Python。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;uv python list，列出系统中当前已经安装的 Python 版本。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;uv python find，查找一个已经安装的 Python 版本。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;uv python pin，固定当前项目使用指定的 Python 版本。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;uv python uninstall，卸载指定版本的 Python。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;比如我要安装 3.12 这个版本，我就可以这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;uv python install 3.13
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;装好了不想要了，就可以这样卸载掉它：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;uv python uninstall 3.13 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="uv-进行项目管理"&gt;&lt;a href="#uv-%e8%bf%9b%e8%a1%8c%e9%a1%b9%e7%9b%ae%e7%ae%a1%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;uv 进行项目管理
&lt;/h3&gt;&lt;p&gt;python 的环境有了以后，我们就可以新建项目了，建项目的时候也要用 uv 来进行初始化。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;uv 的项目管理功能更多的借鉴了 Rust 中 Cargo 工具的项目管理理念。但主要区别是 uv 是通过项目目录中的pyproject.toml文件来完成项目管理的。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;uv init myproject
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;初始化后会生成以下几个文件 ：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-27-cong-chu-shi-hua-yi-ge-xian-dai-python-xiang-mu-zhong-xue-xi/004-32d38507.png"&gt;&lt;/p&gt;
&lt;p&gt;虽然 uv init myproject 会帮你创建项目目录和 pyproject.toml，但默认 不会自动创建虚拟环境（env），所以我们需要手动创建。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;# 手动创建虚拟环境
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;uv venv --python 3.13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;# 激活虚拟环境
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;source .venv/bin/activate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;虚拟环境激活后，项目中会多一个.venv 文件夹。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-27-cong-chu-shi-hua-yi-ge-xian-dai-python-xiang-mu-zhong-xue-xi/005-3a39005f.png"&gt;&lt;/p&gt;
&lt;p&gt;接下来我们要自己创建一下源码目录和测试目录：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;mkdir -p src tests
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;到这里工程的相关目录我们就先到此为止，基本上创建完了，然后我们来编辑&lt;/p&gt;
&lt;p&gt;pyproject.toml 配置文件。&lt;/p&gt;
&lt;h3 id="toml-配置文件"&gt;&lt;a href="#toml-%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;toml 配置文件
&lt;/h3&gt;&lt;p&gt;我们先介绍一下 toml 文件，可能有些朋友不怎么了解它，比如搞 java 开发的。&lt;/p&gt;
&lt;p&gt;TOML（Tom&amp;rsquo;s Obvious, Minimal Language）是一种配置文件格式，设计目标是&lt;strong&gt;易读、易写、易于解析&lt;/strong&gt;，非常适合作为程序的配置语言，尤其是在现代的跨平台开发中被广泛采用。&lt;/p&gt;
&lt;p&gt;你看这名字是不是觉得肯定跟 Tom 大哥有关系？&lt;/p&gt;
&lt;p&gt;对，因为 TOML 由 GitHub 联合创始人 Tom Preston-Werner 在 2013 年发起，用以替代 JSON、INI 等配置格式在可读性和灵活性上的不足。&lt;/p&gt;
&lt;p&gt;不过吧，后来这大哥（和她媳妇）不在 GitHub 干了，因为他们的一些不光彩的行为。具体是什么就不多说了，想八卦一下的可以去查查。&lt;/p&gt;
&lt;p&gt;toml 配置文件用途广泛，常用于以下场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用程序运行时配置&lt;/li&gt;
&lt;li&gt;包管理工具（如 Python 的 pyproject.toml、Rust 的 Cargo.toml）&lt;/li&gt;
&lt;li&gt;构建工具配置（如 poetry.toml, uv.toml）&lt;/li&gt;
&lt;li&gt;数据库或服务连接信息等环境参数配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举个例子吧：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;# 数据库配置
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;[database]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;server = &amp;#34;192.168.1.1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;ports = [ 8001, 8001, 8002 ]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;enabled = true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;# 应用信息
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;[app]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;name = &amp;#34;MyApp&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;version = &amp;#34;1.0.0&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;release_date = 2025-04-25T12:00:00Z
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;TOML 的特点可以总结为：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;“比 JSON 更适合人读，比 YAML 更适合程序解析。”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;它已经成为现代软件开发中最流行的配置文件格式之一，特别是在需要 &lt;strong&gt;清晰结构 + 丰富类型 + 可维护性&lt;/strong&gt; 的场景中表现出色。&lt;/p&gt;
&lt;p&gt;常见语言的支持情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python：tomli / toml / pytoml / tomllib（Python 3.11 原生支持）&lt;/li&gt;
&lt;li&gt;Rust：官方包管理工具 Cargo 就使用 TOML 格式的 Cargo.toml&lt;/li&gt;
&lt;li&gt;Go：支持 BurntSushi/toml 库&lt;/li&gt;
&lt;li&gt;Node.js：支持 @iarna/toml 等多个库&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见用途：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python 包管理：pyproject.toml（PEP 518 标准）&lt;/li&gt;
&lt;li&gt;Rust 项目管理：Cargo.toml&lt;/li&gt;
&lt;li&gt;Web 项目配置：netlify.toml&lt;/li&gt;
&lt;li&gt;DevOps 工具：例如 uv 的配置也是用 toml 文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TOML 与其他格式的对比：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;特性&lt;/th&gt;
 &lt;th&gt;TOML&lt;/th&gt;
 &lt;th&gt;JSON&lt;/th&gt;
 &lt;th&gt;YAML&lt;/th&gt;
 &lt;th&gt;INI&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;可读性&lt;/td&gt;
 &lt;td&gt;✅ 高&lt;/td&gt;
 &lt;td&gt;中&lt;/td&gt;
 &lt;td&gt;中高（但复杂）&lt;/td&gt;
 &lt;td&gt;中&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;注释支持&lt;/td&gt;
 &lt;td&gt;✅ 支持&lt;/td&gt;
 &lt;td&gt;❌ 不支持&lt;/td&gt;
 &lt;td&gt;✅ 支持&lt;/td&gt;
 &lt;td&gt;✅ 支持&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;数据类型支持&lt;/td&gt;
 &lt;td&gt;✅ 多&lt;/td&gt;
 &lt;td&gt;✅ 多&lt;/td&gt;
 &lt;td&gt;✅ 多&lt;/td&gt;
 &lt;td&gt;❌ 有限&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;库支持&lt;/td&gt;
 &lt;td&gt;✅ 常见语言皆支持&lt;/td&gt;
 &lt;td&gt;✅ 全面&lt;/td&gt;
 &lt;td&gt;✅ 全面&lt;/td&gt;
 &lt;td&gt;✅ 较好&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;学习曲线&lt;/td&gt;
 &lt;td&gt;✅ 低&lt;/td&gt;
 &lt;td&gt;✅ 低&lt;/td&gt;
 &lt;td&gt;❌ 偏高&lt;/td&gt;
 &lt;td&gt;✅ 极低&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;你看，TOML 作为配置文件感觉很不错对吧。&lt;/p&gt;
&lt;p&gt;我们关于 TOML 的介绍就到此为止，现在来说一下我们这个初始化的新项目中的 pyproject.toml 文件要写成什么样。&lt;/p&gt;
&lt;p&gt;就这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;requires&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;hatchling&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;backend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hatchling.build&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;myproject&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;一个基于Python 3.13.3的项目&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;readme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;README.md&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;requires&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;gt;=3.13&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;authors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;xiaobox&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;xiaobox@gmail.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;pytest&amp;gt;=7.4.3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;fastapi&amp;gt;=0.110.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;uvicorn&amp;gt;=0.27.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;httpx&amp;gt;=0.27.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;classifiers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Programming Language :: Python :: 3.13&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;License :: OSI Approved :: MIT License&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Operating System :: OS Independent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scripts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;myproject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;src.main:main&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Homepage&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/yourusername/myproject&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Bug Tracker&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/yourusername/myproject/issues&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optional&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;34&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;35&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;black&amp;gt;=23.1.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;36&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;isort&amp;gt;=5.12.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;37&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;mypy&amp;gt;=1.5.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;38&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;39&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;40&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;41&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;testpaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tests&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;42&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;43&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;44&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;45&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;py313&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;46&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;47&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isort&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;48&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;black&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;49&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;line_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;50&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;51&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hatch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wheel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;52&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;src&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;别小看了这个文件，它可是一个使用了 Hatch 构建工具、遵循 PEP 621 和现代 Python 项目结构规范的项目配置，涵盖了运行依赖、开发依赖、CLI 脚本、格式化工具配置、测试路径和打包目标，非常完整规范。&lt;/p&gt;
&lt;p&gt;所以我们得逐行解释一下这个重要的文件。&lt;/p&gt;
&lt;h3 id="toml-配置文件的逐行解释"&gt;&lt;a href="#toml-%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6%e7%9a%84%e9%80%90%e8%a1%8c%e8%a7%a3%e9%87%8a" class="header-anchor"&gt;&lt;/a&gt;toml 配置文件的逐行解释
&lt;/h3&gt;&lt;p&gt;我们上面的配置文件是一个标准的 Python 项目使用 &lt;code&gt;pyproject.toml&lt;/code&gt; 来管理构建系统、依赖、工具配置的典型示例。下面我们来拆解和解释一下。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;✅ &lt;code&gt;[build-system]&lt;/code&gt;：构建系统配置（PEP 517 标准）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;[build-system]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;requires = [&amp;#34;hatchling&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;build-backend = &amp;#34;hatchling.build&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requires&lt;/code&gt;：构建该项目所需的构建工具，这里是 &lt;code&gt;hatchling&lt;/code&gt;，必须先安装。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;build-backend&lt;/code&gt;：指定用哪个构建后端来执行打包任务，这里是 &lt;code&gt;hatchling.build&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;hatchling 有点儿类似 java 中的 Maven 或 Gradle，都是用来执行自动化构建流程的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maven 是把 java 代码编译、构建成 jar 包，方便管理依赖、分发、版本控制&lt;/li&gt;
&lt;li&gt;hatchling 是把 python 代码构建成 Wheel（.whl 文件）或 Source Distribution（.tar.gz 或 .zip 文件），也是为了做依赖管理、分发和版本控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总结来说：&lt;strong&gt;Python 的构建是将代码和依赖打包成 .whl 或 .tar.gz，类似于 Java 打包成 .jar。核心目的是简化分发、确保环境一致性、自动化依赖管理。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;✅ &lt;code&gt;[project]&lt;/code&gt;：项目的核心元信息（PEP 621 标准）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;[project]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;name = &amp;#34;myproject&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;项目名称，最终发布到 PyPI 时会用这个名字。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;version = &amp;#34;0.1.0&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;当前版本号。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;description = &amp;#34;一个基于Python 3.13.3的项目&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;简短的项目说明。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;readme = &amp;#34;README.md&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;指定项目的 README 文件，将作为 PyPI 上项目首页的介绍内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;requires-python = &amp;#34;&amp;gt;=3.13&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;要求的 Python 版本最低为 3.13。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;authors = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; {name = &amp;#34;xiaobox&amp;#34;, email = &amp;#34;xiaobox@gmail.com&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;作者信息，支持多个，用列表表示。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;dependencies = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;pytest&amp;gt;=7.4.3&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;fastapi&amp;gt;=0.110.0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;uvicorn&amp;gt;=0.27.0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;httpx&amp;gt;=0.27.0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;项目的运行时依赖库，在安装时会自动安装这些包。这里我加入了 pytest、&lt;strong&gt;fastapi&lt;/strong&gt; 的依赖，因为我想把这个项目作为一个 api 服务提供出去。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;classifiers = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;Programming Language :: Python :: 3.13&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;License :: OSI Approved :: MIT License&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;Operating System :: OS Independent&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;用于 PyPI 分类（帮助搜索和筛选）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="-projectscripts定义可执行命令如-cli"&gt;&lt;a href="#-projectscripts%e5%ae%9a%e4%b9%89%e5%8f%af%e6%89%a7%e8%a1%8c%e5%91%bd%e4%bb%a4%e5%a6%82-cli" class="header-anchor"&gt;&lt;/a&gt;✅ &lt;code&gt;[project.scripts]&lt;/code&gt;：定义可执行命令（如 CLI）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;[project.scripts]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;myproject = &amp;#34;src.main:main&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;安装后运行 &lt;code&gt;myproject&lt;/code&gt; 命令会调用 &lt;code&gt;src/main.py&lt;/code&gt; 中的 &lt;code&gt;main()&lt;/code&gt; 函数。（我们需要提前把之前的 main.py 文件要先移动到 /src 目录下）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="-projecturls项目的相关链接非必须"&gt;&lt;a href="#-projecturls%e9%a1%b9%e7%9b%ae%e7%9a%84%e7%9b%b8%e5%85%b3%e9%93%be%e6%8e%a5%e9%9d%9e%e5%bf%85%e9%a1%bb" class="header-anchor"&gt;&lt;/a&gt;✅ &lt;code&gt;[project.urls]&lt;/code&gt;：项目的相关链接（非必须）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;[project.urls]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&amp;#34;Homepage&amp;#34; = &amp;#34;https://github.com/yourusername/myproject&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&amp;#34;Bug Tracker&amp;#34; = &amp;#34;https://github.com/yourusername/myproject/issues&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;为项目指定一些有用的链接，如主页、问题反馈页等。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="-projectoptional-dependencies可选依赖比如开发环境"&gt;&lt;a href="#-projectoptional-dependencies%e5%8f%af%e9%80%89%e4%be%9d%e8%b5%96%e6%af%94%e5%a6%82%e5%bc%80%e5%8f%91%e7%8e%af%e5%a2%83" class="header-anchor"&gt;&lt;/a&gt;✅ &lt;code&gt;[project.optional-dependencies]&lt;/code&gt;：可选依赖（比如开发环境）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;[project.optional-dependencies]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;dev = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;black&amp;gt;=23.1.0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;isort&amp;gt;=5.12.0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &amp;#34;mypy&amp;gt;=1.5.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们为开发环境安装了三个库：black、isort 和 mypy&lt;/p&gt;
&lt;p&gt;介绍一下这三个工具&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;black：是一个 Python 代码格式化工具。自动把你的 Python 代码排版成统一风格，比如：缩进、换行、空格都按标准格式处理，让你的 Python 代码看起来更整齐、统一，无需自己动手排版。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;isort：是一个 Python 导入（import）语句自动排序工具。自动整理文件顶部的 import 语句，比如按字母顺序排列，分组标准库、第三方库、自定义模块，保持导入部分有序且规范。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;mypy：是一个 Python 静态类型检查工具。检查你的代码里的类型注解（type hints）是不是正确，比如函数参数和返回值类型对不对，帮你在写代码时发现类型出错的地方，提前避免 bug。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="-toolpytestpytest-配置"&gt;&lt;a href="#-toolpytestpytest-%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;✅ &lt;code&gt;[tool.pytest]&lt;/code&gt;：Pytest 配置
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;testpaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tests&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;指定测试用例所在路径，&lt;code&gt;pytest&lt;/code&gt; 会从 &lt;code&gt;tests/&lt;/code&gt; 目录开始查找测试文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="-toolblack代码格式化工具-black-的配置"&gt;&lt;a href="#-toolblack%e4%bb%a3%e7%a0%81%e6%a0%bc%e5%bc%8f%e5%8c%96%e5%b7%a5%e5%85%b7-black-%e7%9a%84%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;✅ &lt;code&gt;[tool.black]&lt;/code&gt;：代码格式化工具 Black 的配置
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;py313&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;设置代码的行最大长度为 88（默认值），目标 Python 版本是 3.13。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="-toolisortimport-排序工具-isort-的配置"&gt;&lt;a href="#-toolisortimport-%e6%8e%92%e5%ba%8f%e5%b7%a5%e5%85%b7-isort-%e7%9a%84%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;✅ &lt;code&gt;[tool.isort]&lt;/code&gt;：import 排序工具 isort 的配置
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isort&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;black&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;line_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;black&lt;/code&gt; 的风格对 import 排序。&lt;/li&gt;
&lt;li&gt;设置行长度为 88，与 black 保持一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="-toolhatchbuildtargetswheelhatchling-打包配置"&gt;&lt;a href="#-toolhatchbuildtargetswheelhatchling-%e6%89%93%e5%8c%85%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;✅ &lt;code&gt;[tool.hatch.build.targets.wheel]&lt;/code&gt;：Hatchling 打包配置
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hatch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wheel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;src&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;指定打包时要包含的代码目录为 &lt;code&gt;src&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;用一句话总结下这个 &lt;code&gt;pyproject.toml&lt;/code&gt; 配置文件 ：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“这是一个使用 Hatch 构建工具、遵循 PEP 621 和现代 Python 项目结构规范的项目配置，涵盖了运行依赖、开发依赖、CLI 脚本、格式化工具配置、测试路径和打包目标，非常完整规范。”&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="安装和更新依赖"&gt;&lt;a href="#%e5%ae%89%e8%a3%85%e5%92%8c%e6%9b%b4%e6%96%b0%e4%be%9d%e8%b5%96" class="header-anchor"&gt;&lt;/a&gt;安装和更新依赖
&lt;/h3&gt;&lt;p&gt;上面这个文件编辑完成后，我们就可以 安装项目和开发依赖了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;uv pip install -e &amp;#34;.[dev]&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果后面你更新了 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件可以执行以下命令来 “手动刷新” 一个依赖库：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;uv sync --extra dev 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;加入 &lt;code&gt;--extra dev&lt;/code&gt; 参数是因为 &lt;code&gt;uv sync&lt;/code&gt; 默认只安装 [project.dependencies] 中列出的正式依赖。&lt;/p&gt;
&lt;p&gt;不会自动安装 [project.optional-dependencies]（比如 dev 里面的 black、isort、mypy）&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uv sync --extra dev&lt;/code&gt; 的意思是：除了正式依赖，还要把 [project.optional-dependencies.dev] 里的东西也同步上&lt;/p&gt;
&lt;h3 id="uvlock"&gt;&lt;a href="#uvlock" class="header-anchor"&gt;&lt;/a&gt;uv.lock
&lt;/h3&gt;&lt;p&gt;当执行完 &lt;code&gt;uv sync --extra dev&lt;/code&gt; ，安装好依赖好， uv 会在项目根路径生成一个 &lt;code&gt;uv.lock&lt;/code&gt; 文件 。uv.lock 是 锁定依赖版本 的文件。&lt;/p&gt;
&lt;p&gt;它的作用是：把 pyproject.toml 里描述的依赖（比如 &amp;ldquo;fastapi&amp;gt;=0.110.0&amp;rdquo; 这样比较宽松的范围），具体锁定成明确、唯一的版本（比如 &amp;ldquo;fastapi==0.110.1&amp;rdquo;）。&lt;/p&gt;
&lt;p&gt;这样，每次安装时，不管谁来安装（你自己、你的同事、你的服务器），大家安装的依赖版本都是一模一样的，不会因为小版本不同导致奇怪的 bug。&lt;/p&gt;
&lt;p&gt;uv.lock 是自动生成、自动管理的。不需手动编辑。&lt;/p&gt;
&lt;h2 id="其他"&gt;&lt;a href="#%e5%85%b6%e4%bb%96" class="header-anchor"&gt;&lt;/a&gt;其他
&lt;/h2&gt;&lt;p&gt;其他的，如 fastapi 相关的、 打 docker 镜像部署什么的相对本文主题超纲了，就不在本文中过多描述了。&lt;/p&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;本文我们分享了用 uv 初始化和管理 Python 项目的完整流程。&lt;/p&gt;
&lt;p&gt;从安装 uv 开始，我介绍了它为什么比传统工具（pip、pipx、poetry 等）更快更好用，以及 uv 在多 Python 版本管理、依赖锁定、项目初始化方面带来的便利。&lt;/p&gt;
&lt;p&gt;随后，详细讲了如何用 uv 管理本地 Python 环境、新建项目、创建虚拟环境、编辑 pyproject.toml 配置，并逐步解释了各个配置项的作用&lt;/p&gt;
&lt;p&gt;整体来看，uv 提供了一套现代、规范、高效的 Python 项目管理方案，非常适合用来打基础，后续无论是开发 API、打包 Docker 镜像，还是部署上线，都能有条不紊地进行。&lt;/p&gt;
&lt;p&gt;同时我们通过在项目创建的过程中看到各语言（java、nodejs&amp;hellip;）都相通或类似的工程 “最佳实践”，真是应了那句话：“大道至简，真理趋同”&lt;/p&gt;</description></item><item><title>2025北京 IT技术岗位市场趋势分析</title><link>https://xiaobox.github.io/p/2025-04-17-2025-bei-jing-it-ji-shu-gang-wei-shi-chang-qu-shi-fen-xi/</link><pubDate>Thu, 17 Apr 2025 02:20:56 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-04-17-2025-bei-jing-it-ji-shu-gang-wei-shi-chang-qu-shi-fen-xi/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-17-2025-bei-jing-it-ji-shu-gang-wei-shi-chang-qu-shi-fen-xi/cover.jpg" alt="Featured image of post 2025北京 IT技术岗位市场趋势分析" /&gt;&lt;p&gt;过去一年，北京数字经济对 GDP 的贡献继续攀升、AI + 传统产业融合提速，拉动技术岗位需求在 2025 年保持“量稳、质升”——总招聘量与 2024 年大体持平，但 &lt;strong&gt;高端与 AI‑相关职能增幅 15% 以上&lt;/strong&gt;；与此同时，普通开发与测试岗位竞争加剧、薪酬增速放缓。整体呈现“尖端紧缺‑常规内卷”的双轨格局。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-17-2025-bei-jing-it-ji-shu-gang-wei-shi-chang-qu-shi-fen-xi/001-91b70463.png"&gt;&lt;/p&gt;
&lt;p&gt;下面按岗位类型分述趋势、薪酬区间与热门技能。&lt;/p&gt;
&lt;h2 id="宏观驱动与整体需求"&gt;&lt;a href="#%e5%ae%8f%e8%a7%82%e9%a9%b1%e5%8a%a8%e4%b8%8e%e6%95%b4%e4%bd%93%e9%9c%80%e6%b1%82" class="header-anchor"&gt;&lt;/a&gt;宏观驱动与整体需求
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;北京仍是全国数字经济第一城，数字产业增加值年均增速 9%，5G、人工智能、信创（国产软硬件）成为新增岗位主引擎&lt;/li&gt;
&lt;li&gt;2025 Hays 人才趋势显示，&lt;strong&gt;生成式 AI 项目扩张&lt;/strong&gt;、&lt;strong&gt;混合办公&lt;/strong&gt;、&lt;strong&gt;用工灵活化&lt;/strong&gt; 是企业技术团队的三大关键词。&lt;/li&gt;
&lt;li&gt;拉勾《一线城市数字科技人才迁徙洞察》指出，北京对高端技术人才的 &lt;strong&gt;净流入率 17.8%&lt;/strong&gt; ，居四大一线城市之首，但应届生流入比例下降 4 pct，反映供给结构趋紧。&lt;/li&gt;
&lt;li&gt;信息技术业 2024Q2‑2025Q1 招聘量同比下滑 27.8%，但算法、运维支持、产品管理等职能逆势增长。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-17-2025-bei-jing-it-ji-shu-gang-wei-shi-chang-qu-shi-fen-xi/002-dd656b09.png"&gt;&lt;/p&gt;
&lt;h2 id="细分岗位趋势"&gt;&lt;a href="#%e7%bb%86%e5%88%86%e5%b2%97%e4%bd%8d%e8%b6%8b%e5%8a%bf" class="header-anchor"&gt;&lt;/a&gt;细分岗位趋势
&lt;/h2&gt;&lt;h3 id="前端开发"&gt;&lt;a href="#%e5%89%8d%e7%ab%af%e5%bc%80%e5%8f%91" class="header-anchor"&gt;&lt;/a&gt;前端开发
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;重点趋势&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;框架&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;React18、Vue3 + TypeScript 成主流；大厂面试开始考察 RSC、微前端落地经验。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;多端融合&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;ByteDance、京东等推行 Modular/Universal mode，将 H5 + 小程序 + 桌面统一到一套代码。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;AI‑Assist&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;高频使用 GitHub Copilot、ByteDance CodeGeeX 等 AI 辅助，企业更看重 Prompt‑Engineering 基础。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;平均月薪区间 2 – 3.5 万元；头部公司核心前端 &amp;gt; 4.5 万元。&lt;/p&gt;
&lt;h3 id="后端全栈开发"&gt;&lt;a href="#%e5%90%8e%e7%ab%af%e5%85%a8%e6%a0%88%e5%bc%80%e5%8f%91" class="header-anchor"&gt;&lt;/a&gt;后端／全栈开发
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Go + Rust&lt;/strong&gt; 在高并发与可信场景快速渗透，Java 依旧占 48% 招聘量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud‑Native&lt;/strong&gt;：K8s、Service Mesh、Sidecar 架构写入 JD；Serverless FaaS 试点项目增多。&lt;/li&gt;
&lt;li&gt;薪酬与前端近似，但 3–6 年经验段的涨幅（+6.8% YoY）高于前端。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="运维--sre"&gt;&lt;a href="#%e8%bf%90%e7%bb%b4--sre" class="header-anchor"&gt;&lt;/a&gt;运维 / SRE
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自动化 &amp;amp; AIOps&lt;/strong&gt;：日志可观测、LLM‑based Root Cause Analysis 成新卖点。&lt;/li&gt;
&lt;li&gt;大厂 DevOps‑SRE 岗位给出 &lt;strong&gt;15k–50k/月&lt;/strong&gt;，中位值 34k，高于全国均值 21k&lt;/li&gt;
&lt;li&gt;需求集中在云平台运维、容器治理、CI /CD 流水线二次开发。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="测试--qa"&gt;&lt;a href="#%e6%b5%8b%e8%af%95--qa" class="header-anchor"&gt;&lt;/a&gt;测试 / QA
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shift‑Left&lt;/strong&gt;：招聘 JD 要求能写 E2E、契合 DevSecOps；懂 API/性能/安全一体的 Full‑Stack QA 稀缺。&lt;/li&gt;
&lt;li&gt;普通功能测试招聘量降 12%，月薪中位值 &lt;strong&gt;14k&lt;/strong&gt;；自动化 / 安全测试可达 2 – 2.5 万元。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="架构--ai-大模型"&gt;&lt;a href="#%e6%9e%b6%e6%9e%84--ai-%e5%a4%a7%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;架构 / AI 大模型
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;大模型平台化&lt;/strong&gt;：企业亟需能打通推理服务、RAG、知识治理的“业务 + 算法 + 云”复合型架构师。&lt;/li&gt;
&lt;li&gt;北京人社局薪酬季报：AI 大模型架构师、深度学习研究员一季度月薪中位值 &lt;strong&gt;&amp;gt; 4 万元&lt;/strong&gt;，位列所有 IT 职位之首。&lt;/li&gt;
&lt;li&gt;供应缺口：相关岗位投递比仅 1 : 7，而常规开发已超过 1 : 40。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="薪酬对比中位值税前--月"&gt;&lt;a href="#%e8%96%aa%e9%85%ac%e5%af%b9%e6%af%94%e4%b8%ad%e4%bd%8d%e5%80%bc%e7%a8%8e%e5%89%8d--%e6%9c%88" class="header-anchor"&gt;&lt;/a&gt;薪酬对比（中位值，税前 / 月）
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;职能&lt;/th&gt;
 &lt;th&gt;初级 (0‑3 年)&lt;/th&gt;
 &lt;th&gt;中级 (3‑6 年)&lt;/th&gt;
 &lt;th&gt;高级 / 专家&lt;/th&gt;
 &lt;th&gt;YoY 涨幅&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;前端&lt;/td&gt;
 &lt;td&gt;14 k&lt;/td&gt;
 &lt;td&gt;23 k&lt;/td&gt;
 &lt;td&gt;32 k+&lt;/td&gt;
 &lt;td&gt;+3.5 %&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;后端&lt;/td&gt;
 &lt;td&gt;15 k&lt;/td&gt;
 &lt;td&gt;25 k&lt;/td&gt;
 &lt;td&gt;35 k+&lt;/td&gt;
 &lt;td&gt;+6.8 %&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;运维 / SRE&lt;/td&gt;
 &lt;td&gt;16 k&lt;/td&gt;
 &lt;td&gt;28 k&lt;/td&gt;
 &lt;td&gt;40 k+&lt;/td&gt;
 &lt;td&gt;+5.9 %&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;测试&lt;/td&gt;
 &lt;td&gt;12 k&lt;/td&gt;
 &lt;td&gt;18 k&lt;/td&gt;
 &lt;td&gt;25 k&lt;/td&gt;
 &lt;td&gt;+2.1 %&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;架构 / AI&lt;/td&gt;
 &lt;td&gt;25 k&lt;/td&gt;
 &lt;td&gt;38 k&lt;/td&gt;
 &lt;td&gt;45‑60 k&lt;/td&gt;
 &lt;td&gt;+11.4 %&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;（数据综合北京 HR 局薪酬季报、Indeed、Jobui 与 iHR360 行业库）&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-04-17-2025-bei-jing-it-ji-shu-gang-wei-shi-chang-qu-shi-fen-xi/003-a40bfb60.png"&gt;&lt;/p&gt;
&lt;h2 id="-技能热点与企业偏好"&gt;&lt;a href="#-%e6%8a%80%e8%83%bd%e7%83%ad%e7%82%b9%e4%b8%8e%e4%bc%81%e4%b8%9a%e5%81%8f%e5%a5%bd" class="header-anchor"&gt;&lt;/a&gt;🔥 技能热点与企业偏好
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AI 嵌入&lt;/strong&gt;：Prompt‑based 代码补全、Auto‑Test、知识库问答等场景让 Python / LLM SDK 成为附加加分项。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全合规&lt;/strong&gt;：等保 2.0、个人信息保护法落地，带动安全测试、DevSecOps、零信任架构招聘量增 19% YoY&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信创&lt;/strong&gt;：国资系招募国产 CPU / OS 适配工程师，优惠补贴年包 +20%。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="供需与竞争"&gt;&lt;a href="#%e4%be%9b%e9%9c%80%e4%b8%8e%e7%ab%9e%e4%ba%89" class="header-anchor"&gt;&lt;/a&gt;供需与竞争
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;北京技术岗位投递量继续领跑一线城市；但 AI、大模型方向平均 &lt;strong&gt;每 1 份 JD 仅 7 份简历&lt;/strong&gt;，反之普通 Web 开发超过 40 份简历/岗，显现结构性紧缺。&lt;/li&gt;
&lt;li&gt;北京 2025Q1 IT 岗位平均薪资 2.9 万元，中位值 2.46 万元；同比涨幅 1.1%，明显低于 2021‑22 年两位数增速。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="给企业与求职者的建议"&gt;&lt;a href="#%e7%bb%99%e4%bc%81%e4%b8%9a%e4%b8%8e%e6%b1%82%e8%81%8c%e8%80%85%e7%9a%84%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;给企业与求职者的建议
&lt;/h2&gt;&lt;h3 id="企业"&gt;&lt;a href="#%e4%bc%81%e4%b8%9a" class="header-anchor"&gt;&lt;/a&gt;企业
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;拆分 JD&lt;/strong&gt;：将“平台研发”与“AI 应用落地”拆分，便于精准匹配候选人。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用薪酬+Tech Brand 双重激励&lt;/strong&gt;：高端人才更看重技术氛围与成长空间，可开放技术博客、OSS 贡献等展示窗口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;培养内部转岗&lt;/strong&gt;：参考 LinkedIn 数据，内部流动可将员工留存提升 40%&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="求职者"&gt;&lt;a href="#%e6%b1%82%e8%81%8c%e8%80%85" class="header-anchor"&gt;&lt;/a&gt;求职者
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;普通开发/测试应持续补齐 &lt;strong&gt;云原生 + 自动化测试 + 基础 AI&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;关注国央企“信创”“数据要素”等长期项目，以稳就业与培训机会为优势；&lt;/li&gt;
&lt;li&gt;AI &amp;amp; 架构方向建议组合 &lt;strong&gt;算法实践 + 高并发系统设计 + 行业领域知识&lt;/strong&gt;，扩大竞争壁垒。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;北京 2025 年 IT 技术岗位的主旋律是 &lt;strong&gt;“AI 驱动的高端岗位紧缺，传统岗位理性回调”&lt;/strong&gt;。掌握新技能、拥抱跨领域融合将是穿越周期的关键。&lt;/p&gt;

 &lt;/blockquote&gt;</description></item><item><title>Java老兵的十字路口：坚守还是突围？</title><link>https://xiaobox.github.io/p/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/</link><pubDate>Sat, 29 Mar 2025 05:33:55 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/cover.jpg" alt="Featured image of post Java老兵的十字路口：坚守还是突围？" /&gt;&lt;h2 id="java在技术江湖中的现状稳固基石还是夕阳西下"&gt;&lt;a href="#java%e5%9c%a8%e6%8a%80%e6%9c%af%e6%b1%9f%e6%b9%96%e4%b8%ad%e7%9a%84%e7%8e%b0%e7%8a%b6%e7%a8%b3%e5%9b%ba%e5%9f%ba%e7%9f%b3%e8%bf%98%e6%98%af%e5%a4%95%e9%98%b3%e8%a5%bf%e4%b8%8b" class="header-anchor"&gt;&lt;/a&gt;Java在技术江湖中的现状：稳固基石还是夕阳西下？
&lt;/h2&gt;&lt;p&gt;Java 作为企业级开发的常青树，至今在大量核心系统中扮演着中流砥柱的角色。然而，资深 Java 开发者也明显感受到技术环境的变化。一方面，在银行、政府、互联网巨头等复杂业务场景中，Java 的地位依然稳固；大量遗留系统和核心业务仍运行在 Java 上，短期内很难被完全替换。以优酷的版权管理系统为例，这套长达10年的老系统采用了过时的技术框架，积累了 81万行 Java 代码和大量“没人敢动”的if-else逻辑，可谓技术债累累。像这样的遗留系统，全面重构风险极高，只能在业务推动下逐步演进。因此，在许多传统领域，Java 作为“老码农”的看家本领，仍是不可或缺的基石。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/001-40dd5948.png"&gt;&lt;/p&gt;
&lt;p&gt;另一方面，新兴业务和初创项目对技术栈的选择更加多元。近年Go语言等后起之秀在性能、并发和开发效率上表现出色，成为云原生时代的“宠儿”。不少互联网大厂开始在核心服务中引入 Go：例如谷歌、滴滴、Uber、腾讯等都用 Go 开发高并发、高性能的服务。业界一度流传着“Java 老旧笨重、Go 崭新酷炫”的声音。面对这种冲击，不少资深 Java 工程师难免焦虑：Java 会不会像当年的 COBOL 一样淡出主流舞台？&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/002-28c85ce1.png"&gt;&lt;/p&gt;
&lt;p&gt;事实证明，这种担忧有些过度。Java 拥有数百万开发者和完整生态，并非轻易就能被取代的工具。正如有分析指出，Go 的崛起为行业提供了新选择，但并不是对 Java 的简单替代；两种语言各有优势，未来将长期共存。换言之，Java 依旧是技术江湖里的定海神针，只是江湖规矩变了——老兵们需要适应新玩法。&lt;/p&gt;
&lt;h2 id="java-面临的核心挑战笨重背后的突围"&gt;&lt;a href="#java-%e9%9d%a2%e4%b8%b4%e7%9a%84%e6%a0%b8%e5%bf%83%e6%8c%91%e6%88%98%e7%ac%a8%e9%87%8d%e8%83%8c%e5%90%8e%e7%9a%84%e7%aa%81%e5%9b%b4" class="header-anchor"&gt;&lt;/a&gt;Java 面临的核心挑战：笨重背后的突围
&lt;/h2&gt;&lt;p&gt;资深 Java 开发者在项目实践中遇到的挑战，往往并非语言本身跑不动，而是架构转型带来的“不适感”。近年来微服务、云原生风潮兴起，Java 传统的开发模式在新环境下面临诸多掣肘。&lt;/p&gt;
&lt;h3 id="微服务部署压力"&gt;&lt;a href="#%e5%be%ae%e6%9c%8d%e5%8a%a1%e9%83%a8%e7%bd%b2%e5%8e%8b%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;微服务部署压力
&lt;/h3&gt;&lt;p&gt;将单体应用拆成数十上百的微服务后，每个服务都需要独立部署运行。Java 应用启动慢、内存占用高的问题被放大。在同样功能下，一个简单的 Go 容器镜像可能只有十几MB，而等价的 Java（如采用 Helidon 框架）镜像初始体积高达 1.4GB！即使使用模块化手段缩减JDK体积（JLink可降至150MB左右），Java应用的容器仍显得臃肿。如此高的基础开销让追求极致弹性的微服务架构如临大敌：部署50个 Java 微服务可能需要远超预期的硬件资源，这正是很多团队转向更轻量语言的原因之一。有开发者调侃：“即便Java性能再好，内存占用是别人的 2～10倍，成本账算下来也让人犹豫。”而且在无服务器（Serverless）场景下，Java 冷启动延迟更是令人头疼——函数实例首次启动可能耗时数秒到十几秒。这一问题长期无法回避，直到 AWS 推出 SnapStart 等黑科技，把 Java 函数冷启动时间缩短到毫秒级，才算勉强止血。但需要注意，这类优化是云厂商额外提供的特殊支持，侧面说明 Java 在边缘计算、FaaS 等领域的先天劣势依然存在。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/003-c6bc61f8.png"&gt;&lt;/p&gt;
&lt;h3 id="启动速度与样板代码"&gt;&lt;a href="#%e5%90%af%e5%8a%a8%e9%80%9f%e5%ba%a6%e4%b8%8e%e6%a0%b7%e6%9d%bf%e4%bb%a3%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;启动速度与样板代码
&lt;/h3&gt;&lt;p&gt;“万事开头难”在 Java 世界格外贴切。传统 Java Web 应用往往需要预热 JVM、加载大量类和配置，启动一个服务动辄数十秒甚至几分钟。这在强调弹性伸缩的云环境下难以接受。此外，Java 以模板代码多著称，同样的业务逻辑，用 Java 写可能需要冗长的类定义和 Getter/Setter，而用 Go/Kotlin 等语言则简洁得多。例如，Go 以极简的语法实现高并发，让开发者专注于业务；相比之下，过去的 Java 代码显得繁琐累赘，不少老兵自己也调侃天天在写“体力活”。虽然 Java 近年通过 Lambda、Streams、Records 等特性在不断精简，但是遗留项目中的大量样板代码依旧是维护负担。缺乏某些现代语言特性（如模式匹配、代数数据类型）也使Java在表达某些逻辑时不够优雅。这些痛点促使部分团队尝试用 Kotlin 等 JVM 语言替换 Java，以期获得更简洁的语法和更少的冗余。&lt;/p&gt;
&lt;h3 id="并发与性能困境"&gt;&lt;a href="#%e5%b9%b6%e5%8f%91%e4%b8%8e%e6%80%a7%e8%83%bd%e5%9b%b0%e5%a2%83" class="header-anchor"&gt;&lt;/a&gt;并发与性能困境
&lt;/h3&gt;&lt;p&gt;高并发一直是 Java 的强项，但实现方式却日益受到挑战。传统 Java 使用操作系统线程实现并发，每个线程都对应一定的内存和调度开销，在大规模场景下显得“沉重”。为绕过这个瓶颈，过去几年兴起了基于 Reactive 异步编程的微服务框架，通过单线程事件循环避免线程阻塞。然而异步风格代码复杂度高、调试困难，让许多工程师“叫苦不迭”。好消息是，JDK 19/20 引入了虚拟线程（Project Loom）并在 JDK 21 成为正式特性。虚拟线程是由 JVM 管理的超轻量线程，实现了 Go 协程式的并发模型。这意味着开发者可以用以往同步阻塞的简单代码，实现过去需要复杂回调/响应式才能处理的高并发任务。正如 Java 架构师 Brian Goetz 所言：Loom 的出现有望“一举终结Reactive编程的必要”——因为过去Reactive是为解决线程不足的权宜之计。虚拟线程让我们能够创建成千上万个并发任务而不必担心线程耗尽，大幅降低了编写和维护高并发代码的心智负担。然而，新事物也伴随不确定性：老项目若迁移到虚拟线程模型，需要评估线程本地变量、同步机制等是否还能正常工作；调优方式也与传统线程有所不同。目前虚拟线程虽强大，但毕竟是新特性，在大型生产环境中的考验还不充分。Java 老兵们既期待它带来性能飞跃，也需要保持一份观望和谨慎。&lt;/p&gt;
&lt;h3 id="graalvm-与原生执行"&gt;&lt;a href="#graalvm-%e4%b8%8e%e5%8e%9f%e7%94%9f%e6%89%a7%e8%a1%8c" class="header-anchor"&gt;&lt;/a&gt;GraalVM 与原生执行
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/004-3147eb4e.png"&gt;&lt;/p&gt;
&lt;p&gt;为了解决启动慢、内存高的问题，Java 社区近年另一“大招”是 GraalVM 原生镜像。通过提前编译(AOT)，可以将 Java 应用直接打包成本地可执行文件，启动时间和内存占用都有数量级的优化。这项技术被视为让 Java 重返边缘计算和 Serverless 舞台的希望：原生镜像下，一个 Spring Boot 微服务的“Hello World”容器镜像可小到 ~几十MB；运行时不需要JVM，冷启动延迟大幅降低。实际测试中，采用 GraalVM 原生镜像的 Java 服务在某些基准下性能甚至超越 Go：平均延迟仅0.25毫秒，每秒处理事务达到82426次，吞吐率是 Go 实现的两倍多！这种结果令人振奋，仿佛看到了 Java 打了一场漂亮的翻身仗。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/005-846f3af1.png"&gt;&lt;/p&gt;
&lt;p&gt;然而，理想很丰满，现实有时比较骨感。将复杂应用迁移到 GraalVM 原生镜像并非易事。例如，反射、动态代理等机制需要额外配置支持，许多成熟库在AOT编译下可能行为异常。构建原生镜像的过程也比较繁琐，往往需要调整代码、引入特定插件，并忍受较长的编译时间。调试诊断也更具挑战——原生应用无法使用 JVM 的丰富调试工具，需要新的手段排查问题。此外，引入 GraalVM 还意味着团队需要掌握一套新的知识体系。在的总结中作者就指出：“将 Spring Boot 应用打包为 Native 镜像并非没有挑战”，直接迁移复杂项目可能遇到种种坑，需要开发者充分评估和测试。因此，GraalVM 不是万能灵药，而是有门槛的新武器：用得好，Java 如虎添翼；用不好，反而可能引入新的不稳定因素。&lt;/p&gt;
&lt;p&gt;综上，资深 Java 开发者面对的不是“Java 不行了”，而是如何让 Java 行得更轻、更快、更优雅。JVM 社区显然没有躺在功劳簿上吃老本，而是在微服务、云原生时代积极求变。从 Spring Boot 3 对原生镜像的支持，到 JDK 连续的功能升级（如Records、Pattern Matching等），Java 正在努力破除“笨重”的刻板印象。老兵们需要做的，是与时俱进地拥抱这些变化，用新工具、新思路来武装自己的Java技能库。&lt;/p&gt;
&lt;h2 id="用舍之道哪些场景放弃-java哪些场景坚持-java"&gt;&lt;a href="#%e7%94%a8%e8%88%8d%e4%b9%8b%e9%81%93%e5%93%aa%e4%ba%9b%e5%9c%ba%e6%99%af%e6%94%be%e5%bc%83-java%e5%93%aa%e4%ba%9b%e5%9c%ba%e6%99%af%e5%9d%9a%e6%8c%81-java" class="header-anchor"&gt;&lt;/a&gt;用舍之道：哪些场景放弃 Java，哪些场景坚持 Java？
&lt;/h2&gt;&lt;p&gt;技术选型从来都不是非黑即白，对于 Java 的去留更是如此。究竟在哪些业务场景下应该考虑放弃 Java？哪些场景下 Java 仍是首选？结合真实案例和趋势，我们可以做出以下判断：&lt;/p&gt;
&lt;h3 id="场景一边缘计算与serverless--谨慎使用-java"&gt;&lt;a href="#%e5%9c%ba%e6%99%af%e4%b8%80%e8%be%b9%e7%bc%98%e8%ae%a1%e7%ae%97%e4%b8%8eserverless--%e8%b0%a8%e6%85%8e%e4%bd%bf%e7%94%a8-java" class="header-anchor"&gt;&lt;/a&gt;场景一：边缘计算与Serverless – 谨慎使用 Java。
&lt;/h3&gt;&lt;p&gt;对于运行环境受限、对启动延迟敏感的场景，选择 Java 需要非常慎重。典型如物联网设备、边缘网关、函数计算等，这些环境往往内存有限且要求冷启动极快。在这些场合，运行一个庞大的 JVM 显然不如直接使用原生语言（C/C++、Rust）或轻量脚本语言（JavaScript、Python）来得高效。过去不少团队在实现事件驱动的小型服务时，就倾向于用 Node.js 或 Python 编写——不是因为Java不能实现功能，而是因为Java在每次调用都要“热身”的开销让人难以忍受。虽然有 GraalVM Native Image 可以大幅优化，但对小型团队而言，引入它的复杂度可能得不偿失。因此，对于边缘和无服务函数等应用，除非有充足理由和相应优化手段，否则倾向于选择更轻便的技术栈。当然，规则也非绝对：如果业务逻辑需要调用大量现有的 Java 类库（例如进行某种算法运算，而相关库只有Java实现），那么即便在 Serverless 环境下通过原生镜像等方式使用 Java 也是可以考虑的。总的来说，在这些场景，“能不用Java就不用”是较为实际的指导原则。&lt;/p&gt;
&lt;h3 id="场景二高性能微服务--视情况取舍"&gt;&lt;a href="#%e5%9c%ba%e6%99%af%e4%ba%8c%e9%ab%98%e6%80%a7%e8%83%bd%e5%be%ae%e6%9c%8d%e5%8a%a1--%e8%a7%86%e6%83%85%e5%86%b5%e5%8f%96%e8%88%8d" class="header-anchor"&gt;&lt;/a&gt;场景二：高性能微服务 – 视情况取舍
&lt;/h3&gt;&lt;p&gt;在互联网分布式系统中，每种语言都有用武之地。如果团队主要目标是极致的性能和资源利用率，并且成员对 Go/Rust 等语言驾轻就熟，那么把部分微服务用新语言实现未尝不可。例如某些网关服务、实时通信服务，行业里确有用 Rust 或 Go 重写后延迟降低、内存减半的成功案例。特别是低延迟、高并发的基础设施组件（消息队列、代理服务器等），很多开源项目早就避开 Java 转投 Go/Rust 怀抱，这是技术基因所致（Java 更擅长业务逻辑，系统编程领域C系语言传统更强）。但是，对于业务逻辑复杂的微服务，Java 仍然具有难以替代的优势：强大的生态提供了各种中间件客户端、成熟的 ORM 和事务框架、安全完备的验证和监控工具等等。这些“全家桶”式的支持使Java在开发业务系统时如鱼得水，大大减少了造轮子的成本。如果纯粹为了追新把此类服务改用另一种语言，可能会发现需要重建许多Java自带的轮子，得不偿失。因此，我们的立场是：对性能极限有追求的核心组件，可以考虑非Java实现以挖掘潜力；但大多数微服务尤其是业务导向的微服务，Java 依然是稳妥且高效的选择。况且随着Quarkus、Micronaut等专为云环境优化的Java框架出现，以及JVM自身的持续优化，Java 微服务的“笨重”正在被削平。正如某次测试所示，在较大的机器上，Java 的吞吐甚至可与 Go 持平甚至略胜一筹——只要用对了方式，Java 完全能胜任高性能微服务。&lt;/p&gt;
&lt;h3 id="场景三大型核心系统--坚定拥抱-java"&gt;&lt;a href="#%e5%9c%ba%e6%99%af%e4%b8%89%e5%a4%a7%e5%9e%8b%e6%a0%b8%e5%bf%83%e7%b3%bb%e7%bb%9f--%e5%9d%9a%e5%ae%9a%e6%8b%a5%e6%8a%b1-java" class="header-anchor"&gt;&lt;/a&gt;场景三：大型核心系统 – 坚定拥抱 Java
&lt;/h3&gt;&lt;p&gt;对于那些 复杂度高、生命周期长、需要强一致性和可靠性的核心业务，Java 无疑仍是值得长期信赖的编程语言。例如银行的核心账务系统、航空公司的订票系统、阿里的电商交易中台等，这些系统往往经历多年演化，业务规则繁多且严谨，需要大量业内验证过的中间件支撑。Java 的严格类型体系和成熟框架在这里如鱼得水。特别是在金融、政府等对稳定性要求极高的领域，“Java + 大型商用中间件”的组合几乎是默认标配。从技术债的角度考虑，这类系统虽然也面临老旧架构的问题，但重构时通常还是在 Java 体系内升级（比如从 Struts 升级到 Spring Boot，或引入分布式事务框架等），而不会轻易迁移到一门全新的语言上。这不仅因为重写成本高，更因为 Java 多年沉淀的安全性和可靠性难以替代。可以说，在复杂业务长跑中，Java 是一匹稳健的“长途马”，跑得也许不算最快，但足够稳当，生态中现成的工具能够覆盖方方面面，让架构师和开发者更安心。基于这些原因，我们坚定认为：在复杂业务和核心系统场景下，坚持使用 Java 是明智之举。即便引入新的技术插件，也是作为补充而非颠覆，比如用 Python 做小部分AI预测，再把结果喂给Java主系统等等。Java 老兵在这些战场上大可发挥深厚经验，将系统设计得健壮且易于维护，为业务保驾护航。&lt;/p&gt;
&lt;p&gt;归纳来说，用舍有道，视需而定：Java 并非万能，同样也远未过时。关键在于根据项目需求选择最合适的工具。在前沿领域不妨多尝试新语言新架构，以保持竞争力；而在关系到企业命脉的长线工程上，Java 依然值得我们托付。&lt;/p&gt;
&lt;h2 id="java老兵的自我进化坚守阵地or华丽转型"&gt;&lt;a href="#java%e8%80%81%e5%85%b5%e7%9a%84%e8%87%aa%e6%88%91%e8%bf%9b%e5%8c%96%e5%9d%9a%e5%ae%88%e9%98%b5%e5%9c%b0or%e5%8d%8e%e4%b8%bd%e8%bd%ac%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;Java老兵的自我进化：坚守阵地or华丽转型？
&lt;/h2&gt;&lt;p&gt;面对风起云涌的技术浪潮，10年以上经验的 Java 老将们该何去何从？是固守舒适圈，还是勇敢拓展边界？以下几点建议或许对处在十字路口的你有所启发：&lt;/p&gt;
&lt;h3 id="拥抱java新特性跟上生态演进"&gt;&lt;a href="#%e6%8b%a5%e6%8a%b1java%e6%96%b0%e7%89%b9%e6%80%a7%e8%b7%9f%e4%b8%8a%e7%94%9f%e6%80%81%e6%bc%94%e8%bf%9b" class="header-anchor"&gt;&lt;/a&gt;拥抱Java新特性，跟上生态演进
&lt;/h3&gt;&lt;p&gt;不要认为“学了十几年Java就没有新东西可学”。相反，Java生态在飞速更新，每年两个版本迭代。老兵们应该主动学习 JDK近几版的新功能（如Records、Sealed Class、Pattern Matching、虚拟线程等），这些特性能显著改善代码质量和性能，使你的技能焕发新生。例如，试着用 Loom 虚拟线程改造一个老的并发模块，体会一下开发模式的简化；或者研究 GraalVM 如何将现有服务无缝打包为原生镜像，了解其中的限制和调优手段。拥抱新技术不仅能提升生产力，也向团队展示了你与时俱进的技术热情。作为Java老兵，切忌固步自封——持续学习是对抗职业倦怠和时代冲击的最好武器。&lt;/p&gt;
&lt;h3 id="拓展多元技能栈成为t型人才"&gt;&lt;a href="#%e6%8b%93%e5%b1%95%e5%a4%9a%e5%85%83%e6%8a%80%e8%83%bd%e6%a0%88%e6%88%90%e4%b8%bat%e5%9e%8b%e4%ba%ba%e6%89%8d" class="header-anchor"&gt;&lt;/a&gt;拓展多元技能栈，成为“T型”人才
&lt;/h3&gt;&lt;p&gt;在保持Java优势的同时，建议横向拓展一到两门其它语言或领域技能。比如，可以尝试学习 Go 或 Python，用它们做些小项目，体会不同语言在思维模型上的差异。再比如，深入了解一下前端技术或移动开发，哪怕不做前端，也能与前端同事更高效协作。这种“T”字型的技能结构（既有一门精深的主力技术，又对相关技术有所涉猎）将使你在团队中更具价值。很多架构师在成为架构师前，都曾是精通数门语言、熟悉多种数据库和中间件的全能型工程师。对Java老兵来说，学习一门新语言还能帮助跳出现有思维框架，把新理念反哺到Java日常开发中。例如，借鉴函数式编程思想优化Java代码，或者用脚本语言编写自动化工具提升开发效率。多元化的技能还为你提供了职业备胎：万一某天真的不想写Java了，你在其他领域的积累也足以支撑转型，不至于手足无措。&lt;/p&gt;
&lt;h3 id="深入业务和架构提升不可替代性"&gt;&lt;a href="#%e6%b7%b1%e5%85%a5%e4%b8%9a%e5%8a%a1%e5%92%8c%e6%9e%b6%e6%9e%84%e6%8f%90%e5%8d%87%e4%b8%8d%e5%8f%af%e6%9b%bf%e4%bb%a3%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;深入业务和架构，提升不可替代性
&lt;/h3&gt;&lt;p&gt;随着工作年限增长，“懂业务、能设计”往往比单纯的编码能力更重要。Java老兵应该充分利用在一个行业浸润多年的经验，去深入理解业务领域 的本质问题，把握业务发展方向。将业务洞察与技术方案相结合，主动参与系统架构设计和重大技术选型，这会让你成为团队中不可或缺的核心人物。很多时候，业务专家+技术专家的复合型人才，比仅仅精通某种语法的程序员更有竞争力。如果你已经是某核心系统的Owner，不妨尝试推进架构优化和性能提升项目，展示自己在宏观层面的掌控力。同时，培养自己的系统设计能力，多研究业界大型系统的架构案例，学习它们如何权衡取舍。当你能从容驾驭分布式事务、异地多活、CQRS 等架构模式时，你的价值早已超越“Java 工程师”的范畴，而成为真正的技术专家。这种升级，无论未来Java的热度如何，都能让你的职业生涯保持上升。&lt;/p&gt;
&lt;h3 id="考虑转型技术管理或其他新领域"&gt;&lt;a href="#%e8%80%83%e8%99%91%e8%bd%ac%e5%9e%8b%e6%8a%80%e6%9c%af%e7%ae%a1%e7%90%86%e6%88%96%e5%85%b6%e4%bb%96%e6%96%b0%e9%a2%86%e5%9f%9f" class="header-anchor"&gt;&lt;/a&gt;考虑转型技术管理或其他新领域
&lt;/h3&gt;&lt;p&gt;并非每个人都要永远写代码。工作十年以上后，你也可以根据兴趣转型，选择最适合自己的道路。如果你热衷带团队和项目把控，可以逐步走向 技术管理 岗位，担任Team Leader、技术经理甚至CTO，把多年经验用于培养新人和决策把关。很多Java老兵在这一阶段选择带领团队，既可传承自己的开发哲学，又能获得管理成就感。又或者，你对某些新兴领域情有独钟，例如 人工智能、大数据、安全 等，不妨利用业余时间学习相关知识，寻求内部调岗或外部机会。资深程序员转做产品经理、解决方案架构师的例子也屡见不鲜——只要有心，完全可以跳出演员阵容，转到幕后编剧或导演的位置上。当然，做出转型决定前需要评估清楚：你的核心竞争力是什么，新领域是否真心喜欢，从头开始是否有心理准备。转型不是逃避，而是为了更长远的发展。无论选择深耕Java栈还是开拓新跑道，持续的学习和热情都是关键驱动力。&lt;/p&gt;
&lt;h2 id="最后"&gt;&lt;a href="#%e6%9c%80%e5%90%8e" class="header-anchor"&gt;&lt;/a&gt;最后
&lt;/h2&gt;&lt;p&gt;想对每一位焦虑中的Java老兵说：技术江湖瞬息万变，但真正的资深工程师价值从不局限于某种语法。Java 之父 James Gosling 曾打比方说：“Java就像一辆可靠的卡车”，或许它没有跑车那样光鲜，但能载着重载货物稳稳前行。这辆卡车如今也在不断改装升级，动力和油耗都在改进。我们作为司机，要做的不是弃车而逃，而是练就更高超的驾驶技巧，并且学会在不同道路上换合适的交通工具。坚守初心并不代表故步自封，拥抱变化也不意味着全盘否定过去。当我们既掌握了Java这门老牌利器，又勇于学习新招式、新套路，在变化的浪潮中依然能找到自己的方向和节奏。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-03-29-java-lao-bing-de-shi-zi-lu-kou-jian-shou-hai-shi-tu-wei/006-0c6fa2fa.png"&gt;&lt;/p&gt;
&lt;p&gt;10+年开发生涯沉淀下来的经验与智慧，是宝贵的财富。无论Java的流行曲线如何波动，真正优秀的工程师都会不断进化，拓展自己的边界。在这个过程中，我们既要有克制冷静的思考，看清技术演进的本质；也要保持对编程的热爱，不忘初心地享受技术创造的乐趣。愿每一位Java老兵都能在时代洪流中找到属于自己的位置：该出手时果断出手，该坚守时稳如磐石，在新的十年里续写属于你的传奇。&lt;/p&gt;</description></item><item><title>RAG的2024—随需而变，从狂热到理性（上）</title><link>https://xiaobox.github.io/p/2024-11-25-rag-de-2024-sui-xu-er-bian-cong-kuang-re-dao-li-xing-shang/</link><pubDate>Mon, 25 Nov 2024 06:27:48 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-11-25-rag-de-2024-sui-xu-er-bian-cong-kuang-re-dao-li-xing-shang/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-25-rag-de-2024-sui-xu-er-bian-cong-kuang-re-dao-li-xing-shang/cover.jpg" alt="Featured image of post RAG的2024—随需而变，从狂热到理性（上）" /&gt;&lt;p&gt;首先感谢那些依然关注着“土猛的员外”的朋友们，对你们，肃然起敬！&lt;/p&gt;
&lt;p&gt;转眼到了2024年尾，和小伙伴一起创立TorchV也接近一年。虽然这一年做了很多事情，但从技术层面上来说，RAG肯定是不得不提的，所以今天分享一下作为大模型应用创业者所感知的这一年，RAG技术和市场环境的变化。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;首先声明，本文更多来自于本人主观感受，且内容更多是回顾性的结论，不建议作为其他文章的引用材料。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;主要内容包括：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RAG技术变化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;主要架构变化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;技术细节变化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;市场需求变化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;上半年：AI无所不能，大而全；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下半年：回归理性，小而难；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;明年预测：应用才是王道；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从业者变化。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;
&lt;p&gt;其中技术部分放在上篇，市场需求变化放在下篇。&lt;/p&gt;
&lt;h3 id="一rag技术变化"&gt;&lt;a href="#%e4%b8%80rag%e6%8a%80%e6%9c%af%e5%8f%98%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;一、RAG技术变化
&lt;/h3&gt;&lt;p&gt;RAG（检索增强生成）其实是由两部分组成的，分别是检索和大模型生成。当然，既然有检索就必然会先有索引，包括chunking、embedding等动作都是为了建立更好的索引。因为我们之前从零开始创建并运营了一个千万级用户的智能问答类产品，所以在2021年左右其实就已经采用Java技术栈在使用RAG里面“RA”的大部分技术了。在2023年年中，RAG这个词突然火了起来，于是我们就立马就扑进去了，而且相信RAG在企业应用领域比纯粹使用大模型会更具实用性，至少在三年之内是这样的（随着最近传闻Scaling Law遇到瓶颈，好像这个时间还有可能被推后）。短短几个月，RAG开始的火爆程度甚至有超过LLM的趋势，在2024年1月我甚至还参加了“&lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI2MTM2MTgxNQ==&amp;amp;mid=2247486569&amp;amp;idx=1&amp;amp;sn=1bb4d0b44745637079846fa69ea5c3c5&amp;amp;chksm=ea5ad591dd2d5c8743030e2b3e40f376b69d03d473c9a6259f161d6a79c8b562706db3ba197a&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;共识粉碎机”的EP15讨论会&lt;/a&gt;，主要话题就是**“2024年是否会成为RAG元年？”**。&lt;/p&gt;
&lt;h4 id="1主要架构变化"&gt;&lt;a href="#1%e4%b8%bb%e8%a6%81%e6%9e%b6%e6%9e%84%e5%8f%98%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;1.&lt;strong&gt;主要架构变化&lt;/strong&gt;
&lt;/h4&gt;&lt;p&gt;虽然RAG的火热，各种架构思想就被大师们总结出来，最出名的莫过于下面这张图了：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-25-rag-de-2024-sui-xu-er-bian-cong-kuang-re-dao-li-xing-shang/001-3df79d17.png"&gt;&lt;/p&gt;
&lt;p&gt;RAG三种技术架构&lt;/p&gt;
&lt;p&gt;图1:RAG三种架构模式，来源于论文Retrieval-Augmented Generation for Large Language Models: A Survey。&lt;/p&gt;
&lt;p&gt;站在现在（2024年11月）再看，其实Advanced RAG应该还是最主流的架构。因为它的效果明显比Naive RAG要好，但比Modular RAG更容易实现。在实际应用中，我们还需要为客户考虑经济成本和维护成本，很多时候基于客户需求，在Advanced RAG上做一些对症下药，远比全家桶更具适应性。&lt;/p&gt;
&lt;h4 id="2技术细节变化"&gt;&lt;a href="#2%e6%8a%80%e6%9c%af%e7%bb%86%e8%8a%82%e5%8f%98%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;2.&lt;strong&gt;技术细节变化&lt;/strong&gt;
&lt;/h4&gt;&lt;p&gt;相较于架构变化，技术细节的变化更加“风起云涌”，这一点从各自媒体的文章主题变化和从业者之间的交流内容中就能明显感知到。在RAG这个大框架之下，我们看到了每隔一段时间就会有一些技术细节被热炒，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;知识提取&lt;/li&gt;
&lt;li&gt;索引组织&lt;/li&gt;
&lt;li&gt;检索方法&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="01-知识提取"&gt;&lt;a href="#01-%e7%9f%a5%e8%af%86%e6%8f%90%e5%8f%96" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;01 知识提取&lt;/strong&gt;
&lt;/h5&gt;&lt;p&gt;在2024年之前，我们看到的RAG面对的原始知识更多是非常标准的论文，一般是文字版PDF或者HTML，所以那时候内容提取还不是一个大问题。但是在2024年，随着各行各业都开始使用RAG之后，五花八门的文件类型解析变成了从业者们头痛的事情，于是，我们发现有好一波人开始专注于知识提取。&lt;/p&gt;
&lt;p&gt;于是我们看到在2024年，出现了很多专门做知识提取的公司，比如已经在科创板上市合合信息（其实已经是老牌企业），在文件解析方面就非常出色，还有新创的SoMark等，当然大厂在这一块肯定也都涉足，特别是OCR解析，比如百度开源的PaddleOCR，其实还挺良心的。&lt;/p&gt;
&lt;p&gt;我们也是如此，看到现成的python组件在面对多样的知识文件类型时渐感无力，于是就开始在知识提取方面花时间。从多类型文件的解析，包括一些老文件格式（.doc、.xls、.ppt等）的解析，再到比较难的PDF表格解析，需要去处理非常复杂的合并/拆分单元格，并且也有了具备原创知识产权的提取工具组件。&lt;/p&gt;
&lt;p&gt;但是我一直在想，是否可以把整个工作流再前置，如果我们提供知识生产工具，用户可以在我们的知识管理和生产工具上进行知识生产和协作，那么知识提取是不是会更流畅？这个问题可以单独写一篇文章，就放在这个篇文章的上下篇结束之后吧。&lt;/p&gt;
&lt;h5 id="02-索引组织"&gt;&lt;a href="#02-%e7%b4%a2%e5%bc%95%e7%bb%84%e7%bb%87" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;02 索引组织&lt;/strong&gt;
&lt;/h5&gt;&lt;p&gt;在RAG里面，索引组织可能是相对比较有技巧性的部分了。&lt;/p&gt;
&lt;p&gt;了解RAG的人都听过chunking，不严谨地说，就是把文件切成若干片段，为的是可以在LLM的窗口大小之内进行作业。常见的chunking方式有按固定token数量的，有按Page的，也有通过NLTK等来进行切分的。但我始终觉得使用哪种chunking方法并不是重点，如果您使用的LLM的上下文窗口较大，对于一些4、5页的文件，还切它干嘛呢，直接扔进去就完事了。但在chunking过程中，其实有两个隐含的技巧是可以快速提升准确率的：指代消解和附加元数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;指代消解&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们在实操中常见的是两种，一种是切割的时候，上一页有详细信息，而下一页中只有“这种方法”来指代。这时候最简单的方法就是做chunk叠加；还有一种就是类似合同，甲方乙方的具体名字只在最开头的地方出现，剩下全文都用“甲方”、“乙方”指代，这种情况因为被指代的名称是比较好获取的，可以直接加在chunk中，如下所示（伪代码），chunk_meta里面可以设置甲方乙方的全称：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;chunk&amp;gt; &amp;lt;chunk_meta&amp;gt; ... &amp;lt;/chunk_meta&amp;gt; &amp;lt;chunk_content&amp;gt; ... &amp;lt;/chunk_content&amp;gt; &amp;lt;/chunk&amp;gt; &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;附加元数据&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其实元数据的索引存储结构和上面的指代消解示例中差不多，不同的是，我们需要关心的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;元数据从哪里来？&lt;/li&gt;
&lt;li&gt;在检索的过程中什么情况下激活元数据过滤？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;元数据的来源只可能是&lt;strong&gt;人工标注&lt;/strong&gt;或者&lt;strong&gt;应用侧&lt;/strong&gt;（包括文件处理时）生成的，这里我们肯定先不去考虑人工标注了。从应用侧的设计来看，其实是可以拿到很多元数据的。比如我们上传文件、撰写知识的时候，自然可以拿到时间、文件名称（正常命名都会含有实体），也可以在交互设计中要求作者进行分类选择，简介编写等，无一不是增加元数据的手段。&lt;/p&gt;
&lt;p&gt;但我们在索引中增加了元数据内容之后，也不是强行要激活元数据过滤的。我们在实操中是会设置自定义的&lt;strong&gt;系统槽位（system_slot）&lt;/strong&gt;，如果用户提问的文本中，也包含比较多的意图和实体信息，且与系统槽位存在匹配，才会激活。会先使用BM25进行元数据过滤，再进行dense检索。更具语义信息的dense索引有时候因为维度限制会丢失一些信息，或者说dense检索对于元数据过滤并不那么精准。这种设计一方面可以减少检索的时间，另一方面就是可以提高带有时间和实体（如城市、公司、部门等）意图的提问的准确率。说起来这好像是一种“退步”，因为当LLM（或GPT）所向披靡的时候，我们也是一度有点看不上之前的技术的，但是在企业应用实践中，客户问题的解决率才是一切的根本。所以，在TorchV AI中，我们让NLU和Slot（槽位填充）回归了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-25-rag-de-2024-sui-xu-er-bian-cong-kuang-re-dao-li-xing-shang/002-725c8649.png"&gt;&lt;/p&gt;
&lt;p&gt;使用槽位填充的对话&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;是否使用Graph&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对了，还有一个大家争论比较多的索引组织方式就是Graph或者叫图数据，将node（实体）和relation（关系）通过图论组织起来。我们团队从2015年开始一直有在使用neo4j开发一些有趣的应用，在寻找人类直观上不太容易识别的点与点关联的方面确实非常出色。但是这个技术非常大的问题就是索引建立，从关系数据库把结构化数据迁移到Graph数据库中还算容易，但是面对成千上万的非结构化数据的时候，说实话目前也就是只能让GPT4以上的LLM来帮忙了。和重度使用过图知识库的朋友聊的过程中，他们的一致感受是：花费太高昂，但是场景太薄弱，依然还存在手工校对的工作。换句话说，如果花了10万的费用，但在实际使用中却没这么多使用场景，得不偿失。&lt;/p&gt;
&lt;p&gt;当然，如果您手头的工作非Graph不能解决，那就大胆去All-in吧。我们目前的系统并没有使用图数据库。&lt;/p&gt;
&lt;h5 id="03-检索方法"&gt;&lt;a href="#03-%e6%a3%80%e7%b4%a2%e6%96%b9%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;03 检索方法&lt;/strong&gt;
&lt;/h5&gt;&lt;p&gt;有句话（我说的）不一定说的对，在某种意义上，&lt;strong&gt;知识提取&lt;/strong&gt;决定了专有知识的&lt;strong&gt;完整性&lt;/strong&gt;，&lt;strong&gt;索引组织&lt;/strong&gt;决定了&lt;strong&gt;回答准确率&lt;/strong&gt;，而&lt;strong&gt;检索方法&lt;/strong&gt;则在减少幻觉上有重要意义。&lt;/p&gt;
&lt;p&gt;如果我们可以准确检索召回，把非常明确的内容加上指令prompt，给到LLM处理，那么LLM给出的结果几乎不太会一本正经胡说八道。只有检索召回的内容和用户原问题不相关，或者召回内容存在多个不置可否的知识内容时，LLM才会按心情（概率和 temperature）选择错误知识或者使用预训练时学习的知识进行回答。所以，检索做得好，可以将整个RAG幻觉尽可能多的变为白盒。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hybird&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是说到检索，我相信现在大部分的RAG系统都已经用上了Hybird检索了吧，一般来说也就是BM25+语义相似检索的混合检索。&lt;/p&gt;
&lt;p&gt;BM25有自己的固有用武之地，就像前面说的元数据过滤，还有就是一些在类似产品型号和专业术语的检索上，其精确度和稳定性是远高于语义相似检索的。&lt;/p&gt;
&lt;p&gt;语义相似检索有很多方法，因为我们主要用的还是Elasticsearch（也有Milvus），所以其实真正的语义相似检索就是ANN，说的更具体就是以HNSW为主的相似度算法。这个HNSW（最小可导航世界）的逻辑解释起来有点麻烦，但是我可以打一个比方：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;比如你需要从一个城市的南部坐公交去北部，我们要选择最短的坐车路线（含换乘），那么有两种方式选择：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;knn：将所有可能的公交路线（含换乘）做一个整理，假设有28900条路线，然后按花费时间进行排序，选择Top1或者Top n。抛开语义理解错误的问题，这种方法是非常精确的，但是耗时巨大，也可以认为是一种暴力检索；&lt;/li&gt;
&lt;li&gt;ann：还有一种就是相似最近邻，我们这里说到更多的是hnsw。为了让非技术专业的朋友可以听懂，不严谨地说，看着地图，从28900条路线中，选择出发地和目的地两点连线附近的50或100条公交路线。这种方法的效率极高，可能耗时只需要knn的万分之一，但它的问题在于无法确定这50或100条里面哪几条才是最好的。于是，如果你采用ann却不用rerank的话，就会比较拉垮了。&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;RRF Fusion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;RAG-Fusion主要是使用多个不同类型的检索方式进行检索，并按RRF（倒数排序融合）公式进行综合排名的一种检索方式。&lt;/p&gt;
&lt;p&gt;多种检索方式包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sparse（稀疏）检索，比如BM25；&lt;/li&gt;
&lt;li&gt;Dense（稠密）检索，比如语义相似度检索；&lt;/li&gt;
&lt;li&gt;还有就是使用不同配比的混合检索（在TorchV AI中，我们采用alpha值来做BM25和ANN的结果权重配比）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后使用RRF公式&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;RRF(d) = Σ(r ∈ R) 1 / (k + r(d))&lt;/p&gt;
&lt;p&gt;具体公式就不解释了，有兴趣的朋友自己查资料吧。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;来进行再排序，得出一个综合结果。其好处是使用不同的检索器，可以在各类不同问题场景下得到一个“思考”更周密的答案。我们在的系统里面有一个turbo开关，打开就会进行增强检索，其中也包括了该方法，且在召回率方面是有一定效果的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rerank&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其实我应该不用再讲reank了，如果您看过上面hnsw算法的话，就能知道rerank的作用了。TorchV AI在使用rerank的时候也加入了自己的一些优化算法，比如归一化处理和密度函数（舍弃相关性较低的返回结果）。在使用rerank前后，准确率相差确实很大，但你要平心而论，元数据过滤对准确率的提升可能会更明显。因为rerank相对比较被动（根据前序召回的结果，有时候是矮子里拔将军），而元数据过滤则是直接在检索召回环节产生影响，相对更加主动（自己对召回哪些结果起到重要作用）。嗯，这是我自己的理解，不一定对。&lt;/p&gt;
&lt;h4 id=""&gt;&lt;a href="#" class="header-anchor"&gt;&lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;3.&lt;strong&gt;技术部分小结&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OK，RAG技术变化我想先做个小结。&lt;/p&gt;
&lt;p&gt;关于RAG的大流程已经有太多文章了，我自己也写了不少，所以本文我更希望是从点而不是面的角度来讲一些技术实践上发现的变化。其实从RAG本身的各环节技术来说，没有出现新的现象级组件，2024这一年，看到更多的，是碰撞实际需求、探索最佳实践、内化到系统能力中的一个过程。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;市场变化的部分，下周差不多时间发布，敬请期待&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;如果您对TorchV AI的产品有兴趣，也可以直接联系我：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-25-rag-de-2024-sui-xu-er-bian-cong-kuang-re-dao-li-xing-shang/003-6d254516.jpg"&gt;&lt;/p&gt;</description></item><item><title>Java 集合 API 的改进</title><link>https://xiaobox.github.io/p/2024-11-12-java-ji-he-api-de-gai-jin/</link><pubDate>Tue, 12 Nov 2024 04:13:20 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-11-12-java-ji-he-api-de-gai-jin/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-12-java-ji-he-api-de-gai-jin/cover.jpg" alt="Featured image of post Java 集合 API 的改进" /&gt;&lt;h2 id="简介"&gt;&lt;a href="#%e7%ae%80%e4%bb%8b" class="header-anchor"&gt;&lt;/a&gt;简介
&lt;/h2&gt;&lt;p&gt;本文我们将探讨不同 jdk 版本中各类的起源，以及新引入的类和接口背后的目的。我们将分析之前版本存在的问题，以及为何需要引入新的类或接口。此外，我们还将介绍集合类和接口中的新特性。文章将逐一解答这些问题。&lt;/p&gt;
&lt;p&gt;我们将逐步学习 Java 集合类的优化过程，并按版本逐一对比分析。主要讨论的焦点将包括 JDK 1.0、1.2、1.4、1.5、1.6、1.8、9、10、11 和 21 版本的 Java 集合功能&lt;/p&gt;
&lt;h2 id="java-集合-api-的改进"&gt;&lt;a href="#java-%e9%9b%86%e5%90%88-api-%e7%9a%84%e6%94%b9%e8%bf%9b" class="header-anchor"&gt;&lt;/a&gt;Java 集合 API 的改进
&lt;/h2&gt;&lt;p&gt;Java 集合 API 在多年中经历了显著改进，引入了新功能、增强和优化，以提高开发者的生产力、改善性能，并适应修订的编程模式和需求。它将帮助开发者利用 Java 集合的力量构建更健壮、高效和可维护的应用程序。&lt;/p&gt;
&lt;h2 id="jdk-10-中的集合类"&gt;&lt;a href="#jdk-10-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;JDK 1.0 中的集合类
&lt;/h2&gt;&lt;p&gt;在 JDK 1.0 中，有四个类 &lt;code&gt;Vector&lt;/code&gt;、&lt;code&gt;Stack&lt;/code&gt;、&lt;code&gt;Hashtable&lt;/code&gt; 和 &lt;code&gt;Properties&lt;/code&gt;。此外，还有一个名为“&lt;code&gt;Enumeration&lt;/code&gt;”的接口，用于以简单的方式遍历值。进一步分类，Stack 是 Vector 的子类，Properties 是 Hashtable 的子类。&lt;/p&gt;
&lt;h3 id="vector-类的问题"&gt;&lt;a href="#vector-%e7%b1%bb%e7%9a%84%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;Vector 类的问题
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Vector 是线程安全的，即 Vector 中的所有方法都是同步的。因此，它不适合单线程环境。&lt;/li&gt;
&lt;li&gt;由于它在内部基于数组工作，插入和删除操作非常慢。&lt;/li&gt;
&lt;li&gt;它允许在其中添加重复元素&lt;/li&gt;
&lt;li&gt;无法按顺序存储元素&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="hashtable-类的问题"&gt;&lt;a href="#hashtable-%e7%b1%bb%e7%9a%84%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;Hashtable 类的问题
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Hashtable 是线程安全的，即 Hashtable 中的所有方法都是同步的。因此，它不适合单线程环境。&lt;/li&gt;
&lt;li&gt;Hashtable 无法按顺序存储条目&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="enumeration-的问题"&gt;&lt;a href="#enumeration-%e7%9a%84%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;Enumeration 的问题
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;无法删除元素且方法名称过长&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="jdk-12-中的集合类"&gt;&lt;a href="#jdk-12-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;JDK 1.2 中的集合类
&lt;/h2&gt;&lt;p&gt;在 JDK 1.2 中，Sun Micro-system 引入了 &lt;code&gt;ArrayList&lt;/code&gt;、&lt;code&gt;LinkedList&lt;/code&gt;、&lt;code&gt;HashSet&lt;/code&gt;、&lt;code&gt;TreeSet&lt;/code&gt;、&lt;code&gt;HashMap&lt;/code&gt;、&lt;code&gt;TreeMap&lt;/code&gt;、&lt;code&gt;Iterator&lt;/code&gt; 和 &lt;code&gt;ListIterator&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ArrayList：用于提供单线程环境下的解决方案，因为 ArrayList 中的方法不是同步的。&lt;/li&gt;
&lt;li&gt;LinkedList 用于提供更快的元素插入和删除。&lt;/li&gt;
&lt;li&gt;HashSet：不允许有重复元素。&lt;/li&gt;
&lt;li&gt;TreeSet：用于按排序顺序存储元素。&lt;/li&gt;
&lt;li&gt;HashMap：提供单线程环境下的解决方案，因为 HashMap 中的方法不是同步的。&lt;/li&gt;
&lt;li&gt;TreeMap：用于按顺序存储键值对。&lt;/li&gt;
&lt;li&gt;Iterator：用于解决枚举问题。同时还有一个专门处理列表的类 ListIterator。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;HashSet 的问题&lt;/strong&gt;：它不能保持插入顺序，即它不会按照元素添加到集合中的顺序存储元素。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HashMap 的问题&lt;/strong&gt;：像 HashSet 一样，它不能保持插入顺序。&lt;/p&gt;
&lt;h2 id="jdk-14-中的集合类"&gt;&lt;a href="#jdk-14-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;JDK 1.4 中的集合类
&lt;/h2&gt;&lt;p&gt;在 JDK 1.2 中，Sun Microsystems 引入了 &lt;code&gt;LinkedHashSet&lt;/code&gt; 和 &lt;code&gt;LinkedHashMap&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LinkedHashSet：用于解决 HashSet 中插入顺序的问题。它按照元素添加到集合中的顺序存储元素。&lt;/li&gt;
&lt;li&gt;LinkedHashMap：用于解决 HashMap 中插入顺序的问题。它还按照元素添加到集合中的顺序存储元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="jdk-15-中的集合类"&gt;&lt;a href="#jdk-15-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;JDK 1.5 中的集合类
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;for-Each 循环：作为替代迭代器进行迭代的另一种方法&lt;/li&gt;
&lt;li&gt;CopyOnWriteArrayList：引入以允许在修改底层列表的情况下安全地迭代元素。&lt;/li&gt;
&lt;li&gt;CopyOnWriteArraySet：它使用内部 CopyOnWriteArrayList 进行所有操作。因此，它具有与该列表相同的基本属性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="jdk-16-中的集合类"&gt;&lt;a href="#jdk-16-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;JDK 1.6 中的集合类
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;NavigableSet：作为扩展了导航方法的有序集合，用于报告给定搜索目标的最近匹配。&lt;/li&gt;
&lt;li&gt;NavigableMap：作为扩展了导航方法的 SortedMap，返回给定搜索目标的最近匹配项。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-12-java-ji-he-api-de-gai-jin/001-b283acb5.jpg"&gt;&lt;/p&gt;
&lt;h2 id="jdk-18-中的集合类"&gt;&lt;a href="#jdk-18-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;JDK 1.8 中的集合类
&lt;/h2&gt;&lt;p&gt;Java 集合框架也有新更新，以支持 lambda 表达式、流和聚合操作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stream() 作为父接口 Collection 的默认方法：返回一个以该集合为源的顺序 Stream。&lt;/li&gt;
&lt;li&gt;parallelStream() 作为父接口 Collection 的默认方法：返回一个可能并行的 Stream，以这个集合作为其源。&lt;/li&gt;
&lt;li&gt;spliterator() 作为父接口 Collection 的一个默认方法：创建一个遍历此集合中元素的 Spliterator&lt;/li&gt;
&lt;li&gt;removeIf(Predicate filter) 作为父接口 Collection 的默认方法：移除满足给定谓词的所有元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同样重要的是，这里的一个显著点是所有新添加的方法都是接口 Collection 内部的默认方法。这是使用默认方法的最佳示例。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-12-java-ji-he-api-de-gai-jin/002-bf3a4631.png"&gt;&lt;/p&gt;
&lt;h2 id="java-9-中的集合增强"&gt;&lt;a href="#java-9-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e5%a2%9e%e5%bc%ba" class="header-anchor"&gt;&lt;/a&gt;Java 9 中的集合增强
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;新增用于创建不可变列表、集合和映射的 of() 静态工厂方法介绍。这些方法包括：&lt;code&gt;List.of()&lt;/code&gt;, &lt;code&gt;Set.of()&lt;/code&gt;, &lt;code&gt;Map.of()&lt;/code&gt;, &lt;code&gt;Map.ofEntries()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Arrays.mismatch()：新增方法以查找两个数组中第一个不匹配的索引。&lt;/li&gt;
&lt;li&gt;Arrays.compare()：添加了新方法来比较提供的两个数组中的元素。&lt;/li&gt;
&lt;li&gt;为 Arrays.equals() 添加了更多重载方法。&lt;/li&gt;
&lt;li&gt;Enumeration.asIterator()：添加了返回 java.util.Iterator 实例的新方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，在 &lt;code&gt;Stream&lt;/code&gt; API 中添加了一些方法，如 dropWhile、takeWhile 和 ofNullable。&lt;/p&gt;
&lt;h2 id="java-10-中的集合增强"&gt;&lt;a href="#java-10-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e5%a2%9e%e5%bc%ba" class="header-anchor"&gt;&lt;/a&gt;Java 10 中的集合增强
&lt;/h2&gt;&lt;p&gt;引入了 List.copyOf()、Set.copyOf() 和 Map.copyOf()，用于创建现有集合的不变副本。&lt;/p&gt;
&lt;h2 id="java-11-中的集合增强"&gt;&lt;a href="#java-11-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e5%a2%9e%e5%bc%ba" class="header-anchor"&gt;&lt;/a&gt;Java 11 中的集合增强
&lt;/h2&gt;&lt;p&gt;Collection.toArray(IntFunction)：添加了新的默认方法，允许将集合的元素转移到新创建的具有所需运行时类型的数组中。新方法是现有 toArray(…) 方法的重载变体。&lt;/p&gt;
&lt;h2 id="java-21-中的集合增强"&gt;&lt;a href="#java-21-%e4%b8%ad%e7%9a%84%e9%9b%86%e5%90%88%e5%a2%9e%e5%bc%ba" class="header-anchor"&gt;&lt;/a&gt;Java 21 中的集合增强
&lt;/h2&gt;&lt;p&gt;Java 21 在集合框架中引入了三个新接口：&lt;code&gt;SequencedCollection&lt;/code&gt;、&lt;code&gt;SequencedSet&lt;/code&gt; 和 &lt;code&gt;SequencedMap&lt;/code&gt;。这些新的集合接口通过新库提供的默认方法，使我们能够访问其第一个和最后一个元素。该功能还允许我们通过简单的调用方法来获取集合的反转视图。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-12-java-ji-he-api-de-gai-jin/003-e53f56fa.png"&gt;&lt;/p&gt;
&lt;h3 id="sequencedcollection--序列集合"&gt;&lt;a href="#sequencedcollection--%e5%ba%8f%e5%88%97%e9%9b%86%e5%90%88" class="header-anchor"&gt;&lt;/a&gt;SequencedCollection 序列集合
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;default void addFirst(E e)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;default void addLast(E e)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;default E getFirst()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;default E getLast()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;default E removeFirst()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;default E removeLast()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;SequencedCollection&amp;lt;E&amp;gt; reversed()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="sequencedset-序列集合"&gt;&lt;a href="#sequencedset-%e5%ba%8f%e5%88%97%e9%9b%86%e5%90%88" class="header-anchor"&gt;&lt;/a&gt;SequencedSet 序列集合
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;SequencedSet&amp;lt;E&amp;gt; reversed()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="sequencedmap-序列映射"&gt;&lt;a href="#sequencedmap-%e5%ba%8f%e5%88%97%e6%98%a0%e5%b0%84" class="header-anchor"&gt;&lt;/a&gt;SequencedMap 序列映射
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;default Map.Entry&amp;lt;K,V&amp;gt; firstEntry()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;default Map.Entry&amp;lt;K,V&amp;gt; lastEntry()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;default Map.Entry&amp;lt;K,V&amp;gt; pollFirstEntry()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;default Map.Entry&amp;lt;K,V&amp;gt; pollLastEntry()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;default V putFirst(K k, V v)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;default V putLast(K k, V v)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;SequencedMap&amp;lt;K,V&amp;gt; reversed()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;default SequencedSet&amp;lt;Map.Entry&amp;lt;K,V&amp;gt;&amp;gt; sequencedEntrySet()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;default SequencedSet&amp;lt;K&amp;gt; sequencedKeySet()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;default SequencedCollection&amp;lt;V&amp;gt; sequencedValues()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>如何用 30秒和 5 行代码写个 RAG 应用？</title><link>https://xiaobox.github.io/p/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/</link><pubDate>Mon, 30 Sep 2024 16:17:06 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/cover.jpg" alt="Featured image of post 如何用 30秒和 5 行代码写个 RAG 应用？" /&gt;&lt;p&gt;花 30 秒用 5 行代码写个 RAG 应用&lt;/p&gt;
&lt;p&gt;这个点子不是我想出来的，是 llamaindex 想出来的。llamaindex 是啥？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LlamaIndex 是一个框架，用于使用 llm（包括 agents 和 workflows）构建上下文增强的生成式人工智能应用程序。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当然也包括创建 RAG 应用，因为最流行的上下文增强示例就是 RAG（Retrieval-Augmented Generation， RAG），也就是在推理时将上下文与 llm 结合起来的检索增强。&lt;/p&gt;
&lt;p&gt;如果是对 RAG 还不了解的朋友，那么可以参考之前几篇文章中的相关介绍：&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247489151&amp;amp;idx=1&amp;amp;sn=2f2dc98798b88399322f5be8dd253057&amp;amp;chksm=eb6da9f9dc1a20efe6dc6ae4bf52eeb8c8521c4164f50941881fddf49093a5425810abbcf325&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;RAG 实践-Ollama+AnythingLLM 搭建本地知识库&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247489440&amp;amp;idx=1&amp;amp;sn=5a25f4ee0c5c29de6a6c1b9d4aca19ee&amp;amp;chksm=eb6da826dc1a2130d3549a8e629dca81b45c3733005c2fa173e04d66d8c64560856effeae0c9&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;RAG（检索增强生成）系统的问题及解决思路&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;简单来说：RAG（Retrieval-Augmented Generation）是一种结合检索和生成技术的人工智能方法，通过从外部知识源检索相关信息，增强语言模型的生成能力，提高输出的准确性和相关性。&lt;/p&gt;
&lt;h2 id="llamaindex"&gt;&lt;a href="#llamaindex" class="header-anchor"&gt;&lt;/a&gt;LlamaIndex
&lt;/h2&gt;&lt;p&gt;在进入正题前，关于 LlamaIndex 我还是要再多说几句。&lt;/p&gt;
&lt;p&gt;LlamaIndex 是一个开源框架：https://github.com/run-llama/llama_index&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;它是用 &lt;code&gt;Python&lt;/code&gt; 写的&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/001-3d0fc37b.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最新版本是：&lt;code&gt;0.11.14&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/002-d8af0b28.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;官网地址是：&lt;code&gt;https://www.llamaindex.ai/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/003-1eed1656.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在今年夏天的某个会议上，从 LlamaIndex 团队成员那儿得知，他们是一个规模很小的创业公司，当时团队成员大概 15 人左右。&lt;/p&gt;
&lt;h2 id="30-秒-5-行代码"&gt;&lt;a href="#30-%e7%a7%92-5-%e8%a1%8c%e4%bb%a3%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;30 秒 5 行代码
&lt;/h2&gt;&lt;p&gt;前文说了 30 秒 5 行代码的主意不是我想到的，而是来源于 LlamaIndex ，具体说是 LlamaIndex 的文档中写的：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/004-cca65b59.png"&gt;&lt;/p&gt;
&lt;p&gt;事实也的确如此，但是，这里有一些前提条件。&lt;/p&gt;
&lt;p&gt;首先，从字面上你也能看出来，运行这 5 行代码需要 &lt;code&gt;OpenAI&lt;/code&gt; 的 API key。&lt;/p&gt;
&lt;p&gt;其次，无论你懂不懂编程，我告诉你这 5 行代码是 &lt;code&gt;Python&lt;/code&gt; 语言写的。那么你要有一个 &lt;code&gt;Python&lt;/code&gt; 语言的运行环境，以及你最好懂得如何用 &lt;code&gt;Python&lt;/code&gt; 编程。关于这一点，对于不懂编程的朋友确实不太友好了。不过值得庆幸的是，&lt;code&gt;Python&lt;/code&gt; 语言本身很强大，很好上手，容易学习。门槛并不算太高。虽然我们要开发的应用是跟人工智能（AI）相关的，但也不要被吓到了，以为会多么难。其实有很大一部分所谓的 AI 产品也只是 AI、大模型的应用产品。开发那些产品的工程师甚至也只是能算是大模型应用开发工程师，还用不到多么艰深的技术呢。（&lt;strong&gt;这里并没有拉踩的意思，只是客观表述。能把技术应用的很好，做出满足用户需求的创新性产品也是非常有价值的，做出这样产品的人也当然值得尊重&lt;/strong&gt;）&lt;/p&gt;
&lt;h3 id="安装-python"&gt;&lt;a href="#%e5%ae%89%e8%a3%85-python" class="header-anchor"&gt;&lt;/a&gt;安装 Python
&lt;/h3&gt;&lt;p&gt;我们先解决 &lt;code&gt;Python&lt;/code&gt; 的问题，对于已经熟练掌握 &lt;code&gt;Python&lt;/code&gt; 开发的就可以跳过了。对于编程小白或其他语言开发者（如 Java） 可以看看。&lt;/p&gt;
&lt;p&gt;我们得搭建一个 &lt;code&gt;Python&lt;/code&gt; 的开发和运行环境。关于 &lt;code&gt;Python&lt;/code&gt; 的安装网上教程一大堆，无论你的操作系统是 Windows、Mac 还是 Linux 都很容易。我就不罗嗦了。&lt;/p&gt;
&lt;p&gt;我的操作系统是 MacOS, 后文的具体操作细节都是基于我本地的 Mac 电脑，所以请大家注意，下图是我的的电脑系统情况：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/005-fbb462bd.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Python&lt;/code&gt; 比较常见的大版本有 &lt;code&gt;Python2&lt;/code&gt; 和 &lt;code&gt;Python3&lt;/code&gt; , 我使用的版本是 &lt;code&gt;Python3&lt;/code&gt;，具体来说是 &lt;code&gt;3.12.4&lt;/code&gt; 算是个比较新的版本了&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/006-548175e5.png"&gt;&lt;/p&gt;
&lt;p&gt;关于 &lt;code&gt;Python&lt;/code&gt; 的安装，我更建议用 &lt;code&gt;Conda&lt;/code&gt; ，它可以创建多个不同 &lt;code&gt;Python&lt;/code&gt; 版本的环境，相当于一个 &lt;code&gt;Python&lt;/code&gt; 环境和版本管理工具。这样你就可以创建多个不同 &lt;code&gt;Python&lt;/code&gt; 版本的环境，相互之间隔离，互不影响。因为有时候不同的项目用到不同的 &lt;code&gt;Python&lt;/code&gt; 版本，混在一起比较麻烦，有了 &lt;code&gt;Conda&lt;/code&gt; 就方便多了。&lt;/p&gt;
&lt;p&gt;Conda 可以到这里下载安装：https://docs.anaconda.com/free/miniconda/&lt;/p&gt;
&lt;p&gt;安装好以后记得设置一下镜像源，不然下载比较慢：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/pro/ 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;conda config --set show_channel_urls yes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;以下是一些基础命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;//创建虚拟环境
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;conda create -n xinference python=3.11.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;//激活虚拟环境
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;conda activate xinference
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;//退出虚拟环境
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;conda deactivate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;//删除虚拟环境
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;conda remove -n xinference --all
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;//查看虚拟环境
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;conda env list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Python&lt;/code&gt; 安装好以后，我们还需要一个开发工具，即 IDE，你可以选择 &lt;code&gt;VSCode&lt;/code&gt;,&lt;code&gt;PyCharm&lt;/code&gt;,&lt;code&gt;Cursor&lt;/code&gt;,&lt;code&gt;Zed&lt;/code&gt; 等等。这里我推荐 &lt;code&gt;Cursor&lt;/code&gt;，因为借用 claude 及 gpt 等大模型的能力，你可以一边快速开发一边快速学习，高效产出的同时高效吸收，简直太爽啦。&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/007-4a4f0c2a.png"&gt;&lt;/p&gt;
&lt;h3 id="学习-python"&gt;&lt;a href="#%e5%ad%a6%e4%b9%a0-python" class="header-anchor"&gt;&lt;/a&gt;学习 Python
&lt;/h3&gt;&lt;p&gt;关于 &lt;code&gt;Python&lt;/code&gt; 的学习，我这里对于特别小白的，尤其是从来没有接触过编程的朋友没有什么具体的建议，因为我从未指导过类似情况的朋友，所以没有经验，不能乱说，怕误人子弟。因为我既不知道用什么样的方式也不知道用什么样的资料指导有用。不过我觉得对于小白，能持续学习下去是最重要的，不要找太难的资料，可以找一些你容易上手，容易看懂的资料来学习，有持续的正反馈，计算机相关的知识很多，有些确实难懂，有持续的正反馈你才不至于半途而废。&lt;/p&gt;
&lt;p&gt;而对于其他语言的开发者，其实已经具备了基础的计算机知识，甚至也已经是某些编程语言的专家了。那么学习 &lt;code&gt;Python&lt;/code&gt; 对于你来说就比较容易了，一理通百理明。都大差不差的。&lt;/p&gt;
&lt;p&gt;我这里把之前收藏的一些学习资料分享一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/jackfrued/Python-for-Freshmen-2023" target="_blank" rel="noopener"
 &gt;https://github.com/jackfrued/Python-for-Freshmen-2023&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/jackfrued/Python-100-Days" target="_blank" rel="noopener"
 &gt;https://github.com/jackfrued/Python-100-Days&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/walter201230/Python" target="_blank" rel="noopener"
 &gt;https://github.com/walter201230/Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.fullstackpython.com" target="_blank" rel="noopener"
 &gt;https://www.fullstackpython.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;认认真真把其中一个资料看完。&lt;strong&gt;一般来说，用 2 个星期，每天 1-2 个小时左右，快的话甚至一个星期你就能基本掌握这门语言了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我知道很多朋友不满足只是基本掌握，想更精进，那么就需要再加码了，这里我推荐系统地看一些书，因为书籍会相对系统地讲解知识，这样你对 &lt;code&gt;Python&lt;/code&gt; 以及 &lt;code&gt;Python&lt;/code&gt; 相关的技术就会有一个全面而深入的了解了。&lt;/p&gt;
&lt;p&gt;说实话我看的书并不多，也不好意思过多推荐，这里推荐一本我最喜欢的，豆瓣 9.1 分。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/008-2c47ea4f.png"&gt;&lt;/p&gt;
&lt;p&gt;写得确实很好，很实用，不讲虚的。读起来也很流畅、舒服。是市面上难得的原创 &lt;code&gt;Python&lt;/code&gt; 进阶图书。&lt;/p&gt;
&lt;h3 id="编程"&gt;&lt;a href="#%e7%bc%96%e7%a8%8b" class="header-anchor"&gt;&lt;/a&gt;编程
&lt;/h3&gt;&lt;p&gt;现在我们假设你已经有了一个 &lt;code&gt;Python&lt;/code&gt; 的运行环境及开发工具。那么接下来我们就要正式开始编写这 5 行代码了（铺垫这么多终于要写代码了～）&lt;/p&gt;
&lt;p&gt;首先我们要建一个工程，这很简单，不多说。&lt;/p&gt;
&lt;p&gt;然后根据文档所示，我们要安装 &lt;code&gt;llama-index&lt;/code&gt; 的依赖包，在项目根路径下执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;pip install llama-index
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当然，如果你使用的是 &lt;code&gt;Python3&lt;/code&gt; ，可以这样安装：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;pip3 install llama-index
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;安装好依赖包以后我们创建 main.py 文件，并编写程序：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;query_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_query_engine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Some question about the data should go here&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;别看这段只有几行代码，却有好几个问题，我们一个一个地说。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一个问题是&lt;/strong&gt;：如果你直接运行 main.py 这个文件会报错，错误总结来说就是你没有 &lt;code&gt;OpenAI&lt;/code&gt; 的 Api Key 。是啊，我们压根就没有设置，其实我也不想设置，因为这个 key 是要花钱的，我不想花钱，那怎么办？&lt;/p&gt;
&lt;p&gt;用 &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; 的目的就是要通过 &lt;code&gt;OpenAI&lt;/code&gt; 的 API 调用 &lt;code&gt;OpenAI&lt;/code&gt; 的大语言模型。我们知道那是收费的，所以我们要用开源免费的模型，将模型安装到本地使用，这样就不用花钱了。所以我们要用 &lt;code&gt;Ollama&lt;/code&gt; 安装开源模型到本地进行调用。关于 &lt;code&gt;Ollama&lt;/code&gt; 以及模型的安装我在之前的文章中有详细说明，这里就不赘述了。大家可以参考。&lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247489151&amp;amp;idx=1&amp;amp;sn=2f2dc98798b88399322f5be8dd253057&amp;amp;chksm=eb6da9f9dc1a20efe6dc6ae4bf52eeb8c8521c4164f50941881fddf49093a5425810abbcf325&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;RAG 实践-Ollama+AnythingLLM 搭建本地知识库&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我这里下载使用的是 &lt;code&gt;Qwen2:7b&lt;/code&gt; 的模型&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/009-74fbf1b3.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二个问题是&lt;/strong&gt;：代码中的 &lt;code&gt;data&lt;/code&gt; 在哪里&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;data 是一个文件夹，需要我们在项目的根路径下创建，名字就叫 &lt;code&gt;data&lt;/code&gt;。而在 data 文件夹中我们是要下载测试文本的，通过这个地址下载测试文本：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://raw.githubusercontent.com/run-llama/llama" target="_blank" rel="noopener"
 &gt;https://raw.githubusercontent.com/run-llama/llama&lt;/a&gt;_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;文件类型当然就是 .txt 文件。原文是英文的，但因为我想做中文的测试，所以，我把内容全部翻译成了中文并保存。（如果你看过《黑客与画家》 这本书，你一定会对文本内容感兴趣的！） 这个文件的文件名你可以随意取。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三个问题是&lt;/strong&gt;：用哪个 &lt;code&gt;embedding&lt;/code&gt; 模型？&lt;/p&gt;
&lt;p&gt;前面我们说了，我们不用 &lt;code&gt;OpenAI&lt;/code&gt; 的 API 了，这样的话，其实整个代码结构会发生变化 ，就不是原始的那 5 行代码了，而会变成下面这样（别担心，只多了一行）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;huggingface&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbedding&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# bge-base embedding model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embed_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;BAAI/bge-base-en-v1.5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ollama&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;llama3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;360.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到代码中写的 &lt;code&gt;embedding&lt;/code&gt; 模型是 &lt;code&gt;BAAI/bge-base-en-v1.5&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;接触过 RAG 的朋友对 &lt;code&gt;embedding&lt;/code&gt; 模型比较熟悉，这里简单地为不了解的朋友解释一下：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Embedding 模型是将离散的输入（如单词或文档）转换为连续向量表示的模型，在 RAG 中用于将查询和检索到的文档片段映射到同一向量空间，以便计算相似度和生成相关响应。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它和 RAG 的关系，可以参考下图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/010-2e3cae67.png"&gt;&lt;/p&gt;
&lt;p&gt;我们这 5 行代码想实现的就是 RAG，所以一定少不了 &lt;code&gt;embedding&lt;/code&gt; 模型。&lt;code&gt;embedding&lt;/code&gt; 模型也分收费的和开源免费的，另外上文中提到的 &lt;code&gt;BAAI/bge-base-en-v1.5&lt;/code&gt; 是一个处理英文的模型，我想处理的是中文，所以不适用。我们要找一个免费开源且支持中文的 &lt;code&gt;embedding&lt;/code&gt; 模型。到哪里找呢？Hugging Face!（Hugging Face 你可以把它理解成大模型领域的 GitHub）&lt;/p&gt;
&lt;p&gt;从 Hugging Face 上可以找到大量的开源免费的 &lt;code&gt;embedding&lt;/code&gt; 模型，数量很多，选哪一个呢？我们可以从 &lt;a class="link" href="https://huggingface.co/spaces/mteb/leaderboard" target="_blank" rel="noopener"
 &gt;https://huggingface.co/spaces/mteb/leaderboard&lt;/a&gt; 这个大规模文本嵌入基准（MTEB）排行榜中，根据你的需求来挑选。&lt;/p&gt;
&lt;p&gt;比如我选择的是支持中文的，模型大小不是特别大的&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/011-d95f597e.png"&gt;&lt;/p&gt;
&lt;p&gt;最终我选择的模型是 ：&lt;code&gt;BAAI/bge-base-zh-v1.5&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;以上三个问题都解决了以后，我们看一下最终的代码成品：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.embeddings.huggingface&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbedding&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.llms.ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embed_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;BAAI/bge-base-zh-v1.5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;qwen2:7b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;360.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;query_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_query_engine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;作者学习过的编程语言有哪些？&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;实话实说，是比 5 行多了 2 行。但也已经很精练了，因为这是 LlamaIndex 做过高级别封装以后的 API，如果想做具体而细致的编程控制，可以使用低级别封装的 API。&lt;/p&gt;
&lt;p&gt;代码我没有写注释，因为是想让读者看看它有多精练。用 LlamaIndex 就这么简单，几行代码就可以实现 RAG 了。&lt;/p&gt;
&lt;p&gt;以下是我加入注释以后的，方便你理解它：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.embeddings.huggingface&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbedding&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.llms.ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 从指定目录加载文档数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 设置嵌入模型，使用北京智源人工智能研究院的中文嵌入模型&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embed_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;BAAI/bge-base-zh-v1.5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 设置语言模型，使用 Ollama 提供的 Qwen2 7B 模型，并设置请求超时时间&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;qwen2:7b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;360.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 使用加载的文档创建向量存储索引&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 从索引创建查询引擎&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;query_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_query_engine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 使用查询引擎执行特定查询&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;作者学习过的编程语言有哪些？&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 打印查询结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;运行这段代码会自动下载 &lt;code&gt;embedding&lt;/code&gt; 模型，你可能会关心模型下载到哪里了，在我电脑上是这个路径 ：&lt;code&gt;～/Library/Caches/llama_index&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;代码 第一次执行时间比较长，大概有个几十秒。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/012-92563e3b.png"&gt;&lt;/p&gt;
&lt;p&gt;但再次执行应该是有缓存了，就会比较快了，下图就只执行了 10 秒左右。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-30-ru-he-yong-30-miao-he-5-xing-dai-ma-xie-ge-rag-ying-yong/013-e926c405.png"&gt;&lt;/p&gt;
&lt;p&gt;当然，你还可以基于测试文本进行其他查询，看看它分析的是否准确。&lt;/p&gt;
&lt;p&gt;以上图片中的输出每一步都有时间，是因为我对程序做了重构，用装饰器加上了每一步执行时间的打印输出，代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wraps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.embeddings.huggingface&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbedding&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;llama_index.llms.ollama&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;time_it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 耗时：&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 秒&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IndexBuilder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@time_it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@time_it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_embed_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embed_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;BAAI/bge-base-zh-v1.5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@time_it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_llm_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Ollama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;qwen2:7b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;360.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@time_it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;34&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;35&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@time_it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;36&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;37&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;query_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_query_engine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;38&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;query_engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;39&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;40&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@time_it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;41&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;42&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IndexBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;43&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;44&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_documents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;45&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_embed_model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;46&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_llm_model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;47&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;48&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;49&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;50&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;perform_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;作者跟 Sam 的关系是怎样的？&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;51&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;52&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;查询结果：&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;53&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;54&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;55&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;56&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;由于后续除了 LlamaIndex 又安装了几个依赖库，所以在项目根路径下创建了 requirements.txt 文件，文件内容如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;llama-index
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;python-dotenv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;llama-index-llms-ollama
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;llama-index-embeddings-huggingface
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行以下命令一次性安装所有依赖：&lt;code&gt;pip3 install -r requirements.txt&lt;/code&gt; 这样方便一些。&lt;/p&gt;
&lt;h2 id="rag-应用创建完成"&gt;&lt;a href="#rag-%e5%ba%94%e7%94%a8%e5%88%9b%e5%bb%ba%e5%ae%8c%e6%88%90" class="header-anchor"&gt;&lt;/a&gt;RAG 应用创建完成
&lt;/h2&gt;&lt;p&gt;有了以上的代码基础，其实一个小型的 RAG 应用的核心就完成了，我可以基于本地知识库结合大语言模型进行自然语言的查询了。&lt;/p&gt;
&lt;p&gt;比如我问：“&lt;strong&gt;作者跟 Sam 的关系是怎样的？&lt;/strong&gt;”&lt;/p&gt;
&lt;p&gt;回答是：“&lt;strong&gt;作者与 Sam Altman 的关系是在 2013 年决定让他成为 YC（Y Combinator）的总裁。在那之前，他们可能有某种工作或业务上的联系，因为他们讨论了重组 YC 并让 Sam 接任总裁职位的事情。通过这个决策，可以看出作者认为 Sam 适合领导 YC，并且在 Sam 最初拒绝后，作者坚持不懈地说服他接受这一角色。最终，在 2013 年 10 月，Sam 同意从 2014 年冬季开始接管 YC。这表明两人之间有某种程度的合作和信任关系。&lt;/strong&gt;”&lt;/p&gt;
&lt;p&gt;你看，基于本地知识库的回答比单纯用 LLM 靠谱多了吧。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;然后呢？&lt;/strong&gt; 是的，我们只有一个内核还远远不够，我们还需要漂亮的 UI，更加易用和丰富的功能，程序性能还要强，把它做成一个产品。然后产品还要宣传、推广、积累用户、产品迭代。我们还要赚钱，还要考虑如何盈利。&amp;hellip;..&lt;/p&gt;
&lt;p&gt;差不多了，真的。想到这里，你再看看市面上那些 AI 产品是不是相似的配方？&lt;/p&gt;
&lt;h2 id="最后"&gt;&lt;a href="#%e6%9c%80%e5%90%8e" class="header-anchor"&gt;&lt;/a&gt;最后
&lt;/h2&gt;&lt;p&gt;行文至此，关于这 5 行代码的事情我觉得已经说清楚了。最后感慨一下：AI 赛道真是越来越卷了，但无论无何，感谢 &lt;code&gt;Python&lt;/code&gt; , 感谢 &lt;code&gt;LlamaIndex&lt;/code&gt; ，感谢开源和为开源做出贡献的人们。有了他们我们才能够如此享受技术带来的红利。&lt;/p&gt;</description></item><item><title>三分钟解决 Java 进程 cpu 100% 问题</title><link>https://xiaobox.github.io/p/2024-08-22-san-fen-zhong-jie-jue-java-jin-cheng-cpu-100-wen-ti/</link><pubDate>Thu, 22 Aug 2024 08:04:00 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-08-22-san-fen-zhong-jie-jue-java-jin-cheng-cpu-100-wen-ti/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-22-san-fen-zhong-jie-jue-java-jin-cheng-cpu-100-wen-ti/cover.jpg" alt="Featured image of post 三分钟解决 Java 进程 cpu 100% 问题" /&gt;&lt;h2 id="起因"&gt;&lt;a href="#%e8%b5%b7%e5%9b%a0" class="header-anchor"&gt;&lt;/a&gt;起因
&lt;/h2&gt;&lt;p&gt;上午有小伙伴说某个 java 服务响应特别慢，看了一下监控，很多请求都超时了，服务是活着的，但响应非常慢。&lt;/p&gt;
&lt;p&gt;由于服务是多容器负载均衡调度部署的，又花了一点时间定位到具体的宿主机。仔细查下来发现，并不是部署该服务的所有容器都有问题，仅仅是某一个有问题。&lt;/p&gt;
&lt;p&gt;具体的问题是某 java 进程的 cpu 占用率 打满了，是 100% 。&lt;/p&gt;
&lt;p&gt;这里要跟大家说的是，如果某关键服务的所有副本全部出问题了，那么第一时间并不是揪着问题不放，跟那儿使劲查。而是先尝试恢复服务，如果系统在架构上的弹性和容错做的比较好，可能不会造成 “雪崩”，导致全系统异常，无法使用。但是注意，我说的是“关键服务”，就算你隔离、熔断做的再好，关键服务不能用了，整个系统还是不能用，对用户来说就是 “挂了” ，比如你出地铁想扫个共享单车，扫码没问题，但就是开不了锁（开锁服务出问题了）。&lt;/p&gt;
&lt;p&gt;对于我们今天遇到的问题来说，还好，没那么严重，观察下来只是某服务的某副本出了问题。这里我根据系统的具体情况和观察到的情况做了个决定：&lt;strong&gt;先不回滚服务，一边观察一边收集事故现场信息，同时让一位同事准备好，如果事故进一步恶化就立刻回滚。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其实就算 “回滚” 也是有问题的，因为如果要考虑周全的话，仔细一想就会有一些问题，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;回滚单个服务，看起来可行，但实际上问题多多，因为一般情况下，一次 “上线”，会涉及很多服务，相关的上下游都可能会有所牵涉，你如果只单单的回滚了出问题的那个服务 ，其他相关服务可还在最新状态呢，到时候又会引发新的问题。除非你对这个服务了如指掌，非常清楚迭代的内容，也能够判断出回滚的影响可控，否则只回滚单个服务，在大型分布式系统中是有风险的。&lt;/li&gt;
&lt;li&gt;版本回滚，这也是一些公司的常规做法。尤其是出了比较严重的问题，那么一次上线的全部内容都要完整地回滚，而不仅仅是回滚出问题的那个服务。但这也比较麻烦，因为首先 “全部回滚” 的时长可能比较长，时间一长对用户的影响就比较大。另外，回滚后还要处理从上线到回滚完成这段时间以来产生的 “脏数据”。总之复杂度高了，要考虑的问题也多。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;好了，关于回滚，我这里就点到为指。总而言之，还是要根据你系统的具体情况来决定。谨慎、灵活地处置。&lt;/p&gt;
&lt;h2 id="问题解决过程"&gt;&lt;a href="#%e9%97%ae%e9%a2%98%e8%a7%a3%e5%86%b3%e8%bf%87%e7%a8%8b" class="header-anchor"&gt;&lt;/a&gt;问题解决过程
&lt;/h2&gt;&lt;p&gt;上文我说问题只要服务的某个副本上出了问题，你可能会觉得不对，代码都一样的，怎么可能就只有一个副本有问题呢？&lt;/p&gt;
&lt;p&gt;所以先解释下原因，最后事故处理完我们分析代码发现，原来是某个特殊的用户在某个特殊的条件下触下了某个 bug。那部分代码确实只会在这么特殊的条件下才会运行，而且这个动作的执行频率相当低。所以就是这么巧。&lt;/p&gt;
&lt;p&gt;我们进入正题，首先在宿主机上看到某 java 进程的 CPU 占用率是 100% 。由于我们所有的 java 进程全部是 docker 容器启动的，所以要找到具体是宿主机的哪个 docker 容器，这里我用了这个命令：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker ps -q | xargs docker inspect --format '{{.State.Pid}}, {{.Id}}' | grep 2880 &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;假设在宿主机上的这个 java 进程的 pid 为 2880。这行命令就是找出 pid 对应的具体是哪个 docker 容器。详解说明一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker ps&lt;/code&gt; 是用来列出当前运行中的 Docker 容器的命令。-q 选项告诉 docker ps 仅输出每个容器的 ID，而不是完整的容器信息列表。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;|&lt;/code&gt;（管道操作符）：管道操作符用于将前一个命令的输出作为下一个命令的输入。这里，它将 &lt;code&gt;docker ps -q&lt;/code&gt; 命令输出的容器 ID 列表传递给下一个命令。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xargs docker inspect --format '{{.State.Pid}}, {{.Id}}'&lt;/code&gt;：xargs 是一个将输入数据转换为其他命令行参数的工具。在这里，它将 &lt;code&gt;docker ps -q&lt;/code&gt; 输出的容器 ID 列表转换为 &lt;code&gt;docker inspect&lt;/code&gt; 命令的参数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker inspect&lt;/code&gt; 是用于获取有关 Docker 容器的详细信息的命令。&lt;code&gt;--format '{{.State.Pid}}, {{.Id}}'&lt;/code&gt; 是一个格式化选项，它指定了 &lt;code&gt;docker inspect&lt;/code&gt; 命令的输出格式。这里使用了 Go 模板语法来指定只输出每个容器的进程 ID（PID）和容器 ID。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;|&lt;/code&gt;（管道操作符）：再次使用管道操作符，将 &lt;code&gt;docker inspect&lt;/code&gt; 命令的输出传递给下一个命令。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grep 2880&lt;/code&gt;：grep 是一个文本搜索工具，用于搜索包含特定模式的字符串。2880 是要搜索的字符串，代表一个进程 ID（PID）。这个命令会从输入中筛选出包含数字 2880 的行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;将所有部分结合起来，这行命令的作用是：首先，列出所有运行中的 Docker 容器的 ID。然后，对每个容器 ID 执行 docker inspect，以获取每个容器的 PID 和 ID，并以特定格式输出。最后，从这些输出中筛选出 PID 为 2880 的容器，并显示其 PID 和 ID&lt;/p&gt;
&lt;p&gt;到这里我们知道了具体是哪个容器，然后进入到该容器中，查看了一下 cpu 的使用情况，确实很高，接着使用以下命令导出 java 进程的线程堆栈&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jstack -l 1 &amp;gt; jstack.log &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;java 进程的 pid 为 1 ，所以上面命令中 1 就是 java 进程的进程 pid&lt;/p&gt;
&lt;h3 id="分析-threaddump"&gt;&lt;a href="#%e5%88%86%e6%9e%90-threaddump" class="header-anchor"&gt;&lt;/a&gt;分析 threadDump
&lt;/h3&gt;&lt;p&gt;用 jstack 命令导出 java 进程的线程堆栈快照后就可以进行分析了。&lt;/p&gt;
&lt;p&gt;这里我们使用在线的工具网站来分析：https://fastthread.io/&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-22-san-fen-zhong-jie-jue-java-jin-cheng-cpu-100-wen-ti/001-55d83854.png"&gt;&lt;/p&gt;
&lt;p&gt;我个人觉得是最好用的一个。你可能会问有没有本地的好用的工具。我找了一圈，有是有，但很一般，还不如人家这个在线网站呢。&lt;/p&gt;
&lt;p&gt;接着我们反 jstack.log 打个包上传上去。很快就会得到分析结果，会出一个关于这个 Thread Dump 的全方面分析报告，方便你定位相关问题。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-22-san-fen-zhong-jie-jue-java-jin-cheng-cpu-100-wen-ti/002-ca3860fe.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果有异常会有红号感叹号提示。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;除了人家提示的明显异常，我一般重点会看有没有死锁，以及哪些线程占用的 CPU 较高：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-22-san-fen-zhong-jie-jue-java-jin-cheng-cpu-100-wen-ti/003-35de550a.png"&gt;&lt;/p&gt;
&lt;p&gt;剩下的就凭借你对系统的了解和经验作出判断了。&lt;/p&gt;
&lt;p&gt;具体到我们的情况，我们分析了占 CPU 最高的两个线程，全部跟我们自己写的代码有关。具体来说有一个线程的代码执行的是一个递归操作。出问题的就是这部分代码。&lt;/p&gt;
&lt;p&gt;找到原因后，很快我们就修复了 bug。&lt;/p&gt;
&lt;h2 id="最后"&gt;&lt;a href="#%e6%9c%80%e5%90%8e" class="header-anchor"&gt;&lt;/a&gt;最后
&lt;/h2&gt;&lt;p&gt;要说简单也简单，如果你的思路是对的，整个处理过程不超过 3 分钟。但如果你的思路有问题，找到问题的时间就可能会无限长。&lt;/p&gt;</description></item></channel></rss>