<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="https://qiuzhiwang.vip/rss.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <atom:link href="https://qiuzhiwang.vip/rss.xml" rel="self" type="application/rss+xml"/>
    <title>IT枫斗者</title>
    <link>https://qiuzhiwang.vip/</link>
    <description>IT枫斗者的 Java 进阶之路：从 Java 基础、集合、并发与 JVM，到后端工程、分布式与 AI 应用开发。</description>
    <language>zh-CN</language>
    <pubDate>Sat, 07 Nov 2026 07:40:15 GMT</pubDate>
    <lastBuildDate>Sat, 07 Nov 2026 07:40:15 GMT</lastBuildDate>
    <generator>@vuepress/plugin-feed</generator>
    <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
    <category>项目与求职</category>
    <category>动手练习</category>
    <category>工程指南</category>
    <category>面试实战</category>
    <category>Java八股文</category>
    <category>学习实践</category>
    <category>可运行项目</category>
    <category>学习路线</category>
    <category>报名咨询</category>
    <category>训练营FAQ</category>
    <category>AI应用开发</category>
    <category>Offer陪跑营</category>
    <category>Java+AI训练营</category>
    <category>Offer训练营</category>
    <category>Java项目</category>
    <category>Java+AI项目</category>
    <item>
      <title>关于 IT枫斗者与本站内容</title>
      <link>https://qiuzhiwang.vip/about/</link>
      <guid>https://qiuzhiwang.vip/about/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">关于 IT枫斗者与本站内容</source>
      <description>IT枫斗者的 Java 进阶、后端工程与 AI 应用技术知识库，说明阅读入口、作者自述、免费内容与辅导服务。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>本站是 <strong>IT枫斗者</strong> 的技术知识库，以 Java 进阶、后端工程与 AI 应用开发为主线，整理原理、源码阅读、工程实践与排障内容。阅读时结合代码和运行记录，检查自己能否解释实现、验证行为，再按需要整理项目表达与面试复盘。</p>
<p>先从<a href="/home.html" target="_blank">完整进阶之路</a>浏览知识体系，按问题阅读<a href="/guides/" target="_blank">工程指南</a>，或进入<a href="/aiapp/" target="_blank">AI 应用开发</a>。作者故事与辅导服务在下文分别说明。</p>
<h2>内容适合谁</h2>
<ul>
<li>有 Java 基础，希望深入集合、并发、JVM、Spring、数据库与分布式系统的人。</li>
<li>正在做后端开发，需要结合原理分析事务、缓存、线程池等工程问题的人。</li>
<li>想从 Java 后端扩展到 RAG、Agent 等 AI 应用开发的人。</li>
<li>已有项目经历，需要整理真实职责、技术决策和验证材料的人。</li>
</ul>
<p>零基础读者可以先阅读<a href="/javabgw/javajc.html" target="_blank">Java 基础</a>，再按路线安排任务。路线中的周数用于分配任务，不构成学习或求职结果保证。</p>
<h2>作者与经历</h2>
<p>文章署名统一为 <strong>IT枫斗者</strong>。作者的个人回顾与其中讲述的团队故事见<a href="/journey/" target="_blank">从借来的工位开始</a>。技术文章中的事实和观点通过参考资料、版本范围和修订记录尽量区分。</p>
<p>故事中的任职、答疑、团队发展与活动经历以作者个人自述为主，配图保留原有公开材料。博客页面截图能支持截图当时显示的文章、访问和粉丝数据及平台称号，不代表当前实时数据，也不能独立证明全部任职经历。</p>
<p>“累计三千多名学员上岸”等数字是作者对历年陪跑经历的历史自述，目前尚缺可复核的完整统计范围、结果定义和去重依据；不能当作已核验人数、成功率或对个人求职结果的承诺。活动照片、分享中的图表和脱敏聊天各有支持范围，不能据此补出未公开的身份、资历或整体效果。本站整理 AI 应用内容，也不意味着这些历史活动已证明作者具有特定 AI 研发任职或项目交付经历。</p>
<h2>免费内容与服务</h2>
<p><a href="/home.html" target="_blank">技术文章</a>、<a href="/roadmaps/" target="_blank">学习路线</a>和<a href="/resources/" target="_blank">检查表资料</a>可以免费阅读或下载。读者可以先按技术主题学习，再根据需要选择辅导。</p>
<p>付费服务包括面向已有 Java 基础读者的 <a href="/offerxly/" target="_blank">Java 面试训练营</a>、补齐 AI 应用与项目表达的 <a href="/javaai-offer/" target="_blank">Java + AI 训练营</a>，以及从基础讲解、练习反馈到项目指导的 <a href="/javaai-private/" target="_blank">Java + AI 私教带徒</a>。适用对象、费用与沟通流程见<a href="/signup/" target="_blank">报名说明</a>；具体范围、答疑安排和交付内容以双方确认的服务清单与协议为准。</p>
<p>站内已有案例记录不等于所有参与者都能获得相同结果。评估辅导前，应结合自己的基础、时间和目标岗位确认服务是否匹配。</p>
<h2>发现错误怎么办</h2>
<p>请在<a href="https://github.com/mumuhennb/qiuzhiwang/issues" target="_blank" rel="noopener noreferrer">项目 Issues</a>提交文章地址、具体段落、适用版本和建议来源，也可以通过公众号 <strong>IT枫斗者</strong> 或微信 <code>itfdz666</code> 反馈。修订方式见<a href="/editorial/" target="_blank">内容维护与引用规则</a>。</p>
]]></content:encoded>
    </item>
    <item>
      <title>一条知识库项目描述怎么改：职责、实现、验证与边界</title>
      <link>https://qiuzhiwang.vip/career/project-story.html</link>
      <guid>https://qiuzhiwang.vip/career/project-story.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">一条知识库项目描述怎么改：职责、实现、验证与边界</source>
      <description>作者用当前知识库教学服务示范修改一条空泛简历描述，逐句解释依据，并提供本人参与范围的替换示例、验证练习和面试追问。</description>
      <category>项目与求职</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>这是一份由作者 IT枫斗者填写的教学示范，使用<a href="/projects/knowledge-service.html" target="_blank">当前 Java 知识库检索服务</a>及合成文档。下文第一人称用于说明作者如何表达项目实现，不是读者的既有经历，也没有对应的企业客户、生产上线或业务收益。读者应按实际参与改写；下载、启动或阅读源码，不能直接变成“我独立实现了整套系统”。</p>
<h2>修改前：技术名词很多，责任无法核对</h2>
<blockquote>
<p>负责企业级 AI 知识库架构，使用 Java、RAG、多租户和权限控制，提升检索准确率。</p>
</blockquote>
<p>这句空泛描述取自<a href="/samples/project-resume.html" target="_blank">既有项目经历样例</a>的反例。问题在于：“负责”没有说明具体交付，“企业级”没有部署与运营证据，“提升准确率”没有题集、基线与判定规则。既有样例讲的是 Java 单文件实验；这里改用当前具有 HTTP、数据库与认证的知识库服务，重新核对事实，不挪用单文件实验的七个回放场景。</p>
<h2>修改后：作者对当前教学实现的表达</h2>
<blockquote>
<p>在使用合成文档的 Java 21 / Spring Boot 知识库教学服务中，我负责服务端身份约束、文档版本管理与带引用检索的实现。</p>
<p>通过数据库会话取得租户和角色，检索仅使用当前未删除且当前身份可见的文档；更新、删除检查 expectedVersion，更新通过事务写入新版本并切换当前版本指针，响应返回文档 ID、版本和原文引用。</p>
<p>验证以本地 H2、演示身份和合成输入为条件；公开报告记录了默认 17 项测试通过，以及独立进程 23 次 HTTP 检查和重启验证，可据此核对跨租户、权限收紧、旧版本与删除场景。</p>
<p>默认回答来自本地词项匹配和原文摘录；可选 pgvector 的三维 fixture 验证不代表真实模型或线上语义质量，当前没有生产性能与业务收益结论。</p>
</blockquote>
<p>这四句话是一条项目描述的完整展开稿，便于教学核对。实际简历可以压缩篇幅，但保留“个人做了什么、如何实现、在什么条件下验证、结论到哪里为止”。这里的测试数字引用已有公开报告，不表示读者执行过，也不表示本页另做了一轮测试。</p>
<h2>逐句为什么这样改</h2>
<p>| 句子 | 改写理由 | 对应的实现或证据 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>项目经历修改室：从一条描述到一段讲解</title>
      <link>https://qiuzhiwang.vip/career/</link>
      <guid>https://qiuzhiwang.vip/career/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">项目经历修改室：从一条描述到一段讲解</source>
      <description>用当前 Java 知识库教学服务，练习有职责、实现、验证条件和边界的简历表达，以及三分钟项目讲解与五个追问。</description>
      <category>项目与求职</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>先把一个自己做过的动作讲清楚，再写进简历。本栏目首批内容由作者 IT枫斗者基于本站 <strong>Java 知识库检索服务与合成文档</strong>填写示范，提供可以核对的改写过程与口述答案。它没有真实客户、商业交付、线上流量或学员求职结果；读者不能把作者实现改成自己的商业经历。</p>
<h2>首批目录</h2>
<p>| 页面 | 已经填好的内容 | 你需要带走的产物 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>知识库项目三分钟讲解：完整示例与五个追问</title>
      <link>https://qiuzhiwang.vip/career/technical-explanation.html</link>
      <guid>https://qiuzhiwang.vip/career/technical-explanation.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">知识库项目三分钟讲解：完整示例与五个追问</source>
      <description>作者基于合成知识库教学项目填写三分钟口述全文、五个技术追问答案及练习表，逐项对应当前代码和公开验证范围。</description>
      <category>项目与求职</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>讲项目时，先说项目性质和自己的责任，再沿一条请求解释实现，最后给验证依据与限制。以下由作者 IT枫斗者基于<a href="/projects/knowledge-service.html" target="_blank">当前 Java 知识库教学服务</a>填写；文档与身份用于合成教学场景，没有真实客户和生产经历。第一人称属于作者示范。读者必须按自己实际实现、复现或阅读的范围替换，不能把这段口述当作个人商业经历。</p>
<h2>三分钟讲解全文</h2>
<p>下面的时间仅用于练习分配，实际时长取决于语速，不是一次已经测量的录音。可先读完，再合上页面按请求链复述。</p>
<h3>0:00—0:30：项目性质、问题与职责</h3>
<p>“这是我用于教学的 Java 21、Spring Boot 知识库服务，使用合成文档，默认运行在本机。我负责服务端身份约束、文档版本和检索证据返回。它要解释一个具体问题：文档更新、收紧权限或删除后，当前用户还能用哪份原文回答问题。项目没有企业客户和线上流量，下面说的是本地实现及公开验证范围。”</p>
<h3>0:30—1:15：从请求入口讲到证据</h3>
<p>“登录后服务端签发不透明 Bearer token，数据库存 token 哈希；后续请求通过会话关联数据库用户取得租户和角色。检索先连接文档的当前版本，排除删除墓碑，再按当前角色过滤。默认模式对问题与文档提取英文词项和中文二元词项，覆盖分数达到 0.6 才保留证据。回答使用最高分文档的原文，引用里有文档 ID、版本和完整正文；没有候选时返回 refused=true 和空引用。因此我把它称为本地词项检索与原文摘录。”</p>
<h3>1:15—2:00：用一次权限变化说明关键取舍</h3>
<p>“重点场景是公开文档变成 ADMIN 可见。测试先创建包含 cobalt 的 READER 文档，再携带 expectedVersion=1 更新为 ADMIN 的第二版；reader 继续搜 cobalt 应拒答，直接读取是 404。不能因为第一版曾公开，就回退到第一版。实现上只读取 current_version；更新和删除先锁定本租户文档行，再检查预期版本，旧值冲突返回 409。更新在事务中保存新版本并切换指针，删除写墓碑，历史仍保留。这里验证的是状态与可见性，没有承诺物理擦除。”</p>
<h3>2:00—2:35：给出可以打开的验证证据</h3>
<p>“公开报告记录默认 H2 路径 17 项测试通过，另有独立 Java 进程在 127.0.0.1:8085 的 23 次 HTTP 检查，包含应用进程重启后的版本和会话检查。包内有 JUnit XML、smoke 原始响应和源码哈希。这些数字分别对应不同验证口径，不能相加成准确率。它们支持已列场景的断言，也不代表断电恢复、线上负载或所有并发交错都已验证。”</p>
<h3>2:35—3:00：把扩展与未测部分说清楚</h3>
<p>“可选 Spring AI 与 pgvector 路径有真实向量库存储测试，使用三维确定性 fixture；公开报告中真实模型 API 和语义质量未测，而且回答仍是原文摘录。默认每份文档是一个检索单元，没有分块。下一步若扩展同义问法或检索质量，需要固定文档快照、题集和判断规则，再运行真实模型评测，才能讨论效果；当前没有性能和业务收益数字。”</p>
<h2>五个追问的完整回答</h2>
<h3>追问一：客户端能否传 tenant 或 role，让自己看到其他租户？</h3>
<p><strong>示范回答：</strong>“调用者身份来自服务端会话，不由文档请求体或自定义 header 决定。<code>Auth.security</code> 用 Bearer 的 SHA-256 哈希查会话，并关联 <code>app_users</code> 读取 tenant 与 role。文档的 <code>minRole</code> 表示文档所需级别，不能把它理解成当前用户的身份。<code>Knowledge.list</code> 的 SQL 先限定租户、未删除和当前版本，再做角色检查；另一租户即使是 ADMIN，也不能读取这份文档。不存在或不可见的文档统一 404，reader 写入返回 403。公开测试 <code>serverOwnsTenantAndRole</code> 与 <code>tenantIsolationForReadUpdateDeleteAndSearch</code> 分别检查身份注入和跨租户操作。”</p>
<p><strong>边界补充：</strong>“每个请求重新读取用户角色，所以角色修改影响既有 token 的后续请求；已经认证、正在执行中的请求以认证时身份为准。我不会把它说成能中断所有在途请求。当前是固定演示身份，生产注册、密码重置、TLS、限流和完整审计不在项目范围内。”</p>
<h3>追问二：为什么更新后不查旧版本？两个编辑者同时提交怎么办？</h3>
<p><strong>示范回答：</strong>“历史存在不等于仍可访问。新版本可能已经收紧权限，文档也可能已删除；回退旧公开内容会绕过当前状态。<code>Knowledge.list</code> 只连接 <code>kb_documents.current_version</code>，并排除 <code>deleted=TRUE</code>。<code>update</code> 和 <code>delete</code> 在事务中，先按租户与未删除条件执行 <code>FOR UPDATE</code> 行锁，再读取当前可见文档，比较 expectedVersion。两个编辑者拿到同一版本时，先成功的更新会推进版本；后一个持旧值提交应得到 409，需要重新读取后决定是否修改。”</p>
<p><strong>边界补充：</strong>“这是代码所采用的冲突处理方式。公开的 <code>staleVersionConflicts</code> 测试验证陈旧版本的更新与删除返回 409，不是所有并发时序的压力验证；<code>permissionChangeCannotFallBackToPublicOldVersion</code> 检查权限收紧后不回退。删除保留墓碑与历史，没有实现历史恢复接口。我也不会把单机事务描述成跨服务事务或完整分布式一致性。”</p>
<h3>追问三：这是不是模型问答？score=1 能否说明回答完全正确？</h3>
<p><strong>示范回答：</strong>“默认 mode 是 <code>local-lexical-extractive</code>，没有调用 embedding 或聊天模型。<code>Knowledge.tokens</code> 提取词项，<code>search</code> 用命中的问题词项数量除以问题词项总数，空词项集合得 0，至少 0.6 才进入候选。命中时 answer 取排序后第一条证据的完整正文，quote 也来自保存的正文。score=1 只能说明提取出的查询词项全部出现，不能说明文档回答了问题，更不是答案正确概率。”</p>
<p><strong>边界补充：</strong>“例如同一组词可能出现在无关语境，同义改写也可能没有共同词项；这两类都需要额外评测。无候选时 refused=true、citations=[]，说明没有满足当前规则的证据，不能声称系统已经判断所有语义问题都无答案。现有 <code>citationsAreExactAndVersioned</code> 与 <code>unknownQuestionRefusesWithoutCitation</code> 只验证引用一致和固定无命中输入，没有证明一般语义质量。”</p>
<h3>追问四：既然有 Spring AI 与 pgvector，通过测试为什么还不能写语义 RAG 效果？</h3>
<p><strong>示范回答：</strong>“实现确实提供官方 EmbeddingModel 和 PgVectorStore 接入。使用真实模型的 ai 模式需要同时启用 Maven 的 -Pai 与运行时 ai profile，另行准备 PostgreSQL/pgvector 和模型配置；单独启 Maven profile 只是让相应代码参与编译。公开向量集成测试用真实 PostgreSQL，但 EmbeddingModel 是三维确定性 fixture：文本含 java 返回一组固定向量，否则返回另一组。它适合稳定检查向量存储、tenant/minRole metadata 过滤、更新删除和旧候选处理，没有验证模型如何理解自然语言。”</p>
<p><strong>边界补充：</strong>“<code>AiConfiguration.Gateway.search</code> 做 metadata 过滤后，<code>Knowledge.search</code> 再对权威数据库的当前可见文档及版本进行核对，避免旧向量成为证据。公开报告最终 22 项里是 21 通过、1 跳过，跳过的是 <code>LiveEmbeddingTest</code>；<code>liveModelApi</code> 和 <code>semanticRagQuality</code> 都明确未测。0.75 是向量候选阈值，未做语义质量校准；该路径仍返回原文，没有聊天模型生成答案。”</p>
<h3>追问五：你凭什么说项目做完了？如果继续做，先补什么？</h3>
<p><strong>示范回答：</strong>“我只说教学范围内已实现并有公开验证，不说生产可用。项目包有 README、API、实现和验证材料；公开报告能看到默认 17 项测试、最终 ai 编译验证 22 项及跳过项、独立进程 23 次 HTTP 检查。要核对具体主张，打开对应 JUnit 用例或 smoke 响应，而不是只看 passed 字段。报告与源码用 manifest 哈希对应，下载包也有 SHA-256，可以确认材料属于同一版本。这些是项目既有证据，不是读者自己的执行结果。”</p>
<p><strong>边界补充：</strong>“后续若评估检索质量，我会先建立固定文档快照、问题和相关证据标注，把检索相关性、原文支持与回答正确性分开判断；真实模型调用需要另行运行并记录配置。若准备公开部署，则先补身份管理、凭据替换、TLS、限流和审计。这些是后续计划，当前没有完成证明。我不会给出尚未测量的吞吐、延迟，也没有真实客户收益或学员录用结果。”</p>
<h2>填写示例与练习表</h2>
<p>先将“项目已有事实”和“本人参与证据”分开记录。下表已填写作者示范的事实栏，读者操作栏说明应产出什么。作者的报告可以作为核对依据，不能替代本人复跑或修改记录。</p>
<p>| 练习项 | 作者示范：已经能核对的内容 | 你需要执行并记录 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>内容维护、引用与纠错规则</title>
      <link>https://qiuzhiwang.vip/editorial/</link>
      <guid>https://qiuzhiwang.vip/editorial/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">内容维护、引用与纠错规则</source>
      <description>本站如何标注技术版本、资料来源、练习示例、修订时间和项目证据，以及读者如何提交纠错。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>本页说明本站新增和修订内容采用的规则。历史文章会逐步检查，不表示每一篇旧文已经完成版本核验。</p>
<h2>技术结论</h2>
<ol>
<li>优先引用语言、框架或产品的官方文档、公开源代码和原始论文。</li>
<li>涉及源码细节时说明 JDK、框架或产品版本；实现细节不直接当作跨版本承诺。</li>
<li>先给可执行答案，再写适用条件、失败场景和替代方案。面试速记不能代替工程设计。</li>
<li>示例代码、合成数据和真实测试结果分别说明。没有实际运行的数据不写成吞吐量、提升比例或成功率。</li>
</ol>
<h2>修订与时间</h2>
<p>新文章使用实际撰写日期。旧文章只在实质修订时记录更新日期，不因重新构建网站刷新时间；无法确定的历史发布日期不补写。</p>
<p>本次优先修订了 <a href="/javabgw/hashmapdcyl.html" target="_blank">HashMap</a> 的版本与树化边界、<a href="/javabgw/Redis.html" target="_blank">Redis</a> 的缓存一致性和持久化表述，并新增学习路线与实战专题。其他旧文仍可通过反馈继续完善。</p>
<h2>案例与项目</h2>
<ul>
<li>教学项目、演示项目、个人实验与工作经历应分别描述。</li>
<li>简历里的“负责”“设计”“优化”对应本人实际完成的工作，不用教学项目冒充生产经历。</li>
<li>学员结果需要保留时间范围、服务内容与个人背景；涉及个人材料的新增展示需取得本人授权并去除不必要的个人信息。</li>
<li>不给训练营效果、招聘结果或收入作普遍承诺。</li>
</ul>
<h2>引用本站与提交纠错</h2>
<p>实际转载文章与阅读组件的快照、许可证和修改情况，见 <a href="/editorial/sources.html" target="_blank">JavaGuide 参考与转载来源</a>。转载文章保留原作者，复制时的来源说明也与本站文章分开。</p>
<p>引用时保留原文链接、文章标题和适用版本。错误反馈请提供“文章地址 → 原句 → 使用版本 → 依据或复现步骤”，提交到<a href="https://github.com/mumuhennb/qiuzhiwang/issues" target="_blank" rel="noopener noreferrer">仓库 Issues</a>。</p>
<p>版权与转载以原项目许可证及文章具体署名为准，外部图片或引用资料的权利归原作者。本站不会把第三方内容改名为自己的原创。</p>
]]></content:encoded>
    </item>
    <item>
      <title>7 天知识库验收练习：从启动源码到说明失败边界</title>
      <link>https://qiuzhiwang.vip/challenges/knowledge-week.html</link>
      <guid>https://qiuzhiwang.vip/challenges/knowledge-week.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">7 天知识库验收练习：从启动源码到说明失败边界</source>
      <description>按七项任务检查 Java 知识库的启动、身份、文档更新、删除、证据和拒答，使用真实运行记录整理一段可解释的项目经历。</description>
      <category>动手练习</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>目标是留下一份自己的验收记录，而不是七天内完成生产级系统。已有 Java 基础即可开始；某一项没有完成，可以多花一天，把错误记录下来。</p>
<p>准备 <a href="/projects/knowledge-service.html" target="_blank">知识库服务源码</a>、<a href="/handbook/rag-acceptance.html" target="_blank">验收手册</a>和 <a href="/downloads/learning-help/knowledge-week-workbook.md" download>可编辑练习单</a>。先按项目 README 选择运行模式，全文使用合成文档，避免带入公司或个人敏感资料。</p>
<h2>第 1 项：启动并确定实验条件</h2>
<p><strong>动作：</strong> 记录操作系统、JDK、源码版本、运行模式和启动命令。启动项目后发出一条正常请求与一条未认证请求。</p>
<p><strong>保留：</strong> 启动日志、请求方法和路径、响应状态、响应摘要。只写“启动成功”不够，还要知道服务监听在哪里、使用什么身份、数据保存到哪里。</p>
<p><strong>完成条件：</strong> 能解释启动成功与业务请求成功的区别。失败时先按<a href="/help/" target="_blank">使用帮助</a>区分环境、依赖、端口与认证问题。</p>
<h2>第 2 项：理解一条答案的证据</h2>
<p><strong>动作：</strong> 用项目示例文档提出一个可回答的问题，再问一个文档里没有答案的问题。把引用 ID 对应到原文。</p>
<p><strong>保留：</strong> 问题、原文、返回内容、证据 ID 与无答案响应。</p>
<p><strong>完成条件：</strong> 能说明“找到相似内容”“有充分证据”“生成了正确答案”是不同结论。本地检索模式没有模型生成时，只验收它实际提供的检索与摘录能力。</p>
<h2>第 3 项：验证租户与角色</h2>
<p><strong>动作：</strong> 在两个租户准备同名但内容不同的文档，分别使用两个已认证身份提相同问题；再用普通角色请求受限文档。</p>
<p><strong>保留：</strong> 身份所属租户、文档归属、请求及实际引用。不要把客户端传入的 <code>tenantId</code> 当作身份保障。</p>
<p><strong>完成条件：</strong> 能确认进入返回结果的每条证据都属于允许访问的范围。代码中查找身份从认证结果到查询过滤的实际传递位置。</p>
<h2>第 4 项：修改文档，查清旧值来自哪里</h2>
<p><strong>动作：</strong> 把一份规则从“年假五天”更新为“年假十天”，再查询同一问题。保留新旧文档标识与状态，不只看一段回答文字。</p>
<p><strong>保留：</strong> 更新请求、当前版本、候选证据和实际引用。</p>
<p><strong>完成条件：</strong> 能解释当前版本在哪里确定，旧内容为什么不应再次进入结果。需要增加向量入库故障时，按<a href="/guides/rag-document-update.html" target="_blank">文档更新指南</a>另外设计回放；没有执行的分支标为待验证。</p>
<h2>第 5 项：删除与收紧权限</h2>
<p><strong>动作：</strong> 删除当前文档，再提相同问题，检查是否错误回退到旧版本。另用项目支持的方式收紧文档权限，重新查询。</p>
<p><strong>保留：</strong> 删除或修改权限的响应、后续查询、旧版本状态与拒答结果。</p>
<p><strong>完成条件：</strong> 分清“停止提供文档内容”和“所有副本已经物理清除”。外部向量库、对象存储、备份与会话历史需要各自的清理策略，不能用本地一条查询替代。</p>
<h2>第 6 项：把失败写成可重放用例</h2>
<p><strong>动作：</strong> 从前面的步骤选择一个失败，整理最小输入。记录预期、实际、影响、修正与再次运行结果；全部正常时，也可以使用教学实验里的未过滤对照实现观察失败。</p>
<p><strong>保留：</strong> 一份别人能按步骤重放的记录。错误改了就重新执行相同输入，保留前后结果。超时与重试设计可结合<a href="/guides/java-ai-timeout-budget.html" target="_blank">接口截止时间指南</a>继续练习，不把设计建议写成已测性能。</p>
<p><strong>完成条件：</strong> 读者能够区分原始日志、规则推导与未来改进。</p>
<h2>第 7 项：讲清楚自己做了什么</h2>
<p><strong>动作：</strong> 用三分钟交代业务范围、本人修改、请求链路、一个失败分支和未完成部分。对照<a href="/samples/project-resume.html" target="_blank">项目表达样例</a>写一段练习经历。</p>
<p><strong>保留：</strong> 源码版本、运行记录、用例表与讲解提纲。下载并运行公开项目可以写成学习实践，自己新增了什么需要单独说明。</p>
<p><strong>完成条件：</strong> 面对“为什么这样过滤”“旧版本为什么不能回退”“如何知道修复生效”的追问，有代码或记录可以核对。</p>
<h2>做完之后</h2>
<p>可以到<a href="/practice/" target="_blank">自测工作台</a>检查相关知识，再把错题加入复习单。若仍有问题，按<a href="/help/#%E6%80%8E%E6%A0%B7%E6%8F%90%E5%87%BA%E4%B8%80%E4%B8%AA%E5%AE%B9%E6%98%93%E5%BE%97%E5%88%B0%E6%9C%89%E6%95%88%E5%9B%9E%E7%AD%94%E7%9A%84%E9%97%AE%E9%A2%98" target="_blank">提问模板</a>说明输入、预期、实际和已尝试办法。</p>
]]></content:encoded>
    </item>
    <item>
      <title>动手练习：每次留下一份可以检查的结果</title>
      <link>https://qiuzhiwang.vip/challenges/</link>
      <guid>https://qiuzhiwang.vip/challenges/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">动手练习：每次留下一份可以检查的结果</source>
      <description>配合 Java 知识库源码、检索验收手册和自测解析，完成文档版本、租户权限、证据与项目讲解练习。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>可以按自己的时间安排练习。每项都给出输入、要观察的问题和需要保留的材料，不用以阅读量或连续签到天数代替完成任务。</p>
<ul>
<li><a href="/challenges/knowledge-week.html" target="_blank">7 天知识库验收练习</a>：从启动到版本、权限、删除与结果记录。</li>
<li><a href="/practice/" target="_blank">知识自测与错题复习</a>：答题后看解释，导出后续阅读与实践清单。</li>
<li><a href="/practice/questions.html" target="_blank">完整题目解析</a>：不使用交互工具也可以直接查阅。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>缓存删除失败怎么补偿：从提交窗口到旧值回填</title>
      <link>https://qiuzhiwang.vip/guides/cache-invalidation.html</link>
      <guid>https://qiuzhiwang.vip/guides/cache-invalidation.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">缓存删除失败怎么补偿：从提交窗口到旧值回填</source>
      <description>拆解数据库提交后缓存删除失败、超时结果未知和旧值回填竞态，给出同库 outbox 补偿流程、TTL 边界与故障检查步骤。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong>先提交数据库，再删除缓存，可以让后续未命中读取已提交的新值，但提交与删除之间仍有故障窗口。</strong> 要在进程退出后继续补偿，失效意图必须能够持久恢复；本文采用与业务修改同事务写入的 outbox，数据库变更捕获（CDC）也是可选路径。可靠补偿解决“删除没有可靠完成”，仍不能单独解决并发读把旧值重新写回。Cache-Aside 提供的是有条件的最终一致性，不能据此承诺每次读取都是最新值。</p>
<h2>适用版本与边界</h2>
<p>本文面向 Redis Open Source 7.2／7.4、关系数据库与应用自行维护的 Cache-Aside。只使用已有的 <code>GET</code>、<code>SET EX</code>、<code>DEL</code> 语义，不依赖新版本命令。数据库是事实来源；订单扣款、库存条件更新等正确性判断应落在数据库约束或事务中。</p>
<p><a href="https://redis.io/docs/latest/develop/use-cases/cache-aside/java-jedis/#invalidation-on-write" target="_blank" rel="noopener noreferrer">Redis 官方 Jedis 示例</a>说明了写后失效。下文的 outbox 和故障处理是基于这些命令语义作出的工程设计推导，不是 Redis 自动提供的跨库事务。</p>
<h2>1. 为什么写后删除仍可能读到旧值？</h2>
<p>设商品原值为 v1，写请求准备更新为 v2。要区分以下路径，不能都归结为“Redis 删除失败”。</p>
<p>| 失败时序 | 直接后果 | 优先处理 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>从 JDK 8 升到 21：类加载器先查哪三个旧假设</title>
      <link>https://qiuzhiwang.vip/guides/classloader-jdk8-to21.html</link>
      <guid>https://qiuzhiwang.vip/guides/classloader-jdk8-to21.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">从 JDK 8 升到 21：类加载器先查哪三个旧假设</source>
      <description>用 JDK 21 实测系统加载器强转、null 父加载器和 jrt 资源，明确 JDK 8 扩展机制与 JDK 9+ 平台加载器边界。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong>升级到 JDK 21 时，先检查“系统加载器一定是 URLClassLoader”“null 父级能看到全部平台类”“核心类都在 rt.jar”这三个假设。默认的先父后子委派仍在，但父级可见范围和运行库布局已经改变。</strong></p>
<p>父级决定插件能复用哪些已加载类型，插件自己的 URL 决定委派未命中后去哪里找。平台父级能减少对宿主应用依赖的可见性，应用父级便于共享宿主接口；应先确定接口由谁定义，再决定隔离范围。</p>
<p>常见故障是老插件扫描器启动就出现 <code>ClassCastException</code>，改完强转又找不到 JDBC 接口，最后读核心类资源时把 <code>jrt:</code> 当成本地文件路径。它们发生在不同层，逐项验证比盲目添加 JVM 参数更有效。</p>
<h2>1. 把版本边界写进排查记录</h2>
<p>| 项目 | JDK 8 常见 Oracle/OpenJDK 实现 | JDK 9+，包括 21 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>接口里到底能不能写实现：用 JDK 21 验证四类方法</title>
      <link>https://qiuzhiwang.vip/guides/interface-methods-jdk21.html</link>
      <guid>https://qiuzhiwang.vip/guides/interface-methods-jdk21.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">接口里到底能不能写实现：用 JDK 21 验证四类方法</source>
      <description>区分 Java 8、9 与 21 的接口方法，实际编译默认方法冲突、静态方法及私有辅助方法，避免接口扩展误判。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong>直接结论：接口可以包含实现。Java 8 加入默认方法和静态方法，Java 9 加入私有方法；到 Java 21，这些边界仍然成立。“所有接口方法都是抽象方法”会误导实现与升级判断。</strong></p>
<p>真实问题往往出在维护公共接口：给两个无继承关系的接口各加一个相同签名的默认方法，原本正常的实现类重新编译就报错；把静态工具方法当作实例方法调用，也会找不到符号。先确认方法属于哪一类，再判断调用者需要修改什么。</p>
<h2>1. 按版本与调用方式看规则</h2>
<p>| 方法写法 | 起始版本 | 方法体与使用方式 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java AI 项目怎么选：先定数据和业务边界</title>
      <link>https://qiuzhiwang.vip/guides/java-ai-project-selection.html</link>
      <guid>https://qiuzhiwang.vip/guides/java-ai-project-selection.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java AI 项目怎么选：先定数据和业务边界</source>
      <description>面向 1—3 年 Java 后端开发者，从原站项目中选择可获得数据、可演示流程、可评估且有明确权限边界的实践范围。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>AI 辅助环节是否适合加入项目，取决于答案依据从哪里来、业务状态由谁维护、输出出错后由谁兜底。文档问答适合先检索再生成；当前工单状态应从业务接口查询；是否发送答复或变更状态，仍由业务规则与人工确认决定。多加一个模型或工具，也会多出数据权限、依赖超时和结果核验的成本。</p>
<p>对已有 1—3 年 Java 经验的人，优先从熟悉业务中选择数据可合法使用、流程可演示、失败可验证的一小段，再根据实际效果扩展模块。</p>
<h2>1. 从原站项目截取范围</h2>
<p>项目介绍是场景参考，下面的实现范围是个人练习建议，不代表已有企业交付经历。</p>
<p>| 原站项目 | 首版只做什么 | Java 侧验收 | AI 侧验收与边界 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java AI 请求为什么越重试越慢：总截止、幂等与 SSE</title>
      <link>https://qiuzhiwang.vip/guides/java-ai-timeout-budget.html</link>
      <guid>https://qiuzhiwang.vip/guides/java-ai-timeout-budget.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java AI 请求为什么越重试越慢：总截止、幂等与 SSE</source>
      <description>为 Java 21 AI 接口分配排队、检索、生成与重试预算，区分连接超时、总截止和 SSE 空闲，并保留可复现示例。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong>直接结论：在请求入口确定总截止时间，所有阶段和重试共享剩余预算。连接超时、单次模型超时、SSE 首包与空闲超时各管不同问题，任何一个都不能独自代表用户的总等待时间。</strong></p>
<p>模型的单次超时只覆盖调用链中的一段；排队、检索、SDK 重试和网关重试依次叠加，就可能让页面等待远超这一个参数。因此先限制整次请求的等待，再决定是否值得用余额换一次重试。</p>
<p>本文核心示例以 Java 21 执行；Spring 适配说明引用 Framework 6.2.x 的 MVC/WebClient API。<a href="https://docs.spring.io/spring-ai/reference/getting-started.html" target="_blank" rel="noopener noreferrer">Spring AI 官方入门文档</a> 将 2.0.x 对应到 Spring Boot 4.0.x/4.1.x，不能把这里的 Framework 6.2.x 文档当作该组合的版本适配证明。使用 Spring AI 2.0.x 时，应按项目 BOM 实际解析出的 Framework 和客户端版本查配置；本文未验证这套 Spring 集成。</p>
<h2>1. 先约定总预算，再给阶段设上限</h2>
<p>假设练习接口总预算十秒，预留五百毫秒用于整理错误与响应。排队和检索用掉多少，模型就少多少；模型阶段上限六秒，但实际允许值为 <code>min(阶段上限, 剩余时间 - 收尾预留)</code>。这些数字是教学配置，不是测出的服务能力。</p>
<p>同进程用 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html#nanoTime()" target="_blank" rel="noopener noreferrer">System.nanoTime</a> 的差值计时，避免系统时钟校准影响耗时。计时从入口开始，不能等连接拿到才开始。跨进程不能直接发送本机纳秒值。<a href="https://grpc.io/docs/guides/deadlines/" target="_blank" rel="noopener noreferrer">gRPC 的截止传播机制</a> 会把截止换成已扣除耗时的剩余时长，避免直接比较不同机器的时钟。自定义 HTTP 头不会自动获得这些能力：发送前重算余额，接收时取上游余额与本地限制的较小值，再用本地单调时钟扣除后续耗时。接收端仅凭一个剩余时长无法精确知道在途耗时；需要额外的传输安全余量，并由上游自己的总截止约束等待。若选择绝对截止时间，则还需明确时钟同步与偏差处理规则，不能声称仅靠透传即可严格保证端到端截止。</p>
<h2>2. 让每次调用重新取剩余时间</h2>
<p>下载 <a href="/downloads/jdk21-examples/TimeoutBudgetDemo.java">TimeoutBudgetDemo.java</a>。其 <code>Budget.stage</code> 在发起阶段前检查余额，<code>retry</code> 同时检查退避、最小尝试窗口和收尾预留。生产构造方式与核心计算为：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Budget</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> budget </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Budget</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Duration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ofSeconds</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">10</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> System</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">::</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">nanoTime)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Duration</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> allowed </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> budget</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">stage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Duration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ofSeconds</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">6</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">), </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Duration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ofMillis</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">500</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">));</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>上面两行使用下载文件中的嵌套类；不要单独当作完整程序编译。阶段超时必须传入实际客户端，并由请求级定时任务处理“阶段运行中总截止到达”的情况。仅在调用前检查余额，不能中止已卡住的操作。</p>
<p><a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.net.http/java/net/http/HttpClient.Builder.html#connectTimeout(java.time.Duration)" target="_blank" rel="noopener noreferrer">JDK HttpClient 的连接超时</a> 约束建立新连接；<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.net.http/java/net/http/HttpRequest.Builder.html#timeout(java.time.Duration)" target="_blank" rel="noopener noreferrer">HttpRequest.timeout</a> 用于单次请求。Spring 的 <a href="https://docs.spring.io/spring-framework/reference/6.2/web/webflux-webclient/client-builder.html" target="_blank" rel="noopener noreferrer">WebClient 配置</a> 还区分连接和响应配置。连接池等待、正文消费、流结束都要按所用客户端核验，不能把某个参数名当成整条链路保证。</p>
<h2>3. 重试之前做三个判断</h2>
<ol>
<li><strong>故障可重试吗？</strong> 只对策略允许的暂时故障有限重试，认证失败、参数错误不重试。把 SDK、业务、网关的重试次数合并核算，约定“最多两次尝试”包含第一次。</li>
<li><strong>操作允许重复吗？</strong> 超时可能发生在对方执行成功之后。写工单等操作需要服务端持久化幂等键、请求摘要与执行状态，并能查询结果；本机 Map 或每次新建请求号不能保证幂等。纯生成虽未写业务库，也可能重复计费或调用工具。</li>
<li><strong>余额和输出允许吗？</strong> 退避、下一次最小窗口与收尾预留之和必须小于剩余预算；一旦已向用户输出内容，默认禁止透明重试。否则同一答案会混入两次生成结果。</li>
</ol>
<p>本机运行方式：</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">javac </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">encoding UTF</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">8</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> --</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">release </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">21</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> TimeoutBudgetDemo.java</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">java </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">cp . TimeoutBudgetDemo</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>实际 stdout：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>modelCapMs=6000</span></span>
<span class="line"><span>remainingMs=3500</span></span>
<span class="line"><span>retryWithBackoff=false</span></span>
<span class="line"><span>shortRetry=true</span></span>
<span class="line"><span>writeRetry=false</span></span>
<span class="line"><span>afterSseRetry=false</span></span>
<span class="line"><span>atDeadline=TimeoutException</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里用可控逻辑时钟，五百毫秒排队加六秒模型阶段后只剩三千五百毫秒，带一秒退避和三秒尝试的重试被拒绝。<code>shortRetry=true</code> 仅说明更短的教学窗口通过策略计算，不代表已经发出网络请求，更不代表重试会成功。</p>
<h2>4. SSE 需要额外约定终止方式</h2>
<p>分别约定首个业务事件期限、相邻业务事件的空闲期限和流总时长。心跳可以保持链路活跃，但不应重置“模型一直没产出内容”的计时，更不能延长总截止。代理缓冲与空闲超时需一起检查。</p>
<p>| 期限 | 从何时计时 | 何时重置 | 主要识别的失败 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>项目经历怎么写进简历：每项贡献对应证据</title>
      <link>https://qiuzhiwang.vip/guides/project-resume.html</link>
      <guid>https://qiuzhiwang.vip/guides/project-resume.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">项目经历怎么写进简历：每项贡献对应证据</source>
      <description>将 Java 后端与 AI 应用实践整理为真实可核对的简历条目，明确个人职责、实现、验证条件和未完成边界。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>项目经历的说服力来自可解释的因果关系：约束是什么，方案如何改变请求处理过程，又付出了什么代价。先讲机制与取舍，再用代码、设计记录或实验输出说明结论成立的条件。例如，索引可能减少扫描，却增加写入维护成本；只有执行计划变化，还不能推出接口整体提速。</p>
<p>据此按“业务问题—个人改动—验证结果—适用条件”写两至三条贡献。只有本地实现，就写个人实践；只有阅读理解，就放在学习记录中。</p>
<h2>1. 先核对职责</h2>
<p>逐项列出自己独立实现、协作参与和仅了解的模块。协作内容说明自己的接口或交付物；公开项目介绍可用来理解场景，不能据此填写任职、上线规模或业务收益。先读站内 <a href="/aiapp/myzszgzmms.html" target="_blank">没有真实做过怎么描述</a>，再检查主语是否准确。</p>
<p>给准备写进简历的贡献建立如下表格，不必为了填满表格增加组件。</p>
<p>| 准备表达的内容 | 至少准备什么证据 | 没有证据时如何处理 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>RAG 文档更新与删除：怎样验收到片段和引用</title>
      <link>https://qiuzhiwang.vip/guides/rag-document-update.html</link>
      <guid>https://qiuzhiwang.vip/guides/rag-document-update.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">RAG 文档更新与删除：怎样验收到片段和引用</source>
      <description>面向 Java 后端，用版本清单、发布切换与删除标记处理知识库变更，验证旧片段、缓存、权限和失败重试。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>更新与删除改变的是片段的可见性，而不只是向量行。更新先写齐新版本，再切换可读版本，以额外存储和清理成本换取构建失败时旧版仍可用；删除先阻断读取，再清理全部版本的片段。是否生效要同时查存储、检索上下文和引用，不能只看回答是否提到旧内容。</p>
<p>本文以 Spring AI 2.0.x、PostgreSQL/pgvector 为例；本次 API 核对依据为 <a href="https://docs.spring.io/spring-ai/reference/" target="_blank" rel="noopener noreferrer">Spring AI 2.0.2 官方参考文档</a>，依赖仍应由项目 BOM 锁定，文档版本不代表本地已安装版本。下面的清单和状态机是应用设计建议，验收步骤尚未在你的服务运行。先按 <a href="/guides/rag-evaluation.html" target="_blank">RAG 评估指南</a> 固定题集，再增加变更回放。</p>
<h2>1. 为什么重新上传还会搜到旧规则？</h2>
<p>原文从“年假五天”改为“年假十天”，重新切分后只剩一个片段。如果每次随机生成片段 ID，新片段入库不会自动带走旧的三个片段；即使复用部分 ID，多余片段仍可能残留。<a href="https://docs.spring.io/spring-ai/reference/api/etl-pipeline.html" target="_blank" rel="noopener noreferrer">Spring AI ETL</a> 负责读取、转换和写入，业务版本关系需要自己保存。</p>
<p>业务表以租户和文档 ID 为主键，记录当前版本、删除状态、权限版本；另建版本清单，保存内容哈希、切分配置、预期片段数和全部片段 ID。每片元数据带 <code>tenantId/docId/version</code>。片段 ID 按租户、文档、版本、切分配置和序号确定性生成 UUID，重试复用清单，避免重复入库。</p>
<h2>2. 更新按什么顺序实现？</h2>
<ol>
<li><strong>保存变更身份。</strong> 服务端校验文档归属，保存原文快照和任务代次；同一内容、同一配置的重复事件复用任务。</li>
<li><strong>构建待发布版本。</strong> 解析、切分、生成向量并写入，按清单逐 ID 核对数量与内容哈希。部分批次失败保持旧版可读，失败批次可重试。</li>
<li><strong>切换发布。</strong> 校验任务仍是最新、文档未删除，以旧版本为条件更新当前版本；条件不符就停止发布并清理孤立片段，避免迟到任务覆盖新版。</li>
<li><strong>收尾对账。</strong> 在线检索限定当前版本；旧版片段异步删除，单文档缓存键包含查询、租户、权限范围、权限版本、文档 ID 和文档版本；跨文档答案还需绑定所依赖的文档版本集合，并在命中时复查可见性。定时对比清单与实际记录，补偿清理失败。</li>
</ol>
<p>读取先查权威状态，再构造版本过滤；送入模型前再核对候选片段是否仍有效。查不到当前版就返回无证据，不回退旧版。公开文档改为 HR 限定时也要遵守这个顺序。过滤条件由服务端构造，<a href="https://docs.spring.io/spring-ai/reference/api/vectordbs/pgvector.html" target="_blank" rel="noopener noreferrer">PGvector 接入文档</a> 说明了元数据过滤的转换方式。</p>
<p>“当前版本”是每篇文档自己的指针。单文档查询要同时限制 <code>tenantId/docId/version</code>；跨文档查询应在租户和权限范围内，按 <code>(docId = A AND version = A当前版) OR (docId = B AND version = B当前版)</code> 组合，不能用一个 <code>version = v2</code> 代表整个知识库。文档很多时，长过滤表达式会增加维护成本，可改用数据库中与权威状态关联的查询路径；这属于应用设计，不是 Spring AI 自动提供的版本管理。</p>
<p>| 阶段 | 权威指针/状态 | 存储中可能存在 | 读取允许使用 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>RAG 怎么做评估：先查证据，再查答案</title>
      <link>https://qiuzhiwang.vip/guides/rag-evaluation.html</link>
      <guid>https://qiuzhiwang.vip/guides/rag-evaluation.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">RAG 怎么做评估：先查证据，再查答案</source>
      <description>为 Java 知识库服务建立小型离线评估，分别检查检索命中、答案正确性、证据支撑、拒答、权限和耗时。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>RAG 的答案依赖两步：检索先找到证据，模型再根据上下文组织回答。漏检时，改提示词未必有用；证据齐全仍答错时，继续增加召回数量也可能只增加噪声和耗时。因此要分别检查检索、答案和拒答，相似度分数不能当作答案正确率。固定文档和配置后，用人工核对的问题与参考证据回放，才能比较改动的收益与代价。</p>
<h2>1. 建立可复查的题集</h2>
<p>从 <a href="/aiapp/zskxmjg.html" target="_blank">知识库项目架构</a> 的离线入库与在线问答两条链路出发，先用 30—50 题做本地基线；这是练习规模。覆盖单段事实、多段归纳、无答案、过期版本和不同权限。同一问题可以配置不同身份，检查权限变化。</p>
<p>每题记录问题、测试身份、文档版本、参考片段、答案要点和预期行为。参考证据由人核对，不让模型自行生成后直接充当标准答案。将近似问法放在同一组，再按组划分调试集与保留集，减少同题改写泄漏。参考证据同时标注文档版本和原文位置；切分方式变化后，chunk ID 可能变化，应按原文证据映射判定命中，不能直接比较两次运行的 ID。</p>
<h2>2. 定义指标和分母</h2>
<p>下面是本地项目的建议口径。LangChain 官方教程区分答案正确性、回答相关性、证据支撑和检索相关性，可以据此拆开定位。<a href="https://docs.langchain.com/langsmith/evaluate-rag-tutorial" target="_blank" rel="noopener noreferrer">官方评估教程</a></p>
<p>| 检查项 | 具体计算或核对方式 | 用途 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>RAG 答错以后：从一次请求走到故障分支</title>
      <link>https://qiuzhiwang.vip/guides/rag-failure-playbook.html</link>
      <guid>https://qiuzhiwang.vip/guides/rag-failure-playbook.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">RAG 答错以后：从一次请求走到故障分支</source>
      <description>用原文、召回候选、最终上下文和引用定位 RAG 故障，附可运行的 JDK 21 排查树、脱敏记录与失败边界。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong>直接结论：先找到第一处证据断裂，再改对应阶段。没有原文、候选和最终上下文的记录，单看答案无法判断是入库、召回、上下文裁剪还是生成出错；换模型通常不能解释问题。</strong></p>
<p>适用 Java 21、Spring AI 2.0.x 的知识库服务；2026-11-06 核对的 <a href="https://docs.spring.io/spring-ai/reference/index.html" target="_blank" rel="noopener noreferrer">Spring AI 官方参考</a> 显示版本为 2.0.2，滚动文档后续可能变化，使用时应对照项目实际依赖。下面的排查顺序是应用层建议；文中原有本地验证仅运行固定记录分类器，未连接真实向量库或模型，本次文字修订未重新运行分类器。它处理一条失败请求，与 <a href="/guides/rag-evaluation.html" target="_blank">评估指南</a> 的题集统计不同。</p>
<h2>1. 保存一份足够定位的请求快照</h2>
<p>接到“制度明明写了，机器人却回答不知道”，先保留请求号、脱敏问题、服务端租户与权限版本、文档版本、切分和嵌入模型标识，以及检索条件、候选片段 ID、最终送模片段 ID、提示词版本和引用。正文只在受控测试环境抽查，普通日志避免保存客户原文和令牌。</p>
<p>先人工确认问题在当前授权文档里有答案，标出参考片段；若事实只在旧版本或未授权文档里，正确行为可能就是拒答。<a href="https://docs.spring.io/spring-ai/reference/observability/index.html" target="_blank" rel="noopener noreferrer">Spring AI 可观测性</a> 提供客户端、模型、嵌入与向量库的观测能力；业务还需关联片段与版本，不能把一条总耗时当作整条证据链。</p>
<h2>2. 按第一处失败执行排查树</h2>
<p>从上向下检查，命中就停止并补证据；修复后从头回放同一请求：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>请求身份与文档授权有效？否 -> DENY：先阻断读取，复查授权和缓存键</span></span>
<span class="line"><span>  是 -> 参考片段已落库且解析文本完整？否 -> INGEST：查页码、表格、OCR、分块边界</span></span>
<span class="line"><span>    是 -> 查询/文档编码协议正确，向量空间与索引兼容？否 -> EMBEDDING：修正对应编码，按兼容性决定是否重建索引</span></span>
<span class="line"><span>      是 -> 参考片段在候选里？否 -> RETRIEVE：查过滤、查询改写、阈值、Top-K、索引</span></span>
<span class="line"><span>        是 -> 参考片段进入最终上下文？否 -> ASSEMBLY：查重排、去重、token 裁剪</span></span>
<span class="line"><span>          是 -> 上下文版本仍有效？否 -> VERSION：查版本筛选与缓存</span></span>
<span class="line"><span>            是 -> 回答事实逐项有本次证据支撑？否 -> GENERATE：查提示词、历史与引用绑定</span></span>
<span class="line"><span>              是 -> PASS：仅表示本条人工检查通过</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>查入库时，按参考片段 ID 读取存储正文，对照原始页，不以“上传成功”代替落库核对。查召回时，在隔离环境保持授权条件不变，先固定原始问题，再分别比较改写前后、阈值前后和重排前后的 ID 列表。一次只变一项；不能通过去掉租户过滤换取命中。</p>
<p>同样是“没有答出参考事实”，证据消失的位置决定了该改哪一层。以下对照假定身份、授权、入库与编码检查已通过，属于排查建议，不是新增实测结果：</p>
<p>| 参考片段的位置 | 优先解释 | 调整时的取舍 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 与 AI 应用技术专题</title>
      <link>https://qiuzhiwang.vip/guides/</link>
      <guid>https://qiuzhiwang.vip/guides/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 与 AI 应用技术专题</source>
      <description>深入 Java 线程池、JDK 类加载器、Spring 事务、缓存一致性、RAG 文档更新、工具调用与超时治理，结合适用版本、源码示例、故障分析和官方依据。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>从一个具体技术问题深入机制、实现与取舍。专题文章包含适用版本、失败场景、代码或时序分析，并列出参考依据。基础知识可对照<a href="/home.html" target="_blank">完整知识目录</a>。</p>
<h2>Java 语言与运行时</h2>
<p>| 技术问题 | 专题文章 | 重点内容 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring 事务何时真正提交：方法返回、回调与外部副作用</title>
      <link>https://qiuzhiwang.vip/guides/spring-transaction-boundary.html</link>
      <guid>https://qiuzhiwang.vip/guides/spring-transaction-boundary.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Spring 事务何时真正提交：方法返回、回调与外部副作用</source>
      <description>区分 SQL 完成、事务方法返回与物理提交，解释 REQUIRED、提交后监听、REQUIRES_NEW 的边界，并给出 outbox 示例和检查步骤。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong>SQL 执行完成、事务方法执行到末尾，都不能单独证明事务已提交。</strong> 默认代理模式中，事务拦截器在目标方法返回后处理提交；若方法加入外层事务，还要等外层结束。缓存删除、消息通知等副作用应以成功提交为前提，需要失败恢复时再配合同库持久化待办。</p>
<h2>适用版本与边界</h2>
<p>本文依据 Spring Framework 6.2 文档，面向 JDK 17／21、代理模式、<code>PlatformTransactionManager</code> 管理的 JDBC 本地事务。假定业务表与 outbox 使用同一数据源和事务管理器。JTA、多数据源及响应式事务需要另外确认边界；不能把线程绑定事务的推理直接套到 Reactor 上下文。</p>
<h2>1. 为什么“SQL 后面发通知”仍然太早？</h2>
<p>设订单事务依次执行：更新订单 → 发送“已确认”通知 → 返回方法 → 数据库提交失败。数据库没有保留确认结果，通知却已经发出；普通数据库回滚无法撤回外部消息。</p>
<p>另一种时序是外层 A 调用 <code>REQUIRED</code> 方法 B。B 返回后 A 继续执行并失败回滚，B 的修改也随之撤销，因为它们共享物理事务。根据 <a href="https://docs.spring.io/spring-framework/reference/6.2/data-access/transaction/declarative/tx-propagation.html" target="_blank" rel="noopener noreferrer">Spring 传播机制文档</a>，逻辑方法边界不等于独立物理提交边界。</p>
<p>| 观察位置 | 能确认什么 | 仍不能确认什么 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>线程池为什么不扩容：队列、拒绝与过载边界</title>
      <link>https://qiuzhiwang.vip/guides/thread-pool-overload.html</link>
      <guid>https://qiuzhiwang.vip/guides/thread-pool-overload.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">线程池为什么不扩容：队列、拒绝与过载边界</source>
      <description>根据 OpenJDK 21 的任务提交规则，说明队列如何影响线程扩容、拒绝策略如何改变调用方行为，附本机 JDK 21 门闩验证的源码、原始输出与过载检查步骤。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong>线程池达到核心线程数后优先入队，入队失败才尝试扩到最大线程数；队列与线程都无法接纳时才拒绝。</strong> 因而把最大线程数调大，未必改善积压。过载控制需要同时约束排队量、等待时间和下游并发，并把拒绝结果交还业务调用方。</p>
<h2>适用版本与边界</h2>
<p>本文按 OpenJDK 21 的 <code>ThreadPoolExecutor</code> 与平台线程说明，使用标准队列和正常创建线程的线程工厂。不直接套用到 <code>ForkJoinPool</code>、定时线程池或虚拟线程执行器；框架包装的执行器还需检查实际队列与拒绝处理器。代码参数仅供解释规则，不是生产推荐值。</p>
<h2>1. 任务到底先排队还是先创建线程？</h2>
<p>在运行状态下，工作线程少于 <code>corePoolSize</code> 时先尝试创建线程；达到核心数后调用队列的 <code>offer</code>。入队失败，再尝试按 <code>maximumPoolSize</code> 创建线程，失败则进入拒绝处理。成功入队后还会复查关闭状态，必要时移除并拒绝任务。可对照 <a href="https://github.com/openjdk/jdk21u/blob/master/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java" target="_blank" rel="noopener noreferrer">OpenJDK 21u 的 execute 实现</a>。</p>
<p>这里用的是非阻塞 <code>offer</code>，不是等待空位的 <code>put</code>。“使用阻塞队列”不表示提交线程会自动等到入队成功。</p>
<p>| 队列选择 | 核心线程忙时 | 主要代价 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>工具调用怎么加护栏：权限、参数、幂等与人工确认</title>
      <link>https://qiuzhiwang.vip/guides/tool-call-guardrails.html</link>
      <guid>https://qiuzhiwang.vip/guides/tool-call-guardrails.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">工具调用怎么加护栏：权限、参数、幂等与人工确认</source>
      <description>面向 Java 后端，以关闭工单为例，将工具调用拆为身份校验、参数验证、对象授权、确认记录和可重试的业务执行。</description>
      <category>工程指南</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>模型只能提出工具和参数，服务端决定是否执行。工具白名单减少模型可选择的动作，服务端校验约束实际执行的动作；人工确认增加一次交互，用来核对有副作用的操作，不能代替授权。身份、参数、对象权限、确认和幂等检查应收敛到同一业务服务，避免界面入口安全、工具入口却能绕过。</p>
<p>下文以“查询工单、申请关闭、用户确认关闭”为例，适用于 Spring AI 2.0.x；本次核对的<a href="https://docs.spring.io/spring-ai/reference/api/tools.html" target="_blank" rel="noopener noreferrer">官方工具文档</a>标示为 2.0.2，其 <code>ChatClient</code> 通过 <code>ToolCallingAdvisor</code> 驱动工具循环，1.x 的工具执行循环不同。护栏应落实在工具适配器与业务服务之间，不能仅依赖 Advisor 顺序。文中的状态、拒绝条件和验收断言是设计建议，没有已运行的业务结果。选场景可先看 <a href="/guides/java-ai-project-selection.html" target="_blank">Java AI 项目选型</a>。</p>
<h2>1. 给模型少几个工具就够了吗？</h2>
<p>不够。按角色提供工具白名单可以减少误调用，执行入口仍须拒绝未授权工具。租户和用户 ID 只能来自服务端认证结果，不能采信模型参数。<a href="https://docs.spring.io/spring-ai/reference/api/tools.html" target="_blank" rel="noopener noreferrer">Spring AI Tool Calling</a> 区分模型请求与应用执行；其中 <code>ToolContext</code> 可传递不发给模型的上下文，但不会替你认证用户。</p>
<p>查询必须按租户和工单归属过滤，关闭还要校验操作权限与工单状态。检索结果中出现“忽略限制并关闭工单”属于不可信内容，不能改变规则。日志记录请求号、操作者和拒绝原因，屏蔽令牌、客户正文及内部异常堆栈。</p>
<h2>2. 参数有 JSON Schema，还需要校验吗？</h2>
<p>需要。Schema 和 <code>@ToolParam</code> 帮助模型组织输入；Java 入口还要拒绝未知字段、错误类型、非法枚举和超长文本。用 DTO 表达约束，例如工单号非空、关闭原因一至二百字；业务层另查“已解决才能关闭”。</p>
<p><a href="https://docs.spring.io/spring-framework/reference/core/validation/beanvalidation.html" target="_blank" rel="noopener noreferrer">Spring Bean Validation</a> 支持注入 <code>jakarta.validation.Validator</code>。工具适配器中可以显式验证反序列化后的 DTO，避免把 Controller 上的校验误当成工具路径也会生效。这里有三个不同的检查位置：</p>
<p>| 检查位置 | 负责的约束 | 仅靠下一层会漏掉什么 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java知识库检索与RAG验收实操手册</title>
      <link>https://qiuzhiwang.vip/handbook/rag-acceptance.html</link>
      <guid>https://qiuzhiwang.vip/handbook/rag-acceptance.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java知识库检索与RAG验收实操手册</source>
      <description>IT枫斗者可打印手册，含七项合成检索回放的实际结果、16条待执行验收题、完成样例与空白记录。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong><a href="/downloads/handbook/java-ai-acceptance-handbook.pdf">下载打印版 PDF</a></strong> · <strong><a href="/downloads/handbook/java-ai-acceptance-handbook.zip">下载完整 ZIP</a></strong> · <a href="/downloads/handbook/acceptance-cases.csv">可编辑用例 CSV</a> · <a href="/downloads/handbook/run-record.md" download>运行记录 Markdown</a></p>
<p>IT枫斗者 · Java + AI 项目实操</p>
<p>从文档状态到可核验证据，把“看起来能答”变成可重复检查的记录。</p>
<p>版本：2026-11-06 · 适合打印的 A4 实操版</p>
<p>本册使用 A、B 租户的教学合成资料，包含七项已执行的检索回放与十六项待执行的新验收用例。完成样例取自真实回放，空白表供你记录自己的项目。</p>
<p><strong>结果边界</strong>：现有实验采用确定性词项匹配与原文摘录。没有调用大模型，没有向量数据库，没有生产身份认证或负载测试。七项通过不等于生产准确率。</p>
<p><strong>使用顺序</strong>：先读第 01 节的边界，按第 02 节复跑，再用第 06-09 节定位失败，最后填写第 14-16 节。新后端服务与原七项回放分开记账。</p>
<p>下载配套：/downloads/handbook/java-ai-acceptance-handbook.zip</p>
<p>在线阅读：/handbook/rag-acceptance.html</p>
<!-- pdf-page -->
<h2>01 · 先固定环境与验收边界</h2>
<h3>你在验收什么</h3>
<p>这里首先验收“哪些文档允许成为证据”，然后再讨论答案是否忠实于证据。实验的输入身份由用例指定，不能视为真实认证。实际服务的租户和角色应从服务端认证结果取得；若只是教学 token，也要在结论中注明。</p>
<p>| 层次 | 本册已有证据 | 仍需另行验证 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>IT枫斗者实操手册</title>
      <link>https://qiuzhiwang.vip/handbook/</link>
      <guid>https://qiuzhiwang.vip/handbook/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">IT枫斗者实操手册</source>
      <description>Java知识库检索与RAG验收手册，提供可打印PDF、实际回放、可编辑用例和空白记录。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>把检索验收写成可重跑的记录</h2>
<p><a href="/handbook/rag-acceptance.html" target="_blank">在线阅读《Java知识库检索与RAG验收实操手册》</a>。从环境卡、七个合成回放案例到更新、删除、权限、证据与拒答排查，按步骤留下输入、上下文和引用证据。</p>
<p><strong><a href="/downloads/handbook/java-ai-acceptance-handbook.pdf">下载 A4 打印版 PDF</a></strong> · <strong><a href="/downloads/handbook/java-ai-acceptance-handbook.zip">下载配套 ZIP</a></strong></p>
<p>| 材料 | 用途 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>使用帮助：阅读文章、查看源码与使用资料</title>
      <link>https://qiuzhiwang.vip/help/</link>
      <guid>https://qiuzhiwang.vip/help/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">使用帮助：阅读文章、查看源码与使用资料</source>
      <description>说明文章阅读、版本核对、源码与资料使用、项目排错，以及可选路线进度和答题记录的保存方式。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>这里说明如何查阅技术文章、核对版本、阅读源码和使用下载资料。路线、答题与进度记录是可选辅助工具。需要交流时，可以直接按 <a href="/join/" target="_blank">交流说明</a>说说基础、目标和一个问题。</p>
<h2>第一次来，从哪里开始？</h2>
<p>| 想阅读的内容 | 建议入口 | 内容说明 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 面试追问实战</title>
      <link>https://qiuzhiwang.vip/interview/</link>
      <guid>https://qiuzhiwang.vip/interview/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 面试追问实战</source>
      <description>用线程池与 Spring 事务两场完整教学演练，把30秒回答、连续追问、故障推理、可执行练习和项目证据连接起来。</description>
      <category>面试实战</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>面试中，第一问常常只要求一个结论，第二问就会改变条件：线程池还没到最大线程数，为什么拒绝了？异常明明被捕获，为什么最后仍然回滚？这个系列训练的是<strong>先说清题设，再沿执行过程推理，最后拿练习记录支撑回答</strong>。适合已学过 Java 并发与 Spring、正在准备后端面试的同学。</p>
<p>这里的对话均为原创教学场景，不对应真实公司面试、作者个人面试经历或录用结果。参考回答用于练习组织语言；涉及版本、代理方式、队列或事务资源时，要主动补充条件，不能把一段话套到所有项目。</p>
<h2>两场完整演练，从哪里开始</h2>
<p>| 演练 | 第一问 | 追问会走到哪里 | 本次产物 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 面试追问实战：线程池满了以后，请求去哪了？</title>
      <link>https://qiuzhiwang.vip/interview/thread-pool.html</link>
      <guid>https://qiuzhiwang.vip/interview/thread-pool.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 面试追问实战：线程池满了以后，请求去哪了？</source>
      <description>从2个核心线程、4个最大线程和容量2的队列出发，逐轮推理7次提交、CallerRuns门闩陷阱、过载排障与请求超时，复用已运行源码和实际输出。</description>
      <category>面试实战</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="/interview/" target="_blank">返回系列目录</a> · 下一场：<a href="/interview/transaction.html" target="_blank">Spring 事务追问</a></p>
<p>这是一场教学演练。我们不重新罗列线程池的所有参数，而是围绕同一段程序连续改变条件。先把每轮问题自己答一遍，再对照推理；答案最关键的部分往往是“在哪些条件下成立”。</p>
<h2>开场：30秒参考回答</h2>
<blockquote>
<p>对运行中的标准 ThreadPoolExecutor，工作线程少于核心数时先尝试创建线程，达到核心数后先入队，入队失败才尝试扩到最大数，再无法接纳才走拒绝策略。因此最大数4不代表第3个任务就会新建线程。我还会确认队列容量、拒绝策略和关闭状态；过载时要让业务看到失败，并检查排队时间与下游承载能力。</p>
</blockquote>
<p>本篇限定 JDK 21、平台线程、标准队列和正常创建线程的线程工厂；不套用到虚拟线程执行器、ForkJoinPool 或定时任务池。提交路径依据 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html" target="_blank" rel="noopener noreferrer">JDK 21 ThreadPoolExecutor API</a>，关闭与入队复查等细节可对照 <a href="https://github.com/openjdk/jdk21u/blob/master/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java" target="_blank" rel="noopener noreferrer">OpenJDK 21u 源码的 execute 方法</a>。</p>
<h2>第一轮：先锁定题设，再推7次提交</h2>
<p><strong>面试官：</strong>核心线程数2、最大线程数4、ArrayBlockingQueue容量2，使用 AbortPolicy。一个线程连续 execute 提交7个任务，哪个被拒绝？</p>
<p><strong>先追问题设：</strong>池处于运行状态，提交期间不关闭，没有其他提交者；每个任务都等待同一个初始值为1的门闩，全部提交完才放行，因此工作任务不会提前结束。线程能正常创建，配置不会中途变化。这正是本站 <a href="/downloads/thread-pool-overload.java">已有完整源码</a> 的条件。</p>
<p>在这些条件下逐步推导：</p>
<p>| 提交任务 | 本次走哪条路径 | 提交后的工作线程数 | 队列项数 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 面试追问实战：Spring 事务为什么没按预期回滚？</title>
      <link>https://qiuzhiwang.vip/interview/transaction.html</link>
      <guid>https://qiuzhiwang.vip/interview/transaction.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 面试追问实战：Spring 事务为什么没按预期回滚？</source>
      <description>沿同一条订单调用链追问代理、自调用、异常类型、捕获和rollback-only，再区分事务提交与消息送达，附独立JDBC练习及官方版本依据。</description>
      <category>面试实战</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="/interview/" target="_blank">返回系列目录</a> · 上一场：<a href="/interview/thread-pool.html" target="_blank">线程池追问</a></p>
<p>本篇是一场围绕“下单、写记录、发通知”的教学演练。每轮先预测数据库里最终留下什么，再解释原因。不能只说“注解失效了”，要指出哪次调用经过代理、哪个异常到达拦截器、谁拥有最终的提交边界。</p>
<h2>开场：30秒参考回答</h2>
<blockquote>
<p>我会先确认调用是否经过事务代理、用哪个事务管理器，以及有没有外层事务。未修改规则时，RuntimeException和Error触发回滚，受检异常默认不触发；内部捕获后正常返回，也不能只凭曾经抛过异常判断回滚。同类自调用不会让内层注解重新生效，但可能仍处于外层事务。方法体返回不等于物理提交，提交成功也不等于消息已经送达。</p>
</blockquote>
<h2>版本、资源与题设先说清</h2>
<p>截至 <strong>2026-11-06</strong> 联网核对：<a href="https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions" target="_blank" rel="noopener noreferrer">Spring官方版本政策</a>将 <strong>7.0.x</strong>列为当前生产线，<strong>6.2.x开源支持已于2026年6月结束</strong>，商业长期支持另有安排。官方文档当前指向 <a href="https://docs.spring.io/spring-framework/reference/index.html" target="_blank" rel="noopener noreferrer">7.0.10稳定版</a>；预览版与仍能访问的旧版本文档不等于当前开源受支持版本。</p>
<p>为与本站题库和 <a href="/guides/spring-transaction-boundary.html" target="_blank">事务边界指南</a>衔接，以下仍按 <strong>Spring Framework 6.2语义、JDK 17／21、默认proxy模式、PlatformTransactionManager管理的JDBC本地事务</strong>演练。这是明确教学基线，不要求升级现有框架。假定订单与outbox共用一个数据源及同一事务管理器，不讨论JTA、多数据源或响应式上下文。</p>
<p>回滚题还假定没有定制全局规则、没有数据库或其他参与者提前标记回滚。Spring 6.2允许通过 <code>@EnableTransactionManagement(rollbackOn = ALL_EXCEPTIONS)</code>改变全局默认，因此“受检异常默认提交”必须附上配置条件。见 <a href="https://docs.spring.io/spring-framework/reference/6.2/data-access/transaction/declarative/annotations.html" target="_blank" rel="noopener noreferrer">@Transactional配置说明</a>。</p>
<h2>第一轮：public方法上的注解，为什么还不够</h2>
<p><strong>面试官：</strong>一个Spring Bean的普通入口方法调用 <code>this.save()</code>；save是public且有 <code>@Transactional</code>，写一行订单后抛 IllegalStateException，会回滚吗？</p>
<p><strong>先补条件：</strong>入口没有事务，也没有类级事务声明；JDBC连接为普通自动提交模式。</p>
<p><strong>参考追答：</strong>这次自调用绕过代理，save上的注解没有建立事务。写入已自动提交，后面的Java异常不能把它撤销。应把事务入口放到另一个Bean，通过注入的代理调用；或者让外部经代理调用本来就应作为事务边界的入口。把方法改成public不会改变 <code>this</code> 的调用路径。<a href="https://docs.spring.io/spring-framework/reference/6.2/core/aop/proxying.html" target="_blank" rel="noopener noreferrer">Spring AOP官方说明</a></p>
<p><strong>再追问：如果外层入口本身经代理开启了事务呢？</strong> save仍在外层已有事务中执行，自调用并不会自动“脱离事务”。此时异常继续逃出外层，符合回滚规则，就会使外层回滚。若内层标 <code>REQUIRES_NEW</code>，自调用也不会凭这个注解另开事务。要在图里写清“外部 → 入口代理 → 目标入口 → this.save”，不能笼统回答“同类调用一定没事务”。</p>
<h2>第二轮：只改变异常和捕获位置，结局如何变化</h2>
<p><strong>面试官：</strong>改为从外部经代理调用save，写入成功后抛异常。你能给出判断表吗？</p>
<p>以下每行都是独立场景，使用默认回滚规则；异常是手工抛出的Java异常，数据库执行本身没有失败：</p>
<p>| 变化 | 拦截器观察到什么 | 正常情况下最终状态 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java基础</title>
      <link>https://qiuzhiwang.vip/javabgw/javajc.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/javajc.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java基础</source>
      <description>理解 Java 类型、面向对象、字符串、异常、拷贝、反射与内部类，结合语言规范区分版本规则和常见误区。</description>
      <category>Java八股文</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>先掌握“概念定义 + 对比区别 + 常见坑”三种回答模板。<br>
如果时间紧，优先看：面向对象、== 与 equals、String、接口 vs 抽象类。</p>
</div>
<h2>Java基础</h2>
<p><strong>回答提示：是一种比较通俗的解释，比较好理解，如果‘参考答案’不太好记住，可以直接复习回答提示也可以。</strong></p>
<h2>1、面向对象和面向过程的区别是什么？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>面向过程就像是做菜时按步骤来，先洗菜、切菜、炒菜，一步步完成。它关注解决问题的步骤和流程，直接用函数实现每个步骤，然后依次调用。</li>
<li>而面向对象则更像是把做菜看成一个整体，关注构成这个问题的各个“对象”，比如“锅”、“铲子”、“食材”等。每个对象有自己的属性和行为（方法），如“锅”有“加热”的方法。面向对象通过创建对象并调用它们的方法来解决问题，更强调事物的属性和行为，以及它们之间的关系。</li>
<li>总结来说，面向过程更直接，关注怎么做；面向对象更抽象，关注做什么和由谁来做。面向对象具有更高的复用性、扩展性和维护性，但可能性能稍低；面向过程则性能较高，但复用和维护可能较复杂。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>面向过程和面向对象是两种不同的处理问题的角度。</li>
<li><strong>面向过程</strong>更注重事情的每一个步骤及顺序。主要是分析出解决问题所需要的步骤，然后用函数把这些步骤一步一步实现，使用的时候一个一个依次调用</li>
<li><strong>面向对象</strong>更注重事情有哪些参与者（对象）、及各自需要做什么。主要是把事物对象化，包括其属性和行为。通过调用不同对象之间的方法，以组合的方式解决问题。</li>
</ul>
<h2>2、面向对象的三大基本特征</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li><strong>封装：</strong>就是把数据和操作数据的方法“打包”在一起，形成一个独立的单元（类）。外界不需要知道内部的具体实现细节，只需要通过接口（类提供的方法）与之交互。这就像是一个黑盒子，我们只知道它的输入和输出，但不知道里面是怎么工作的。</li>
<li><strong>继承:</strong>子类可以继承父类的属性和方法，这样就可以在不需要重新编写原有代码的基础上，扩展新的功能。比如，我们可以定义一个“动物”类，然后让“猫”和“狗”类继承它，从而复用“动物”类中的属性和方法。</li>
<li><strong>多态:</strong>是同一个接口（方法）在不同的子类中有不同的实现。这样，我们就可以用同一个父类类型的引用来调用不同子类的方法，实现不同的功能。这增加了程序的灵活性和可扩展性。</li>
</ul>
<p>继承，方法重写，父类引用指向子类对象</p>
<p>父类类型 变量名 = new 子类对象 ;</p>
<p>变量名.方法名();</p>
<p><strong>参考回答：</strong></p>
<ul>
<li><strong>三大基本特征：</strong>封装、继承、多态。</li>
<li><strong>封装</strong></li>
<li>封装的意义，在于明确标识出允许外部使用的所有成员函数和数据项，提供对应的函数给外部进行调用，内部实现对外部调用不可见，外部调用无需修改或关心内部实现。</li>
<li><strong>继承</strong></li>
<li>继承基类的方法，并做出自己的改变和/或扩展，子类共性的方法或者属性直接使用父类的，而不需要自己再定义，只需扩展自己个性化的。</li>
<li><strong>多态</strong></li>
<li>基于对象所属类的不同，外部对同一个方法的调用，实际执行的逻辑不同，父类中定义的属性和方法被子类继承后或者接口定义的方法被子类实现，通过重写使得父类和子类具有不同的实现，从而同一个属性或方法在父类及其各个子类中具有不同的含义。无法调用子类特有的功能。</li>
</ul>
<h2>3、方法重载和重写有什么区别？</h2>
<p><strong>参考回答（适用于 JDK 21）：</strong></p>
<ul>
<li><strong>重载（overload）</strong>：方法名相同、参数列表不同；仅返回类型不同不构成重载。编译器根据参数表达式的编译期类型等规则选择签名。</li>
<li><strong>重写（override）</strong>：子类为继承的实例方法提供符合规则的实现；调用已选定签名的实例方法时，再根据对象的运行时类型选择实现。静态方法是隐藏，不是重写；字段也不参与这种动态分派。</li>
</ul>
<p>依据：<a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.4.8" target="_blank" rel="noopener noreferrer">JLS 21 §8.4.8</a>、<a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.4.9" target="_blank" rel="noopener noreferrer">§8.4.9</a>。</p>
<h2>4、==和equals区别(了解)</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li><code>==</code>操作符：它主要用于比较基本数据类型的值是否相等，比如两个整数是否相等。对于引用数据类型，<code>==</code>比较的是两个对象的内存地址是否相同，即它们是否是同一个对象的引用。简单来说，<code>==</code>看的是“是不是同一个东西”。</li>
<li><code>equals</code>方法：它是<code>Object</code>类的一个方法，用于比较两个对象的内容是否相等。默认情况下，<code>equals</code>方法的行为和<code>==</code>对于引用类型是一样的，即比较内存地址。但是，很多类（如<code>String</code>、<code>Integer</code>等）都重写了<code>equals</code>方法，使其能够比较对象的内容是否相等，而不是内存地址。简单来说，<code>equals</code>看的是“内容是否一样”。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li><strong>==：</strong>对于基本数据类型，直接比较值；对于引用数据类型，比较引用（内存地址）。</li>
<li><strong>equals()：</strong>首先检查两个对象是否为同一个对象的引用（这通常是通过==运算符完成的，但具体实现可能有所不同），如果不是，则根据类的具体实现来比较对象的内容。许多类（如String、Integer等）都重写了equals()方法以提供更有意义的比较逻辑。</li>
</ul>
<h2>5、equals() 既然已经能实现对比的功能了，为什么还要 hashCode() 呢？（了解）</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>想象一下你有一堆对象，你想要快速地找出里面是否有某个特定的对象。如果只用<code>equals()</code>方法逐个比较，效率会很低，因为<code>equals()</code>可能会比较对象的很多内部细节。</li>
<li>而<code>hashCode()</code>方法则是用来快速判断两个对象在逻辑上是否可能相等的一个“捷径”。如果两个对象的<code>hashCode()</code>值不同，那么根据<code>hashCode()</code>的设计原则，这两个对象在逻辑上一定不相等，我们甚至不需要调用<code>equals()</code>方法就可以知道结果。这样，我们可以先将对象存储或查找在基于哈希值的集合中（如<code>HashSet</code>、<code>HashMap</code>），大大减少了不必要的<code>equals()</code>调用，提高了效率。</li>
<li><code>**hashCode()**</code><strong>方法通过提供一个快速的初步判断，帮助我们缩小了可能相等的对象的范围，与</strong><code>**equals()**</code><strong>方法一起工作，实现了高效的对象比较和查找。</strong></li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>因为重写的 equals() 里一般比较的比较全面比较复杂，这样效率就比较低，而利用 hashCode() 进行对比，则只要生成一个 hash 值进行比较就可以了，效率很高，那么 hashCode() 既然效率这么高为什么还要 equal() 呢？</li>
<li>因为 hashCode() 并不是完全可靠，有时候不同的对象他们生成的 hashcode 也会一样（生成 hash 值得公式可能存在的问题），所以 hashCode() 只能说是大部分时候可靠，并不是绝对可靠，所以我们可以得出结论：</li>
<li><strong>equals()</strong> 相等的两个对象他们的 <strong>hashCode()</strong> 肯定相等，也就是用 <strong>equals()</strong> 对比是绝对可靠的。</li>
<li><strong>hashCode()</strong> 相等的两个对象他们的 <strong>equals()</strong> 不一定相等，也就是 <strong>hashCode()</strong> 不是绝对可靠的。</li>
</ul>
<h2>6、String、StringBuilder和StringBuffer的区别？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>String是final修饰的，不可变，每次操作都会产生新的String对象.</li>
<li>StringBuffer是在原对象上操作，是线程安全的，是synchronized修饰的。</li>
<li>StringBuilder是在原对象上操作，是线程不安全的。</li>
<li><strong>性能：</strong>StringBuilder &gt; StringBuffer &gt; String</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>字符串类型<code>String</code>是不可变的，即一旦创建后，其值不可被修改。相比之下，<code>StringBuilder</code>和<code>StringBuffer</code>是可变的，允许对其内容进行修改。其中，<code>StringBuffer</code>是线程安全的，而<code>StringBuilder</code>是非线程安全的。</li>
</ul>
<h2>7、String str=new String(&quot;IT枫斗者&quot;)创建了几个对象？</h2>
<p><strong>参考回答：</strong></p>
<ul>
<li>1个或者2个</li>
</ul>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>new String("IT枫斗者");</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ul>
<li>相信很多小伙伴们听到的答案很多，有说创建了1个对象，也有说创建了2个对象。答案对，也不对，关键是要知道问题底层的原理。</li>
</ul>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>String str1 = "IT枫斗者";  // 在常量池中</span></span>
<span class="line"><span></span></span>
<span class="line"><span>String str2 = new String("IT枫斗者"); // 在堆上</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>当直接赋值时，字符串“IT枫斗者”会被存储在常量池中，只有1份，此时的赋值操作等于是创建0个或1个对象。如果常量池中已经存在了“IT枫斗者”，那么不会再创建对象，直接将引用赋值给str1；如果常量池中没有“IT枫斗者”，那么创建一个对象，并将引用赋值给str1。</li>
<li><strong>那么，通过new String(“IT枫斗者”);的形式又是如何呢？答案是1个或2个。</strong></li>
<li>当JVM遇到上述代码时，会先检索常量池中是否存在“IT枫斗者&quot;，IT枫斗者&quot;这个字符串，则会先在常量池中创建这个一个字符串。然后再执行new操作，会在堆内存中创建一个存储“IT枫斗者”的String对象，对象的引用赋值给str2。此过程创建了2个对象。</li>
<li>当然，如果检索常量池时发现已经存在了对应的字符串，那么只会在堆内创建一个新的String对象，此过程只创建了1个对象。</li>
</ul>
<h2>8、接口和抽象类的区别？（最少要回答出1,2,3,5,7）</h2>
<p><strong>参考回答：</strong></p>
<p><strong>1. 定义与目的</strong></p>
<ul>
<li>接口：是一种行为规范或约定，它定义了对象应该做什么，而不关心对象具体如何做。接口没有实例字段和构造器；方法能否包含实现取决于版本和修饰符：Java 8 起支持 <code>default</code>、<code>static</code> 方法，Java 9 起还支持 <code>private</code> 方法。</li>
<li>抽象类：是一种不能被实例化的类，它提供了一种模板，用于被其他类继承并实现其中的抽象方法。抽象类可以包含方法的实现，也可以不包含（即抽象方法）。</li>
</ul>
<p><strong>2. 成员变量</strong></p>
<ul>
<li>接口：字段隐含为 <code>public static final</code>，必须初始化；引用不能重新赋值，但被引用的可变对象仍可能改变。</li>
<li>抽象类：可以包含字段、属性、方法（包括抽象方法和非抽象方法）、构造器等。</li>
</ul>
<p><strong>3. 方法</strong></p>
<ul>
<li>接口：未写 <code>private</code>、<code>default</code>、<code>static</code> 的接口方法隐含为 <code>public abstract</code>，不能有方法体；Java 8 起 <code>default</code> 实例方法和 <code>static</code> 方法有实现，Java 9 起 <code>private</code> 实例方法和 <code>private static</code> 方法也可以有实现。静态方法通过声明接口调用，私有方法不被实现类继承。</li>
<li>抽象类：方法可以是抽象的，也可以是非抽象的。抽象方法没有具体实现，需要在子类中重写；非抽象方法可以有具体实现。抽象类中的方法可以有不同的访问级别。</li>
</ul>
<p><strong>4. 实例化</strong></p>
<ul>
<li>接口：不能被实例化，它只能被类实现。</li>
<li>抽象类：不能被直接实例化，但可以通过其子类（如果子类不是抽象的）来实例化。</li>
</ul>
<p><strong>5. 继承与实现</strong></p>
<ul>
<li>接口：一个类可以实现多个接口，从而获取多个接口中的方法。这是接口实现多态性的一个重要方式。</li>
<li>抽象类：一个类只能继承一个抽象类（在大多数面向对象的编程语言中，如Java）。这种单继承的特性限制了抽象类的使用范围。</li>
</ul>
<p><strong>6. 扩展性</strong></p>
<ul>
<li>接口：新增抽象方法通常要求具体实现类补齐实现，不能笼统说“添加方法无需修改实现类”；新增 <code>default</code> 方法可以提供缺省行为，但仍要检查同签名默认方法冲突和既有业务语义。</li>
<li>抽象类：扩展性相对较差，如果添加新的抽象方法，所有子类都需要实现这个方法，这可能会影响到现有的代码。</li>
</ul>
<p><strong>7. 设计层面</strong></p>
<ul>
<li>抽象类：是对类的抽象，是一种模板式设计。它对整个类进行抽象，包括属性和行为，子类必定是抽象类的一种类型，继承是“是不是”的关系。</li>
<li>接口：是对行为的抽象，是一种辐射式设计。它是对类局部（行为）的抽象，接口实现是“有没有”的关系，比如一个类是否能实现某个接口，取决于它是否具备该接口所定义的行为</li>
</ul>
<h2>9、浅拷贝和深拷贝？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>浅拷贝仍然指向原始对象的内存地址。这意味着，如果原始对象中的子对象发生了变化，浅拷贝得到的对象也会受到影响。浅拷贝适用于对象结构较简单，不包含复杂数据结构或不需要独立修改子对象的情况。</li>
<li>深拷贝递归地复制了对象内部的所有子对象或属性，创建全新的内存空间来存储这些对象。因此，深拷贝得到的对象与原始对象是完全独立的，对其中一个对象的修改不会影响到另一个对象。深拷贝适用于需要保持数据独立性和完整性的场景，如数据备份、多线程编程等。</li>
</ul>
<p><strong>参考答案：</strong></p>
<p><strong>1. 定义与本质</strong></p>
<ul>
<li>浅拷贝（Shallow Copy）：</li>
<li>创建一个新对象，然后将当前对象的非静态字段复制到该新对象，如果字段是值类型的，那么对该字段执行逐位复制；如果字段是引用类型的话，则复制引用但不复制引用的对象。</li>
<li>因此，原始对象及其副本引用同一个对象。</li>
</ul>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>BeanUtils.copyProperties方法用于将一个bean的属性值复制到另一个bean中，前提是这两个bean之间有相同的属性名和兼容的属性类型。这种方法在处理JavaBean之间的数据转换时非常有用，尤其是在不同层（如数据访问层和服务层）之间传递数据时。</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>JVM</title>
      <link>https://qiuzhiwang.vip/javabgw/JVM.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/JVM.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">JVM</source>
      <description>从运行时内存、类加载和 ConstantValue 到垃圾回收与诊断工具，区分 JVM 规范、HotSpot 实现及 JDK 版本。</description>
      <category>Java八股文</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>建议先看题目目录，再按“概念 -&gt; 原理 -&gt; 场景 -&gt; 优化”顺序复习。<br>
每题先讲结论，再补关键机制和项目实践，回答会更稳。</p>
</div>
<h2>1、JAVA 四种引用类型(了解)</h2>
<ol>
<li>
<p><strong>强引用：</strong></p>
</li>
<li>
<p>描述：强引用是我们最常见的引用类型，只要一个对象具有强引用，它就不会被垃圾回收器回收。</p>
</li>
<li>
<p>例子：</p>
</li>
</ol>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>Object obj = new Object();</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ol>
<li>
<p>在这个例子中，<code>obj</code>是一个强引用，指向<code>Object</code>的一个实例。只要<code>obj</code>还在使用，这个<code>Object</code>实例就不会被回收。</p>
</li>
<li>
<p><strong>软引用：</strong></p>
</li>
<li>
<p>描述：软引用是一种内存敏感的引用，如果一个对象只具有软引用，那么在内存空间不足时，垃圾回收器会考虑回收它。</p>
</li>
<li>
<p>例子：</p>
</li>
</ol>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>SoftReference&#x3C;Object> softRef = new SoftReference&#x3C;>(new Object());</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ol>
<li>
<p>在这个例子中，<code>softRef</code>是一个软引用，指向一个<code>Object</code>实例。如果内存不足，这个实例可能会被回收。</p>
</li>
<li>
<p><strong>弱引用：</strong></p>
</li>
<li>
<p>描述：弱引用比软引用更加弱，它不足以阻止对象的垃圾回收。无论当前内存空间足够与否，只要垃圾回收器发现了只具有弱引用的对象，就会回收它。</p>
</li>
<li>
<p>例子：</p>
</li>
</ol>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>WeakReference&#x3C;Object> weakRef = new WeakReference&#x3C;>(new Object());</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ol>
<li>
<p>在这个例子中，<code>weakRef</code>是一个弱引用，指向一个<code>Object</code>实例。无论内存空间如何，这个实例都可能被回收。</p>
</li>
<li>
<p><strong>虚引用：</strong></p>
</li>
<li>
<p>描述：虚引用是所有引用类型中最弱的一个。它无法阻止垃圾回收器的回收过程，也无法通过虚引用来获取对象的实例。它的主要作用是跟踪对象被垃圾回收的状态。</p>
</li>
<li>
<p>例子：</p>
</li>
</ol>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>PhantomReference&#x3C;Object> phantomRef = new PhantomReference&#x3C;>(new Object(), new ReferenceQueue&#x3C;>());</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ol>
<li>在这个例子中，<code>phantomRef</code>是一个虚引用，指向一个<code>Object</code>实例，并且关联了一个引用队列。这个实例随时可能被回收，而且回收时，<code>phantomRef</code>会被加入到关联的引用队列中。</li>
</ol>
<h2>2、类加载过程（按图顺序向面试官介绍，细节了解）</h2>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755162545311-e487bc9f-3e10-4b03-9555-cc92f7226adb-8a5ed745-8b4e28f1e4ad-1152w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755162545311-e487bc9f-3e10-4b03-9555-cc92f7226adb-8a5ed745-8b4e28f1e4ad-800w.webp" srcset="/images/optimized/1755162545311-e487bc9f-3e10-4b03-9555-cc92f7226adb-8a5ed745-8b4e28f1e4ad-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1152" height="388" decoding="async" data-image-source="/javabgw-img/1755162545311-e487bc9f-3e10-4b03-9555-cc92f7226adb.png" data-image-width="1152" data-image-height="388" data-image-detail="/images/optimized/1755162545311-e487bc9f-3e10-4b03-9555-cc92f7226adb-8a5ed745-8b4e28f1e4ad-1152w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li><strong>加载：</strong>加载是类加载过程中的一个阶段，这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象，作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取，这里既可以从 ZIP 包中读取（比如从 jar 包和 war 包中读取），也可以在运行时计算生成（动态代理），也可以由其它文件生成（比如将 JSP 文件转换成对应的 Class 类）。</li>
<li><strong>验证 :</strong>这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求，并且不会危害虚拟机自身的安全。</li>
<li><strong>准备：</strong>创建类或接口的静态字段（包括 <code>static final</code>），并赋予类型默认值，如 <code>int</code> 为 <code>0</code>、引用为 <code>null</code>；不执行 Java 初始化表达式。这里描述的是 JVMS 的逻辑阶段，不承诺字段的物理位置。</li>
</ul>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> InitDemo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> age </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 26</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> LIMIT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 6</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> *</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 7</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> NAME </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "IT枫斗者"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Integer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> BOXED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 42</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> RUNTIME </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Integer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">parseInt</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"42"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>final</code> 只限制再次赋值。按 JLS，只有以常量表达式初始化的基本类型或 <code>String</code> 类型的 <code>final</code> 变量才是常量变量；上例的 <code>LIMIT</code>、<code>NAME</code> 是，<code>BOXED</code>、<code>RUNTIME</code> 不是。Java 编译器通常为前两者生成 <code>ConstantValue</code> 属性，而后两者的赋值要执行 <code>&lt;clinit&gt;</code> 字节码。编译期记录常量值不等于运行时字段已在编译期分配和赋值。</p>
<p>规范顺序可简记为：<code>准备：静态字段默认值 → 初始化过程：ConstantValue 赋值 → 执行 &lt;clinit&gt; 中的赋值与 static 块</code>。JVMS 8 和 21 都将 <code>ConstantValue</code> 的赋值规定为初始化的一部分，且早于调用 <code>&lt;clinit&gt;</code>；不要把“所有 final 都在准备阶段赋最终值”当作规范。HotSpot 可在创建类镜像等较早的内部步骤提前设置常量字段，这是实现细节，不能据此改变规范的阶段定义（可对照 <a href="https://github.com/openjdk/jdk/blob/jdk-21%2B35/src/hotspot/share/classfile/javaClasses.cpp" target="_blank" rel="noopener noreferrer">OpenJDK 21 javaClasses.cpp 的 initialize_static_field</a>）。直接读取 <code>InitDemo.LIMIT</code> 通常使用编译进调用方的常量值，不触发 <code>InitDemo</code> 初始化；读取 <code>BOXED</code> 则会触发。</p>
<p>依据：<a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.2" target="_blank" rel="noopener noreferrer">JVMS 8 §5.4.2 / §5.5</a>、<a href="https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.7.2" target="_blank" rel="noopener noreferrer">JVMS 21 §4.7.2 ConstantValue</a>、<a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.12.4" target="_blank" rel="noopener noreferrer">JLS 21 §4.12.4</a>。</p>
<ul>
<li>
<p><strong>解析：</strong>JVM会在此阶段把类的二进制数据中的符号引用替换为直接引用。</p>
</li>
<li>
<p><strong>符号引用：</strong>符号引用与虚拟机实现的布局无关，引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同，但是它们能接受的符号引用必须是一致的，因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。</p>
</li>
<li>
<p>直接引用可以是指向目标的指针，相对偏移量或是一个能间接定位到目标的句柄。如果有 了直接引用，那引用的目标必定已经在内存中存在。</p>
</li>
<li>
<p><strong>初始化 ：</strong>按初始化协议处理常量字段后，执行编译器汇集静态字段初始化表达式和静态块生成的 <code>&lt;clinit&gt;()</code>（若存在），例如将 <code>age</code> 从 <code>0</code> 赋为 <code>26</code>。它不是实例构造器 <code>&lt;init&gt;</code>；没有相应初始化代码的类可以没有 <code>&lt;clinit&gt;</code>。解析也可以按需延迟，不必全部完成后才初始化。</p>
</li>
<li>
<p><strong>使用：</strong>对象都出来了，业务系统直接调用阶段。</p>
</li>
<li>
<p><strong>卸载 ：</strong>类的生命周期不等于单个对象的生命周期；只有定义它的类加载器可被回收时，该类才可能卸载，并非业务用完就立即卸载。</p>
</li>
</ul>
<h2>3、类加载器种类以及加载范围</h2>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755162545040-5350b37f-2376-4a71-885c-620ab282c12e-ed09e37e-7353da9c6520-590w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755162545040-5350b37f-2376-4a71-885c-620ab282c12e-ed09e37e-7353da9c6520-590w.webp" srcset="/images/optimized/1755162545040-5350b37f-2376-4a71-885c-620ab282c12e-ed09e37e-7353da9c6520-590w.webp 590w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="590" height="479" decoding="async" data-image-source="/javabgw-img/1755162545040-5350b37f-2376-4a71-885c-620ab282c12e.png" data-image-width="590" data-image-height="479" data-image-detail="/images/optimized/1755162545040-5350b37f-2376-4a71-885c-620ab282c12e-ed09e37e-7353da9c6520-590w.webp"></a><figcaption>img</figcaption></figure>
<p><strong>参考回答：</strong></p>
<p>上图按 JDK 8 常见实现理解；JDK 9 模块化以后，应区分以下版本边界，不能直接套用扩展目录和 <code>rt.jar</code>。</p>
<p>| 类型 | JDK 8（Oracle/OpenJDK 常见实现） | JDK 9+，包括 JDK 21 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/javabgw-img/1755162545311-e487bc9f-3e10-4b03-9555-cc92f7226adb.png" type="image/png"/>
    </item>
    <item>
      <title>IT枫斗者：技术交流与更新</title>
      <link>https://qiuzhiwang.vip/join/</link>
      <guid>https://qiuzhiwang.vip/join/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">IT枫斗者：技术交流与更新</source>
      <description>关注 IT枫斗者的 Java 进阶之路、AI 应用开发与源码解析更新，通过自有公众号和微信交流技术问题、反馈内容与了解服务。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>这里是 <strong>IT枫斗者</strong>的技术交流入口。可以讨论 Java 后端、AI 应用和项目实现，也可以反馈文章中的问题、资料下载或源码运行情况。</p>
<p><a href="/home.html" target="_blank">阅读 Java 进阶之路</a> · <a href="/guides/" target="_blank">技术专题</a> · <a href="/projects/" target="_blank">项目源码</a> · <a href="/resources/" target="_blank">免费资源</a></p>
<h2>公众号：IT枫斗者</h2>
<p>在微信搜索 <strong>IT枫斗者</strong>，关注后继续阅读 Java 原理、源码解析、AI 应用工程和排障文章。点击下方关注按钮可以展开本站公众号二维码。</p>
<h2>微信交流：itfdz666</h2>
<p>微信：<strong>itfdz666</strong>。添加时备注“<strong>Java进阶</strong>”“<strong>AI项目</strong>”或“<strong>项目简历</strong>”，便于说明交流方向。</p>
<p>可以直接说正在看的文章、遇到的问题，或当前的基础和目标。比如：“我会写 Spring Boot 接口，想理解知识库里的权限过滤，应该先看哪个模块？”</p>
<p>如果需要定位一个具体问题，再按情况补充版本、错误信息或复现步骤：</p>
<p>| 问题类型 | 有助于沟通的信息 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 知识库检索验收：权限、版本与删除回放</title>
      <link>https://qiuzhiwang.vip/labs/rag-evidence.html</link>
      <guid>https://qiuzhiwang.vip/labs/rag-evidence.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 知识库检索验收：权限、版本与删除回放</source>
      <description>运行一个无外部依赖的 Java 21 小实验，检查检索是否读到旧文档、其他租户和越权资料，附源码、七项回放结果与原始 JSON。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>知识库返回了“正确格式”的文本，不代表引用来自当前用户有权访问的最新文档。这里把检索前的过滤单独拿出来验证：先确定租户和文档最新状态，再检查删除标记与角色，最后检索。</p>
<p><strong><a href="/downloads/rag-evidence-lab.zip">下载 Java 源码与回放结果</a></strong></p>
<h2>在自己电脑上运行</h2>
<p>解压后，在文件所在目录执行：</p>
<div class="language-sh line-numbers-mode" data-highlighter="shiki" data-ext="sh" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-sh"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">java</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --version</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">java</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --source</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 21</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> RagEvidenceLab.java</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> results.json</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>需要 JDK 21 或更新版本，不需要 Maven、第三方 Java 包和模型账号。<code>results.json</code> 是你这次运行的实际输出；下面的结果来自本站构建时执行的同一份源码。</p>
<h2>看清对照实现漏了什么</h2>
<p>语料包含 A、B 两个租户的年假规则、HR 限定文档、一份带删除标记的 VPN 文档和普通工单说明，全部是教学合成数据。</p>
<p>对照实现直接在全部文档和版本中按词项查找，遇到相同分数取前一条。它可能返回 A 的旧年假规则，也可能把 HR 文档放入普通员工的上下文。</p>
<p>修正实现分成三个独立步骤：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>测试身份的租户</span></span>
<span class="line"><span>  → 按文档ID选择最新版本（保留删除状态）</span></span>
<span class="line"><span>  → 排除已删除、无权访问的文档</span></span>
<span class="line"><span>  → 词项匹配与原文摘录</span></span>
<span class="line"><span>  → 保存答案、引用来源和上下文文档ID</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>顺序很重要：如果先排除已删除的记录，再找最新版本，就可能重新选中已经被删除的旧版；如果先排除当前角色不可见的版本，也可能回退到旧的公开版本。</p>
<h2>这次实际运行的结果</h2>
<p>本次 Java 运行：<code>21.0.12</code>；合成场景 <strong>7 项</strong>。缺少过滤的对照实现通过 <strong>3 项</strong>，修正实现通过 <strong>7 项</strong>。</p>
<p>这些是下面七个固定场景的回放结果，用于检查代码行为，不表示真实 RAG 的准确率。</p>
<table>
<thead>
<tr>
<th>用例</th>
<th>检查内容</th>
<th>对照来源</th>
<th>修正来源</th>
<th>修正结果</th>
</tr>
</thead>
<tbody>
<tr>
<td>C01</td>
<td>当前文档版本</td>
<td><code>A/leave/v1</code></td>
<td><code>A/leave/v2</code></td>
<td>通过</td>
</tr>
<tr>
<td>C02</td>
<td>租户隔离</td>
<td><code>A/leave/v1</code></td>
<td><code>B/leave/v1</code></td>
<td>通过</td>
</tr>
<tr>
<td>C03</td>
<td>普通员工不能检索HR资料</td>
<td><code>A/salary/v1</code></td>
<td><code>NONE</code></td>
<td>通过</td>
</tr>
<tr>
<td>C04</td>
<td>删除标记覆盖旧文档</td>
<td><code>A/vpn/v1</code></td>
<td><code>NONE</code></td>
<td>通过</td>
</tr>
<tr>
<td>C05</td>
<td>没有证据时停止</td>
<td><code>NONE</code></td>
<td><code>NONE</code></td>
<td>通过</td>
</tr>
<tr>
<td>C06</td>
<td>授权HR可检索HR资料</td>
<td><code>A/salary/v1</code></td>
<td><code>A/salary/v1</code></td>
<td>通过</td>
</tr>
<tr>
<td>C07</td>
<td>普通工单查询</td>
<td><code>A/ticket/v1</code></td>
<td><code>A/ticket/v1</code></td>
<td>通过</td>
</tr>
</tbody>
</table>
<p><a href="/downloads/rag-evidence-report.json">查看原始 JSON</a> · <a href="/downloads/rag-evaluation-filled.csv">下载填写完成的 CSV</a></p>
<p>JSON 中保留运行时间、Java 版本、源码 SHA-256，以及每题的查询、身份、期望引用、对照结果和修正结果。可以用源码包复跑，对照自己的结果。</p>
<h2>选一个失败场景追到代码</h2>
<p><code>C03</code> 使用普通员工身份查询“薪酬”。对照实现能找到 <code>A/salary/v1</code>，说明仅在回答之后隐藏文本已经太晚：敏感片段先进入了上下文。</p>
<p>修正实现的 <code>currentVisibleDocs</code> 在检索之前排除无权限文档，<code>C03</code> 得到 <code>NONE</code> 和“没有可用证据”。程序不仅比对最终答案，也逐一检查进入上下文的文档是否属于当前可见集合。</p>
<h2>接下来怎样扩展</h2>
<ul>
<li>新增一个“公开文档改为 HR 限定”的版本，验证不会回退到旧公开版。</li>
<li>把演示身份改为服务端认证后的身份，阻止客户端自行选择角色和租户。</li>
<li>接入向量库时核对过滤是否真的生效，再加入无答案、错引用、模型超时与提示注入用例。</li>
</ul>
<p>当前匹配器采用确定性词项匹配，答案是原文摘录；它没有向量检索、模型生成、真正的身份认证和线上负载。实验用于检查检索边界，不能代表完整 RAG 的质量。</p>
<p>需要按同样口径记录自己的项目，可以使用<a href="/samples/rag-evaluation.html" target="_blank">验收表填写样例</a>和<a href="/resources/" target="_blank">空白资料</a>。</p>
<h2>继续阅读与资料</h2>
<p>继续阅读：<a href="/guides/rag-document-update.html" target="_blank">文档更新与删除</a>、<a href="/guides/tool-call-guardrails.html" target="_blank">工具调用权限与幂等</a>。</p>
]]></content:encoded>
    </item>
    <item>
      <title>项目实验：先运行，再解释</title>
      <link>https://qiuzhiwang.vip/labs/</link>
      <guid>https://qiuzhiwang.vip/labs/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">项目实验：先运行，再解释</source>
      <description>从 Java 知识库检索的小型实验开始，用可运行源码和完整回放结果核对权限、文档版本与证据边界。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>这里放可以自己运行、核对输入与输出的实验。先看一个具体问题，再决定要补哪些原理和框架能力。</p>
<h2>Java 知识库检索验收</h2>
<p>知识库能返回一段文字，仍可能返回了别的租户、旧版本或没有权限的内容。这个实验使用 Java 21 标准库，把问题缩小到检索阶段，比较缺少过滤和补齐过滤后的行为。</p>
<p><strong><a href="/labs/rag-evidence.html" target="_blank">看源码思路与完整回放</a></strong> · <a href="/downloads/rag-evidence-lab.zip">下载可运行源码包</a></p>
<p>只需 JDK 21，无外部依赖、模型账号或 API Key。数据为合成规则，结果来自本地 Java 回放；实验没有接入模型生成或向量检索。</p>
<h2>从实验走到项目</h2>
<p>用 <a href="/samples/rag-evaluation.html" target="_blank">RAG 验收表填写样例</a>记录一次回放，再根据<a href="/aiapp/zskxmjg.html" target="_blank">知识库项目架构</a>理解真实服务还需要哪些环节。<a href="/javaai-projects/02-yunhu.html" target="_blank">云呼智服工单场景</a>用于理解它可以嵌入的业务流程。</p>
<p>完整业务系统的实现范围、部署与生产验证应单独说明，不从这个小实验推导出生产能力。</p>
]]></content:encoded>
    </item>
    <item>
      <title>学习自测题库：24 题与完整解析</title>
      <link>https://qiuzhiwang.vip/practice/questions.html</link>
      <guid>https://qiuzhiwang.vip/practice/questions.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">学习自测题库：24 题与完整解析</source>
      <description>Java 基础、并发与事务、AI 检索、项目表达四组原创单选题，静态提供全部题干、选项、正确答案、完整解释、适用版本、深入阅读和可执行任务。</description>
      <category>学习实践</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<!-- Generated by node scripts/build-study-library.mjs; edit questions.mjs, then regenerate. -->
<p>这里完整列出 24 道原创单选题与解析。正文在构建时生成，关闭 JavaScript 也能阅读。先写下自己的选择，再对照答案，最后完成每题的下一步任务。</p>
<p><a href="/practice/" target="_blank">进入交互式学习工作台</a> · 题库版本：2026-11-06。每组 6 题。</p>
<p>题设中的代码、数量和故障时序用于学习判断，不代表本站或使用者的实际运行结果。固定题的作答不能预测就业或面试通过。实践任务需要你亲自执行，并保留真实记录。</p>
<h2>题目目录</h2>
<ul>
<li><a href="#java">Java 基础</a>：把语言规则写成可运行的小例子，保留实际输出。</li>
<li><a href="#concurrency">并发与事务</a>：画出执行时序，再用受控的并发或回滚验证边界。</li>
<li><a href="#retrieval">AI 检索</a>：固定文档与题集，逐题检查证据、版本和权限。</li>
<li><a href="#project">项目表达</a>：整理个人职责、实现和可复查材料，明确尚未验证的部分。</li>
</ul>
<h2>Java 基础</h2>
<p>把语言规则写成可运行的小例子，保留实际输出。</p>
<h3>1. 字符串内容与引用</h3>
<p><strong>题目：</strong> 下面的两个判断分别输出什么？</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> a </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> String</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"java"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> b </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> String</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"java"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">System</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">out</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">println</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(a </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">==</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> b);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">System</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">out</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">println</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">a</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">equals</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(b));</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>A. true、true</li>
<li>B. false、true</li>
<li>C. true、false</li>
<li>D. false、false</li>
</ul>
<p><strong>正确答案：B. false、true</strong></p>
<p><strong>解释：</strong> new String 创建新的 String 对象。两个引用的 == 判断对象身份，equals 判断字符串内容，所以依次为 false、true；不要把字符串常量池的示例直接套到显式 new 上。</p>
<p><strong>适用版本与边界：</strong> Java SE 8—21；按 Java SE 21 语言规范核对。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/javabgw/javajc.html" target="_blank">Java 基础：== 与 equals</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 在本机创建 StringCheck.java，分别运行 new String 与字符串字面量两组比较，保存代码、JDK 版本和真实输出。</p>
<h3>2. 相等对象的哈希约束</h3>
<p><strong>题目：</strong> 自定义 Key 覆写 equals，使业务编号相同的实例相等，却沿用 Object.hashCode。要使它可靠地用作 HashMap 的键，应做什么？</p>
<ul>
<li>A. 只把 HashMap 换成容量更大的</li>
<li>B. 删除 equals，保留业务相等语义</li>
<li>C. 按相同业务编号实现 hashCode，并保持键参与相等判断的字段稳定</li>
<li>D. 在 get 前手动触发 GC</li>
</ul>
<p><strong>正确答案：C. 按相同业务编号实现 hashCode，并保持键参与相等判断的字段稳定</strong></p>
<p><strong>解释：</strong> equals 相等的对象必须有相同的 hashCode。哈希表先依据哈希定位，再比较键；还要避免入表后修改参与 equals/hashCode 的字段。扩容不能修复契约错误。</p>
<p><strong>适用版本与边界：</strong> Java SE 8—21，Object/HashMap 契约；本题假设业务编号定义相等性。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/javabgw/javajh.html" target="_blank">Java 集合</a></li>
<li><a href="/javabgw/hashmapdcyl.html" target="_blank">HashMap 底层原理</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 用两个编号相同的 Key 执行 put/get，补齐 equals/hashCode 后再次运行，并增加一次入表后改编号的反例。</p>
<h3>3. 参数传递</h3>
<p><strong>题目：</strong> 执行下面的代码，调用方 xs 的内容是什么？</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> change</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">List</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">&#x3C;</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">String</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> p) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">  p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"B"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  p </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ArrayList</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Arrays</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">asList</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"C"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">List</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> xs </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ArrayList</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Arrays</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">asList</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"A"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">change</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(xs)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">System</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">out</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">println</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(xs);</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>A. [A]</li>
<li>B. [A, B]</li>
<li>C. [C]</li>
<li>D. 编译失败</li>
</ul>
<p><strong>正确答案：B. [A, B]</strong></p>
<p><strong>解释：</strong> Java 按值传递；此处传入的是引用值的副本。p.add 改变同一个列表对象；给局部变量 p 重新赋值不改变调用方 xs 的指向，结果为 [A, B]。</p>
<p><strong>适用版本与边界：</strong> Java SE 8—21；java.util.ArrayList/Arrays。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/javabgw/javajc.html" target="_blank">Java 基础：参数传递</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 将代码补进 main，打印调用前后列表；再写一个返回新列表的方法，由调用方显式接收返回值。</p>
<h3>4. finally 改写返回值</h3>
<p><strong>题目：</strong> 下面的方法返回什么？</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> value</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">() {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  try</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> { </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">return</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> }</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  finally</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> { </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">return</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>A. 1</li>
<li>B. 2</li>
<li>C. 先返回 1，再返回 2</li>
<li>D. 必然抛出异常</li>
</ul>
<p><strong>正确答案：B. 2</strong></p>
<p><strong>解释：</strong> try 的 return 先产生待返回值，但 finally 仍会执行。finally 的 return 使原来的返回过程被替代，结果为 2；这种写法也可能掩盖异常，应避免。</p>
<p><strong>适用版本与边界：</strong> Java SE 8—21，JLS 14.20.2 的 try-finally 完成规则。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/javabgw/javajc.html" target="_blank">Java 基础：异常处理</a></li>
<li><a href="/javabgw/yc.html" target="_blank">异常问答</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 运行原例，再删除 finally 中的 return 对比结果；把 try 改为抛出异常，观察 finally 返回如何掩盖它。</p>
<h3>5. 固定长度列表</h3>
<p><strong>题目：</strong> Arrays.asList(&quot;A&quot;, &quot;B&quot;) 返回的列表，哪项操作在本题条件下可以正常执行？</p>
<ul>
<li>A. add(&quot;C&quot;)</li>
<li>B. remove(0)</li>
<li>C. clear()</li>
<li>D. set(0, &quot;C&quot;)</li>
</ul>
<p><strong>正确答案：D. set(0, &quot;C&quot;)</strong></p>
<p><strong>解释：</strong> Arrays.asList 返回由数组支持的固定长度列表，允许 set 替换元素，不支持改变长度的 add/remove/clear。本题列表非空；需要增删时用 new ArrayList&lt;&gt;(原列表)。</p>
<p><strong>适用版本与边界：</strong> Java SE 8—21，Arrays.asList；不等同于 Java 9+ 的 List.of。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/javabgw/javajh.html" target="_blank">Java 集合：List</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 分别运行 set 和 add 并记录异常类型；再复制成 ArrayList 验证可增删，避免仅凭类名猜测能力。</p>
<h3>6. GC 请求不是完成证明</h3>
<p><strong>题目：</strong> 执行 System.gc() 后，只根据下一行日志，能确定什么？</p>
<ul>
<li>A. 所有不可达对象均已回收</li>
<li>B. 发生过一次完整的 Full GC</li>
<li>C. 发出了尽力执行垃圾回收的请求，不能据此确认具体对象已回收</li>
<li>D. 所有软引用均已清空</li>
</ul>
<p><strong>正确答案：C. 发出了尽力执行垃圾回收的请求，不能据此确认具体对象已回收</strong></p>
<p><strong>解释：</strong> System.gc() 是请求，API 不保证回收某个对象、回收数量或完成时间；JVM 也可能配置为忽略显式请求。判断回收行为应结合实际 JVM 配置、GC 日志和可观测数据。</p>
<p><strong>适用版本与边界：</strong> Java SE 8—21；具体收集器行为和参数依 JVM 实现。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/javabgw/JVM.html" target="_blank">JVM：引用与垃圾回收</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 在 JDK 21 用 -Xlog:gc 运行最小分配示例，记录 JVM 参数和实际 GC 日志；把请求时刻与日志事件分开描述。</p>
<h2>并发与事务</h2>
<p>画出执行时序，再用受控的并发或回滚验证边界。</p>
<h3>7. volatile 与复合操作</h3>
<p><strong>题目：</strong> 两个线程同时对 volatile int n 执行 n++，各执行 1000 次并 join。哪项判断成立？</p>
<ul>
<li>A. 最终一定是 2000</li>
<li>B. volatile 保证可见性，但不使 n++ 整体原子化</li>
<li>C. join 会补回丢失的递增</li>
<li>D. 把 n 改为 long 就能保证原子递增</li>
</ul>
<p><strong>正确答案：B. volatile 保证可见性，但不使 n++ 整体原子化</strong></p>
<p><strong>解释：</strong> n++ 包含读、加、写，线程可能读取相同旧值而覆盖更新；volatile 不把整段操作变为原子操作。join 保证等待线程结束，不修复丢失更新。可使用 AtomicInteger.incrementAndGet 或共同锁保护。</p>
<p><strong>适用版本与边界：</strong> Java SE 8—21，Java 内存模型；无其他同步保护的 n++。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/javabgw/bfdxc.html" target="_blank">并发与多线程</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 用 CountDownLatch 同时启动两个递增线程并多次运行，再替换为 AtomicInteger；某次恰好得到 2000 也不能证明原代码正确。</p>
<h3>8. 线程池先排队还是扩容</h3>
<p><strong>题目：</strong> 运行中的 ThreadPoolExecutor：core=2、max=4、有界队列容量=2。两个工作线程均被门闩阻塞，队列为空；此时再提交一个任务，且没有关闭和其他并发提交。会怎样？</p>
<ul>
<li>A. 先创建第 3 个工作线程</li>
<li>B. 立即拒绝</li>
<li>C. 先进入队列</li>
<li>D. 由提交线程立即执行</li>
</ul>
<p><strong>正确答案：C. 先进入队列</strong></p>
<p><strong>解释：</strong> 工作线程达到 core 后优先尝试入队，入队失败才尝试扩容到 max。本题队列有空位，因此第三个任务先排队；max=4 不代表每次都立即扩容到 4。</p>
<p><strong>适用版本与边界：</strong> JDK 21 ThreadPoolExecutor.execute；题设排除关闭和提交竞态。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/thread-pool-overload.html" target="_blank">线程池过载处理</a></li>
<li><a href="/open-reading/thread-pool-best-practices.html" target="_blank">JavaGuide 线程池选读</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 用门闩阻塞两个核心线程，逐个提交任务，记录 poolSize、queue.size 与拒绝时刻；结束时释放门闩并关闭线程池。</p>
<h3>9. CallerRunsPolicy 的执行位置</h3>
<p><strong>题目：</strong> 线程池已饱和但未关闭，使用 CallerRunsPolicy。HTTP 请求线程调用 execute 提交任务，且没有自定义策略。任务由谁执行？</p>
<ul>
<li>A. HTTP 提交线程</li>
<li>B. 一个必定新建的线程</li>
<li>C. JVM 的 GC 线程</li>
<li>D. 任务必定被丢弃</li>
</ul>
<p><strong>正确答案：A. HTTP 提交线程</strong></p>
<p><strong>解释：</strong> CallerRunsPolicy 在池未关闭时让调用 execute 的线程运行任务。这能减慢提交速度，也会占用 HTTP 请求线程，不能视为无成本的过载保护。池已关闭时该策略不运行任务。</p>
<p><strong>适用版本与边界：</strong> JDK 21 ThreadPoolExecutor.CallerRunsPolicy；线程池饱和且未关闭。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/thread-pool-overload.html" target="_blank">线程池过载：拒绝策略</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 在任务和请求入口打印线程名，复现饱和路径；再单独测试 shutdown 后提交，记录两条路径的差异。</p>
<h3>10. Spring 同类调用边界</h3>
<p><strong>题目：</strong> Spring 默认代理模式，无既有事务。普通方法通过 this.save() 调用同一个 Bean 上标有 @Transactional 的 save。只靠这个注解，事务会新生效吗？</p>
<ul>
<li>A. 一定会</li>
<li>B. 只要 save 是 public 就一定会</li>
<li>C. 换成 RuntimeException 才会</li>
<li>D. 不会，调用未经过事务代理</li>
</ul>
<p><strong>正确答案：D. 不会，调用未经过事务代理</strong></p>
<p><strong>解释：</strong> 默认代理模式只拦截经过代理的外部方法调用；this.save() 的自调用绕过代理，注解不能新建事务。若已有事务则是另一种题设；AspectJ 模式也需单独讨论。</p>
<p><strong>适用版本与边界：</strong> Spring Framework 6.2、默认 proxy 模式、无外层事务；不讨论 AspectJ。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/spring-transaction-boundary.html" target="_blank">Spring 事务边界</a></li>
<li><a href="/javabgw/Spring.html" target="_blank">Spring 问答</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 把事务入口移到另一个 Bean 并经注入代理调用；写集成测试在写入后抛异常，用独立查询核对是否回滚。</p>
<h3>11. 提交后事件的崩溃窗口</h3>
<p><strong>题目：</strong> 订单已提交，进程在 AFTER_COMMIT 监听器发送消息前崩溃。要使重启后仍能恢复投递，哪项方案针对这个窗口有效？</p>
<ul>
<li>A. 只把监听器改名</li>
<li>B. 订单与 outbox 待办同库同事务保存，提交后由扫描器重试，接收方幂等</li>
<li>C. 把内存队列容量翻倍</li>
<li>D. 仅在日志中输出发送成功</li>
</ul>
<p><strong>正确答案：B. 订单与 outbox 待办同库同事务保存，提交后由扫描器重试，接收方幂等</strong></p>
<p><strong>解释：</strong> 内存中的提交后事件不能跨进程崩溃恢复。同库同事务的 outbox 将业务变更和投递意图一起提交，后台可重新扫描。发送成功但确认前崩溃仍可能重发，因此接收方需要幂等。</p>
<p><strong>适用版本与边界：</strong> Spring Framework 6.2、JDBC 本地事务；订单与 outbox 使用同库及同一事务管理器。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/spring-transaction-boundary.html" target="_blank">Spring 事务：提交后事件与 outbox</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 设计两次故障注入：提交后未发送、发送后未确认。重启扫描器，检查最终投递及重复消息处理记录。</p>
<h3>12. 写后删除仍有竞态</h3>
<p><strong>题目：</strong> 读请求 R 已从数据库取到旧值，暂停在回填前；写请求 W 提交新值并删除缓存；R 再回填旧值。哪项判断正确？</p>
<ul>
<li>A. 成功删除一次就消除了全部竞态</li>
<li>B. Redis 单线程可阻止这个跨数据库时序</li>
<li>C. 缓存可能重新出现旧值，需要按业务设计 TTL、版本校验或补偿</li>
<li>D. 把删除放到数据库提交前就总能解决</li>
</ul>
<p><strong>正确答案：C. 缓存可能重新出现旧值，需要按业务设计 TTL、版本校验或补偿</strong></p>
<p><strong>解释：</strong> 缓存命令串行执行不能让数据库读取、提交与回填成为一个原子操作。本题时序能在删除成功后重新写入旧值。TTL 限制陈旧时长，版本检查和补偿需有明确实现与验证，不能宣称天然强一致。</p>
<p><strong>适用版本与边界：</strong> Cache-Aside，Redis 6/7 与独立关系型数据库；未使用跨资源原子协议。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/cache-invalidation.html" target="_blank">缓存失效与补偿</a></li>
<li><a href="/javabgw/Redis.html" target="_blank">Redis：缓存一致性</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 用两个门闩复现 R 读旧值、W 提交删除、R 回填的顺序，记录观察窗口，再验证你选择的修复方案。</p>
<h2>AI 检索</h2>
<p>固定文档与题集，逐题检查证据、版本和权限。</p>
<h3>13. 检索命中率的分母</h3>
<p><strong>题目：</strong> 10 个有答案且有权限的问题中，8 个的前 5 个片段命中至少一个参考片段；另有 2 个无答案题。本题定义的 Hit@5 是多少？</p>
<ul>
<li>A. 8/12</li>
<li>B. 10/12</li>
<li>C. 8/10</li>
<li>D. 只看生成答案，无法计算</li>
</ul>
<p><strong>正确答案：C. 8/10</strong></p>
<p><strong>解释：</strong> 按题设，Hit@5 的分母是有答案且有权限的 10 题，分子是命中的 8 题，结果为 80%。无答案题应单独评价拒答；命中一个片段也不代表多段证据充分或答案必然正确。</p>
<p><strong>适用版本与边界：</strong> 与框架无关的离线 RAG 评估；口径采用本站 RAG 评估指南，题设数字仅为计算示例。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/rag-evaluation.html" target="_blank">RAG 评估：指标与分母</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 为自己的题集增加 hasAnswer、authorized 和 referenceChunkIds，分别列出检索命中与拒答结果，保留每题分母归属。</p>
<h3>14. 权限过滤的位置</h3>
<p><strong>题目：</strong> 员工只能读部门 A 文档。检索服务把部门 B 的片段发送给模型后，才在浏览器隐藏引用。权限控制完成了吗？</p>
<ul>
<li>A. 完成，用户看不到引用即可</li>
<li>B. 没有，服务端应让未授权内容不进入检索结果和模型上下文，并检查响应与日志</li>
<li>C. 完成，只要在提示词写保密</li>
<li>D. 只需降低 temperature</li>
</ul>
<p><strong>正确答案：B. 没有，服务端应让未授权内容不进入检索结果和模型上下文，并检查响应与日志</strong></p>
<p><strong>解释：</strong> 隐藏界面不能撤回已经发送给模型的数据。权限身份应来自可信服务端上下文，并约束检索、上下文、缓存和输出。提示词不能替代业务授权；验收还要检查日志及缓存键是否混用身份。</p>
<p><strong>适用版本与边界：</strong> RAG 通用权限边界；Spring AI 1.x 接入时须核对实际版本的过滤接口。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/rag-evaluation.html" target="_blank">RAG 评估：权限</a></li>
<li><a href="/guides/tool-call-guardrails.html" target="_blank">工具调用防护</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 为同一问题配置 A/B 两种测试身份，保存召回片段 ID 与模型上下文，验证越权片段没有进入对应链路。</p>
<h3>15. 删除文档后的验收</h3>
<p><strong>题目：</strong> 知识库删除接口返回成功，但旧分块、回答缓存和历史引用都未检查。能据此认定在线问答不再暴露该文档吗？</p>
<ul>
<li>A. 能，HTTP 200 证明所有链路已清理</li>
<li>B. 能，只要删除文件名</li>
<li>C. 能，等模型自行遗忘即可</li>
<li>D. 不能，需要检查旧分块、缓存和引用的实际可达性与删除传播</li>
</ul>
<p><strong>正确答案：D. 不能，需要检查旧分块、缓存和引用的实际可达性与删除传播</strong></p>
<p><strong>解释：</strong> 文件删除成功只说明某个步骤完成。旧版本分块可能仍在索引中，缓存也可能继续返回旧答案；应按文档 ID、版本和权限检查检索、上下文及引用，并定义传播窗口。</p>
<p><strong>适用版本与边界：</strong> 带向量索引和回答缓存的 RAG；删除 API 的语义按实际实现核对。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/rag-document-update.html" target="_blank">RAG 文档更新与删除</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 先准备能命中旧文档的问题，记录分块 ID；执行删除后重复检索、缓存命中与引用访问，记录残留及清理时间。</p>
<h3>16. 评估集的近似问法泄漏</h3>
<p><strong>题目：</strong> 同一问题的五个近似改写被随机拆到调试集与保留集，然后反复根据保留集结果改提示词。更合理的处理是什么？</p>
<ul>
<li>A. 把近似问题按组划分，停止用保留集调参；被用来调参的保留题不能继续当独立验证</li>
<li>B. 只改文件名，保留同样拆分</li>
<li>C. 删除所有失败题</li>
<li>D. 把调试集和保留集的结果直接相加</li>
</ul>
<p><strong>正确答案：A. 把近似问题按组划分，停止用保留集调参；被用来调参的保留题不能继续当独立验证</strong></p>
<p><strong>解释：</strong> 近似改写共享语义，分散到两组容易泄漏。按问题族分组可减少重叠；保留集一旦参与调参，就失去独立性，应补充新的独立样本并标注数据与配置版本。</p>
<p><strong>适用版本与边界：</strong> 离线 RAG 评估方法，适用于不同模型和框架；不作线上效果保证。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/rag-evaluation.html" target="_blank">RAG 评估：题集与单变量调整</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 为每题增加 familyId，按 familyId 划分集合并检查交集；保留调参记录，新增一批未看过结果的验收题。</p>
<h3>17. 有引用不等于有依据</h3>
<p><strong>题目：</strong> 答案带了一个真实存在的引用，但片段没有提到答案中的退款期限。哪项结论成立？</p>
<ul>
<li>A. 引用存在，所以退款期限已被证实</li>
<li>B. 相似度高就能证实期限</li>
<li>C. 引用存在不证明该事实被支持，需要逐项核对证据，缺失时说明证据不足</li>
<li>D. 只要回答流畅就可通过</li>
</ul>
<p><strong>正确答案：C. 引用存在不证明该事实被支持，需要逐项核对证据，缺失时说明证据不足</strong></p>
<p><strong>解释：</strong> 引用有效性和事实支撑是不同检查。片段可访问仍可能不支持答案中的具体期限；应核对事实、版本及授权，对证据不足的问题补检或拒答，而非补造规则。</p>
<p><strong>适用版本与边界：</strong> RAG 通用事实核对；本题不假定特定模型或向量数据库。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/rag-evaluation.html" target="_blank">RAG 评估：证据支撑</a></li>
<li><a href="/guides/rag-document-update.html" target="_blank">RAG 文档版本</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 取一个回答，把事实陈述逐句列成表，给每句对应原文片段和版本；无依据的句子单独列为失败项。</p>
<h3>18. 模型工具调用与授权</h3>
<p><strong>题目：</strong> 模型生成了符合 JSON Schema 的关闭工单参数，工单属于其他租户。服务端下一步应怎样做？</p>
<ul>
<li>A. 立即执行，Schema 已保证权限</li>
<li>B. 使用可信登录身份检查租户及对象权限，不通过就拒绝执行</li>
<li>C. 让模型再说一次确认即可</li>
<li>D. 直接相信参数中的 tenantId</li>
</ul>
<p><strong>正确答案：B. 使用可信登录身份检查租户及对象权限，不通过就拒绝执行</strong></p>
<p><strong>解释：</strong> Schema 约束结构，不能证明调用者有权操作该对象。服务端应从可信会话取得身份，核对对象归属、业务状态和操作权限；确认、幂等与审计也需由业务层落实。</p>
<p><strong>适用版本与边界：</strong> 模型工具调用的通用服务端边界；不依赖特定 function calling API。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/tool-call-guardrails.html" target="_blank">工具调用：权限、参数、幂等</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 为工具处理器增加跨租户请求测试，核对拒绝结果和数据库无变更；再测试同一幂等键重复请求。</p>
<h2>项目表达</h2>
<p>整理个人职责、实现和可复查材料，明确尚未验证的部分。</p>
<h3>19. 哪些内容可写成个人成果</h3>
<p><strong>题目：</strong> 下面哪个表述只报告了题设已提供的可核对事实？</p>
<ul>
<li>A. 看过架构图，就写负责整套系统</li>
<li>B. 教程介绍日活百万，所以写我的项目日活百万</li>
<li>C. 未做压测，但写吞吐提升十倍</li>
<li>D. 独立实现了练习项目的文档删除接口，附提交、删除用例及本地运行记录，并注明未上线</li>
</ul>
<p><strong>正确答案：D. 独立实现了练习项目的文档删除接口，附提交、删除用例及本地运行记录，并注明未上线</strong></p>
<p><strong>解释：</strong> 最后一项对应明确职责、产物、验证条件和部署边界。阅读教程不能证明个人负责系统，项目介绍的规模不能变成个人业绩，未经测试不能填写提升比例。</p>
<p><strong>适用版本与边界：</strong> 项目与简历证据规范，适用于 2026 年本轮材料；非招聘评价标准。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/project-resume.html" target="_blank">项目经历与证据</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 选一条自己的简历描述，逐项标注本人职责、代码或文档位置、实际验证和未完成边界；删掉无证据数字。</p>
<h3>20. 性能数字的比较条件</h3>
<p><strong>题目：</strong> 两次耗时记录分别来自不同机器、不同题集，且一次排除了失败请求。能据此报告“优化后 P95 降低 40%”吗？</p>
<ul>
<li>A. 能，数字更小即可</li>
<li>B. 不能，应统一对照条件并记录样本数、失败率及统计口径，再据实比较</li>
<li>C. 只要换算成平均数就能</li>
<li>D. 删掉慢请求即可</li>
</ul>
<p><strong>正确答案：B. 不能，应统一对照条件并记录样本数、失败率及统计口径，再据实比较</strong></p>
<p><strong>解释：</strong> 机器、负载、数据、配置及失败过滤差异会改变分布。要归因于优化，应控制条件并报告成功耗时的口径、样本数和失败率；未经这样的验证，题设不能支持 40% 的改善结论。</p>
<p><strong>适用版本与边界：</strong> HTTP/检索链路的对照测量原则；无预设性能结果。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/project-resume.html" target="_blank">项目简历：数字与技术含义</a></li>
<li><a href="/guides/rag-evaluation.html" target="_blank">RAG 评估：耗时与失败</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 写一份固定环境、题集、并发、预热、配置版本和失败处理方式的运行说明，保存两轮原始记录后再计算比较。</p>
<h3>21. 数据不足时如何选项目</h3>
<p><strong>题目：</strong> 准备做企业知识库，但目前没有授权企业文档。哪项计划可以立即执行且边界明确？</p>
<ul>
<li>A. 用公开可用且许可允许的资料构建小范围检索练习，注明数据来源与未接入生产</li>
<li>B. 编造一套真实企业客户数据</li>
<li>C. 直接写已经服务多家企业</li>
<li>D. 先承诺准确率 99% 再找数据</li>
</ul>
<p><strong>正确答案：A. 用公开可用且许可允许的资料构建小范围检索练习，注明数据来源与未接入生产</strong></p>
<p><strong>解释：</strong> 小范围、许可清晰且可获得的数据能支撑真实实现与验收。练习应明确数据及部署边界，不把合成或公开材料包装成生产客户结果，也不在评估前承诺准确率。</p>
<p><strong>适用版本与边界：</strong> Java AI 项目范围规划；数据许可按所选资料单独核对。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/java-ai-project-selection.html" target="_blank">Java AI 项目选型</a></li>
<li><a href="/javaai-projects/" target="_blank">Java AI 项目入口</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 写一页项目范围：数据来源及许可、用户问题、Java 业务入口、AI 能力边界和最小验收，先实现其中一个闭环。</p>
<h3>22. 故障描述需要什么材料</h3>
<p><strong>题目：</strong> 要说明“重复请求未重复扣减库存”，下面哪组材料最能直接支持这个具体结论？</p>
<ul>
<li>A. 一张服务启动截图</li>
<li>B. 依赖清单含 Redis 和 MySQL</li>
<li>C. 两次使用同一业务幂等键的请求、对应响应、事务及库存变更记录，并标注环境</li>
<li>D. 只展示接口返回 200</li>
</ul>
<p><strong>正确答案：C. 两次使用同一业务幂等键的请求、对应响应、事务及库存变更记录，并标注环境</strong></p>
<p><strong>解释：</strong> 要证明重复请求的业务效果，需要把相同幂等键、请求、响应和实际数据变化串起来。组件名字或启动成功不证明幂等。结论仍限于已测场景；并发和崩溃路径要另测。</p>
<p><strong>适用版本与边界：</strong> Java 后端项目验证；具体事务及幂等实现依实际数据库与框架。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/project-resume.html" target="_blank">项目经历：证据驱动追问</a></li>
<li><a href="/guides/tool-call-guardrails.html" target="_blank">工具调用：重复执行与幂等</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 保存一组连续重复请求和一组并发重复请求，核对库存只变化一次；再明确尚未覆盖的故障窗口。</p>
<h3>23. 检索与微调的能力边界</h3>
<p><strong>题目：</strong> 项目只实现了文档切分、向量检索和把片段加入提示词，没有任何训练作业。哪项描述符合已实现内容？</p>
<ul>
<li>A. 完成了基础模型预训练</li>
<li>B. 完成了模型微调</li>
<li>C. 训练了企业专属大模型</li>
<li>D. 实现了 RAG 检索增强链路；没有更新模型权重的证据</li>
</ul>
<p><strong>正确答案：D. 实现了 RAG 检索增强链路；没有更新模型权重的证据</strong></p>
<p><strong>解释：</strong> 将外部文档检索后加入上下文属于检索增强，不等于更新模型权重。没有训练作业、训练配置和权重产物，不应把 RAG 写成微调或预训练。</p>
<p><strong>适用版本与边界：</strong> RAG 与训练的能力边界；模型供应商与框架无关。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/java-ai-project-selection.html" target="_blank">Java AI 项目选型：先跑检索</a></li>
<li><a href="/guides/project-resume.html" target="_blank">项目简历：核实技术含义</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 画出入库与在线问答两条链路，逐段对应代码；在简历中用实际实现的能力名称替换夸大的训练表述。</p>
<h3>24. 自测结果应如何使用</h3>
<p><strong>题目：</strong> 24 道固定题全部答对后，下面哪项下一步合理？</p>
<ul>
<li>A. 直接认定面试必过</li>
<li>B. 选择一个仍缺实践证据的主题，执行下一步任务并整理真实记录</li>
<li>C. 生成一个就业概率</li>
<li>D. 把题目答对数写成线上项目成果</li>
</ul>
<p><strong>正确答案：B. 选择一个仍缺实践证据的主题，执行下一步任务并整理真实记录</strong></p>
<p><strong>解释：</strong> 固定题只能反馈这些题的作答情况，不能预测就业或面试结果。把解析中的知识落实为代码、时序图、回放记录和职责说明，才能补充可复查的学习材料。</p>
<p><strong>适用版本与边界：</strong> 本工具 2026-11-06 题库；统计只描述当前题库内的作答。</p>
<p><strong>进一步阅读：</strong></p>
<ul>
<li><a href="/guides/project-resume.html" target="_blank">项目简历与证据</a></li>
<li><a href="/roadmaps/" target="_blank">学习路线</a></li>
</ul>
<p><strong>可执行的下一步：</strong> 从错题或尚无实践记录的主题挑一项，完成其具体任务；在复习单写下文件位置、实际现象与仍未解决的问题。</p>
<h2>规则核对与后续学习</h2>
<p>语言与 API 规则可核对 <a href="https://docs.oracle.com/javase/specs/jls/se21/html/index.html" target="_blank" rel="noopener noreferrer">Java SE 21 语言规范</a>、<a href="https://docs.oracle.com/en/java/javase/21/docs/api/index.html" target="_blank" rel="noopener noreferrer">Java SE 21 API</a>；线程池规则见 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html" target="_blank" rel="noopener noreferrer">ThreadPoolExecutor API</a>。Spring 题设采用 <a href="https://docs.spring.io/spring-framework/reference/6.2/data-access/transaction/declarative/annotations.html" target="_blank" rel="noopener noreferrer">Spring Framework 6.2 事务文档</a>。题设有明确版本限制时，按该限制复现。</p>
<p>工程题的具体判断口径见每题链接的站内指南。先完成一个代码例子、故障回放或证据表，再回到 <a href="/practice/" target="_blank">学习工作台</a> 保存自己的实践记录，或直接在自己的文档中整理。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 与 AI 学习自测工作台</title>
      <link>https://qiuzhiwang.vip/practice/</link>
      <guid>https://qiuzhiwang.vip/practice/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 与 AI 学习自测工作台</source>
      <description>24 道原创自测题，覆盖 Java 基础、并发与事务、AI 检索和项目表达。即时解析、错题回看、本地进度及 Markdown 复习单，把知识落实为可以复查的练习材料。</description>
      <category>学习实践</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>这里的任务是找到理解缺口，并完成一项可以复查的练习。题库共 <strong>24 道原创单选题，四组各 6 题</strong>。所有题目都有确定答案、解释、适用版本、站内深阅读与下一步任务。</p>
<p><strong>无需 JavaScript 也能学习：</strong><a href="/practice/questions.html" target="_blank">静态阅读全部 24 题与完整解析</a>。可以在自己的文档中先写答案，再对照解析，保存实际练习材料。</p>
<h2>先选一个能完成的任务</h2>
<p>| 主题 | 你会判断什么 | 完成后留下什么 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>使用记录与隐私说明</title>
      <link>https://qiuzhiwang.vip/privacy/</link>
      <guid>https://qiuzhiwang.vip/privacy/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">使用记录与隐私说明</source>
      <description>说明学习进度与错题、本地来源记录、可选第一方统计、关闭和清理方式，以及第三方评论服务。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>当前版本会在本浏览器保存最近最多 200 条页面访问、资料下载点击、资料入口点击、咨询入口点击和复制本站公开微信号的动作。来源记录只包含入口路径、UTM 渠道标签和来源网站域名，不记录简历、聊天内容或其他网站的浏览历史。</p>
<p><strong>默认版本的这些记录保存在你的设备，未启用远端上传，不代表全站总访问量。</strong> 下载点击不等于文件已经下载成功，复制微信号不等于已经添加好友或购买服务。</p>
<p>浏览器启用“请勿跟踪”（Do Not Track）时，不写入上述使用记录。你也可以在浏览器设置中清除本站存储；这会同时清除来源标签和记录。请不要在 UTM 参数中填写手机号、姓名、邮箱或简历信息。</p>
<p>评论使用原站的 Giscus/GitHub 服务，加载与登录遵循对应服务的隐私规则。是否登录和发表评论由你自行决定。</p>
<h2>学习进度与错题</h2>
<p>学习工作台根据你的作答保存本浏览器的进度和错题，便于继续复习。记录不包含姓名、简历或联系方式。可以导出复习单，或使用工作台的重置操作清理本工具的数据；换设备或清理站点存储后不保证保留。</p>
<p>成长路线另行保存所选目标与自己勾选的练习标记，仅用于在本浏览器继续学习，不上传。可以在成长路线中重置当前路线；其他路线和自测错题不会随之清空。勾选由自己决定，不代表网站验证了你的实践结果。下载的路线练习单是空白模板，不会自动包含浏览器中的标记。</p>
<h2>可选站点统计</h2>
<p>网站提供可选的第一方统计能力，默认未启用。站点运营者配置并启用后，可以接收页面路径、事件时间、随机会话标识、主题和预先约定的渠道代码，以及资料或咨询入口的点击路径。传输不包含页面查询参数、完整来源网址、答题内容、微信号或聊天内容，接收器不保存 IP 和 User-Agent 字段。</p>
<p>统计用途是核对哪些内容和资料被使用，不能据此推断你已经关注公众号、添加微信或购买服务。浏览器开启 Do Not Track 时不发送这些统计动作。若以后增加订阅、表单或其他数据用途，会在相应入口另行说明。</p>
]]></content:encoded>
    </item>
    <item>
      <title>首个 Java 知识库项目：从启动到保存验收结果</title>
      <link>https://qiuzhiwang.vip/projects/knowledge-service-first-run.html</link>
      <guid>https://qiuzhiwang.vip/projects/knowledge-service-first-run.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">首个 Java 知识库项目：从启动到保存验收结果</source>
      <description>用 PowerShell 跟做 Java 21 与 Spring Boot 知识库服务，从环境、教学身份、创建与查询文档到 v2 更新、无证据拒答和权限对照，保存不含凭据的项目成果。</description>
      <category>可运行项目</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>学过 Java 类、方法和集合，能看懂 JSON，就可以跟做这一单元。你会启动一个本机 HTTP 服务，用接口创建文档，再检查回答引用的是哪个版本、当前身份有没有权读取，最后保存自己的结果。暂时不要求读懂整个 Spring Security 或检索实现。</p>
<p>先<a href="/downloads/knowledge-service.zip">下载项目源码</a>，完整解压。已有网站源码时，项目位于 <code>examples/knowledge-service</code>。<a href="/projects/knowledge-service.html" target="_blank">项目总览</a>保留源码阅读路径、源码摘要与原有测试报告。</p>
<h2>先知道这次会运行什么</h2>
<p>默认实现是 <strong>H2 文件数据库 + 本地词项检索 + 原文摘录</strong>，返回模式为 <code>local-lexical-extractive</code>。英文按词项、中文主要按相邻双字切分；问题词项覆盖率至少 0.6 才返回引用，分数不是模型置信度。正文整体作为一个检索单元，答案直接取首条引用的原文。词项重合不保证真正回答了问题。</p>
<p>本单元不用 Docker、PostgreSQL 或模型密钥，也不要加 <code>-Pai</code>。既有 pgvector 实测使用真实数据库与三维确定性 fixture，证明存储、过滤和版本处理；<strong>真实模型调用、语义质量和生成回答未验收</strong>。源码的可选 AI 模式也没有聊天模型生成步骤。</p>
<p>2026-11-06 的教学命令实跑与源码核对范围见文末。以下“预期”是你运行时要比较的目标，不代表你电脑上已经通过。</p>
<h2>1. 准备环境，找到正确目录</h2>
<p>本页使用 PowerShell 命令，本次实跑环境为 Windows 上的 PowerShell 7.6.9；Windows PowerShell 5.1 未实跑。打开终端 A，进入含 <code>pom.xml</code> 的 <code>knowledge-service</code> 目录。仓库用户可用 <code>Set-Location 'D:/zhuomian/qiuzhiwang_pro/examples/knowledge-service'</code>；下载包用户换成自己的解压目录。</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">java </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">version</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">mvn.cmd</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">version</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Test-Path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> .</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">pom.xml</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>预期：Java 为 21，Maven 为 3.9+，最后输出 <code>True</code>。还要核对 Maven 输出中的 Java version / runtime，避免它实际使用另一个 JDK。第一次启动需要联网下载 Maven 依赖。</p>
<h2>2. 启动服务，隔离端口和数据库</h2>
<p>项目默认配置是 <code>127.0.0.1:8085</code>、<code>jdbc:h2:file:./data/knowledge;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0</code>。本教程改用 <strong>18085</strong>，每次生成一个独立临时目录。数据库 URL 用环境变量覆盖，端口用 Spring Boot 运行参数覆盖；不需要修改配置文件。</p>
<p>在终端 A 依次执行：</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">$ErrorActionPreference</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'Stop'</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 只探测端口是否空闲，不关闭任何已有服务。绑定失败就先停在这里。</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$probe</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">System.Net.Sockets.TcpListener</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]::new([</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">System.Net.IPAddress</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]::Loopback, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">18085</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">try</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$probe.Start</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">finally</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$probe.Stop</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$taskDir</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> Join-Path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ([</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">System.IO.Path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]::GetTempPath()) (</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'knowledge-first-run-'</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">guid</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]::NewGuid().ToString(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'N'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">))</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">New-Item</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">ItemType Directory </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">Path </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$taskDir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> | </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Out-Null</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$dbPath</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">Join-Path</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taskDir</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'knowledge'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">).Replace(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'\'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'/'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$env:SPRING_DATASOURCE_URL</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "jdbc:h2:file:</span><span style="--shiki-light:#50A14F;--shiki-dark:#E06C75">$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dbPath</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$taskDir</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  # 记下此目录，数据库在这里，不在项目的 data 目录。</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">mvn.cmd</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">B </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">ntp spring</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">boot:run </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'-Dspring-boot.run.arguments=--server.port=18085 --spring.profiles.active=default'</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>终端保持运行。预期日志显示 Tomcat 使用 18085、Hikari 的 H2 URL 指向刚才的临时目录，最后出现 <code>Started Application</code>。URL 不对就先停止本次启动再核对，避免写入旧库。这里显式使用运行 profile <code>default</code>，不要同时启用 <code>ai</code>。</p>
<p>打开<strong>终端 B</strong>，后续接口命令都在同一个终端 B 顺序执行，不要中途关闭，否则变量会丢失：</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">$ErrorActionPreference</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'Stop'</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$base</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'http://127.0.0.1:18085/api'</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$health</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> Invoke-RestMethod</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "</span><span style="--shiki-light:#50A14F;--shiki-dark:#E06C75">$</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">base</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/health"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$health</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>预期为 <code>status = UP</code>。浏览器也可打开 <code>http://127.0.0.1:18085/</code> 查看控制台；本单元的可复核结果由下面的接口命令取得。网站页面不会自动启动或连接你的本机服务。</p>
<h2>3. 登录教学身份，确认服务端认定的角色</h2>
<p>这些固定密码仅限本机教学。空数据库启动时会初始化以下用户；不会自动导入样例文档。</p>
<p>| 用户 | 教学密码 | 租户 | 角色 | 本单元用途 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 知识库检索服务：认证、文档版本、权限与证据返回</title>
      <link>https://qiuzhiwang.vip/projects/knowledge-service.html</link>
      <guid>https://qiuzhiwang.vip/projects/knowledge-service.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 知识库检索服务：认证、文档版本、权限与证据返回</source>
      <description>可下载运行的 Java 21 与 Spring Boot 知识库项目，提供数据库持久化、Bearer 身份、租户角色过滤、文档更新删除和带引用检索；附 Spring AI pgvector 接入及实际测试范围。</description>
      <category>可运行项目</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong><a href="/downloads/knowledge-service.zip">下载项目源码与验证记录</a></strong> · <a href="/downloads/knowledge-service-report.json">查看机器可读测试报告</a></p>
<p>这个项目把检索边界放进真实 HTTP 服务：登录取得服务端身份，写入有版本的文档，再检查“当前用户能用哪些原文回答这个问题”。源码围绕认证、数据库事务、文档状态和检索过滤展开，可以沿请求链路理解这些模块如何协作。</p>
<p>本页整理源码阅读路径、接口约定与已有验证证据。需要逐步启动时，可查<a href="/projects/knowledge-service-first-run.html" target="_blank">本地运行与 API 调用说明</a>，其中给出环境、身份、创建文档、版本更新、拒答和权限对照的完整命令。</p>
<p>默认模式采用 H2 文件数据库与本地词项检索，不需要模型密钥或 Docker。它返回原文摘录；语义向量检索使用单独的 Spring AI / pgvector 模式，运行条件与验证范围见下文和包内 README。</p>
<h2>本地运行</h2>
<p>需要 <strong>JDK 21 与 Maven 3.9+</strong>。下面是包内默认启动方式，默认端口为 8085、数据库为当前目录的 <code>data/knowledge.mv.db</code>。首次跟做请优先使用<a href="/projects/knowledge-service-first-run.html" target="_blank">教程里的 18085 与临时数据库命令</a>，不要同时执行两套启动步骤。</p>
<p>完整解压后进入 <code>knowledge-service</code> 目录，在 Windows 执行：</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.\</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">start.cmd</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># 或者</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">mvn.cmd</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">B </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">ntp spring</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">boot:run</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Linux/macOS 使用 <code>sh start.sh</code>。启动后打开 <code>http://127.0.0.1:8085/</code>，在项目控制台登录、添加文档并查询。健康检查路径为 <code>/api/health</code>。</p>
<p>本地演示用户写在包内 README。控制台默认使用 H2，数据保存在启动目录的 <code>data/knowledge.mv.db</code>；重启后可以继续查看。相同数据库文件不能同时由多个服务进程占用。</p>
<p>首次遇到依赖下载、端口或 JDK 问题，按<a href="/help/" target="_blank">使用帮助</a>记录第一条错误。网站本身提供代码和说明，不会自动连接你电脑上的服务。</p>
<h2>可以沿着哪条链路读源码？</h2>
<p>| 环节 | 实现重点 | 验证要点 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 与 AI 项目解析：源码、架构与工程实践</title>
      <link>https://qiuzhiwang.vip/projects/</link>
      <guid>https://qiuzhiwang.vip/projects/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 与 AI 项目解析：源码、架构与工程实践</source>
      <description>阅读 Java 21 与 Spring Boot 知识库服务源码，分析认证、持久化、文档版本、租户权限和检索引用，并提供可运行实验、启动说明与行业架构拆解。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>从接口进入实现，分析模块职责、数据流与失败处理。这里整理可下载的服务源码、机制实验和行业架构方案；每个项目说明已有功能、依赖与验证范围。</p>
<h2>Java AI：向量检索与模型问答</h2>
<p><a href="/projects/java-ai-rag.html" target="_blank">Java AI 知识库项目</a>把文档分块、嵌入接口、向量相似度与模型回答放在同一条请求链上。重点看模型调用怎样与租户权限、文档版本和证据引用衔接，以及失败、超时和流式输出怎样处理。</p>
<p>模型供应商需要自行配置。项目说明区分本地 HTTP 契约测试和真实模型验证，源码提供独立启动方式。</p>
<h2>知识库检索服务</h2>
<p><strong><a href="/projects/knowledge-service.html" target="_blank">Java 知识库检索服务：认证、文档版本、权限与证据返回</a></strong></p>
<p>基于 Java 21 与 Spring Boot，围绕“当前用户可以依据哪些文档回答问题”组织接口与存储。可以沿下面几条链路阅读源码：</p>
<ul>
<li><strong>认证与身份</strong>：登录、Bearer token、过期撤销，以及请求中的服务端租户和角色。</li>
<li><strong>文档与事务</strong>：当前版本、历史版本、<code>expectedVersion</code> 并发冲突与删除墓碑。</li>
<li><strong>检索与引用</strong>：授权后的候选集、当前状态复核、原文摘录、来源版本和无证据拒答。</li>
<li><strong>向量接入</strong>：Spring AI / pgvector 独立模式，以及向量候选与权威文档状态的衔接。</li>
</ul>
<p>默认模式使用 H2、词项检索与原文摘录。向量接入条件与已有测试范围在项目说明中单列，模型生成仍属于后续扩展。</p>
<p><a href="/projects/knowledge-service.html" target="_blank">源码与实现说明</a> · <a href="/downloads/knowledge-service.zip">下载源码包</a> · <a href="/projects/knowledge-service-first-run.html" target="_blank">本地运行说明</a> · <a href="/handbook/rag-acceptance.html" target="_blank">接口验证手册</a></p>
<h2>机制实验与代码示例</h2>
<p>| 内容 | 分析重点 | 阅读入口 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>面试复盘填写样例：HashMap 树化条件</title>
      <link>https://qiuzhiwang.vip/samples/interview-review.html</link>
      <guid>https://qiuzhiwang.vip/samples/interview-review.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">面试复盘填写样例：HashMap 树化条件</source>
      <description>用一段教学问答演示如何记录答错的地方、核对 JDK 版本、修订回答并安排下一步，不把口头记忆当作已经掌握。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>下面是一份<strong>教学演练填写样例</strong>，不是学员的真实面试记录，也没有求职结果。</p>
<h2>一份填写完成的记录</h2>
<p>| 字段 | 示例填写 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>项目经历填写样例：用源码和回放记录说明贡献</title>
      <link>https://qiuzhiwang.vip/samples/project-resume.html</link>
      <guid>https://qiuzhiwang.vip/samples/project-resume.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">项目经历填写样例：用源码和回放记录说明贡献</source>
      <description>以本站可运行的 Java 检索实验为例，把笼统技术栈介绍改为可以定位到实现与验收记录的项目条目。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>这个例子对应<a href="/labs/rag-evidence.html" target="_blank">本站 Java 检索验收实验</a>。它是个人教学实验，只有合成文档与本地回放，没有生产客户、流量或收益数据。</p>
<h2>从笼统描述开始</h2>
<blockquote>
<p>负责企业级 AI 知识库架构，使用 Java、RAG、多租户和权限控制，提升检索准确率。</p>
</blockquote>
<p>这句话没有解释实现范围，“企业级”和“提升准确率”也缺少相应证据。</p>
<h2>先填事实，再写项目条目</h2>
<p>| 字段 | 填写内容 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>RAG 验收表填写样例：记录一次检索回放</title>
      <link>https://qiuzhiwang.vip/samples/rag-evaluation.html</link>
      <guid>https://qiuzhiwang.vip/samples/rag-evaluation.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">RAG 验收表填写样例：记录一次检索回放</source>
      <description>对照 Java 小实验的实际输出，记录测试身份、文档版本、预期来源、实际来源和修正情况，区分固定回归与生产质量。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>这份记录来自<a href="/labs/rag-evidence.html" target="_blank">Java 检索验收实验</a>的实际运行。它只检查检索边界，尚未覆盖模型生成，不能作为完整 RAG 的验收结论。</p>
<p><strong><a href="/downloads/rag-evaluation-filled.csv">下载填写完成的验收 CSV</a></strong> · <a href="/downloads/rag-evidence-report.json">核对原始 JSON</a></p>
<h2>先把实验条件写完整</h2>
<p>| 字段 | 本例填写 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>填写样例：看完再动手</title>
      <link>https://qiuzhiwang.vip/samples/</link>
      <guid>https://qiuzhiwang.vip/samples/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">填写样例：看完再动手</source>
      <description>对照填写完成的面试复盘、项目简历与 RAG 验收记录，理解空白检查表应该留下哪些具体信息。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>不知道模板该填到什么程度时，先看一份完整样例。</p>
<p>| 你要完成的事 | 看一份填好的 | 数据从哪里来 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>后端进阶技术阅读与源码资料</title>
      <link>https://qiuzhiwang.vip/resources/backend-engineering.html</link>
      <guid>https://qiuzhiwang.vip/resources/backend-engineering.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">后端进阶技术阅读与源码资料</source>
      <description>按 Java 运行时、并发、事务、缓存与 AI 后端工程组织技术教程和源码入口，提供可直接下载的技术阅读与源码包、官方依据及实际运行记录。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>面向已有 Java 基础、希望深入实现机制与工程取舍的读者。这里将技术文章、Java 示例和项目源码放在同一份目录中，便于从问题定位到实现与参考依据。</p>
<h2>IO、并发与数据存储进阶</h2>
<p><a href="/tutorials/" target="_blank">16 篇后端进阶教程</a>覆盖 IO/NIO、AQS、ThreadLocal、CompletableFuture、虚拟线程、JDK 迁移、MySQL 的事务与锁，以及 Redis 复制、持久化和高可用。各篇给出机制解释、适用版本和参考资料。</p>
<p><a href="/downloads/backend-engineering-pack.zip">下载后端工程阅读与源码包</a> · <a href="/downloads/backend-engineering-pack-manifest.json">文件清单与 SHA-256</a> · <a href="/downloads/java-advanced-verification.json">Java 示例统一运行记录</a></p>
<h2>技术阅读目录</h2>
<p>| 主题 | 在线阅读 | 适合了解什么 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 转 AI 资料：验收手册、用例与检索回放</title>
      <link>https://qiuzhiwang.vip/resources/java-ai.html</link>
      <guid>https://qiuzhiwang.vip/resources/java-ai.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 转 AI 资料：验收手册、用例与检索回放</source>
      <description>面向有 Java 基础的开发者，用验收手册与现有合成用例检查引用、权限和文档更新，保留自己的实际回放结果。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>适合会写 Java 后端接口，正在做知识库问答或 AI 应用的同学。这组资料帮助你把“演示能跑”拆成可以逐条检查的行为：引用能否定位、权限是否生效、文档变化后有没有继续返回旧内容。</p>
<div class="hint-container tip">
<p class="hint-container-title">资料包与在线预览</p>
<p><strong><a href="/downloads/java-ai-practice-kit.zip" download>下载 Java AI ZIP：17 页验收手册、用例表、检索实验与后端源码</a></strong></p>
<p>无需先关注或加微信。可先看 <a href="/resources/#%E6%89%8B%E5%86%8C%E7%9B%AE%E5%BD%95%E9%A2%84%E8%A7%88" target="_blank">17 页 PDF 目录预览</a>和 <a href="/samples/rag-evaluation.html" target="_blank">RAG 验收填写样例</a>，再决定取哪些材料。解压后先读包内说明；手册、空白用例与已有记录也可在下方单独取用。</p>
</div>
<p><span id="今天完成什么"></span></p>
<h2>资料范围与源码说明</h2>
<p>这组资料包含验收手册、空白用例、填写样例、检索实验记录与后端源码。手册说明引用、权限和文档更新等验收维度；样例与原始 JSON 展示合成文档上的预期来源、对照结果和修正结果。</p>
<p>可先阅读手册目录与在线正文，再结合下方删除场景理解文档最新状态、删除标记和权限过滤的关系。查看源码包时，以包内 README 的环境、启动方式和实现范围为准；记录中的 Java 版本与源码哈希用于识别对应实验。</p>
<p>需要动手时，可按<a href="/labs/rag-evidence.html" target="_blank">检索实验说明</a>复跑并保存自己的结果。教学实验只检查合成数据上的检索边界，模型生成、向量检索与真实认证需要另外验证；未执行的用例保留为空白。</p>
<h2>资料预览：一条删除场景的实际记录</h2>
<p>下表来自已有的 <a href="/samples/rag-evaluation.html" target="_blank">RAG 验收填写样例</a>与本站检索实验，使用合成文档。</p>
<p>| 记录项 | 现有记录 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 面试资料：检查表、复习计划与复盘样例</title>
      <link>https://qiuzhiwang.vip/resources/java-interview.html</link>
      <guid>https://qiuzhiwang.vip/resources/java-interview.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 面试资料：检查表、复习计划与复盘样例</source>
      <description>用 Java 面试检查表找出一项回答缺口，对照填写样例完成复盘，再用线程池实验留下可核对的练习记录。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>适合已经学过 Java、准备后端面试，却不知道先补哪一项的同学。这组资料用来把“看过知识点”变成一份能讲清结论、原理、例子和边界的回答记录。</p>
<div class="hint-container tip">
<p class="hint-container-title">资料包与在线预览</p>
<p><strong><a href="/downloads/java-interview-practice-kit.zip" download>下载 Java 面试 ZIP：检查表、28 天计划、复盘样例与线程池实验</a></strong></p>
<p>无需先关注或加微信。解压后先看包内说明，从检查表挑一题开始。想先看看填写方式，可以直接读 <a href="/samples/interview-review.html" target="_blank">HashMap 复盘样例</a>；已有模板、填写样例和实验材料也可在下方单独取用。</p>
</div>
<p><span id="今天完成什么"></span></p>
<h2>资料内容与阅读方式</h2>
<p>这组资料包含面试知识检查表、28 天复习计划、HashMap 复盘样例与线程池饱和实验。检查表按结论、原理、例子和边界组织回答；复盘样例展示如何从一个笼统结论追溯到版本条件与源码分支。</p>
<p>阅读时可先对照下方样例，再查阅单份资料。源码结论需要注明所用 JDK 版本与具体调用路径；实验说明和已有 stdout 可用于理解队列与拒绝策略，自己的运行结果应另行保存。</p>
<p>需要练习时，可任选一题记录原回答、核对源码或运行小例子，再补充修订说明。复习计划和记录模板按需使用，不是阅读或交流的前置要求。</p>
<h2>资料预览：一份复盘如何填写</h2>
<p>下表节选自已有的 <a href="/samples/interview-review.html" target="_blank">HashMap 复盘样例</a>。这是教学演练，供对照填写。</p>
<p>| 记录项 | 样例内容 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>项目简历资料：事实模板、填写样例与证据整理</title>
      <link>https://qiuzhiwang.vip/resources/project-resume.html</link>
      <guid>https://qiuzhiwang.vip/resources/project-resume.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">项目简历资料：事实模板、填写样例与证据整理</source>
      <description>对照现有项目填写样例，整理真实职责、实现范围、验证证据与技术取舍，完成一条能接受追问的项目经历。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>适合做过工作项目或个人练习，但简历只剩技术名词、面试时讲不清自己贡献的同学。这份资料先帮你收集事实，再写出“我负责什么、为什么这样做、怎样验证、还有什么边界”。</p>
<div class="hint-container tip">
<p class="hint-container-title">资料包与在线预览</p>
<p><strong><a href="/downloads/project-resume-practice-kit.zip" download>下载项目简历 ZIP：事实模板、项目表达与面试复盘样例</a></strong></p>
<p>无需先关注或加微信。可先读 <a href="/samples/project-resume.html" target="_blank">项目经历填写样例</a>，再选自己参与的一项工作。解压后先看包内说明；模板和完整填写样例也可在下方单独取用。</p>
</div>
<p><span id="今天完成什么"></span></p>
<h2>模板内容与样例说明</h2>
<p>这组资料围绕项目事实、个人职责、实现范围、验证证据与技术取舍组织项目经历。模板区分生产工作、个人项目、教学练习和设计方案；填写样例展示从笼统描述到可追问条目的改写过程。</p>
<p>可先阅读下方 Java 检索教学实验样例，再打开完整填写材料，对照代码位置、源码哈希与回放记录理解每条描述的依据。自己的条目只写亲自实现、参与或验证过的部分，并说明项目性质与实现边界。</p>
<p>需要整理经历时，再复制模板填写事实、证据和失败场景，或练习三分钟讲解。涉及性能数字时，保存基线、环境、样本与计算口径；没有测量依据就写功能与验证方法。</p>
<h2>资料预览：已有实验如何写进项目条目</h2>
<p>下表摘自 <a href="/samples/project-resume.html" target="_blank">项目经历填写样例</a>，对应本站 Java 检索教学实验。</p>
<p>| 模板字段 | 现有样例 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>技术阅读与源码资料中心</title>
      <link>https://qiuzhiwang.vip/resources/</link>
      <guid>https://qiuzhiwang.vip/resources/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">技术阅读与源码资料中心</source>
      <description>按 Java 后端、AI 应用与项目表达选读技术教程、源码示例和资料包，保留面试、简历及检索验收资料入口。</description>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>这里按技术主题汇集教程、Java 示例、项目源码与参考资料。先按目录选择要理解的机制，再对照源码或样例阅读。<strong>现有资料可直接下载，无需先关注公众号或加微信。</strong></p>
<h2>内容目录与用途</h2>
<p>| 内容 | 阅读用途 | 源码与资料 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/itfdzgzh.png" type="image/png"/>
    </item>
    <item>
      <title>Java 转 AI 路线：先跑通知识服务，再接真实模型</title>
      <link>https://qiuzhiwang.vip/roadmaps/java-ai.html</link>
      <guid>https://qiuzhiwang.vip/roadmaps/java-ai.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 转 AI 路线：先跑通知识服务，再接真实模型</source>
      <description>基于可运行的 Java 知识库服务，逐步完成启动、版本、权限、逐题验收与排障，另列真实 embedding API 和生成回答的扩展目标及未验证边界。</description>
      <category>学习路线</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>适合已有 Java、Spring Boot、SQL 和 HTTP 接口基础，准备增加 AI 应用能力的同学。主项目是<a href="/projects/knowledge-service.html" target="_blank">Java 知识库服务</a>，源码位于 <code>examples/knowledge-service</code>，也可<a href="/downloads/knowledge-service.zip">下载源码包</a>。沿同一条“登录 → 文档 → 检索 → 引用”链路动手，逐层检查实现与结果。</p>
<p>前六步是本地检索与后端验收主线，可按每周 8—10 小时、约六周安排。真实模型与生成另留时间，按环境和验证结果推进。还不能独立开发接口时，先补<a href="/roadmaps/stages.html#before-start" target="_blank">基础准备</a>。</p>
<h2>先看清你准备运行哪一层</h2>
<p>| 层次 | 当前项目提供什么 | 这条路线怎样验收 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 面试路线：把知识点练成能解释的答案</title>
      <link>https://qiuzhiwang.vip/roadmaps/java-interview.html</link>
      <guid>https://qiuzhiwang.vip/roadmaps/java-interview.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 面试路线：把知识点练成能解释的答案</source>
      <description>面向已有 Java 基础的开发者，用集合、线程池、事务、数据库和排障实验补齐面试证据，每步给出任务、产物、检查点与卡住后的办法。</description>
      <category>学习路线</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>适合学过 Java、写过简单后端接口，准备求职或跳槽的同学。先选一个自己能运行的项目，围绕同一条请求链复习。每步可以安排一周，每周约 8—10 小时；已经掌握的内容用现有代码和记录核对后跳过，没有完成的步骤可以延长。</p>
<p>如果还不能独立写接口或执行 SQL，先看<a href="/roadmaps/stages.html#before-start" target="_blank">成长阶段说明</a>。这条路线整理已有基础，不承担从零学习全部后端知识的任务。</p>
<h2>第一步：找一个答不清的点，把集合题写成代码</h2>
<ul>
<li><strong>阅读：</strong> 先在<a href="/practice/" target="_blank">自测工作台</a>独立答一组 Java 题，再读<a href="/javabgw/hashmapdcyl.html" target="_blank">HashMap 底层原理</a>；参考<a href="/samples/interview-review.html" target="_blank">面试复盘样例</a>记录自己漏掉的条件。</li>
<li><strong>任务：</strong> 用半小时画出一条接口的调用链，标出集合、事务、SQL 和外部调用。选一个真实缺口做小程序：例如用自定义 Key 验证两个相等对象能否取出同一个值，再修改已入表 Key 中参与哈希计算的字段，观察查找行为。先写预测再运行。</li>
<li><strong>产物：</strong> 一段独立可运行的代码、JDK 版本、原回答、预期与实际输出，以及修订后的两分钟解释。</li>
<li><strong>检查点：</strong> 能说明 <code>equals</code>、<code>hashCode</code> 与键是否变化分别影响什么；能沿自己使用的 JDK 源码定位依据。讨论树化时能说明容量和操作路径，不只背常量。</li>
<li><strong>卡住怎么办：</strong> 暂停业务项目，只保留一个类、一个 Map 和两个 Key。打印哈希值与相等比较结果；看不懂源码先补<a href="/javabgw/javajc.html" target="_blank">Java 基础</a>和<a href="/javabgw/jhkj.html" target="_blank">集合框架</a>，一次解决一个条件。</li>
</ul>
<h2>第二步：跟着一次线程池饱和往下问</h2>
<ul>
<li><strong>阅读：</strong> <a href="/interview/thread-pool.html" target="_blank">线程池面试专题</a>、<a href="/guides/thread-pool-overload.html" target="_blank">线程池过载指南</a>，不熟悉线程协作时补<a href="/javabgw/dxcybfbc.html" target="_blank">多线程与并发编程</a>。</li>
<li><strong>任务：</strong> 按指南的门闩示例，配置核心线程 2、最大线程 4、有界队列 2 和 <code>AbortPolicy</code>。在门闩未释放时连续提交 7 个任务，先推导哪些任务创建线程、哪些排队、何时拒绝，再运行；结束时释放门闩并关闭线程池。</li>
<li><strong>产物：</strong> 参数表、提交时序、自己的运行输出和拒绝分支处理说明。文章给出的输出只供对照，不替代本次执行。</li>
<li><strong>检查点：</strong> 能解释“核心数满后先入队，队列接纳失败后再尝试扩容”；区分规则推导、实际观察与任务开始顺序。能回答入口收到拒绝时如何返回，为什么不能仍报成功。</li>
<li><strong>卡住怎么办：</strong> 先原样运行最小示例，检查门闩是否提前释放、队列是否真有容量上限、是否使用同一拒绝策略。没有打印任务开始时间，就不推断全部执行顺序；不要用增加 <code>sleep</code> 掩盖时序问题。</li>
</ul>
<h2>第三步：确认事务到底在哪一层生效</h2>
<ul>
<li><strong>阅读：</strong> <a href="/interview/transaction.html" target="_blank">事务面试专题</a>、<a href="/guides/spring-transaction-boundary.html" target="_blank">Spring 事务提交边界</a>，结合自己项目的 Spring 版本与事务配置阅读。</li>
<li><strong>任务：</strong> 在练习数据库中准备一个有两次写入的 Service 方法，通过另一个 Bean 调用它。在第二次写入前抛出未捕获的运行时异常，从新请求或独立连接核对两次写入的结果；再在练习副本比较同类内部调用未经过代理的路径。每次恢复相同初始数据。</li>
<li><strong>产物：</strong> Controller、Service、代理与数据库的调用图，事务配置，异常抛出位置，以及正常、异常两条路径的数据记录。</li>
<li><strong>检查点：</strong> 能解释入口是否经过事务代理、异常是否传播、是否加入外层事务；知道方法体执行完与物理事务提交不同。涉及缓存或通知时，能指出数据库回滚无法撤销哪些外部动作。</li>
<li><strong>卡住怎么办：</strong> 先缩到同一数据源、两个 Bean 和一个本地事务。检查异常是否被捕获、方法是否由 Spring 管理、测试外层是否另有事务；不要同时引入异步线程或多数据源。指南中的设计片段需要自己实现和验证。</li>
</ul>
<h2>第四步：把 SQL 和缓存放回同一条读写链</h2>
<ul>
<li><strong>阅读：</strong> <a href="/javabgw/MySQL.html" target="_blank">MySQL</a>、<a href="/guides/cache-invalidation.html" target="_blank">缓存失效与旧值回填</a>。</li>
<li><strong>任务：</strong> 在本地 MySQL 固定一份数据、一个列表查询和排序条件，保存原 SQL 的普通 <code>EXPLAIN</code>，再比较一个候选索引的计划。接着画出“读未命中并取得旧值 → 写提交并删除缓存 → 读回填旧值”的交错时序；项目已有 Redis 时再实际回放，否则将该部分标为时序推导。</li>
<li><strong>产物：</strong> 数据库版本、表结构、SQL、索引、数据分布与前后计划；一张缓存竞态图，注明已执行与未执行的部分。</li>
<li><strong>检查点：</strong> 能说明索引服务了哪些过滤或排序条件，计划变化能证明什么；能区分删除失败、结果未知与删除成功后的旧值回填。没有重复测量就不写接口提速比例。</li>
<li><strong>卡住怎么办：</strong> 去掉联表，只保留一个查询和一个索引；先确认实际使用的是 MySQL，不能把 H2 的结果写成 MySQL 实验。缓存先推演两个请求的先后关系，再决定是否需要实现补偿任务。</li>
</ul>
<h2>第五步：用日志和线程状态定位一次慢请求</h2>
<ul>
<li><strong>阅读：</strong> <a href="/javabgw/JVM.html" target="_blank">JVM</a>、<a href="/javabgw/cjt.html" target="_blank">场景题</a>和<a href="/guides/thread-pool-overload.html" target="_blank">线程池指南中的排障顺序</a>。</li>
<li><strong>任务：</strong> 在自己的本地练习接口中制造可控等待，例如等待门闩的下游桩；为同一请求关联入口、排队、执行与数据库耗时。核对当前 JDK 支持的诊断命令后采集线程状态，解除等待，再用同一输入复跑。</li>
<li><strong>产物：</strong> 一份“现象 → 证据 → 假设 → 验证”的记录，附请求关联标识、时间线和相关线程栈，保留未证实的假设。</li>
<li><strong>检查点：</strong> 能用等待位置解释本次慢请求，并说明解除等待后发生什么；不凭一次慢响应断言 CPU、GC 或数据库一定有问题。本地人为故障只说明这一条分支。</li>
<li><strong>卡住怎么办：</strong> 先固定一个请求、一个等待点，确认日志能关联。拿不到诊断权限时保留应用时间线，注明缺少线程证据；指标不足就继续采集，不急于调线程数或堆大小。</li>
</ul>
<h2>第六步：用自己的材料做两轮模拟问答</h2>
<ul>
<li><strong>阅读：</strong> <a href="/interview/" target="_blank">面试专题入口</a>、<a href="/samples/interview-review.html" target="_blank">面试复盘样例</a>、<a href="/career/project-story.html" target="_blank">项目故事与证据</a>。</li>
<li><strong>任务：</strong> 选前面两项亲自完成的实验或项目改动，录一分钟介绍与三分钟链路讲解。第一轮回答“为什么、怎么做、怎么验证”；第二轮继续问“失败怎么办、换一个条件还成立吗、本人改了什么”。独自练习也可以先写问题，隔一天再回答。</li>
<li><strong>产物：</strong> 两轮录音或逐字提纲、五个追问、修订清单，以及下一次补做的一个实验。</li>
<li><strong>检查点：</strong> 每个结论能定位到代码或记录，能主动说明 JDK、框架、数据与运行条件。答不清的地方能说出待验证项，而不是借用别人的项目结果。</li>
<li><strong>卡住怎么办：</strong> 先对照一段代码讲清输入和输出，再补原理。把“并发不熟”改成“还不能解释队列满后的拒绝分支”这样的小问题，回到对应步骤补一份证据。</li>
</ul>
<h2>做到什么程度可以先告一段落？</h2>
<p>至少保留一个集合实验、一个线程池实验、一份事务数据记录、一份 SQL 或缓存分析、一份慢请求排查，以及两轮问答复盘。记录里清楚区分实际运行、规则推导和待验证项，就能看出下一轮该补哪里；这些材料不代表全部面试题已经掌握。</p>
<p>把个人改动带入<a href="/roadmaps/project-to-resume.html" target="_blank">项目到简历路线</a>，准备 AI 应用实践时进入<a href="/roadmaps/java-ai.html" target="_blank">Java 转 AI 路线</a>。资料可从<a href="/resources/java-interview.html" target="_blank">Java 面试资料</a>查找，起点判断见<a href="/roadmaps/stages.html" target="_blank">成长阶段说明</a>。</p>
]]></content:encoded>
    </item>
    <item>
      <title>项目到简历路线：把做过的事写成有依据的贡献</title>
      <link>https://qiuzhiwang.vip/roadmaps/project-to-resume.html</link>
      <guid>https://qiuzhiwang.vip/roadmaps/project-to-resume.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">项目到简历路线：把做过的事写成有依据的贡献</source>
      <description>面向已有 Java 项目的开发者，逐步盘点职责、补齐证据、改写条目并练习追问，每步说明产物、检查点和卡住后的处理办法。</description>
      <category>学习路线</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>适合有 Java 基础、手里已有工作项目或个人练习，准备整理简历的同学。先选一个自己能解释的项目，把做过的动作与证据对应起来，再写条目。可以每步安排一周，每周约 6—8 小时；材料齐全就直接核对，缺少实现就回项目补做。</p>
<p>还没有可运行项目时，先完成<a href="/roadmaps/java-ai.html" target="_blank">Java 转 AI 路线</a>的本地知识服务主线，或把自己的一个后端接口跑通。<a href="/roadmaps/stages.html" target="_blank">成长阶段说明</a>可以帮助判断从哪里开始。</p>
<h2>第一步：先把“我的部分”圈出来</h2>
<ul>
<li><strong>阅读：</strong> <a href="/career/" target="_blank">项目表达入口</a>、<a href="/guides/project-resume.html" target="_blank">项目简历指南</a>，再看自己的代码、需求与提交记录。阅读材料帮助整理问题，职责以实际参与为准。</li>
<li><strong>任务：</strong> 写清项目性质、使用者、需要解决的问题和运行范围；逐模块标注独立完成、协作参与、运行验证或仅阅读。选一至两项本人实际做过的动作，记录谁负责上下游、自己交付了什么。</li>
<li><strong>产物：</strong> 职责与材料对应表：<code>模块 / 我的动作 / 合作边界 / 代码或文档 / 已有验证 / 缺少的材料</code>。项目性质按工作项目、个人项目、教学复现或设计方案如实填写。</li>
<li><strong>检查点：</strong> 每个“负责”“实现”“优化”都能指出具体对象；团队已有能力与自己的改动分开。只运行过公开源码时，先写复现、阅读或验收，不写成从零开发。</li>
<li><strong>卡住怎么办：</strong> 不从架构图找所有权，先找一个自己能完整解释的提交、一条接口或一次排查。工作代码不能公开时，整理允许展示的脱敏流程、接口约定和验证方法；仍无法说明的模块先不放入条目。</li>
</ul>
<h2>第二步：为一项动作补齐正常与失败证据</h2>
<ul>
<li><strong>阅读：</strong> <a href="/resources/project-resume.html" target="_blank">项目简历资料与模板</a>。知识服务可结合<a href="/projects/knowledge-service.html" target="_blank">项目接口说明</a>和<a href="/handbook/rag-acceptance.html" target="_blank">验收手册</a>；普通后端按自己接口契约选用场景。</li>
<li><strong>任务：</strong> 为第一步的一项动作固定代码版本、数据和环境，先执行一条正常请求，再选两个适用的边界：非法输入、无权限、旧版本写入、重复请求或外部依赖失败。先写预期，执行后保存实际结果；发现问题先留原记录，修复后复跑同一输入。</li>
<li><strong>产物：</strong> 一份证据索引，每项包含运行命令或操作、测试身份、数据来源、预期、实际响应、判定、本人改动和已知限制。无法执行的场景写原因与下一步。</li>
<li><strong>检查点：</strong> 别人能根据记录在相同条件下重复操作，证据能支持准备写的结论。没有重复请求防护就不能宣称幂等；没有外部依赖就不填写“模型超时已通过”。截图可以辅助，原请求与响应仍需保留。</li>
<li><strong>卡住怎么办：</strong> 把项目缩到一个接口和一份合成数据。测试无法复现时先记录条件差异；无法补证据时降低表述范围，把“已解决一致性问题”改成实际验证的具体分支，或暂时移除该结论。</li>
</ul>
<h2>第三步：改写条目，每一句都回到事实</h2>
<ul>
<li><strong>阅读：</strong> <a href="/career/project-story.html" target="_blank">项目故事与证据</a>、<a href="/guides/project-resume.html" target="_blank">项目简历指南</a>及<a href="/samples/project-resume.html" target="_blank">项目表达填写样例</a>。样例中的动作和成绩不是个人经历。</li>
<li><strong>任务：</strong> 从“参与开发，使用 Spring Boot、Redis、AI”这样的笼统句子出发，按下面的结构写两至三条描述。每句话标出对应证据，检查主语、实现层次、数字与适用条件。</li>
<li><strong>产物：</strong> 修改前后两版描述，以及“句子 → 证据位置”的索引。方括号只填自己的事实，缺少事实就删去相应部分。</li>
<li><strong>检查点：</strong> 看得出具体问题、个人动作和验证办法，技术名称用于解释实现。性能或质量比例能说明环境、样本、基线、测量方法与分母；只有一次功能回放时，就只写本次功能验证。</li>
<li><strong>卡住怎么办：</strong> 先用口语写“我改了哪一段，原来会怎样，现在怎么核对”，再压缩句子。没有量化指标可以描述边界和验证方法，不必凑提升百分比；没有个人改动就保留为学习与复现记录。</li>
</ul>
<p>下面是待填写的结构，没有预设个人成果：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>项目性质：[工作项目 / 个人实践 / 教学复现 / 设计方案]</span></span>
<span class="line"><span>为解决[具体问题]，我[实际完成或参与的动作]，</span></span>
<span class="line"><span>采用[关键做法与选择原因]；</span></span>
<span class="line"><span>在[环境、数据与输入]下，通过[命令、回放或核对方法]</span></span>
<span class="line"><span>得到[实际结果]，当前未覆盖[已知限制]。</span></span>
<span class="line"><span>证据：[提交、设计记录或运行结果位置]</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>知识服务条目尤其要分清这几层</h3>
<p>| 自己实际完成什么 | 条目应说明什么 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java 成长路线：面试、转 AI、项目到简历</title>
      <link>https://qiuzhiwang.vip/roadmaps/</link>
      <guid>https://qiuzhiwang.vip/roadmaps/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java 成长路线：面试、转 AI、项目到简历</source>
      <description>面向已有 Java 基础的开发者，从一个具体目标出发，按阅读、任务、产物、检查点和排障建议完成面试、知识库实践或项目表达。</description>
      <category>学习路线</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>已经学过 Java，接下来该补面试、动手做 AI 应用，还是把项目讲清楚？先选一个当前最需要解决的目标，围绕同一个项目留下代码、运行记录和自己的解释。这里以已有 Java 基础为起点，每一步都给出可以动手的任务。</p>
<p>也可以直接阅读完整路线：<a href="/roadmaps/java-interview.html" target="_blank">Java 面试</a> · <a href="/roadmaps/java-ai.html" target="_blank">Java 转 AI</a> · <a href="/roadmaps/project-to-resume.html" target="_blank">项目到简历</a>。先拿不准从哪里开始，就看<a href="/roadmaps/stages.html" target="_blank">成长阶段说明</a>，按手头能拿出的材料判断。</p>
<h2>选一条主线，先交付一件东西</h2>
<p>| 当前情况 | 选择路线 | 建议排期与最终产物 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>成长阶段说明：从会写接口到能验证、能讲清楚</title>
      <link>https://qiuzhiwang.vip/roadmaps/stages.html</link>
      <guid>https://qiuzhiwang.vip/roadmaps/stages.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">成长阶段说明：从会写接口到能验证、能讲清楚</source>
      <description>按可展示的材料判断 Java 学习起点，说明基础准备、跑通项目、个人改动、失败验收和项目表达的任务与完成条件。</description>
      <category>学习路线</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>“学过”很难判断进度，“能交付什么”更容易核对。下面的阶段描述一件事从理解到完成的过程，不按工作年限划分。你可能已经能写接口，却还没查过一次事务失败；也可能项目做过不少，只缺一份清楚的表达。找到当前缺的材料，补这一段即可。</p>
<p>| 现在能拿出什么 | 从哪里继续 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java训练营、Java+AI与私教报名咨询</title>
      <link>https://qiuzhiwang.vip/signup/</link>
      <guid>https://qiuzhiwang.vip/signup/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java训练营、Java+AI与私教报名咨询</source>
      <description>对照 Java训练营、Java+AI训练营与私教带徒的适用对象及阶段交付示例，先聊基础、目标和一个问题，再由双方确认服务范围与费用。</description>
      <category>报名咨询</category>
      <pubDate>Fri, 06 Nov 2026 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>先对照基础和目标选择方向。现有服务按个人评估安排学习、项目和求职任务，具体内容以双方确认的服务清单与协议为准。</p>
<p>| 现有产品 | 适用对象 | 交付形式 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>更新日志</title>
      <link>https://qiuzhiwang.vip/changelog.html</link>
      <guid>https://qiuzhiwang.vip/changelog.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">更新日志</source>
      <description>2026-11-06：Java 进阶之路与授权文章整合 增加一级“进阶之路”入口，总目录按十二章组织 Java 基础、集合、并发、JVM、框架、数据库、分布式与 AI 应用。 授权公开文章补入各章，保留原作者与来源说明；推广入口统一接本站资源、公众号与服务。 总目录采用主题原有的文档阅读布局，左侧章节导航、正文阅读和页内目录配合使用。 2026-11-...</description>
      <content:encoded><![CDATA[<h2>2026-11-06：Java 进阶之路与授权文章整合</h2>
<ul>
<li>增加一级“进阶之路”入口，总目录按十二章组织 Java 基础、集合、并发、JVM、框架、数据库、分布式与 AI 应用。</li>
<li>授权公开文章补入各章，保留原作者与来源说明；推广入口统一接本站资源、公众号与服务。</li>
<li>总目录采用主题原有的文档阅读布局，左侧章节导航、正文阅读和页内目录配合使用。</li>
</ul>
<h2>2026-11-06：技术阅读入口调整</h2>
<ul>
<li>首页以六类技术知识、推荐文章和项目解析为主；首屏直接进入知识目录与技术专题。</li>
<li>重排导航和侧栏，Java、Spring、数据库、并发、AI 工程内容优先；学习路线、自测和练习移至辅助入口。</li>
<li>Java、AI、专题与项目总览改为技术目录，按原理、实现与工程问题组织内容。保留原有文章、源码、资料和学习工具。</li>
</ul>
<h2>2026-11-06：成长路线与实战系列（先前更新）</h2>
<ul>
<li>学习栏目按“准备 Java 面试、从 Java 走向 AI 应用、把项目写进简历”提供路线入口。</li>
<li>成长路线支持在当前浏览器自记学习标记、继续未标记步骤，并提供三份可下载练习单。标记与自测错题分别保存。</li>
<li>新增“Java 面试追问实战”“项目经历修改室”，知识库项目增加首次启动的跟做单元。</li>
<li>资料、帮助和交流入口围绕具体问题衔接；第一次交流可以先说明基础、目标和一个卡点。</li>
</ul>
<h2>先前更新</h2>
<ul>
<li>完成站点品牌替换：Logo、favicon、页脚版权、GitHub 链接已更新为 IT枫斗者。</li>
<li>FAQ 已改造成 Java Offer 训练营常见问题。</li>
<li>评论系统已切换到 Giscus，并接入 GitHub 登录评论。</li>
<li>搜索框移动端样式已统一优化。</li>
<li>项目已重构为单站点结构，支持直接在根目录运行和打包。</li>
</ul>
<h2>后续计划</h2>
<ul>
<li>持续补充 Java 八股、AI 应用开发、面试实战等内容。</li>
<li>根据真实搜索查询与读者问题，持续修订内容、资料和路线。</li>
<li>持续整理真实面试案例与训练营问答。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>IT枫斗者的 Java 进阶之路</title>
      <link>https://qiuzhiwang.vip/home.html</link>
      <guid>https://qiuzhiwang.vip/home.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">IT枫斗者的 Java 进阶之路</source>
      <description>一份按阅读顺序组织的 Java 后端进阶知识库。从 Java 基础、集合源码、并发、JVM，到 Spring、MySQL、Redis、分布式与 AI 应用，汇集技术文章、项目源码和学习资料。</description>
      <content:encoded><![CDATA[<p>这里是 <strong>IT枫斗者的 Java 进阶之路</strong>。把 Java 开发中经常用到的知识按主题整理在一起：既能从基础开始连续阅读，也能在写代码、排查故障或准备面试时回来查阅。</p>
<p>内容从语言基础延伸到后端框架、数据库、中间件、系统设计与 AI 应用。每个模块先列基础阅读，再接源码分析和工程专题。文章中的代码、适用版本和参考资料，帮助把概念放回具体实现中理解。</p>
<p><strong>在线阅读免费开放。</strong> 配套的源码、手册和主题资料可以直接下载；技术更新和交流统一通过本站的<a href="/join/" target="_blank">IT枫斗者公众号与微信</a>。</p>
<h2>阅读顺序与知识地图</h2>
<p>| 阅读阶段 | 主要内容 | 入口 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>技术文章来源与转载说明</title>
      <link>https://qiuzhiwang.vip/editorial/sources.html</link>
      <guid>https://qiuzhiwang.vip/editorial/sources.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">技术文章来源与转载说明</source>
      <description>IT枫斗者 Java 进阶之路的公开文章来源、原作者、固定快照、Apache-2.0 许可证与本站修改记录。</description>
      <content:encoded><![CDATA[<p>本站品牌、Logo、公众号与微信属于 <strong>IT枫斗者</strong>。技术文章与引用分别标注来源，下面列出实际使用的公开内容。</p>
<h2>JavaGuide 公开文章</h2>
<p>上游仓库：<a href="https://github.com/Snailclimb/JavaGuide" target="_blank" rel="noopener noreferrer">Snailclimb/JavaGuide</a>。固定快照：<code>992dae31eade2a34ba8dbbbcf5ed9de5511325ec</code>。完整<a href="/licenses/javaguide-apache-2.0.txt">Apache-2.0 许可证</a>随站点及阅读包提供。</p>
<p>| 本站文章 | 原文 | 原作者 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>模块</title>
      <link>https://qiuzhiwang.vip/faq/mk.html</link>
      <guid>https://qiuzhiwang.vip/faq/mk.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">模块</source>
      <description>模块协作 FAQ：主责模块讲深、跨模块讲清，形成可被面试验证的项目表达。</description>
      <category>训练营FAQ</category>
      <content:encoded><![CDATA[<h2>先说结论</h2>
<p>面试准备优先级是：主责模块必须讲深，非主责模块做到“边界清晰 + 上下游明白”。</p>
<h2>面试官通常看什么</h2>
<ul>
<li>你是否真正承担过核心职责。</li>
<li>你能否说明模块上下游依赖和协作方式。</li>
<li>出问题时你是如何定位、沟通、推动落地的。</li>
</ul>
<h2>非主责模块准备到什么程度</h2>
<ul>
<li>能说明这个模块解决什么问题。</li>
<li>能说清和你主责模块的调用关系。</li>
<li>能描述典型故障时的协作流程。</li>
<li>不要求你背出对方模块的全部实现细节。</li>
</ul>
<h2>三步补齐跨模块认知</h2>
<ol>
<li>画一张系统调用链，标出你负责位置。</li>
<li>对每个上下游写一句“输入-处理-输出”。</li>
<li>准备 2 个跨模块协作案例，重点讲推进过程。</li>
</ol>
<h2>推荐表达模板</h2>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>我主责的是 X 模块，核心职责是 A/B/C。</span></span>
<span class="line"><span>和我最相关的上游是 U，下游是 D。</span></span>
<span class="line"><span>当时出现过一次 P 问题，我先在 X 模块定位，再和 U/D 对齐边界，</span></span>
<span class="line"><span>最终通过 R 方案解决，结果是 T 指标提升/故障恢复。</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>避免两种说法</h2>
<ul>
<li>“这个不是我负责，我不清楚。”</li>
<li>“所有模块我都很熟。”</li>
</ul>
<p>第一种显得边界外全部失能，第二种容易被追问击穿。最稳妥是“主责深、协作清”。</p>
]]></content:encoded>
    </item>
    <item>
      <title>配合</title>
      <link>https://qiuzhiwang.vip/faq/ph.html</link>
      <guid>https://qiuzhiwang.vip/faq/ph.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">配合</source>
      <description>配合 FAQ：按流程推进训练与投递，减少临时救火，提升面试通过率。</description>
      <category>训练营FAQ</category>
      <content:encoded><![CDATA[<h2>先说结论</h2>
<p>同样的投入，按流程配合的人通常结果更稳定。不要把准备工作压到面试前集中爆发。</p>
<h2>推荐推进流程</h2>
<ol>
<li>简历定稿：岗位方向和项目描述先对齐。</li>
<li>项目梳理：形成 1 分钟和 3 分钟两个版本。</li>
<li>题库训练：高频问题先覆盖，再做追问训练。</li>
<li>模拟面试：暴露表达盲点和知识断层。</li>
<li>正式投递：按节奏推进，不盲投。</li>
<li>面后复盘：把反馈沉淀为下一轮提升动作。</li>
</ol>
<h2>资料边界与协作规范</h2>
<ul>
<li>专属资料仅用于个人学习和面试准备，不外传。</li>
<li>提问请附上背景、现象和已尝试动作，便于快速定位。</li>
<li>重要问题优先结构化表达，减少来回沟通成本。</li>
</ul>
<h2>常见偏离与修正</h2>
<ul>
<li>偏离：简历刚改完就急投，项目还没讲顺。<br>
修正：至少完成一次模拟再投递。</li>
<li>偏离：面试前夜集中提问，焦虑堆积。<br>
修正：按周复盘，提前清理高频卡点。</li>
<li>偏离：只要答案，不做二次复述。<br>
修正：每次反馈后必须口头复述一遍。</li>
</ul>
<h2>配合的核心价值</h2>
<p>流程不是束缚，而是帮你在高压期保持稳定输出。</p>
]]></content:encoded>
    </item>
    <item>
      <title>训练营 FAQ</title>
      <link>https://qiuzhiwang.vip/faq/</link>
      <guid>https://qiuzhiwang.vip/faq/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">训练营 FAQ</source>
      <description>训练营常见问题总览：源码边界、模块协作、提问方式、准备心态、配合流程与项目选择建议。</description>
      <category>训练营FAQ</category>
      <content:encoded><![CDATA[<h2>写在前面</h2>
<div class="hint-container tip">
<p class="hint-container-title">先看这页，再进入训练和投递</p>
<p>这组 FAQ 不讲空话，核心目标只有一个：帮你减少无效焦虑、降低准备偏差，用更稳的节奏拿到面试结果。</p>
</div>
<h2>你能在这里解决什么问题</h2>
<ul>
<li>源码能不能发、哪些内容可以公开交流。</li>
<li>非主责模块要准备到什么程度才够面试。</li>
<li>怎样提问效率最高、能更快拿到有效反馈。</li>
<li>焦虑期如何稳住节奏，不被细节拖垮。</li>
<li>如何与辅导流程配合，避免“临时抱佛脚”。</li>
<li>3 年及以上经验，项目该怎么选、怎么讲。</li>
</ul>
<h2>目录导航（建议阅读顺序）</h2>
<ol>
<li><a href="/faq/ym.html" target="_blank">源码</a>：保密边界与可交流范围。</li>
<li><a href="/faq/mk.html" target="_blank">模块</a>：主责深挖与跨模块认知。</li>
<li><a href="/faq/tw.html" target="_blank">提问</a>：高效提问模板与时间节奏。</li>
<li><a href="/faq/xt.html" target="_blank">心态</a>：面试准备期的心理与执行策略。</li>
<li><a href="/faq/ph.html" target="_blank">配合</a>：标准协作流程与注意事项。</li>
<li><a href="/faq/xm.html" target="_blank">项目</a>：项目选择与表达建议。</li>
</ol>
<h2>30 分钟速读路线</h2>
<ul>
<li>第 1 步（10 分钟）：读 <code>源码 + 模块</code>，先建立边界感。</li>
<li>第 2 步（10 分钟）：读 <code>提问 + 配合</code>，把协作效率提上来。</li>
<li>第 3 步（10 分钟）：读 <code>心态 + 项目</code>，形成可执行计划。</li>
</ul>
<h2>使用建议</h2>
<ul>
<li>每次遇到卡点，先回到对应 FAQ 章节找“标准动作”。</li>
<li>把“我觉得”改成“我验证过”，提问时附上已尝试内容。</li>
<li>每周至少复盘一次，把高频问题沉淀为你的个人话术库。</li>
</ul>
<h2>下一步</h2>
<ul>
<li><a href="/javaai-projects/" target="_blank">查看 Java+AI 项目介绍</a></li>
<li><a href="/offerxly/" target="_blank">进入 Offer 训练营</a></li>
<li><a href="/faq/" target="_blank">查看常见问题</a></li>
<li><a href="/offerxly/rhbm.html" target="_blank">了解如何报名</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>提问</title>
      <link>https://qiuzhiwang.vip/faq/tw.html</link>
      <guid>https://qiuzhiwang.vip/faq/tw.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">提问</source>
      <description>提问 FAQ：如何把问题一次说清、提升反馈速度，并形成高质量复盘。</description>
      <category>训练营FAQ</category>
      <content:encoded><![CDATA[<h2>高效提问的标准</h2>
<p>好问题不是“我不会”，而是“我做了什么、卡在哪里、希望得到什么帮助”。</p>
<h2>提问前自查清单</h2>
<ul>
<li>问题是否可复现，现象是否能描述清楚。</li>
<li>你已经做过哪些排查，结果是什么。</li>
<li>你期望得到哪类帮助：思路、定位、还是表达优化。</li>
</ul>
<h2>推荐提问模板</h2>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>【背景】我在准备/实现的是：</span></span>
<span class="line"><span>【现象】当前问题是：</span></span>
<span class="line"><span>【已尝试】我已经做了 1/2/3，结果分别是：</span></span>
<span class="line"><span>【卡点】我不确定的是：</span></span>
<span class="line"><span>【期望】希望得到：定位建议/讲解思路/面试表达优化</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>时间节奏建议</h2>
<ul>
<li>日常问题：集中整理后统一提问，效率最高。</li>
<li>紧急问题：先给最小复现信息，再补充上下文。</li>
<li>面试前夜：优先做复盘，不建议临时堆新问题。</li>
</ul>
<h2>不推荐的提问方式</h2>
<ul>
<li>零散抛结论，不给上下文。</li>
<li>只说“报错了”，不贴关键日志或重现路径。</li>
<li>临时突击式提问，把准备压力全部前置到最后一晚。</li>
</ul>
<p>提问质量越高，反馈越快，你的迭代速度也会越快。</p>
]]></content:encoded>
    </item>
    <item>
      <title>项目</title>
      <link>https://qiuzhiwang.vip/faq/xm.html</link>
      <guid>https://qiuzhiwang.vip/faq/xm.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">项目</source>
      <description>项目 FAQ：不同经验阶段如何选择主讲项目，并给出可落地的表达与准备策略。</description>
      <category>训练营FAQ</category>
      <content:encoded><![CDATA[<h2>3 年及以上经验的项目选择结论</h2>
<p>优先讲你真实工作中的主责项目，训练营项目更适合作为补充案例，而不是第一主讲项目。</p>
<h2>为什么这样更稳</h2>
<ul>
<li>中高级面试更看重真实业务深度和长期责任范围。</li>
<li>你的主责项目更容易经受连续追问。</li>
<li>训练营项目用于补齐技术亮点，更合理也更自然。</li>
</ul>
<h2>三种可选策略</h2>
<p>| 策略 | 适用人群 | 风险 | 建议 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>心态</title>
      <link>https://qiuzhiwang.vip/faq/xt.html</link>
      <guid>https://qiuzhiwang.vip/faq/xt.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">心态</source>
      <description>心态 FAQ：缓解求职焦虑、聚焦可执行动作，建立稳定面试节奏。</description>
      <category>训练营FAQ</category>
      <content:encoded><![CDATA[<h2>先说结论</h2>
<p>焦虑不是问题，失控的焦虑才是问题。准备期最重要的是把注意力放回“可执行动作”。</p>
<h2>面试准备的三层重点</h2>
<p>| 层级 | 你要做到什么 | 典型产出 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>源码</title>
      <link>https://qiuzhiwang.vip/faq/ym.html</link>
      <guid>https://qiuzhiwang.vip/faq/ym.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">源码</source>
      <description>训练营项目源码相关 FAQ：明确保密边界、可交流内容与面试推荐说法。</description>
      <category>训练营FAQ</category>
      <content:encoded><![CDATA[<h2>先说结论</h2>
<p>公司真实项目源码通常受保密协议约束，不能对外传播。这个边界必须明确、必须遵守。</p>
<h2>为什么不能外发源码</h2>
<ul>
<li>源码属于企业核心资产，涉及商业机密与知识产权。</li>
<li>合同和保密协议通常有明确约束，违规存在法律风险。</li>
<li>职业信誉比一次“展示细节”更重要，面试官也看重这点。</li>
</ul>
<h2>可以交流什么，不可以交流什么</h2>
<p>| 类型 | 是否建议 | 说明 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>20题逐题追问版</title>
      <link>https://qiuzhiwang.vip/aiapp/20tztzwb.html</link>
      <guid>https://qiuzhiwang.vip/aiapp/20tztzwb.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">20题逐题追问版</source>
      <description>AI应用开发岗20道高频问题与追问脚本，覆盖主答、追问拆解、标准应答与一句话加分总结。</description>
      <category>AI应用开发</category>
      <content:encoded><![CDATA[
<div class="hint-container tip">
<p class="hint-container-title">这篇怎么用效果最好</p>
<p>不要“一口气读完”，按下面节奏练：</p>
<ol>
<li>每次选 3~5 题，先读主答。</li>
<li>再把追问连续说一遍，训练抗压表达。</li>
<li>最后用“一句话加分总结”做收尾。</li>
</ol>
<p>建议搭配 <a href="/aiapp/mnmswd.html" target="_blank">模拟面试问答</a> 做实战演练。</p>
</div>
<h2>推荐训练法</h2>
<ul>
<li>初级：每天 5 题，先保证主答流畅。</li>
<li>进阶：每天 3 题，主答 + 追问全链路回答。</li>
<li>冲刺：随机抽题，2 分钟内完整作答。</li>
</ul>
<p>结构是：</p>
<ul>
<li><strong>主问题标准回答</strong></li>
<li><strong>面试官可能追问</strong></li>
<li><strong>你可以怎么答</strong></li>
<li><strong>一句话加分总结</strong></li>
</ul>
<p>这样你不只是能背“主答案”，还能接住面试官往下挖。</p>
]]></content:encoded>
    </item>
    <item>
      <title>模拟面试问答</title>
      <link>https://qiuzhiwang.vip/aiapp/mnmswd.html</link>
      <guid>https://qiuzhiwang.vip/aiapp/mnmswd.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">模拟面试问答</source>
      <description>AI应用开发岗模拟面试脚本，按真实面试轮次组织高频问题与高分回答，适合录音演练与复盘。</description>
      <category>AI应用开发</category>
      <content:encoded><![CDATA[
<div class="hint-container tip">
<p class="hint-container-title">建议这样练，完读率和记忆率更高</p>
<ol>
<li>找同学或自己扮演面试官，按轮次提问。</li>
<li>每轮只练 3~4 题，避免一口气刷完。</li>
<li>录音后复盘三件事：是否跑题、是否冗长、是否有结论。</li>
</ol>
<p>搭配 <a href="/aiapp/msbzbsg.html" target="_blank">面试标准背诵稿</a> 使用，效果更好。</p>
</div>
<h2>使用目标</h2>
<ul>
<li>训练真实面试节奏，而不是只背单题答案。</li>
<li>训练连续表达能力（主答 + 追问 + 收尾）。</li>
<li>训练“听题后 5 秒组织答案”的反应能力。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>面试标准背诵稿</title>
      <link>https://qiuzhiwang.vip/aiapp/msbzbsg.html</link>
      <guid>https://qiuzhiwang.vip/aiapp/msbzbsg.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">面试标准背诵稿</source>
      <description>AI应用开发岗高频背诵稿，聚焦短时冲刺场景，覆盖岗位认知、RAG、Prompt、稳定性与项目表达。</description>
      <category>AI应用开发</category>
      <content:encoded><![CDATA[<p><strong>《AI 应用开发岗面试标准背诵稿（精简高频版）》</strong></p>
<div class="hint-container tip">
<p class="hint-container-title">冲刺使用方式</p>
<ul>
<li>面试前 60 分钟：通读 1 遍，标记不熟问题。</li>
<li>面试前 30 分钟：只背第 1、4、9、16、20、21、22 题。</li>
<li>面试前 10 分钟：只看“项目介绍万能模板 + 最后一句高分总结”。</li>
</ul>
</div>
<h2>这份稿子的定位</h2>
<p>这是一份“高压场景速记稿”，目标是让你在短时间内形成稳定输出。<br>
如果你要系统准备，建议配合 <a href="/aiapp/msdasc.html" target="_blank">面试答案手册</a> 与 <a href="/aiapp/20tztzwb.html" target="_blank">20题逐题追问版</a>。</p>
]]></content:encoded>
    </item>
    <item>
      <title>面试答案手册</title>
      <link>https://qiuzhiwang.vip/aiapp/msdasc.html</link>
      <guid>https://qiuzhiwang.vip/aiapp/msdasc.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">面试答案手册</source>
      <description>AI应用开发岗面试答案大全，覆盖岗位认知、RAG、Prompt、Agent、稳定性、成本、项目表达与高频追问。</description>
      <category>AI应用开发</category>
      <content:encoded><![CDATA[
<div class="hint-container tip">
<p class="hint-container-title">先看这个，再开始背</p>
<p>如果你时间有限，先优先看三章：<br>
「一、岗位认知类」-&gt;「四、RAG 全链路」-&gt;「十、项目表达能力高频题」。</p>
<p>这篇更适合当“系统词典”，不建议从头硬背。<br>
建议搭配 <a href="/aiapp/20tztzwb.html" target="_blank">20题逐题追问版</a> 和 <a href="/aiapp/msbzbsg.html" target="_blank">面试标准背诵稿</a> 一起使用。</p>
</div>
<h2>使用建议</h2>
<ol>
<li>第一轮：按模块快速通读，建立整体地图。</li>
<li>第二轮：挑高频题改写成你自己的版本。</li>
<li>第三轮：和模拟问答结合，训练连续表达。</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>没有真实做过怎么描述</title>
      <link>https://qiuzhiwang.vip/aiapp/myzszgzmms.html</link>
      <guid>https://qiuzhiwang.vip/aiapp/myzszgzmms.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">没有真实做过怎么描述</source>
      <description>没有完整AI项目经历时的真实面试表达指南，避免夸大与穿帮，提供可落地的角色定位与话术模板。</description>
      <category>AI应用开发</category>
      <content:encoded><![CDATA[<div class="hint-container warning">
<p class="hint-container-title">先明确底线</p>
<p>这篇的核心不是“教你包装成大佬”，而是教你 <strong>真实、稳妥、有边界</strong> 地表达。<br>
目标是让你在追问下依然站得住，而不是靠夸大撑场面。</p>
</div>
<p><strong>快速使用方式</strong></p>
<ol>
<li>先看“一、最重要原则”和“二、话术模板”。</li>
<li>再看“六、最容易露馅细节”，把高风险表达删掉。</li>
<li>最后背“十、防露馅完整版”，改写成你自己的版本。</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>企业知识库项目问答</title>
      <link>https://qiuzhiwang.vip/aiapp/qyzskxmwd.html</link>
      <guid>https://qiuzhiwang.vip/aiapp/qyzskxmwd.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">企业知识库项目问答</source>
      <description>企业知识库问答项目面试话术，包含1分钟/3分钟介绍、项目亮点、高频追问和收尾模板。</description>
      <category>AI应用开发</category>
      <content:encoded><![CDATA[<p><strong>《企业知识库问答项目：完整项目话术》</strong></p>
<div class="hint-container tip">
<p class="hint-container-title">这篇是“项目表达主战场”</p>
<p>面试里最拉开差距的，不是概念题，而是“你到底做过什么”。<br>
建议你按这条路线训练：</p>
<ol>
<li>先背 1 分钟版（保底不慌）。</li>
<li>再背 3 分钟版（讲清业务与技术）。</li>
<li>最后练追问（证明你不是背稿）。</li>
</ol>
</div>
<h2>快速导航</h2>
<ol>
<li><strong>1分钟项目介绍版</strong>：高压场景保底输出。</li>
<li><strong>3分钟详细介绍版</strong>：结构化完整表达。</li>
<li><strong>项目亮点总结</strong>：回答“你这个项目有什么价值”。</li>
<li><strong>10个高频追问 + 标准回答</strong>：抗追问能力训练。</li>
<li><strong>最后收尾金句</strong>：形成面试记忆点。</li>
</ol>
<p>你可以直接套成自己的项目经历。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java AI 应用开发：RAG、知识库与工程设计</title>
      <link>https://qiuzhiwang.vip/aiapp/</link>
      <guid>https://qiuzhiwang.vip/aiapp/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java AI 应用开发：RAG、知识库与工程设计</source>
      <description>面向 Java 后端的 AI 应用技术目录，涵盖 Spring AI、知识库架构、RAG 文档更新、权限过滤、工具调用、评估、超时与故障排查，并提供服务源码。</description>
      <category>AI应用开发</category>
      <content:encoded><![CDATA[<p>AI 应用进入业务系统之后，除了模型调用，还要处理数据更新、权限、延迟和失败恢复。这里从 Java 后端视角整理知识库架构、检索链路与服务治理，连接技术专题和可阅读的项目源码。</p>
<h2>技术目录</h2>
<p>| 主题 | 阅读入口 | 重点内容 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>知识库项目架构</title>
      <link>https://qiuzhiwang.vip/aiapp/zskxmjg.html</link>
      <guid>https://qiuzhiwang.vip/aiapp/zskxmjg.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">知识库项目架构</source>
      <description>企业知识库问答系统架构讲解稿，覆盖五层架构、离线入库链路、在线问答链路、权限治理与监控方案。</description>
      <category>AI应用开发</category>
      <content:encoded><![CDATA[<p>《企业知识库问答项目：技术架构图讲解稿》</p>
<div class="hint-container tip">
<p class="hint-container-title">面试实战用法</p>
<p>这一篇专门解决“请你讲讲系统架构”这类问题。<br>
建议按“30 秒总起手 -&gt; 2 分钟全链路 -&gt; 1 分钟追问补充”三段来练。</p>
</div>
<p>这个版本专门用于面试场景，回答以下高频问题：</p>
<ul>
<li>“你画一下这个系统架构”</li>
<li>“你讲讲整体技术方案”</li>
<li>“用户请求进来后链路怎么走”</li>
<li>“离线入库和在线问答分别怎么设计”</li>
</ul>
<h2>演讲顺序建议</h2>
<ol>
<li>先讲分层（五层模型）。</li>
<li>再讲两条链路（离线入库 + 在线问答）。</li>
<li>最后讲工程化能力（权限、安全、监控、降级）。</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>路线图与服务</title>
      <link>https://qiuzhiwang.vip/javaai-offer/learning-and-service.html</link>
      <guid>https://qiuzhiwang.vip/javaai-offer/learning-and-service.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">路线图与服务</source>
      <description>Java + AI陪跑营路线图、陪跑方式与求职服务说明。</description>
      <category>Offer陪跑营</category>
      <content:encoded><![CDATA[<h2>学习方式</h2>
<p>开班先沟通学习经历、已有项目、目标岗位和技术短板，再制定个人计划。每个路线节点按“明确重点 → 学习与整理 → 打卡反馈 → 路线检查 → 补讲与复测”推进。</p>
<p>陪跑不只检查是否记住答案，还会追问原因、使用场景、异常处理和验证方法。以工具调用为例，需要能说清模型如何提出调用请求、Java 后端如何校验参数和权限、业务接口如何执行、结果怎样保存，以及超时后如何避免重复执行。</p>
<h2>报名前置与技术环境</h2>
<p>陪跑营默认面向已经接触过 Java 语法、面向对象、集合、异常和基本 Web 开发的同学。以下内容会在开班前确认，不要求所有人一开始都熟练：</p>
<ul>
<li>Java 与 Spring 基础、数据库和常见后端接口开发。</li>
<li>目标岗位需要的 Java 版本、Spring Boot、Spring AI 或 LangChain4j 版本范围。</li>
<li>MySQL、Redis、消息队列、Elasticsearch、向量数据库和模型服务的使用方式。</li>
<li>本地开发、第三方 API、云资源和数据安全要求。</li>
</ul>
<p>具体版本、模型提供商、演示环境和可能产生的 API 或云资源费用，以当期项目和报名服务清单为准。</p>
<h2>六段路线图</h2>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/java-ai-offer-roadmap-5a4b90fd-a51777d9c700-2658w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：Java + AI 面试陪跑营路线思维导图"><img alt="Java + AI 面试陪跑营路线思维导图" tabindex="0" loading="lazy" src="/images/optimized/java-ai-offer-roadmap-5a4b90fd-a51777d9c700-800w.webp" srcset="/images/optimized/java-ai-offer-roadmap-5a4b90fd-a51777d9c700-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="2658" height="5871" decoding="async" data-image-source="/java-ai-offer-roadmap.png" data-image-width="2658" data-image-height="5871" data-image-detail="/images/optimized/java-ai-offer-roadmap-5a4b90fd-a51777d9c700-2658w.webp"></a><figcaption>Java + AI 面试陪跑营路线思维导图</figcaption></figure>
<p>| 路线节点 | 学习内容 | 陪跑重点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/java-ai-offer-roadmap.png" type="image/png"/>
    </item>
    <item>
      <title>多个实战项目（两个主讲项目）</title>
      <link>https://qiuzhiwang.vip/javaai-offer/projects.html</link>
      <guid>https://qiuzhiwang.vip/javaai-offer/projects.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">多个实战项目（两个主讲项目）</source>
      <description>Java + AI陪跑营的多个面试项目讲解、两个主讲项目的技术主线和高频追问准备。</description>
      <category>Offer陪跑营</category>
      <content:encoded><![CDATA[<h2>项目讲解主线</h2>
<p>陪跑营中的项目准备以“会讲、能答、讲得真实”为目标。项目材料用于理解业务和技术方案，面试表达必须结合自己的学习、实践和参与情况，不把未参与的开发经历包装成个人经历。</p>
<h2>项目匹配方向</h2>
<p>项目会结合目标岗位和个人经历选择，不要求所有人学习同一个业务。项目库中可参考以下方向：</p>
<p>| 方向 | 典型业务 | 重点陪跑能力 |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>Java+AI训练营</title>
      <link>https://qiuzhiwang.vip/javaai-offer/</link>
      <guid>https://qiuzhiwang.vip/javaai-offer/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java+AI训练营</source>
      <description>Java+AI训练营：面向有 Java 基础、准备求职或转向 Java+AI 的同学，系统陪跑技术表达、项目面试和求职流程。</description>
      <category>Java+AI训练营</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">先判断是否适合</p>
<p>这套陪跑面向已经接触过 Java 后端、准备求职或转向 Java + AI 的同学。报名时会结合基础、项目、目标岗位和投入时间确定起点。</p>
</div>
<h2>你可能存在以下问题</h2>
<ul>
<li><strong>Java 学过不少，但知识点零散</strong>：面试时难以把后端原理、AI 链路和项目表达串起来。</li>
<li><strong>有 Java 后端项目，但讲不完整</strong>：说不清架构、个人职责、技术难点和异常处理。</li>
<li><strong>接触过 AI，但讲不深</strong>：需要补充模型调用、RAG、Agent、工具执行、权限和失败恢复。</li>
<li><strong>简历写了很多技术名词</strong>：项目一被追问，就不知道怎样说明自己的实际工作和掌握范围。</li>
<li><strong>已经开始投递</strong>：需要同步准备项目表达、简历、多轮模拟面试和真实面试复盘。</li>
</ul>
<blockquote>
<p>报名时会结合你的 Java 基础、工作年限、已有项目、目标岗位、目标城市和每周可投入时间确定陪跑起点，不要求所有人从同一套内容重新学起。</p>
</blockquote>
<h2>报名后你会获得什么</h2>
<ul>
<li><strong>个人基础与目标岗位评估</strong>；</li>
<li><strong>个人学习重点与阶段任务</strong>；</li>
<li><strong>Java+AI岗位能力补强</strong>；</li>
<li><strong>企业级项目库匹配与真实项目整理</strong>；</li>
<li><strong>多个项目的业务、架构和面试表达陪跑</strong>；</li>
<li><strong>两个主讲项目的深度讲解与追问准备</strong>；</li>
<li><strong>一对一定制代写简历</strong>；</li>
<li><strong>个人职业经历与岗位化自我介绍体系</strong>；</li>
<li><strong>多轮模拟面试，直至符合面试标准</strong>；</li>
<li><strong>真实面试问题复盘与针对性补强</strong>；</li>
<li><strong>根据岗位与地区情况，优先推荐或内推给本地合作企业</strong>。</li>
</ul>
<h2>四步陪跑流程：从技术评估到面试上场</h2>
<div class="hint-container info">
<p class="hint-container-title">陪跑方式</p>
<p>训练营不会要求所有人按照同一套课表从头学到尾。已经掌握的内容先检查，需要补强的内容重点陪跑，面试暂时用不到的内容不作为统一必修任务。</p>
</div>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/java-ai-offer-roadmap-5a4b90fd-a51777d9c700-2658w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：Java + AI 面试训练营路线思维导图"><img alt="Java + AI 面试训练营路线思维导图" tabindex="0" loading="lazy" src="/images/optimized/java-ai-offer-roadmap-5a4b90fd-a51777d9c700-800w.webp" srcset="/images/optimized/java-ai-offer-roadmap-5a4b90fd-a51777d9c700-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="2658" height="5871" decoding="async" data-image-source="/java-ai-offer-roadmap.png" data-image-width="2658" data-image-height="5871" data-image-detail="/images/optimized/java-ai-offer-roadmap-5a4b90fd-a51777d9c700-2658w.webp"></a><figcaption>Java + AI 面试训练营路线思维导图</figcaption></figure>
<h3>1. 技术评估</h3>
<p>先了解你的 Java、Spring、数据库和 AI 基础，梳理已有项目和真实经历，再确认目标岗位、目标城市、近期面试安排和每周可投入时间。</p>
<p>这一阶段会明确：</p>
<ul>
<li>哪些内容需要优先补强；</li>
<li>哪些内容可以快速检查或跳过；</li>
<li>当前距离目标岗位的面试要求还有哪些差距；</li>
<li>接下来应该先准备技术、项目还是面试。</li>
</ul>
<h3>2. 岗位能力补强</h3>
<p>围绕目标岗位陪跑真正需要讲清楚的内容：</p>
<ul>
<li>Java 后端与 Spring；</li>
<li>模型调用与 Spring AI；</li>
<li>RAG 与企业知识库；</li>
<li>Agent、Tool Calling 与 MCP；</li>
<li>多模态应用；</li>
<li>权限、异常、稳定性、性能和部署。</li>
</ul>
<p>陪跑过程中会安排资料学习、问题答疑、练习反馈、路线检查、薄弱项补讲和补讲后的再次检查。</p>
<p>这一阶段的目标是：能先说清技术结论，再解释原理和使用场景；能把技术放进具体项目请求链路；能说明 Java 后端、模型、知识库和工具如何配合；能回答权限、超时、重复请求、模型失败和服务不可用等问题。</p>
<h3>3. 项目表达成型</h3>
<p>结合目标岗位、工作年限和个人经历匹配多个企业级项目，其中两个作为主讲项目。</p>
<p>重点整理：</p>
<ul>
<li>项目背景和业务目标；</li>
<li>系统架构和核心流程；</li>
<li>Java 后端职责和 AI 接入点；</li>
<li>数据、权限、异常和稳定性处理；</li>
<li>项目经验的分层表达：项目概述、完整项目展开与技术细节说明；</li>
<li>项目技术难点、场景化问题复盘、高频追问和简历项目描述。</li>
</ul>
<p>这一阶段的目标是：能讲清项目解决了什么业务问题，一次请求经过哪些服务，模型和 Java 后端分别负责什么，以及出现异常后如何处理。</p>
<h3>4. 求职冲刺</h3>
<p>进入简历、自我介绍、模拟面试、投递和真实面试复盘阶段：</p>
<ul>
<li>一对一定制代写简历；</li>
<li>个人自我介绍、项目经历与岗位匹配表达整理；</li>
<li>多轮模拟面试，直至符合面试标准；</li>
<li>招聘平台资料、岗位筛选和 HR 沟通指导；</li>
<li>真实面试逐场复盘；</li>
<li>根据反馈继续补强技术和项目表达；</li>
<li>根据岗位与地区情况，优先推荐或内推给本地合作企业；</li>
<li>收到 Offer 后，协助比较岗位职责、薪资结构、地点和个人发展方向。</li>
</ul>
<h2>项目经验深度梳理与面试表达体系</h2>
<p>我们会结合目标岗位、工作年限和个人经历，匹配多个企业级 Java+AI 项目，并确定两个重点展开的主讲项目。项目陪跑不是背诵项目名称或固定话术，而是围绕真实业务场景建立完整、可追问、能落地的项目表达体系：</p>
<ul>
<li><strong>业务背景与项目价值</strong>：项目服务什么业务，解决了什么核心问题，最终产生了哪些业务或技术价值；</li>
<li><strong>系统架构与核心链路</strong>：系统由哪些模块组成，一次请求如何流转，模块之间如何协作；</li>
<li><strong>个人职责与负责模块</strong>：明确个人负责的功能边界、核心代码、技术贡献和实际参与深度；</li>
<li><strong>技术方案与实现细节</strong>：说明 Java 后端、AI 能力、数据存储、权限控制和外部服务如何配合；</li>
<li><strong>技术难点与方案权衡</strong>：结合项目场景梳理性能、稳定性、并发、数据一致性、异常处理等关键问题，以及方案选择的原因；</li>
<li><strong>问题排查与结果复盘</strong>：还原开发或上线过程中遇到的问题、定位过程、解决方案和最终效果；</li>
<li><strong>面试表达与深度追问</strong>：根据面试官的提问深度，准备项目概述、完整展开、技术细节和场景化追问等不同层次的回答。</li>
</ul>
<p>每个项目都会沿着“业务背景 → 项目目标 → 整体架构 → 核心流程 → 负责模块 → 技术实现 → 难点与方案权衡 → 问题排查 → 项目成果 → 面试追问”建立主线，并同步整理自我介绍衔接、简历描述和面试中的多层次表达。项目名称和业务方向会根据目标岗位匹配，具体以陪跑时确认的项目材料为准。</p>
<p>我们提供企业级项目库匹配与真实项目整理服务。项目选择会结合目标岗位、工作年限和个人经历；项目库用于学习和面试表达，简历中的项目会根据真实的企业级项目背景、项目架构、个人职责和参与范围进行优化升级，确保项目内容前后一致、表达专业，并能够经得起面试官的连续追问。</p>
<h2>求职服务</h2>
<ul>
<li><strong>一对一定制代写简历</strong>，并练习自我介绍；</li>
<li><strong>多轮模拟面试</strong>，覆盖技术问题、项目深挖和综合表达，直至符合面试标准；</li>
<li>招聘平台资料、岗位筛选、HR 沟通和薪资表达指导；</li>
<li>根据岗位与地区情况，优先推荐或内推给本地合作企业；</li>
<li>对真实面试逐场复盘，安排下一轮针对性准备；</li>
<li>收到 Offer 后，结合岗位职责、薪资结构、地点和个人目标做比较。</li>
</ul>
<h2>附赠内容</h2>
<ul>
<li><strong>附赠：提供常见 AI 八股文面试题及分析。</strong></li>
<li><strong>附赠：根据学员需求客制化适配，增加 AI 板块或个人工作内容。</strong></li>
</ul>
<p>具体提供形式和适配范围，以报名时确认的服务清单为准。</p>
<h2>经过陪跑，你能做到</h2>
<ul>
<li>能先说清结论，再解释原理、实现流程和适用场景；</li>
<li>能把技术放进项目请求链路，说明数据、权限、异常和验证方式；</li>
<li>能讲清项目解决的业务问题、系统架构和个人职责；</li>
<li>能回答模型调用、RAG、工具执行、Agent、权限、超时和失败恢复等追问；</li>
<li>能根据面试场景，清晰完成两个主讲项目的概述、完整展开和技术深挖；</li>
<li>能让简历中的项目内容与真实学习、实践和个人掌握范围保持一致；</li>
<li>能根据模拟面试反馈继续补强，直到符合面试标准。</li>
</ul>
<h2>学习周期与投入方式</h2>
<p>陪跑不是固定课表，而是根据个人基础、学习投入和求职进度推进。常见投入节奏如下：</p>
<p>| 每日投入 | 每周投入 | 参考周期 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/java-ai-offer-roadmap.png" type="image/png"/>
    </item>
    <item>
      <title>Java + AI 私教带徒｜从零基础到项目实战</title>
      <link>https://qiuzhiwang.vip/javaai-private/</link>
      <guid>https://qiuzhiwang.vip/javaai-private/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java + AI 私教带徒｜从零基础到项目实战</source>
      <description>面向零基础、转行和基础薄弱学员，老师从基础开始带着学 Java、做后端和 AI 项目，并提供完整的简历优化、模拟面试、投递指导和面试陪跑。</description>
      <category>Offer训练营</category>
      <content:encoded><![CDATA[<p>从零基础开始，老师带你学 Java、数据库、后端开发和 AI 应用，一步步完成项目。学习中有人讲解、答疑和检查作业，找工作时也有完整的简历优化和面试陪跑：帮你修改简历、练习项目讲解、进行模拟面试，并根据每次面试反馈继续查漏补缺。</p>
<p><strong>计算机与编程入门 → Java 基础 → 后端开发 → 企业级项目 → Java + AI 应用 → 简历优化与面试陪跑</strong></p>
<div class="hint-container tip">
<p class="hint-container-title">适合从零开始，也适合补齐基础</p>
<p>不要求报名时已经会写 Java。零基础可以按顺序开始；有基础的同学先做评估，从需要补强的阶段开始。学习起点、任务节奏和项目深度会根据基础、目标和投入时间调整。</p>
</div>
<h2>适合哪些人</h2>
<ul>
<li>没有编程基础，希望从零开始学习软件开发。</li>
<li>非计算机专业，准备转向 Java 后端或 Java + AI 应用开发。</li>
<li>学过 Java，但知识零散，还不能独立完成一个功能。</li>
<li>看过课程、跟过项目，但遇到报错或需求变化时容易卡住。</li>
<li>不清楚数据库、接口、Spring 后端和前端之间如何配合。</li>
<li>缺少学习计划、作业反馈和持续跟进，希望有老师带着推进。</li>
<li>已有开发基础，想系统补齐后端工程能力、项目实践或 AI 应用能力。</li>
</ul>
<p>如果你已经具备比较完整的 Java 后端经验，主要目标是面试冲刺，可以了解 <a href="/javaai-offer/" target="_blank">Java + AI 训练营</a>。私教带徒更适合希望从基础开始、逐步学习和实践的同学。</p>
<h2>这套私教如何学习</h2>
<p>学习不是从一开始就讲复杂框架、Agent 或企业级项目，而是按照知识之间的先后关系逐步推进。</p>
<p>每个阶段都会经过：</p>
<p><strong>讲解知识 → 老师示范 → 一起拆解 → 独立练习 → 作业检查 → 错误修改 → 阶段复测</strong></p>
<p>老师会根据练习和阶段检查的情况判断下一步怎么学。基础还不稳，就先补前置知识；某些内容已经掌握，就先验证后进入下一阶段。目标是让你逐步理解代码、独立完成任务，并能说明自己的实现思路。</p>
<h2>完整学习路线</h2>
<p>先通过这张路线图了解整体安排：从入学评估、Java 基础和后端开发，到 Java + AI 学习、项目实战，再到阶段检查与求职准备。</p>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/java-ai-private-roadmap-93e092a6-ee8921dd07bd-2574w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：私教带徒学习路线：入学评估、Java 基础、后端开发、Java + AI、项目实战、学习辅导与求职准备"><img alt="私教带徒学习路线：入学评估、Java 基础、后端开发、Java + AI、项目实战、学习辅导与求职准备" tabindex="0" loading="lazy" src="/images/optimized/java-ai-private-roadmap-93e092a6-ee8921dd07bd-800w.webp" srcset="/images/optimized/java-ai-private-roadmap-93e092a6-ee8921dd07bd-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="2574" height="5334" decoding="async" data-image-source="/java-ai-private-roadmap.png" data-image-width="2574" data-image-height="5334" data-image-detail="/images/optimized/java-ai-private-roadmap-93e092a6-ee8921dd07bd-2574w.webp"></a><figcaption>私教带徒学习路线：入学评估、Java 基础、后端开发、Java + AI、项目实战、学习辅导与求职准备</figcaption></figure>
<blockquote>
<p>私教带徒学习路线总览，具体学习起点与推进节奏根据基础评估和阶段检查安排。</p>
</blockquote>
<h3>第一阶段：计算机与开发环境入门</h3>
<ul>
<li>文件、目录、压缩包、软件安装和环境变量；</li>
<li>命令行、进程、内存和端口等基础概念；</li>
<li>客户端、服务端、浏览器和网络请求；</li>
<li>IP、域名、DNS、TCP、HTTP 和 HTTPS 入门；</li>
<li>JDK、IDEA、Git 和 Maven 的安装与使用；</li>
<li>Linux 常用命令和日志查看入门。</li>
</ul>
<p><strong>阶段目标：</strong>能够配置开发环境、运行简单程序，认识常见开发工具和基本操作，并对环境问题有初步排查思路。</p>
<h3>第二阶段：编程思维与 Java 入门</h3>
<ul>
<li>变量、数据类型、输入输出和运算符；</li>
<li>条件判断、循环、数组和方法；</li>
<li>如何拆分需求、整理步骤和绘制简单流程；</li>
<li>如何阅读代码和理解程序执行顺序；</li>
<li>如何看懂报错信息，使用断点进行调试。</li>
</ul>
<p><strong>阶段目标：</strong>能够根据简单需求编写 Java 程序，解释代码的基本执行过程，并完成基础练习。</p>
<h3>第三阶段：Java 核心与面向对象</h3>
<ul>
<li>类、对象、封装、继承、多态和接口；</li>
<li>构造方法、抽象类以及类之间的关系；</li>
<li>String、日期时间、BigDecimal 和常用 API；</li>
<li>List、Set、Map、泛型和集合遍历；</li>
<li>异常处理、文件读写、日志和单元测试；</li>
<li>线程、同步和线程池基础；</li>
<li>常见数据结构、查找、排序和算法入门。</li>
</ul>
<p><strong>阶段目标：</strong>能够完成结构相对清晰的 Java 程序，使用集合处理数据，理解常见异常并进行调试。</p>
<h3>第四阶段：数据库与 Web 基础</h3>
<ul>
<li>MySQL 数据库、表、字段、主键和数据类型；</li>
<li>增删改查、分页、排序、聚合和多表关联；</li>
<li>索引、事务和锁的基础概念；</li>
<li>HTTP 请求、响应、状态码和 JSON；</li>
<li>Cookie、Session 和登录状态入门；</li>
<li>Java 访问数据库和处理查询结果；</li>
<li>接口参数设计、数据校验和基础联调。</li>
</ul>
<p><strong>阶段目标：</strong>能够根据简单业务设计表结构、编写常用 SQL，并理解一个请求如何通过接口读写数据。</p>
<h3>第五阶段：Spring Boot 后端开发</h3>
<ul>
<li>Spring 的 Bean、依赖注入和配置；</li>
<li>Spring Boot 项目结构、启动和常用配置；</li>
<li>Controller、Service 和数据访问层的职责；</li>
<li>MyBatis 或 MyBatis-Plus 的常见用法；</li>
<li>统一返回、参数校验和异常处理；</li>
<li>登录身份、基础权限和接口文档；</li>
<li>接口测试和前后端联调。</li>
</ul>
<p><strong>阶段目标：</strong>能够创建并运行 Spring Boot 项目，完成常见业务接口，并处理参数错误、数据不存在和权限不足等情况。</p>
<h3>第六阶段：工程化、测试与部署</h3>
<ul>
<li>Git 代码提交、分支和历史记录；</li>
<li>Maven 依赖管理、构建和打包；</li>
<li>JUnit 和接口测试；</li>
<li>日志等级、异常记录和问题定位；</li>
<li>Linux 文件、进程、端口和服务操作；</li>
<li>Docker 与 Docker Compose 基础；</li>
<li>项目启动、部署和常见运行问题排查。</li>
</ul>
<p><strong>阶段目标：</strong>能够管理代码、运行测试、打包项目，理解基本部署步骤，并通过日志和运行状态排查常见问题。</p>
<h3>第七阶段：微服务与中间件</h3>
<ul>
<li>Redis 缓存与缓存更新；</li>
<li>消息队列、确认、重试和幂等；</li>
<li>Elasticsearch 搜索和索引维护；</li>
<li>Gateway、服务注册、配置和服务调用；</li>
<li>限流、熔断、超时与降级；</li>
<li>事务、补偿和数据一致性基础；</li>
<li>服务调用失败和消息异常的排查。</li>
</ul>
<p><strong>阶段目标：</strong>能够读懂基础的服务调用链路，说明缓存、消息和搜索的作用，并理解常见失败场景的处理方式。</p>
<h3>第八阶段：大模型与 Java AI 应用入门</h3>
<ul>
<li>大模型的能力特点和使用边界；</li>
<li>Prompt、上下文、Token 和多轮会话；</li>
<li>使用 Java 调用大模型；</li>
<li>Spring AI 或 LangChain4j 的基础应用；</li>
<li>流式输出和结构化结果；</li>
<li>模型调用超时、失败、重试和降级；</li>
<li>AI 服务与现有业务接口的协作方式。</li>
</ul>
<p><strong>阶段目标：</strong>能够使用 Java 接入模型，理解一次模型请求的基本过程，并处理常见调用异常。</p>
<h3>第九阶段：RAG、工具调用与 Agent</h3>
<ul>
<li>文档清洗、切分、向量化和知识入库；</li>
<li>向量检索、混合检索、引用来源和效果检查；</li>
<li>知识权限和数据访问范围控制；</li>
<li>Tool Calling 的参数校验和业务执行；</li>
<li>Agent 与固定工作流的区别；</li>
<li>MCP 的基本概念和使用场景；</li>
<li>人工确认、幂等、重试和失败恢复；</li>
<li>提示词注入、越权访问等安全问题。</li>
</ul>
<p><strong>阶段目标：</strong>能够解释模型、知识库和 Java 后端如何配合，并完成有权限控制和异常处理的基础 AI 功能。</p>
<h2>项目实战：从 Java 后端到 Java + AI 应用</h2>
<p>老师会先带你练习 Java、SQL 和接口开发，掌握基础后，再带你做完整的 Java 后端和 AI 应用项目。从看懂需求、设计数据库，到写代码、测试和部署，每一步都有讲解和答疑，遇到问题一起解决。</p>
<p>项目完成后，老师会帮你梳理自己做了什么、用到了哪些技术、解决了什么问题，再一起优化简历、练习项目讲解。后续还包括模拟面试、投递指导和真实面试后的复盘，陪你把找工作的每一步准备好。</p>
<h3>训练项目一：企业服务交易与权益履约平台</h3>
<p>这是一个围绕企业服务/课程售卖与履约管理的 Java 后端项目，覆盖从用户进入系统、检索服务、创建订单，到支付结果处理、权益开通、退款和权益回收的完整业务链路：</p>
<p><code>用户认证 → 服务目录与搜索 → 创建订单 → 支付回调 → 权益开通 → 退款审核与权益回收</code></p>
<figure><img src="/enterprise-service-trade-architecture.svg" alt="企业服务交易与权益履约平台总体架构图，展示用户入口、业务模块和数据可靠性底座" tabindex="0" loading="lazy"><figcaption>企业服务交易与权益履约平台总体架构图，展示用户入口、业务模块和数据可靠性底座</figcaption></figure>
<blockquote>
<p>项目一总体架构：从用户入口、交易履约模块到数据与可靠性底座。</p>
</blockquote>
<p>总体图用于认识系统分层，下面的模块图进一步展开支付、消息、退款、搜索和权益这几条关键链路。桌面端可以查看完整细节，窄屏设备可横向查看。</p>
<h4>核心模块流程图</h4>
<h5>支付与订单</h5>
<p>支付通知先验签并校验金额、来源和订单状态，再按幂等规则更新支付记录与订单。重复通知不会重复执行业务；校验失败和处理异常会保留记录，便于排查与恢复。项目中的支付为训练环境模拟流程，不连接真实支付渠道。</p>
<figure><img src="/enterprise-service-trade-payment.svg" alt="支付回调与订单一致性流程图：参数校验、幂等判断、订单更新与异常处理" tabindex="0" loading="lazy"><figcaption>支付回调与订单一致性流程图：参数校验、幂等判断、订单更新与异常处理</figcaption></figure>
<h5>可靠消息</h5>
<p>业务事务先写入本地消息记录，再由投递任务发送消息；消费者记录处理状态，重复投递通过幂等校验避免重复执行，失败消息按策略重试并进入后续排查流程。</p>
<figure><img src="/enterprise-service-trade-messaging.svg" alt="可靠消息投递与消费幂等流程图：本地消息、投递重试、消费去重与失败处理" tabindex="0" loading="lazy"><figcaption>可靠消息投递与消费幂等流程图：本地消息、投递重试、消费去重与失败处理</figcaption></figure>
<h5>退款与权益</h5>
<p>退款申请经过资格检查和人工审核后推进退款状态，退款结果再触发权益冻结或回收；异常情况进入补偿和审计链路，避免退款状态与可用权益长期不一致。</p>
<figure><img src="/enterprise-service-trade-refund.svg" alt="退款审核与权益回收流程图：退款校验、审核、退款处理和权益回收" tabindex="0" loading="lazy"><figcaption>退款审核与权益回收流程图：退款校验、审核、退款处理和权益回收</figcaption></figure>
<h5>搜索一致性</h5>
<p>服务数据变更后通过可靠事件更新搜索索引，并提供失败重试与全量重建路径。查询以业务数据为准，索引异常时可以发现差异并修复。</p>
<figure><img src="/enterprise-service-trade-search.svg" alt="服务搜索与索引一致性流程图：服务变更、索引更新、失败重试与重建" tabindex="0" loading="lazy"><figcaption>服务搜索与索引一致性流程图：服务变更、索引更新、失败重试与重建</figcaption></figure>
<h5>履约补偿</h5>
<p>权益围绕待生效、可用、冻结、已使用和已回收等状态受控流转。订单支付和退款事件驱动状态变化，重复事件由幂等规则处理，状态不一致时通过补偿任务恢复。</p>
<p><img src="/enterprise-service-trade-entitlement.svg" alt="权益状态机与履约补偿流程图：权益状态流转、事件处理与异常补偿" loading="lazy"><br>
项目按业务模块逐步开发，主要内容包括：</p>
<ul>
<li><strong>项目与公共能力</strong>：Maven 多模块、统一依赖、统一返回、异常处理、分页和请求追踪；</li>
<li><strong>网关与认证</strong>：Gateway 路由、用户登录、JWT 鉴权和身份传递；</li>
<li><strong>服务目录与交易履约</strong>：服务/课程管理、订单创建、模拟支付、履约状态和权益开通；</li>
<li><strong>缓存与搜索</strong>：Redis 热点数据缓存、Elasticsearch 搜索、索引同步与重建；</li>
<li><strong>可靠消息</strong>：本地消息记录、投递确认、消费幂等、重试和死信处理；</li>
<li><strong>交易一致性</strong>：支付回调校验、重复通知、超时关单、退款状态和权益回收；</li>
<li><strong>运营与恢复</strong>：退款审核、操作审计、失败记录、人工恢复和补偿；</li>
<li><strong>测试与部署</strong>：接口测试、异常场景验证、日志排查、部署和运行说明。</li>
</ul>
<p>项目重点会结合真实业务状态讲清楚：支付回调重复到达怎么办、消息发送成功但消费失败怎么办、退款成功但权益未回收怎么办、搜索索引和数据库不一致如何发现与修复。项目中的支付为训练环境模拟流程，不连接真实支付渠道。</p>
<h3>项目二：康龄智慧养老护理平台</h3>
<p>这是面向养老机构日常运营和护理管理的综合业务平台，围绕老人档案、家属会员、房间床位、入住合同、护理计划、健康评估、设备监测、报警处理和预约服务，把 Java 业务流程、Spring AI、Dify 和物联网能力组合到一条可追踪的业务链路中。</p>
<p>核心业务链路：</p>
<p><code>老人建档 → 健康评估 → 护理分级 → 房间床位 → 入住办理 → 合同管理 → 护理执行 → 设备监测 → 报警处理 → 家属服务与智能问答</code></p>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/kangling-elderly-care-business-flow-cebc4be7-2b20e1edd24b-2720w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：康龄智慧养老护理平台核心业务流程"><img alt="康龄智慧养老护理平台核心业务流程" tabindex="0" loading="lazy" src="/images/optimized/kangling-elderly-care-business-flow-cebc4be7-2b20e1edd24b-800w.webp" srcset="/images/optimized/kangling-elderly-care-business-flow-cebc4be7-2b20e1edd24b-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="2720" height="1520" decoding="async" data-image-source="/kangling-elderly-care-business-flow.png" data-image-width="2720" data-image-height="1520" data-image-detail="/images/optimized/kangling-elderly-care-business-flow-cebc4be7-2b20e1edd24b-2720w.webp"></a><figcaption>康龄智慧养老护理平台核心业务流程</figcaption></figure>
<blockquote>
<p>项目总体架构：Vue 3 管理端和微信小程序通过 REST、SSE 访问 Spring Boot 后端；后端统一承接业务流程、权限、数据落库和审计；Spring AI、Dify 和华为云 IoTDA 分别承担知识问答、智能体工具调用和设备数据接入。</p>
</blockquote>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/kangling-elderly-care-architecture-7bbacde7-c6ef09d0b4b5-1792w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：康龄智慧养老护理平台总体架构"><img alt="康龄智慧养老护理平台总体架构" tabindex="0" loading="lazy" src="/images/optimized/kangling-elderly-care-architecture-7bbacde7-c6ef09d0b4b5-800w.webp" srcset="/images/optimized/kangling-elderly-care-architecture-7bbacde7-c6ef09d0b4b5-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1792" height="1216" decoding="async" data-image-source="/kangling-elderly-care-architecture.png" data-image-width="1792" data-image-height="1216" data-image-detail="/images/optimized/kangling-elderly-care-architecture-7bbacde7-c6ef09d0b4b5-1792w.webp"></a><figcaption>康龄智慧养老护理平台总体架构</figcaption></figure>
<h4>AI 与知识库</h4>
<p>Spring AI 通过 <code>ChatClient</code> 调用大模型，使用 Redis 保存会话记忆，通过 <code>QuestionAnswerAdvisor</code>、<code>TokenTextSplitter</code> 和 <code>RedisVectorStore</code> 完成知识库增强问答，并通过 <code>NursingProjectTool</code> 查询护理项目。知识库链路为文档上传、OSS 保存、PDF 解析、文本切分、向量化和检索生成。</p>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/kangling-spring-ai-architecture-e4705d48-1ae90ab740e3-2720w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：康龄智慧养老平台 Spring AI 架构"><img alt="康龄智慧养老平台 Spring AI 架构" tabindex="0" loading="lazy" src="/images/optimized/kangling-spring-ai-architecture-e4705d48-1ae90ab740e3-800w.webp" srcset="/images/optimized/kangling-spring-ai-architecture-e4705d48-1ae90ab740e3-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="2720" height="1520" decoding="async" data-image-source="/kangling-spring-ai-architecture.png" data-image-width="2720" data-image-height="1520" data-image-detail="/images/optimized/kangling-spring-ai-architecture-e4705d48-1ae90ab740e3-2720w.webp"></a><figcaption>康龄智慧养老平台 Spring AI 架构</figcaption></figure>
<p>Dify 采用后端代理模式，前端不直接持有 API Key。平台提供流式聊天、会话列表、历史消息、会话删除和预约查询工具，并使用当前登录用户 ID 隔离会话。预约工具通过 <code>getReservationByDay</code> 查询指定日期的记录，同时返回查询日期、参数化 SQL、查询参数和业务结果，便于排查和审计。</p>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/kangling-dify-architecture-4fa83fe0-66de3204f66a-2720w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：康龄智慧养老平台 Dify 智能体架构"><img alt="康龄智慧养老平台 Dify 智能体架构" tabindex="0" loading="lazy" src="/images/optimized/kangling-dify-architecture-4fa83fe0-66de3204f66a-800w.webp" srcset="/images/optimized/kangling-dify-architecture-4fa83fe0-66de3204f66a-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="2720" height="1520" decoding="async" data-image-source="/kangling-dify-architecture.png" data-image-width="2720" data-image-height="1520" data-image-detail="/images/optimized/kangling-dify-architecture-4fa83fe0-66de3204f66a-2720w.webp"></a><figcaption>康龄智慧养老平台 Dify 智能体架构</figcaption></figure>
<h4>物联网与报警</h4>
<p>物联网模块基于华为云 IoTDA、Qpid JMS 和 AMQP 接收智能床位及其他设备上报数据。<code>AmqpClient</code> 建立连接并监听断线重连，设备消息通过线程池异步处理，写入设备最新状态、历史数据和报警处理链路。</p>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/kangling-iot-architecture-3c535907-6ca3f820945b-2720w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：康龄智慧养老平台物联网架构"><img alt="康龄智慧养老平台物联网架构" tabindex="0" loading="lazy" src="/images/optimized/kangling-iot-architecture-3c535907-6ca3f820945b-800w.webp" srcset="/images/optimized/kangling-iot-architecture-3c535907-6ca3f820945b-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="2720" height="1520" decoding="async" data-image-source="/kangling-iot-architecture.png" data-image-width="2720" data-image-height="1520" data-image-detail="/images/optimized/kangling-iot-architecture-3c535907-6ca3f820945b-2720w.webp"></a><figcaption>康龄智慧养老平台物联网架构</figcaption></figure>
<p>项目开发内容包括：</p>
<ul>
<li>老人、家属、房间、床位、入住、合同、护理、健康、设备、报警和预约等业务建模；</li>
<li>Vue 3 Web 管理端和微信小程序端的角色化服务入口；</li>
<li>Spring Security、JWT、BCrypt、角色权限和菜单权限控制；</li>
<li>MySQL、MyBatis-Plus、Redis、阿里云 OSS 和 Quartz 等基础能力；</li>
<li>Spring AI 知识库问答、护理项目工具调用和 SSE 流式响应；</li>
<li>Dify 会话代理、预约查询工具、会话隔离和 SQL 可追溯；</li>
<li>IoTDA 设备接入、异步消息处理、设备监测和报警规则；</li>
<li>覆盖权限不足、模型超时、无召回、重复请求、设备断线和下游服务失败等场景。</li>
</ul>
<p>项目重点是由 Java 后端负责身份、权限、状态、数据落库和审计，AI 负责知识检索、业务查询和对话编排，物联网服务负责设备数据接入；涉及业务状态变化的操作仍由后端完成校验和事务处理。</p>
<h3>项目学习的完整过程</h3>
<p>每个项目都会按以下顺序推进：</p>
<p>| 项目阶段 | 学习与开发内容 | 阶段验收 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/java-ai-private-roadmap.png" type="image/png"/>
    </item>
    <item>
      <title>研发管理平台（研云通）</title>
      <link>https://qiuzhiwang.vip/javaai-projects/01-dmpt.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/01-dmpt.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">研发管理平台（研云通）</source>
      <description>Java 企业级研发管理平台项目介绍，覆盖项目、需求、任务、版本、待办提醒、数据分析与研发协同流程。</description>
      <category>Java项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个面向研发团队的项目协同与研发管理平台，围绕项目、需求、任务、版本、待办事项和数据分析，帮助团队把需求进入、任务分派、开发执行、版本发布和过程复盘串成完整流程。</p>
<h2>项目背景</h2>
<p>研发团队在实际协作中通常同时面对项目立项、需求评审、任务拆分、开发执行、版本发布和过程复盘。信息散落在即时通讯、表格和多个系统中时，负责人难以掌握整体进度，需求变更容易遗漏，延期任务也缺少及时提醒。研发管理平台（研云通）以项目和研发流程为主线，把需求、任务、版本、成员、待办和效能数据统一起来，帮助团队形成可追踪、可协同、可复盘的研发管理闭环。</p>
<h2>业务概述</h2>
<p>平台服务于产品经理、项目经理、研发人员、测试人员和管理者。用户可以创建项目和需求，经过评审后拆分为可执行任务，再分配负责人、设置优先级和截止时间。开发过程中持续更新任务状态，系统根据版本计划和依赖关系生成待办提醒；需求变更、缺陷修复和发布结果统一留痕，最终沉淀为研发效能报表。</p>
<p>主要业务场景包括：</p>
<ul>
<li>项目、需求、任务、缺陷和版本的统一管理；</li>
<li>需求评审、任务分派、状态流转和延期升级；</li>
<li>个人待办、团队看板、版本燃尽和研发效能统计；</li>
<li>成员、角色、组织、标签和项目权限的细粒度控制；</li>
<li>发布记录、操作日志、变更记录和历史数据追溯。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用前后端分离的企业级架构，前端负责项目看板、表单和统计图表，Java 后端按项目、需求、任务、版本和系统管理划分业务模块。核心服务通过统一认证、权限校验、事务控制和审计日志保证数据安全，提醒、统计和索引更新等耗时操作通过消息队列异步处理。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot：构建项目、需求、任务和版本管理接口；</li>
<li>Spring Cloud Alibaba：支持服务注册、配置管理、网关路由和服务治理；</li>
<li>Spring Security + OAuth2：实现登录认证、角色权限和项目级数据隔离；</li>
<li>MyBatis / MyBatis-Plus：完成复杂查询、批量更新和数据持久化；</li>
<li>MySQL：保存项目主数据、任务状态、版本记录和审计信息。</li>
</ul>
<h3>3. 中间件与工程能力</h3>
<ul>
<li>Redis：缓存项目看板、成员权限和热点统计，配合分布式锁避免重复操作；</li>
<li>RabbitMQ / RocketMQ：异步发送待办提醒、状态通知和统计任务；</li>
<li>Elasticsearch：支持需求、任务、缺陷和操作记录的全文检索；</li>
<li>XXL-JOB：定时生成日报、周报、逾期提醒和效能统计；</li>
<li>Vue + Element UI：构建项目看板、任务列表和管理后台。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 项目与需求管理</h3>
<p>维护项目基本信息、成员、里程碑和需求池，支持需求创建、评审、拆分、优先级调整、关联版本和变更记录。</p>
<h3>2. 任务管理</h3>
<p>将需求拆分为开发、测试、设计和运维任务，支持负责人分配、工时填写、依赖关系、状态流转、延期原因和批量操作。</p>
<h3>3. 版本管理</h3>
<p>维护版本计划、发布日期、需求范围和发布状态，关联版本内任务，生成完成率、延期率和燃尽趋势。</p>
<h3>4. 待办与提醒</h3>
<p>根据任务截止时间、状态变化、审批结果和版本节点生成个人及团队待办，通过站内信、邮件或企业微信进行提醒和升级。</p>
<h3>5. 权限与组织管理</h3>
<p>按组织、项目、角色和资源控制访问范围，区分项目负责人、产品、研发、测试和只读成员的操作权限。</p>
<h3>6. 数据分析与报表</h3>
<p>统计需求吞吐量、任务完成率、平均处理时长、延期分布、版本交付情况和成员工作负载，为管理层提供研发效能依据。</p>
<h3>7. 日志审计与系统维护</h3>
<p>记录登录、数据变更、权限调整和导出行为，提供参数配置、字典管理、健康检查、备份和故障排查能力。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>使用状态机和版本号控制需求、任务、发布状态，避免并发更新覆盖；</li>
<li>通过幂等键、消息重试和失败补偿保证提醒与统计任务不重复、不丢失；</li>
<li>将业务事实、统计口径和权限范围分开设计，确保报表结果可解释；</li>
<li>通过统一项目数据模型打通产品、研发、测试和管理者视角，减少重复维护；</li>
<li>AI 可用于需求摘要、任务拆分建议和周报生成，正式状态变更仍由 Java 事务服务控制。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>你会从需求池、任务拆解、版本发布和研发效能分析入手，理解一个研发团队为什么需要一套真正可协同、可追踪的项目管理平台。陪跑过程中，不只是认识模块，还会把项目、需求、任务、成员、版本和待办之间的关系讲清楚，建立一套可以落到代码和面试表达里的业务模型。</p>
<ul>
<li>学会把复杂研发流程拆成领域对象、状态机、权限和提醒规则。</li>
<li>掌握项目协同中常见的事务、异步通知、数据统计和操作审计设计。</li>
<li>形成一套可用于简历、面试和项目演示的“业务背景—技术方案—难点处理”表达。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一张能讲清项目全貌的架构图和主流程图。</li>
<li>一套围绕需求、任务、版本和待办的模块拆解思路。</li>
<li>一份可直接改写到简历和面试自我介绍中的项目亮点清单。</li>
</ul>
<h2>核心业务流程</h2>
<figure><img src="/project-diagrams/dmpt/00-main.svg" alt="研发管理平台（研云通）主流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）主流程图</figcaption></figure>
<p>平台围绕“创建项目与需求 → 拆分任务并分配负责人 → 开发过程更新任务状态 → 需求变更与版本管理 → 自动提醒与异常升级 → 验收发布并归档 → 生成研发效能报表”建立研发协同闭环。</p>
<h2>模块流程图</h2>
<h3>任务管理</h3>
<figure><img src="/project-diagrams/dmpt/01-module.svg" alt="研发管理平台（研云通）任务管理流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）任务管理流程图</figcaption></figure>
<h3>公共模块</h3>
<figure><img src="/project-diagrams/dmpt/02-module.svg" alt="研发管理平台（研云通）公共模块流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）公共模块流程图</figcaption></figure>
<h3>基础模块</h3>
<figure><img src="/project-diagrams/dmpt/03-module.svg" alt="研发管理平台（研云通）基础模块流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）基础模块流程图</figcaption></figure>
<h3>待办事项提醒</h3>
<figure><img src="/project-diagrams/dmpt/04-module.svg" alt="研发管理平台（研云通）待办事项提醒流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）待办事项提醒流程图</figcaption></figure>
<h3>数据分析与报表</h3>
<figure><img src="/project-diagrams/dmpt/05-module.svg" alt="研发管理平台（研云通）数据分析与报表流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）数据分析与报表流程图</figcaption></figure>
<h3>版本管理</h3>
<figure><img src="/project-diagrams/dmpt/06-module.svg" alt="研发管理平台（研云通）版本管理流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）版本管理流程图</figcaption></figure>
<h3>状态自动流转</h3>
<figure><img src="/project-diagrams/dmpt/07-module.svg" alt="研发管理平台（研云通）状态自动流转流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）状态自动流转流程图</figcaption></figure>
<h3>系统维护与支持</h3>
<figure><img src="/project-diagrams/dmpt/08-module.svg" alt="研发管理平台（研云通）系统维护与支持流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）系统维护与支持流程图</figcaption></figure>
<h3>需求管理</h3>
<figure><img src="/project-diagrams/dmpt/09-module.svg" alt="研发管理平台（研云通）需求管理流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）需求管理流程图</figcaption></figure>
<h3>项目管理</h3>
<figure><img src="/project-diagrams/dmpt/10-module.svg" alt="研发管理平台（研云通）项目管理流程图" tabindex="0" loading="lazy"><figcaption>研发管理平台（研云通）项目管理流程图</figcaption></figure>
<h2>业务模块</h2>
<p>| 模块 | 主要能力 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/dmpt/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>云呼智服工单中台</title>
      <link>https://qiuzhiwang.vip/javaai-projects/02-yunhu.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/02-yunhu.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">云呼智服工单中台</source>
      <description>Java+AI 客服工单中台项目介绍，覆盖智能客服、坐席辅助、FAQ 知识库、工单流转、RAG 检索和面试项目表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个面向售后与客服场景的工单中台，解决重复问题多、流转链路长、人工答复不统一、知识更新慢的问题。项目把用户咨询、工单分派、知识检索、坐席答复和结案统计整合起来。</p>
<h2>项目背景</h2>
<p>客服中心通常同时接入电话、在线聊天、公众号、小程序和邮件等渠道，客户问题、订单信息、知识文档和历史工单分散在不同系统中。坐席需要反复查询资料，复杂问题还要转交技术、仓储或售后部门，容易出现重复回答、分派错误、超时和处理结果无法追踪的问题。云呼智服工单中台把多渠道诉求、知识库、坐席工作台和工单流程统一起来，形成可量化的服务闭环。</p>
<h2>业务概述</h2>
<p>平台面向客户、客服坐席、班组长、技术支持和售后管理人员。客户可以自助查询常见问题和工单进度，坐席可以查看客户画像、历史订单和推荐知识，复杂问题通过工单自动或手动分派给责任部门。系统持续记录首响时间、处理时长、转派次数、解决率和满意度，为服务质量改进提供依据。</p>
<p>主要业务场景包括：</p>
<ul>
<li>多渠道接入客户咨询并统一生成会话；</li>
<li>FAQ、产品手册和历史案例检索，辅助坐席快速答复；</li>
<li>工单创建、分派、转派、挂起、升级、结案和评价；</li>
<li>超时提醒、SLA 监控、回访任务和服务质量分析；</li>
<li>优质答复审核后回流知识库，持续优化服务内容。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用微服务和前后端分离架构，渠道接入、会话管理、工单中心、知识检索、通知和报表按业务域拆分。统一网关负责鉴权与路由，工单状态和 SLA 由后端服务控制，AI 作为坐席辅助层提供意图识别、知识检索和答复草稿。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建工单、客户、知识和统计服务；</li>
<li>Spring Security + OAuth2：实现坐席、主管、技术和客户的角色权限控制；</li>
<li>MySQL：保存客户、会话、工单、审批和评价数据；</li>
<li>MyBatis：支持工单列表、多条件筛选、批量处理和复杂报表查询。</li>
</ul>
<h3>3. 中间件与 AI 能力</h3>
<ul>
<li>Redis：缓存会话上下文、客户画像和热门 FAQ，配合分布式锁实现幂等；</li>
<li>RabbitMQ / RocketMQ：异步发送工单通知、超时升级和知识更新消息；</li>
<li>Elasticsearch + 向量库：支持 FAQ、手册和历史案例的关键词与语义检索；</li>
<li>XXL-JOB：执行 SLA 扫描、回访任务、日报统计和失败补偿；</li>
<li>Spring AI：实现 RAG 问答、意图分类、摘要生成和工具查询。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 多渠道接入与会话中心</h3>
<p>统一接入 Web、App、公众号、电话转文本等渠道，生成会话、客户身份和上下文，保证重复消息可识别、可合并。</p>
<h3>2. 坐席工作台</h3>
<p>集中展示客户画像、订单信息、历史工单、推荐知识和处理动作，AI 生成可编辑的答复草稿，坐席确认后再发送。</p>
<h3>3. 工单中心</h3>
<p>支持工单创建、分类、优先级、责任人、转派、挂起、升级、结案和评价，使用状态机确保流程状态合法。</p>
<h3>4. 自助服务门户</h3>
<p>客户可以查询 FAQ、提交工单、查看处理进度、补充材料和评价结果，降低重复咨询压力。</p>
<h3>5. 审批与协同流转</h3>
<p>通过流程节点、会签、加签和退回机制处理退款、技术升级、补偿等复杂业务，保留每一步审批记录。</p>
<h3>6. 搜索、统计与质检</h3>
<p>支持工单全文检索、服务时长统计、一次解决率、满意度、投诉趋势和坐席工作量分析。</p>
<h3>7. 权限、审计与运维</h3>
<p>控制客户和工单数据的可见范围，记录登录、查看、导出和处理行为，配合监控、任务调度和告警完成日常运维。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>使用幂等键、状态版本号、消息重试和死信队列保证工单不丢不重；</li>
<li>使用 SLA 时间窗口和升级规则自动识别超时工单，减少人工盯办；</li>
<li>RAG 返回知识来源、版本和更新时间，低置信度时转人工，避免模型直接编造答案；</li>
<li>结案后的优质答复进入审核队列，审核通过后再切分、向量化和发布；</li>
<li>通过工单数据和服务指标帮助企业定位高频问题、流程瓶颈和培训重点。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>客服工单是最容易让人感受到业务复杂度的场景之一：同一个问题可能从多个渠道进入，经过机器人、坐席、技术、仓储和售后多次流转，最后还要接受 SLA 和满意度检验。你会沿着一条真实的服务链路，把 AI 问答、知识库检索和 Java 工单流程连接起来，看到一个客服系统如何从“能回答”升级到“能闭环”。</p>
<ul>
<li>学会设计多渠道会话、工单状态机、超时升级和幂等控制。</li>
<li>掌握 RAG 知识库、坐席辅助、消息重试和服务质量分析的落地方式。</li>
<li>能清楚说明 AI 负责什么、Java 业务服务负责什么，以及人工确认如何介入。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套可演示的咨询受理、工单处理、结案评价和知识回流链路。</li>
<li>一份客服中台的技术难点与性能优化清单。</li>
<li>一套适合 Java+AI 岗位的项目介绍和面试追问准备。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>客服中心往往同时承接电话、在线聊天、公众号和小程序等多个入口。相同问题反复出现，坐席需要在多个系统之间查询客户、订单和历史工单；当问题需要转交技术、仓储或售后部门时，责任边界和处理时限也容易失控。云呼智服工单中台将多渠道咨询统一接入，把知识库、坐席工作台、工单中心和服务质量分析放到同一条业务链路中。</p>
<p>平台围绕“咨询受理、问题识别、知识检索、坐席确认、工单分派、超时提醒、结案评价、知识回流”建立服务闭环。AI 负责理解用户意图、提取问题要素、检索 FAQ 和历史案例、生成答复草稿；Java 服务负责客户身份、工单状态、责任人、SLA、事务和审计，确保辅助建议不会绕过正式工单流程。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/yunhu/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>智慧园区综合运营管理平台</title>
      <link>https://qiuzhiwang.vip/javaai-projects/03-chengzhi.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/03-chengzhi.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">智慧园区综合运营管理平台</source>
      <description>Java+AI 智慧园区综合运营管理平台项目介绍，覆盖统一接入、园区安防、物业工单、门禁车辆、IoT、运营分析与运维审计。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个面向园区运营方、物业团队、安保人员、企业员工和访客的综合运营管理平台。平台把安防监控、物业服务、门禁控制、车辆管理、智能设备接入、数据报表和运维审计统一到同一套业务底座中，让园区从“多个系统各自运行”升级为“统一入口、统一事件、统一协同、统一分析”。</p>
<p>项目的核心价值不是单独增加一个看板，而是把用户请求、设备事件、业务工单和运营数据串成可追踪的闭环。AI 能力作为辅助层，用于知识检索、事件摘要、异常归纳和运营问答；正式的权限校验、状态流转、数据写入和审计留痕仍由 Java 业务服务负责。</p>
<h2>项目背景</h2>
<p>现代园区同时包含园区运营方、物业、安保、企业员工、访客和大量智能设备。视频监控、门禁、停车、物业报修、设备告警和能耗数据如果分散在多个系统中，管理人员很难统一判断事件影响，也无法快速联动责任部门。智慧园区综合运营管理平台以“统一接入、统一事件、统一协同、统一分析”为目标，支撑园区安全、服务和运营管理。</p>
<h2>业务概述</h2>
<p>平台围绕园区内的人、车、物、事和环境建立统一数据底座。用户或设备产生事件后，系统完成身份校验、规则判断、责任分派、消息通知、现场处理、复核验收和结果归档。物业人员处理报修和服务工单，安保人员处理门禁与告警，运营人员通过看板查看园区运行指标，管理层根据趋势分析进行资源调度。</p>
<p>主要业务场景包括：</p>
<ul>
<li>视频、门禁、停车和 IoT 设备统一接入；</li>
<li>访客预约、人员通行、车辆识别和异常告警；</li>
<li>物业报修、巡检、派单、验收和满意度评价；</li>
<li>能耗、设备、安防和服务工单的多维度统计；</li>
<li>事件联动、消息通知、操作审计和运维监控。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用微服务、前后端分离和事件驱动架构。统一网关承接管理端、企业端、访客端和设备端请求，安防、物业、门禁、车辆、设备和报表按业务域拆分。设备事件通过消息队列进入事件中心，再由规则引擎匹配处理流程，保证实时接入与业务处理解耦。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建园区业务微服务；</li>
<li>Spring Security + OAuth2：实现园区、企业、物业、安保和访客的分级授权；</li>
<li>MySQL + MyBatis：保存人员、组织、设备、工单、通行和事件数据；</li>
<li>Elasticsearch：支持事件、工单、访客和设备日志的快速检索。</li>
</ul>
<h3>3. 中间件与数据能力</h3>
<ul>
<li>Redis：缓存设备状态、门禁权限、停车位和热点看板数据；</li>
<li>RabbitMQ / RocketMQ：异步处理设备事件、告警通知、工单和统计任务；</li>
<li>MQTT / HTTP 适配器：接入门禁、摄像头、停车和传感器等设备；</li>
<li>XXL-JOB：执行巡检计划、数据汇总、告警升级和报表生成；</li>
<li>Vue + Element UI + ECharts：建设运营工作台和可视化驾驶舱。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 安防监控</h3>
<p>接入视频和安防事件，支持实时查看、异常识别、分级告警、责任人通知和处置结果复核。</p>
<h3>2. 物业管理</h3>
<p>维护楼宇、房间、设备和服务目录，支持报修、巡检、派单、处理、验收和评价闭环。</p>
<h3>3. 门禁与访客管理</h3>
<p>配置人员、企业、访客和临时通行权限，记录进出日志，对异常时段、异常区域和重复通行进行告警。</p>
<h3>4. 车辆与停车管理</h3>
<p>接入车牌识别和停车场数据，管理车辆档案、停车位、预约、进出记录和停车流量。</p>
<h3>5. 智能设备与数据处理</h3>
<p>统一维护设备台账、协议、状态和采集数据，对离线、故障和异常指标进行清洗、入库和通知。</p>
<h3>6. 事件中心与消息协同</h3>
<p>把设备告警、人工上报和物业请求统一转换为事件，按照规则分派给责任部门，支持升级、转派和多渠道通知。</p>
<h3>7. 报表与可视分析</h3>
<p>从安防、物业、设备、能耗和车辆维度生成运营报表、趋势图、排行和领导驾驶舱。</p>
<h3>8. 权限、审计与运维</h3>
<p>实现多租户、组织、角色和资源权限控制，记录敏感操作和数据导出，提供服务健康检查、监控、备份和故障处理。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>设备事件和人工工单统一建模，避免设备告警与人工处理各自形成孤岛；</li>
<li>通过消息队列、事件去重和失败重试支撑大批量设备接入；</li>
<li>对门禁、车辆和物业数据实行分域授权，确保不同企业和管理角色只能访问必要数据；</li>
<li>AI 可用于园区制度问答、工单摘要、异常归纳和运营指标解释，正式控制动作仍由后端服务执行；</li>
<li>平台适用于产业园、写字楼、住宅社区、教育园区等多种园区场景。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>智慧园区把安防、物业、门禁、车辆和 IoT 设备放在同一张业务地图上，天然包含多角色协作、实时事件处理和复杂权限控制。你会从园区运营方、物业人员、安保人员、企业员工和访客的不同视角理解需求，再把设备事件、人工工单、服务请求和运营报表串成完整闭环，适合用来建立企业级项目的整体视野。</p>
<ul>
<li>学会拆分园区运营中的业务域、设备域、事件域和数据分析域。</li>
<li>掌握 IoT 数据接入、告警联动、工单流转、权限隔离和运营看板的设计方法。</li>
<li>理解 AI 在制度问答、事件摘要和异常归纳中的辅助边界，避免把模型输出当成业务事实。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套能够覆盖“感知—判断—派单—处置—复盘”的园区项目讲解框架。</li>
<li>一份微服务、消息队列、缓存、审计和运维监控的技术落点清单。</li>
<li>一套适合中高级 Java 面试的复杂场景追问答案。</li>
</ul>
<h2>项目全景</h2>
<figure><img src="/project-diagrams/chengzhi/00-main.svg" alt="智慧园区综合运营管理平台主流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台主流程图</figcaption></figure>
<h2>模块流程图</h2>
<h3>安防监控</h3>
<figure><img src="/project-diagrams/chengzhi/01-module.svg" alt="智慧园区综合运营管理平台安防监控流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台安防监控流程图</figcaption></figure>
<h3>物业管理</h3>
<figure><img src="/project-diagrams/chengzhi/02-module.svg" alt="智慧园区综合运营管理平台物业管理流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台物业管理流程图</figcaption></figure>
<h3>门禁控制</h3>
<figure><img src="/project-diagrams/chengzhi/03-module.svg" alt="智慧园区综合运营管理平台门禁控制流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台门禁控制流程图</figcaption></figure>
<h3>车辆管理</h3>
<figure><img src="/project-diagrams/chengzhi/04-module.svg" alt="智慧园区综合运营管理平台车辆管理流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台车辆管理流程图</figcaption></figure>
<h3>用户认证与权限管理</h3>
<figure><img src="/project-diagrams/chengzhi/05-module.svg" alt="智慧园区综合运营管理平台用户认证与权限管理流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台用户认证与权限管理流程图</figcaption></figure>
<h3>智能设备与数据处理</h3>
<figure><img src="/project-diagrams/chengzhi/06-module.svg" alt="智慧园区综合运营管理平台智能设备与数据处理流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台智能设备与数据处理流程图</figcaption></figure>
<h3>数据报表与可视分析</h3>
<figure><img src="/project-diagrams/chengzhi/07-module.svg" alt="智慧园区综合运营管理平台数据报表与可视分析流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台数据报表与可视分析流程图</figcaption></figure>
<h3>日志管理与审计</h3>
<figure><img src="/project-diagrams/chengzhi/08-module.svg" alt="智慧园区综合运营管理平台日志管理与审计流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台日志管理与审计流程图</figcaption></figure>
<h3>消息通知与任务调度</h3>
<figure><img src="/project-diagrams/chengzhi/09-module.svg" alt="智慧园区综合运营管理平台消息通知与任务调度流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台消息通知与任务调度流程图</figcaption></figure>
<h3>系统维护与运维管理</h3>
<figure><img src="/project-diagrams/chengzhi/10-module.svg" alt="智慧园区综合运营管理平台系统维护与运维管理流程图" tabindex="0" loading="lazy"><figcaption>智慧园区综合运营管理平台系统维护与运维管理流程图</figcaption></figure>
<h2>总体技术架构</h2>
<h3>分层职责</h3>
<p>| 层次 | 主要职责 | 技术 / 组件 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/chengzhi/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>志愿魔方 AI 升学咨询平台</title>
      <link>https://qiuzhiwang.vip/javaai-projects/04-zhiyuan.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/04-zhiyuan.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">志愿魔方 AI 升学咨询平台</source>
      <description>Java+AI 升学咨询项目介绍，覆盖志愿填报、院校推荐、政策解读、咨询报告生成、RAG 知识库和项目面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个面向学生和家长的升学咨询平台，解决“信息多但难理解、政策多但解释难、院校多但选择难”的问题。系统通过数据检索、规则解释和报告生成，为用户提供更易理解的志愿建议。</p>
<h2>项目背景</h2>
<p>学生和家长在升学决策中需要同时理解成绩、位次、选科、批次、院校、专业、招生计划、政策和地域偏好。信息来源多、政策更新快、专业限制复杂，单纯依赖静态列表或泛化聊天回答，很难给出可解释、可复核的建议。志愿魔方 AI 升学咨询平台将学生档案、院校专业数据、政策知识和咨询记录统一管理，为学生、家长和顾问提供辅助决策能力。</p>
<h2>业务概述</h2>
<p>用户登录后建立学生档案，录入成绩、位次、选科、地区、兴趣和目标专业。系统根据硬性条件筛选院校和专业，再结合冲稳保策略、历史数据和政策要求生成候选方案。AI 负责政策问答、方案解释、风险提示和报告整理，顾问可以复核、修改并向学生发送最终方案。</p>
<p>主要业务场景包括：</p>
<ul>
<li>学生信息、成绩、位次和选科档案管理；</li>
<li>院校、专业、招生计划、录取数据和政策版本维护；</li>
<li>志愿筛选、冲稳保分层、方案对比和风险提示；</li>
<li>政策原文检索、招生章程问答和时间节点提醒；</li>
<li>咨询报告生成、顾问复核、家校沟通和历史记录追踪。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用前后端分离和模块化服务架构，学生端、家长端和顾问端共用统一业务服务。规则服务负责成绩、选科和批次等确定性条件过滤，数据同步服务负责院校与政策版本更新，AI 服务只对检索结果进行解释和生成，不直接修改学生档案或提交志愿。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建用户、档案、院校、推荐和报告服务；</li>
<li>Spring Security + OAuth2：保护学生敏感数据和顾问工作台；</li>
<li>MySQL + MyBatis：保存学生档案、院校专业、政策、方案和咨询记录；</li>
<li>Elasticsearch：支持院校、专业、政策和历史案例的多条件检索。</li>
</ul>
<h3>3. 中间件与 AI 能力</h3>
<ul>
<li>Redis：缓存热门院校、筛选条件、会话和临时方案；</li>
<li>MQ：异步执行数据同步、报告生成、通知和失败重试；</li>
<li>向量库 + RAG：检索招生章程、政策原文和专业说明并返回引用；</li>
<li>XXL-JOB：定时更新政策、录取数据、风险规则和统计报表；</li>
<li>Vue + ECharts：展示方案对比、分数趋势和成长报告。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 学生档案管理</h3>
<p>维护学生成绩、位次、选科、地区、兴趣、目标专业和家庭偏好，支持档案版本和隐私权限。</p>
<h3>2. 院校专业数据中心</h3>
<p>管理院校、专业、招生计划、选科要求、学费、就业方向和历史录取数据，并保留来源与生效版本。</p>
<h3>3. 志愿筛选与方案生成</h3>
<p>按照硬性条件过滤候选项，再按冲、稳、保层次组合方案，展示匹配理由、限制条件和风险项。</p>
<h3>4. 政策知识库与 AI 问答</h3>
<p>对政策、招生章程和专项计划进行切分、向量化和版本管理，回答时展示来源、发布时间和适用范围。</p>
<h3>5. 成长与咨询报告</h3>
<p>生成方案摘要、院校对比、专业解释、风险清单和待确认问题，顾问可在线修改并留下复核记录。</p>
<h3>6. 会员、消息与系统管理</h3>
<p>管理学生、家长、顾问和机构角色，支持报告通知、时间节点提醒、数据导出和审计查询。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>所有推荐结果关联成绩区间、选科要求、政策依据和数据版本，做到可解释；</li>
<li>对政策过期、计划变更、专业限制和材料要求单独提示风险；</li>
<li>通过异步任务生成长报告，避免用户长时间等待；</li>
<li>学生成绩、联系方式和咨询记录按机构、顾问和学生进行数据隔离；</li>
<li>AI 只提供辅助方案，最终选择和正式提交必须由学生、家长或顾问确认。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>升学咨询看似是推荐页面，背后却包含成绩、位次、选科、批次、招生计划、政策版本和风险提示等大量数据关系。你会把规则筛选、政策检索、推荐解释和报告生成组合成一条可信的咨询链路，理解 AI 应用如何在敏感决策场景中做到有依据、可复核、能交给人工确认。</p>
<ul>
<li>学会处理时效数据、规则版本、推荐条件和风险提示。</li>
<li>掌握 RAG 政策问答、带引用的推荐解释和异步报告生成。</li>
<li>建立“AI 辅助建议 + Java 规则校验 + 人工最终确认”的安全边界。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套从学生画像到方案对比、风险提示和报告交付的业务流程。</li>
<li>一份可用于面试的推荐系统、知识库和权限设计亮点。</li>
<li>一套能体现 Java 后端与 AI 应用结合能力的项目表达。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>升学咨询同时涉及成绩、选科、批次、院校、专业、招生章程、地域偏好和职业方向等多类信息。数据来源多、政策更新快，用户又希望看到清晰的推荐依据和风险提示，单纯依靠静态院校列表或泛化聊天回答很难形成可靠的咨询体验。</p>
<p>志愿魔方 AI 升学咨询平台将学生档案、院校专业数据、招生政策、历史录取信息和咨询记录统一管理，形成“条件采集、候选筛选、规则解释、风险提示、方案对比、报告生成、人工复核”的服务链路。平台输出的是带依据的辅助方案，最终选择仍由学生、家长或专业顾问确认，适合展示数据服务、规则引擎和生成式 AI 的组合应用。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/zhiyuan/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>园区智枢智能运营服务平台</title>
      <link>https://qiuzhiwang.vip/javaai-projects/05-yuanqu.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/05-yuanqu.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">园区智枢智能运营服务平台</source>
      <description>Java+AI 园区运营服务项目介绍，覆盖企业服务、设备协同、多角色工单流转、运营知识库、Tool Calling 和面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个面向园区运营的综合服务平台，围绕企业入驻、空间服务、设备报修、活动通知、政策解读和服务工单展开。AI 的作用是帮助用户更快找到服务入口，并减少人工咨询负担。</p>
<h2>项目背景</h2>
<p>运营服务平台面向金融或大型企业的运营团队，处理账单、额度、风险、客户注销和结果推送等高频业务。上游系统提供的数据格式不一，运营人员需要经过校验、审批、重复单检测和批量处理后，再将结果同步给下游系统。如果缺少统一的平台，业务容易依赖人工表格，处理过程难追踪，异常也难以及时发现。</p>
<h2>业务概述</h2>
<p>平台接收账单、额度申请、风险客户和注销信息等上游数据，完成文件解析、客户信息校验、业务规则匹配、审批流转、结果更新和下游推送。运营人员在统一工作台处理任务，管理者查看业务量、处理时长、异常率和报表结果，系统对关键操作进行权限控制和审计留痕。</p>
<p>主要业务场景包括：</p>
<ul>
<li>大批量账单导入、解析、校验、对账和差错处理；</li>
<li>临时或固定额度申请、资质校验、审批和结果同步；</li>
<li>风险客户提报、风险报表和跨模块客户信息共享；</li>
<li>客户注销挽留策略、跟进记录和结果分析；</li>
<li>批量报表、下游推送、任务调度和异常重试。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用微服务与分布式模块化架构，按账务、额度、风险、挽留、系统管理和公共能力拆分服务。服务之间通过 OpenFeign 同步调用，通过 RabbitMQ 异步传递账单结果、审批通知和报表任务，网关统一处理认证、路由和流量控制。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：承载业务服务和统一接口；</li>
<li>Nacos：服务注册、配置管理和动态参数；</li>
<li>MyBatis-Plus：实现批量入库、条件查询和分页处理；</li>
<li>MySQL：保存账单、客户、额度、风险和审批数据；</li>
<li>EasyExcel：流式解析大批量账单，降低内存占用。</li>
</ul>
<h3>3. 中间件与工程能力</h3>
<ul>
<li>Redis：缓存客户、额度和风险热点数据，使用分布式锁防止重复审批；</li>
<li>RabbitMQ：异步处理结果推送、报表生成和通知消息；</li>
<li>Elasticsearch：保存历史账单，支持按卡号、币种和时间快速检索；</li>
<li>Sentinel：进行限流、熔断和降级；</li>
<li>Seata：保障跨服务业务的一致性；</li>
<li>XXL-JOB + Jenkins：完成对账、统计、推送和自动化部署。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 账务服务</h3>
<p>接收并解析账单文件，执行字段校验、客户匹配、调账审批、余额更新、差错处理和历史查询。</p>
<h3>2. 额度调整</h3>
<p>接收微信端和后台申请，进行灰名单、征信和客户资质校验，完成重复单检测、审批、额度更新和结果通知。</p>
<h3>3. 风险提报</h3>
<p>管理风险客户、风险类型、处理状态和关联额度信息，生成风险清单和管理报表。</p>
<h3>4. 注销挽留</h3>
<p>根据客户额度、风险和历史行为生成挽留任务，记录联系过程、策略执行和最终注销结果。</p>
<h3>5. 审批、调度与报表</h3>
<p>统一管理流程节点、审批实例、任务权限和版本，异步生成大数据量报表并推送给相关人员。</p>
<h3>6. 系统管理与审计</h3>
<p>提供用户、角色、菜单、参数、日志和任务权限管理，记录数据导入、审批、推送和导出行为。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>使用 EasyExcel 监听器和批量入库支持大文件处理，避免一次性加载造成内存溢出；</li>
<li>使用流程定义、实例、节点和版本模型支撑不同业务的可配置审批；</li>
<li>Redis 分布式锁与数据库唯一索引共同防止重复审批和重复推送；</li>
<li>通过 Sentinel、Seata、消息重试和补偿任务提升分布式系统稳定性；</li>
<li>报表、历史账单和下游推送异步化，降低前台等待时间，提升运营处理效率。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>园区运营平台连接企业、物业、运营人员和设备服务商，业务往往不是一个页面就能完成，而是由服务目录、资源预约、设备报修、审批、通知和运营分析共同组成。你会沿着“服务发起—智能分流—工单协同—结果评价—运营复盘”的路径，理解服务中台如何支撑园区持续运营。</p>
<ul>
<li>学会把企业服务、空间资源、设备状态和工单流程统一建模。</li>
<li>掌握多角色权限、配置化服务目录、设备告警联动和异步通知。</li>
<li>理解 RAG、Tool Calling 在查询、摘要和服务建议中的可控用法。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套园区服务中台的业务地图、模块边界和接口拆分思路。</li>
<li>一份围绕状态机、SLA、幂等、消息重试和审计的难点清单。</li>
<li>一套适合简历、作品集和面试演示的项目亮点。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>现代园区往往同时服务园区运营方、物业、企业员工、访客和设备运维人员。企业入驻、会议室预约、政策咨询、空间报修、活动报名和设备巡检分散在不同群组或系统中，运营人员需要重复回答问题、手工派单并追踪处理结果，服务质量很难稳定复制。</p>
<p>园区智枢智能运营服务平台把企业服务目录、空间资源、设备状态、服务工单、通知活动和园区制度统一到一个运营中台。平台以“统一入口、智能分流、工单协同、过程留痕、运营分析”为主线，AI 负责知识问答、意图分类、工单摘要和服务建议，正式的资源占用、费用、权限和工单写操作由 Java 服务完成。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/yuanqu/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>驿链智治快递异常治理平台</title>
      <link>https://qiuzhiwang.vip/javaai-projects/06-yilian.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/06-yilian.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">驿链智治快递异常治理平台</source>
      <description>Java+AI 快递异常治理项目介绍，覆盖异常识别、投诉治理、责任判断、历史案例检索、处置闭环和项目面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个围绕快递异常治理的业务平台，核心处理延误、丢件、破损、错投、投诉等问题。AI 负责辅助识别异常类型、查找历史处置方式和给出建议，Java 后端负责正式流转和闭环。</p>
<h2>项目背景</h2>
<p>城市管理部门每天会接收到大量道路、环境、设施、井盖、照明和公共安全事件。传统模式依赖电话、表格和人工派单，容易出现事件分类不准、责任部门不清、处理进度不可见和结果无法复盘的问题。智慧城市管理平台通过统一事件中心、巡查管理、自动派遣和设备接入，把城市治理从被动处理升级为全过程协同。</p>
<h2>业务概述</h2>
<p>平台服务于城市管理中心、街道部门、巡查人员、处置单位和管理者。事件可由市民、热线、移动端、摄像头和传感器上报，系统完成分类定级、规则匹配、自动派遣、现场处理、复核验收和归档统计。巡查和专项普查任务与事件中心联动，管理者通过驾驶舱查看区域、类型、时效和部门绩效。</p>
<p>主要业务场景包括：</p>
<ul>
<li>多渠道城市事件上报、分类、定级和定位；</li>
<li>巡查计划、路线、任务执行和异常反馈；</li>
<li>井盖、路灯、摄像头和环境传感器等设备接入；</li>
<li>规则派遣、跨部门协同、现场处置和复核验收；</li>
<li>城市设施、环境、安全和治理指标的统计分析。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用微服务、事件驱动和前后端分离架构，统一网关接入管理端、移动巡查端、公众上报端和 IoT 设备。事件中心负责接收和标准化数据，规则服务负责分类和派遣，工单服务负责状态流转，分析服务负责指标汇总和可视化。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建事件、巡查、派遣和治理服务；</li>
<li>Spring Security：统一认证和部门、区域、角色权限控制；</li>
<li>MySQL + MyBatis：保存事件、任务、设施、巡查和处理记录；</li>
<li>Elasticsearch：支持城市事件、地址、设施和历史案例的全文检索。</li>
</ul>
<h3>3. 中间件与数据能力</h3>
<ul>
<li>Redis：缓存区域、设施、派遣规则和热点统计；</li>
<li>MQ：解耦事件接入、规则计算、通知、索引和报表任务；</li>
<li>IoT 接入层：处理设备心跳、状态、告警和采集数据；</li>
<li>XXL-JOB：执行巡查计划、超时扫描、统计和数据同步；</li>
<li>Vue + ECharts：建设事件地图、部门看板和领导驾驶舱。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 事件上报与受理</h3>
<p>接收公众、热线、移动端、巡查和设备事件，统一生成事件编号，记录位置、图片、类型、优先级和来源。</p>
<h3>2. 自动派遣与工作流</h3>
<p>根据事件类型、区域、责任部门、人员负载和 SLA 自动派遣，支持转派、退回、协同、升级和流程回溯。</p>
<h3>3. 巡查与专项普查</h3>
<p>制定巡查路线、计划和周期任务，记录现场签到、问题发现、照片和处理反馈，形成普查报告。</p>
<h3>4. 城市治理与设施台账</h3>
<p>管理道路、井盖、路灯、公共设施和环境指标，关联事件、维修记录和设备状态。</p>
<h3>5. IoT 数据采集</h3>
<p>接入摄像头、传感器和智能设备，进行数据清洗、状态监控、离线检测和异常告警。</p>
<h3>6. 协作、消息与统计</h3>
<p>为部门、街道和处置单位提供协作消息、任务提醒、事件查询、趋势分析和绩效报表。</p>
<h3>7. 权限、审计与运维</h3>
<p>按区域、部门和角色隔离数据，记录事件查看、派遣、结案和导出行为，提供监控、备份和故障处理能力。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>通过规则引擎把事件类型、区域和部门职责转化为可配置的派遣规则；</li>
<li>通过事件状态机和 SLA 计时保证从上报到验收的过程透明；</li>
<li>通过 IoT 设备告警与人工工单联动，形成感知、派遣、处理和复盘闭环；</li>
<li>通过 Elasticsearch 和统计聚合支持海量历史事件快速查询；</li>
<li>多部门协同、权限隔离和完整审计满足城市治理的数据安全要求。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>快递异常治理不是简单地登记投诉，而是要把异常识别、责任判断、任务派遣、协同处理、客户沟通和结果复盘连成闭环。你会从一条异常件开始，逐步理解平台如何面对多来源数据、跨部门协同和高频状态变化，并把治理结果沉淀为可复用的规则和案例。</p>
<ul>
<li>学会设计异常事件模型、责任链路、工单状态和自动派遣规则。</li>
<li>掌握消息驱动、批量数据处理、历史案例检索和多维度统计分析。</li>
<li>形成对“识别准确、处理及时、过程可追溯、结果可复盘”的系统化理解。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套从异常上报到结案评价的完整业务讲解路径。</li>
<li>一份包含性能、可靠性、权限和数据治理的技术方案。</li>
<li>一套能够体现真实企业协作经验的面试表达素材。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>快递业务链路长、参与方多，异常可能发生在揽收、分拨、运输、派送和签收任一环节。延误、破损、丢件、错投和客户投诉通常需要关联运单轨迹、网点信息、客服记录和历史赔付规则。如果只靠人工逐单查询，处理时间长，责任判断和服务口径也容易不一致。</p>
<p>驿链智治快递异常治理平台围绕“异常发现、证据汇聚、责任研判、协同处置、客户反馈、复盘分析”建设统一工作台。平台将物流轨迹、异常规则、投诉工单和历史案例串联起来，AI 用于异常分类、轨迹摘要、相似案例检索和回复草稿，Java 后端负责责任流转、赔付审批、时限控制和最终结果落库。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/yilian/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>融策信贷尽调与审批辅助平台</title>
      <link>https://qiuzhiwang.vip/javaai-projects/07-rongce.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/07-rongce.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">融策信贷尽调与审批辅助平台</source>
      <description>Java+AI 信贷风控项目介绍，覆盖 RAG、Tool Calling、信贷尽调、审批辅助、权限审计、人工确认和面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个信贷尽调与审批辅助平台，解决资料多、尽调慢、审批信息分散、风险判断依赖经验的问题。AI 用于辅助摘要、检索和风险提示，但最终审批必须由人确认。</p>
<h2>项目背景</h2>
<p>银行和金融机构的信贷业务涉及客户申请、材料采集、尽职调查、授信审批、放款和贷后跟踪。传统流程依赖人工收集和阅读大量材料，信息分散在移动端、审批系统和附件中，容易出现资料缺失、重复录入、风险提示不及时和审批过程难追溯等问题。融策信贷尽调与审批辅助平台以移动化、流程化和可审计为目标，提高客户经理和审批人员的处理效率。</p>
<h2>业务概述</h2>
<p>客户经理在移动端登录后创建客户和贷款申请，上传身份证明、经营资料、征信和担保材料。系统根据产品规则执行完整性校验和客户调查任务，审批人员查看尽调报告、风险指标和历史记录后进行多级审批。放款后同步贷款状态、还款信息和贷后任务，所有关键操作都保留证据链。</p>
<p>主要业务场景包括：</p>
<ul>
<li>客户建档、贷款申请、材料上传和补件；</li>
<li>客户调查、访谈记录、经营核验和尽调报告；</li>
<li>授信额度、利率、期限和风险规则审批；</li>
<li>放款前校验、放款审批、资金发放和状态同步；</li>
<li>贷后跟踪、异常提醒、合规审计和历史查询。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用移动端、管理端和后端服务分离的微服务架构。申请、材料、尽调、授信、放款和贷后按业务域拆分，统一认证服务负责身份和权限，流程服务负责审批节点和状态流转，消息服务负责通知、同步和任务调度。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建信贷业务微服务；</li>
<li>Spring Security + OAuth2：实现客户经理、审批员、复核员和管理员权限控制；</li>
<li>MySQL / Oracle：保存客户、申请、材料索引、审批和还款数据；</li>
<li>MyBatis：支持复杂查询、审批列表和批量同步。</li>
</ul>
<h3>3. 中间件与 AI 能力</h3>
<ul>
<li>Redis：缓存客户摘要、风险规则和审批任务，配合锁保证幂等；</li>
<li>MQ：异步处理材料解析、征信同步、审批通知和贷后任务；</li>
<li>对象存储：保存材料原文件和版本信息；</li>
<li>OCR / RAG：提取材料字段、检索制度和生成尽调摘要；</li>
<li>XXL-JOB：执行征信同步、逾期扫描、报表和补偿任务。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 客户与信贷申请</h3>
<p>维护客户、联系人、产品、申请金额、期限和用途，校验重复申请、黑名单和基础准入条件。</p>
<h3>2. 材料中心</h3>
<p>支持材料分类、上传、版本、有效期、缺失检查、OCR 提取和人工修订，记录每次查看和下载行为。</p>
<h3>3. 贷款调查</h3>
<p>生成调查任务和核查清单，记录访谈、经营场所、财务信息、担保情况和尽调结论。</p>
<h3>4. 授信审批</h3>
<p>支持多级审批、会签、退回、补件、额度调整和风险规则命中，审批意见与依据完整留痕。</p>
<h3>5. 放款与贷后</h3>
<p>执行放款前校验、资金发放、状态同步、还款跟踪和贷后检查，异常情况进入人工处理队列。</p>
<h3>6. 权限、通知与审计</h3>
<p>按机构、岗位和客户范围控制访问，发送审批、补件和异常通知，记录敏感材料查看和审批操作。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>材料、规则、审批意见和额度结果形成完整证据链，支持事后复核；</li>
<li>通过版本号、幂等键和事务控制解决补件、退回、重复提交和并发审批问题；</li>
<li>AI 只提供材料摘要、条款检索和疑点提示，额度、利率、审批和放款必须人工确认；</li>
<li>通过异步解析和消息重试提升大批量材料处理能力；</li>
<li>通过脱敏、数据隔离、操作审计和导出控制满足金融合规要求。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>信贷尽调和审批是最能体现企业级系统严谨性的场景之一。资料接收、OCR 解析、风险核验、补件、会签、审批和贷后跟踪每一步都要求有依据、有权限、有留痕。你会在这个项目中理解 AI 如何帮助处理材料和信息，而真正的额度、利率、审批和放款动作如何由 Java 服务和人工确认共同守住边界。</p>
<ul>
<li>学会设计材料中心、风险规则、审批状态机、版本控制和审计模型。</li>
<li>掌握文档解析、RAG 检索、风险摘要、异步任务和失败补偿。</li>
<li>能够回答金融项目最关心的数据安全、合规、幂等和人工兜底问题。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套可讲清“资料—尽调—审批—决策—贷后”的金融业务流程。</li>
<li>一份围绕合规、安全和可靠性的高级项目亮点清单。</li>
<li>一套适合中高级 Java+AI 岗位的面试深挖素材。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>信贷业务需要同时核验客户基本信息、经营状况、财务资料、征信记录、抵押担保和历史往来。传统尽调过程依赖人工阅读大量材料，审批意见分散在表格、邮件和系统备注中，容易出现资料遗漏、口径不一致和风险提示不及时的问题。</p>
<p>融策信贷尽调与审批辅助平台以客户、授信申请、材料、尽调任务、风险规则和审批节点为核心对象，建立从资料接收、OCR / 文档解析、风险核验、尽调报告、多人会签、审批决策到贷后跟踪的全流程。AI 只提供材料摘要、条款检索、疑点提示和报告草稿，任何额度、利率、审批和放款动作都必须在权限和人工确认边界内完成。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/rongce/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>桩云智维新能源充电运维平台</title>
      <link>https://qiuzhiwang.vip/javaai-projects/08-zhuangyun.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/08-zhuangyun.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">桩云智维新能源充电运维平台</source>
      <description>Java+AI 新能源充电运维项目介绍，覆盖充电桩故障诊断、设备告警、FOTA、运维工单、安全边界和面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个新能源充电运维平台，处理设备状态监控、故障码识别、异常工单、远程运维和监管上报等问题。AI 的作用是帮助运维人员更快定位问题和检索处理方案。</p>
<h2>项目背景</h2>
<p>新能源汽车充电网络同时连接用户、充电桩、运营商、支付渠道和监管平台。用户需要快速找到可用充电桩并完成扫码充电，运营人员需要掌握设备状态、订单、故障和收益，设备厂商还需要远程升级和维护。桩云智维新能源充电运维平台将用户服务、订单交易、设备运维和监管数据统一起来，支撑充电业务稳定运行。</p>
<h2>业务概述</h2>
<p>用户通过 PC、小程序或 App 定位附近充电站，查看桩状态、预约并扫码启动充电。平台校验用户、设备和占用状态后创建订单，持续接收充电进度和电量数据，结束充电后完成计费、支付和结算。设备异常自动生成告警和工单，运营人员可以远程查看、重启或升级设备，监管和第三方系统通过接口同步数据。</p>
<p>主要业务场景包括：</p>
<ul>
<li>充电站、充电桩、枪口和实时状态管理；</li>
<li>用户定位、扫码、预约、启动、暂停和结束充电；</li>
<li>订单计费、钱包、优惠、支付回调和对账；</li>
<li>设备故障、远程控制、FOTA 升级和运维工单；</li>
<li>运营报表、政府监管、第三方支付和数据分析。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用用户端、运营端和设备端分离的微服务架构。用户服务、设备服务、订单服务、支付服务、运维服务和数据服务通过网关统一接入，设备侧通过 MQTT 或厂商协议接入，订单与支付使用消息队列解耦，确保设备状态和交易链路互不阻塞。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建充电、订单、支付和运维服务；</li>
<li>Spring Security + OAuth2：实现用户、运营商、维修人员和管理员权限控制；</li>
<li>MySQL：保存用户、桩、订单、支付、工单和配置数据；</li>
<li>MyBatis：支持订单查询、计费规则和运营统计。</li>
</ul>
<h3>3. 中间件与设备能力</h3>
<ul>
<li>Redis：缓存桩状态、站点信息、预约占用和用户会话；</li>
<li>MQTT / MQ：传递设备心跳、充电进度、告警和控制指令；</li>
<li>分布式锁：防止重复预约、重复启动和重复支付回调；</li>
<li>XXL-JOB：执行对账、订单关单、设备巡检和升级任务；</li>
<li>Vue / 小程序 / App：提供用户充电和运营管理界面。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 充电桩服务</h3>
<p>维护站点、桩、枪口、协议和实时状态，支持定位、可用性查询、占用和设备心跳监控。</p>
<h3>2. 订单管理</h3>
<p>处理预约、启动、充电中、结束、异常和关闭等状态，关联电量、时长、服务费和优惠规则。</p>
<h3>3. 钱包与支付</h3>
<p>支持余额、充值、支付、退款、优惠券、支付回调、对账和异常补偿，保证金额和订单状态一致。</p>
<h3>4. FOTA 与运维工单</h3>
<p>管理设备版本、升级批次、灰度策略、升级结果和失败回滚，设备故障自动生成运维工单。</p>
<h3>5. 会员、资源与权限</h3>
<p>维护会员等级、车辆、发票、站点资源、运营商和维修人员权限。</p>
<h3>6. 监管、集成与分析</h3>
<p>对接政府监管、支付、地图和第三方平台，统计充电量、利用率、故障率、收入和设备健康度。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>通过订单状态机、支付幂等和消息补偿保证充电交易不重复、不丢失；</li>
<li>设备状态采用心跳、事件和定时校验多种方式确认，避免页面显示过期状态；</li>
<li>远程控制和 FOTA 升级必须经过设备校验、权限校验和操作回执；</li>
<li>通过 Redis、MQ 和异步报表支撑高峰期用户访问和海量设备数据；</li>
<li>用户服务、设备运维和监管数据统一沉淀，帮助运营商优化站点和设备资源。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>充电运维平台同时面对设备在线状态、用户订单、钱包支付、故障告警和远程升级，既有互联网交易链路，也有物联网设备控制。你会把设备数据和用户服务放在同一个系统里理解，掌握如何在高并发、弱网络和高风险操作场景下保证业务稳定与操作安全。</p>
<ul>
<li>学会设计充电桩接入、订单状态、支付幂等、告警工单和远程操作审批。</li>
<li>掌握 MQTT / 消息队列、Redis、分布式锁、任务调度和设备状态同步。</li>
<li>理解远程升级、异常重试和人工确认在设备类系统中的必要性。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套从用户找桩、充电、结算到故障处理的完整项目故事线。</li>
<li>一份互联网交易与 IoT 运维结合场景的技术难点清单。</li>
<li>一套可以直接用于简历和面试的性能、安全与可靠性表达。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>充电网络由充电桩、充电站、运营平台、支付系统和监管接口共同组成。设备离线、通信异常、过温、充电中断和计费异常等问题既影响用户体验，也会带来运维成本和安全风险。运维人员需要同时查看实时状态、历史故障、固件版本、工单记录和现场处理手册。</p>
<p>桩云智维新能源充电运维平台将站点、设备、枪口、订单、告警、工单和远程操作统一管理，形成“设备接入、状态监测、异常识别、工单派发、远程诊断、现场处理、结果复盘”的运维闭环。AI 用于故障码解释、告警聚合、处理手册检索和工单摘要；远程重启、参数变更、FOTA 和停机等高风险动作由 Java 服务执行权限校验、二次确认和审计。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/zhuangyun/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>杏云 CRM 智能销售助手</title>
      <link>https://qiuzhiwang.vip/javaai-projects/09-xingyun-crm.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/09-xingyun-crm.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">杏云 CRM 智能销售助手</source>
      <description>Java+AI CRM 智能销售助手项目介绍，覆盖客户画像、销售跟进、商机推进、销售话术、RAG 知识库和面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个 CRM 智能销售助手，围绕客户跟进、商机推进、销售话术、产品介绍和常见异议处理展开。AI 不是单独聊天，而是嵌入销售工作流，帮助销售更快做判断和记录。</p>
<h2>项目背景</h2>
<p>销售团队的客户资料、线索来源、沟通记录、报价方案和回款信息经常分散在 CRM、表格和即时通讯中。销售需要持续判断客户阶段和下一步动作，管理者需要了解商机质量、转化率和团队预测，信息缺失会导致客户遗忘、商机停滞和复盘困难。杏云 CRM 智能销售助手把客户管理和 AI 辅助能力融入销售工作流。</p>
<h2>业务概述</h2>
<p>平台服务于销售人员、销售主管、售前顾问和管理者。线索进入后，系统完成客户去重、分配、建档和跟进计划；销售记录电话、拜访、会议和报价，商机按照阶段推进并触发提醒。AI 根据客户画像、产品资料和历史互动生成会议摘要、跟进建议、话术草稿和异议处理思路，正式的客户数据、商机状态和报价审批由 Java 服务控制。</p>
<p>主要业务场景包括：</p>
<ul>
<li>客户、联系人、标签、来源和负责人统一管理；</li>
<li>线索分配、商机阶段、赢率、预计成交和跟进计划；</li>
<li>产品资料、报价版本、竞品信息和合同协同；</li>
<li>客户画像、通话摘要、话术建议和下一步行动；</li>
<li>销售漏斗、转化率、跟进周期、回款和团队预测分析。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用前后端分离和按业务域拆分的服务架构，客户、线索、商机、产品、跟进、合同和分析模块通过统一网关提供服务。AI 服务负责检索资料和生成建议，业务服务负责客户权限、商机状态、审批、数据落库和审计。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建客户、商机、跟进和分析服务；</li>
<li>Spring Security + OAuth2：控制组织、团队、负责人和共享客户范围；</li>
<li>MySQL：保存客户、联系人、线索、商机、报价和回款数据；</li>
<li>MyBatis / MyBatis-Plus：支持复杂筛选、漏斗统计和批量导入。</li>
</ul>
<h3>3. 中间件与 AI 能力</h3>
<ul>
<li>Redis：缓存客户画像、会话上下文、热点产品和提醒任务；</li>
<li>MQ：异步处理跟进提醒、导入、同步、统计和通知；</li>
<li>Elasticsearch + 向量库：检索产品手册、报价政策、竞品资料和历史案例；</li>
<li>XXL-JOB：执行商机超期提醒、日报、周报和数据同步；</li>
<li>Spring AI：实现摘要、话术、知识问答和受控工具调用。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 客户与联系人</h3>
<p>维护客户基本信息、联系人、行业、区域、标签、来源和归属，支持客户去重、合并和共享。</p>
<h3>2. 线索与商机</h3>
<p>管理线索池、分配规则、商机阶段、预计金额、赢率、竞争信息和下一步计划，阶段变化自动触发任务。</p>
<h3>3. 跟进工作台</h3>
<p>记录电话、拜访、会议、纪要和客户反馈，AI 生成摘要与行动项，销售确认后转为正式跟进记录和待办。</p>
<h3>4. 产品与方案</h3>
<p>管理产品、版本、价格、报价、竞品和合同模板，查询和报价动作受角色权限与审批规则约束。</p>
<h3>5. 销售助手</h3>
<p>检索产品知识、客户历史和异议案例，生成话术、方案说明和风险提示，支持人工编辑和引用来源。</p>
<h3>6. 经营分析与审计</h3>
<p>统计销售漏斗、阶段转化、成交周期、回款和团队目标，记录客户查看、导出、报价和权限变更行为。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>客户数据按组织、团队、负责人和共享规则隔离，避免越权查看；</li>
<li>将客户事实、交易数据和 AI 建议分开存储，模型不能自行补全金额和承诺；</li>
<li>通过状态机、提醒任务和待办机制让建议真正转化为销售行动；</li>
<li>RAG 返回产品资料版本和适用范围，低置信度时转人工确认；</li>
<li>统一漏斗、回款和阶段口径，为管理者提供可追踪的经营分析。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>CRM 的价值不在于保存客户资料，而在于帮助销售持续推进商机、减少遗漏、提高转化。你会围绕线索、客户、跟进、商机、报价、回款和经营分析建立完整的销售工作台，再把 AI 话术、会议摘要和下一步建议嵌入真实工作流，让 AI 输出真正能够转成行动。</p>
<ul>
<li>学会设计客户数据隔离、线索分配、商机阶段和跟进提醒。</li>
<li>掌握 RAG 产品知识问答、话术生成、会议摘要和受控工具调用。</li>
<li>理解如何区分系统事实、模型建议和销售人员最终确认的承诺。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套从获客到回款的 CRM 业务链路和数据模型。</li>
<li>一份销售助手的 AI 接入、权限控制和审计设计方案。</li>
<li>一套更贴近真实工作场景的 Java+AI 项目面试表达。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>销售团队的客户资料、通话记录、拜访纪要和报价信息通常分散在 CRM、表格和即时通讯中。销售需要持续判断客户阶段、下一步动作和成交风险，管理者则要关注商机质量、跟进节奏和团队预测。如果缺少统一的过程记录，客户容易遗忘、商机容易停滞，复盘也很难依赖真实数据。</p>
<p>杏云 CRM 智能销售助手围绕“客户建档、线索分配、跟进记录、商机推进、方案推荐、合同协同、回款跟踪和经营分析”搭建销售工作台。AI 根据客户画像、历史互动和产品资料提供下一步建议、话术草稿、异议处理和会议摘要，Java 服务负责客户权限、商机状态、审批、数据落库和销售过程审计。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/xingyun-crm/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>观澜 AIOps 智能告警分析平台</title>
      <link>https://qiuzhiwang.vip/javaai-projects/10-guanlan-aiops.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/10-guanlan-aiops.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">观澜 AIOps 智能告警分析平台</source>
      <description>Java+AI AIOps 智能告警分析项目介绍，覆盖告警降噪、故障归因、Runbook 检索、运维辅助、Agent 和面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个面向运维与平台团队的 AIOps 平台，解决告警太多、重复告警多、定位慢、知识散、应急流程不统一的问题。AI 负责归纳和检索，Java 负责告警流转、规则执行和统计归档。</p>
<h2>项目背景</h2>
<p>微服务、容器、数据库、中间件和云资源持续产生指标、日志、链路、变更和告警。一个故障往往会在多个系统中同时产生大量重复告警，值班人员需要先判断影响范围，再查找 Runbook、发布记录和历史案例，定位速度高度依赖个人经验。观澜 AIOps 智能告警分析平台通过统一接入、告警治理和知识沉淀，帮助运维团队建立稳定的故障响应流程。</p>
<h2>业务概述</h2>
<p>平台接入业务系统和第三方监控数据，对信号进行标准化、去重、聚合、抑制和升级，形成统一的告警事件。值班人员在预警工作台查看影响服务、拓扑关系、最近变更和历史案例，系统给出根因候选和 Runbook 建议，处置结果、时间线和复盘结论沉淀到知识库。</p>
<p>主要业务场景包括：</p>
<ul>
<li>指标、日志、链路、变更和工单数据统一接入；</li>
<li>重复告警合并、噪声抑制、优先级调整和升级；</li>
<li>服务拓扑、影响范围、变更关联和根因候选分析；</li>
<li>值班排班、协同群组、Runbook 和工单联动；</li>
<li>MTTA、MTTR、告警量、恢复率和服务趋势分析。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用事件驱动和可观测性架构，数据接入、规则计算、告警中心、拓扑服务、分析服务和通知服务独立部署。采集数据进入消息总线后进行标准化和规则计算，告警状态由事件中心统一维护，AI 只读取授权数据生成摘要和诊断建议。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建告警、规则、拓扑和分析服务；</li>
<li>Spring Security：控制租户、服务、值班组和敏感运维数据访问；</li>
<li>MySQL：保存告警、规则、服务、变更、工单和审计数据；</li>
<li>Elasticsearch：存储日志、事件、时间线和历史案例，支持高速查询。</li>
</ul>
<h3>3. 中间件与 AI 能力</h3>
<ul>
<li>Kafka / RabbitMQ：承载监控信号、告警事件和通知消息；</li>
<li>Redis：保存时间窗口、去重指纹、拓扑缓存和抑制状态；</li>
<li>时序数据库：保存指标和资源趋势数据；</li>
<li>向量库 + RAG：检索 Runbook、历史故障和复盘案例；</li>
<li>XXL-JOB：执行告警扫描、聚合、报表和知识同步。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 数据接入与信号管理</h3>
<p>适配指标、日志、链路、云资源和第三方系统，统一字段、服务标识、时间和严重等级，支持失败重试和补偿。</p>
<h3>2. 预警工作台</h3>
<p>集中展示待确认、处理中、已恢复和已关闭告警，支持确认、分派、升级、协同和处置时间线。</p>
<h3>3. 规则管理</h3>
<p>配置阈值、组合条件、时间窗口、抑制规则、升级规则和版本，支持测试、灰度和回滚。</p>
<h3>4. 服务拓扑与风险分析</h3>
<p>维护应用、主机、数据库和中间件依赖关系，结合变更记录和告警时间计算影响范围和根因候选。</p>
<h3>5. 应急处置与通知</h3>
<p>管理值班表、群组、Runbook、工单和多渠道通知，生产执行动作需要权限、审批和回执校验。</p>
<h3>6. 报表、审计与运维</h3>
<p>统计告警量、重复率、响应时长、恢复时长和服务趋势，记录规则变更、告警处理、工具调用和数据导出。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>每次告警合并、抑制和升级都记录规则、时间窗口和关联对象，保证降噪可解释；</li>
<li>通过指纹、时间窗口和状态机解决重复告警、延迟恢复和乱序消息问题；</li>
<li>AI 输出根因候选和证据，不能直接重启服务或修改生产配置；</li>
<li>故障时间线和人工结论经审核后沉淀为 Runbook，持续降低重复排障成本；</li>
<li>通过 MTTA、MTTR 和告警趋势帮助团队评估稳定性治理效果。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>AIOps 项目直接面对线上系统的稳定性问题：告警来自不同平台，重复、噪声和关联关系让定位变慢，值班人员还要在压力下查 Runbook、看变更和组织处置。你会把告警接入、降噪聚合、拓扑分析、根因建议和复盘知识串成闭环，理解 AI 如何帮助运维人员更快判断，而不是替代生产操作。</p>
<ul>
<li>学会设计告警去重、抑制、升级、恢复和事件状态机。</li>
<li>掌握指标、日志、链路、变更、服务拓扑和 Runbook 的关联方式。</li>
<li>理解生产环境中的 Tool Calling、审批、回执和安全边界。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套从告警产生到故障复盘的 AIOps 项目讲解框架。</li>
<li>一份体现可观测性、异步处理、降噪策略和可靠性的技术亮点。</li>
<li>一套适合平台研发、后端和 AI 工程岗位的面试素材。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>微服务、容器、数据库、中间件和云资源持续产生监控指标、日志、链路和告警。单个故障往往会在多个系统中产生大量重复告警，值班人员需要先判断影响范围，再查找 Runbook、变更记录和历史案例，定位速度高度依赖个人经验。</p>
<p>观澜 AIOps 智能告警分析平台把指标、日志、链路、变更和工单统一接入，以“采集、去重、聚合、分派、诊断、处置、复盘”为主线建立运维闭环。AI 用于告警摘要、相似事件检索、根因分析建议和 Runbook 问答，Java 服务负责规则计算、告警状态、责任人、应急流程和审计，确保自动化动作可控。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/guanlan-aiops/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>智造星图 MES 生产调度与设备诊断平台</title>
      <link>https://qiuzhiwang.vip/javaai-projects/11-zhizao-mes.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/11-zhizao-mes.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">智造星图 MES 生产调度与设备诊断平台</source>
      <description>Java+AI MES 生产调度项目介绍，覆盖制造业排产、设备诊断、工艺协同、质量管理、RAG 知识库和面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个面向制造业的 MES 平台，核心处理生产计划、工艺协同、设备诊断、质量管理和异常闭环。AI 的作用是辅助诊断设备问题、解释工艺规则和给出调度建议。</p>
<h2>项目背景</h2>
<p>制造企业的生产计划、工艺、设备、质量、物料和人员信息往往分散在 ERP、设备系统和纸质记录中。生产计划变化后，现场无法及时同步；设备异常发生后，维修、质量和计划部门缺少统一协同；产品出现问题时，也难以快速追溯原料、工序和设备。智造星图 MES 生产调度与设备诊断平台面向生产现场建立统一的执行与分析平台。</p>
<h2>业务概述</h2>
<p>平台接收订单和生产计划，按照产线、设备、工艺和人员能力生成排产任务。生产过程中记录工序执行、设备状态、物料消耗和质量检验，异常停机或质量超限时触发告警和处置流程。管理人员通过看板查看生产进度、设备健康、良率、在制品和异常趋势，维修人员根据设备手册和历史案例完成诊断和维护。</p>
<p>主要业务场景包括：</p>
<ul>
<li>生产订单、工艺路线、排产计划和工单管理；</li>
<li>设备台账、实时状态、点检、保养和故障诊断；</li>
<li>工序报工、质量检验、不良处理和批次追溯；</li>
<li>物料、人员、产线和现场任务协同；</li>
<li>生产效率、良率、设备利用率和异常趋势分析。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用生产执行、设备接入、质量管理、追溯分析和系统管理分域的架构。生产计划和工单服务处理核心事务，设备接入服务接收实时数据，消息总线传递状态和告警，分析服务生成生产和设备看板，AI 服务辅助查询工艺和维修知识。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建生产、设备、质量、追溯和分析服务；</li>
<li>Spring Security：控制工厂、车间、产线、班组和岗位权限；</li>
<li>MySQL：保存订单、工单、工艺、设备、质量和追溯记录；</li>
<li>MyBatis：支持生产报工、批次查询和复杂统计。</li>
</ul>
<h3>3. 中间件与数据能力</h3>
<ul>
<li>Redis：缓存设备状态、工艺参数、排产计划和看板数据；</li>
<li>MQTT / Kafka / RabbitMQ：接收设备数据并异步传递告警和状态；</li>
<li>Elasticsearch：检索维修记录、工艺文件、质量案例和追溯信息；</li>
<li>XXL-JOB：执行点检、保养、报表和数据归档任务；</li>
<li>Spring AI + 向量库：检索工艺规范、设备手册和维修案例。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 生产计划与调度</h3>
<p>维护订单、工艺路线、产能、设备和班次，根据交期、优先级和资源约束生成排产和工单。</p>
<h3>2. 生产执行与报工</h3>
<p>记录工单接收、开工、暂停、完工、报工、物料消耗和人员操作，确保生产状态及时同步。</p>
<h3>3. 设备管理与诊断</h3>
<p>维护设备台账、点检、保养、维修和故障记录，采集运行指标并生成健康度和诊断建议。</p>
<h3>4. 质量管理与追溯</h3>
<p>配置检验项目、抽检规则和不良等级，关联批次、工序、人员、设备和原料，支持问题反查。</p>
<h3>5. 工艺与知识库</h3>
<p>管理工艺文件、作业指导书、设备手册和质量规范，AI 根据权限检索并解释相关内容。</p>
<h3>6. 看板、告警与系统管理</h3>
<p>展示产量、良率、设备利用率、在制品和异常趋势，支持消息通知、权限控制、日志审计和运维监控。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>通过设备事件、生产工单和质量记录建立统一时间线，支持问题定位和批次追溯；</li>
<li>设备状态更新采用幂等、顺序控制和断线补偿，避免实时数据覆盖或丢失；</li>
<li>排产建议保留人工确认，AI 只提供解释和候选方案，不直接改变生产计划；</li>
<li>通过异步计算和缓存提升大屏、报表和历史追溯查询性能；</li>
<li>将生产现场数据沉淀为可分析指标，帮助企业提升交付、良率和设备利用率。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>MES 连接生产计划、工艺、设备、质量和物料，是最能体现“系统要真正服务现场”的项目类型。你会从订单和排产出发，看到工序执行、设备状态、质量检验、异常停机和产品追溯如何互相影响，并理解数据实时性、流程可视化和业务一致性的重要性。</p>
<ul>
<li>学会拆解生产调度、工艺管理、质量追溯、设备诊断和人员权限。</li>
<li>掌握实时数据接入、任务状态同步、异常告警和报表分析。</li>
<li>能够从业务价值、技术架构和现场落地三个层面讲清 MES 项目。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套从生产计划到成品追溯的制造业业务流程。</li>
<li>一份围绕并发、数据一致性、设备接入和可视化的技术方案。</li>
<li>一套适合中高级 Java 项目的简历描述和面试回答。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>制造企业需要同时管理订单、生产计划、工艺路线、物料、设备、人员和质量标准。计划变更、设备停机、来料异常或质量波动都会影响交付，传统依赖纸面记录和人工沟通的方式难以保证生产现场、计划部门和质量部门使用同一套实时数据。</p>
<p>智造星图 MES 生产调度与设备诊断平台以“订单分解、排产下发、工艺执行、设备采集、质量检验、异常处置、追溯分析”为核心链路，将生产现场数据和管理流程统一起来。AI 负责工艺知识问答、设备故障码解释、异常摘要和排产建议，Java 服务负责工单、批次、质量判定、设备状态和生产数据的正式落库。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/zhizao-mes/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>星购售后智能客服与订单助手</title>
      <link>https://qiuzhiwang.vip/javaai-projects/12-xinggou.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/12-xinggou.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">星购售后智能客服与订单助手</source>
      <description>Java+AI 售后客服与订单助手项目介绍，覆盖订单查询、售后政策、退款退货、FAQ 知识库、工单处理和面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个面向售后和订单服务的智能客服助手，主要处理订单查询、退款退货、发票规则、售后政策和常见问题。AI 负责理解问题、检索知识和生成草稿，Java 负责真正的订单和工单处理。</p>
<h2>项目背景</h2>
<p>门店、外卖、团购和会员业务同时运行时，订单、库存、支付、后厨、桌台和售后容易出现状态不一致。收银员需要快速完成点单和收款，后厨需要准确接收制作任务，顾客需要查询订单、退款和售后进度，总部还要掌握多门店经营情况。星购售后智能客服与订单助手将交易、履约、售后和智能服务连接起来。</p>
<h2>业务概述</h2>
<p>系统接入堂食、外卖、团购和线上商城订单，完成商品、库存、优惠和会员校验后生成统一订单。支付成功后，订单进入后厨制作、出餐、配送或自提流程；退款、退货、缺货和投诉等问题进入售后工单。AI 通过 FAQ、订单查询工具和政策知识库辅助客服快速处理问题，正式退款和库存变更由 Java 业务服务控制。</p>
<p>主要业务场景包括：</p>
<ul>
<li>门店收银、扫码点单、外卖、团购和多渠道订单接入；</li>
<li>商品、库存、优惠、桌台、后厨和出餐状态协同；</li>
<li>聚合支付、退款、结算、对账和异常补偿；</li>
<li>会员、积分、优惠券、售后工单和客户评价；</li>
<li>门店经营报表、异常预警和总部多店管理。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用订单中心、商品中心、库存中心、支付中心、门店中心、售后中心和客服中心拆分的架构。统一网关承接收银端、顾客端、客服端和管理端请求，订单状态通过消息事件同步给后厨、库存、会员和财务系统。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建订单、支付、库存、售后和客服服务；</li>
<li>Spring Security：实现门店、收银员、客服、店长和总部管理员权限；</li>
<li>MySQL：保存商品、订单、支付、库存、会员和售后数据；</li>
<li>MyBatis：支持订单查询、经营统计和批量处理。</li>
</ul>
<h3>3. 中间件与 AI 能力</h3>
<ul>
<li>Redis：缓存商品、库存、桌台、优惠和会话数据；</li>
<li>RabbitMQ / Kafka：异步处理订单事件、支付通知、库存同步和客服消息；</li>
<li>Elasticsearch：检索订单、商品、售后案例和 FAQ；</li>
<li>XXL-JOB：执行对账、自动关单、库存校准和经营报表；</li>
<li>Spring AI：实现政策问答、订单查询、客服摘要和售后分流。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 门店收银与订单中心</h3>
<p>支持堂食、外卖、团购和线上订单统一接入，完成商品、门店、价格、优惠和配送方式校验。</p>
<h3>2. 订单与后厨协同</h3>
<p>管理待支付、已支付、制作中、待取餐、配送中、完成、取消和售后状态，向后厨和配送端推送任务。</p>
<h3>3. 聚合支付与结算</h3>
<p>接入微信、支付宝和第三方渠道，处理支付回调、退款、分账、日结、对账和异常补偿。</p>
<h3>4. 库存、桌台与会员</h3>
<p>维护库存、锁定、扣减、回滚、桌台、会员、积分、优惠券和消费记录。</p>
<h3>5. 售后客服与知识库</h3>
<p>客服查询订单和规则，AI 检索 FAQ、售后政策和历史案例，生成答复草稿并按复杂度转人工。</p>
<h3>6. 总部管理与经营分析</h3>
<p>提供多门店、商品、权限、营业额、客单价、退款率、库存异常和经营趋势分析。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>通过订单状态机和事件幂等保证收银、支付、后厨和售后状态一致；</li>
<li>支付回调、退款和库存扣减使用唯一业务号、分布式锁和补偿任务防止重复执行；</li>
<li>高峰期采用缓存、消息队列和读写分离提升收银和订单查询能力；</li>
<li>AI 只读取授权订单和知识，退款、改价、库存变更必须经过 Java 服务校验；</li>
<li>通过经营报表和异常预警帮助门店发现缺货、退款、出餐和服务质量问题。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>售后客服与订单助手把门店、顾客、支付、库存、后厨和客服连接在一起，任何一个状态不同步都可能影响用户体验。你会从订单查询、退款售后、规则解释和工单协同切入，理解电商与线下门店业务如何共用一套可靠的订单和服务底座。</p>
<ul>
<li>学会处理多渠道订单、支付回调、库存扣减、退款和售后状态流转。</li>
<li>掌握 FAQ / RAG 问答、订单查询工具、客服摘要和人工转接。</li>
<li>理解高频交易场景下的幂等、补偿、消息可靠投递和权限审计。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套从下单、履约到售后闭环的项目故事线。</li>
<li>一份客服 AI 与交易系统结合的技术难点清单。</li>
<li>一套适合初中高级 Java 开发者的项目讲解与简历亮点。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>电商售后问题通常与订单状态、支付方式、物流节点、商品类目和售后政策直接相关。用户希望快速知道“能不能退、退到哪一步、什么时候到账”，客服则需要在订单系统、仓储系统和政策文档之间来回查询。纯 FAQ 只能回答固定问题，直接让模型调用订单接口又会带来权限和误操作风险。</p>
<p>星购售后智能客服与订单助手将订单查询、售后规则、退款退货、发票服务、客服工单和用户通知整合起来。AI 负责识别意图、补齐查询条件、检索政策和生成回复草稿；Java 后端通过订单、售后和支付服务完成权限校验、状态判断、幂等控制和正式写操作，形成“问答解释 + 业务办理 + 工单兜底”的完整链路。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/xinggou/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>数策 ERP 财务报表分析助手</title>
      <link>https://qiuzhiwang.vip/javaai-projects/13-shuce-erp.html</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/13-shuce-erp.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">数策 ERP 财务报表分析助手</source>
      <description>Java+AI ERP 财务报表分析项目介绍，覆盖经营指标、指标口径、财务制度问答、权限审计、RAG 和面试表达。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<h2>项目定位</h2>
<p>这是一个面向 ERP 和财务分析场景的助手，帮助用户更快理解报表、经营指标、财务制度和预算口径。AI 负责解释和归纳，Java 负责权限、流程和数据落库。</p>
<h2>项目背景</h2>
<p>集团企业的财务数据通常来自总账、应收应付、采购、销售、库存、预算和多个子公司系统。管理者需要查看利润、现金流、费用、预算执行和经营指标，但不同系统的数据口径、期间和权限并不一致，人工合并报表耗时长，差异也难以追溯。数策 ERP 财务报表分析助手将财务数据、指标规则、制度知识和组织权限统一起来。</p>
<h2>业务概述</h2>
<p>平台接收业务系统和子公司的财务数据，进行格式校验、科目映射、规则匹配和凭证审核。系统自动对账并识别差异，完成集团数据合并后生成利润表、资产负债表、现金流量表和经营分析报表。用户可以用自然语言查询指标、解释口径和查看明细，AI 输出必须关联数据来源、计算期间和权限范围。</p>
<p>主要业务场景包括：</p>
<ul>
<li>子公司、业务系统和 Excel 财务数据接入；</li>
<li>科目、指标、公式、组织和会计期间的统一管理；</li>
<li>凭证生成、审核、自动对账和差异处理；</li>
<li>集团数据合并、报表生成、预算执行和经营分析；</li>
<li>财务制度问答、自然语言问数、报表解释和审计追踪。</li>
</ul>
<h2>技术架构选型</h2>
<h3>1. 整体架构</h3>
<p>采用数据接入、规则中心、凭证中心、对账中心、报表中心、分析中心和权限审计分层的架构。数据接入服务负责多来源数据标准化，指标服务负责口径和计算，报表服务负责查询与导出，AI 服务负责受控问数和结果解释。</p>
<h3>2. 后端技术</h3>
<ul>
<li>Spring Boot / Spring Cloud Alibaba：构建 ERP 财务业务服务；</li>
<li>Spring Security：实现集团、子公司、部门、岗位和数据范围权限；</li>
<li>MySQL / Oracle：保存凭证、科目、指标、报表和审计数据；</li>
<li>MyBatis：支持复杂财务查询、合并计算和批量处理。</li>
</ul>
<h3>3. 中间件与 AI 能力</h3>
<ul>
<li>Redis：缓存指标定义、组织权限和热点报表；</li>
<li>MQ：异步处理数据导入、报表生成、对账和通知；</li>
<li>Elasticsearch：检索凭证、制度、报表和经营明细；</li>
<li>XXL-JOB：执行月结、对账、合并、报表和归档任务；</li>
<li>RAG + Tool Calling：检索制度和指标口径，生成经过校验的查询条件。</li>
</ul>
<h2>主要业务模块</h2>
<h3>1. 数据接入与合并</h3>
<p>接收业务系统、子公司和 Excel 数据，完成格式校验、科目映射、重复检测、批量入库和错误反馈。</p>
<h3>2. 报表设计与指标中心</h3>
<p>配置报表维度、指标公式、数据来源、计算周期、版本和责任人，支持拖拽设计和权限化发布。</p>
<h3>3. 规则配置与凭证管理</h3>
<p>维护会计规则、审批规则、凭证模板和审核流程，记录凭证生成、修改、审核和作废历史。</p>
<h3>4. 自动对账</h3>
<p>按金额、科目、期间、单据号和业务来源进行匹配，识别未达账、重复账和差异项，支持人工确认和调整。</p>
<h3>5. 数据分析与自然语言问数</h3>
<p>支持同比、环比、预算执行、区域、产品和客户维度分析，AI 将问题转换为受控查询意图并返回指标解释。</p>
<h3>6. 检索、调度与安全审计</h3>
<p>提供凭证、制度、报表和明细检索，异步执行大报表导出和月结任务，记录查看、分享、导出和权限变更行为。</p>
<h2>技术要点与应用价值</h2>
<ul>
<li>每个指标保存定义、公式、数据来源、生效版本和责任人，保证口径统一；</li>
<li>自然语言只生成经过校验的查询意图，真实数据访问通过权限服务和参数化 SQL 执行；</li>
<li>大范围、多维度报表通过 MQ 和 XXL-JOB 异步生成，支持进度、重试和失败补偿；</li>
<li>分析摘要关联原始报表、过滤条件和计算周期，财务人员可快速回到明细复核；</li>
<li>通过集团合并、自动对账和经营分析减少人工整理，让财务数据更快服务管理决策。</li>
</ul>
<h2>陪跑价值与学习收获</h2>
<p>ERP 财务分析真正难的地方，是同一个指标可能有不同口径、不同组织权限和不同统计周期。你会围绕报表、指标、预算、制度和经营分析搭建一条可复核的数据链路，理解 AI 如何帮助管理者问数和读报表，同时保证正式数据仍由规则、权限和审计体系说了算。</p>
<ul>
<li>学会设计指标定义、公式版本、数据权限、报表钻取和异步导出。</li>
<li>掌握财务制度 RAG、自然语言问数、受控 SQL 和异常指标摘要。</li>
<li>能够回答数据安全、口径一致、结果复核和大报表性能优化等问题。</li>
</ul>
<h2>你将带走的成果</h2>
<ul>
<li>一套从数据汇总到报表解释、经营洞察和复核留痕的业务流程。</li>
<li>一份 ERP + AI 项目中权限、指标、检索和异步任务的技术方案。</li>
<li>一套能够体现企业级数据思维的简历与面试表达。</li>
</ul>
<h2>业务背景与建设目标</h2>
<p>ERP 中的财务数据通常来自总账、应收应付、采购、销售、库存和预算等多个模块。管理者关心收入、毛利、现金流、费用率和预算执行，但不同部门对指标口径、统计周期和数据权限的理解可能不一致。单纯把报表导出后交给人工分析，难以做到实时解释、统一口径和可追溯复核。</p>
<p>数策 ERP 财务报表分析助手将经营指标、报表数据、财务制度、预算规则和组织权限统一组织起来，形成“指标查询、口径解释、报表钻取、异常发现、经营摘要、人工复核”的分析链路。AI 用于自然语言问数、制度问答、指标解释和分析摘要，Java 服务负责数据权限、指标计算、查询审计和正式报表生成。</p>
<h2>业务模块与关键流程</h2>
<p>| 业务模块 | 核心能力 | Java / AI 落点 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/project-diagrams/shuce-erp/00-main.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>Java+AI 项目介绍 | 企业级项目库</title>
      <link>https://qiuzhiwang.vip/javaai-projects/</link>
      <guid>https://qiuzhiwang.vip/javaai-projects/</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java+AI 项目介绍 | 企业级项目库</source>
      <description>Java 企业级项目总览，覆盖研发管理、客服工单、智慧园区、升学咨询、园区运营、快递治理、信贷风控、充电运维、CRM、AIOps、MES、售后客服与 ERP 分析。</description>
      <category>Java+AI项目</category>
      <content:encoded><![CDATA[
<p>需要直接运行源码，可以先使用<a href="/projects/knowledge-service.html" target="_blank">公开知识库检索服务</a>。本栏目用于选择行业场景、理解业务和架构；代码项目的实现及测试范围在各自说明中列出。</p>
<p>这是一套面向 Java 后端、Java AI 应用开发和求职表达的企业级项目库。重点不是堆技术名词，而是帮助你讲清楚业务场景、核心流程、Java 后端职责、AI 接入点、权限审计和落地边界。</p>
<p>以下内容展示的是部分公开项目。实际项目库不止这些，更多未公布项目会根据你的工作年限、岗位方向、简历背景和面试目标单独匹配。</p>
<h2>进入项目陪跑前，你会先看到什么</h2>
<p>这套项目库服务于想把 Java 后端、AI 应用和真实业务表达串起来的开发者。每个项目都按“业务背景 → 核心流程 → Java 技术落点 → AI 使用边界 → 难点与面试表达”组织，先让你看懂业务，再带你拆解系统，最后把项目讲到面试官愿意继续追问的程度。</p>
<p>你会看到的不是孤立的技术名词，而是可以被验证、被演示、被写进简历的完整项目故事：谁在使用系统，为什么要做这套系统，数据如何流转，哪些动作必须由后端控制，AI 在哪里提升效率，以及系统遇到异常时如何兜底。</p>
<p>每个项目详情页都按项目资料文档展开：项目背景、业务概述、技术架构选型、主要业务模块、技术要点与应用价值，并配套主流程图、模块流程图、面试讲法和简历表达。</p>
<h3>通过陪跑，你会获得</h3>
<ul>
<li><strong>一套真实业务视角</strong>：从用户、角色、流程和业务目标出发，建立项目全貌。</li>
<li><strong>一套可落地的技术路径</strong>：把 Spring Boot、微服务、Redis、MQ、Elasticsearch、RAG、Agent 等技术放到具体模块里理解。</li>
<li><strong>一套可复用的面试表达</strong>：能用清晰的结构讲背景、职责、难点、结果和取舍。</li>
<li><strong>一套可以持续升级的作品</strong>：从基础版本开始，逐步补齐权限、审计、监控、AI 和工程化能力。</li>
</ul>
<p>如果你正在准备 Java 后端、Java+AI、企业应用开发或中高级岗位，这里可以作为选项目、拆项目和准备面试的入口。先选择一个与你目标岗位接近的场景，再沿着流程图和模块说明逐步深入。</p>
<h2>按人群选项目</h2>
<p>| 人群 | 推荐项目 | 选择理由 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/xiangmubiaodafa.png" type="image/png"/>
    </item>
    <item>
      <title>并发多线程</title>
      <link>https://qiuzhiwang.vip/javabgw/bfdxc.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/bfdxc.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">并发多线程</source>
      <description>围绕线程状态、可见性、CAS、锁、等待通知和线程池，解释 Java 并发中的协作机制与失败边界。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>建议按“线程基础 -&gt; 并发三特性 -&gt; 锁与 AQS -&gt; 线程池 -&gt; 线上排障”顺序复习。<br>
面试回答时尽量带一个真实场景（如高并发下延迟抖动、线程池打满、锁竞争）。</p>
</div>
<p>本文以 <strong>Java SE 21</strong> 的规范和 API 为基准；基础 JMM、监视器和并发工具的语义也适用于 JDK 8/17。虚拟线程仅讨论 JDK 21，HotSpot 锁优化属于实现细节，需区分版本。</p>
<h2>推荐训练法</h2>
<ol>
<li>先过题目清单，确认知识覆盖面。</li>
<li>每题先用 20~40 秒口述结论，再补机制细节。</li>
<li>对高频题（CAS、synchronized、线程池）做二次复述。</li>
</ol>
<h2>1、什么是进程、线程、协程？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>进程提供资源与地址空间隔离；平台线程由操作系统调度，Java 虚拟线程由 JVM 调度到承载它的平台线程上。</li>
<li>协程通常是用户态轻量执行单元；具体调度和切换成本取决于实现，不能直接等同于 Java 线程。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>一个进程可包含多个线程，线程共享进程资源但执行路径独立。</li>
<li>协程常用于高并发 IO 场景，通过用户态调度减少线程切换开销。</li>
</ul>
<h2>2、线程状态有哪些？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>Java 线程状态：<code>NEW</code>、<code>RUNNABLE</code>、<code>BLOCKED</code>、<code>WAITING</code>、<code>TIMED_WAITING</code>、<code>TERMINATED</code>。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li><code>BLOCKED</code> 专指等待进入或重新进入对象监视器（<code>synchronized</code>），不是所有等锁场景；<code>ReentrantLock</code> 竞争时通常通过 park 进入 <code>WAITING</code> 或 <code>TIMED_WAITING</code>。</li>
<li><code>RUNNABLE</code> 包含正在执行和等待处理器等操作系统资源的情形，不能仅凭它判断 CPU 正忙。状态定义见 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.State.html" target="_blank" rel="noopener noreferrer">Thread.State（Java 21）</a>。</li>
</ul>
<h2>3、创建线程有哪些方式？</h2>
<ol>
<li>继承 <code>Thread</code>。</li>
<li>实现 <code>Runnable</code>。</li>
<li>实现 <code>Callable</code>，交给执行器提交并通过 <code>Future</code> 取结果；<code>Callable</code> / <code>Future</code> 本身不会创建或启动线程。</li>
<li>通过 <code>ExecutorService</code> 提交任务，由具体执行器决定线程管理方式。</li>
</ol>
<p>JDK 21 可使用 <code>Thread.ofVirtual().start(task)</code> 或 <code>Executors.newVirtualThreadPerTaskExecutor()</code>。虚拟线程适合大量等待 IO 的任务，不会让 CPU 计算本身变快，也不应池化；下游并发限制可用 <code>Semaphore</code>。见 <a href="https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html" target="_blank" rel="noopener noreferrer">Oracle 虚拟线程指南</a>。</p>
<h2>4、并发三大特性是什么？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>原子性：操作不可分割。</li>
<li>可见性：通过 happens-before 等同步关系，保证一个线程的写入对另一个线程的相应读取可见，不是任意共享变量都能“立即看到更新”。</li>
<li>有序性：程序执行顺序受约束。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li><code>synchronized</code> 可保证原子性和可见性；<code>volatile</code> 保证可见性并约束重排序。</li>
</ul>
<p>机制上，同一监视器的解锁 happens-before 后续加锁；对同一 <code>volatile</code> 变量的写 happens-before 后续读，并通过传递性发布此前写入的普通字段。这不意味着所有指令都禁止重排，而是重排不能破坏规范保证的可观察行为。<code>count++</code> 包含读、加一、写回，即使 <code>count</code> 为 volatile，多个线程仍可能覆盖结果；计数可用 <code>AtomicInteger.incrementAndGet()</code>。见 <a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-17.html#jls-17.4.5" target="_blank" rel="noopener noreferrer">JLS 21 §17.4.5</a>。</p>
<h2>5、什么是 CAS？优缺点是什么？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>CAS：比较并交换，原子指令，常用于无锁并发。</li>
<li>优点：可用于实现无锁更新，避免为单次更新挂起线程；吞吐优势取决于竞争程度。</li>
<li>缺点：可能遇到 ABA，重试循环会消耗 CPU；单次 CAS 更新一个位置，多字段不变量需锁或将不可变状态封装后用 <code>AtomicReference</code> 整体替换。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li><code>compareAndSet</code> 失败返回 <code>false</code>，不会自行无限重试；调用方或上层原子更新方法决定是否循环。竞争激烈时应评估重试成本。见 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/atomic/package-summary.html" target="_blank" rel="noopener noreferrer">atomic 包说明</a>。</li>
</ul>
<h2>6、什么是 ABA 问题？如何解决？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>值从 A -&gt; B -&gt; A，CAS 看起来没变但过程已变。</li>
<li>可用版本号机制解决，如 <code>AtomicStampedReference</code>。</li>
</ul>
<h2>7、<code>synchronized</code> 的底层和使用范围</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>修饰实例方法：锁对象实例。</li>
<li>修饰静态方法：锁 Class 对象。</li>
<li>修饰代码块：锁指定对象。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li><code>synchronized</code> 是可重入、独占的监视器同步；正常返回或异常退出同步区域都会释放监视器。HotSpot 的锁表示与优化不是语言保证，不能把“偏向锁 → 轻量级锁 → 重量级锁”当成所有 JDK 的固定流程。JDK 15 已默认禁用并弃用偏向锁，见 <a href="https://www.oracle.com/java/technologies/javase/15all-relnotes.html" target="_blank" rel="noopener noreferrer">Oracle JDK 15 发布说明</a>。</li>
</ul>
<h2>8、<code>volatile</code> 能保证什么？不能保证什么？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>能保证可见性和一定有序性。</li>
<li>不能保证复合操作原子性（如 <code>count++</code>）。</li>
</ul>
<h2>9、<code>ReentrantLock</code> 和 <code>synchronized</code> 有什么区别？</h2>
<ol>
<li><code>ReentrantLock</code> 支持可中断加锁、超时尝试、公平锁。</li>
<li><code>synchronized</code> 语法简单，JVM 层优化充分。</li>
<li>复杂并发控制（多条件队列）更适合 <code>ReentrantLock</code>。</li>
</ol>
<h2>10、什么是 AQS？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>AQS（AbstractQueuedSynchronizer）是并发同步框架核心。</li>
<li>通过原子更新的 <code>int state</code> 与 FIFO 等待队列组织竞争；子类定义获取/释放状态的规则，AQS 负责排队、阻塞与唤醒。常说的 CLH 队列是其队列设计来源，不能据此断言锁一定公平。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li><code>ReentrantLock</code>、<code>Semaphore</code>、<code>CountDownLatch</code> 的内部同步器都基于 AQS。入队不等于严格按先后获得锁，是否允许新线程插队取决于获取逻辑。见 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/locks/AbstractQueuedSynchronizer.html" target="_blank" rel="noopener noreferrer">AQS API</a>。</li>
</ul>
<h2>11、<code>wait</code> / <code>notify</code> / <code>notifyAll</code> 和 <code>sleep</code> 的区别</h2>
<ol>
<li><code>wait</code> 必须在持有该对象监视器时调用（同步方法或同步块均可），只释放该对象的监视器，不释放持有的其他锁。</li>
<li><code>sleep</code> 让当前线程暂停一段时间，不释放已持有的监视器，也不提供同步可见性保证。</li>
<li><code>notify</code> 任意选择一个等待线程，不承诺随机或公平；<code>notifyAll</code> 通知全部等待线程。两者同样要求持有该对象监视器，且不会立刻释放它。被通知线程重新获得监视器后才能从 <code>wait</code> 返回。</li>
</ol>
<p>等待必须放在条件循环中：虚假唤醒或其他线程抢先消耗条件，都要求重新判断。以下为机制片段，<code>ready</code> 的读写均须由同一个 <code>monitor</code> 保护，调用方法需声明或处理 <code>InterruptedException</code>：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 等待方</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">synchronized</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> (monitor) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    while</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">!</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">ready) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        monitor</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">wait</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    }</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // 在锁内处理依赖 ready 的共享状态</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 通知方</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">synchronized</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> (monitor) {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    ready </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    monitor</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">notifyAll</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>见 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#wait()" target="_blank" rel="noopener noreferrer">Object.wait / notify（Java 21）</a>。</p>
<h2>12、<code>CountDownLatch</code>、<code>CyclicBarrier</code>、<code>Phaser</code> 的区别</h2>
<ul>
<li><code>CountDownLatch</code>：一次性门闩，常用于主线程等待子任务结束。</li>
<li><code>CyclicBarrier</code>：可复用屏障，常用于多线程分阶段对齐。</li>
<li><code>Phaser</code>：更灵活，支持动态注册与多阶段同步。</li>
</ul>
<h2>13、<code>Semaphore</code> 适合什么场景？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>控制同时访问资源的数量，典型场景是连接池资源保护。它限制的是并发数，不直接保证每秒请求数；成功获取许可后应在 <code>finally</code> 中释放。</li>
</ul>
<h2>14、<code>ThreadLocal</code> 的作用和风险</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>作用：线程隔离上下文（如用户信息、追踪ID）。</li>
<li>风险：在线程池中不清理会导致上下文串到后续任务，也可能长期保留对象。请求上下文宜在 <code>try/finally</code> 中 <code>remove()</code>；线程隔离只隔离变量绑定，若多个线程绑定同一个可变对象，对象本身仍需同步。</li>
</ul>
<h2>15、为什么要用线程池？</h2>
<ol>
<li>降低线程创建/销毁开销。</li>
<li>控制并发度，保护系统资源。</li>
<li>便于统一监控和治理。</li>
</ol>
<h2>16、线程池核心参数有哪些？</h2>
<ul>
<li><code>corePoolSize</code></li>
<li><code>maximumPoolSize</code></li>
<li><code>keepAliveTime</code> 及其时间单位 <code>unit</code></li>
<li><code>workQueue</code></li>
<li><code>threadFactory</code></li>
<li><code>RejectedExecutionHandler</code></li>
</ul>
<p>在运行状态下，<code>execute</code> 的核心顺序是：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>少于 corePoolSize → 尝试新建工作线程</span></span>
<span class="line"><span>否则 → 尝试入队</span></span>
<span class="line"><span>入队失败 → 尝试扩至 maximumPoolSize</span></span>
<span class="line"><span>仍无法接收（或池已关闭）→ 拒绝处理</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>入队后还需复查池状态，避免关闭竞态漏处理任务。无界队列通常让任务积压而非触发扩容，因此仅调大 <code>maximumPoolSize</code> 往往无效；有界队列才能同时约束积压量。默认仅超出核心数的空闲线程超时退出，开启 <code>allowCoreThreadTimeOut(true)</code> 且超时为正数后核心线程也可退出。详见 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html" target="_blank" rel="noopener noreferrer">ThreadPoolExecutor 官方说明</a>。</p>
<h2>17、线程池拒绝策略有哪些？</h2>
<ol>
<li><code>AbortPolicy</code>：直接抛异常。</li>
<li><code>CallerRunsPolicy</code>：池未关闭时由提交线程执行，被关闭后丢弃任务。</li>
<li><code>DiscardOldestPolicy</code>：池未关闭时移除队列头任务，再尝试提交新任务；并非丢弃正在执行或全局最旧的任务。</li>
<li><code>DiscardPolicy</code>：静默丢弃被拒绝的任务。</li>
</ol>
<p>静默丢弃通过 <code>submit</code> 提交的任务可能让对应 <code>Future</code> 一直无法完成，不能再无期限等待它。</p>
<h2>18、线程池参数怎么估算？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>CPU 密集：线程数接近 CPU 核数。</li>
<li>IO 密集的平台线程池可按等待/计算时间比例估算初值；“2N”没有通用保证，还受下游容量、连接数与内存约束。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>实际工程要结合压测、队列积压、RT 与 CPU 使用率动态调参。</li>
</ul>
<h2>19、JMM 是什么？为什么需要它？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>JMM 是 Java 内存访问规则，统一不同硬件平台的并发语义。</li>
<li>解决可见性、有序性和跨平台一致性问题。</li>
</ul>
<h2>20、乐观锁和悲观锁怎么理解？</h2>
<ul>
<li>悲观锁：先加锁再操作，冲突场景更稳。</li>
<li>乐观并发控制：读取状态并计算候选结果，再通过 CAS 或版本条件原子地校验并提交；失败后重试或放弃。不能先修改共享数据再事后校验，也不保证读多写少时一定更快。</li>
</ul>
<h2>21、如何定位线上并发问题？</h2>
<ol>
<li>先看监控：线程数、CPU、队列长度、拒绝次数。</li>
<li>再看 JVM：<code>jstack</code>、<code>jcmd</code>、GC 日志。</li>
<li>最后看代码：锁粒度、线程池配置、共享变量访问。</li>
</ol>
<h2>22、面试表达模板（可直接复述）</h2>
<ul>
<li>我会先判断是“计算瓶颈”还是“并发控制瓶颈”，再决定优先调线程池还是降锁竞争。</li>
<li>对线程池我会重点盯队列积压和拒绝策略，避免任务堆死。</li>
<li>对锁问题我会通过线程 dump 定位阻塞链，再做锁粒度优化或改无锁结构。</li>
</ul>
<h2>继续阅读与资料</h2>
<p>继续追问：<a href="/guides/thread-pool-overload.html" target="_blank">线程池为何不扩容，队列与拒绝如何影响调用方</a>。</p>
]]></content:encoded>
    </item>
    <item>
      <title>场景题</title>
      <link>https://qiuzhiwang.vip/javabgw/cjt.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/cjt.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">场景题</source>
      <description>分析库存扣减、超时取消、消息确认、缓存更新和慢请求等后端场景，结合 SQL 与时序解释一致性取舍。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>建议先看题目目录，再按“概念 -&gt; 原理 -&gt; 场景 -&gt; 优化”顺序复习。<br>
每题先讲结论，再补关键机制和项目实践，回答会更稳。</p>
</div>
<p>本文的具体机制以 MySQL 8.4 / InnoDB、Redis 7.x、RabbitMQ 4.4、Maven 3.9.x 和 Caffeine 3.x（Java 11+）为适用范围；其他组件需结合所用版本确认 API 与投递语义。代码为机制示意，未在数据库或中间件环境运行。</p>
<h2>1、如何设计一个订单号生成服务</h2>
<ul>
<li><strong>看数据量的大小：</strong>在设计订单号生成服务时，需要充分考虑数据量的大小，以确定生成的ID所需的位数。这可以避免位数不足导致存储问题，同时确保足够的位数以防止影响索引检索效率。</li>
<li><strong>不重复、唯一性：</strong>订单号应有数据库唯一约束兜底。UUID 是概率唯一；Snowflake 依赖节点编号不冲突，并处理时钟回拨和序列耗尽；数据库序列或 Redis 自增还需评估故障恢复、持久化和主从切换时的重复风险，不能仅凭算法名称承诺全局唯一。</li>
<li><strong>考虑高可用：</strong>通过多节点部署、负载均衡、健康检查等技术手段，确保服务在面对故障时能够保持可用状态，避免影响其他服务。</li>
<li><strong>易于理解：</strong>为了方便排查问题，需要考虑到订单号的格式和组成，可以使用时间戳、随机数、用户ID等信息来拼接订单号。</li>
<li><strong>安全性：</strong>不能让别人通过订单号看出公司的经营状况，比如你的订单就是流水号的话，那么别人就可以从订单号推测出你公司的整体运营状况。</li>
</ul>
<h2>2、订单超时自动取消功能如何设计</h2>
<ul>
<li><strong>定时任务：</strong>通过定时任务隔一段时间扫表，对于过期超时的订单数据更新状态为已取消。这种方式容易实现；缺点就是定时任务是固定时间间隔的，所以一些订单并不能及时到点的去立马取消，可能过期了一两分钟才被定时任务扫描到；而且这种方式也增加了数据库的压力，当表里数据越来越多之后，扫描的时间也会越来越长。所以这种方式适合对时间要求不高的，数据量也不是很多的业务场景。</li>
<li><strong>DelayQueue延迟队列：</strong>DelayQueue是JDK提供的一个无界队列，订单生成以后，设置过期时间放入定义好的DelayQueue，然后创建一个线程，在线程中通过<code>while(true)</code>不断的从DelayQueue中获取过期的数据。这种方式不依赖数据库，也不依赖其他组件，实现也方便；但是DelayQueue是一个无界队列，如果放入的订单数据过多，就会造成OOM。所以这种方式适合数据量比较小的，万一丢失了影响也不大的业务场景。</li>
<li><strong>Redisson：</strong><code>RDelayedQueue</code> 可将任务延迟转入目标队列，具体 API 以项目使用的 Redisson 版本为准。Lua 能保证单次脚本执行期间不被其他命令穿插，但不能保证业务处理不重复；消费者仍需处理重试、宕机与幂等。</li>
<li><strong>MQ延迟消息：</strong>订单创建后发送延迟任务，到期再查数据库中的订单状态。RocketMQ 的延迟能力和精度取决于版本；RabbitMQ 可用 TTL + 死信交换机构造延迟链路，不一定需要插件。容量取决于部署、消息大小和保留时间，不能笼统承诺“万亿级”。需处理订单落库与发消息之间的失败窗口，例如事务内写 outbox，再可靠投递、重试和对账。</li>
<li><strong>MQ的死信队列：</strong>RabbitMQ 4.4 中，消息 TTL 到期、长度超限或消费者拒绝且不重新入队等情形可触发死信；需配置死信交换机及路由。延迟队列中放的是超时检查任务，支付行为不直接消费这条任务。不同 TTL 混排时，过期消息可能要到队首才被清理和死信化，不能当作精确定时器。参见 <a href="https://www.rabbitmq.com/docs/ttl" target="_blank" rel="noopener noreferrer">RabbitMQ TTL</a>。</li>
<li><strong>Redis过期监听：</strong><code>notify-keyspace-events Ex</code> 开启过期事件通知，但 Redis 7.x 的通知基于 Pub/Sub，断线期间消息会丢失；事件在实际删除键时产生，不保证 TTL 到点立即触发，且只携带键名。适合作为辅助信号，不能作为取消订单的唯一可靠来源。参见 <a href="https://redis.io/docs/latest/develop/pubsub/keyspace-notifications/" target="_blank" rel="noopener noreferrer">Redis 键空间通知</a>。</li>
</ul>
<p>超时任务可能重复或晚到，支付回调也可能同时执行。以 MySQL 8.4 / InnoDB 为例，把状态与截止时间放进同一条条件更新：</p>
<div class="language-sql line-numbers-mode" data-highlighter="shiki" data-ext="sql" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-sql"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">UPDATE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> orders</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">SET</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> status</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'CANCELLED'</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">WHERE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> id </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ? </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">AND</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> status</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'PENDING'</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> AND</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> expire_at </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">&#x3C;=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> CURRENT_TIMESTAMP;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>只有影响行数为 1 才是本次成功取消；支付回调也须从 <code>PENDING</code> 条件迁移，避免先读后写覆盖终态。若取消还要释放库存，可在同一数据库事务内完成，跨服务则记录可靠事件并幂等执行。支付平台已扣款而本地订单已取消时，还需查单和退款补偿。定期按状态、到期时间索引扫描作为兜底。InnoDB 的更新会加锁并判断更新条件，参见 <a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-locking-reads.html" target="_blank" rel="noopener noreferrer">锁定读与并发更新</a>。</p>
<h2>3、如何设计一个购物车功能</h2>
<ul>
<li>购物车系统的主要功能是在用户选择商品后，在下单之前保存用户的意向商品、数量等信息，以便用户进行统一支付。购物车的操作主要包括加入购物车、查看购物车以及通过购物车下单等。</li>
<li>一般情况下，在存储购物车信息时，并不需要保存商品的所有详细信息，只需记录一个SKU ID，并加上数量、时间等字段即可。商品的库存、价格、介绍等信息可以在渲染购物车时实时查询和计算。</li>
<li>对于未登录用户，购物车信息可以临时缓存在客户端，使用Cookie或LocalStorage等技术进行存储。以下是一个JSON格式的未登录用户购物车存储示例：</li>
</ul>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>{</span></span>
<span class="line"><span>    "cart": [</span></span>
<span class="line"><span>        {</span></span>
<span class="line"><span>            "SKUID": 10086,</span></span>
<span class="line"><span>            "timestamp": 1666513990,</span></span>
<span class="line"><span>            "count": 2</span></span>
<span class="line"><span>        },</span></span>
<span class="line"><span>        {</span></span>
<span class="line"><span>            "SKUID": 10010,</span></span>
<span class="line"><span>            "timestamp": 1666513990,</span></span>
<span class="line"><span>            "count": 10</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>    ]</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>已登录用户的购物车需要使用持久化存储，可以选择数据库或Redis缓存。如果使用数据库，可以创建表格存储<code>user_id</code>、<code>sku_id</code>、<code>count</code>、<code>time_stamp</code>等业务字段。如果使用Redis，可以在未登录用户购物车的基础上添加<code>user_id</code>作为key，如下所示：</li>
</ul>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>{</span></span>
<span class="line"><span>    "KEY": 12343123,</span></span>
<span class="line"><span>    "VALUE": [</span></span>
<span class="line"><span>         {</span></span>
<span class="line"><span>            "SKUID": 10086,</span></span>
<span class="line"><span>            "timestamp": 1666513990,</span></span>
<span class="line"><span>            "count": 2</span></span>
<span class="line"><span>        },</span></span>
<span class="line"><span>        {</span></span>
<span class="line"><span>            "SKUID": 10010,</span></span>
<span class="line"><span>            "timestamp": 1666513990,</span></span>
<span class="line"><span>            "count": 10</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>    ]</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>使用Redis和数据库各有优势：Redis性能更高，响应时间更短，能支撑更多并发请求；而MySQL具有较高的数据可靠性和支持丰富的查询方式和事务机制。</li>
</ul>
<h2>4、如果让你设计一个秒杀系统，怎么设计？</h2>
<p>一个秒杀系统面临着多个独特的挑战，设计的时候应该考虑以下问题：</p>
<ol>
<li><strong>高并发瞬时流量：</strong></li>
</ol>
<p>在处理秒杀场景的高并发瞬时流量时，系统的稳定性和可用性成为关键挑战。通常，我们会在整体架构设计上采取逐层的流量过滤策略，确保系统能够有效处理巨大的请求压力。</p>
<p>客户端可通过按钮防重复点击、退避重试等方式减少无效请求，但攻击者能绕过客户端，真正的准入控制和限流必须由网关及服务端执行。</p>
<p>接下来，通过CDN（内容分发网络）和Nginx进行统一接入，不仅可以实现负载均衡和流量分发，还可以配置黑白名单、IP限流等策略，进行更细粒度的流量过滤。</p>
<p>在服务端，通过Nginx配置的流量过滤和前端的丢弃策略，可以减轻服务端的压力。服务器层面也可以配置各种限流策略，例如基于Sentinel的动态限流，自定义的限流算法等。</p>
<p>此外，针对一些查询操作和写操作，可以使用缓存来提高性能。在缓存方面，本地缓存的响应速度更快，近端缓存效果优于远端缓存，可以有效抵挡部分请求。</p>
<p>由于秒杀业务本身允许一定的失败率，通过层层过滤，我们可以在前端、Nginx、服务器等层次上过滤更多的流量，提升系统的稳定性，确保在高并发情况下仍能提供可用的服务。</p>
<ol start="2">
<li><strong>热点数据：</strong></li>
</ol>
<p>秒杀系统常面临热点数据问题，特别是在大家抢购同一商品时，该商品成为热点数据。这带来了高频的查询请求和非常频繁的写请求，尤其是在库存扣减等操作上。</p>
<p>针对热点数据，主要的解决方案包括拆分和缓存。</p>
<p>首先是拆分，即将热点数据分割成多个相对冷门的数据，并通过负载均衡将不同请求分散到不同的数据上，以降低单一数据的请求压力。</p>
<p>其次是利用缓存。通常，对于秒杀业务，可以提前预知哪些数据会成为热点，因此可以提前进行缓存的预热。这不仅包括在Redis等缓存中的预热，还应在本地缓存上执行预热，以避免Redis的热key问题。在处理热点数据时，充分利用缓存可以有效减轻数据库的负担，提高系统的响应速度。</p>
<p>通过拆分和缓存的组合应用，能够有效缓解热点数据带来的高并发压力，提升系统的稳定性和性能。</p>
<ol start="3">
<li><strong>数据量大：</strong></li>
</ol>
<p>秒杀系统经常面临高频的下单操作，导致最终产生的订单量巨大，进而带来了查询效率低的问题。</p>
<p>针对这个挑战，有几种可行的解决方案：</p>
<ul>
<li><strong>加缓存：</strong> 在查询订单时可以引入缓存，将常被查询的订单数据缓存在内存中，以提高查询效率。可以使用分布式缓存如Redis等。</li>
<li><strong>使用Elasticsearch（ES）：</strong> 当订单数据量庞大时，可以考虑使用Elasticsearch进行存储和检索。ES对于大规模文档的全文搜索和分析非常高效，适用于订单数据的查询。</li>
<li><strong>分库分表：</strong> 当单一数据库无法承载巨大的订单数据时，可以考虑采用分库分表的方式，将订单数据分散存储在多个数据库或表中，提高查询性能。</li>
<li><strong>数据归档：</strong> 对于历史订单数据，可以实施数据归档策略，将过早的历史数据归档到冷数据存储中，减轻热数据存储的压力。这样可以保证查询性能，同时降低存储成本。</li>
</ul>
<p>选择适当的方案取决于具体业务需求和系统架构。可能需要综合考虑缓存的实时性、ES的搜索能力、分库分表的扩展性以及数据归档的存储成本等因素。</p>
<ol start="4">
<li><strong>库存的正确扣减：</strong></li>
</ol>
<p>在秒杀业务中，由于高频的并发库存扣减操作，提升库存扣减的性能并确保准确性是一项极为重要的任务。稍有不慎可能导致超卖、少卖等严重问题。</p>
<p>为了解决这个问题，可以采取以下策略：</p>
<ol>
<li><strong>优化数据库事务：</strong> 通过合理设置数据库事务隔离级别，减小事务的范围，降低锁竞争，提高并发处理能力。合理使用事务的提交点，确保扣减库存和生成订单等关键操作的原子性。</li>
<li><strong>使用乐观锁：</strong> 在数据库层面引入乐观锁机制，通过版本号或时间戳等方式进行标记，减少锁的争用，提高并发性能。热点库存冲突率高时，乐观锁重试也会放大压力，应限制重试次数并按负载选择方案。</li>
<li><strong>分布式锁：</strong> 对于分布式系统，可以引入分布式锁，确保同一时刻只有一个请求能够扣减库存。分布式锁可以有效避免并发写入冲突。</li>
<li><strong>异步扣减：</strong> 将库存扣减操作异步化，通过消息队列等方式将扣减请求发送到异步处理队列中。这样可以降低同步扣减的压力，提高系统整体吞吐量。</li>
<li><strong>缓存预减：</strong>在 Redis 内通过 Lua 原子执行“检查可用库存不少于购买量，再扣减”；库存不足直接拒绝，不能等库存为负才到数据库扣减。预留成功后还需可靠落单，失败补偿和超时释放必须幂等。脚本原子性不等于 Redis 与数据库之间的事务原子性，参见 <a href="https://redis.io/docs/latest/develop/programmability/eval-intro/" target="_blank" rel="noopener noreferrer">Redis Lua 执行机制</a>。</li>
<li><strong>定时任务回补：</strong>只对已成功预留但最终取消或失败的订单释放库存，以预留记录和状态迁移作为依据，重复任务不能重复回补；未扣减成功的请求不应增加库存。</li>
</ol>
<p>以上策略的选择应根据具体业务需求和系统架构来决定。综合考虑事务隔离级别、数据一致性、系统复杂度等因素，可以设计出更高效和稳定的库存扣减方案。</p>
<ol start="5">
<li><strong>黄牛抢购：</strong></li>
</ol>
<p>在秒杀业务中，由于商品存在差价，吸引了大量黄牛参与抢购，因此如何识别并阻挡这些黄牛的下单行为显得至关重要。</p>
<p>首先，防刷的核心在于检测可能是黄牛的用户，这涉及到风控领域，通常需要借助算法模型。通过分析用户的IP、设备信息、网络数据、行为模式等，可以推算出可能是黄牛的用户。</p>
<p>一旦识别出潜在的黄牛用户，需要对他们的流量进行防控。这可以通过以下手段实现：</p>
<ul>
<li><strong>用户ID黑名单：</strong> 将识别出的黄牛用户ID加入黑名单中。Nginx和业务系统都可以在流量过滤时检查黑名单，如果用户在黑名单中，直接拒绝请求。</li>
<li><strong>IP地址、设备限流：</strong> 对于黄牛用户的IP地址和设备进行限流。例如，限制某个IP地址在一段时间内只能下单几次。这可以利用令牌桶、漏桶等限流算法来实现。</li>
<li><strong>借助限流工具：</strong> 利用诸如Nginx、Sentinel、Guava等限流工具，直接在系统层面进行流量控制。这些工具提供了强大的限流功能，能够有效防止过量的请求。</li>
</ul>
<p>通过综合利用黑名单、限流算法和专门的防刷工具，可以提高系统对黄牛的防控能力，确保秒杀活动的公平性和稳定性。</p>
<ol start="6">
<li><strong>重复下单：</strong></li>
</ol>
<p>在秒杀业务中，用户频繁尝试下单支付可能导致重复下单问题，占用额外库存，最终导致少卖的情况。因此，需要采取一些手段来避免重复下单。</p>
<p>可给同一次业务请求分配幂等键，服务端通过唯一约束或原子写入绑定请求与订单，重试时返回原订单。页面 token 相同本身不能保证幂等，还需服务端原子处理并校验请求参数一致。</p>
<p>此外，在秒杀业务中通常存在限购策略，因此可以结合业务场景，判断用户是否已有在途订单，通过限购方式来防止用户重复下单。</p>
<p>然而，上述过程中可能存在并发情况，导致token检测和订单重复性判断时的并发问题。幂等性不必依赖显式分布式锁，也可由数据库唯一约束与事务保障。</p>
<p>通过引入锁机制，可以确保在并发场景下，同一用户不会重复下单。可以采用分布式锁，数据库乐观锁，或者业务层面的锁机制，并结合订单唯一约束、事务和失败恢复，防止重复扣减库存和生成重复订单。这有助于维护系统的稳定性和数据的准确性。</p>
<ol start="7">
<li><strong>对普通交易的影响：</strong></li>
</ol>
<p>秒杀通常作为电商网站的一个功能，往往与其他业务一同部署，包括服务器、缓存和数据库等资源。为避免秒杀业务对普通交易造成过大的影响，需要考虑隔离策略。</p>
<p>隔离可以采用逻辑隔离和物理隔离两种方式，具体实施视业务需求而定。</p>
<ol>
<li><strong>物理隔离：</strong> 物理隔离是指将秒杀业务的前后端服务以及数据存储完全分离。这样可以确保秒杀活动对其他业务的影响最小化。前后端服务可以单独部署，甚至使用不同的服务器集群，以确保资源独立。</li>
<li><strong>逻辑隔离：</strong> 在应用层面进行逻辑隔离，即通过对数据进行标记或打标签的方式来区分秒杀订单和普通订单。例如，在订单和商品数据上添加标记，明确标识出哪些是秒杀订单。这样方便查询和分析，但标签本身不隔离资源竞争；还需独立线程池、连接池配额、队列和限流，防止秒杀耗尽普通交易资源。</li>
</ol>
<p>综合考虑业务场景和系统复杂度，可以选择适当的隔离策略。物理隔离更彻底，但成本和复杂度也更高。逻辑隔离则相对灵活，能够在业务层面实现隔离效果，同时降低了部署和维护的难度。</p>
<h2>5、应用程序中存在包冲突的情况下，怎么发现和解决</h2>
<p>包冲突是指在应用程序中引入的不同版本的库或依赖之间存在冲突，可能导致编译错误、运行时异常或其他不稳定行为。解决包冲突通常需要以下步骤：</p>
<ol>
<li><strong>诊断冲突：</strong> 确定哪些包发生了冲突，以及冲突的具体原因。可以通过查看编译日志、运行时异常信息或使用工具来分析依赖关系。</li>
<li><strong>查看依赖关系：</strong> 使用构建工具或依赖管理工具查看项目的依赖关系。对于Java项目，可以查看Maven的依赖配置文件。了解每个依赖项的版本以及它们之间的关系。</li>
<li><strong>升级或降级依赖版本：</strong> 如果发现包冲突是由于不同依赖项使用了相同库的不同版本而引起的，可以考虑升级或降级相关依赖项的版本，以便它们使用相同的库版本。</li>
<li><strong>排除依赖项：</strong> 对于某些依赖项，可以使用排除（exclusion）机制来排除特定的传递性依赖项。这样可以防止冲突的库被引入。</li>
<li>在Maven中，可以使用 <code>&lt;exclusions&gt;</code> 元素：</li>
</ol>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>&#x3C;dependency></span></span>
<span class="line"><span>    &#x3C;groupId>example&#x3C;/groupId></span></span>
<span class="line"><span>    &#x3C;artifactId>example-artifact&#x3C;/artifactId></span></span>
<span class="line"><span>    &#x3C;version>1.0&#x3C;/version></span></span>
<span class="line"><span>    &#x3C;exclusions></span></span>
<span class="line"><span>        &#x3C;exclusion></span></span>
<span class="line"><span>            &#x3C;groupId>conflicting-group&#x3C;/groupId></span></span>
<span class="line"><span>            &#x3C;artifactId>conflicting-artifact&#x3C;/artifactId></span></span>
<span class="line"><span>        &#x3C;/exclusion></span></span>
<span class="line"><span>    &#x3C;/exclusions></span></span>
<span class="line"><span>&#x3C;/dependency></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol>
<li><strong>使用类加载器隔离：</strong> 对于较为复杂的场景，可以考虑使用类加载器隔离机制，将不同版本的库加载到不同的类加载器中。这样可以在同一应用中使用不同版本的库而不会发生冲突。但需要注意，类加载器隔离可能引入新的问题，需要谨慎使用。</li>
<li><strong>统一兼容版本：</strong>优先遵循框架 BOM 和兼容矩阵，通过 <code>dependencyManagement</code> 统一版本；不要盲目把所有依赖升级到最新版。</li>
<li><strong>使用依赖检查工具：</strong>Maven Enforcer 的 <code>dependencyConvergence</code> 规则可检查依赖收敛，但不是依赖锁文件。Maven 3.9.x 通常按“路径最近优先，同深度先声明优先”仲裁传递依赖；<code>dependencyManagement</code> 管理版本并不自动引入依赖。可用 <code>mvn dependency:tree -Dverbose</code> 查看实际解析路径，定位运行时 <code>NoSuchMethodError</code> 等问题后再排除或统一版本。参见 <a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html" target="_blank" rel="noopener noreferrer">Maven 依赖机制</a>。</li>
</ol>
<h2>6、跨节点join查询如何实现</h2>
<p>在分布式系统中，跨节点的Join查询是一个具有挑战性的问题，因为传统的关系数据库系统通常设计为在单一节点上执行Join操作。在分布式环境下，有一些常见的解决方案：</p>
<ol>
<li><strong>数据复制：</strong> 将涉及到Join操作的表数据在分布式环境中的每个节点上都复制一份。这样，每个节点可以在本地执行Join操作而无需跨节点通信。这种方法虽然简单，但会导致数据冗余和一致性的问题。</li>
<li><strong>分布式Join算法：</strong> 开发专门的分布式Join算法，使得在分布式环境下也能高效执行Join操作。例如，MapReduce框架就提供了一些分布式Join的实现。这需要对数据进行划分和分布式计算。</li>
<li><strong>使用分布式数据库：</strong> 选择支持分布式Join的分布式数据库系统。一些分布式数据库提供了内建的分布式Join支持，可以在分布式环境中高效地执行Join查询。</li>
<li><strong>分片和合并：</strong> 将Join操作分解为可在每个节点上执行的部分，然后在一个中心节点上合并结果。这种方式需要设计合适的数据分片策略和结果合并机制。</li>
<li><strong>数据缓存：</strong> 在每个节点上缓存可能用于Join的数据片段，以减少跨节点通信的需求。这需要根据查询模式预先缓存可能用到的数据。</li>
<li><strong>消息传递：</strong> 使用消息传递机制，将涉及Join的查询分解为子查询，分发到各个节点执行，然后将结果进行合并。这需要有效的通信机制和任务协调。</li>
<li><strong>全局索引：</strong> 在所有节点上建立全局索引，以加速Join查询。这需要维护一致的全局索引，可能带来额外的维护成本。</li>
</ol>
<h2>7、Excel中百万级数据怎么导入到数据库</h2>
<p>在处理百万级数据从Excel读取并插入到数据库的场景中，涉及到多方面的问题，需要综合考虑性能、内存占用、错误处理等因素。以下是一个可能的解决方案：</p>
<ol>
<li><strong>内存溢出问题</strong></li>
</ol>
<p>使用流式读取方式，分批处理Excel数据。可使用 Apache POI 5.x 的 XSSF SAX/Event API 读取 <code>.xlsx</code>；SXSSF 主要用于流式写出，不是流式读取接口。现有 EasyExcel 项目也应采用监听器分批处理，避免在回调中累积全部行；流式读取并不能消除共享字符串、业务缓冲等内存开销。参见 <a href="https://poi.apache.org/components/spreadsheet/how-to.html" target="_blank" rel="noopener noreferrer">Apache POI 读取与写出说明</a>。</p>
<ol>
<li><strong>性能问题</strong></li>
</ol>
<p>考虑多线程处理，使用线程池同时读取不同的sheet，以提升并发性能。对于数据插入操作，借助多线程和数据库的批量插入功能，进一步提高性能。</p>
<ol>
<li><strong>错误处理</strong></li>
</ol>
<ul>
<li><strong>数据检查：</strong> 在读取和插入之前进行数据检查，处理数据格式错误、不一致等问题。</li>
<li><strong>异常处理：</strong> 在插入过程中，对异常进行处理。采用自动重试机制，一定次数内尝试重新插入。记录日志以便后续分析。</li>
<li><strong>数据重复处理：</strong> 设置Excel中某几个字段为数据库唯一性约束，处理数据重复问题。可以选择覆盖、跳过或报错，根据业务需求选择合适的方式。</li>
</ul>
<h3>整体方案</h3>
<ol>
<li>用事件式读取逐行解析 Excel；默认单线程读取，确需并发时使用独立读取资源，避免多个线程共享非线程安全的工作簿对象。</li>
<li>数据分批插入，设置一个批次大小，当达到批次大小时执行一次批量插入操作。</li>
<li>使用有界队列（如指定容量的 <code>ArrayBlockingQueue</code>）连接读取与写入，写入变慢时阻塞生产者形成背压；无界的 <code>ConcurrentLinkedQueue</code> 即使线程安全也可能耗尽内存。失败重试需有导入批次/行标识，避免已提交批次被重复写入。</li>
</ol>
<p>通过这种方案，能够在相对较短的时间内处理百万级数据的读取和插入，同时保证了内存的有效利用和错误处理的可靠性。具体的实现可以基于以上思路，结合实际项目需求和技术栈进行调整。</p>
<h2>8、缓存预热怎么实现</h2>
<p>在系统中进行缓存预热是为了提高系统性能和响应速度的一种策略。常见的缓存预热方案包括：</p>
<h3>1. 启动过程中预热</h3>
<p>一种常见的实现方式是在系统启动时进行缓存预热，特别适用于本地缓存。在Spring应用程序启动时，可以通过监听应用启动事件或在初始化阶段，将需要缓存的数据加载到缓存中。Spring提供了多个事件和扩展点，如<code>ApplicationReadyEvent</code>、<code>CommandLineRunner</code>、<code>InitializingBean</code>、<code>@PostConstruct</code>等，可用于执行预热逻辑。</p>
<h3>2. 定时任务</h3>
<p>定时任务是在系统运行过程中通过设定的时间间隔更新缓存，以确保缓存中的数据保持最新。在Spring中，可以使用<code>@Scheduled</code>注解轻松实现定时任务，适用于需要动态更新的情况。</p>
<h3>3. 用时加载</h3>
<p>请求到达后才加载属于懒加载，会让首次请求承担回源延迟；严格意义的预热是在流量到来前主动加载热点。示例代码如下：</p>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>public Data fetchData(String key) {</span></span>
<span class="line"><span>    // 先检查缓存中是否存在数据</span></span>
<span class="line"><span>    Data cachedData = cache.get(key);</span></span>
<span class="line"><span></span></span>
<span class="line"><span>    if (cachedData == null) {</span></span>
<span class="line"><span>        cachedData = loadFromSource(key); // 业务数据源</span></span>
<span class="line"><span>        if (cachedData != null) {</span></span>
<span class="line"><span>            cache.put(key, cachedData);</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>    </span></span>
<span class="line"><span>    return cachedData;</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>4. 缓存加载器</h3>
<p>某些缓存框架提供了缓存加载器的机制，当缓存中不存在数据时，自动调用加载器加载数据到缓存中。例如，在Caffeine中可以使用<code>LoadingCache</code>实现，简化了缓存预热的逻辑。示例代码如下：</p>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>import com.github.benmanes.caffeine.cache.Caffeine;</span></span>
<span class="line"><span>import com.github.benmanes.caffeine.cache.LoadingCache;</span></span>
<span class="line"><span>import org.springframework.stereotype.Service;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>import java.util.concurrent.TimeUnit;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>@Service</span></span>
<span class="line"><span>public class MyCacheService {</span></span>
<span class="line"><span></span></span>
<span class="line"><span>    private final LoadingCache&#x3C;String, String> cache;</span></span>
<span class="line"><span>    </span></span>
<span class="line"><span>    public MyCacheService() {</span></span>
<span class="line"><span>        this.cache = Caffeine.newBuilder()</span></span>
<span class="line"><span>                .refreshAfterWrite(1, TimeUnit.MINUTES)  // 写入满1分钟后，再次访问时触发异步刷新</span></span>
<span class="line"><span>                .build(key -> loadDataFromSource(key));  // 使用加载器加载数据</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>    </span></span>
<span class="line"><span>    public String getValue(String key) {</span></span>
<span class="line"><span>        return cache.get(key);</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>    </span></span>
<span class="line"><span>    private String loadDataFromSource(String key) {</span></span>
<span class="line"><span>        return "value-for-" + key; // 示例占位，实际应查询数据源</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Caffeine 3.x 的 <code>refreshAfterWrite</code> 只使条目具备刷新资格，访问才触发异步刷新，期间通常返回旧值；不是每分钟自动扫描刷新。<code>LoadingCache</code> 构造时也不会自行加载全部数据，主动预热需调用 <code>getAll(hotKeys)</code> 等。参见 <a href="https://github.com/ben-manes/caffeine/wiki/Refresh" target="_blank" rel="noopener noreferrer">Caffeine Refresh</a>。</p>
<h2>9、表数据量大的时候只能分库分表？</h2>
<p>当MySQL中的单表数据量庞大时，考虑分库分表通常是为了提升数据库吞吐量和查询效率。然而，分库分表方案的实现较为复杂，且可能引发一系列问题，如跨库事务、分页查询等。在采取分库分表之前，应优先考虑以下方案：</p>
<ol>
<li><strong>数据库优化：</strong> 通过优化数据库结构、使用合适的索引、调整SQL语句等手段，可以有效提升查询性能。在2000万数据量级别，合理使用索引和进行表结构设计优化能够显著改善数据库性能。</li>
<li><strong>缓存：</strong> 减轻数据库压力可以通过缓存实现。在近端引入本地缓存或分布式缓存，能够迅速返回结果并减轻数据库负担，提高系统性能。</li>
<li><strong>分区：</strong>MySQL 8.4 分区仍是同一逻辑表，不减少总数据量或自动扩容到多台服务器。收益依赖查询条件能否触发分区裁剪，以及按分区归档/删除等维护需求；应结合 <code>EXPLAIN</code> 验证实际访问分区。参见 <a href="https://dev.mysql.com/doc/refman/8.4/en/partitioning-pruning.html" target="_blank" rel="noopener noreferrer">MySQL 分区裁剪</a>。</li>
<li><strong>数据归档：</strong> 定期清理不再需要或不再活跃的数据，将其归档到辅助存储中（如历史表、离线数仓），可以减少数据量，进而提升数据库效率。</li>
<li><strong>分布式数据库：</strong> 考虑采用分布式数据库系统，充分利用多个节点分散数据，提高系统性能和容量。尽管分布式数据库成本较高，但相较于分库分表的改造而言，是否有收益需要根据事务语义、访问模式、迁移成本和实测结果判断。</li>
</ol>
<p>这些方案在实施时需要根据具体业务场景和需求进行综合考虑，选择最适合的优化方案，以达到性能提升的效果。</p>
<h2>10、线上发现一个接口响应很慢，怎么排查问题</h2>
<ul>
<li>监控工具：可以使用性能监控工具来实时监控接口的响应时间、吞吐量和错误率等指标。常见的监控工具：Arthas、Prometheus、Grafana、AppDynamics等。</li>
<li>日志分析：查看接口的日志文件，关注接口的执行时间、请求参数和响应结果等消息。同构分析可以找到执行时间较长的接口，以及可能导致慢接口的原因。</li>
<li>接口性能测试：通过模拟多用户并发请求，观察接口的响应时间和吞吐量等指标，找出慢响应的接口。常见的性能测试工具：JMeter、LoadRunner等。</li>
<li>数据库查询分析：如果接口涉及数据库查询，通过数据库查询分析工具（如EXPLAIN语句）来检查查询执行计划，确保数据库查询效率。</li>
<li>代码剖析：对接口代码进行剖析，了解代码中的性能瓶颈和耗时操作，通过剖析结果找到影响性能的代码段，进行优化，常见的剖析工具：Java Profiler、VisualVM等</li>
</ul>
<h2>11、MQ里面数据到达1000万的时候，怎么尽快消费掉</h2>
<ul>
<li>增加消费者数量: 消息堆积了，消费不过来了，那就把消费者的数量增加一下，让更多的实例来消费这些消息。</li>
<li>提升消费者消费速度：排查数据库、外部调用和锁等待等瓶颈，采用受控并发和批处理。业务提交成功后再确认；仅放进内存线程池就返回成功，会在进程崩溃时丢任务。若先落本地持久化任务表，需保证可恢复扫描和幂等执行。RabbitMQ 4.4 的确认语义参见 <a href="https://www.rabbitmq.com/docs/confirms" target="_blank" rel="noopener noreferrer">消费者确认</a>。</li>
<li>降低生产者的生产速度：如果生产者可控的话，可以让生产者生成消息的速度慢一点。</li>
<li>清理过期消息：有一些过期消息、或者一直无法成功的消息，在业务做评估之后，如果无影响或者影响不大其实是可以清理的。</li>
<li>调整 RocketMQ 的配置参数: RocketMQ 提供了很多可配置的参数，例如消息消费模式、消息拉取间隔时间等，可以根据实际情况来调整这些参数，从而优化消息消费的效率。</li>
<li>增加Topic队列数: 如果一个 Topic 的队列数比较少，那么就容易出现消息堆积的情况。可以通过增加队列数来提高消息的处理并发度，从而减少消息堆积。</li>
</ul>
<h2>12、涉及到订单超卖问题怎么解决</h2>
<p>订单超卖：就是当有两个并发线程同时查询库存商品，这时数据库中商品库存就剩1个，所以两个线程得到的库存数量都是1，然后经过库存校验之后分别开始进行库存扣减，最终导致库存被扣减成负数。</p>
<ul>
<li>悲观锁：在同一显式事务内执行 <code>SELECT ... FOR UPDATE</code>、校验和扣减，事务提交或回滚后释放锁；仅在自动提交模式执行一次查询，不能保护后续独立更新。</li>
<li>乐观锁：更新条件同时校验版本和 <code>stock &gt;= quantity</code>，成功后递增版本；版本匹配本身不代表库存充足，冲突时应有界重试或返回失败。</li>
<li>限流：对订单的创建进行限流，防止短时间内大量订单的涌入，从而减小并发冲突的可能性。可以使用令牌桶算法或漏桶算法来实现限流；</li>
</ul>
<p>限制同一IP在一定时间内只能发送一个请求。可以通过在网关中自定义全局过滤器来实现IP限流；</p>
<p>限制接口每秒只接受一定数量的请求。可以使用Sentinel的限流功能；</p>
<ul>
<li>库存预留：在用户下单时，先检查库存是否足够，并将一部分库存标记为已预留状态。在订单支付完成后再确认扣减库存。</li>
<li>缓存：单条 <code>DECRBY</code> 或负数 <code>INCRBY</code> 虽然原子，却允许扣成负数；检查与扣减应放进同一 Lua 脚本，并处理缓存与数据库之间的可靠同步和补偿。</li>
<li>消息队列：异步处理可以削峰，但多消费者不天然串行，全局有序也不是默认保证。仍需原子扣减、幂等处理与失败补偿；限流只是减压，不能代替库存正确性约束。</li>
</ul>
<p>单库库存扣减可先采用下面的条件更新（MySQL 8.4 / InnoDB，<code>sku_id</code> 为主键，购买量必须为正整数）：</p>
<div class="language-sql line-numbers-mode" data-highlighter="shiki" data-ext="sql" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-sql"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">UPDATE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> inventory</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">SET</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> stock </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> stock - ?</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">WHERE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> sku_id </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ? </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">AND</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> stock </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">>=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ?;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">-- 第一个和第三个参数都是同一购买量；影响行数为 1 才允许继续创建订单。</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>检查与扣减在同一条更新内完成，InnoDB 通过记录锁协调并发，避免两个请求都根据旧库存通过检查。扣减和订单写入应在同一事务内提交，写单失败则回滚；幂等键唯一约束防止同一请求重复扣减。跨库订单不能直接套用本地事务结论，需要库存预留、可靠消息与补偿机制。</p>
]]></content:encoded>
    </item>
    <item>
      <title>常用框架</title>
      <link>https://qiuzhiwang.vip/javabgw/cykj.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/cykj.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">常用框架</source>
      <description>梳理 Spring 容器、Bean 生命周期、事务传播、代理与配置加载，说明常用后端框架的协作机制和版本边界。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>建议先看题目目录，再按“概念 -&gt; 原理 -&gt; 场景 -&gt; 优化”顺序复习。<br>
每题先讲结论，再补关键机制和项目实践，回答会更稳。</p>
</div>
<h2>Spring</h2>
<p>版本说明：以下容器与事务机制以 Spring Framework 6.2.x、Spring Boot 3.5.x 为基线；旧版组件另行注明。</p>
<p>1、SpringBean的作用域<br>
<strong>参考回答：</strong> singleton：默认作用域，每个容器内每个 Bean 定义对应一个实例，不是整个 JVM 只有一个对象。共享可变状态仍需并发控制。<br>
prototype：每次向容器获取该 Bean 时创建新实例；直接注入单例 Bean 时，仅在该单例创建时解析一次，不会随业务方法调用重新创建。需要按次获取可注入 <code>ObjectProvider&lt;T&gt;</code> 并调用 <code>getObject()</code>。<br>
request：每次HTTP请求都会创建一个新的Bean实例。每个请求的Bean实例对于该请求是唯一的。这个作用域仅在基于Web的Spring ApplicationContext中有效。适用于处理HTTP请求的控制器或服务。<br>
session：在一个HTTP会话（Session）中，定义一个Bean实例。对于同一用户的所有请求，都将使用相同的Bean实例。这个作用域也仅在基于Web的Spring ApplicationContext中有效，适用于保存用户特定的数据或状态。<br>
application：每个 ServletContext 一个实例；websocket：每个 WebSocket 会话一个实例。历史 globalSession 属于旧 Portlet 支持，Spring 6.2 的 Servlet 应用没有这个内置作用域，也不会自动等同于 session。参见 <a href="https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html" target="_blank" rel="noopener noreferrer">Bean 作用域</a>。<br>
2、SpringBean的生命周期<br>
<strong>参考回答：</strong> 实例化：容器依据 BeanDefinition，通过构造器、工厂方法等创建对象；构造器依赖在此时解析。BeanFactoryPostProcessor 在常规 Bean 实例化前处理 Bean 定义，不是每个 Bean 实例化中的回调。非懒加载单例通常在上下文刷新时预实例化。<br>
属性赋值：Spring IoC容器在创建Bean的过程中，会将Bean在XML文件中配置的属性值和BeanDefinition中的属性值设置到Bean中。这个过程也称为依赖注入（DI），可以通过构造方法注入、Setter方法注入等方式完成。<br>
Bean后处理器（BeanPostProcessor）处理：在Bean的实例化和属性赋值之后，Spring会调用BeanPostProcessor的postProcessBeforeInitialization方法，对Bean进行进一步的处理。这是一个可选的步骤，开发者可以通过实现BeanPostProcessor接口并覆盖相应的方法来定义自己的处理逻辑。<br>
初始化：通常按 @PostConstruct → InitializingBean.afterPropertiesSet() → 自定义 init-method 的顺序执行（同一个方法不会重复调用）。@PostConstruct 由后置处理器在初始化前阶段触发；Spring 6 使用 jakarta.annotation.PostConstruct。<br>
实例化后处理：在Bean完成初始化之后，Spring会再次调用BeanPostProcessor的postProcessAfterInitialization方法，对Bean进行最后的处理。这也是一个可选的步骤，开发者可以在这里添加一些额外的逻辑，例如对Bean进行代理等。<br>
使用：此时Bean已经准备就绪，可以被应用程序使用了。Spring容器会管理Bean的生命周期，并在需要的时候将其提供给应用程序。<br>
销毁：受容器管理的单例在正常关闭时依次执行 @PreDestroy、DisposableBean.destroy()、自定义 destroy-method。prototype 创建后交给调用者，容器不自动执行其销毁回调。参见 <a href="https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html" target="_blank" rel="noopener noreferrer">生命周期回调</a>。<br>
3、Spring AOP原理<br>
<strong>参考回答：</strong> Spring AOP（Aspect Oriented Programming，面向切面编程）的原理主要基于动态代理模式，用于在不修改原有业务代码的前提下，为多个目标对象统一管理并增加额外功能。其核心概念包括切面（Aspect）、连接点（Join Point）、通知（Advice）等。<br>
切面（Aspect）：封装横切关注点的模块，由切点与通知等组成；切点决定匹配哪些方法执行，通知定义何时执行什么逻辑。<br>
连接点（Join Point）：Spring AOP 只支持 Spring Bean 的方法执行连接点，不支持构造器调用或字段访问；更广的连接点属于 AspectJ 的能力。<br>
通知（Advice）：通知包括各种类型的切面代码，如前置通知、后置通知、环绕通知、异常通知和最终通知等。这些通知会在连接点执行时被触发。<br>
Spring AOP以动态代理技术为主要方式进行织入。具体来说，当目标对象的方法被调用时，Spring AOP会检查该方法是否是一个连接点。如果是，则会在方法执行前后（或者方法执行过程中）织入相应的通知代码。这些通知代码可以在不修改原有业务代码的情况下，为目标对象增加额外的功能。<br>
4、Spring事务传播机制<br>
<strong>参考回答：</strong> REQUIRED（默认）：<br>
如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。<br>
SUPPORTS：<br>
如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。<br>
MANDATORY：<br>
如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。<br>
REQUIRES_NEW：<br>
创建独立物理事务，并挂起当前事务；内外事务可分别提交或回滚，但异常若继续传播仍可能触发外层回滚。外层资源未释放，内层可能再占用连接，需要关注连接池容量。<br>
NOT_SUPPORTED：<br>
以非事务方式运行，如果当前存在事务，则把当前事务挂起。<br>
NEVER：<br>
以非事务方式运行，如果当前存在事务，则抛出异常。<br>
NESTED：<br>
如果当前存在事务，通常在同一个物理事务内创建保存点，内层失败可回滚到保存点；外层整体回滚时内层结果也一起回滚。无事务时按 REQUIRED 开启事务。该能力依赖事务管理器及资源支持，常见于 JDBC 保存点，不能假设 JPA/JTA 均支持。参见 <a href="https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/tx-propagation.html" target="_blank" rel="noopener noreferrer">事务传播机制</a>。<br>
5、什么是Spring的循环依赖问题？（了解）<br>
<strong>参考回答：</strong> Spring的循环依赖问题是指在Spring容器中，两个或多个Bean之间相互依赖，形成了一个循环引用的情况。具体来说，当一个Bean A依赖于另一个Bean B，而Bean B又依赖于Bean A时，就可能出现循环依赖的问题。<br>
6、什么是spring三级缓存以及如何解决循环依赖（重要）<br>
<strong>参考回答：</strong> singletonObjects 保存完成创建的单例；earlySingletonObjects 保存已取得的早期引用；singletonFactories 保存获取早期引用的 ObjectFactory。工厂捕获已实例化的对象，按需返回原对象或早期代理，不会再次构造 Bean。这些是容器对象缓存，不是数据库查询缓存。详细机制见下方保留的“Srping 一级、spring二级、三级缓存是如何解决循环依赖的”章节。<br>
6、spring事务失效的原因？<br>
<strong>参考回答：</strong> 方法不可代理：注解名为 @Transactional。Spring 6.0 起，基于类的事务代理默认支持 protected 和包可见方法；JDK 接口代理要求 public 且在接口中声明。private 方法不能被代理，类代理也不能增强 final 方法，不能笼统说非 public 都无效。<br>
类没有被Spring管理：Spring事务是由AOP机制实现的，AOP机制的本质就是动态代理。从Spring IOC容器获取bean时，Spring会为目标类创建代理，从而支持事务。如果类没有被Spring容器管理（例如，没有添加@Service、@Component等注解），则Spring无法为目标类创建代理，事务也会失效。<br>
异常被捕获且未正确处理：默认情况下，Spring只有在方法抛出运行时异常或者错误时才会回滚事务。如果事务方法内部抛出了异常，但该异常被catch语句捕获且没有重新抛出，或者捕获的异常类型不是运行时异常，那么事务将不会回滚。<br>
事务属性设置不当：如果在Spring的事务管理中，事务的传播特性（propagation）或回滚规则（rollbackFor）设置不当，也可能导致事务失效。例如，如果在一个支持当前事务的方法中调用了需要新事务的方法，并且后者方法抛出了异常，但异常并未被Spring识别为需要回滚事务的异常，那么后者的事务将不会回滚。<br>
数据库配置问题：如果数据库配置不支持事务（例如，使用了不支持事务的数据库引擎），或者数据库本身就是不支持事务的，那么Spring事务也会失效。<br>
调用同一个类中的方法：默认代理模式下，this.inner() 绕过代理，即使 inner 标有 @Transactional，也不会触发它的传播或回滚规则；如果 outer 已开启事务，inner 的数据库操作仍可能参与外层事务。应通过另一个受管 Bean 的代理调用需要独立事务边界的方法。参见 <a href="https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html" target="_blank" rel="noopener noreferrer">声明式事务</a>。<br>
事务嵌套使用不当：在一个已经开启了事务的方法中再次开启事务，对于Spring的PROPAGATION_REQUIRED模式（默认模式），它只会用原来的事务，这可能导致预期之外的行为。<br>
7、spring bean是否为线程安全的？<br>
<strong>参考回答：</strong> 作用域不等于线程安全。单例可以是无状态且线程安全的；共享可变字段需同步或并发设计。prototype 即使每次获取都创建新对象，也可能被单例长期持有或传给多个线程，不能据此保证无共享。<br>
8、spring中用到的设计模式？<br>
<strong>参考回答：</strong> 工厂模式（Factory Pattern）：<br>
Spring使用工厂模式通过BeanFactory、ApplicationContext等接口创建并管理对象实例。这种方式将对象的创建与使用解耦，使得程序更加灵活和可扩展。<br>
单例模式（Singleton Pattern）：<br>
Spring中的Bean默认都是单例的，即在整个Spring IoC容器中，每个Bean只会有一个实例。这通过Bean的scope属性进行控制，当scope为singleton时，即表示使用单例模式。<br>
代理模式（Proxy Pattern）：<br>
在AOP（面向切面编程）和remoting中被广泛使用。Spring AOP的实现基于动态代理模式，可以在运行时动态地将代码切入到类的指定方法、指定位置上。<br>
模板方法模式（Template Method Pattern）：<br>
解决代码重复问题。Spring中例如JdbcTemplate就是模板方法模式的体现，它封装了数据访问的底层操作，定义了数据访问的骨架，使得开发者可以只关注自己的业务SQL。<br>
前端控制器模式（Front Controller Pattern）：<br>
Spring MVC中的DispatcherServlet就是前端控制器模式的实现，它负责接收请求，并根据请求信息调用相应的处理器（Controller）进行处理。<br>
依赖注入模式（Dependency Injection Pattern）：<br>
贯穿于BeanFactory和ApplicationContext接口的核心理念。通过依赖注入，Spring可以在运行时动态地将一个对象所需要的外部依赖注入到该对象中，从而降低了代码的耦合度。<br>
装饰器模式（Decorator Pattern）：<br>
在Spring中，BeanWrapper是一个很好的装饰器模式的例子。它允许你在运行时动态地给一个对象添加一些额外的职责。<br>
委派模式（Delegation Pattern）：<br>
在Spring MVC中，DispatcherServlet就是一个典型的委派模式的例子。它接收请求，但并不处理请求，而是将请求委派给相应的控制器进行处理。<br>
策略模式（Strategy Pattern）：<br>
在Spring中，HandlerMapping接口的实现类就使用了策略模式。它定义了一个处理请求的接口，具体的处理策略由实现类来完成。<br>
适配器模式（Adapter Pattern）：<br>
在Spring MVC中，HandlerAdapter接口的实现类就使用了适配器模式。它将不同的处理器（Controller）适配成统一的接口，使得DispatcherServlet可以统一调用。<br>
观察者模式（Observer Pattern）：<br>
在Spring的事件驱动模型中，使用了观察者模式。当某个事件发生时，会通知所有注册的观察者（Listener）进行处理。<br>
能说一部分就行了<br>
9、Spring IOC<br>
<strong>参考回答：</strong> Spring IoC（Inversion of Control，控制反转）是Spring框架的核心特性之一。它的基本思想是将对象的创建和管理过程从应用程序代码中抽离出来，由Spring容器负责创建和管理对象之间的依赖关系。通过这种方式，应用程序代码不再需要直接创建和管理对象，而是向Spring容器发出请求，由容器负责创建和返回所需的对象。<br>
Spring IoC的主要优点包括：<br>
降低耦合性：通过IoC容器来管理对象之间的依赖关系，使得系统更加模块化、可测试和易于扩展。<br>
提高灵活性和可维护性：由于对象的创建和管理由Spring容器负责，因此可以更加灵活地配置和修改对象的行为。同时，由于代码不再包含对象的创建和管理逻辑，因此可以更加专注于业务逻辑的实现，提高代码的可维护性。<br>
支持多种配置方式：Spring IoC支持多种配置方式，包括XML配置、注解配置和Java配置等。这些配置方式可以根据项目的实际需求进行灵活选择和使用。<br>
在实现Spring IoC时，有三种主要的方式：<br>
使用XML实现Spring IoC：通过编写XML配置文件来定义和配置Bean，将配置信息集中在一个XML文件中进行管理。<br>
使用注解方式实现Spring IoC：通过在类和方法上添加注解来定义和配置Bean，使得配置更加简洁、直观，并且减少了对XML文件的依赖。<br>
使用JavaConfig实现Spring IoC：通过编写Java配置类来定义和配置Bean，将配置信息集中在一个Java类中，提供了一种更加面向对象和类型安全的配置方式。<br>
10、spring AOP在项目中的应用场景？<br>
<strong>参考回答：</strong> 日志记录：在系统中记录日志是非常重要的，可以使用AOP来实现日志记录的功能。通过在方法执行前、执行后或异常抛出时记录日志，可以方便地跟踪系统的运行情况和问题排查。<br>
事务管理：在数据库操作或其他需要事务管理的操作中，使用AOP可以将事务管理的逻辑与业务逻辑分离，使得事务的控制更加简单和集中。通过AOP，可以在方法开始前开启事务，在方法执行完毕后提交或回滚事务，以确保数据的一致性和完整性。<br>
安全控制：在系统中包含某些需要安全控制的操作，如登录、修改密码、授权等，可以使用AOP来实现安全控制的功能。通过AOP，可以在方法调用前进行安全性检查，例如检查用户的权限或身份验证，以确保只有授权用户可以访问特定的方法或资源。<br>
性能监控：在系统运行过程中，有时需要对某些方法的性能进行监控，以找到系统的瓶颈并进行优化。通过AOP，可以在方法调用前后记录方法的执行时间，以监控和优化系统的性能。<br>
缓存控制：在系统中有些数据可以缓存起来以提高访问速度，可以使用AOP来实现缓存控制的功能。通过AOP，可以在方法执行前查询缓存中是否有数据，如果有则返回，否则执行方法并将方法返回值存入缓存中。<br>
异常处理：使用AOP，可以集中处理方法中抛出的异常，例如将异常转换为统一的错误码或进行日志记录。这有助于更好地管理和监控异常，并降低异常对系统的影响。<br>
参数校验和转换：使用AOP，可以在方法调用前对方法的参数进行校验和转换，以确保参数的有效性和符合业务要求。这有助于减少因参数问题导致的错误和异常，并提高系统的健壮性。<br>
12、Spring中如何开启事务？<br>
<strong>参考回答：</strong> 编程式事务：<br>
编程式事务是通过编程的方式自己去实现事务，比如事务的开启、提交、回滚操作，需要开发人员自己调用commit()或者rollback()等方法来实现。<br>
这种事务管理方式需要在逻辑代码中手动书写事务控制逻辑，因此是侵入性的。它的缺点是代码复用性差，每一个事务都需要自己编写事务逻辑，可能会造成代码冗余。然而，它的优点在于事务边界更容易控制，可以在业务的任意位置进行事务提交或者回滚操作。<br>
声明式事务：<br>
声明式事务则是通过配置或注解的方式，将事务管理逻辑与业务逻辑进行分离，由Spring框架来自动处理事务的开启、提交和回滚。<br>
使用声明式事务时，通常在受管业务类或方法上标注 @Transactional，通过代理调用后由事务管理器按传播规则开启或参与事务。默认仅 RuntimeException 和 Error 导致回滚，受检异常需配置 rollbackFor；捕获异常也不代表一定可提交，事务可能已被标为 rollback-only。<br>
普通 Spring Java 配置通常还需 @EnableTransactionManagement 和适当的事务管理器；Spring Boot 在满足自动配置条件时会启用事务管理，不必重复添加该注解。<br>
13、Spring ioc在项目中的应用场景？（了解）<br>
<strong>参考回答：</strong> 解耦与模块化：<br>
在复杂的系统中，各个组件之间可能存在复杂的依赖关系。使用Spring IoC，可以将这些依赖关系交由Spring容器管理，使得组件之间的耦合度降低，提高了系统的模块化程度。<br>
依赖注入：<br>
Spring IoC支持依赖注入（Dependency Injection, DI），这意味着Spring容器会自动将所需的依赖项注入到对象的实例中。这使得开发者无需在代码中手动创建和管理依赖对象，从而简化了代码并提高了可读性。<br>
配置集中管理：<br>
Spring IoC允许开发者将配置信息（如数据库连接信息、第三方服务接口地址等）集中在一个或多个配置文件中（如XML文件、Java配置类或YAML文件）。这样，当配置信息需要变更时，只需修改配置文件而无需修改代码。<br>
插件化开发：<br>
在一些大型系统中，可能需要支持多种插件或扩展。通过使用Spring IoC，可以将这些插件或扩展作为独立的组件进行开发，并通过配置文件或注解将它们集成到系统中。这使得插件的添加、删除和替换变得更加容易。<br>
测试支持：<br>
在测试过程中，可能需要模拟或替换某些组件的依赖项。Spring IoC提供了方便的测试支持，允许开发者在测试环境中注入模拟对象或测试数据，从而验证系统的正确性。<br>
动态代理：<br>
Spring IoC结合AOP（Aspect-Oriented Programming，面向切面编程）可以实现动态代理。动态代理允许开发者在不修改原始代码的情况下，为类添加额外的功能（如日志记录、事务管理、性能监控等）。这使得系统的功能扩展变得更加灵活和方便。<br>
容器化应用：<br>
在云原生和微服务架构中，应用通常以容器的形式部署。Spring IoC作为Spring框架的核心特性之一，可以与容器化技术（如Docker、Kubernetes等）很好地结合使用。通过Spring IoC管理应用的依赖关系，可以确保应用在容器化环境中能够正常运行并与其他服务进行交互。<br>
14、SpringBoot和Spring的区别是什么？（了解）<br>
<strong>参考回答：</strong> 设计理念：Spring是一个全面的框架，它提供了许多功能，但同时也增加了项目的复杂度。相反，Spring Boot旨在简化Spring应用程序的初始搭建和开发过程，尽可能自动配置Spring应用程序。基于“约定优于配置”的原则，Spring Boot简化了项目的配置流程。<br>
创建独立应用：Spring Boot可以创建独立的Spring应用程序。它集成了Tomcat、Jetty和Undertow等服务器，并且不需要部署它们。这意味着开发者可以直接打包他们的应用程序为一个可执行的JAR或WAR文件，从而简化了部署过程。<br>
配置：在Spring中，我们需要进行大量的XML配置、JavaConfig和注解处理，以整合各种组件。而在Spring Boot中，它使用了特定的方式来进行配置，极大地简化了这些文件的配置过程。例如，Spring Boot引入了“starters”的概念，这些是预先配置好的Maven依赖项，可以简化Maven配置。<br>
嵌入式容器：Spring Boot嵌入了一个Web服务器（如Tomcat、Jetty或Undertow），这使得应用程序可以作为一个独立的可执行JAR或WAR文件运行，而不需要单独的服务器或容器。<br>
自动配置：Spring Boot的强大之处在于其自动配置功能。它通过检查项目的类路径、属性设置和其他条件，自动为你配置Spring应用程序。例如，如果你在类路径中添加了H2数据库，Spring Boot会自动为你配置一个内存数据库。<br>
依赖管理：在Spring Boot中，你只需要引入相应的starter依赖，就可以快速地整合第三方库和框架。这种依赖管理方式使得项目结构更加清晰，减少了版本冲突的可能性。<br>
15、springBean的初始化过程？<br>
<strong>参考回答：</strong> Bean的定义：<br>
在Spring配置中定义Bean的信息，包括类名、属性、作用域等。<br>
Bean的加载和解析：<br>
Spring容器启动时，加载并解析配置文件或注解，生成<code>BeanDefinition</code>对象。<br>
实例化Bean：<br>
根据<code>BeanDefinition</code>，通过反射创建Bean实例。如果是单例Bean，会被缓存起来。<br>
依赖注入：<br>
为Bean注入所需的依赖，方式包括构造器注入、setter方法注入和字段注入。<br>
初始化前处理：<br>
如果存在<code>BeanPostProcessor</code>，调用其<code>postProcessBeforeInitialization</code>方法，对Bean进行初始化前的定制化处理。<br>
调用初始化方法：<br>
执行Bean的初始化方法，可能包括：<br>
实现<code>InitializingBean</code>接口的<code>afterPropertiesSet</code>方法。<br>
在配置中指定的<code>init-method</code>方法。<br>
使用<code>@PostConstruct</code>注解的方法。<br>
初始化后处理：<br>
如果存在<code>BeanPostProcessor</code>，调用其<code>postProcessAfterInitialization</code>方法，对Bean进行初始化后的定制化处理，如AOP代理等。<br>
Bean的就绪和使用：<br>
经过上述步骤，Bean已完全初始化，可以通过<code>getBean()</code>方法获取并使用。<br>
16、如何在Spring启动过程中做缓存预热？<br>
<strong>参考回答：</strong> 监听应用启动事件：Spring框架提供了多种方式来监听应用启动事件，以便在应用启动后执行特定的逻辑。你可以使用@EventListener注解或实现ApplicationListener接口来监听ApplicationReadyEvent事件。当这个事件被触发时，意味着Spring应用已经启动并准备好接受请求了。<br>
使用CommandLineRunner或ApplicationRunner：如果你不想直接监听ApplicationReadyEvent事件，你也可以使用CommandLineRunner或ApplicationRunner接口。这两个接口都定义了一个run方法，Spring Boot会在所有Spring Beans都初始化完成之后调用这个方法。<br>
使用@PostConstruct注解：方法在依赖注入后、Bean 初始化阶段执行，不是构造函数结束后立即执行；此时整个应用未必已就绪，不应依赖该 Bean 的事务代理进行预热。ApplicationReadyEvent、CommandLineRunner 和 ApplicationRunner 均属于 Spring Boot API。<br>
17、spring中常用注解？<br>
<strong>参考回答：</strong> 组件注解：<br>
@Component：用于标识一个类为Spring的组件，可以被自动扫描并注册为Bean。<br>
@Controller：用于标识一个类为控制器层（Controller）组件，通常用于处理HTTP请求。<br>
@Service：用于标注业务逻辑层组件，即Service层。<br>
@Repository：用于标识一个类为数据访问层（DAO）组件，用于操作数据库。<br>
@RestController：结合@Controller和@ResponseBody，用于标识一个类为RESTful风格的控制器，用于返回JSON或XML数据。<br>
注入注解：<br>
@Autowired：由Spring提供，用于自动注入依赖。它默认按类型匹配的方式在容器查找匹配的Bean，当有且仅有一个匹配的Bean时，Spring将其注入到标注的变量中。<br>
@Qualifier：与@Autowired配合使用，用于在存在多个相同类型的Bean时，指定要注入的Bean的名称或限定符。<br>
@Resource：与@Autowired类似，但除了按类型匹配外，还支持按名称匹配。<br>
@Value：用于注入配置文件中的值或表达式的结果，例如注入一个字符串或数字常量。<br>
Java配置类相关注解：<br>
@Configuration：用于标识一个类为配置类，通常与@Bean一起使用，以替代传统的XML配置文件。<br>
@Bean：注解在方法上，声明当前方法的返回值为一个Bean，用于在配置类中定义Bean。<br>
切面（AOP）相关注解：<br>
@Aspect：声明一个切面，用于定义通知（Advice）和切点（Pointcut）。<br>
@Before、@After、@AfterReturning、@AfterThrowing、@Around：这些注解用于定义通知，分别表示在方法执行前、后、返回后、抛出异常后和环绕方法执行通知。<br>
其他常用注解：<br>
@RequestMapping：用于映射Web请求到特定的处理器方法。<br>
@PathVariable：用于从URI模板变量中解析值，并绑定到方法参数上。<br>
@RequestParam：用于从请求参数中解析值，并绑定到方法参数上。<br>
@RequestBody：用于将HTTP请求正文转换为Java对象。<br>
@ResponseBody：用于将Java对象转换为HTTP响应正文。<br>
18、spring中注入方式？<br>
<strong>参考回答：</strong> set()方法注入；<br>
构造器注入：①通过index设置参数的位置；②通过type设置参数类型；<br>
静态工厂注入；<br>
实例工厂；<br>
19、spring中单例Bean是线程安全的吗？<br>
<strong>参考回答：</strong> 单例本身不保证线程安全，也不必然不安全；关键是是否共享可变状态及如何同步。优先让服务保持无状态，将请求数据放在方法局部变量中；仅将 singleton 改成 prototype 不能修复所有并发问题，尤其是直接注入单例时仍只注入一次。<br>
20、SpringMVC的执行流程?<br>
用户发送请求至前端控制器 DispatcherServlet。<br>
DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。<br>
处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找)，生成处理器及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。<br>
DispatcherServlet 调用 HandlerAdapter 处理器适配器。<br>
HandlerAdapter 经过适配调用具体的处理器(Controller，也叫后端控制器)<br>
Controller 执行完成返回 ModelAndView。<br>
HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。<br>
DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。<br>
ViewReslover 解析后返回具体 View。<br>
DispatcherServlet 根据 View 进行渲染视图（即将模型数据填充至视图中）。<br>
DispatcherServlet 响应用户。</p>
<p>21、解释一下DispatcherServlet的作用？(了解)<br>
DispatcherServlet是Spring MVC的前端控制器，它负责接收所有的HTTP请求，并根据请求的类型和内容，将请求分发到相应的Controller进行处理。同时，它也负责将Controller返回的结果传递给视图进行渲染。<br>
22、Spring MVC的主要组件？（了解）<br>
Spring MVC的主要组件包括DispatcherServlet（前端控制器）<br>
HandlerMapping（处理器映射器）<br>
HandlerAdapter（处理器适配器）<br>
Controller（控制器）<br>
ModelAndView（模型和视图）<br>
ViewResolver（视图解析器）。<br>
23、SpringMVC 的常用注解有哪些？<br>
@RequestMapping ：用于处理请求 url 映射的注解，可用于类或方法上。用于类上，则表示类中的所有响应请求的方法都是以该地址作为父路径；<br>
@RequestBody ：注解实现接收 HTTP 请求的 json 数据，将 json 转换为 Java对象；<br>
@ResponseBody ：注解实现将 Controller 方法返回对象转化为 json 对象响应给客户<br>
24、@Controller注解的作用？（了解）<br>
在Spring MVC 中，控制器Controller 负责处理由DispatcherServlet 分发的请求，它把用户请求的数据经过业务处理层处理之后封装成一个Model ，然后再把该Model 返回给对应的View 进行展示。在Spring MVC 中提供了一个非常简便的定义Controller 的方法，你无需继承特定的类或实现特定的接口，只需使用@Controller 标记一个类是Controller ，然后使用@RequestMapping 和@RequestParam 等一些注解用以定义URL 请求和Controller 方法之间的映射，这样的Controller 就能被外界访问到。此外Controller 不会直接依赖于HttpServletRequest 和HttpServletResponse 等HttpServlet 对象，它们可以通过Controller 的方法参数灵活的获取到。<br>
@Controller 用于标记在一个类上，使用它标记的类就是一个Spring MVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法，并检测该方法是否使用了@RequestMapping 注解。@Controller 只是定义了一个控制器类，而使用@RequestMapping 注解的方法才是真正处理请求的处理器。单单使用@Controller 标记在一个类上还不能真正意义上的说它就是Spring MVC 的一个控制器类，因为这个时候Spring 还不认识它。那么要如何做Spring 才能认识它呢？这个时候就需要我们把这个控制器类交给Spring 来管理。有两种方式：<br>
在Spring MVC 的配置文件中定义MyController 的bean 对象。<br>
在Spring MVC 的配置文件中告诉Spring 该到哪里去找标记为@Controller 的Controller 控制器。<br>
25、Spring事务的实现方式和原理以及隔离级别？<br>
在使用Spring框架时，可以有两种使用事务的方式，一种是编程式的，一种是申明式的， @Transactional注解就是申明式的。<br>
首先，事务这个概念是数据库层面的，Spring只是基于数据库中的事务进行了扩展，以及提供了一些能让程序员更加方便操作事务的方式。<br>
比如通过代理调用 @Transactional 方法时，同一线程、同一事务资源且参与同一事务的 SQL 可统一提交或回滚；新线程、独立事务和其他数据源不会自动纳入这一原子边界。<br>
事务拦截器读取事务属性，委托 PlatformTransactionManager 获取、提交或回滚事务；以 JDBC 新事务为例通常会关闭连接的自动提交，不能将这一细节泛化为所有事务管理器。正常返回也可能因 rollback-only 标记而回滚。<br>
当然，针对哪些异常回滚事务是可以配置的，可以利用@Transactional注解中的rollbackFor属性进行配置，默认情况下会对RuntimeException和Error进行回滚。<br>
spring事务隔离级别：<br>
ISOLATION_DEFAULT：这是个 PlatfromTransactionManager 默认的隔离级别，使用数据库默认的事务隔离级别。<br>
ISOLATION_READ_UNCOMMITTED：读未提交，允许事务在执行过程中，读取其他事务未提交的数据。<br>
ISOLATION_READ_COMMITTED：读已提交，允许事务在执行过程中，读取其他事务已经提交的数据。<br>
ISOLATION_REPEATABLE_READ：防止脏读和不可重复读，但按隔离级别定义仍可能有幻读，实际行为取决于数据库实现和读取方式。<br>
ISOLATION_SERIALIZABLE：保证并发执行的效果等价于某个串行顺序，不代表数据库必须让所有事务物理上逐个执行。</p>
]]></content:encoded>
    </item>
    <item>
      <title>操作系统</title>
      <link>https://qiuzhiwang.vip/javabgw/czxt.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/czxt.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">操作系统</source>
      <description>理解进程线程、虚拟内存、页表与 TLB、缺页、死锁和 Linux IO，连接操作系统机制与 Java 后端排障。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>这页建议按“进程线程 -&gt; 内存 -&gt; 调度 -&gt; IO”顺序复习。<br>
面试时优先说结论和场景，再补原理细节，回答会更像真实项目参与者。</p>
</div>
<p><strong>适用范围：</strong>操作系统机制以带 MMU 的 Linux 为例；Linux API 语义参考 Linux man-pages，调度补充区分 Linux 6.6 起的变化。Java 示例以 Java SE 21 为基线，区分平台线程和 Java 21 正式引入的虚拟线程，不把 JVM 内存区域当作所有语言的操作系统概念。</p>
<h2>快速导航</h2>
<ol>
<li>先掌握：<code>进程/线程</code>、<code>用户态/内核态</code>、<code>上下文切换</code>。</li>
<li>再掌握：<code>死锁</code>、<code>内存分页</code>、<code>页面置换</code>。</li>
<li>冲刺补强：<code>IO 模型</code>、<code>零拷贝</code>、<code>调度策略</code>。</li>
</ol>
<h2>1、进程和线程的区别是什么？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>进程是资源分配的基本单位，线程是 CPU 调度的基本单位。</li>
<li>操作系统线程共享进程的地址空间、打开的文件等资源，各有寄存器上下文和栈。Java 层面，堆和方法区由线程共享，程序计数器与 JVM 栈线程私有；本地方法栈由具体 JVM 实现决定，不能将这些 JVM 术语直接推广到其他语言。</li>
<li>进程切换成本通常高于线程切换，因为涉及更多资源状态的保存与恢复。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>进程更像“独立车间”，线程更像“车间里的工人”。</li>
<li>多进程隔离性更好，稳定性高；多线程通信更方便，开销更小。</li>
<li>Java 平台线程常通过线程池复用，减少创建和销毁成本。Java 21 虚拟线程由 JVM 调度到承载它的少量平台线程上，适合大量等待 IO 的任务，通常每任务创建一个虚拟线程，无须池化虚拟线程；CPU 密集任务仍受可用 CPU 限制。</li>
</ul>
<p>依据：<a href="https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-2.html#jvms-2.5" target="_blank" rel="noopener noreferrer">JVMS 21 运行时数据区</a>、<a href="https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html" target="_blank" rel="noopener noreferrer">Java 21 虚拟线程指南</a>。</p>
<h2>2、并发和并行有什么区别？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>并发：同一时间段内处理多个任务，宏观同时，微观可能轮流执行。</li>
<li>并行：同一时刻多个任务同时执行，通常依赖多核 CPU。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>单核 CPU 主要是并发，多核 CPU 能实现并行。</li>
<li>面试中可以补一句：我们平时说“并发编程”，工程上通常是“并发 + 并行”混合场景。</li>
</ul>
<h2>3、什么是用户态和内核态？为什么要区分？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>用户态权限受限，不能直接访问硬件和关键内核资源。</li>
<li>内核态权限高，可以执行特权指令和硬件操作。</li>
<li>通过权限隔离提升系统稳定性和安全性。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>应用代码大多运行在用户态，涉及磁盘、网络、进程管理等能力时，通过系统调用陷入内核态。</li>
<li>这种分层设计避免了业务程序直接操作硬件导致系统崩溃。</li>
</ul>
<h2>4、什么是系统调用？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>系统调用是用户程序访问内核服务的标准入口。</li>
<li>常见系统调用：<code>read</code>、<code>write</code>、<code>open</code>、<code>close</code>、<code>fork</code>。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>应用层无法直接操作设备，必须通过系统调用由内核代为执行。</li>
<li>一次真正的系统调用会进入内核并返回用户态，但不一定切换到另一个线程：权限级别切换与调度上下文切换是两回事。调用阻塞或发生抢占时才可能调度其他线程。</li>
</ul>
<h2>5、线程上下文切换为什么昂贵？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>需要保存和恢复寄存器、程序计数器、栈指针等上下文。</li>
<li>工作集变化或迁移到另一 CPU 可能降低缓存命中率；同一进程的线程共享地址空间，切换并不必然刷新 TLB，也不会必然清空 CPU 缓存。地址空间切换及 TLB 保留策略取决于 CPU 和内核实现。</li>
<li>切换本身不做业务计算，属于纯开销。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>并发不是线程越多越好。线程数过高会把时间花在切换而非计算上。</li>
<li>在 Java 项目中应结合任务类型和 CPU 核数配置线程池，而不是盲目放大。</li>
</ul>
<h2>6、进程通信（IPC）方式有哪些？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>匿名管道（Pipe）：Linux 中是单向字节流，常通过继承文件描述符用于有亲缘关系的进程；双向通信可用两条管道。命名管道（FIFO）可供具有相应权限的无亲缘关系进程打开。</li>
<li>消息队列：按消息传递，解耦较好。</li>
<li>共享内存：速度快，但要配合同步机制。</li>
<li>信号量：用于并发控制。</li>
<li>Socket：既可用于跨主机通信（TCP/UDP），也可通过 Unix domain socket 用于本机 IPC。</li>
</ul>
<p>管道语义依据：<a href="https://man7.org/linux/man-pages/man7/pipe.7.html" target="_blank" rel="noopener noreferrer">Linux pipe(7)</a>。</p>
<p><strong>参考回答：</strong></p>
<ul>
<li>本机高性能场景常用共享内存 + 锁；跨机器服务通信一般使用 Socket（如 TCP）。</li>
</ul>
<h2>7、线程同步方式有哪些？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>互斥锁：同一时刻只允许一个线程访问临界区。</li>
<li>读写锁：读共享、写独占，适合读多写少。</li>
<li>信号量：控制并发访问资源数量。</li>
<li>条件变量：线程间等待/通知协作。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>Java 中常见实现对应 <code>synchronized</code>、<code>ReentrantLock</code>、<code>ReadWriteLock</code>、<code>Semaphore</code>、<code>Condition</code>。</li>
<li>选型关键是冲突比例、是否可中断、公平性和可重入需求。</li>
</ul>
<h2>8、死锁产生的条件与避免方式</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>四个必要条件：互斥、请求并保持、不可剥夺、循环等待。</li>
<li>破坏任意一个条件即可避免死锁。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>工程上可通过统一加锁顺序破坏循环等待；使用可超时的锁时，超时后必须释放已持有的锁并退出或退避重试。单纯缩小临界区只降低冲突，不能保证消除死锁。</li>
<li>排查时可结合 <code>jstack</code>、线程 dump、监控告警定位阻塞链路。</li>
</ul>
<h2>9、什么是虚拟内存？为什么需要它？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>给每个进程提供独立的虚拟地址空间；其中可以有未映射的空洞，虚拟地址连续也不代表物理页连续。</li>
<li>通过页表映射到物理内存，实现隔离、扩展和按需加载。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>虚拟地址空间可以大于物理内存，但不保证所有申请都能成功、所有页面都能同时驻留。按需分配、文件映射和可选的 swap 支持不同的内存使用方式；虚拟内存不等于交换分区。</li>
<li>代价是地址转换和缺页处理会引入额外开销。</li>
</ul>
<p>机制上，CPU 优先用 TLB 缓存地址翻译；TLB 未命中时查页表，并不意味着一定缺页。合法但尚未驻留的页面可由内核分配物理页或从文件/swap 调入后重试指令；非法地址或权限违规不能靠调页解决，在 Linux 用户进程中通常表现为 <code>SIGSEGV</code>。</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>虚拟地址 → TLB / 页表 → 物理页</span></span>
<span class="line"><span>                     ↘ 页面不在内存 → 缺页处理 → 建立映射 → 重试</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>依据：<a href="https://docs.kernel.org/admin-guide/mm/concepts.html" target="_blank" rel="noopener noreferrer">Linux 内存管理概念</a>。</p>
<h2>10、页面置换算法有哪些？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>FIFO：先进先出，实现简单但可能出现 Belady 异常。</li>
<li>LRU：淘汰最近最少使用页面，效果较好但实现成本更高。</li>
<li>LFU：淘汰访问次数最少页面，适合热点稳定场景。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>实际系统往往采用 LRU 近似算法，在性能和命中率之间折中。</li>
</ul>
<h2>11、常见 CPU 调度算法</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>先来先服务（FCFS）：简单但可能导致短任务等待长任务。</li>
<li>短作业优先（SJF）：平均等待时间较优，但依赖任务时长预测。</li>
<li>时间片轮转（RR）：公平性好，适合分时系统。</li>
<li>多级反馈队列：结合优先级和时间片，工程中较常见。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>服务端系统更关注吞吐和响应延迟平衡，通常使用更复杂的动态调度策略。Linux 6.6 起，公平调度类开始从 CFS 转向 EEVDF：先选尚欠 CPU 时间的合格任务，再比较虚拟截止时间。这不是把整个 Linux 调度器替换成一种算法；实时调度类仍有各自策略。</li>
</ul>
<p>依据：<a href="https://docs.kernel.org/scheduler/sched-eevdf.html" target="_blank" rel="noopener noreferrer">Linux EEVDF 调度器说明</a>。</p>
<h2>12、IO 模型有哪些？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>阻塞 IO：以 socket 的 <code>read</code> 为例，无数据时可等待，返回时数据已复制到用户缓冲区；也可能返回 EOF、错误或少于请求量的数据。</li>
<li>非阻塞 IO：当前无法继续时返回 <code>EAGAIN</code>/<code>EWOULDBLOCK</code>；可以重试，也可以结合就绪通知，避免忙轮询。</li>
<li>IO 多路复用：一个线程监听多个连接（<code>select</code>/<code>poll</code>/<code>epoll</code>）。</li>
<li>异步 IO：内核完成 IO 后通知用户线程。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>高并发网络服务常用 IO 多路复用，Java 里对应 NIO 的 Selector/Reactor 思路；NIO 也包含阻塞模式的通道，不能把全部 NIO 都等同于非阻塞。</li>
</ul>
<p>Linux 的 <code>epoll_wait</code> 通知的是“可以尝试 IO”，不负责将数据复制到用户缓冲区，等待事件时本身可以阻塞；随后仍需调用 <code>read</code>/<code>write</code>。异步 IO 的完成通知则表示提交的操作已经完成。对于边缘触发（ET），通常配合非阻塞描述符，持续读写直到 <code>EAGAIN</code>，避免遗漏仍待处理的数据。</p>
<p>依据：<a href="https://man7.org/linux/man-pages/man7/epoll.7.html" target="_blank" rel="noopener noreferrer">Linux epoll(7)</a>、<a href="https://man7.org/linux/man-pages/man2/read.2.html" target="_blank" rel="noopener noreferrer">read(2)</a>。</p>
<h2>13、什么是零拷贝？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>目标是减少用户态和内核态之间的数据复制次数。</li>
<li>Linux <code>sendfile</code> 在内核中传输文件描述符间的数据，避免传统 <code>read</code> + <code>write</code> 经用户缓冲区中转；<code>mmap</code> 将文件映射到地址空间，但随后普通 <code>write</code> 仍可能发生数据复制，不能仅凭用了映射就断言全程零复制。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>零拷贝可以降低 CPU 开销并提升吞吐，特别适合大文件传输和高流量网关场景。它不意味着数据完全不移动，也不保证消除内核内部复制；效果取决于传输路径及硬件能力。需要在用户态压缩、加密或改写正文时，不能直接套用原样发送文件的路径。</li>
<li><code>sendfile</code> 成功也可能只发送部分字节，必须按实际返回值推进偏移并处理重试。</li>
</ul>
<p>依据：<a href="https://man7.org/linux/man-pages/man2/sendfile.2.html" target="_blank" rel="noopener noreferrer">Linux sendfile(2)</a>。</p>
<h2>14、面试高频追问怎么答得更像真实项目参与者？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>回答结构建议：结论 -&gt; 机制 -&gt; 线上场景 -&gt; 权衡。</li>
<li>有真实测量时给出线程池大小、QPS、延迟和测试条件；没有测量时明确说明是机制分析，不编造项目经历或效果数字。</li>
</ul>
<p><strong>机制分析示例：</strong></p>
<ul>
<li>如果增大线程池后延迟反而升高，可以检查 CPU 使用率、可运行线程数与上下文切换，再排查锁竞争、IO 等待和下游容量。</li>
<li>若证据指向 CPU 饱和，继续增加线程通常不能提升算力；若主要是 IO 等待，则还需结合下游并发上限评估线程数。</li>
<li>这只是分析路径，是否改善吞吐或延迟需要实际测量后才能判断。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>多线程与并发编程</title>
      <link>https://qiuzhiwang.vip/javabgw/dxcybfbc.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/dxcybfbc.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">多线程与并发编程</source>
      <description>梳理 Java 线程、同步器、AQS、ThreadLocal 和线程池，结合共享状态与资源生命周期解释并发正确性。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>并发题建议按“锁 -&gt; 可见性 -&gt; 原子性 -&gt; 线程池”顺序复习。<br>
每道题尽量补一句“线上场景”或“性能权衡”，会更像真实工程实践。</p>
</div>
<p>适用版本：语言语义与并发 API 以 Java SE 21 为依据，基础用法同样适用于 JDK 8/17。对象头和锁实现另行限定为传统 HotSpot 布局及历史实现，不能作为所有 JVM、所有版本的固定结构。</p>
<h2>synchronized的用法</h2>
<h3>1、synchronized的用法</h3>
<h4>1. 修饰实例方法</h4>
<p>当<code>synchronized</code>用于修饰一个<strong>实例方法</strong>时，该方法被称为同步方法。这意味着在同一时刻，只有一个线程可以进入该方法的代码块。这种同步是基于对象的实例锁，即每个对象都有一个锁，当线程进入一个对象的同步方法时，它必须先获得该对象的锁。如果另一个线程已经持有该对象的锁，则该线程必须等待，直到锁被释放。</p>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>public synchronized void syncMethod() {</span></span>
<span class="line"><span>    // method body</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>2. 修饰静态方法</h4>
<p>修饰<strong>静态方法</strong>时，同步是基于类对象（Class对象）的锁。这意味着所有实例共享同一把锁。当一个线程进入一个静态的同步方法时，它获得的是该类的锁。如果其他线程已经持有该类的锁，则新来的线程必须等待。</p>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>public static synchronized void staticSyncMethod() {</span></span>
<span class="line"><span>    // method body</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>3. 修饰代码块</h4>
<p>当<code>synchronized</code>用于修饰一个<strong>代码块</strong>时，它允许你指定一个锁对象。这种方式提供了更细粒度的控制，因为它允许你只同步访问特定资源和数据部分的代码。代码块中的代码在执行时必须持有指定的锁对象。</p>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>private final Object lock = new Object(); // 同一实例的调用共享同一把锁</span></span>
<span class="line"><span></span></span>
<span class="line"><span>public void someMethod() {</span></span>
<span class="line"><span>    synchronized(lock) {</span></span>
<span class="line"><span>        // code block</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>锁必须被访问同一共享状态的线程共同使用；方法内每次 <code>new Object()</code> 得到不同的锁，无法实现调用之间的互斥。实例锁与该类的 <code>Class</code> 锁也是两把不同的锁。</p>
<h3>2、锁升级</h3>
<p><strong>（1）、内存布局 （对象的内存布局）</strong></p>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755157817012-e07218cb-d462-4ca5-b80e-9087266d540a-f44ce222-e14edf80fd5d-514w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755157817012-e07218cb-d462-4ca5-b80e-9087266d540a-f44ce222-e14edf80fd5d-514w.webp" srcset="/images/optimized/1755157817012-e07218cb-d462-4ca5-b80e-9087266d540a-f44ce222-e14edf80fd5d-514w.webp 514w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="514" height="562" decoding="async" data-image-source="/javabgw-img/1755157817012-e07218cb-d462-4ca5-b80e-9087266d540a.png" data-image-width="514" data-image-height="562" data-image-detail="/images/optimized/1755157817012-e07218cb-d462-4ca5-b80e-9087266d540a-f44ce222-e14edf80fd5d-514w.webp"></a><figcaption>img</figcaption></figure>
<p>在 Java 中，每个对象的内存布局是由 Java 虚拟机（JVM）的具体实现决定的，例如 Oracle HotSpot JVM。这里，我们可以讨论 HotSpot JVM 中普通（非数组）对象的典型内存布局。对象的内存布局通常包括三部分：对象头（Header）、实例数据（Instance Data）和对齐填充（Padding）。</p>
<h4>1. 对象头（Object Header）</h4>
<p>以下按 64 位 HotSpot、传统对象头说明：对象头包含标记字（Mark Word）和类型指针；这不是 Java 语言规范规定的布局。</p>
<ul>
<li><strong>Mark Word</strong>：这部分通常占用 8 字节，用于存储对象自身的运行时数据，如哈希码、锁信息（锁状态标记、线程持有的锁等）、垃圾收集信息（如年龄、是否存活等）。</li>
<li><strong>Class Pointer</strong>：这部分通常也占用 4 字节（默认情况下)</li>
</ul>
<h4>2. 实例数据（Instance Data）</h4>
<p>实例数据部分占用的字节数取决于对象的具体字段。Java 数据类型的字节如下：</p>
<ul>
<li><code>boolean</code> 和 <code>byte</code>：1 字节</li>
<li><code>char</code> 和 <code>short</code>：2 字节</li>
<li><code>int</code> 和 <code>float</code>：4 字节</li>
<li><code>long</code> 和 <code>double</code>：8 字节</li>
<li>引用类型（如对象引用）：在 64 位 JVM 中通常是 4 字节（使用压缩指针时）或 8 字节。</li>
</ul>
<p>字段的排列可能会由 JVM 进行优化，以减少由于内存对齐引起的空间浪费。</p>
<h4>3. 对齐填充（Padding）</h4>
<p>为了使对象的总大小是 8 字节的倍数（这里假定 HotSpot 对象对齐为 8 字节；并非内存寻址的最小单位），JVM 可能会在实例数据之后添加一些填充字节。这样做是为了确保内存对齐，有助于提高 CPU 缓存的效率。</p>
<p><strong>示例</strong></p>
<p>考虑一个简单的 Java 对象：</p>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>public class Example {</span></span>
<span class="line"><span>    int a;    // 4 字节</span></span>
<span class="line"><span>    boolean b; // 1 字节</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在这个例子中，对象的内存布局可能如下：</p>
<ul>
<li>对象头：12 字节（压缩类指针）或 16 字节（非压缩）</li>
<li>实例数据：<code>int</code> 4 字节 + <code>boolean</code> 1 字节 = 5 字节</li>
<li>对齐填充：为了使总大小为 8 字节的倍数，压缩类指针时需要添加 7 字节，非压缩类指针时需要添加 3 字节的填充（按这里的简化字段布局计算）。</li>
</ul>
<p>这样，总的大小可能是 24 字节（12+5+7，使用压缩类指针的情况）或 24 字节（16+5+3，未使用压缩指针）。</p>
<p>Java 示例：简单的 Person 类</p>
<p>考虑下面的 Java 类 <code>Person</code>，它有两个字段：<code>name</code> 和 <code>age</code>。此示例将帮助我们理解类型指针在不涉及继承的情况下如何工作。</p>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>public class Person {</span></span>
<span class="line"><span>    private String name;</span></span>
<span class="line"><span>    private int age;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>    public Person(String name, int age) {</span></span>
<span class="line"><span>        this.name = name;</span></span>
<span class="line"><span>        this.age = age;</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span></span></span>
<span class="line"><span>    public void printInfo() {</span></span>
<span class="line"><span>        System.out.println("Name: " + name + ", Age: " + age);</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span></span></span>
<span class="line"><span>    public static void main(String[] args) {</span></span>
<span class="line"><span>        Person person = new Person("John Doe", 30);</span></span>
<span class="line"><span>        person.printInfo();  // 输出: Name: John Doe, Age: 30</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>类型指针的作用</p>
<p>当我们创建 <code>Person</code> 类的一个实例时：</p>
<ol>
<li>
<p><strong>对象创建</strong>：JVM 在堆内存中为 <code>Person</code> 对象分配空间。这块空间包括对象头、实例数据（name 和 age），以及可能的一些填充字节来满足内存对齐要求。</p>
</li>
<li>
<p><strong>对象头</strong>：对象头包含了几个重要的组成部分，其中关键的部分是类型指针。</p>
</li>
<li>
<p><strong>类型指针</strong>：这个指针指向 <code>Person</code> 类的元数据。该元数据包含了有关 <code>Person</code> 类的各种信息，如字段布局、方法表、类加载器信息等。</p>
</li>
<li>
<p><strong>使用类型指针</strong>：</p>
</li>
<li>
<p>当调用 <code>person.printInfo()</code> 方法时，即使这个例子没有显示继承和多态，类型指针仍然起到关键作用。JVM 使用类型指针查找到 <code>Person</code> 类的元数据，进而定位到 <code>printInfo()</code> 方法的实际代码。</p>
</li>
<li>
<p>类型指针关联的是类元数据；实际调用可能经过解释器、虚方法分派或 JIT 内联。它本身不保证任意热部署都能成功，类重定义还受 JVM 的能力与限制约束。</p>
</li>
</ol>
<p><strong>（2）、markword 里面具体存的是什么东西</strong></p>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755157817055-076dc030-d9a6-471e-9a13-6dc20dff8daa-cd5ab000-05efbe33c9b2-1257w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755157817055-076dc030-d9a6-471e-9a13-6dc20dff8daa-cd5ab000-05efbe33c9b2-800w.webp" srcset="/images/optimized/1755157817055-076dc030-d9a6-471e-9a13-6dc20dff8daa-cd5ab000-05efbe33c9b2-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1257" height="631" decoding="async" data-image-source="/javabgw-img/1755157817055-076dc030-d9a6-471e-9a13-6dc20dff8daa.png" data-image-width="1257" data-image-height="631" data-image-detail="/images/optimized/1755157817055-076dc030-d9a6-471e-9a13-6dc20dff8daa-cd5ab000-05efbe33c9b2-1257w.webp"></a><figcaption>img</figcaption></figure>
<h3>3、synchronized早期为什么叫做重量级锁</h3>
<p>在JDK早期 synchronized被称之为重量级锁，是因为我的JVM也是在用户态，有线程向jvm申请锁的时候，那我们JVM需要向操作系统申请锁，那这个过程涉及到从用户态切换到内核态，再从内核态切换到用户态，所以称synchronized为重量级锁。</p>
<p>比较底层的实现（操作系统层面）</p>
<p>当一个 Java 线程尝试进入一个由 <code>synchronized</code> 关键字保护的同步块或方法时，它首先需要获得锁。如果锁已被另一个线程持有，尝试获取锁的线程需要等待。在 Java 的早期实现中，这种等待是通过操作系统的互斥量（Mutex）实现的。这意味着以下几个步骤：</p>
<ul>
<li><strong>锁请求</strong>：当线程无法立即获取锁时，JVM 会在用户态下发出一个系统调用，请求操作系统的互斥量。</li>
<li><strong>状态切换</strong>：执行系统调用（如请求互斥量）涉及从用户态切换到内核态，因为互斥量的管理是在操作系统内核中处理的。</li>
<li><strong>等待和唤醒</strong>：线程在内核控制的等待队列中阻塞，直到锁变为可用。当锁释放时，操作系统负责唤醒等待的线程，这通常再次涉及从内核态回到用户态的切换。</li>
</ul>
<h3>4、锁升级过程</h3>
<p>随着 Java 平台的发展，特别是从 JDK 6 开始，对 <code>synchronized</code> 锁的实现进行了优化。引入了如轻量级锁和偏向锁等技术，使得许多锁操作现在可以完全在用户态下完成，避免了频繁的模式切换。当锁竞争不激烈时，这些优化可以显著减少锁的开销。</p>
<p>下面是 JDK 8 HotSpot 开启偏向锁时的历史实现示意，并不是每次加锁都必须经过的状态链。JDK 15 已默认禁用并废弃偏向锁，因此不要用此流程解释 JDK 17/21 的默认行为。锁位、栈锁记录和膨胀条件属于实现细节，不属于 <code>synchronized</code> 的语言契约。参见 <a href="https://www.oracle.com/java/technologies/javase/15all-relnotes.html" target="_blank" rel="noopener noreferrer">Oracle JDK 15 发布说明：Biased Locking</a>。</p>
<p><strong>无锁状态</strong></p>
<ul>
<li><strong>锁状态</strong>: 当没有线程尝试获取对象的锁时，对象处于无锁状态。无锁并不意味着对象不能被锁定，而是表明当前没有锁定行为。</li>
<li><strong>标记位</strong>: 在无锁状态下，对象头的标记位通常设置为01（在开启偏向锁的情况下），这表示这个对象可以进入偏向锁状态。</li>
</ul>
<p><strong>偏向锁</strong></p>
<p>偏向锁是为了优化单个线程多次获取同一个锁的情况。当第一次有线程获取锁时，JVM会将对象头标记为偏向这个线程，以后该线程进入和退出同步块时不需要进行全面的同步。</p>
<ul>
<li><strong>锁状态</strong>: 对象头偏向第一个获得它的线程。</li>
<li><strong>标记位</strong>: 对象头的标记位设置为偏向锁模式，其中包括偏向线程的ID。状态位是01，偏向标志位设置为1，后面跟着偏向的线程ID。</li>
</ul>
<p><strong>轻量级锁</strong></p>
<p>在历史偏向锁实现中，其他线程访问可能触发偏向撤销或重偏向，并不必然升级为轻量级锁。轻量级锁的工作原理是在每个线程的栈帧中创建一个锁记录（Lock Record），它包含了锁对象指针。</p>
<ul>
<li><strong>锁状态</strong>: 当锁对象已被线程锁定，但没有其他线程争用它时，使用轻量级锁。</li>
<li><strong>标记位</strong>: 轻量级锁的状态位为00，锁指针指向线程栈上的锁记录。</li>
</ul>
<p><strong>重量级锁</strong></p>
<p>当轻量级锁的自旋锁尝试失败，即有多个线程竞争同一个锁时，轻量级锁会升级为重量级锁。这时，JVM会在操作系统层面挂起其他试图获取这个锁的线程。</p>
<ul>
<li><strong>锁状态</strong>: 涉及操作系统帮助管理线程阻塞和唤醒的重量级锁。</li>
<li><strong>标记位</strong>: 重量级锁的状态位通常为10，对象头的锁指针指向一个监视器对象（Monitor），该对象包含了锁的所有者、等待锁的线程列表等信息。</li>
</ul>
<p><strong>补充内容：</strong></p>
<p>在Java中，轻量级锁升级到重量级锁的自旋次数并没有一个固定的标准，因为这个值可以根据JVM的具体实现和运行时的调优参数而变化。不过，一般来说，HotSpot JVM中有几个因素决定了自旋的次数：</p>
<p>自旋是运行时实现策略，不能按“固定重试 10 次后必然膨胀”理解；竞争情况、JVM 版本和锁模式都会影响处理方式。历史参数 <code>-XX:PreBlockSpin</code> 也不应作为 JDK 17/21 通用调优选项。</p>
<p><strong>synchronized</strong>在java代码层面表示</p>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>package com.javatiaozao;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>public class Main {</span></span>
<span class="line"><span>    public static void main(String[] args) {</span></span>
<span class="line"><span>        Object o = new Object();</span></span>
<span class="line"><span>        synchronized (o) {</span></span>
<span class="line"><span>            System.out.println("我已经上锁了");</span></span>
<span class="line"><span>        }</span></span>
<span class="line"><span>        System.out.println("Hello world!");</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>synchronized</strong>在 JVM 字节码层面表示</p>
<div class="language-plain line-numbers-mode" data-highlighter="shiki" data-ext="plain" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-plain"><span class="line"><span>0 new #2 &#x3C;java/lang/Object></span></span>
<span class="line"><span>3 dup</span></span>
<span class="line"><span>4 invokespecial #1 &#x3C;java/lang/Object.&#x3C;init> : ()V></span></span>
<span class="line"><span>7 astore_1</span></span>
<span class="line"><span>8 aload_1</span></span>
<span class="line"><span>9 dup</span></span>
<span class="line"><span>10 astore_2</span></span>
<span class="line"><span>11 monitorenter</span></span>
<span class="line"><span>12 getstatic #3 &#x3C;java/lang/System.out : Ljava/io/PrintStream;></span></span>
<span class="line"><span>15 ldc #4 &#x3C;我已经上锁了></span></span>
<span class="line"><span>17 invokevirtual #5 &#x3C;java/io/PrintStream.println : (Ljava/lang/String;)V></span></span>
<span class="line"><span>20 aload_2</span></span>
<span class="line"><span>21 monitorexit</span></span>
<span class="line"><span>22 goto 30 (+8)</span></span>
<span class="line"><span>25 astore_3</span></span>
<span class="line"><span>26 aload_2</span></span>
<span class="line"><span>27 monitorexit</span></span>
<span class="line"><span>28 aload_3</span></span>
<span class="line"><span>29 athrow</span></span>
<span class="line"><span>30 getstatic #3 &#x3C;java/lang/System.out : Ljava/io/PrintStream;></span></span>
<span class="line"><span>33 ldc #6 &#x3C;Hello world!></span></span>
<span class="line"><span>35 invokevirtual #5 &#x3C;java/io/PrintStream.println : (Ljava/lang/String;)V></span></span>
<span class="line"><span>38 return</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/javabgw-img/1755157817012-e07218cb-d462-4ca5-b80e-9087266d540a.png" type="image/png"/>
    </item>
    <item>
      <title>ElasticSearch</title>
      <link>https://qiuzhiwang.vip/javabgw/ElasticSearch.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/ElasticSearch.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">ElasticSearch</source>
      <description>从倒排索引、分词、近实时搜索和缓存到分片副本，理解 Elasticsearch 的查询机制与数据同步边界。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>建议先看题目目录，再按“概念 -&gt; 原理 -&gt; 场景 -&gt; 优化”顺序复习。<br>
每题先讲结论，再补关键机制和项目实践，回答会更稳。</p>
</div>
<p>本文以 <strong>Elasticsearch 8.19（自建集群）</strong> 为基准；MySQL 对比以 <strong>MySQL 8.0</strong> 为准。示例采用 Kibana Dev Tools 请求格式，未在实际集群执行。</p>
<h2>1、ES是什么，为什么要用ES</h2>
<p>ES是一个开源的分布式搜索和分析引擎，它被设计用于快速、灵活地搜索和分析大量数据。</p>
<ul>
<li>Elasticsearch提供出色的全文搜索能力，适用于需要对大量数据进行高效搜索和查询的应用场景，使用倒排索引技术，能够快速高效地执行文本搜索，支持模糊搜索等。可以用来解决使用数据库进行模糊搜索时存在的性能问题；</li>
<li>Elasticsearch能够提供近实时的搜索和分析结果（写入成功不代表立即可被搜索到），适用于对数据的及时性有要求的场景;</li>
<li>支持多种数据类型，适用于处理不同结构和格式的数据，使其更灵活和通用;</li>
</ul>
<p>和Mysql相比：</p>
<ul>
<li>MySQL 的查询性能取决于索引与查询方式，不能笼统认为大数据量下一定慢。<code>LIKE '%关键词%'</code> 通常无法通过普通 B-tree 索引进行有效的范围定位；全文检索则应比较各自的全文索引能力。</li>
<li>MySQL 8.0 的全文索引支持 <code>CHAR</code>、<code>VARCHAR</code>、<code>TEXT</code>，也支持全文解析器：例如内置的 ngram 解析器可处理中文、日文和韩文。ES 的优势在于更丰富的分析器、相关性查询与分布式搜索能力，而不是 MySQL“不支持分词”。参见 <a href="https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html" target="_blank" rel="noopener noreferrer">MySQL 8.0 官方全文搜索说明</a> 和 <a href="https://dev.mysql.com/doc/refman/8.0/en/fulltext-search-ngram.html" target="_blank" rel="noopener noreferrer">ngram 解析器</a>。</li>
</ul>
<h2>2、ES索引的构建</h2>
<p>Elasticsearch采用Rest风格API，因此其API就是一次http请求，你可以用任何工具发起http请求。</p>
<p>可以通过RESTful API或相关的客户端库来操作创建索引。</p>
<p>创建索引的请求格式：</p>
<ul>
<li>请求方式：PUT</li>
<li>请求路径：/索引库名</li>
<li>请求参数：json格式：</li>
</ul>
<div class="language-http line-numbers-mode" data-highlighter="shiki" data-ext="http" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-http"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">PUT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> /articles</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "settings"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "number_of_shards"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">3</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "number_of_replicas"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">2</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "mappings"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "properties"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "title"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">"type"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"text"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">"analyzer"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"standard"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      "status"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">"type"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"keyword"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>number_of_shards</code> 是主分片数量；<code>number_of_replicas</code> 是<strong>每个主分片</strong>的副本数，因此这里共需分配 9 个分片副本（3 主 + 6 副）。同一主分片及其副本不能放在同一节点，至少需要 3 个符合分配条件的数据节点才能全部分配；单节点使用这组设置会留下未分配副本，集群呈 yellow。主分片数通常在创建时确定，副本数可以动态调整，不能把副本数误当作集群节点数。</p>
<p><code>text</code> 会经过分析器生成词项，适合全文检索；<code>keyword</code> 适合状态等精确匹配字段。<code>standard</code> 并不等于面向中文的专用分词器，应按语言和检索需求选择分析器。参见 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.19/indices-create-index.html" target="_blank" rel="noopener noreferrer">Create index API</a>。</p>
<h2>3、ES为啥查询这么快</h2>
<ul>
<li>倒排索引：Elasticsearch使用倒排索引结构，这是一种能够快速定位文档的数据结构。倒排索引通过记录每个词项(terms)出现在哪些文档中来加速搜索。相比于传统的行式数据库存储结构，倒排索引在文本搜索场景中的性能更好。</li>
<li>分布式搜索引擎：Elasticsearch是一个分布式系统，可以在多个节点上运行。它将数据分成多个分片，每个分片可以独立运行在不同的节点上。这种分布式架构允许Elasticsearch并行处理查询请求，提高了查询的速度和扩展性。</li>
<li>缓存机制：节点查询缓存可复用符合条件的 filter 上下文查询结果；分片请求缓存默认主要缓存 <code>size: 0</code> 的请求结果，例如聚合。缓存有准入、失效和容量限制，不是所有查询都会命中。操作系统文件缓存也会影响读取速度。</li>
<li>按访问方式组织数据：JSON 是请求和文档的表示形式，本身不保证检索快。全文检索主要利用倒排索引；排序、聚合通常利用 <code>doc_values</code>，它在索引时构建，按文档组织字段值。默认不应为了对 <code>text</code> 排序而直接启用耗堆内存的 fielddata，可使用 <code>keyword</code> 子字段。</li>
</ul>
<p>近实时性描述的是<strong>数据何时对搜索可见</strong>，不是查询执行速度。refresh 会使新 segment 可被搜索读取，不要求先完成一次完整的 Lucene commit；它不能等同于持久化保证。8.19 自建集群默认每秒 refresh，但默认只对最近 30 秒内收到过搜索请求的索引周期刷新，这也不是严格的一秒延迟承诺。需要写后搜索可见时，可使用 <code>refresh=wait_for</code> 等待刷新，避免每次写入都强制 <code>refresh=true</code> 产生大量小 segment。</p>
<div class="language-http line-numbers-mode" data-highlighter="shiki" data-ext="http" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-http"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">PUT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> /articles/_doc/1?refresh=wait_for</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{ </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">"title"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"distributed search"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">"status"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"published"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">GET /articles/_search</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{ </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">"query"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">"match"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">"title"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"search"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } } }</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上例在写入成功返回后再执行搜索；<code>wait_for</code> 等待的是搜索可见性，不是跨 MySQL 与 ES 的事务提交。分片并行也有网络与结果归并成本，分片越多不一定越快。</p>
<p>官方资料：<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.19/near-real-time.html" target="_blank" rel="noopener noreferrer">近实时搜索</a>、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.19/docs-refresh.html" target="_blank" rel="noopener noreferrer">refresh 参数</a>、<a href="https://www.elastic.co/docs/reference/elasticsearch/configuration-reference/node-query-cache-settings" target="_blank" rel="noopener noreferrer">节点查询缓存</a>、<a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/shard-request-cache" target="_blank" rel="noopener noreferrer">分片请求缓存</a>、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.19/doc-values.html" target="_blank" rel="noopener noreferrer">doc values</a>。</p>
<h2>4、倒排索引</h2>
<p>传统的我们的检索是通过文章，逐个遍历找到对应关键词的位置。 而倒排索引，恰恰相反，它是从词出发，记载这个词在哪些文章中存在。</p>
<p>倒排索引不只是 FST，而是包含词项词典、词项定位结构和倒排列表（postings）等部分。倒排列表记录包含某词项的文档 ID，还可按字段配置记录词频、位置等信息，用于评分和短语查询。</p>
<p>以 Lucene 9.12 的 Lucene90 BlockTree 格式为例，词典按块存储，FST 用来索引词项前缀并定位词典块；随后从词典找到对应的 postings。FST 通过共享路径压缩词项索引，帮助快速定位，但它本身不是完整的文档倒排列表。具体文件格式随 Lucene codec 和版本变化，不能把这一实现概括为所有版本的唯一结构。</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>文档 1: distributed search</span></span>
<span class="line"><span>文档 2: search engine</span></span>
<span class="line"><span>          ↓ 分析后建立词项与文档的关系</span></span>
<span class="line"><span>词项 distributed → [1]</span></span>
<span class="line"><span>词项 search      → [1, 2]</span></span>
<span class="line"><span>词项 engine      → [2]</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>查询 <code>search</code> 时先定位词项，再读取其倒排列表；多词查询可对列表求交或求并，而不必逐篇扫描全文。<code>match</code> 会先分析查询文本，<code>term</code> 则直接匹配索引中的词项，不能把两者当作相同的全文搜索方式。</p>
<p>参见 <a href="https://lucene.apache.org/core/9_12_0/core/org/apache/lucene/codecs/lucene90/blocktree/Lucene90BlockTreeTermsWriter.html" target="_blank" rel="noopener noreferrer">Lucene 9.12 BlockTree 官方说明</a>。</p>
<h2>5、ES中的数据和数据库同步的问题</h2>
<ul>
<li>同步双写：写 MySQL 后再写 ES，任意一步失败都可能产生不一致；单纯顺序调用不会获得跨系统原子性，需要补偿、重试和对账。</li>
<li>异步双写：使用 MQ 异步写 ES，削峰并降低请求链路耦合，但数据库提交与消息发送之间仍有失败窗口。可采用事务 outbox：在同一数据库事务内写业务数据和待发送事件，再由独立任务投递 MQ；消费者需支持重复消息和失败重试。</li>
<li>定时扫表：通过 <code>updated_at</code> 等变更标记扫描增量，时效性受调度间隔影响。只用时间戳水位可能漏掉同时间记录或晚提交事务，需要重叠扫描、去重与对账；物理删除后的行无法再被扫到，需软删除标记或独立删除事件。</li>
<li>基于 Binlog 的 CDC 同步：读取已提交的数据库变更，可直接写 ES，也可经 MQ 消费后写入 ES。它仍有传输、消费和 refresh 延迟，属于最终一致性方案，需处理初始全量与增量衔接、消费位点和日志保留期。</li>
</ul>
<p>通常以 MySQL 主键作为 ES <code>_id</code>，重复投递同一记录时覆盖同一文档；但固定 ID 只能避免重复文档，不能阻止旧事件覆盖新状态。还需按同一业务键保证顺序，或使用可单调比较的业务版本和 ES 外部版本控制；删除事件也应纳入顺序设计。批量写入时逐项检查 Bulk 响应的失败并重试，不能仅凭 HTTP 200 判定整批成功。</p>
<p>这些是同步架构的设计建议，ES API 本身不提供与 MySQL 的分布式事务。官方 API 依据：<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.19/docs-index_.html" target="_blank" rel="noopener noreferrer">Index API 的 ID 与版本控制</a>、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.19/docs-bulk.html" target="_blank" rel="noopener noreferrer">Bulk API 响应与单项错误</a>。</p>
]]></content:encoded>
    </item>
    <item>
      <title>HashMap 底层原理：JDK 21 扩容、树化条件与线程安全</title>
      <link>https://qiuzhiwang.vip/javabgw/hashmapdcyl.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/hashmapdcyl.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">HashMap 底层原理：JDK 21 扩容、树化条件与线程安全</source>
      <description>沿 HashMap 的哈希定位、容量、扩容和树化阅读源码，区分不同方法的扩容时机与并发使用边界。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<p>HashMap 的底层是哈希桶数组，冲突元素用链表或红黑树组织；哈希分布良好时，<code>put/get</code> 通常接近 O(1)。它允许一个 <code>null</code> 键和多个 <code>null</code> 值，不保证遍历顺序，也不提供并发写入安全。面试中的 <code>8/64/6</code> 是不同实现路径上的阈值，不能合并成“到 8 必树化、到 6 必退化”。</p>
<p><strong>适用版本：</strong> 本文以 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html" target="_blank" rel="noopener noreferrer">Java SE 21 HashMap API</a> 和 <a href="https://github.com/openjdk/jdk/blob/jdk-21-ga/src/java.base/share/classes/java/util/HashMap.java" target="_blank" rel="noopener noreferrer">OpenJDK <code>jdk-21-ga</code> 源码</a> 为核对基准。JDK 8 引入链表与红黑树结合的实现；文中的 JDK 1.7/1.8 对比用于解释历史变化。树化细节属于实现策略，不是 <code>Map</code> 接口对所有 JDK 的承诺。</p>
<h2>1、HashMap 的底层数据结构是什么？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>JDK 1.7：数组 + 链表。</li>
<li>JDK 8 起（本文核对 JDK 21）：数组 + 链表 + 红黑树。</li>
<li>当桶内链表过长且容量达到条件时，链表会树化，提高查询效率。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>HashMap 的底层是哈希桶数组，桶里存放 <code>Node&lt;K, V&gt;</code>。</li>
<li>发生哈希冲突时，桶中通过链表或树组织节点；链表插入触发树化检查后，还要满足表容量条件，具体边界见第 6 题。</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1-b89fa6ef-9141c621c77a-877w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：HashMap 桶数组连接链表或红黑树，节点包含 hash、key、value 和 next"><img alt="HashMap 桶数组连接链表或红黑树，节点包含 hash、key、value 和 next" tabindex="0" loading="lazy" src="/images/optimized/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1-b89fa6ef-9141c621c77a-800w.webp" srcset="/images/optimized/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1-b89fa6ef-9141c621c77a-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="877" height="431" decoding="async" data-image-source="/javabgw-img/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1.png" data-image-width="877" data-image-height="431" data-image-detail="/images/optimized/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1-b89fa6ef-9141c621c77a-877w.webp"></a><figcaption>HashMap 桶数组连接链表或红黑树，节点包含 hash、key、value 和 next</figcaption></figure>
<p>图示表达桶与节点的关系。图中文字“数组容量 &gt;64”为旧示意；JDK 21 在数组长度小于 64 时优先扩容，至少 64 才进入树化分支。链表触发检查的计数与具体方法有关，以第 6 题的源码说明为准。</p>
<h2>2、HashMap 的默认初始化参数有哪些？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>默认初始容量：<code>16</code>。</li>
<li>默认负载因子：<code>0.75</code>。</li>
<li>扩容阈值：<code>capacity * loadFactor</code>。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>默认情况下，HashMap 在第一次 <code>put</code> 时初始化数组容量为 16。</li>
<li>普通 <code>put</code> 新增映射后，若元素个数超过阈值（容量 * 负载因子），触发扩容，容量通常翻倍。</li>
</ul>
<p>这里的默认情况指无参构造；创建空对象时不会立刻分配长度为 16 的数组。<code>size</code> 是整个 Map 的键值对数量，容量是桶数组长度；覆盖已有 key 不增加 <code>size</code>。例如默认表容量为 16、阈值为 12，在没有冲突引发提前扩容的情况下，插入第 13 个不同 key 才因超过负载阈值扩容。</p>
<h2>3、HashMap 的 <code>put</code> 流程是怎样的？</h2>
<p><strong>回答提示：</strong></p>
<ol>
<li>计算 key 的 hash。</li>
<li>定位桶下标（位运算）。</li>
<li>桶为空直接插入。</li>
<li>桶不为空则比较 key：相同覆盖，不同挂链表/树。</li>
<li>判断是否需要树化或扩容。</li>
</ol>
<p><strong>参考回答：</strong></p>
<ul>
<li><code>put</code> 核心逻辑就是“算桶位 + 冲突处理 + 阈值判断”。</li>
<li>冲突时先比较 <code>hash</code> 和 <code>equals</code>，相同 key 走覆盖；不同 key 追加到链表尾或红黑树。</li>
</ul>
<p>JDK 21 对非空 key 的哈希做高位扰动，再用 <code>(n - 1) &amp; hash</code> 定位桶，<code>n</code> 为数组长度。定位到同一桶不代表 key 相等；自定义 key 应保持 <code>equals/hashCode</code> 一致，放入后不要修改参与这两个方法计算的字段，否则可能无法按原 key 查到数据。</p>
<h2>4、HashMap 的 <code>get</code> 流程是怎样的？</h2>
<p><strong>回答提示：</strong></p>
<ol>
<li>计算 key 的 hash 并定位桶。</li>
<li>先比较头节点。</li>
<li>若是链表则遍历匹配。</li>
<li>若是红黑树则按树查找。</li>
</ol>
<p><strong>参考回答：</strong></p>
<ul>
<li><code>get</code> 的时间复杂度在理想情况下接近 O(1)，冲突严重时会退化。</li>
<li>JDK 8 起通过树化降低极端冲突下链表查询的退化风险，树桶的复杂度边界见第 7 题。</li>
</ul>
<p><code>get(key)</code> 返回 <code>null</code> 既可能表示 key 不存在，也可能表示对应的 value 为 <code>null</code>；需要区分时用 <code>containsKey(key)</code>。</p>
<h2>5、哈希冲突怎么处理？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>HashMap 主要采用拉链法（链地址法）。</li>
<li>同一个桶位的不同 key 通过链表或红黑树组织。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>哈希冲突不可避免，关键是“冲突后怎么组织”。</li>
<li>HashMap 采用“数组 + 链/树”结构，把冲突元素放在同一个桶中并继续检索。</li>
</ul>
<h2>6、链表什么时候转红黑树？红黑树什么时候退化回链表？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li><code>8</code>：树化检查阈值，但要结合插入入口的计数方式理解。</li>
<li><code>64</code>：允许树化的最小桶数组长度，不是 Map 元素个数。</li>
<li><code>6</code>：扩容时树桶拆分后，每一侧转回链表的节点数阈值；普通删除使用另一套判断。</li>
</ul>
<p><strong>参考回答：</strong></p>
<p>在 OpenJDK 21 中，需要分清以下路径：</p>
<p>| 路径 | 实际条件与边界 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/javabgw-img/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1.png" type="image/png"/>
    </item>
    <item>
      <title>Java集合</title>
      <link>https://qiuzhiwang.vip/javabgw/javajh.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/javajh.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Java集合</source>
      <description>比较 List、Set、Map 和队列的顺序、复杂度与并发语义，解释 ArrayList 初始化扩容及集合复合操作。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>建议先看题目目录，再按“概念 -&gt; 原理 -&gt; 场景 -&gt; 优化”顺序复习。<br>
每题先讲结论，再补关键机制和项目实践，回答会更稳。</p>
</div>
<p><strong>回答提示：是一种比较通俗的解释，比较好理解，如果‘参考答案’不太好记住，可以直接复习回答提示也可以。</strong></p>
<blockquote>
<p>适用版本：本文以 Java SE 21 API 为准；ArrayList 容量增长细节以 OpenJDK 21 实现为例，属于实现细节而非接口保证。历史实现差异另行标注。</p>
</blockquote>
<h2>1、Java中的集合类有哪些？如何分类的？(此问题基本不会被问到，但是要大体理解整个集合的内容，如果能记住此问题（灵活记住），面试集合的基础问题基本都可以回答)</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li><strong>List（列表）：</strong>List接口及其实现类用于存储一组有序的元素。元素可以重复，且可以通过索引访问。常见的List实现类有ArrayList、LinkedList等。ArrayList基于动态数组实现，访问元素速度快；LinkedList基于双向链表实现，已定位节点或操作两端时插删为 O(1)，按索引定位仍需 O(n)。</li>
<li><strong>Set（集合）：</strong>Set接口及其实现类用于存储不重复的元素；是否有确定遍历顺序取决于实现。Set不允许出现重复元素，因此常用于去重操作。常见的Set实现类有HashSet、TreeSet等。HashSet基于哈希表实现，不保证遍历顺序；LinkedHashSet 通常按插入顺序遍历；TreeSet基于红黑树实现，元素会按照自然顺序或者自定义顺序排序。</li>
<li><strong>Queue（队列）：</strong>Queue接口及其实现类用于在处理前保存元素，出队顺序由实现决定，通常是 FIFO，但优先队列并非如此。常见的Queue实现类有LinkedList（也实现了List接口）、PriorityQueue等。LinkedList可以用作双端队列；PriorityQueue是一个基于优先级堆的无界队列，元素的排序方式可以通过比较器来定义。</li>
<li><strong>Map（映射）：</strong>Map接口及其实现类用于存储键值对。键是唯一的，每个键都映射到一个值。常见的Map实现类有HashMap、TreeMap等。HashMap基于哈希表实现，键值对存储无序；TreeMap基于红黑树实现，键会按照自然顺序或者自定义顺序排序。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>集合类存放于 java.util 包中，常见接口包括 List（列表）、Set（集）、Queue（队列）和 Map（映射）。List、Set、Queue 都属于 Collection 体系；Queue 不是 List 的子接口，Map 是独立体系。</li>
<li>Collection：Collection 是集合 List、Set、Queue 的最基本的接口。</li>
<li>Iterator：迭代器，可以通过迭代器遍历集合中的数据</li>
<li>Map：是映射表的基础接口</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8-569eb326-8638cf72ca3e-747w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8-569eb326-8638cf72ca3e-747w.webp" srcset="/images/optimized/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8-569eb326-8638cf72ca3e-747w.webp 747w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="747" height="1012" decoding="async" data-image-source="/javabgw-img/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8.png" data-image-width="747" data-image-height="1012" data-image-detail="/images/optimized/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8-569eb326-8638cf72ca3e-747w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>上图可辅助理解；接口关系以文字和官方 API 为准。Java 21 还引入了表达确定遍历顺序的 SequencedCollection、SequencedSet、SequencedMap 等接口。ArrayList 常见扩容约为 1.5 倍，具体边界见第 3 题。</li>
</ul>
<p>官方资料：<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collection.html" target="_blank" rel="noopener noreferrer">Collection</a>、<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html" target="_blank" rel="noopener noreferrer">Set</a>、<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Queue.html" target="_blank" rel="noopener noreferrer">Queue</a>。</p>
<h2>2、那些集合是线程安全的？</h2>
<p><strong>参考回答：</strong></p>
<ol>
<li><strong>Vector：</strong>一个线程安全的List实现，它的大部分方法都是同步的，因此可以在多线程环境中使用。不过，由于同步操作会带来性能开销，所以在不需要线程安全的场合，建议使用ArrayList。</li>
<li><strong>Hashtable</strong>：一个线程安全的Map实现，它同样是同步的，可以在多线程环境中使用。与HashMap相比，Hashtable的性能可能稍差一些，因为同步操作会增加额外的开销。</li>
<li><strong>Stack：</strong>一个线程安全的栈实现，它也是同步的，可以在多线程环境中使用。单线程栈通常优先使用 Deque 的实现（如 ArrayDeque）；ArrayDeque 本身不保证线程安全。</li>
<li><strong>ConcurrentHashMap：</strong>线程安全的 Map 实现（不是 HashMap 的子类）。OpenJDK 7 使用 Segment 分段锁；OpenJDK 8 起主要使用 CAS 和桶级 synchronized 协调更新，普通 get 通常不加锁。不允许 null 键或值，不能把吞吐量优势视为所有负载下的保证。</li>
<li><strong>CopyOnWriteArrayList：</strong>一个线程安全的List实现，它通过在写操作时复制整个数组来实现线程安全。这种策略适用于读多写少的场景，因为写操作会涉及到复制整个数组，所以性能开销较大。</li>
</ol>
<p>线程安全的方法不等于任意复合操作都原子化。例如 <code>if (!map.containsKey(k)) map.put(k, v)</code> 有检查与写入之间的竞争窗口，可使用 <code>ConcurrentHashMap.putIfAbsent(k, v)</code> 完成原子条件插入。多键操作仍不构成事务。</p>
<p>官方资料：<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html" target="_blank" rel="noopener noreferrer">ConcurrentHashMap</a>、<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Stack.html" target="_blank" rel="noopener noreferrer">Stack</a>。</p>
<h2>3、ArrayList的扩容机制是什么？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>初始容量：OpenJDK 21 的无参构造器使用共享空数组，首次添加时通常分配容量 10；<code>new ArrayList&lt;&gt;(n)</code> 则按显式容量处理，不能混为一谈。</li>
<li>扩容触发：当向ArrayList中添加元素，且当前元素数量达到容量上限时，就会触发扩容操作。</li>
<li>扩容策略：ArrayList的扩容策略是增长大约50%的容量。</li>
<li>数组复制：扩容过程中，ArrayList会创建一个新的、更大容量的数组，并将原有数组中的所有元素复制到新数组中。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>以 OpenJDK 21 为例，容量不足时，优先增长量是 <code>oldCapacity &gt;&gt; 1</code>，新容量还必须满足本次操作的最小需求，并处理数组长度上限。批量添加时，最小需求可能超过约 1.5 倍的容量。增长策略属于 JDK 类库实现细节，不能归因于 JVM 随意选择倍数。</li>
<li>扩容会分配新数组并复制已有元素引用，不会克隆元素对象。一次扩容需要 O(n) 复制，连续尾部添加的均摊时间复杂度为 O(1)。size 是实际元素个数，capacity 是内部数组长度。</li>
</ul>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>无参构造：size=0，内部空数组</span></span>
<span class="line"><span>首次 add：size=1，capacity=10</span></span>
<span class="line"><span>第 11 次逐个 add：容量 10 → 15，复制原有引用后写入</span></span>
<span class="line"><span>第 16 次逐个 add：容量 15 → 22（15 + (15 >> 1)）</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这是根据源码推导的普通逐个追加示意，假定中途未调用 ensureCapacity、trimToSize 等方法，不是运行测量结果。</p>
<p>官方资料：<a href="https://github.com/openjdk/jdk21u/blob/master/src/java.base/share/classes/java/util/ArrayList.java" target="_blank" rel="noopener noreferrer">OpenJDK 21 ArrayList 源码（构造器、grow、add）</a>。</p>
<h2>4、ArrayList和linkedList的区别？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li><strong>内部实现：</strong>ArrayList是基于动态数组实现的，底层数组存放连续的元素引用，引用指向的对象不保证连续；而LinkedList则是基于双向链表实现，元素通过指针相互连接。</li>
<li><strong>随机访问：</strong>ArrayList支持快速随机访问，时间复杂度为O(1)；LinkedList则需要遍历，时间复杂度为O(n)。</li>
<li><strong>内存占用：</strong>LinkedList每个节点除了存储数据外，还需要存储指向前一个和后一个节点的指针，因此比ArrayList更占内存。</li>
<li><strong>使用场景：</strong>ArrayList适用于需要频繁进行随机访问的场景；LinkedList 的优势限于两端操作或已通过迭代器定位后的局部插删；按索引插删仍有 O(n) 的定位成本。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>ArrayList：基于动态数组，适合按下标访问；中间插删需移动后续引用，通常为 O(n)。预估元素数量并指定合理容量可以减少扩容，但是否快于其他结构应结合实际负载判断。</li>
<li>LinkedList：基于双向链表，按索引访问会从较近的一端遍历。两端插删或迭代器已定位后的插删为 O(1)，按索引插删整体通常仍为 O(n)。</li>
<li>遍历 LinkedList 可使用增强 for、iterator 或 listIterator，总体为 O(n)。应避免 <code>for (int i = 0; i &lt; list.size(); i++) list.get(i)</code>，反复按索引定位会使整体退化为 O(n²)。问题在于 get(i)，并不是 for 语法本身。indexOf 同样需要线性查找，找不到时返回 -1。</li>
</ul>
<p>官方资料：<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/LinkedList.html" target="_blank" rel="noopener noreferrer">LinkedList（实现与索引访问说明）</a>。</p>
<h2>5、HashMap和HashTable有什么区别？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li><strong>线程安全性：</strong>HashMap是非线程安全的，而HashTable是线程安全的。HashTable通过synchronized关键字实现同步，但可能导致性能下降。</li>
<li><strong>null值处理：</strong>HashMap允许null作为键和值，而HashTable不允许。</li>
<li><strong>性能：</strong>由于HashMap不需要考虑线程同步，通常性能更高。</li>
</ul>
<p><strong>参考回答：</strong></p>
<p><strong>区别 ：</strong></p>
<p>线程安全性</p>
<ul>
<li>HashMap方法没有synchronized修饰，线程非安全，HashTable线程安全；</li>
</ul>
<p><strong>空键和空值</strong>：</p>
<ul>
<li>HashMap允许key和value为null，而HashTable不允许</li>
</ul>
<p><strong>历史遗留</strong>：（了解）</p>
<ul>
<li><strong>Hashtable</strong> 是 Java 1.0 中的遗留类，虽然仍然可以使用，但通常建议使用 <strong>HashMap</strong>，并结合 <code>java.util.concurrent</code> 包中的现代并发类来处理多线程问题。</li>
<li><strong>HashMap</strong> 是在 Java 2 中引入的，设计更现代化，使用也更加灵活。</li>
</ul>
<p><strong>迭代器</strong>：(了解)</p>
<ul>
<li><strong>HashMap</strong> 使用的是快速失败（fail-fast）的迭代器，如果在创建迭代器后对集合进行了结构上的修改（除了使用迭代器自身的 <code>remove</code> 方法外），迭代器可能抛出 <code>ConcurrentModificationException</code>；fail-fast 只是尽力检测错误，不能作为并发正确性的保证。</li>
<li><strong>Hashtable</strong> 的 <code>keySet()</code>、<code>entrySet()</code> 等集合视图也提供 fail-fast 迭代器；只有 <code>keys()</code>、<code>elements()</code> 返回的旧式 <strong>Enumeration</strong> 不是 fail-fast。枚举期间结构被修改，结果未定义，并不意味着枚举可安全并发使用。</li>
</ul>
<p><strong>性能</strong>：（了解）</p>
<ul>
<li>在没有线程安全需求的场景中，<strong>HashMap</strong> 通常比 <strong>Hashtable</strong> 性能更高，因为它没有同步的开销。</li>
</ul>
<p>官方资料：<a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Hashtable.html" target="_blank" rel="noopener noreferrer">Hashtable（迭代器、Enumeration 与线程安全）</a>。</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/javabgw-img/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8.png" type="image/png"/>
    </item>
    <item>
      <title>集合框架</title>
      <link>https://qiuzhiwang.vip/javabgw/jhkj.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/jhkj.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">集合框架</source>
      <description>整理 Java 集合接口与实现，分析数组、链表、哈希与树结构，以及扩容、迭代器和版本差异。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>集合题核心是三条线：整体分类、常用容器对比、HashMap 原理。<br>
回答时尽量带复杂度和适用场景，避免只背定义。</p>
<p>本文以 <strong>Java SE 17 API、OpenJDK 17u 实现</strong>为核对基准；JDK 7/8 仅用于历史对比。扩容倍率、树化阈值属于实现细节，不是所有 Java 实现必须遵守的 API 契约。</p>
</div>
<h2>Java集合</h2>
<p><strong>回答提示：是一种比较通俗的解释，比较好理解，如果‘参考答案’不太好记住，可以直接复习回答提示也可以。</strong></p>
<h2>1、Java中的集合类有哪些？如何分类的？(此问题基本不会被问到，但是要大体理解整个集合的内容，如果能记住此问题（灵活记住），面试集合的基础问题基本都可以回答)</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li><strong>List（列表）：</strong>List接口及其实现类用于存储一组有序的元素。元素可以重复，且可以通过索引访问。常见的List实现类有ArrayList、LinkedList等。ArrayList基于动态数组实现，访问元素速度快；LinkedList基于双向链表实现，首尾插删为 O(1)，包含按索引定位的插删通常为 O(n)。</li>
<li><strong>Set（集合）：</strong>Set接口及其实现类用于存储一组不重复的元素；是否有迭代顺序由实现决定（如 LinkedHashSet 保留插入顺序、TreeSet 排序）。Set不允许出现重复元素，因此常用于去重操作。常见的Set实现类有HashSet、TreeSet等。HashSet基于哈希表实现，元素存储无序；TreeSet基于红黑树实现，元素会按照自然顺序或者自定义顺序排序。</li>
<li><strong>Queue（队列）：</strong>Queue接口及其实现类通常按先进先出（FIFO）组织元素，但并非全部如此，例如 PriorityQueue 按优先级取出队首，迭代也不保证排序。常见的Queue实现类有LinkedList（也实现了List接口）、PriorityQueue等。LinkedList可以用作双端队列；PriorityQueue是一个基于优先级堆的无界队列，元素的排序方式可以通过比较器来定义。</li>
<li><strong>Map（映射）：</strong>Map接口及其实现类用于存储键值对。键是唯一的，每个键都映射到一个值。常见的Map实现类有HashMap、TreeMap等。HashMap基于哈希表实现，键值对存储无序；TreeMap基于红黑树实现，键会按照自然顺序或者自定义顺序排序。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>集合框架主要位于 <code>java.util</code>，并发集合主要位于 <code>java.util.concurrent</code>。<code>Collection</code> 下有 List、Set、Queue 等接口，Queue 不属于 List；Map 是独立的接口层次。</li>
<li>Collection：Collection 是集合 List、Set、Queue 的最基本的接口。</li>
<li>Iterator：迭代器，可以通过迭代器遍历集合中的数据</li>
<li>Map：是映射表的基础接口</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8-569eb326-8638cf72ca3e-747w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8-569eb326-8638cf72ca3e-747w.webp" srcset="/images/optimized/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8-569eb326-8638cf72ca3e-747w.webp 747w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="747" height="1012" decoding="async" data-image-source="/javabgw-img/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8.png" data-image-width="747" data-image-height="1012" data-image-detail="/images/optimized/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8-569eb326-8638cf72ca3e-747w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>一定要牢牢记住这张图，大多比较浅的面试题都是从这上面问的。 ArrayList 在 OpenJDK 17u 中通常以约 1.5 倍作为优先增长量，但仍需满足最小所需容量和数组大小限制</li>
</ul>
<h2>2、那些集合是线程安全的？</h2>
<p><strong>参考回答：</strong></p>
<ol>
<li><strong>Vector：</strong>一个线程安全的List实现，它的大部分方法都是同步的，因此可以在多线程环境中使用。不过，由于同步操作会带来性能开销，所以在不需要线程安全的场合，建议使用ArrayList。</li>
<li><strong>Hashtable</strong>：一个线程安全的Map实现，它同样是同步的，可以在多线程环境中使用。与HashMap相比，Hashtable的性能可能稍差一些，因为同步操作会增加额外的开销。</li>
<li><strong>Stack：</strong>一个线程安全的栈实现，它也是同步的，可以在多线程环境中使用。官方建议优先使用 Deque 接口表达栈操作；单线程常用 ArrayDeque，它本身不保证线程安全。</li>
<li><strong>ConcurrentHashMap：</strong>一个线程安全的 Map 实现（不是 HashMap 的子类，且不接受 null 键和值）。早期版本（JDK 1.7 及以前）使用分段锁（Segment Locking）技术来提升并发性能，JDK 1.8 以后则使用了更加细粒度的锁，如空桶插入使用 CAS，非空桶更新通常在桶首节点上使用 synchronized，树桶另有协调机制。在多线程环境下，ConcurrentHashMap 的并发性能通常优于 Hashtable 以及通过 <code>Collections.synchronizedMap(new HashMap&lt;&gt;())</code> 包装得到的同步 HashMap，因为后两者都是对整个 map 加锁，从而限制了并发能力，而 ConcurrentHashMap 则支持更高的并发访问。</li>
<li><strong>CopyOnWriteArrayList：</strong>一个线程安全的List实现，它通过在写操作时复制整个数组来实现线程安全。这种策略适用于读多写少的场景，因为写操作会涉及到复制整个数组，所以性能开销较大。</li>
</ol>
<p>这些容器的线程安全不代表任意多步业务操作都是原子的。例如 ConcurrentHashMap 的“读取后加一再写回”应改用 <code>merge(key, 1, Integer::sum)</code> 等原子方法；同步包装集合的遍历还需要按官方要求在包装对象上加锁。CopyOnWriteArrayList 的迭代器读取创建时的数组快照，不反映后续写入，也不支持迭代器自身的增删改。</p>
<h2>3、ArrayList的扩容机制是什么？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li>初始容量：OpenJDK 17u 的无参 <code>new ArrayList&lt;&gt;()</code> 先使用共享空数组，首次添加元素时通常分配容量 10；<code>new ArrayList&lt;&gt;(0)</code> 则首次添加时只需容量 1。容量与 <code>size()</code> 不同。</li>
<li>扩容触发：当向ArrayList中添加元素，且当前元素数量达到容量上限时，就会触发扩容操作。</li>
<li>扩容策略：ArrayList的扩容策略是增长大约50%的容量。</li>
<li>数组复制：扩容过程中，ArrayList会创建一个新的、更大容量的数组，并将原有数组中的所有元素复制到新数组中。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>OpenJDK 17u 通常优先增长 <code>oldCapacity &gt;&gt; 1</code>，即新容量约为旧容量的 1.5 倍；如果该增长不足以容纳批量添加所需元素，则至少满足最小所需容量，并处理数组长度上限。扩容通过复制元素引用到新数组实现，不会复制元素对象本身。尾部追加的均摊时间为 O(1)，单次扩容为 O(n)。增长策略由类库实现决定，不由 JVM 规范规定。</li>
</ul>
<h2>4、ArrayList和linkedList的区别？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li><strong>内部实现：</strong>ArrayList是基于动态数组实现的，元素引用存放在同一数组中（元素对象本身不保证连续）；而LinkedList则是基于双向链表实现，元素通过指针相互连接。</li>
<li><strong>随机访问：</strong>ArrayList支持快速随机访问，时间复杂度为O(1)；LinkedList则需要遍历，时间复杂度为O(n)。</li>
<li><strong>内存占用：</strong>LinkedList每个节点除了存储数据外，还需要存储指向前一个和后一个节点的指针，因此比ArrayList更占内存。</li>
<li><strong>使用场景：</strong>ArrayList适用于需要频繁进行随机访问的场景；而 LinkedList 在首尾操作、或已由迭代器定位到位置后插删时才有 O(1) 的链接调整优势；按索引插删仍需 O(n) 定位。</li>
</ul>
<p><strong>参考回答：</strong></p>
<ul>
<li>ArrayList：基于动态数组，连续存放元素引用，适合下标访问（随机访问），扩容机制：因为数组长度固定，超出长度存数据时需要新建数组，然后将老数组的数据拷贝到新数组，如果不是尾部插入数据还会涉及到元素的移动（往后复制一份，插入新元素），预估容量可减少扩容复制；尾部追加通常具有较好的局部性，但性能结论应结合实际负载测量</li>
<li>LinkedList：基于双向链表，首尾操作为 O(1)；按索引访问会从较近的一端遍历，最坏 O(n)，不能笼统地说所有插删都更快</li>
<li>遍历 LinkedList 可用增强 for 或 Iterator，整体 O(n)；应避免 <code>for (int i = 0; i &lt; list.size(); i++) list.get(i)</code> 这种反复按索引访问的写法，其完整遍历为 O(n²)。<code>indexOf</code> 返回未找到时的 -1，也需线性查找。</li>
</ul>
<h2>5、HashMap和HashTable有什么区别？</h2>
<p><strong>回答提示：</strong></p>
<ul>
<li><strong>线程安全性：</strong>HashMap是非线程安全的，而HashTable是线程安全的。HashTable通过synchronized关键字实现同步，但可能导致性能下降。</li>
<li><strong>null值处理：</strong>HashMap允许null作为键和值，而HashTable不允许。</li>
<li><strong>性能：</strong>由于HashMap不需要考虑线程同步，通常性能更高。</li>
</ul>
<p><strong>参考回答：</strong></p>
<p><strong>区别 ：</strong></p>
<p>线程安全性</p>
<ul>
<li>HashMap方法没有synchronized修饰，线程非安全，HashTable线程安全；</li>
</ul>
<p><strong>空键和空值</strong>：</p>
<ul>
<li>HashMap允许key和value为null，而HashTable不允许</li>
</ul>
<p><strong>历史遗留</strong>：（了解）</p>
<ul>
<li><strong>Hashtable</strong> 是 Java 1.0 中的遗留类，虽然仍然可以使用，但通常建议使用 <strong>HashMap</strong>，并结合 <code>java.util.concurrent</code> 包中的现代并发类来处理多线程问题。</li>
<li><strong>HashMap</strong> 是在 Java 2 中引入的，设计更现代化，使用也更加灵活。</li>
</ul>
<p><strong>迭代器</strong>：(了解)</p>
<ul>
<li><strong>HashMap</strong> 使用的是快速失败（fail-fast）的迭代器，如果在创建迭代器后对集合进行了结构上的修改（除了使用迭代器自身的 <code>remove</code> 方法外），迭代器通常会抛出 <code>ConcurrentModificationException</code>，但这是尽力检测，不能据此保证并发安全。</li>
<li><strong>Hashtable</strong> 的 <code>keys()</code>、<code>elements()</code> 返回非 fail-fast 的 Enumeration；<code>keySet()</code>、<code>entrySet()</code> 等集合视图返回的 Iterator 则是尽力检测的 fail-fast 迭代器。</li>
</ul>
<p><strong>性能</strong>：（了解）</p>
<ul>
<li>在没有线程安全需求的场景中，<strong>HashMap</strong> 通常比 <strong>Hashtable</strong> 性能更高，因为它没有同步的开销。</li>
</ul>
<h2>HashMap底层原理</h2>
<p><strong>HashMap底层原理 （了解）</strong></p>
<ul>
<li>无参 HashMap 默认负载因子为 0.75，桶数组延迟到首次插入时分配，默认长度 16。通常在新增映射后 <code>size &gt; threshold</code> 才扩容，阈值通常为 <code>capacity × loadFactor</code>；默认容量 16 时，第 13 个不同的键触发负载阈值扩容。这里统计的是映射数，不是已占用桶数。扩容通常将数组长度翻倍，并重新分配桶位置，不是按 hashCode 排序；CPU 能执行乘法，采用位运算不是因为“不支持乘法”。</li>
<li>JDK 7 使用数组加链表；自 JDK 8 起增加红黑树桶。以 OpenJDK 17u 的普通 <code>put</code> 路径为例：向已有 8 个节点的链表追加第 9 个不同键时尝试树化；数组长度至少 64 才树化，否则优先扩容。其他插入路径应结合具体源码判断，不能把阈值常量 8 简化成“第 8 个节点必树化”。</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1-b89fa6ef-9141c621c77a-877w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1-b89fa6ef-9141c621c77a-800w.webp" srcset="/images/optimized/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1-b89fa6ef-9141c621c77a-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="877" height="431" decoding="async" data-image-source="/javabgw-img/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1.png" data-image-width="877" data-image-height="431" data-image-detail="/images/optimized/1755157781407-08a8de04-cfdf-4335-ab78-bc7b4424cdb1-b89fa6ef-9141c621c77a-877w.webp"></a><figcaption>img</figcaption></figure>
<p><strong>底层数据结构</strong></p>
<ul>
<li>HashMap 的数组槽位称为桶，保存桶首节点的引用；每个节点才是一个 key-value 映射。在 <strong>Java7</strong> 中叫 Entry，<strong>Java8</strong> 中叫 Node。 因为它本身所有的位置都为 null，在 put 插入的时候会根据 key 的 hash 去计算一个 index 值。比如，我 put （&quot;枫斗者&quot;，666），在 HashMap 中插入 &quot;枫斗者&quot; 这个元素，通过 hash 算法算出 index 位置是 3。这时的结构如下所示，还是个数组。</li>
</ul>
<p>|      |      |      | 枫斗者 |      |      |      |      |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/javabgw-img/1755161630454-45b432b1-d967-46d6-afa4-9ba4c01a54d8.png" type="image/png"/>
    </item>
    <item>
      <title>计算机网络</title>
      <link>https://qiuzhiwang.vip/javabgw/jsjwl.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/jsjwl.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">计算机网络</source>
      <description>从 ARP、路由、TCP 握手与拥塞控制到 HTTP、Cookie 和 Session，按协议边界理解网络通信。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>建议先看题目目录，再按“概念 -&gt; 原理 -&gt; 场景 -&gt; 优化”顺序复习。<br>
每题先讲结论，再补关键机制和项目实践，回答会更稳。</p>
</div>
<p>适用范围：ARP 示例以 IPv4 以太网为例；TCP 连接状态以 RFC 9293（2022）为准；拥塞控制先讲 RFC 5681 的经典算法；HTTP 方法语义依据 RFC 9110（2022），并区分 HTTP/1.1、HTTP/2 与 HTTP/3。协议机制不等于某个操作系统或框架的默认配置。</p>
<h2>1、网络7层架构是什么？</h2>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163943397-ee595666-0dc2-4903-8b8c-12afaa5205d6-d7c50c2c-6f2a6b360dcb-1080w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163943397-ee595666-0dc2-4903-8b8c-12afaa5205d6-d7c50c2c-6f2a6b360dcb-800w.webp" srcset="/images/optimized/1755163943397-ee595666-0dc2-4903-8b8c-12afaa5205d6-d7c50c2c-6f2a6b360dcb-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1080" height="392" decoding="async" data-image-source="/javabgw-img/1755163943397-ee595666-0dc2-4903-8b8c-12afaa5205d6.png" data-image-width="1080" data-image-height="392" data-image-detail="/images/optimized/1755163943397-ee595666-0dc2-4903-8b8c-12afaa5205d6-d7c50c2c-6f2a6b360dcb-1080w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li><strong>应用层（数据）：</strong>确定进程之间通信的性质以满足用户需要以及提供网络与用户应用</li>
<li><strong>表示层（数据）：</strong>主要解决用户信息的语法表示问题，如加密解密</li>
<li><strong>会话层（数据）：</strong>提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制，这是参考模型的职责划分，实际 Web 登录验证通常由应用层实现</li>
<li><strong>运输层（段）：</strong>实现网络不同主机上用户进程之间的数据通信，可靠与不可靠的传输，传输层的错误检测，流量控制等</li>
<li><strong>网络层（包）：</strong>提供逻辑地址（IP）、选路，数据从源端到目的端的传输</li>
<li><strong>数据链路层（帧）：</strong>包含 MAC 等子层，将上层数据封装成帧，用MAC地址访问媒介，错误检测；是否纠错或重传取决于具体链路协议</li>
<li><strong>物理层（比特流）：</strong>设备之间比特流的传输，物理接口，电气特性等</li>
</ul>
<h2>2、TCP/IP 四层模型？</h2>
<ul>
<li><strong>应用层：</strong>对应于OSI参考模型的（应用层、表示层、会话层）。</li>
<li><strong>传输层:</strong> 对应OSI的传输层，为应用进程提供端到端通信；TCP 提供可靠、有序的字节流，UDP 不保证交付或顺序。</li>
<li><strong>网际层：</strong>对应于OSI参考模型的网络层，主要解决主机到主机的通信问题。</li>
<li><strong>网络接口层：</strong>与OSI参考模型的数据链路层、物理层对应。</li>
</ul>
<h2>3、请简单说下ARP协议的工作过程？</h2>
<ul>
<li>主机先查路由表确定出口与下一跳：同链路通信通常解析目的主机的 MAC；跨网段通信解析网关的 MAC，而不是远端主机的 MAC。</li>
<li>若 ARP 缓存已有下一跳 IPv4 地址对应的有效 MAC，直接封装以太网帧；否则在本地链路广播 ARP Request，询问谁拥有这个下一跳地址。</li>
<li>拥有该地址的主机通常单播回复 ARP Reply；发送方更新缓存后发送数据。请求重试仍无响应时，本次邻居解析失败，不等于能断定远端主机已关机。</li>
</ul>
<h2>4、请简单说下ARP协议的工作过程？</h2>
<p>这一题可结合跨网段示意理解：IP 目的地址与以太网帧的目的 MAC 不一定属于同一台设备。</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>A: 192.168.1.10/24  →  网关: 192.168.1.1  →  B: 192.168.2.20/24</span></span>
<span class="line"><span>A 查路由后 ARP 查询 192.168.1.1</span></span>
<span class="line"><span>A 发出的帧：目的 MAC = 网关 MAC；内部 IP 包：目的 IP = B</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>ARP 广播不会由路由器转发到远端子网。接收方会根据已有缓存和是否为请求目标更新映射，不能简单理解为“非目标设备一律忽略所有信息”。ARP 不提供身份认证；IPv6 使用 ICMPv6 邻居发现而不是 ARP。参见 <a href="https://www.rfc-editor.org/rfc/rfc826.html" target="_blank" rel="noopener noreferrer">RFC 826 的生成与接收算法</a>、<a href="https://www.rfc-editor.org/rfc/rfc4861.html" target="_blank" rel="noopener noreferrer">RFC 4861</a>。</p>
<h2>5、TCP和UDP什么区别？</h2>
<ul>
<li>TCP 面向连接，提供可靠、有序的字节流，不保留应用消息边界；应用需用长度字段或分隔符等方式划分消息。UDP 无连接并保留数据报边界，自身不提供重传、排序、流量控制或拥塞控制；应用或上层协议可补充这些能力（如 QUIC）。TCP 的可靠性也不意味着网络中断后仍保证成功交付，应用仍须处理连接错误。</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163943683-2e434048-bbf3-4d46-a8aa-72ae6423e324-0c8b6929-3d0565dbe464-816w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163943683-2e434048-bbf3-4d46-a8aa-72ae6423e324-0c8b6929-3d0565dbe464-800w.webp" srcset="/images/optimized/1755163943683-2e434048-bbf3-4d46-a8aa-72ae6423e324-0c8b6929-3d0565dbe464-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="816" height="231" decoding="async" data-image-source="/javabgw-img/1755163943683-2e434048-bbf3-4d46-a8aa-72ae6423e324.png" data-image-width="816" data-image-height="231" data-image-detail="/images/optimized/1755163943683-2e434048-bbf3-4d46-a8aa-72ae6423e324-0c8b6929-3d0565dbe464-816w.webp"></a><figcaption>img</figcaption></figure>
<h2>6、详细说下 TCP 三次握手的过程？</h2>
<ul>
<li><strong>三次握手</strong></li>
<li>TCP 建立连接的过程叫做握手，握手需要在客户和服务器之间交换三个 TCP 报文段。</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163943483-8ff069ac-2db9-4b08-821f-a53e36fefb53-3a853f06-adce1a041901-788w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163943483-8ff069ac-2db9-4b08-821f-a53e36fefb53-3a853f06-adce1a041901-788w.webp" srcset="/images/optimized/1755163943483-8ff069ac-2db9-4b08-821f-a53e36fefb53-3a853f06-adce1a041901-788w.webp 788w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="788" height="476" decoding="async" data-image-source="/javabgw-img/1755163943483-8ff069ac-2db9-4b08-821f-a53e36fefb53.png" data-image-width="788" data-image-height="476" data-image-detail="/images/optimized/1755163943483-8ff069ac-2db9-4b08-821f-a53e36fefb53-3a853f06-adce1a041901-788w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>最初客户端和服务端都处于 CLOSED(关闭) 状态。本例中 A（Client） 主动打开连接，B（Server） 被动打开连接。</li>
<li>一开始，B 的 TCP 服务器进程首先创建传输控制块TCB，准备接受客户端进程的连接请求。然后服务端进程就处于 LISTEN(监听) 状态，等待客户端的连接请求。如有，立即作出响应。</li>
<li>第一次握手：A 的 TCP 客户端进程也是首先创建传输控制块 TCB。然后，在打算建立 TCP 连接时，向 B 发出连接请求报文段，这时首部中的同步位 SYN=1，同时选择一个初始序号 seq = x。本例 SYN 不携带数据；协议允许 SYN 携带数据，但在连接确认前不能将数据交付应用。SYN 标志本身消耗一个序号。这时，TCP 客户进程进入 SYN-SENT（同步已发送）状态。</li>
<li>第二次握手：B 收到连接请求报文后，如果同意建立连接，则向 A 发送确认。在确认报文段中应把 SYN 位和 ACK 位都置 1，确认号是 ack = x + 1，同时也为自己选择一个初始序号 seq = y。本例 SYN+ACK 不携带数据，SYN 本身同样消耗一个序号。这时 TCP 服务端进程进入 SYN-RCVD（同步收到）状态。</li>
<li>第三次握手：TCP 客户进程收到 B 的确认后，还要向 B 给出确认。确认报文段的 ACK 置 1，确认号 ack = y + 1，而自己的序号 seq = x + 1。这时 ACK 报文段可以携带数据。但如果不携带数据则不消耗序号，这种情况下，下一个数据报文段的序号仍是 seq = x + 1。这时，TCP 连接已经建立，A 进入 ESTABLISHED（已建立连接）状态；B 在收到有效的第三次握手 ACK 后才进入 ESTABLISHED。</li>
</ul>
<h2>7、为什么两次握手不可以呢？</h2>
<ul>
<li>为了防止已经失效的连接请求报文段突然又传送到了 B，因而产生错误。比如下面这种情况：A 发出的第一个连接请求报文段并没有丢失，而是在网路结点长时间滞留了，以致于延误到连接释放以后的某个时间段才到达 B。本来这是一个早已失效的报文段。但是 B 收到此失效的链接请求报文段后，就误认为 A 又发出一次新的连接请求。于是就向 A 发出确认报文段，同意建立连接。</li>
<li>对于上面这种情况，如果不进行第三次握手，B 发出确认后就认为新的运输连接已经建立了，并一直等待 A 发来数据。B 的许多资源就这样白白浪费了。</li>
<li>如果采用了三次握手，由于 A 实际上并没有发出建立连接请求，对于已无对应连接的旧 SYN+ACK，A 通常回复 RST，使 B 清理半连接；若无有效回应，B 也会在重试超时后清理。第三次握手让 B 确认 A 收到了自己的初始序号。</li>
</ul>
<h2>8、为什么不需要四次握手？</h2>
<ul>
<li>有人可能会说 A 发出第三次握手的信息后在没有接收到 B 的请求就已经进入了连接状态，那如果 A 的这个确认包丢失或者滞留了怎么办？</li>
<li>第三次握手已经确认了双方的初始序号，不需要再为纯 ACK 增加一次握手。若最后的 ACK 丢失，B 仍处于 SYN-RCVD，可重传 SYN+ACK，A 再回复 ACK；后续带有效确认号的数据也可能完成确认。若持续丢包，则重试最终超时，建立连接仍可能失败。参见 <a href="https://www.rfc-editor.org/rfc/rfc9293.html#section-3.5" target="_blank" rel="noopener noreferrer">RFC 9293 §3.5</a>。</li>
</ul>
<h2>9、详细说下 TCP 四次挥手的过程？</h2>
<ul>
<li>数据传输结束后，通信的双方都可以释放连接。现在 A 和 B 都处于 ESTABLISHED 状态。</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163943822-d0aa04d8-fd35-417c-ba10-2e57bd70a031-5592102e-5349c4e000c0-785w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163943822-d0aa04d8-fd35-417c-ba10-2e57bd70a031-5592102e-5349c4e000c0-785w.webp" srcset="/images/optimized/1755163943822-d0aa04d8-fd35-417c-ba10-2e57bd70a031-5592102e-5349c4e000c0-785w.webp 785w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="785" height="574" decoding="async" data-image-source="/javabgw-img/1755163943822-d0aa04d8-fd35-417c-ba10-2e57bd70a031.png" data-image-width="785" data-image-height="574" data-image-detail="/images/optimized/1755163943822-d0aa04d8-fd35-417c-ba10-2e57bd70a031-5592102e-5349c4e000c0-785w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>第一次挥手：A 的应用进程先向其 TCP 发出连接释放报文段，并停止再发送数据，主动关闭 TCP 连接。A 把连接释放报文段首部的终止控制位 FIN 置 1，其序号 seq = u（等于前面已传送过的数据的最后一个字节的序号加 1），这时 A 进入 FIN-WAIT-1（终止等待1）状态，等待 B 的确认。请注意：TCP 规定，FIN 报文段即使不携带数据，也将消耗掉一个序号。</li>
<li>第二次挥手：B 收到连接释放报文段后立即发出确认，确认号是 ack = u + 1，而这个报文段自己的序号是 v（等于 B 前面已经传送过的数据的最后一个字节的序号加1），然后 B 就进入 CLOSE-WAIT（关闭等待）状态。TCP 服务端进程这时应通知高层应用进程，因而从 A 到 B 这个方向的连接就释放了，这时的 TCP 连接处于半关闭（half-close）状态，即 A 已经没有数据要发送了，但 B 若发送数据，A 仍要接收。也就是说，从 B 到 A 这个方向的连接并未关闭，这个状态可能会持续一段时间。A 收到来自 B 的确认后，就进入 FIN-WAIT-2(终止等待2)状态，等待 B 发出的连接释放报文段。</li>
<li>第三次挥手：若 B 已经没有要向 A 发送的数据，其应用进程就通知 TCP 释放连接。这时 B 发出的连接释放报文段必须使 FIN = 1。假定 B 的序号为 w（在半关闭状态，B 可能又发送了一些数据）。B 还必须重复上次已发送过的确认号 ack = u + 1。这时 B 就进入 LAST-ACK(最后确认)状态，等待 A 的确认。</li>
<li>第四次挥手：A 在收到 B 的连接释放报文后，必须对此发出确认。在确认报文段中把 ACK 置 1，确认号 ack = w + 1，而自己的序号 seq = u + 1（前面发送的 FIN 报文段要消耗一个序号）。然后进入 TIME-WAIT(时间等待) 状态。请注意，现在 TCP 连接还没有释放掉。必须经过时间等待计时器设置的时间 2MSL（MSL：最长报文段寿命）后，A 才能进入到 CLOSED 状态，然后撤销传输控制块，结束这次 TCP 连接。当然如果 B 一收到 A 的确认就进入 CLOSED 状态，然后撤销传输控制块。在本例的正常关闭路径中，B 先于 A 关闭；TIME-WAIT 用于应对最后 ACK 丢失后的 FIN 重传，并让旧报文过期。ACK 与 FIN 可以合并，因此关闭不一定恰好四个报文段；同时关闭和 RST 中止也不遵循这一示例路径。</li>
</ul>
<h2>10、TCP 协议是如何保证可靠传输的？</h2>
<ul>
<li>数据包校验：通过校验和检测传输差错，但无法检出所有可能的变化，也不提供抗篡改认证，若校验出包有错，则丢弃报文段并且不给出响应，这时 TCP 发送数据端超时后会重发数据；</li>
<li>对失序数据包重排序：既然 TCP 报文段作为 IP 数据报来传输，而 IP 数据报的到达可能会失序，因此 TCP 报文段的到达也可能会失序。TCP 将对失序数据进行重新排序，然后才交给应用层；</li>
<li>丢弃重复数据：对于重复数据，能够丢弃重复数据；</li>
<li>应答机制：当 TCP 收到发自 TCP 连接另一端的数据，它将发送一个确认。可以采用延迟确认，但并非所有 ACK 都延迟，失序报文等场景通常需及时反馈；</li>
<li>超时重发：当 TCP 发出一个段后，它启动一个定时器，等待目的端确认收到这个报文段。如果不能及时收到一个确认，将重发这个报文段；</li>
<li>流量控制：TCP 连接的接收端缓冲空间有限，大小可以由实现动态调整。TCP 的接收端只允许另一端发送接收端缓冲区所能接纳的数据，这可以防止较快主机致使较慢主机的缓冲区溢出，这就是流量控制。TCP 使用的流量控制协议是可变大小的滑动窗口协议。</li>
</ul>
<h2>11、谈下你对 TCP 拥塞控制的理解？使用了哪些算法？</h2>
<ul>
<li>拥塞控制和流量控制不同，前者是一个全局性的过程，而后者指点对点通信量的控制。在某段时间，若对网络中某一资源的需求超过了该资源所能提供的可用部分，网络的性能就要变坏。这种情况就叫拥塞。</li>
<li>拥塞控制就是为了防止过多的数据注入到网络中，这样就可以使网络中的路由器或链路不致于过载。拥塞控制所要做的都有一个前提，就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程，涉及到所有的主机，所有的路由器，以及与降低网络传输性能有关的所有因素。相反，流量控制往往是点对点通信量的控制，是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率，以便使接收端来得及接收。</li>
<li>为了进行拥塞控制，TCP 发送方要维持一个拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度，并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。</li>
<li>RFC 5681 描述的经典 TCP 拥塞控制包含四种相互配合的算法，即：慢开始、拥塞避免、快重传和快恢复。在网络层也可以使路由器采用适当的分组丢弃策略（如：主动队列管理 AQM），以减少网络拥塞的发生。</li>
<li><strong>慢开始：</strong></li>
<li>慢开始算法的思路是当主机开始发送数据时，如果立即把大量数据字节注入到网络，那么可能会引起网络阻塞，因为现在还不知道网络的负荷情况。经验表明，较好的方法是先探测一下，即由小到大逐渐增大发送窗口，也就是由小到大逐渐增大拥塞窗口数值。cwnd 以字节计；在持续有数据、ACK 正常返回且未受其他限制时，慢开始阶段每 RTT 近似翻倍。初始窗口并非固定为 1 MSS；RFC 6928 的实验性方案允许最多 10 MSS（还受字节上限约束），实际取值依实现而定。</li>
<li><strong>拥塞避免：</strong></li>
<li>拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢增大，即经典拥塞避免阶段每 RTT 大约增加 1 MSS，而不是增加 1 字节；这不是所有拥塞控制算法的统一增长规则。</li>
<li><strong>快重传与快恢复：</strong></li>
<li>在 TCP/IP 中，快速重传和快恢复（fast retransmit and recovery，FRR）是一种拥塞控制算法，它能快速恢复丢失的数据包。</li>
<li>经典快重传在收到三个重复 ACK 时推断报文段丢失并重传，无须等到重传超时。重复 ACK 也可能由重排序产生，因此这是一种丢包推断；没有快重传并不意味着定时器会让整个连接立即暂停。</li>
<li>经典 Reno 快恢复在调整 ssthresh 和 cwnd 后继续利用 ACK 反馈发送，避免每次丢包都退回慢开始；多包丢失时恢复效率可能降低。现代实现还可结合 SACK、RACK-TLP 等机制，不能把“收到三个重复 ACK”当成唯一丢包检测条件。</li>
</ul>
<p>参考：<a href="https://www.rfc-editor.org/rfc/rfc5681.html#section-3" target="_blank" rel="noopener noreferrer">RFC 5681 §3</a>、<a href="https://www.rfc-editor.org/rfc/rfc6928.html#section-2" target="_blank" rel="noopener noreferrer">RFC 6928 §2</a>、<a href="https://www.rfc-editor.org/rfc/rfc8985.html" target="_blank" rel="noopener noreferrer">RFC 8985</a>。</p>
<h2>11、说下 GET 和 POST 的区别？</h2>
<ul>
<li>GET 请求目标资源的表示；POST 请求目标资源按其自身语义处理提交的内容，例如创建订单、提交表单，并不限于更新资源。两者都各自是一次 HTTP 请求。即使用 <code>Expect: 100-continue</code> 先等临时响应再发送内容，也仍是同一次请求；TCP 分包次数不等于 HTTP 请求次数。</li>
<li>GET 是安全且幂等的方法：安全指客户端不请求改变资源状态，幂等指多次相同请求的预期服务器效果与一次相同，并非每次响应内容必须相同。访问日志等副作用不违背安全语义。POST 不保证幂等，业务可用幂等键等机制防止重复下单。</li>
<li>查询参数位于 URI 的 query 部分，不是请求头字段；在 HTTP/1.1 中通常出现在请求行。POST 同样可以带查询参数，请求体也不限于表单。GET 请求内容没有普遍定义的语义，可能被中间件拒绝，不宜依赖 GET body 传参。</li>
<li><code>%E4%BD%A0%E5%A5%BD</code> 是“你好”的 UTF-8 字节的百分号编码，不是 Base64，也不是加密。表单编码 <code>application/x-www-form-urlencoded</code> 将空格编码为 <code>+</code>；这一规则不能泛化到所有 URI 部分。</li>
<li>POST 不自带加密，传输机密性依赖 HTTPS。URL 参数更容易出现在历史记录和访问日志中，但请求体也可能被记录，敏感数据的保护不能只靠更换方法。</li>
<li>HTTP 没有规定 GET/POST 的统一大小上限；URI、请求头、请求体均受客户端、代理和服务器配置限制。POST 并非无限大。</li>
</ul>
<p>方法语义适用于 HTTP/1.1、HTTP/2、HTTP/3；报文在线路上的表示不同。参见 <a href="https://www.rfc-editor.org/rfc/rfc9110.html#section-9.2" target="_blank" rel="noopener noreferrer">RFC 9110 §9.2–9.3</a>、<a href="https://www.rfc-editor.org/rfc/rfc3986.html#section-2.1" target="_blank" rel="noopener noreferrer">RFC 3986 §2.1</a>。</p>
<h2>12、什么是 Cookie 和 Session ？</h2>
<ul>
<li>
<p><strong>什么是 Cookie？</strong></p>
</li>
<li>
<p>HTTP Cookie（也叫 Web Cookie或浏览器 Cookie）是服务器发送到用户浏览器并保存在本地的一小块数据，浏览器会根据 Domain、Path、Secure 等属性及浏览器的 Cookie 策略决定是否随请求发送它。通常，它用于告知服务端两个请求是否来自同一浏览器，如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。</p>
</li>
<li>
<p><strong>Cookie 主要用于以下三个方面：</strong></p>
</li>
<li>
<p>会话状态管理（如用户登录状态、购物车、游戏分数或其它需要记录的信息）</p>
</li>
<li>
<p>个性化设置（如用户自定义设置、主题等）</p>
</li>
<li>
<p>浏览器行为跟踪（如跟踪分析用户行为等）</p>
</li>
<li>
<p><strong>什么是 Session？</strong></p>
</li>
<li>
<p>Session 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息。这样，当用户在应用程序的 Web 页之间跳转时，存储在 Session 对象中的变量将不会丢失，而是在整个用户会话中一直存在下去。服务端 Session 通常在超时或显式失效时结束，关闭浏览器不等于服务端立即销毁 Session。常见机制是浏览器携带会话 ID，服务端据此查找状态；多实例部署需考虑共享存储或会话路由。</p>
</li>
</ul>
<h2>13、Cookie 和 Session 的区别？</h2>
<ul>
<li>作用范围不同，Cookie 保存在客户端（浏览器），Session 保存在服务器端。</li>
<li>存取方式的不同，Cookie 值须符合协议字符约束，非 ASCII 数据需先编码；Session 可保存的数据类型取决于框架与存储，分布式存储通常还要求可序列化，一般情况下我们可以在 Session 中保持一些常用变量信息，比如说 UserId 等。</li>
<li>有效期不同，Cookie 可设置为长时间保持，比如我们经常使用的默认登录功能，Session 的有效期由服务端管理；关闭浏览器可能丢失会话 Cookie，但不会直接通知服务端销毁 Session，浏览器恢复功能也可能保留它。</li>
<li>隐私策略不同，Cookie 存储在客户端，比较容易遭到不法获取，早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取；Session 存储在服务端，但客户端持有的会话 ID 被窃取后仍可能遭到会话劫持，需结合 HTTPS、Secure、HttpOnly 等保护。</li>
<li>存储大小不同， RFC 6265 建议通用客户端至少支持每个 Cookie 4096 字节（名称、值与属性合计），实际限制依浏览器而定；这不是业务可依赖的无限配额。Session 容量受服务端内存、存储与框架限制。</li>
</ul>
<p>Cookie 的存储、发送规则与容量建议参见 <a href="https://www.rfc-editor.org/rfc/rfc6265.html#section-5" target="_blank" rel="noopener noreferrer">RFC 6265 §5–6</a>。</p>
<h2>14、对称加密与非对称加密有什么区别？</h2>
<ul>
<li>对称加密：指加密和解密使用同一密钥，优点是运算速度较快，缺点是如何安全将密钥传输给另一方。常见的对称加密算法有：AES 等。</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163943707-cafd7c9e-aed9-4c80-8423-5fe76d701eba-6a8a93ad-23071dffbf62-1080w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163943707-cafd7c9e-aed9-4c80-8423-5fe76d701eba-6a8a93ad-23071dffbf62-800w.webp" srcset="/images/optimized/1755163943707-cafd7c9e-aed9-4c80-8423-5fe76d701eba-6a8a93ad-23071dffbf62-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1080" height="290" decoding="async" data-image-source="/javabgw-img/1755163943707-cafd7c9e-aed9-4c80-8423-5fe76d701eba.png" data-image-width="1080" data-image-height="290" data-image-detail="/images/optimized/1755163943707-cafd7c9e-aed9-4c80-8423-5fe76d701eba-6a8a93ad-23071dffbf62-1080w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>非对称加密：指的是加密和解密使用不同的密钥（即公钥和私钥）。公钥与私钥是成对存在的，如果用公钥对数据进行加密，只有对应的私钥才能解密。常见的非对称加密算法有RSA。</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163945785-02098d2f-e745-401c-8e6d-ac72f6ab125d-c82720a7-575429993053-1080w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163945785-02098d2f-e745-401c-8e6d-ac72f6ab125d-c82720a7-575429993053-800w.webp" srcset="/images/optimized/1755163945785-02098d2f-e745-401c-8e6d-ac72f6ab125d-c82720a7-575429993053-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1080" height="348" decoding="async" data-image-source="/javabgw-img/1755163945785-02098d2f-e745-401c-8e6d-ac72f6ab125d.png" data-image-width="1080" data-image-height="348" data-image-detail="/images/optimized/1755163945785-02098d2f-e745-401c-8e6d-ac72f6ab125d-c82720a7-575429993053-1080w.webp"></a><figcaption>img</figcaption></figure>
<h2>15、Http请求的过程与原理?</h2>
<ul>
<li>HTTP 是应用层协议，可以传递 HTML、图片、JSON 等表示。下面以新建连接的 HTTP/1.1 为例；HTTPS 还需 TLS 握手，HTTP/2 通常复用 TCP 连接，而 HTTP/3 使用基于 UDP 的 QUIC，不能套用 TCP 三次握手。</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163946336-55f9b6f0-5ed7-4fdd-9d7d-0c4f8cfcf343-55c1abe0-83b198fdda95-1080w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163946336-55f9b6f0-5ed7-4fdd-9d7d-0c4f8cfcf343-55c1abe0-83b198fdda95-800w.webp" srcset="/images/optimized/1755163946336-55f9b6f0-5ed7-4fdd-9d7d-0c4f8cfcf343-55c1abe0-83b198fdda95-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1080" height="514" decoding="async" data-image-source="/javabgw-img/1755163946336-55f9b6f0-5ed7-4fdd-9d7d-0c4f8cfcf343.png" data-image-width="1080" data-image-height="514" data-image-detail="/images/optimized/1755163946336-55f9b6f0-5ed7-4fdd-9d7d-0c4f8cfcf343-55c1abe0-83b198fdda95-1080w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>
<p>Http请求过程</p>
</li>
<li>
<p>客户端进行DNS域名解析，得到对应的IP地址</p>
</li>
<li>
<p>根据这个IP，找到对应的服务器建立连接（三次握手）</p>
</li>
<li>
<p>建立TCP连接后发起HTTP请求（一个完整的http请求报文）</p>
</li>
<li>
<p>服务器响应HTTP请求，客户端得到html代码</p>
</li>
<li>
<p>客户端解析html代码，用html代码中的资源(如js,css,图片等等)渲染页面。</p>
</li>
<li>
<p>HTTP/1.1 默认使用持久连接，可复用以继续请求资源；仅在需要关闭时才释放 TCP 连接，不是每个响应后都四次挥手。DNS、连接和资源也可能命中缓存或复用。</p>
</li>
</ul>
<p>版本依据：<a href="https://www.rfc-editor.org/rfc/rfc9112.html#section-9" target="_blank" rel="noopener noreferrer">RFC 9112 §9</a>、<a href="https://www.rfc-editor.org/rfc/rfc9114.html" target="_blank" rel="noopener noreferrer">RFC 9114</a>。</p>
<h2>16、说说ARP与RARP 的区别？</h2>
<ul>
<li>
<p>ARP（Address Resolution Protocol）和RARP（Reverse Address Resolution Protocol）都是用于在网络通信中进行地址转换的协议，但它们在功能、工作方式和用途上有一些区别。</p>
</li>
<li>
<p><strong>功能不同：</strong></p>
</li>
<li>
<p>ARP协议用于将IP地址转换为MAC地址。在通信过程中，当一台主机需要知道另一台主机的MAC地址时，它可以使用ARP协议查询目标主机的MAC地址。</p>
</li>
<li>
<p>RARP协议则用于将MAC地址转换为IP地址。在启动过程中，当一台主机需要知道自己的IP地址时，可以向网络中发送RARP请求，以获取自己的IP地址。</p>
</li>
<li>
<p><strong>工作方式不同：</strong></p>
</li>
<li>
<p>ARP协议是一种广播协议。当主机需要知道另一台主机的MAC地址时，它在本地局域网上广播一个ARP请求包，只有目标主机会响应并将自己的MAC地址发送回来。</p>
</li>
<li>
<p>RARP 客户端在本地链路广播请求，由维护 MAC 到 IP 映射的 RARP 服务器应答，不要求客户端事先知道服务器地址。</p>
</li>
<li>
<p><strong>用途不同：</strong></p>
</li>
<li>
<p>由于现代操作系统和网络设备能够自动分配IP地址，RARP协议的使用已经相对较少。</p>
</li>
<li>
<p>ARP协议在现代网络通信中仍然扮演着重要的角色，用于解析目标主机的MAC地址。</p>
</li>
<li>
<p>总体而言，ARP和RARP在网络中的地址解析过程中有各自的功能和应用场景，不能把二者当成可互换的地址配置方案；RARP 的广播请求机制见 <a href="https://www.rfc-editor.org/rfc/rfc903.html" target="_blank" rel="noopener noreferrer">RFC 903</a>。</p>
</li>
</ul>
<h2>17、TCP/IP原理？</h2>
<ul>
<li>TCP/IP 协议不是 TCP 和 IP 这两个协议的合称，而是指因特网整个 TCP/IP 协议族。从协议分层模型方面来讲，TCP/IP 由四个层次组成：网络接口层、网络层、传输层、应用层。</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163946827-a8ae97a2-8585-4bfa-a814-b55782dcc414-6fcdeff6-ed1e5afa7c64-445w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163946827-a8ae97a2-8585-4bfa-a814-b55782dcc414-6fcdeff6-ed1e5afa7c64-445w.webp" srcset="/images/optimized/1755163946827-a8ae97a2-8585-4bfa-a814-b55782dcc414-6fcdeff6-ed1e5afa7c64-445w.webp 445w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="445" height="254" decoding="async" data-image-source="/javabgw-img/1755163946827-a8ae97a2-8585-4bfa-a814-b55782dcc414.png" data-image-width="445" data-image-height="254" data-image-detail="/images/optimized/1755163946827-a8ae97a2-8585-4bfa-a814-b55782dcc414-6fcdeff6-ed1e5afa7c64-445w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li><strong>网络访问层(Network Access Layer)</strong></li>
<li>网络访问层(Network Access Layer)在 TCP/IP 参考模型中并没有详细描述，只是指出主机必须使用某种协议与网络相连。</li>
<li><strong>网络层(Internet Layer)</strong></li>
<li>网络层(Internet Layer)是整个体系结构的关键部分，其功能是使主机可以把分组发往任何网络，并使分组独立地传向目标。这些分组可能经由不同的网络，到达的顺序和发送的顺序也可能不同。高层如果需要顺序收发，那么就必须自行处理对分组的排序。互联网层使用因特网协议(IP，Internet Protocol)。</li>
<li><strong>传输层(Transport Layer-TCP/UDP)</strong></li>
<li>传输层(Transport Layer)使源端和目的端机器上的对等实体可以进行会话。在这一层定义了两个端到端的协议：传输控制协议(TCP，Transmission Control Protocol)和用户数据报协议(UDP，User Datagram Protocol)。TCP 是面向连接的协议，它提供可靠的字节流传输和对上层应用的连接服务。为此，除了基本的数据传输外，它还有可靠性保证、流量控制、多路复用等功能；TCP 本身不提供加密和应用身份认证。UDP 是面向无连接的不可靠传输的协议，主要用于不需要 TCP 的排序和流量控制等功能的应用程序。</li>
<li><strong>应用层(Application Layer)</strong></li>
<li>应用层(Application Layer)包含所有的高层协议，包括：虚拟终端协议(TELNET，TELecommunicationsNETwork)、文件传输协议(FTP，File Transfer Protocol)、电子邮件传输协议(SMTP，Simple Mail Transfer Protocol)、域名服务(DNS，Domain Name Service)、网上新闻传输协议(NNTP，Net News Transfer Protocol)和超文本传送协议(HTTP，HyperText Transfer Protocol)等。</li>
</ul>
<h2>18、说下CDN 原理？</h2>
<ul>
<li>CDN 一般包含<strong>分发服务系统</strong>、<strong>负载均衡系统</strong>和<strong>管理系统</strong></li>
<li><strong>分发服务系统</strong></li>
<li>其基本的工作单元就是各个 Cache 服务器。负责直接响应用户请求，将内容快速分发到用户；同时还负责内容更新，保证和源站内容的同步。根据内容类型和服务种类的不同，分发服务系统分为多个子服务系统，如：网页加速服务、流媒体加速服务、应用加速服务等。每个子服务系统都是一个分布式的服务集群，由功能类似、地域接近的分布部署的Cache 集群组成。在承担内容同步、更新和响应用户请求之外，分发服务系统还需要向上层的管理调度系统反馈各个Cache 设备的健康状况、响应情况、内容缓存状况等，以便管理调度系统能够根据设定的策略决定由哪个 Cache 设备来响应用户的请求。</li>
<li><strong>负载均衡系统：</strong></li>
<li>负载均衡系统是整个 CDN 系统的中枢。负责对所有的用户请求进行调度，确定提供给用户的最终访问地址。</li>
<li>使用分级实现。最基本的两极调度体系包括全局负载均衡（GSLB）和本地负载均衡（SLB）。</li>
<li>GSLB 根据用户地址和用户请求的内容，主要根据就近性原则，确定向用户服务的节点。一般通过 DNS 解析或者应用层重定向（Http 3XX 重定向）的方式实现。</li>
<li>SLB 主要负责节点内部的负载均衡。当用户请求从 GSLB 调度到 SLB 时，SLB 会根据节点内各个Cache 设备的工作状况和内容分布情况等对用户请求重定向。SLB 的实现有四层调度（LVS）、七层调度（Nginx）和链路负载调度等。</li>
<li><strong>管理系统：分为运营管理和网络管理子系统。</strong></li>
<li>网络管理系统实现对 CDN 系统的设备管理、拓扑管理、链路监控和故障管理，为管理员提供对全网资源的可视化的集中管理，通常用 web 方式实现。</li>
<li>运营管理是对 CDN 系统的业务管理，负责处理业务层面的与外界系统交互所必须的一些收集、整理、交付工作。包括用户管理、产品管理、计费管理、统计分析等。</li>
</ul>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/javabgw-img/1755163943397-ee595666-0dc2-4903-8b8c-12afaa5205d6.png" type="image/png"/>
    </item>
    <item>
      <title>Kafka</title>
      <link>https://qiuzhiwang.vip/javabgw/Kafka.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/Kafka.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Kafka</source>
      <description>分析 Kafka 分区副本、acks、ISR、位移提交、重复消费和幂等，区分复制可靠性与业务处理结果。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>这页题量很大，建议先吃透 4 条主线：Kafka 是什么 -&gt; 为什么快 -&gt; 可靠性怎么保 -&gt; 顺序与幂等如何落地。<br>
面试回答时先给结论，再给关键参数（如 acks、ISR、offset），最后补一个业务场景。</p>
</div>
<p>本文以 <strong>Apache Kafka 4.0.x 与官方 Java 客户端</strong>为基准；消费者分配讨论普通 consumer group（<code>subscribe</code>），不涵盖 Share Group。历史版本差异另行注明。</p>
<h2>推荐训练法</h2>
<ol>
<li>第一遍只看问题标题，先建立知识地图。</li>
<li>第二遍优先掌握“可靠性、顺序性、重复消费”三块高频追问。</li>
<li>第三遍用 2 分钟口述 1 题，训练可表达性。</li>
</ol>
<h2>什么是消息中间件？（了解）</h2>
<ul>
<li>消息中间件是基于队列与消息传递技术，在网络环境中为应用系统提供<strong>同步或异步、可靠的</strong>消息传输的支撑性软件系统。</li>
<li>消息中间件利用<strong>高效可靠</strong>的消息传递机制进行平台无关的数据交流，并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型，它可以在分布式环境下扩展进程间的通信。</li>
</ul>
<h2>Kafka是什么？有什么作用？（了解）</h2>
<ul>
<li>Kafka 是一个分布式的流式处理平台，它以高吞吐、可持久化、可水平扩展、支持流数据处理等多种特性而被广泛使用</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163618950-c309bd91-56cc-4f6e-8662-389ccf1e204f-aa580358-09591e861dc9-1080w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163618950-c309bd91-56cc-4f6e-8662-389ccf1e204f-aa580358-09591e861dc9-800w.webp" srcset="/images/optimized/1755163618950-c309bd91-56cc-4f6e-8662-389ccf1e204f-aa580358-09591e861dc9-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1080" height="459" decoding="async" data-image-source="/javabgw-img/1755163618950-c309bd91-56cc-4f6e-8662-389ccf1e204f.png" data-image-width="1080" data-image-height="459" data-image-detail="/images/optimized/1755163618950-c309bd91-56cc-4f6e-8662-389ccf1e204f-aa580358-09591e861dc9-1080w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>
<p>主要功能体现于三点：</p>
</li>
<li>
<p><strong>消息系统</strong>：kafka与传统的消息中间件都具备<strong>系统解耦、冗余存储、流量削峰、缓冲、异步通信、扩展性、可恢复性</strong>等功能。与此同时，kafka还提供了大多数消息系统难以实现的消息顺序性保障及回溯性消费的功能。</p>
</li>
<li>
<p><strong>存储系统</strong>：kafka把<strong>消息持久化到磁盘</strong>，相比于其他基于内存存储的系统而言，有效的降低了消息丢失的风险。这得益于其消息持久化和多副本机制。也可以将kafka作为长期的存储系统来使用，需要按容量配置保留策略；日志压缩只保留每个 key 的最新状态（删除标记也有保留期限），不等于永久保留所有历史事件。</p>
</li>
<li>
<p><strong>流式处理平台</strong>：kafka为流行的流式处理框架提供了可靠的数据来源，还提供了一个完整的流式处理框架，比如窗口、连接、变换和聚合等各类操作。</p>
</li>
</ul>
<h2>Kafka中有哪几个组件?</h2>
<ul>
<li><strong>主题</strong>：Kafka主题是一堆或一组消息。</li>
<li><strong>生产者</strong>：在Kafka，生产者发布通信以及向Kafka主题发布消息。</li>
<li><strong>消费者</strong>：Kafka消费者订阅了一个主题，并且还从主题中读取和处理消息。</li>
<li><strong>经纪人</strong>：在管理主题中的消息存储时，我们使用Kafka Brokers。</li>
</ul>
<h2>Kafka Replicas是怎么管理的？（了解）</h2>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163618532-d1d12b18-595c-460e-b062-b0a5cee7513f-31c2eaaa-fba21043d156-982w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163618532-d1d12b18-595c-460e-b062-b0a5cee7513f-31c2eaaa-fba21043d156-800w.webp" srcset="/images/optimized/1755163618532-d1d12b18-595c-460e-b062-b0a5cee7513f-31c2eaaa-fba21043d156-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="982" height="516" decoding="async" data-image-source="/javabgw-img/1755163618532-d1d12b18-595c-460e-b062-b0a5cee7513f.png" data-image-width="982" data-image-height="516" data-image-detail="/images/optimized/1755163618532-d1d12b18-595c-460e-b062-b0a5cee7513f-31c2eaaa-fba21043d156-982w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>AR:分区中的<strong>所有 Replica 统称为 AR</strong></li>
<li>ISR:所有与 Leader 副本<strong>保持一定程度同步</strong>的Replica(包括 Leader 副本在内)组成 ISR</li>
<li>OSR:与 Leader 副本<strong>同步滞后过多的</strong> Replica 组成了 OSR</li>
<li>Leader 负责维护和跟踪 ISR 集合中所有 Follower 副本的滞后状态，当 Follower 副本落后过多时，就会将其放入 OSR 集合，当 Follower 副本追上了 Leader 的进度时，就会将其放入 ISR 集合。</li>
<li>默认情况下，<strong>只有 ISR 中的副本才有资格晋升为 Leader</strong>。</li>
</ul>
<h2>kafka的消费者是pull(拉)还是push(推)模式，这种模式有什么好处？</h2>
<ul>
<li>Kafka 遵循了一种大部分消息系统共同的传统的设计：producer 将消息推送到 broker，consumer 从broker 拉取消息。</li>
<li>优点：pull模式消费者自主决定是否批量从broker拉取数据，而push模式在无法知道消费者消费能力情况下，不易控制推送速度，太快可能造成消费者奔溃，太慢又可能造成浪费。</li>
<li>缺点：如果 broker 没有可供消费的消息，将导致 consumer 不断在循环中轮询，直到新消息到到达。为了避免这点，broker 长轮询由 <code>fetch.min.bytes</code> 指定期望最小字节数，<code>fetch.max.wait.ms</code> 限制等待时间；二者与应用 <code>poll(Duration)</code> 的等待上限不同。</li>
</ul>
<h2>kafka维护消息状态的跟踪方法（了解）</h2>
<ul>
<li>在<strong>同一个消费者组内</strong>，同一分区在稳定分配时只交给一个消费者；不同组可以独立消费同一分区。已提交 offset 代表下一条待消费位置，区别于客户端已拉取的位置。</li>
<li>offset 不等于业务完成凭证。先提交再处理，崩溃后可能跳过未处理消息；先处理再提交，二者之间崩溃会重放。位移跟踪不会自动消除丢失或重复。</li>
</ul>
<h2>kafka判断一个节点还活着的有那两个条件？</h2>
<ul>
<li><strong>存活与同步资格是两件事</strong>。旧 ZooKeeper 模式依赖 broker 的 ZooKeeper 会话；Kafka 4.0 已移除该模式。KRaft 中 broker 向活动 controller 发送心跳，超时会被围栏（fence）。</li>
<li>follower 是否留在 ISR 还取决于复制进度，未在 <code>replica.lag.time.max.ms</code> 允许时间内追上 leader 会被移出 ISR。进程存活不代表能承担干净的 leader 切换。</li>
</ul>
<p>参见官方 <a href="https://kafka.apache.org/40/getting-started/zk2kraft/" target="_blank" rel="noopener noreferrer">KRaft 与 ZooKeeper 差异</a>。</p>
<h2>讲一讲 kafka 的 ack 的三种机制</h2>
<p>Java 客户端使用 <code>acks</code>（<code>request.required.acks</code> 是旧客户端名称），4.0 默认 <code>all</code>：</p>
<ul>
<li><code>0</code>：不等待 broker 响应，无法确认是否收到记录，客户端通常也无法据此重试。</li>
<li><code>1</code>：leader 写本地日志后响应，不等 follower 复制；此时 leader 故障可能丢失已确认记录。</li>
<li><code>all</code>（<code>-1</code>）：等待当前 ISR 确认，不是等待全部配置副本。配合 <code>min.insync.replicas</code> 约束最少 ISR 数量，不足则拒绝写入，不能宣称绝不丢失。</li>
</ul>
<p>参见官方 <a href="https://kafka.apache.org/40/configuration/producer-configs/#acks" target="_blank" rel="noopener noreferrer">acks 与幂等配置</a>。</p>
<h2>kafka 分布式（不是单机）的情况下，如何保证消息的顺序消费?</h2>
<ul>
<li>Kafka 中发送 1 条消息的时候，可以指定(topic, partition, key) 3 个参数，partiton 和 key 是可选的。</li>
<li>Kafka 分布式的单位是 partition，同一个 partition 用一个 write ahead log 组织，所以可以保证FIFO 的顺序。不同 partition 之间不能保证顺序。因此你可以指定 partition，将相应的消息发往同 1个 partition，并且在消费端，同组稳定分配时，1 个 partition 只交给 1 个 consumer，就可以实现这些消息的顺序消费。</li>
<li>另外，你也可以指定 key（比如 order id），具有同 1 个 key 的所有消息，会发往同 1 个partition，分区数与策略不变时，同 key 可保持分区内日志顺序；消费端还须按分区串行处理。关闭幂等且有多个在途请求时，生产重试可能乱序。</li>
</ul>
<h2>kafka 如何不消费重复数据？比如扣款，我们不能重复的扣。</h2>
<ul>
<li><strong>幂等生产者：</strong>producer ID、epoch 和分区序列号避免同一生产会话重试导致重复写入；应用再次 <code>send</code> 同一业务事件仍可产生两条记录。4.0 无冲突配置时默认启用，要求 <code>acks=all</code>、<code>retries&gt;0</code>、<code>max.in.flight.requests.per.connection&lt;=5</code>。</li>
<li><strong>事务：</strong>Kafka 可原子提交输出记录与消费位移，消费端使用 <code>isolation.level=read_committed</code> 隔离未提交和已中止的事务记录。这适用于 Kafka 到 Kafka 的处理，不能直接将外部数据库扣款纳入 Kafka 事务。</li>
<li><strong>业务幂等：</strong>用事件 ID 建唯一约束，将“写去重记录”和“更新余额”放在同一数据库事务中，再提交 Kafka 位移；重放时约束阻止再次扣款。调用外部支付接口还需对方支持幂等键。</li>
<li><strong>手动提交 offset：</strong>处理完成再提交仍有“业务成功、提交前崩溃”的重放窗口；提交失败也需处理，手动提交本身不去重。</li>
</ul>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>poll(offset=42) -> 数据库事务：去重记录 + 扣款 -> commit offset=43</span></span>
<span class="line"><span>                                      |</span></span>
<span class="line"><span>                               提交前崩溃：重放 42</span></span>
<span class="line"><span>                               唯一约束阻止重复扣款</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>参见官方 <a href="https://kafka.apache.org/40/design/design/#message-delivery-semantics" target="_blank" rel="noopener noreferrer">交付语义</a> 和 <a href="https://kafka.apache.org/40/configuration/consumer-configs/#isolation.level" target="_blank" rel="noopener noreferrer">隔离级别</a>。</p>
<h2>partition的数据文件（offffset，MessageSize，data）（了解）</h2>
<ul>
<li>标题中的 <code>(offset, MessageSize, data)</code> 只能作为旧格式的简化记忆，不是 Kafka 4.0 的完整布局。自 0.11 起的 magic=2 格式以 <strong>RecordBatch</strong> 为单位，含 baseOffset、batchLength、CRC、属性、时间戳、producer ID/epoch、序列号等；批内 Record 使用 offset delta，还可包含 key、value、headers。</li>
<li>offset 是分区内逻辑位置，不是文件字节地址；日志压缩清理后可能有空洞，不能保证指定 offset 仍有记录。batchLength 也不是单条消息体大小。</li>
<li>分区日志分成多个 segment：<code>.log</code> 存数据，<code>.index</code> 是 offset 到文件位置的稀疏索引，<code>.timeindex</code> 辅助按时间查找。先按 base offset 定位 segment，再查索引并顺序扫描日志。</li>
</ul>
<p>参见官方 <a href="https://kafka.apache.org/40/implementation/message-format/" target="_blank" rel="noopener noreferrer">消息格式</a> 与 <a href="https://kafka.apache.org/40/implementation/log/" target="_blank" rel="noopener noreferrer">日志实现</a>。</p>
<h2>kafka如何实现数据的高效读取？（顺序读写、分段命令、二分查找）</h2>
<ul>
<li>
<p>Kafka实现数据的高效读取主要依赖于其独特的设计，包括顺序读写、分段存储和二分查找等机制。具体来说：</p>
</li>
<li>
<p>顺序读写：Kafka采用顺序写入的方式，新消息会被追加到已有消息的末尾。这种写入模式减少了磁盘I/O操作的随机访问，大大提高了写入效率。同样，读取时也是顺序读取，这与磁盘的物理特性相契合，进一步提升了读取性能。</p>
</li>
<li>
<p>分段存储：Kafka将每个partition划分为多个segment，每个 segment 包含日志与索引文件，按大小或时间等条件滚动，实际大小不固定。这样设计避免了管理大文件的复杂性，同时方便了对旧数据的清理。每个segment文件独立存储，使得数据管理和维护更加高效。</p>
</li>
<li>
<p>二分查找：Kafka为每个segment维护了一个索引文件，其中存储了消息的offset和物理位置的映射关系。当需要查找特定offset的消息时，Kafka会利用二分查找算法先定位 segment，再在稀疏索引中找不大于目标 offset 的最近位置，随后顺序扫描日志，从而实现了高效的数据访问。</p>
</li>
<li>
<p>此外，Kafka还通过内存缓存、消息压缩等技术手段进一步提升了数据的读取效率。内存缓存使得Kafka可以将索引文件的部分内容保留在内存中，加速查找操作；而消息压缩则减少了磁盘和网络带宽的使用，使得数据在传输和存储时更加高效。</p>
</li>
</ul>
<h2>Kafka 消费者端的 Rebalance 操作什么时候发生？（了解）</h2>
<ul>
<li>
<p>在Kafka中，消费者端的Rebalance操作主要发生在以下几种情况，以确保消费者组能够公平且高效地分配订阅主题的分区：</p>
</li>
<li>
<p><strong>消费者组成员变化：</strong>当有新消费者加入消费者组或现有消费者离开（无论是正常关闭、崩溃还是长时间未发送心跳被判定为“死亡”）时，会触发Rebalance。这是因为消费者组的成员变化可能导致分区分配不再均衡。</p>
</li>
<li>
<p><strong>订阅主题分区数变化：</strong>如果消费者组订阅的任一主题的分区数发生变化（如分区增加；Kafka 不支持直接减少已有主题的分区数），也会触发Rebalance。这是为了根据新的分区情况重新分配消费者与分区之间的关系。</p>
</li>
<li>
<p><strong>消费者组订阅变化：</strong>当消费者组订阅的主题列表发生变化，比如消费者取消了对某些主题的订阅，同样可能引发Rebalance。</p>
</li>
<li>
<p><strong>协调者故障或变化：</strong>负责管理消费者组的协调者（Coordinator）如果发生故障，或者消费者组被重新分配给了另一个协调者，不必然触发分区重分配，状态恢复时可继续原分配。</p>
</li>
<li>
<p>Rebalance过程是一个协调消费者组内成员重新分配分区的过程，它确保了消费者组能够动态地适应变化，保持高效的消息处理能力。然而，频繁的Rebalance可能会对性能产生负面影响，因此在设计和使用Kafka时，需要合理设置相关参数和配置，以减少不必要的Rebalance操作。</p>
</li>
</ul>
<p>4.0 的 <code>group.protocol</code> 默认是 <code>classic</code>；选择 <code>consumer</code> 协议后使用新的增量协调机制，不能将所有再均衡都理解成全组停顿。经典协议使用 <code>session.timeout.ms</code>、<code>heartbeat.interval.ms</code>，新协议的心跳与会话超时由服务端相关参数控制。处理过慢超过 <code>max.poll.interval.ms</code> 也会影响组成员资格。手动 <code>assign</code> 不参与这种自动分区分配。参见官方 <a href="https://kafka.apache.org/40/configuration/consumer-configs/" target="_blank" rel="noopener noreferrer">消费者配置</a>。</p>
<h2>Kafka的那些设计让它有如此高的性能?</h2>
<ul>
<li><strong>顺序写入与零拷贝技术：</strong>与传统的数据库系统不同，Kafka采用顺序写入的方式，这大大减少了磁盘寻道时间，提高了写入性能。同时，在适用的明文传输路径上可利用零拷贝减少数据搬运；TLS 等路径不能一概套用 sendfile 的零拷贝结论。</li>
<li><strong>分区与副本机制：</strong>Kafka将数据分散到多个分区中，每个分区都可以独立处理数据写入和读取请求，充分利用了集群的计算和存储能力。同时，每个分区都有多个副本，既保证了数据的可靠性，复制由 follower 拉取推进，但 <code>acks=all</code> 仍等待 ISR；副本增加网络与存储开销。</li>
<li><strong>批量操作与压缩：</strong>Kafka支持批量写入和压缩功能，批量写入减少了网络传输和磁盘IO的次数，而压缩功能则降低了磁盘存储空间的使用，并减少了数据传输的开销。</li>
<li><strong>高效的消费者组机制：</strong>消费者组允许多个消费者并行消费不同分区；同组同一分区在稳定分配时只交给一个消费者，提高了并发处理能力。同时，Kafka支持多种消费模式，如发布订阅模式和队列模式，以适应不同的业务需求。</li>
<li><strong>可扩展的架构：</strong>Kafka的分布式架构可以轻松通过添加更多的节点来扩展处理能力。这种水平扩展的能力使得Kafka能够应对大规模数据处理和实时流处理场景。</li>
</ul>
<h2>Kafka为什么不支持读写分离？</h2>
<p>问题的前提已过时：Kafka <strong>自 2.4 起支持 follower fetching</strong>。默认仍读 leader；配置 broker 的 <code>replica.selector.class=org.apache.kafka.common.replica.RackAwareReplicaSelector</code>、各 broker 的 <code>broker.rack</code> 和消费者 <code>client.rack</code> 后，可优先选择合适的就近副本。</p>
<p>生产者仍向 leader 写入；follower 读取受已提交数据边界限制，不是任意读取滞后副本尾部。就近读取主要减少跨机架/可用区流量，副本滞后仍可能增加读取等待。</p>
<p>参见官方 <a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-392%3A+Allow+consumers+to+fetch+from+closest+replica" target="_blank" rel="noopener noreferrer">KIP-392</a>。</p>
<h2>分区Leader选举策略有几种？</h2>
<p>这里指<strong>数据分区 leader</strong>，区别于 KRaft controller quorum 选主；不是按 offset 最大竞选，也没有供业务任意实现选举算法的普通客户端插件。</p>
<ul>
<li><strong>干净选举：</strong>从可用 ISR 中选择替代者；没有合格副本时通常等待恢复，不会自动退到 OSR。</li>
<li><strong>首选副本选举：</strong>分配列表第一个副本是 preferred replica；它在 ISR 中且可用时，可通过管理操作或自动 leader 均衡迁回。</li>
<li><strong>非干净选举：</strong>显式允许 <code>unclean.leader.election.enable</code> 后，无可用 ISR 时可使用落后副本恢复服务，可能丢失已提交数据。4.0 默认关闭。</li>
</ul>
<p>参见官方 <a href="https://kafka.apache.org/40/configuration/broker-configs/#unclean.leader.election.enable" target="_blank" rel="noopener noreferrer">Broker 配置</a>。</p>
<h2>请简述下你在哪些场景下会选择 Kafka？</h2>
<ul>
<li><strong>实时数据流处理：</strong>Kafka擅长处理大规模、高吞吐量的实时数据流。当需要实时收集、处理和分析大量数据时，比如日志聚合、监控数据、用户行为追踪等，Kafka是一个理想的选择。</li>
<li><strong>微服务间通信：</strong>在微服务架构中，服务间需要高效、可靠的通信机制。Kafka作为消息中间件，可以提供解耦、异步和可靠的通信方式，帮助微服务实现松耦合和高内聚。</li>
<li><strong>事件驱动架构：</strong>Kafka支持事件驱动架构，允许系统根据事件进行响应和处理。这对于需要实时处理事件、触发后续操作或进行复杂工作流协调的应用场景非常有用。</li>
<li><strong>日志和数据湖：</strong>Kafka可以作为日志和数据湖的存储引擎，为大数据分析、机器学习等提供数据源。它的顺序存储和高效读取特性使得处理大规模数据变得更加容易和高效。</li>
<li><strong>分布式系统的消息总线：</strong>在需要构建分布式系统的消息总线时，Kafka可以提供高可用、可扩展和容错的消息传递服务，确保系统组件之间的通信畅通无阻。</li>
</ul>
<h2>请谈一谈 Kafka 数据一致性原理</h2>
<ul>
<li><strong>副本复制：</strong>leader 接收写入，follower 拉取日志；普通消费者默认读 leader，也可按前述配置读取 follower。</li>
<li><strong>提交边界：</strong>高水位（HW）反映复制提交进度，普通消费者只读高水位之前的记录。ISR 用于复制确认和干净选举，OSR 仍会复制日志追赶。<code>read_committed</code> 还受最后稳定偏移量（LSO）限制，并跳过已中止事务记录。</li>
<li><strong>幂等与事务：</strong>生产重试去重、Kafka 内原子提交与外部业务幂等解决不同问题，不能笼统称为所有系统之间精确一次传递。可靠性还取决于副本数、确认策略和故障范围。</li>
</ul>
<h2>生产者发送消息有哪些模式？</h2>
<ul>
<li><strong>发后即忘</strong>（fire-and-forget）:它只管往 Kafka 里面发送消息，但是<strong>不关心消息是否正确到达</strong>，这种方式的效率最高，但是可靠性也最差，比如当发生某些不可重试异常的时候会造成消息的丢失</li>
<li><strong>同步</strong>（sync）:producer.send()返回一个Future对象，调用get()方法变回进行同步等待，就知道消息是否发送成功，<strong>逐条 <code>send(...).get()</code> 才会串行等待，也可先发送多条再等待各 Future</strong></li>
<li><strong>异步</strong>（async）:Kafka支持 producer.send() 传入一个回调函数，消息不管成功或者失败都会调用这个回调函数，这样就算是异步发送，我们也知道消息的发送情况，然后再回调函数中选择记录日志还是重试都取决于调用方</li>
</ul>
<h2>发送消息的分区策略有哪些？</h2>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163618481-517b8c32-65da-4dc1-bb9b-ea5e204a4db9-1134aa8e-46dc889f12da-1080w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163618481-517b8c32-65da-4dc1-bb9b-ea5e204a4db9-1134aa8e-46dc889f12da-800w.webp" srcset="/images/optimized/1755163618481-517b8c32-65da-4dc1-bb9b-ea5e204a4db9-1134aa8e-46dc889f12da-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1080" height="504" decoding="async" data-image-source="/javabgw-img/1755163618481-517b8c32-65da-4dc1-bb9b-ea5e204a4db9.png" data-image-width="1080" data-image-height="504" data-image-detail="/images/optimized/1755163618481-517b8c32-65da-4dc1-bb9b-ea5e204a4db9-1134aa8e-46dc889f12da-1080w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li><strong>1.轮询：依次</strong>将消息发送该topic下的所有分区，可显式选择 RoundRobinPartitioner；4.0 默认无 key 时采用粘性分区聚合批次，并可自适应偏向较快 broker，不等同于逐条轮询。</li>
<li><strong>2.key 指定分区：</strong>在创建消息是 key 不为空，并且使用默认分区器，Kafka 会将 key 进行 hash，然后<strong>根据hash值映射到指定的分区上</strong>。这样的好处是 key 相同的消息会在一个分区下，Kafka 并不能保证全局有序，但是在每个分区下的消息是有序的，按照顺序存储，按照顺序消费。在保证同一个 key 的消息是有序的，这样基本能满足消息的顺序性的需求。但是<strong>如果 partation 数量发生变化，那就很难保证 key 与分区之间的映射关系了</strong>。</li>
<li><strong>3.自定义策略：</strong>实现 Partitioner 接口就能自定义分区策略。</li>
<li>4.指定 Partiton 发送</li>
</ul>
<p>参见官方 <a href="https://kafka.apache.org/40/configuration/producer-configs/#partitioner.class" target="_blank" rel="noopener noreferrer">分区器配置</a>。</p>
<h2>Kafka 支持读写分离吗？为什么？（了解）</h2>
<p>支持，具体条件见上文“Kafka为什么不支持读写分离？”：2.4+ 的 follower fetching 需要副本选择器和机架配置配合，默认仍读 leader。生产者继续写 leader，follower 仍需复制数据。跨机架流量与副本滞后是主要权衡因素。</p>
<h2>那 Kafka 是怎么去实现负载均衡的？（了解）</h2>
<ul>
<li>Kafka 的负责均衡主要是<strong>通过分区来实现</strong>的，我们知道 Kafka 是<strong>主写主读</strong>的架构，如下图:</li>
</ul>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163618655-fda77c4b-3680-4f04-a361-6c0cc09d75f3-085d41af-f77ef8b7463e-1080w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163618655-fda77c4b-3680-4f04-a361-6c0cc09d75f3-085d41af-f77ef8b7463e-800w.webp" srcset="/images/optimized/1755163618655-fda77c4b-3680-4f04-a361-6c0cc09d75f3-085d41af-f77ef8b7463e-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1080" height="669" decoding="async" data-image-source="/javabgw-img/1755163618655-fda77c4b-3680-4f04-a361-6c0cc09d75f3.png" data-image-width="1080" data-image-height="669" data-image-detail="/images/optimized/1755163618655-fda77c4b-3680-4f04-a361-6c0cc09d75f3-085d41af-f77ef8b7463e-1080w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>共三个 broker ，里面各有三个副本，总共有三个 partation， 深色的是 leader，浅色的是 follower，上下灰色分别代表生产者和消费者，虚线代表 follower 从 leader 拉取消息。</li>
<li>我们从这张图就可以很明显的看出来，<strong>每个 broker 都有消费者拉取消息，每个 broker 也都有生产者发送消息，图中 leader 数量相同，但分区流量和消息大小不同，实际负载未必一样</strong>，这也说明了 kafka 独特的架构方式可以通过主写主读来实现负载均衡。</li>
</ul>
<h2>Kafka 的可靠性是怎么保证的？</h2>
<figure><a class="image-detail-trigger" data-image-viewer href="/images/optimized/1755163618627-303cc572-7080-4085-af2b-6dc5fd9980ac-bdc6d434-aed9e9f1e366-1080w.webp" target="_blank" rel="noopener noreferrer" aria-label="查看大图：img"><img alt="img" tabindex="0" loading="lazy" src="/images/optimized/1755163618627-303cc572-7080-4085-af2b-6dc5fd9980ac-bdc6d434-aed9e9f1e366-800w.webp" srcset="/images/optimized/1755163618627-303cc572-7080-4085-af2b-6dc5fd9980ac-bdc6d434-aed9e9f1e366-800w.webp 800w" sizes="(max-width: 959px) calc(100vw - 40px), (max-width: 1439px) calc(100vw - 360px), 900px" width="1080" height="448" decoding="async" data-image-source="/javabgw-img/1755163618627-303cc572-7080-4085-af2b-6dc5fd9980ac.png" data-image-width="1080" data-image-height="448" data-image-detail="/images/optimized/1755163618627-303cc572-7080-4085-af2b-6dc5fd9980ac-bdc6d434-aed9e9f1e366-1080w.webp"></a><figcaption>img</figcaption></figure>
<ul>
<li>
<p><strong>1.acks：</strong>这个参数用来指定分区中有多少个副本收到这条消息，生产者才认为这条消息是写入成功的，这个参数有三个值：</p>
</li>
<li>
<p>1.acks = 1。Java 客户端自 3.0 起默认 all，4.0 也如此。生产者发送消息，<strong>只要 leader 副本成功写入消息，就代表成功</strong>。这种方案的问题在于，当返回成功后，如果 leader 副本和 follower 副本还<strong>没有来得及同步</strong>，leader 就崩溃了，那么在选举后新的 leader 就没有这条<strong>消息，也就丢失了</strong>。</p>
</li>
<li>
<p>2.acks = 0。生产者发送消息后直接算写入成功，不需要等待响应。这个方案的问题很明显，<strong>只要服务端写消息时出现任何问题，都会导致消息丢失</strong>。</p>
</li>
<li>
<p>3.acks = -1 或 acks = all。生产者发送消息后，需要等待 ISR 中的所有副本都成功写入消息后才能收到服务端的响应。毫无疑问这种方案的<strong>可靠性是最高</strong>的，但是如果 ISR 中只有leader 副本，那么就和 acks = 1 毫无差别了。</p>
</li>
<li>
<p><strong>2.消息发送的方式：</strong>我们可以<strong>通过同步或者异步</strong>获取响应结果，<strong>失败做重试</strong>来保证消息的可靠性。</p>
</li>
<li>
<p><strong>3.手动提交位移：</strong><code>enable.auto.commit=true</code> 时，客户端按 <code>auto.commit.interval.ms</code> 周期提交 poll 返回记录对应的位置，不感知业务是否完成。但是如果消费者消费出错，没有进入真正的业务处理，那么就可能会导致这条消息消费失败，从而丢失。我们可以开启手动提交位移，等待业务正常处理完成后，再提交offset。</p>
</li>
</ul>
<p><strong>确认与可用性的关系：</strong>副本数为 3，设置 <code>acks=all</code>、<code>min.insync.replicas=2</code>，关闭非干净选举时：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>ISR={A,B,C} -> C 落后被移出 -> ISR={A,B}：A、B 复制后可确认</span></span>
<span class="line"><span>ISR={A}    -> 不足 min ISR=2：拒绝写入，等待副本恢复</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>min ISR 是写入门槛，不是只等待其中两个副本：ISR 为三个时 <code>all</code> 仍等待当前 ISR。确认也不等于每个副本已同步刷盘。超时不能证明记录未写入，应利用幂等重试并检查最终结果。</p>
<p>参见官方 <a href="https://kafka.apache.org/40/configuration/topic-level-configs/#min.insync.replicas" target="_blank" rel="noopener noreferrer">min.insync.replicas</a>。上述为机制示意，未运行集群故障实验。</p>
<h2>分区数越多越好吗？吞吐量就会越高吗？（了解）</h2>
<ul>
<li><strong>增加分区可提高并行度，但吞吐量不必然与分区数成正比，还受磁盘、网络、批次和业务处理能力限制</strong></li>
</ul>
<p><strong>客户端/服务器端需要使用的内存就越多（超过了一定限度，就会对性能造成影响）</strong></p>
<ul>
<li>服务端在很多组件中都维护了分区级别的缓存，分区数越大，<strong>缓存成本</strong>也就越大。</li>
<li>同组有效消费并行度受分区数限制，但不会自动创建线程，一个消费者也可处理多个分区，<strong>线程的开销成本</strong>也就越大</li>
<li>生产者发送消息有缓存的概念，会为每个分区缓存消息，当积累到一定程度或者时间时会将消息发送到分区，<strong>分区越多，这部分的缓存</strong>也就越大</li>
</ul>
<h2>什么情况下 kafka 会丢失消息？</h2>
<ul>
<li>
<p>Kafka 有三次消息传递的过程：生产者发消息给 Broker，Broker 同步消息和持久化消息，Broker 将消息传递给消费者。</p>
</li>
<li>
<p>1.生产者发送数据:</p>
</li>
<li>
<p>当 acks 为 0，<strong>只要服务端写消息时出现任何问题，都会导致消息丢失</strong>。</p>
</li>
<li>
<p>当 acks 配置为 1 时，生产者发送消息，只要 leader 副本成功写入消息，就代表成功。这种方案的问题在于，当返回成功后，<strong>如果 leader 副本和 follower 副本还没有来得及同步，leader 就崩溃了，那么在选举后新的 leader 就没有这条消息，也就丢失了</strong>。</p>
</li>
<li>
<p>2.Broker 存储数据:kafka 通过 Page Cache 将数据写入磁盘。</p>
</li>
<li>
<p>Page Cache 就是当往磁盘文件写入的时候，系统会先将数据流写入缓存中，但是<strong>什么时候将缓存的数据写入文件中是由操作系统自行决定</strong>。单副本整机断电可能丢失尚未刷盘的数据；其他存活同步副本可用于恢复，不能将单节点断电等同于集群必然丢失消息。多副本同时失效仍可能丢失已确认数据。</p>
</li>
<li>
<p>3.消费者消费数据:在开启<strong>自动提交 offset</strong> 时，客户端周期提交已拉取记录对应的位置，并不等待业务处理完成，<strong>如果业务还没有来得及处理，那么消息就会丢失</strong>。</p>
</li>
</ul>
]]></content:encoded>
      <enclosure url="https://qiuzhiwang.vip/javabgw-img/1755163618950-c309bd91-56cc-4f6e-8662-389ccf1e204f.png" type="image/png"/>
    </item>
    <item>
      <title>Linux</title>
      <link>https://qiuzhiwang.vip/javabgw/Linux.html</link>
      <guid>https://qiuzhiwang.vip/javabgw/Linux.html</guid>
      <source url="https://qiuzhiwang.vip/rss.xml">Linux</source>
      <description>整理 Linux 进程、日志、端口、资源与 JVM 诊断命令，结合日志轮转等机制说明排障中的常见误区。</description>
      <category>Java八股文</category>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">阅读提示</p>
<p>建议先看题目目录，再按“概念 -&gt; 原理 -&gt; 场景 -&gt; 优化”顺序复习。<br>
每题先讲结论，再补关键机制和项目实践，回答会更稳。</p>
</div>
<p>本文命令以 Linux 上的 Bash 5.x、GNU coreutils 9.x、procps-ng 4.x、iproute2 6.x 和 util-linux 2.39+ 为适用环境；BusyBox、BSD 工具的选项可能不同。Java 诊断部分以 HotSpot JDK 17/21 为准，调度算法另注明内核版本。</p>
<h2>1、绝对路径用什么符号表示？当前目录、上层目录用什么表示？ 主目录用什么表示? 切换目录用什么命令？ （了解）</h2>
<ul>
<li>绝对路径：以 <code>/</code> 开头，如 <code>/etc/init.d</code>。</li>
<li>当前目录和上层目录： <code>./</code> <code>../</code></li>
<li>主目录：在 Bash 中，未加引号的 <code>~/</code> 会展开为当前用户的主目录；<code>~</code> 是 Shell 展开语法，不是绝对路径的起始符。</li>
<li>切换目录： <code>cd</code></li>
</ul>
<h2>2、 怎么查看当前进程？怎么执行退出？怎么查看当前路径？</h2>
<ul>
<li>查看当前进程： <code>ps</code></li>
<li><code>ps</code> 默认查看与当前用户及终端关联的进程；<code>ps -l</code> 使用长格式，并不表示本次登录的全部进程。<code>ps aux</code> 或 <code>ps -ef</code> 可查看所有进程快照，避免混用有歧义的 <code>ps -aux</code>。按名称查询可用 <code>pgrep -a java</code>；<code>grep *</code> 会受到 Shell 通配符展开影响，不是进程查询语法。</li>
<li><code>top</code> 持续刷新 CPU、内存等指标。结束进程通常先用 <code>kill -TERM PID</code> 请求正常退出；<code>kill -KILL PID</code>（即 <code>kill -9</code>）无法被捕获，不能执行应用清理逻辑，仅在必要时使用。</li>
<li>执行退出： <code>exit</code></li>
<li>查看当前路径： <code>pwd</code></li>
</ul>
<h2>3、查看文件有哪些命令？</h2>
<ul>
<li><code>vi 文件名</code> #编辑方式查看，可修改</li>
<li><code>cat 文件名</code> #显示全部文件内容</li>
<li><code>more 文件名</code> #分页显示文件内容</li>
<li><code>less 文件名</code> #分页查看，支持前后翻页和搜索</li>
<li><code>tail 文件名</code> #仅查看尾部，还可以指定行数</li>
<li><code>head 文件名</code> #仅查看头部,还可以指定行数</li>
</ul>
<h2>4、列举几个常用的Linux命令</h2>
<ul>
<li><code>ls</code>: 列出当前目录下的文件和子目录信息。</li>
<li>使用方式：<code>ls [-options] [file/directory]</code></li>
<li>示例：<code>ls /home/user</code> 列出 <code>/home/user</code> 目录下非隐藏条目的信息；包含隐藏条目可用 <code>ls -a</code>。</li>
<li><code>cd</code>: 切换当前工作目录。</li>
<li>使用方式：<code>cd [directory]</code></li>
<li>示例：<code>cd Documents</code> 切换到 <code>Documents</code> 目录，使用 <code>cd ..</code> 返回上一级目录。</li>
<li><code>pwd</code>: 显示当前工作目录的绝对路径。</li>
<li>使用方式：<code>pwd</code></li>
<li><code>mkdir</code>: 创建新的目录。</li>
<li>使用方式：<code>mkdir [directory]</code></li>
<li>示例：<code>mkdir javacn</code> 创建 “javacn” 文件夹。</li>
<li><code>touch</code>: 创建新文件或者更新已存在文件的时间戳。</li>
<li>使用方式：<code>touch [file]</code></li>
<li>示例：<code>touch newfile.txt</code></li>
<li><code>rm</code>: 删除指定的文件或目录。</li>
<li>使用方式：<code>rm [-options] file/directory</code></li>
<li>示例：<code>rm file.txt</code>（删除单个文件）、<code>rm -r directory</code>（递归删除目录及其内容）。</li>
<li><code>cp</code>: 复制文件或目录。</li>
<li>使用方式：<code>cp [-options] source destination</code></li>
<li>示例：<code>cp file1.txt file2.txt</code>（复制文件）、<code>cp -r dir1 dir2</code>（复制目录）</li>
<li><code>mv</code>: 移动或重命名文件或目录。</li>
<li>使用方式：<code>mv [-options] source destination</code></li>
<li>示例：<code>mv file.txt newfile.txt</code>（重命名文件）、<code>mv file.txt /dir/</code>（移动文件到指定目录）。</li>
<li><code>find</code>: 在指定目录下查找满足条件的文件。</li>
<li>使用方式：<code>find [path] [expression]</code></li>
<li>示例：<code>find . -name &quot;*.txt&quot;</code> 在当前目录及其子目录下查找扩展名为 <code>.txt</code> 的文件。</li>
<li><code>grep</code>: 搜索文件中匹配特定模式的行。</li>
<li>使用方式：<code>grep [options] pattern file(s)</code></li>
<li>示例：<code>grep &quot;hello&quot; file.txt</code> 在 <code>file.txt</code> 中查找包含 “hello” 的行。</li>
</ul>
<h2>5、你平时是怎么查看日志的？</h2>
<ul>
<li>Linux查看日志的命令有多种: <code>tail</code>、<code>cat</code>、<code>tac</code>、<code>head</code>（<code>echo</code> 用于输出参数，并不读取日志文件） 等，本文只介绍几种常用的方法</li>
<li><code>1、tail</code></li>
<li>最常用的一种查看方式 命令格式: <code>tail [选项] [文件]</code></li>
<li><code>-f</code> 持续跟踪新增内容，<code>-q</code> 不输出文件名首部，<code>-v</code> 总是输出文件名首部； <code>-c&lt;数目&gt;</code> 显示的字节数 <code>-n&lt;行数&gt;</code> 显示行数 <code>-q, --quiet, --silent</code> 从不输出给出文件名的首部 <code>-s, --sleep-interval=S</code> 与<code>-f</code>合用,表示在每次反复的间隔休眠S秒</li>
<li>例如：</li>
<li><code>tail -n 10 test.log</code> 查询日志尾部最后10行的日志;</li>
<li><code>tail -n +10 test.log</code> 从第10行开始输出（包含第10行）;</li>
<li><code>tail -n 10 -f test.log</code> 先显示最后10行，再持续输出新增内容</li>
</ul>
<p>一般还会配合着 <code>grep</code> 搜索用，例如：</p>
<ul>
<li><code>tail -n 1000 -F test.log | grep --line-buffered '关键字'</code>（GNU grep 的 <code>--line-buffered</code> 可减少管道输出缓冲延迟）</li>
</ul>
<p>GNU <code>tail -f</code> 默认跟踪已打开的文件描述符。日志轮转若将 <code>test.log</code> 重命名并新建同名文件，旧描述符仍指向旧文件；<code>-F</code> 等价于 <code>--follow=name --retry</code>，会尝试按名称重新打开，因此更适合这类轮转。轮转期间仍可能存在观察间隙，不能当作可靠的日志采集协议。参考：<a href="https://www.gnu.org/s/coreutils/manual/html_node/tail-invocation.html" target="_blank" rel="noopener noreferrer">GNU tail 手册</a>。</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>test.log → 重命名为 test.log.1；新建 test.log</span></span>
<span class="line"><span>-f       → 继续跟踪旧文件描述符</span></span>
<span class="line"><span>-F       → 检测名称变化并尝试重新打开 test.log</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果一次性查询的数据量太大,可以进行翻页查看，例如:</p>
<ul>
<li>
<p><code>tail -n 4700 aa.log | less</code> 分页显示最后4700行，空格向前翻页，<code>q</code> 退出</p>
</li>
<li>
<p><code>2、head</code></p>
</li>
<li>
<p>跟<code>tail</code>是相反的<code>head</code>是看前多少行日志</p>
</li>
<li>
<p><code>head -n 10 test.log</code> 查询日志文件中的头10行日志;</p>
</li>
<li>
<p><code>head -n -10 test.log</code> 查询日志文件除了最后10行的其他所有日志;</p>
</li>
<li>
<p><code>head</code> 与 <code>tail</code> 的选项并不完全相同，例如 <code>head</code> 不支持 <code>-f</code> 跟踪。</p>
</li>
<li>
<p><code>3、cat</code></p>
</li>
<li>
<p><code>cat</code> 是由第一行到最后一行连续显示在屏幕上</p>
</li>
<li>
<p>一次显示整个文件 :</p>
</li>
<li>
<p><code>$ cat filename</code></p>
</li>
<li>
<p>从键盘创建一个文件 :</p>
</li>
<li>
<p><code>$cat &gt; filename</code></p>
</li>
<li>
<p>将几个文件合并为一个文件：</p>
</li>
<li>
<p><code>cat file1 file2 &gt; file</code>：目标不存在时创建，已存在时覆盖；目标不能同时作为输入文件</p>
</li>
<li>
<p>将一个日志文件的内容追加到另外一个 :</p>
</li>
<li>
<p><code>cat -n textfile1 &gt;&gt; textfile2</code>（追加带行号的内容）</p>
</li>
<li>
<p>清空一个日志文件:</p>
</li>
<li>
<p><code>: &gt; textfile2</code>（Bash 空命令配合重定向，将文件截断为零字节）</p>
</li>
<li>
<p>注意：<code>&gt;</code> 默认创建或截断覆盖文件（设置 <code>noclobber</code> 时行为不同），<code>&gt;&gt;</code> 创建或追加。千万不要弄混了。</p>
</li>
<li>
<p><code>cat</code> 的常用选项有 <code>-n</code>（所有行编号）和 <code>-b</code>（仅非空行编号），不能照搬 <code>tail</code> 的选项。</p>
</li>
</ul>
<p>重定向由 Shell 在执行命令前处理，覆盖输入文件会先截断它。参考：<a href="https://www.gnu.org/s/bash/manual/html_node/Redirections.html" target="_blank" rel="noopener noreferrer">GNU Bash 重定向</a>。</p>
<ul>
<li><code>4、more</code></li>
<li><code>more</code> 是分页显示文本的工具，它以全屏幕的方式按页显示文本文件的内容，支持vi中的关键字定位操作。<code>more</code>命令中内置了若干快捷键，常用的有<code>H</code>（获得帮助信息），<code>Enter</code>（向下翻滚一行），空格（向下滚动一屏），<code>Q</code>（退出命令）。<code>more</code> 按需读取，并不要求启动时加载整个文件；常规文件可向后翻页，管道等不可定位的输入存在限制。</li>
<li>该命令一次显示一屏文本，满屏后停下来，并且在屏幕的底部出现一个提示信息，给出至今己显示的该文件的百分比：<code>--More--（XX%）</code></li>
<li><code>more</code>的语法：<code>more 文件名</code></li>
<li><code>Enter</code> 向下n行，需要定义，默认为1行</li>
<li><code>Ctrl f</code> 向下滚动一屏</li>
<li>空格键 向下滚动一屏</li>
<li><code>Ctrl b</code> 返回上一屏</li>
<li><code>=</code> 输出当前行的行号</li>
<li><code>:f</code> 输出文件名和当前行的行号</li>
<li><code>v</code> 调用vi编辑器</li>
<li><code>!命令</code> 调用Shell，并执行命令</li>
<li><code>q</code>退出<code>more</code></li>
</ul>
<p>参考：<a href="https://github.com/util-linux/util-linux/blob/master/text-utils/more.1.adoc" target="_blank" rel="noopener noreferrer">util-linux more 官方手册源文件</a>。</p>
<ul>
<li>
<p><code>5、sed</code></p>
</li>
<li>
<p>这个命令可以查找日志文件特定的一段 , 根据时间的一个范围查询，可以按行号或文本模式范围查询</p>
</li>
<li>
<p>按照行号</p>
</li>
<li>
<p><code>sed -n '5,10p' filename</code> 这样你就可以只查看文件的第5行到第10行。</p>
</li>
<li>
<p>按照时间段</p>
</li>
<li>
<p><code>sed -n '/2014-12-17 16:17:20/,/2014-12-17 16:17:36/p' test.log</code></p>
</li>
<li>
<p>这里匹配的是起止行的正则文本，并不解析时间；两端都包含。结束模式缺失时会一直输出到文件末尾，起始模式缺失时不会自动选取区间内其他时间的日志。参考：<a href="https://www.gnu.org/software/sed/manual/html_node/Addresses.html" target="_blank" rel="noopener noreferrer">GNU sed 地址范围</a>。</p>
</li>
<li>
<p><code>6、less</code></p>
</li>
<li>
<p><code>less</code>命令在查询日志时，一般流程是这样的</p>
</li>
<li>
<p><code>less log.log</code></p>
</li>
<li>
<p><code>shift + G</code> 命令到文件尾部 然后输入 <code>?</code> 加上你要搜索的关键字例如 <code>?1213</code></p>
</li>
<li>
<p>按 <code>n</code> 向上查找关键字</p>
</li>
<li>
<p><code>shift+n</code> 反向查找关键字</p>
</li>
<li>
<p><code>less</code>与<code>more</code>类似，使用<code>less</code>可以随意浏览文件，<code>more</code> 也能在可定位文件中向后翻页；两者都不要求预先读取整个文件。<code>less</code> 更适合交互式搜索和回看。</p>
</li>
<li>
<p><code>less log2013.log</code> 查看文件</p>
</li>
<li>
<p><code>ps -ef | less</code> <code>ps</code>查看进程信息并通过<code>less</code>分页显示</p>
</li>
<li>
<p><code>history | less</code> 查看命令历史使用记录并通过<code>less</code>分页显示</p>
</li>
<li>
<p><code>less log2013.log log2014.log</code> 浏览多个文件</p>
</li>
<li>
<p>常用命令参数：</p>
</li>
<li>
<p><code>-b &lt;缓冲区大小&gt;</code> 设置缓冲区的大小</p>
</li>
<li>
<p><code>-g</code> 只高亮当前搜索定位到的匹配项，而非屏幕上所有匹配项</p>
</li>
<li>
<p><code>-i</code> 搜索模式不含大写字母时忽略大小写；<code>-I</code> 始终忽略大小写</p>
</li>
<li>
<p><code>-m</code> 显示类似<code>more</code>命令的百分比</p>
</li>
<li>
<p><code>-N</code> 显示每行的行号</p>
</li>
<li>
<p><code>-o &lt;文件名&gt;</code> 将管道输入记录到文件，仅适用于管道输入，不是保存屏幕排版结果</p>
</li>
<li>
<p><code>-Q</code> 不使用警告音</p>
</li>
<li>
<p><code>-s</code> 显示连续空行为一行</p>
</li>
<li>
<p><code>/字符串</code>：向下搜索&quot;字符串&quot;的功能</p>
</li>
<li>
<p><code>?字符串</code>：向上搜索&quot;字符串&quot;的功能</p>
</li>
<li>
<p><code>n</code>：重复前一个搜索（与 <code>/</code> 或 <code>?</code> 有关）</p>
</li>
<li>
<p><code>N</code>：反向重复前一个搜索（与 <code>/</code> 或 <code>?</code> 有关）</p>
</li>
<li>
<p><code>b</code> 向后翻一页</p>
</li>
<li>
<p><code>h</code> 显示帮助界面</p>
</li>
<li>
<p><code>q</code> 退出<code>less</code>命令</p>
</li>
</ul>
<p>一般本人查日志配合应用的其他命令</p>
<ul>
<li><code>history</code> // 当前 Shell 保存的历史记录，受历史配置与容量限制</li>
<li><code>history | grep XXX</code> // 历史记录中包含某些指令的记录</li>
<li><code>history | more</code> // 分页查看记录</li>
<li><code>history -c</code> // 清空所有的历史记录</li>
<li><code>!!</code> 重复执行上一个命令</li>
<li><code>!323</code> 重新执行历史编号323对应的命令（Bash 交互式历史展开）</li>
</ul>
<h2>6、Linux上如何查询某个端口是否被占用？</h2>
<p>优先使用 iproute2 的 <code>ss</code>，例如查询本地8080端口：</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ss</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -ltnp</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'sport = :8080'</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  # TCP 监听套接字</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ss</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -aunp</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'sport = :8080'</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  # UDP 套接字（包括已连接的 UDP）</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p><code>-n</code> 显示数字端口，<code>-p</code> 请求进程信息；查看其他用户进程可能需要相应权限。应同时确认本地地址、IPv4/IPv6 和协议；TCP 未监听也可能已绑定或处于其他状态，可再用 <code>ss -tanp 'sport = :8080'</code> 检查。没有监听输出不能直接保证下一次绑定成功。</p>
<p>已安装 net-tools 时也可用 <code>netstat -tulnp</code>，原来的 <code>-tuln</code> 没有 <code>-p</code>，不会显示 PID/程序名。按数字 <code>grep</code> 还可能误匹配其他端口。<code>ps aux | grep 8080</code> 仅搜索命令行文字，不能判断套接字占用。</p>
<p>参考：<a href="https://man7.org/linux/man-pages/man8/ss.8.html" target="_blank" rel="noopener noreferrer">iproute2 ss 手册</a>。</p>
<h2>7、实际工作中如何排查CPU飙升问题？</h2>
<p>以 Linux 上的 HotSpot JDK 17/21 为例，先用 <code>top</code>（按 <code>P</code> 以 CPU 排序）定位进程，再用 <code>top -H -p PID</code> 观察线程。线程的 CPU 时间统计和线程栈是两个时刻的快照，需要多次采样才能判断热点，不能仅凭线程状态认定死循环。</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># PID=1234、TID=1256 仅为占位示例，替换为现场观察到的值</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">printf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '%x\n'</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1256</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">                  # 十进制 Linux TID 转十六进制</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">jcmd</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1234</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> Thread.print</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -l</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 在完整线程栈中搜索 nid=0x4e8</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">jstat</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -gcutil</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1234</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1000</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 5</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 每秒一次，共5次，观察 GC 统计变化</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">jcmd</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1234</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> GC.heap_info</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">               # 堆概况</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>平台线程栈中的 <code>nid</code> 对应 Linux 原生线程 ID，十六进制转换后可关联线程并保留完整调用栈；只 <code>grep</code> 线程标题会丢失定位所需的栈帧。JDK 21 的虚拟线程不与 OS 线程一一对应，这个方法主要定位承载它们的平台线程。</p>
<p><code>jmap -heap</code> 不是 JDK 17/21 的有效用法，使用上述 <code>GC.heap_info</code>。仅在确需分析堆对象时考虑 <code>jcmd 1234 GC.heap_dump /path/to/heap.hprof</code>；堆转储影响较大，默认可能触发 Full GC，并占用磁盘，不能作为 CPU 飙升时必做步骤。<code>jcmd</code> 需要在 JVM 所在机器上以相同有效用户和组运行，容器中还需注意 PID 命名空间；可用 <code>jcmd PID help</code> 核对目标 JVM 支持的命令。</p>
<p>参考：<a href="https://docs.oracle.com/en/java/javase/21/docs/specs/man/jcmd.html" target="_blank" rel="noopener noreferrer">Oracle JDK 21 jcmd 手册</a>。</p>
<h2>8、场景题：项目本地能运行，发布到Linux后运行不了，可能是什么原因？该如何解决？</h2>
<ul>
<li>环境差异：本地环境和服务器环境可能存在差异，例如Java版本、系统环境变量、依赖库等。解决方法是确保服务器环境和本地环境尽可能一致</li>
<li>权限问题：可能是Linux服务器上的文件权限或者防火墙设置导致的。解决方法是检查并修改相关的权限设置</li>
<li>依赖问题：可能是项目依赖的库在服务器上不存在或版本不匹配。解决方法是检查项目的依赖，并确保它们在服务器上正确地被安装和配置</li>
<li>配置问题：可能是配置文件中的路径、数据库连接信息等在服务器上不适用。解决方法是检查并修改配置文件，确保它们在服务器环境中是正确的</li>
</ul>
<h2>8、什么是零拷贝技术？它有哪些使用场景？</h2>
<ul>
<li>零拷贝技术是一种优化数据传输的方法，它通过最小化数据在内存中的拷贝次数来提高数据传输的效率和性能。通常，在数据传输过程中，数据需要从一个缓冲区（如内核缓冲区）拷贝到另一个缓冲区（如用户空间缓冲区），然后再传输到目标位置。而零拷贝技术的目标是尽可能减少或消除这些拷贝操作，从而降低系统开销和提高性能。零拷贝技术的使用场景包括但不限于以下几种：</li>
<li>网络数据传输：在网络传输过程中，零拷贝技术可以避免数据在内核空间和用户空间之间的拷贝，从而提高网络数据传输的效率。常见的应用包括高性能网络服务器、网络流媒体服务等</li>
<li>文件传输：在文件传输过程中，零拷贝技术可以减少数据在文件系统缓存和用户空间缓冲区之间的拷贝次数，提高文件传输的效率。例如，在文件系统中实现零拷贝的技术可以加速文件的读取和写入操作</li>
<li>内存映射文件：内存映射文件是一种将文件映射到进程地址空间的技术，可以让应用程序直接操作文件而无需进行显式的读写操作。映射文件页可省去将文件内容读入独立用户缓冲区的一次复制，但仍有缺页处理等成本</li>
<li>数据库系统：在数据库系统中，零拷贝技术可以用于加速数据的读取和写入操作，提高数据库的性能和吞吐量。例如，采用内存映射文件的数据库可减少文件页与独立用户缓冲区之间的复制；实际收益取决于缓存、事务和 I/O 实现</li>
</ul>
<p>以 Linux <code>sendfile()</code> 为例，数据在内核内从输入文件描述符传到输出描述符，避免 <code>read()</code> 到用户缓冲区后再 <code>write()</code> 回内核的往返拷贝；并不保证磁盘、内存、网卡之间完全没有数据搬运。<code>mmap()</code> 则把文件页映射到地址空间，也不等于自动实现文件到网络的零拷贝。应用若需在用户态修改数据，仍可能需要额外拷贝；数据库是否受益取决于实际 I/O 路径，不能一概而论。<code>sendfile()</code> 成功也可能只发送部分数据，调用方需处理短传输；Linux 2.6.33 起输出描述符不再局限于套接字。参考：<a href="https://man7.org/linux/man-pages/man2/sendfile.2.html" target="_blank" rel="noopener noreferrer">Linux man-pages sendfile(2)</a>。</p>
<h2>9、Linux 调度程序是根据进程的动态优先级还是静态优先级来调度进程的?（了解）</h2>
<p>不能概括为“统一按动态优先级调度”，需要区分调度策略；Linux 实际调度的是可运行线程。</p>
<ul>
<li>普通线程（<code>SCHED_OTHER</code>）：Linux 2.6.23 引入 CFS，以 nice 值决定权重，通过虚拟运行时间分配 CPU；nice 较小通常意味着较大份额，并非永远先执行。</li>
<li>Linux 6.6 起普通公平调度开始转向 EEVDF：先用 lag 判断线程是否应得到 CPU（lag ≥ 0 为 eligible），再从 eligible 线程中选择虚拟截止时间最早者。该截止时间用于公平调度，不是应用的实时期限。</li>
<li>实时线程（<code>SCHED_FIFO</code> / <code>SCHED_RR</code>）使用静态实时优先级，同优先级分别按 FIFO 或时间片轮转；<code>SCHED_DEADLINE</code> 则按运行预算、周期和截止时间等参数调度，不能套用普通线程的 nice 规则。</li>
</ul>
<p>参考：<a href="https://docs.kernel.org/scheduler/sched-design-CFS.html" target="_blank" rel="noopener noreferrer">内核 CFS 文档</a>、<a href="https://docs.kernel.org/scheduler/sched-eevdf.html" target="_blank" rel="noopener noreferrer">内核 EEVDF 文档</a>。发行版可能回移补丁，实际实现还应结合发行版内核源码确认。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>