<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>七夏</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://qixia.site/</id>
  <link href="https://qixia.site/" rel="alternate"/>
  <link href="https://qixia.site/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 七夏</rights>
  <subtitle>落羽拾光</subtitle>
  <title>七夏的技术札记🐿️</title>
  <updated>2026-07-22T01:33:15.024Z</updated>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="编程入门" scheme="https://qixia.site/categories/%E7%BC%96%E7%A8%8B%E5%85%A5%E9%97%A8/"/>
    <category term="Python二级" scheme="https://qixia.site/tags/Python%E4%BA%8C%E7%BA%A7/"/>
    <category term="Python基础" scheme="https://qixia.site/tags/Python%E5%9F%BA%E7%A1%80/"/>
    <content>
      <![CDATA[<h2 id="1-标识符"><a href="#1-标识符" class="headerlink" title="1. 标识符"></a>1. 标识符</h2><ul><li><strong>命名规则</strong>：<ul><li>由字母（A<del>Z, a</del>z）、数字（0~9）和下划线（<code>_</code>）组成。</li><li><strong>不能以数字开头</strong>。</li><li>区分大小写（如 <code>Name</code> 和 <code>name</code> 不同）。</li><li>不能与保留字（关键字）重名。</li></ul></li><li><strong>命名规范（PEP 8 建议）</strong>：<ul><li>变量名、函数名、方法名：全小写，单词间用下划线分隔（如 <code>my_var</code>）。</li><li>类名：首字母大写（驼峰命名法，如 <code>MyClass</code>）。</li><li>常量名：全大写，单词间用下划线分隔（如 <code>MAX_SIZE</code>）。</li></ul></li><li><strong>合法性检查</strong>：可通过 <code>str.isidentifier()</code> 方法判断一个字符串是否为合法标识符。</li></ul><hr><h2 id="2-保留字（关键字）"><a href="#2-保留字（关键字）" class="headerlink" title="2. 保留字（关键字）"></a>2. 保留字（关键字）</h2><ul><li>Python 保留字不能用作标识符，否则会引发语法错误。</li><li>保留字列表如下（不同版本略有增减，以实际环境为准）：<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">False, None, True, and, as, assert, async, await, break, class, continue, def, del, elif, else, except, finally, for, from, global, if, import, in, is, lambda, nonlocal, not, or, pass, raise, return, try, while, with, yield, match, case</span><br></pre></td></tr></table></figure></li><li>可通过 <code>import keyword; print(keyword.kwlist)</code> 查看当前环境的所有关键字。</li></ul><hr><h2 id="3-常量和变量"><a href="#3-常量和变量" class="headerlink" title="3. 常量和变量"></a>3. 常量和变量</h2><ul><li><strong>常量</strong>：在程序运行过程中值保持不变的量。<ul><li>字面常量：直接写出的值，如 <code>123</code>、<code>&quot;hello&quot;</code>、<code>3.14</code>。</li><li>命名常量：通常用全大写变量名表示（约定，非强制），如 <code>PI = 3.14159</code>。</li></ul></li><li><strong>变量</strong>：通过赋值语句动态绑定数据类型和值。<ul><li>赋值格式：<code>变量名 = 表达式</code>（如 <code>x = 10</code>）。</li><li>支持链式赋值：<code>a = b = c = 0</code>。</li><li>支持增量赋值（增强赋值运算符，见 5.3 节）。</li></ul></li><li><strong>动态类型</strong>：变量无需显式声明类型，可随时指向不同类型对象（如 <code>x = 5</code> 后 <code>x = &quot;text&quot;</code> 是合法的）。</li></ul><hr><h2 id="4-基本数据类型"><a href="#4-基本数据类型" class="headerlink" title="4. 基本数据类型"></a>4. 基本数据类型</h2><h3 id="4-1-数字型-Numeric"><a href="#4-1-数字型-Numeric" class="headerlink" title="4.1 数字型 (Numeric)"></a>4.1 数字型 (Numeric)</h3><h4 id="4-1-1-整数型-int"><a href="#4-1-1-整数型-int" class="headerlink" title="4.1.1 整数型 (int)"></a>4.1.1 整数型 (int)</h4><ul><li>任意大小的整数（仅受内存限制）。</li><li>支持多种进制表示：<ul><li>十进制：<code>123</code>, <code>-456</code></li><li>二进制：<code>0b1010</code>（前缀 <code>0b</code> 或 <code>0B</code>）</li><li>八进制：<code>0o177</code>（前缀 <code>0o</code> 或 <code>0O</code>）</li><li>十六进制：<code>0x1F</code>（前缀 <code>0x</code> 或 <code>0X</code>）</li></ul></li><li>进制转换函数：<code>bin()</code>、<code>oct()</code>、<code>hex()</code> 分别返回二进制、八进制、十六进制字符串。</li></ul><h4 id="4-1-2-浮点数型-float"><a href="#4-1-2-浮点数型-float" class="headerlink" title="4.1.2 浮点数型 (float)"></a>4.1.2 浮点数型 (float)</h4><ul><li>双精度浮点数（遵循 IEEE 754 标准），精度约为 15~17 位有效数字。</li><li>支持科学计数法：<code>1.23e-4</code> 表示 0.000123。</li><li><strong>注意</strong>：浮点数运算可能存在精度误差（如 <code>0.1 + 0.2 != 0.3</code>），可借助 <code>decimal</code> 模块进行精确计算。</li></ul><h4 id="4-1-3-复数型-complex"><a href="#4-1-3-复数型-complex" class="headerlink" title="4.1.3 复数型 (complex)"></a>4.1.3 复数型 (complex)</h4><ul><li>形式：<code>a + bj</code> 或 <code>a + bJ</code>，其中 <code>a</code> 为实部，<code>b</code> 为虚部（均为浮点数）。</li><li>可通过 <code>real</code> 和 <code>imag</code> 属性获取实部和虚部（均为 <code>float</code> 类型）。</li><li>示例：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">c = <span class="number">3.1</span> + <span class="number">4j</span></span><br><span class="line"><span class="built_in">print</span>(c.real)   <span class="comment"># 输出 3.1</span></span><br><span class="line"><span class="built_in">print</span>(c.imag)   <span class="comment"># 输出 4.0</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="4-2-字符串型-str"><a href="#4-2-字符串型-str" class="headerlink" title="4.2 字符串型 (str)"></a>4.2 字符串型 (str)</h3><ul><li>用单引号（<code>&#39;...&#39;</code>）、双引号（<code>&quot;...&quot;</code>）或三引号（<code>&#39;&#39;&#39;...&#39;&#39;&#39;</code> 或 <code>&quot;&quot;&quot;...&quot;&quot;&quot;</code>）括起来的字符序列。</li><li>三引号可用于多行字符串，保留换行格式。</li><li><strong>转义字符</strong>：如 <code>\n</code>（换行）、<code>\t</code>（制表符）、<code>\\</code>（反斜杠）、<code>\&#39;</code>（单引号）、<code>\&quot;</code>（双引号）等。</li><li><strong>原始字符串</strong>：在引号前加 <code>r</code>（如 <code>r&#39;C:\Users&#39;</code>），转义字符不生效，常用于文件路径和正则表达式。</li><li><strong>字符串是不可变序列</strong>：不能通过下标修改单个字符，只能生成新字符串。</li></ul><h3 id="4-3-布尔型-bool"><a href="#4-3-布尔型-bool" class="headerlink" title="4.3 布尔型 (bool)"></a>4.3 布尔型 (bool)</h3><ul><li>只有两个值：<code>True</code> 和 <code>False</code>（注意首字母大写）。</li><li>布尔值实际上是 <code>int</code> 的子类，<code>True</code> 对应整数 <code>1</code>，<code>False</code> 对应 <code>0</code>，可参与算术运算（但通常不推荐）。</li><li>在条件判断中，以下值会被视为 <code>False</code>：<code>None</code>、<code>False</code>、数值零（<code>0</code>, <code>0.0</code>, <code>0j</code>）、空序列（<code>&quot;&quot;</code>, <code>[]</code>, <code>()</code>, <code>{}</code>, <code>set()</code>）等。</li></ul><h3 id="4-4-type-函数与类型检查"><a href="#4-4-type-函数与类型检查" class="headerlink" title="4.4 type() 函数与类型检查"></a>4.4 <code>type()</code> 函数与类型检查</h3><ul><li><code>type(obj)</code> 返回对象 <code>obj</code> 的数据类型（如 <code>&lt;class &#39;int&#39;&gt;</code>）。</li><li>更推荐使用 <code>isinstance(obj, type)</code> 进行类型检查，因其支持继承关系。<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">isinstance</span>(<span class="number">3.14</span>, <span class="built_in">float</span>)   <span class="comment"># True</span></span><br><span class="line"><span class="built_in">isinstance</span>(<span class="literal">True</span>, <span class="built_in">int</span>)     <span class="comment"># True（因为 bool 是 int 的子类）</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="4-5-数据类型转换（显式转换）"><a href="#4-5-数据类型转换（显式转换）" class="headerlink" title="4.5 数据类型转换（显式转换）"></a>4.5 数据类型转换（显式转换）</h3><ul><li>常用转换函数：<ul><li><code>int(x)</code>：将 <code>x</code> 转换为整数（支持字符串、浮点数等）。</li><li><code>float(x)</code>：转换为浮点数。</li><li><code>str(x)</code>：转换为字符串。</li><li><code>bool(x)</code>：转换为布尔值。</li><li><code>complex(real[, imag])</code>：创建复数。</li></ul></li><li><strong>注意</strong>：<ul><li>浮点数转整数时直接截断小数部分（非四舍五入）。</li><li>字符串转数字时，字符串必须符合数字字面量格式。<code>int(&quot;123&quot;)</code> 合法，<code>int(&quot;12.3&quot;)</code> 会抛出 <code>ValueError</code>，<code>int(&quot;abc&quot;)</code> 同样报错。</li></ul></li><li><strong>隐式类型转换</strong>：混合类型运算时，Python 自动提升为更高级类型（如 <code>int + float</code> → <code>float</code>）。</li></ul><blockquote><p><strong>字符编码相关函数</strong>：<code>chr(x)</code> 返回 Unicode 编码 <code>x</code> 对应的单字符；<code>ord(x)</code> 返回单字符 <code>x</code> 的 Unicode 编码（整数）。二者互为逆操作。</p></blockquote><hr><h2 id="5-运算符"><a href="#5-运算符" class="headerlink" title="5. 运算符"></a>5. 运算符</h2><h3 id="5-1-基本算术运算符"><a href="#5-1-基本算术运算符" class="headerlink" title="5.1 基本算术运算符"></a>5.1 基本算术运算符</h3><table><thead><tr><th>运算符</th><th>功能</th><th>示例</th></tr></thead><tbody><tr><td><code>+</code></td><td>加法</td><td><code>3 + 2</code> → <code>5</code></td></tr><tr><td><code>-</code></td><td>减法</td><td><code>3 - 2</code> → <code>1</code></td></tr><tr><td><code>*</code></td><td>乘法</td><td><code>3 * 2</code> → <code>6</code></td></tr><tr><td><code>/</code></td><td>真除法（结果浮点数）</td><td><code>3 / 2</code> → <code>1.5</code></td></tr><tr><td><code>//</code></td><td>整除（向下取整）</td><td><code>3 // 2</code> → <code>1</code>；<code>-3 // 2</code> → <code>-2</code></td></tr><tr><td><code>%</code></td><td>取余（模运算）</td><td><code>3 % 2</code> → <code>1</code>；<code>-3 % 2</code> → <code>1</code>（与除数同号）</td></tr><tr><td><code>**</code></td><td>幂运算</td><td><code>2 ** 3</code> → <code>8</code>；<code>9 ** 0.5</code> → <code>3.0</code></td></tr></tbody></table><blockquote><p><strong>注意</strong>：<code>/</code> 和 <code>//</code> 的区别；取余结果的符号由除数决定。</p></blockquote><h3 id="5-2-字符串运算符"><a href="#5-2-字符串运算符" class="headerlink" title="5.2 字符串运算符"></a>5.2 字符串运算符</h3><ul><li><code>+</code>：字符串拼接，例如 <code>&quot;Hello&quot; + &quot; &quot; + &quot;World&quot;</code> → <code>&quot;Hello World&quot;</code>。</li><li><code>*</code>：重复复制，例如 <code>&quot;Hi&quot; * 3</code> → <code>&quot;HiHiHi&quot;</code>。</li><li><code>in</code> &#x2F; <code>not in</code>：成员判断，检查子串是否存在于字符串中，返回布尔值。</li><li>字符串也支持 <code>==</code>、<code>!=</code>、<code>&gt;</code>、<code>&lt;</code>、<code>&gt;=</code>、<code>&lt;=</code> 等比较运算符，按字典序（Unicode码点）逐字符比较。</li></ul><h3 id="5-3-赋值运算符与增强赋值运算符"><a href="#5-3-赋值运算符与增强赋值运算符" class="headerlink" title="5.3 赋值运算符与增强赋值运算符"></a>5.3 赋值运算符与增强赋值运算符</h3><ul><li>基本赋值：<code>=</code>。</li><li>增强赋值（将算术运算与赋值结合）：<br><code>+=</code>、<code>-=</code>、<code>*=</code>、<code>/=</code>、<code>//=</code>、<code>%=</code>、<code>**=</code>。</li><li>示例：<code>x += 3</code> 等价于 <code>x = x + 3</code>。</li><li>增强赋值会原地修改可变对象（如列表），但对不可变对象（如数字、字符串）会创建新对象。</li></ul><h3 id="5-4-关系运算符（比较运算符）"><a href="#5-4-关系运算符（比较运算符）" class="headerlink" title="5.4 关系运算符（比较运算符）"></a>5.4 关系运算符（比较运算符）</h3><ul><li>包括：<code>==</code>（等于）、<code>!=</code>（不等于）、<code>&gt;</code>、<code>&lt;</code>、<code>&gt;=</code>、<code>&lt;=</code>。</li><li>返回布尔值 <code>True</code> 或 <code>False</code>。</li><li>支持链式比较：<code>a &lt; b &lt;= c</code> 等价于 <code>a &lt; b and b &lt;= c</code>，代码更简洁。</li><li>不同类型之间比较时，Python 会尝试进行合理比较（如数字与数字），否则可能引发 <code>TypeError</code>。</li></ul><h3 id="5-5-逻辑运算符"><a href="#5-5-逻辑运算符" class="headerlink" title="5.5 逻辑运算符"></a>5.5 逻辑运算符</h3><ul><li><code>and</code>、<code>or</code>、<code>not</code>。</li><li>操作数不限于布尔值，Python 会将其隐式转换为布尔值进行判断，但返回的是<strong>最后一个被求值的操作数</strong>（短路求值）。<ul><li><code>x and y</code>：若 <code>x</code> 为假则返回 <code>x</code>，否则返回 <code>y</code>。</li><li><code>x or y</code>：若 <code>x</code> 为真则返回 <code>x</code>，否则返回 <code>y</code>。</li><li><code>not x</code>：返回布尔值，始终为 <code>True</code> 或 <code>False</code>。</li></ul></li><li><strong>优先级</strong>：<code>not</code> &gt; <code>and</code> &gt; <code>or</code>（可用括号调整）。</li></ul><h3 id="5-6-运算符优先级（从高到低简要）"><a href="#5-6-运算符优先级（从高到低简要）" class="headerlink" title="5.6 运算符优先级（从高到低简要）"></a>5.6 运算符优先级（从高到低简要）</h3><ol><li>圆括号 <code>()</code></li><li>幂运算 <code>**</code></li><li>正负号 <code>+x</code>, <code>-x</code></li><li>乘除、取余、整除 <code>*</code>, <code>/</code>, <code>//</code>, <code>%</code></li><li>加减 <code>+</code>, <code>-</code></li><li>比较运算符 <code>==</code>, <code>!=</code>, <code>&gt;</code>, <code>&lt;</code>, <code>&gt;=</code>, <code>&lt;=</code></li><li>逻辑非 <code>not</code></li><li>逻辑与 <code>and</code></li><li>逻辑或 <code>or</code></li><li>赋值运算符（最低）</li></ol><blockquote><p>建议在复杂表达式中使用括号增强可读性，避免依赖优先级记忆。</p></blockquote><hr><h2 id="6-input-函数的使用"><a href="#6-input-函数的使用" class="headerlink" title="6. input() 函数的使用"></a>6. <code>input()</code> 函数的使用</h2><ul><li>功能：从标准输入读取一行文本，以<strong>字符串</strong>形式返回（不包含末尾换行符）。</li><li>语法：<code>input([提示信息])</code>，提示信息为可选字符串。</li><li><strong>重要</strong>：<code>input()</code> 始终返回字符串类型，即使输入数字。</li><li>类型转换：<ul><li>安全做法：使用 <code>int()</code>、<code>float()</code> 等显式转换，并配合 <code>try-except</code> 处理无效输入。</li><li>示例：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    a = <span class="built_in">float</span>(<span class="built_in">input</span>(<span class="string">&quot;第一个数：&quot;</span>))</span><br><span class="line">    b = <span class="built_in">float</span>(<span class="built_in">input</span>(<span class="string">&quot;第二个数：&quot;</span>))</span><br><span class="line">    <span class="built_in">print</span>(a + b)</span><br><span class="line"><span class="keyword">except</span> ValueError:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;请输入有效数字！&quot;</span>)</span><br></pre></td></tr></table></figure></li><li><strong>不建议使用 <code>eval(input())</code></strong>，因为 <code>eval()</code> 会执行任意表达式，存在安全风险（用户可输入恶意代码）。</li></ul></li><li>多个输入可结合 <code>split()</code> 处理，例如 <code>x, y = map(int, input().split())</code>。</li></ul><hr><h2 id="7-输出不换行（print-的-end-参数）"><a href="#7-输出不换行（print-的-end-参数）" class="headerlink" title="7. 输出不换行（print() 的 end 参数）"></a>7. 输出不换行（<code>print()</code> 的 <code>end</code> 参数）</h2><ul><li><code>print()</code> 函数默认以换行符 <code>\n</code> 结尾。</li><li>通过 <code>end</code> 参数自定义结尾字符。</li><li>示例：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">print</span>(<span class="string">&quot;111&quot;</span>, end=<span class="string">&quot;&quot;</span>)    <span class="comment"># 结尾为空字符串</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;222&quot;</span>, end=<span class="string">&quot;&quot;</span>)    <span class="comment"># 输出：111222（无换行）</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;333&quot;</span>, end=<span class="string">&quot; &quot;</span>)   <span class="comment"># 输出：333 （末尾加空格，不换行）</span></span><br></pre></td></tr></table></figure></li><li><strong><code>sep</code> 参数</strong>：控制多个输出项之间的分隔符，默认为空格。<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">print</span>(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>, sep=<span class="string">&quot;-&quot;</span>)   <span class="comment"># 输出：a-b-c</span></span><br></pre></td></tr></table></figure></li></ul><hr>]]>
    </content>
    <id>https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E5%9F%BA%E7%A1%80%E8%AF%AD%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/</id>
    <link href="https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E5%9F%BA%E7%A1%80%E8%AF%AD%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/"/>
    <published>2026-07-20T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="1-标识符"><a href="#1-标识符" class="headerlink" title="1. 标识符"></a>1. 标识符</h2><ul>
<li><strong>命名规则</strong>：<ul>
<li>由字母（A<del>Z, a</del]]>
    </summary>
    <title>Python 二级备考笔记（一）：基础语法与数据类型</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="编程入门" scheme="https://qixia.site/categories/%E7%BC%96%E7%A8%8B%E5%85%A5%E9%97%A8/"/>
    <category term="Python二级" scheme="https://qixia.site/tags/Python%E4%BA%8C%E7%BA%A7/"/>
    <category term="Python基础" scheme="https://qixia.site/tags/Python%E5%9F%BA%E7%A1%80/"/>
    <content>
      <![CDATA[<h2 id="1、函数的定义"><a href="#1、函数的定义" class="headerlink" title="1、函数的定义"></a>1、函数的定义</h2><ul><li>函数是一段具有特定功能的、可重用的语句组，是一种功能的抽象。</li><li>用函数名来表示并通过函数名进行完成功能调用。</li><li>两个作用：降低编程难度和代码复用。</li><li>Python定义一个函数语法形式：</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> &lt;函数名&gt;(&lt;参数列表&gt;):</span><br><span class="line">    &lt;函数体&gt;</span><br><span class="line">    <span class="keyword">return</span> &lt;返回值列表&gt;</span><br></pre></td></tr></table></figure><ul><li><strong>注意</strong>：函数体相对于 <code>def</code> 关键字必须<strong>缩进</strong>（通常为 4 个空格或 1 个 Tab），这是 Python 语法的强制要求。</li><li>即便函数没有参数，<strong>括号也不能省略</strong>，如 <code>def func():</code>。</li><li>函数定义时参数列表中的变量称为<strong>形参</strong>，函数体中的代码不会立即执行，只有在函数被调用时才会运行。</li></ul><hr><h2 id="2、函数的调用"><a href="#2、函数的调用" class="headerlink" title="2、函数的调用"></a>2、函数的调用</h2><ul><li>调用程序在 <strong>调用处</strong> 暂停执行；</li><li>在调用时将 <strong>实参复制</strong> 给函数的 <strong>形参</strong>；</li><li><strong>执行</strong> 函数体语句；</li><li>函数执行结束给出返回值，程序回到调用前的 <strong>暂停处</strong> 继续执行。</li></ul><blockquote><p><strong>调用方式</strong>：直接使用 <code>函数名(实参列表)</code> 即可，如 <code>result = add(3, 5)</code>。在调用之前，函数必须已经被定义。</p></blockquote><hr><h2 id="3、lambda函数（匿名函数）"><a href="#3、lambda函数（匿名函数）" class="headerlink" title="3、lambda函数（匿名函数）"></a>3、lambda函数（匿名函数）</h2><ul><li>一种特殊的函数——匿名函数，又称 lambda 函数；</li><li>匿名函数并非没有名字，而是将函数名作为函数结果返回；</li><li>lambda 函数用于定义简单的、<strong>能够在一行内表示</strong>的函数；</li><li>定义形式：<code>&lt;函数名&gt; = lambda &lt;参数列表&gt; : &lt;表达式&gt;</code></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">f = <span class="keyword">lambda</span> x, y: x + y</span><br><span class="line"><span class="built_in">print</span>(f(<span class="number">3</span>, <span class="number">5</span>))   <span class="comment"># 输出 8</span></span><br></pre></td></tr></table></figure><ul><li><strong>二级考试常考结合</strong>：<code>lambda</code> 常作为 <code>sort()</code>、<code>sorted()</code>、<code>map()</code>、<code>filter()</code> 等高阶函数的参数使用。</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 按元组第二个元素排序</span></span><br><span class="line">pairs = [(<span class="number">1</span>, <span class="string">&#x27;one&#x27;</span>), (<span class="number">3</span>, <span class="string">&#x27;three&#x27;</span>), (<span class="number">2</span>, <span class="string">&#x27;two&#x27;</span>)]</span><br><span class="line">pairs.sort(key=<span class="keyword">lambda</span> pair: pair[<span class="number">1</span>])</span><br><span class="line"><span class="built_in">print</span>(pairs)  <span class="comment"># 按字母顺序排序</span></span><br></pre></td></tr></table></figure><hr><h2 id="4、函数参数的传递"><a href="#4、函数参数的传递" class="headerlink" title="4、函数参数的传递"></a>4、函数参数的传递</h2><h3 id="4-1、基本概念：形参与实参"><a href="#4-1、基本概念：形参与实参" class="headerlink" title="4.1、基本概念：形参与实参"></a>4.1、基本概念：形参与实参</h3><ul><li><strong>形参</strong>：函数定义时函数头中所包含的参数，类似于占位符，本身并不是拥有值的变量。</li><li><strong>实参</strong>：函数调用时所使用的实际参数。</li><li><strong>传递机制</strong>：只有当函数被调用时，调用者将实参的值传递给形参，形参才具有值。</li></ul><hr><h3 id="4-2、可选参数和可变数量参数"><a href="#4-2、可选参数和可变数量参数" class="headerlink" title="4.2、可选参数和可变数量参数"></a>4.2、可选参数和可变数量参数</h3><h4 id="1-可选参数（默认参数）"><a href="#1-可选参数（默认参数）" class="headerlink" title="1. 可选参数（默认参数）"></a>1. 可选参数（默认参数）</h4><ul><li>在定义函数时，允许某些参数存在<strong>默认值</strong>，调用时若未传入该参数，则使用默认值。</li><li><strong>示例</strong>：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">dup</span>(<span class="params"><span class="built_in">str</span>, times=<span class="number">2</span></span>):</span><br><span class="line">    <span class="built_in">print</span>(<span class="built_in">str</span> * times)</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>dup(<span class="string">&quot;knock~&quot;</span>)        <span class="comment"># 未传 times，使用默认值 2</span></span><br><span class="line">knock~knock~</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>dup(<span class="string">&quot;knock~&quot;</span>, <span class="number">4</span>)     <span class="comment"># 传入 times=4，覆盖默认值</span></span><br><span class="line">knock~knock~knock~knock~</span><br></pre></td></tr></table></figure></li><li><strong>⚠️ 重要规则</strong>：如果函数中<strong>混用</strong>默认值参数和非默认值参数，则<strong>非默认值参数必须定义在默认值参数之前</strong>。即 <code>def func(a, b=1):</code> 合法，<code>def func(a=1, b):</code> 非法。</li></ul><hr><h4 id="2-可变数量参数"><a href="#2-可变数量参数" class="headerlink" title="2. 可变数量参数"></a>2. 可变数量参数</h4><ul><li>指在函数定义时可以使用<strong>个数不确定</strong>的参数，使得同一函数可以接受不同数量的实参。</li><li><strong>定义方式</strong>：在参数前增加星号（<code>*</code>）来指定该参数为可变数量参数。</li><li><strong>特性</strong>：传入的多个实参会自动打包成一个<strong>元组（tuple）</strong> 类型。</li><li><strong>示例</strong>：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">vfunc</span>(<span class="params">a, *b</span>):</span><br><span class="line">    <span class="built_in">print</span>(<span class="built_in">type</span>(b))       <span class="comment"># &lt;class &#x27;tuple&#x27;&gt;</span></span><br><span class="line">    <span class="keyword">for</span> n <span class="keyword">in</span> b:</span><br><span class="line">        a += n</span><br><span class="line">    <span class="keyword">return</span> a</span><br><span class="line"></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>vfunc(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line"><span class="number">15</span>                       <span class="comment"># 返回 1+2+3+4+5 的结果</span></span><br></pre></td></tr></table></figure></li><li><strong>双星号（<code>**</code>）可变参数</strong>：接收关键字参数，打包成<strong>字典（dict）</strong>，形如 <code>def func(**kwargs):</code>，二级考试偶有出现，了解即可。</li></ul><hr><h3 id="4-3、参数的位置和名称传递"><a href="#4-3、参数的位置和名称传递" class="headerlink" title="4.3、参数的位置和名称传递"></a>4.3、参数的位置和名称传递</h3><h4 id="1-位置传递（默认方式）"><a href="#1-位置传递（默认方式）" class="headerlink" title="1. 位置传递（默认方式）"></a>1. 位置传递（默认方式）</h4><ul><li>实参默认按照<strong>位置顺序</strong>一一对应地传递给形参。</li><li><strong>示例</strong>：调用 <code>dup(&quot;knock~&quot;, 4)</code> 时，第一个实参 <code>&quot;knock~&quot;</code> 默认赋值给形参 <code>str</code>，第二个实参 <code>4</code> 赋值给形参 <code>times</code>。</li></ul><h4 id="2-名称传递（关键字传递）"><a href="#2-名称传递（关键字传递）" class="headerlink" title="2. 名称传递（关键字传递）"></a>2. 名称传递（关键字传递）</h4><ul><li>调用函数时，通过<strong>指定参数名称</strong>来传递实参。</li><li><strong>优点</strong>：由于显式指定了形参名，参数之间的<strong>顺序可以任意调整</strong>，不影响传递结果。</li><li>调用示例：<code>dup(times=4, str=&quot;knock~&quot;)</code>，顺序颠倒依然有效。</li><li><strong>⚠️ 混用规则</strong>：位置参数必须在关键字参数之前，如 <code>dup(&quot;knock~&quot;, times=4)</code> 合法，<code>dup(times=4, &quot;knock~&quot;)</code> 非法。</li></ul><hr><h2 id="5、函数的返回值"><a href="#5、函数的返回值" class="headerlink" title="5、函数的返回值"></a>5、函数的返回值</h2><ul><li><code>return</code> 语句用来退出函数，并将程序返回到函数被调用的位置继续执行。</li><li>可以将 <strong>0 个、1 个或多个</strong> 结果返回给函数被调用处的变量。</li><li>当返回多个值时，这些值以 <strong>元组（tuple）类型</strong> 保存。</li><li>函数可以没有 <code>return</code> 语句，此时函数并不返回值（相当于返回 <code>None</code>）。</li><li><strong>执行机制</strong>：函数一旦执行到 <code>return</code> 语句，立即结束函数执行，后续代码不再运行。</li></ul><hr><h2 id="6、变量作用域（全局变量与局部变量）"><a href="#6、变量作用域（全局变量与局部变量）" class="headerlink" title="6、变量作用域（全局变量与局部变量）"></a>6、变量作用域（全局变量与局部变量）</h2><ul><li>程序中的变量分为两类：<strong>全局变量</strong> 和 <strong>局部变量</strong>。</li><li><strong>全局变量</strong>：在函数之外定义的变量，一般没有缩进，在程序执行全过程有效。</li><li><strong>局部变量</strong>：在函数内部使用的变量，仅在函数内部有效，当函数退出时变量将不存在。</li></ul><h3 id="6-1、变量的作用域原则"><a href="#6-1、变量的作用域原则" class="headerlink" title="6.1、变量的作用域原则"></a>6.1、变量的作用域原则</h3><ul><li><strong>简单数据类型变量</strong>（如 int、float、str、bool 等）：无论是否与全局变量重名，在函数内部默认均为 <strong>局部变量</strong>，仅在函数内部创建和使用，函数退出后变量被释放。</li><li>若要将简单数据类型变量声明为全局变量，需在函数内部使用 <strong><code>global</code></strong> 保留字显式声明。<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">x = <span class="number">10</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">func</span>():</span><br><span class="line">    <span class="keyword">global</span> x</span><br><span class="line">    x = <span class="number">20</span></span><br><span class="line">func()</span><br><span class="line"><span class="built_in">print</span>(x)   <span class="comment"># 输出 20（全局变量被修改）</span></span><br></pre></td></tr></table></figure></li><li><strong>组合数据类型变量</strong>（如 list、dict、set 等）：<ul><li>如果在函数内部 <strong>没有</strong> 被真实创建（即未赋值）的同名变量，则函数内部会将其视为 <strong>全局变量</strong>，可直接使用并<strong>修改其内容</strong>（如 <code>append</code>、<code>update</code> 等操作）。</li><li>如果在函数内部 <strong>真实创建</strong>（即赋值）了组合数据类型变量，则无论是否存在同名全局变量，该变量均为 <strong>局部变量</strong>，函数仅对局部变量进行操作，不影响全局变量。</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">lst = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">func1</span>():</span><br><span class="line">    lst.append(<span class="number">4</span>)    <span class="comment"># 未赋值，视为全局变量，修改生效</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">func2</span>():</span><br><span class="line">    lst = [<span class="number">5</span>, <span class="number">6</span>]     <span class="comment"># 赋值了，视为局部变量，全局不受影响</span></span><br><span class="line">    lst.append(<span class="number">7</span>)</span><br><span class="line">func1()</span><br><span class="line">func2()</span><br><span class="line"><span class="built_in">print</span>(lst)           <span class="comment"># 输出 [1, 2, 3, 4]</span></span><br></pre></td></tr></table></figure></li></ul><hr><h2 id="7、递归函数（新增-⭐-考纲必考）"><a href="#7、递归函数（新增-⭐-考纲必考）" class="headerlink" title="7、递归函数（新增 ⭐ 考纲必考）"></a>7、递归函数（新增 ⭐ 考纲必考）</h2><ul><li><strong>定义</strong>：函数在内部调用自身，称为递归调用。</li><li><strong>两个必要条件</strong>：<ol><li><strong>递归基例（终止条件）</strong>：明确指定函数在什么情况下不再调用自身，直接返回结果。</li><li><strong>递归链条（递推公式）</strong>：将原问题分解为规模更小的子问题，通过调用自身来解决。</li></ol></li><li><strong>经典示例 —— 阶乘</strong>：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">factorial</span>(<span class="params">n</span>):</span><br><span class="line">    <span class="keyword">if</span> n == <span class="number">0</span>:          <span class="comment"># 递归基例</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">    <span class="keyword">else</span>:               <span class="comment"># 递归链条</span></span><br><span class="line">        <span class="keyword">return</span> n * factorial(n - <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(factorial(<span class="number">5</span>))    <span class="comment"># 输出 120</span></span><br></pre></td></tr></table></figure></li><li><strong>经典示例 —— 斐波那契数列</strong>：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">fib</span>(<span class="params">n</span>):</span><br><span class="line">    <span class="keyword">if</span> n &lt;= <span class="number">1</span>:          <span class="comment"># 递归基例</span></span><br><span class="line">        <span class="keyword">return</span> n</span><br><span class="line">    <span class="keyword">else</span>:               <span class="comment"># 递归链条</span></span><br><span class="line">        <span class="keyword">return</span> fib(n - <span class="number">1</span>) + fib(n - <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(fib(<span class="number">6</span>))           <span class="comment"># 输出 8（第6项为8）</span></span><br></pre></td></tr></table></figure></li><li><strong>⚠️ 易错点</strong>：<ul><li>必须保证递归能在有限次调用后终止，否则会触发 <code>RecursionError</code>（超过递归深度限制）。</li><li>Python 默认递归深度限制约为 1000，二级考试中递归深度很小，无需担心。</li></ul></li><li><strong>常见考题形式</strong>：给出递归函数代码，要求填写缺失部分，或写出函数执行结果。</li></ul><hr><h2 id="8、高阶函数与函数作为参数（扩展，二级常考）"><a href="#8、高阶函数与函数作为参数（扩展，二级常考）" class="headerlink" title="8、高阶函数与函数作为参数（扩展，二级常考）"></a>8、高阶函数与函数作为参数（扩展，二级常考）</h2><ul><li>Python 中函数也是<strong>对象</strong>，可以像其他数据类型一样赋值给变量，或作为参数传递给另一个函数。</li><li><strong><code>map(function, iterable)</code></strong>：将 <code>function</code> 依次作用于 <code>iterable</code> 的每个元素，返回一个迭代器。<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">nums = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line">squared = <span class="built_in">map</span>(<span class="keyword">lambda</span> x: x**<span class="number">2</span>, nums)</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">list</span>(squared))    <span class="comment"># 输出 [1, 4, 9, 16]</span></span><br></pre></td></tr></table></figure></li><li><strong><code>filter(function, iterable)</code></strong>：保留 <code>function</code> 返回 <code>True</code> 的元素，返回一个迭代器。<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">nums = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>]</span><br><span class="line">evens = <span class="built_in">filter</span>(<span class="keyword">lambda</span> x: x % <span class="number">2</span> == <span class="number">0</span>, nums)</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">list</span>(evens))      <span class="comment"># 输出 [2, 4, 6]</span></span><br></pre></td></tr></table></figure></li><li><strong><code>sorted(iterable, key=func)</code></strong>：通过 <code>key</code> 指定的函数来提取排序依据。<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">words = [<span class="string">&quot;apple&quot;</span>, <span class="string">&quot;banana&quot;</span>, <span class="string">&quot;kiwi&quot;</span>]</span><br><span class="line">sorted_words = <span class="built_in">sorted</span>(words, key=<span class="keyword">lambda</span> w: <span class="built_in">len</span>(w))</span><br><span class="line"><span class="built_in">print</span>(sorted_words)     <span class="comment"># 按长度排序：[&#x27;kiwi&#x27;, &#x27;apple&#x27;, &#x27;banana&#x27;]</span></span><br></pre></td></tr></table></figure></li></ul><hr><h3 id="🧾-函数模块速查表"><a href="#🧾-函数模块速查表" class="headerlink" title="🧾 函数模块速查表"></a>🧾 函数模块速查表</h3><table><thead><tr><th align="left">考点</th><th align="left">说明</th><th align="left">示例</th></tr></thead><tbody><tr><td align="left"><strong>函数定义</strong></td><td align="left"><code>def</code> + 函数名 + 括号 + 冒号</td><td align="left"><code>def add(a, b): return a + b</code></td></tr><tr><td align="left"><strong>可选参数</strong></td><td align="left">默认值参数，必须放在非默认参数之后</td><td align="left"><code>def func(a, b=1):</code></td></tr><tr><td align="left"><strong>可变参数 <code>*</code></strong></td><td align="left">接收多个位置参数，打包为元组</td><td align="left"><code>def func(*args):</code></td></tr><tr><td align="left"><strong>可变参数 <code>**</code></strong></td><td align="left">接收多个关键字参数，打包为字典</td><td align="left"><code>def func(**kwargs):</code></td></tr><tr><td align="left"><strong>关键字传递</strong></td><td align="left">指定形参名传参，顺序可调</td><td align="left"><code>func(b=2, a=1)</code></td></tr><tr><td align="left"><strong><code>return</code></strong></td><td align="left">返回结果并结束函数</td><td align="left"><code>return x, y</code> 返回元组</td></tr><tr><td align="left"><strong><code>global</code></strong></td><td align="left">函数内声明使用全局变量</td><td align="left"><code>global x</code></td></tr><tr><td align="left"><strong>递归</strong></td><td align="left">函数调用自身，须有终止条件</td><td align="left">阶乘 &#x2F; 斐波那契</td></tr><tr><td align="left"><strong><code>lambda</code></strong></td><td align="left">一行匿名函数</td><td align="left"><code>lambda x: x*2</code></td></tr><tr><td align="left"><strong><code>map</code></strong></td><td align="left">对可迭代对象每个元素应用函数</td><td align="left"><code>map(lambda x: x*2, [1,2,3])</code></td></tr><tr><td align="left"><strong><code>filter</code></strong></td><td align="left">筛选满足条件的元素</td><td align="left"><code>filter(lambda x: x&gt;0, [-1,2,-3])</code></td></tr><tr><td align="left"><strong><code>sorted(key=...)</code></strong></td><td align="left">按指定规则排序</td><td align="left"><code>sorted(lst, key=lambda x: len(x))</code></td></tr></tbody></table><hr>]]>
    </content>
    <id>https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E5%87%BD%E6%95%B0%E5%92%8C%E4%BB%A3%E7%A0%81%E5%A4%8D%E7%94%A8/</id>
    <link href="https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E5%87%BD%E6%95%B0%E5%92%8C%E4%BB%A3%E7%A0%81%E5%A4%8D%E7%94%A8/"/>
    <published>2026-07-20T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="1、函数的定义"><a href="#1、函数的定义" class="headerlink" title="1、函数的定义"></a>1、函数的定义</h2><ul>
<li>函数是一段具有特定功能的、可重用的语句组，是一种功能的抽象。</li>
<li>用函数名]]>
    </summary>
    <title>Python 二级备考笔记（三）：函数和代码复用</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="编程入门" scheme="https://qixia.site/categories/%E7%BC%96%E7%A8%8B%E5%85%A5%E9%97%A8/"/>
    <category term="Python二级" scheme="https://qixia.site/tags/Python%E4%BA%8C%E7%BA%A7/"/>
    <category term="Python基础" scheme="https://qixia.site/tags/Python%E5%9F%BA%E7%A1%80/"/>
    <content>
      <![CDATA[<h2 id="1-程序控制结构"><a href="#1-程序控制结构" class="headerlink" title="1. 程序控制结构"></a>1. 程序控制结构</h2><h3 id="1-1-顺序结构"><a href="#1-1-顺序结构" class="headerlink" title="1.1 顺序结构"></a>1.1 顺序结构</h3><ul><li>程序按代码的书写顺序从上到下逐条执行，每条语句执行一次（除非被分支或循环改变流程）。</li></ul><h3 id="1-2-分支结构（选择结构）"><a href="#1-2-分支结构（选择结构）" class="headerlink" title="1.2 分支结构（选择结构）"></a>1.2 分支结构（选择结构）</h3><ul><li>通过 <code>if</code>、<code>elif</code>、<code>else</code> 实现多路条件判断。</li><li><strong>基本语法</strong>：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> 条件表达式<span class="number">1</span>:</span><br><span class="line">    语句块<span class="number">1</span></span><br><span class="line"><span class="keyword">elif</span> 条件表达式<span class="number">2</span>:</span><br><span class="line">    语句块<span class="number">2</span></span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    语句块<span class="number">3</span></span><br></pre></td></tr></table></figure></li><li><code>elif</code> 可以有零个或多个，<code>else</code> 最多一个且放在最后。</li><li>条件表达式的结果会被隐式转换为布尔值（<code>True</code> 或 <code>False</code>）。</li><li><strong>注意</strong>：Python 3.10 之前没有 <code>switch</code>&#x2F;<code>case</code> 语句，使用 <code>if-elif-else</code> 实现多分支判断。二级考试环境（3.5.3~3.9.10）不支持 <code>match</code> 语句。</li><li><strong>常见易错点</strong>：<ul><li>不要忘记冒号（<code>:</code>）。</li><li>缩进必须严格一致（建议使用 4 个空格，不要混用 Tab 和空格）。</li><li>条件中慎用赋值（<code>=</code>），避免误写成比较（<code>==</code>）。</li></ul></li></ul><h3 id="1-3-循环结构"><a href="#1-3-循环结构" class="headerlink" title="1.3 循环结构"></a>1.3 循环结构</h3><h4 id="1-3-1-for-循环"><a href="#1-3-1-for-循环" class="headerlink" title="1.3.1 for 循环"></a>1.3.1 <code>for</code> 循环</h4><ul><li>用于遍历可迭代对象（字符串、列表、元组、字典、<code>range</code> 等）。</li><li>基本语法：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> 变量 <span class="keyword">in</span> 可迭代对象:</span><br><span class="line">    循环体</span><br></pre></td></tr></table></figure></li><li>遍历字典时默认遍历键，可通过 <code>.keys()</code>、<code>.values()</code>、<code>.items()</code> 分别获取键、值、键值对。</li><li>与 <code>range()</code> 结合使用最频繁（见 1.3.4 节）。</li></ul><h4 id="1-3-2-while-循环"><a href="#1-3-2-while-循环" class="headerlink" title="1.3.2 while 循环"></a>1.3.2 <code>while</code> 循环</h4><ul><li>当条件为真时重复执行循环体，直到条件为假。</li><li>基本语法：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> 条件表达式:</span><br><span class="line">    循环体</span><br></pre></td></tr></table></figure></li><li><strong>注意</strong>：必须确保循环条件最终会变为假，否则形成死循环。可在循环体内通过修改条件变量或使用 <code>break</code> 退出。</li></ul><h4 id="1-3-3-循环控制语句"><a href="#1-3-3-循环控制语句" class="headerlink" title="1.3.3 循环控制语句"></a>1.3.3 循环控制语句</h4><ul><li><code>break</code>：立即终止整个循环，跳出循环体。</li><li><code>continue</code>：跳过本次循环剩余语句，立即进入下一次循环判断。</li><li><code>else</code> 子句（与循环搭配）：<ul><li>当循环<strong>正常结束</strong>（未触发 <code>break</code>）时执行 <code>else</code> 块。</li><li>若遇到 <code>break</code>，则跳过 <code>else</code>。</li><li>示例：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">3</span>):</span><br><span class="line">    <span class="keyword">if</span> i == <span class="number">1</span>:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    <span class="built_in">print</span>(i)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;循环正常结束&quot;</span>)   <span class="comment"># 不会执行，因为 break 了</span></span><br></pre></td></tr></table></figure></li></ul></li></ul><h4 id="1-3-4-range-函数详述"><a href="#1-3-4-range-函数详述" class="headerlink" title="1.3.4 range() 函数详述"></a>1.3.4 <code>range()</code> 函数详述</h4><ul><li>生成一个不可变的整数序列，常用于 <code>for</code> 循环。</li><li>三种调用方式：<ul><li><code>range(stop)</code>：生成 0 到 stop-1。</li><li><code>range(start, stop)</code>：生成 start 到 stop-1。</li><li><code>range(start, stop, step)</code>：step 可为正或负（负值表示递减）。</li></ul></li><li><code>range</code> 对象是惰性求值的，节省内存。可用 <code>list(range(...))</code> 转为列表。</li><li><strong>注意</strong>：<code>stop</code> 取值是开区间，不包含 <code>stop</code> 本身。</li></ul><hr><h2 id="2-异常处理-try-except"><a href="#2-异常处理-try-except" class="headerlink" title="2. 异常处理 try-except"></a>2. 异常处理 <code>try-except</code></h2><ul><li><p><strong>作用</strong>：捕获并处理运行时错误，避免程序崩溃，提供更友好的错误提示。</p></li><li><p><strong>基本结构</strong>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    <span class="comment"># 可能引发异常的代码</span></span><br><span class="line"><span class="keyword">except</span> 异常类型<span class="number">1</span>:</span><br><span class="line">    <span class="comment"># 处理异常类型1</span></span><br><span class="line"><span class="keyword">except</span> 异常类型<span class="number">2</span> <span class="keyword">as</span> e:   <span class="comment"># 可获取异常对象</span></span><br><span class="line">    <span class="comment"># 处理异常类型2</span></span><br><span class="line"><span class="keyword">except</span> (TypeError, ValueError):  <span class="comment"># 多个异常合并处理</span></span><br><span class="line">    <span class="comment"># ...</span></span><br><span class="line"><span class="keyword">except</span> Exception:        <span class="comment"># 捕获所有异常（谨慎使用）</span></span><br><span class="line">    <span class="comment"># ...</span></span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="comment"># 没有异常发生时执行</span></span><br><span class="line"><span class="keyword">finally</span>:</span><br><span class="line">    <span class="comment"># 无论是否发生异常，最后一定会执行（如释放资源）</span></span><br></pre></td></tr></table></figure></li><li><p><strong>执行流程</strong>：</p><ul><li>先执行 <code>try</code> 块，若发生异常，则按顺序匹配 <code>except</code>；若匹配成功则执行对应处理块。</li><li>若没有匹配的 <code>except</code>，异常继续向上抛出（导致程序终止）。</li><li>若 <code>try</code> 块无异常，则执行 <code>else</code> 块（如果存在）。</li><li>无论是否异常，最后都会执行 <code>finally</code> 块（常用于关闭文件、释放锁等）。</li></ul></li><li><p><strong>常见内置异常</strong>：</p><ul><li><code>ValueError</code>：值错误（如 <code>int(&quot;abc&quot;)</code>）</li><li><code>TypeError</code>：类型错误（如 <code>&quot;1&quot; + 2</code>）</li><li><code>ZeroDivisionError</code>：除零错误</li><li><code>IndexError</code>：索引越界</li><li><code>KeyError</code>：字典键不存在</li><li><code>FileNotFoundError</code>：文件未找到</li></ul></li><li><p><strong>示例代码</strong>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    score = <span class="built_in">float</span>(<span class="built_in">input</span>(<span class="string">&quot;请输入分数：&quot;</span>))</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;你的分数为：&quot;</span>, score)</span><br><span class="line">    result = <span class="number">1</span> / <span class="number">0</span></span><br><span class="line"><span class="keyword">except</span> ValueError:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;出现异常！请输入数字！&quot;</span>)</span><br><span class="line"><span class="keyword">except</span> ZeroDivisionError:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;出现异常，除数不能为零！&quot;</span>)</span><br><span class="line"><span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;未知错误：<span class="subst">&#123;e&#125;</span>&quot;</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;计算成功！&quot;</span>)</span><br><span class="line"><span class="keyword">finally</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;无论是否异常，此句都会执行。&quot;</span>)</span><br></pre></td></tr></table></figure></li><li><p><strong>⚠️ 捕获异常的规范写法</strong>：</p><ul><li>优先捕获<strong>具体的异常类型</strong>（如 <code>ValueError</code>、<code>ZeroDivisionError</code>），而非直接使用 <code>except:</code> 或 <code>except Exception</code>。</li><li><code>except:</code> 会捕获包括 <code>SystemExit</code>、<code>KeyboardInterrupt</code> 在内的所有异常，可能掩盖程序终止信号，非特殊情况应避免使用。</li><li>如果确实需要捕获所有异常，建议使用 <code>except Exception as e:</code>，至少保留了异常对象的获取能力。</li></ul></li></ul><hr>]]>
    </content>
    <id>https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E7%A8%8B%E5%BA%8F%E6%8E%A7%E5%88%B6%E4%B8%8E%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/</id>
    <link href="https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E7%A8%8B%E5%BA%8F%E6%8E%A7%E5%88%B6%E4%B8%8E%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/"/>
    <published>2026-07-20T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="1-程序控制结构"><a href="#1-程序控制结构" class="headerlink" title="1. 程序控制结构"></a>1. 程序控制结构</h2><h3 id="1-1-顺序结构"><a href="#1-1-顺序结构" class="he]]>
    </summary>
    <title>Python 二级备考笔记（二）：程序控制与异常处理</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="编程入门" scheme="https://qixia.site/categories/%E7%BC%96%E7%A8%8B%E5%85%A5%E9%97%A8/"/>
    <category term="Python二级" scheme="https://qixia.site/tags/Python%E4%BA%8C%E7%BA%A7/"/>
    <category term="Python基础" scheme="https://qixia.site/tags/Python%E5%9F%BA%E7%A1%80/"/>
    <content>
      <![CDATA[<h2 id="1-文件的基本操作"><a href="#1-文件的基本操作" class="headerlink" title="1. 文件的基本操作"></a>1. 文件的基本操作</h2><h3 id="1-1-文件的类型"><a href="#1-1-文件的类型" class="headerlink" title="1.1 文件的类型"></a>1.1 文件的类型</h3><ul><li><strong>文本文件</strong>：采用统一字符编码（如 UTF-8、GBK），以字符为单位存储，可直接用文本编辑器查看。</li><li><strong>二进制文件</strong>：没有统一的字符编码，以字节为单位存储（如图片、音频、视频、可执行文件等），需要特定程序解析。</li></ul><h3 id="1-2-文件的操作流程"><a href="#1-2-文件的操作流程" class="headerlink" title="1.2 文件的操作流程"></a>1.2 文件的操作流程</h3><blockquote><p><strong>标准流程</strong>：打开文件（<code>open()</code>） → 读&#x2F;写操作 → 关闭文件（<code>close()</code>）</p></blockquote><h4 id="1-2-1-打开文件-——-open-函数"><a href="#1-2-1-打开文件-——-open-函数" class="headerlink" title="1.2.1 打开文件 —— open() 函数"></a>1.2.1 打开文件 —— <code>open()</code> 函数</h4><p><strong>语法</strong>：  </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;变量名&gt; = <span class="built_in">open</span>(&lt;文件路径及文件名&gt;, &lt;打开模式&gt;, encoding=&lt;编码&gt;)</span><br></pre></td></tr></table></figure><ul><li><code>encoding</code> 参数仅在文本模式下有效，用于指定字符编码（如 <code>&quot;utf-8&quot;</code>）。</li></ul><p><strong>打开模式详解</strong>：</p><table><thead><tr><th>模式</th><th>描述</th></tr></thead><tbody><tr><td><code>&#39;r&#39;</code></td><td><strong>只读模式</strong>（默认）。文件必须存在，否则抛出 <code>FileNotFoundError</code>。</td></tr><tr><td><code>&#39;w&#39;</code></td><td><strong>覆盖写模式</strong>。文件不存在则创建，存在则<strong>完全覆盖</strong>原有内容（清空后写入）。</td></tr><tr><td><code>&#39;x&#39;</code></td><td><strong>创建写模式</strong>。文件不存在则创建，存在则抛出 <code>FileExistsError</code>（防止意外覆盖）。</td></tr><tr><td><code>&#39;a&#39;</code></td><td><strong>追加写模式</strong>。文件不存在则创建，存在则在<strong>文件末尾</strong>追加新内容。</td></tr><tr><td><code>&#39;b&#39;</code></td><td><strong>二进制模式</strong>，需与其他模式组合（如 <code>&#39;rb&#39;</code>、<code>&#39;wb&#39;</code>）。</td></tr><tr><td><code>&#39;t&#39;</code></td><td><strong>文本模式</strong>（默认），可省略。</td></tr><tr><td><code>&#39;+&#39;</code></td><td><strong>读写模式</strong>，与 <code>r/w/x/a</code> 结合，增加读写功能（如 <code>&#39;r+&#39;</code> 可读可写，文件必须存在）。</td></tr></tbody></table><p><strong>常用组合示例</strong>：</p><ul><li><code>&#39;r&#39;</code>：只读文本，文件必须存在。</li><li><code>&#39;r+&#39;</code>：读写文本，文件必须存在，写入从文件开头覆盖（不会截断）。</li><li><code>&#39;w&#39;</code>：写入文本，覆盖原文件或新建。</li><li><code>&#39;a+&#39;</code>：追加读写，文件不存在则创建，写入追加到末尾，也可读取。</li><li><code>&#39;rb&#39;</code>：只读二进制（如图片）。</li></ul><h4 id="1-2-2-文件的读取方法"><a href="#1-2-2-文件的读取方法" class="headerlink" title="1.2.2 文件的读取方法"></a>1.2.2 文件的读取方法</h4><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>f.read(size)</code></td><td>读取 <code>size</code> 个字符（文本模式）或字节（二进制模式）。不指定 <code>size</code> 则读取全部内容。</td></tr><tr><td><code>f.readline(size)</code></td><td>读取一行（包括换行符 <code>\n</code>）。<code>size</code> 可选，表示最多读取该行前 <code>size</code> 个字符。</td></tr><tr><td><code>f.readlines(hint)</code></td><td>读取所有行，返回字符串列表（每行作为一个元素）。<code>hint</code> 可选，表示读取大约 <code>hint</code> 行。</td></tr></tbody></table><blockquote><p><strong>注意</strong>：<code>read()</code> 和 <code>readlines()</code> 会一次性将内容加载到内存，大文件建议逐行读取以节省内存。</p></blockquote><h4 id="1-2-3-文件的写入方法"><a href="#1-2-3-文件的写入方法" class="headerlink" title="1.2.3 文件的写入方法"></a>1.2.3 文件的写入方法</h4><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>f.write(s)</code></td><td>将字符串（文本模式）或字节串（二进制模式）写入文件。返回写入的字符数&#x2F;字节数。</td></tr><tr><td><code>f.writelines(lines)</code></td><td>将字符串列表 <code>lines</code> 写入文件，<strong>不会自动添加换行符</strong>，需要手动在元素中包含 <code>\n</code>。</td></tr></tbody></table><h4 id="1-2-4-文件路径说明"><a href="#1-2-4-文件路径说明" class="headerlink" title="1.2.4 文件路径说明"></a>1.2.4 文件路径说明</h4><ul><li><strong>同一级目录</strong>：直接写文件名，如 <code>&quot;内容.txt&quot;</code>。</li><li><strong>绝对路径</strong>：如 <code>&quot;C:\\Users\\用户名\\Desktop\\code\\内容.txt&quot;</code>（Windows 注意转义），或使用原始字符串 <code>r&quot;C:\Users\用户名\Desktop\code\内容.txt&quot;</code>，或用正斜杠 <code>&quot;C:/Users/用户名/Desktop/code/内容.txt&quot;</code>（Python 支持跨平台）。</li><li><strong>相对路径</strong>：相对于当前工作目录，如 <code>&quot;a\\b\\内容.txt&quot;</code>。</li></ul><h4 id="1-2-5-文件指针移动-——-seek"><a href="#1-2-5-文件指针移动-——-seek" class="headerlink" title="1.2.5 文件指针移动 —— seek()"></a>1.2.5 文件指针移动 —— <code>seek()</code></h4><ul><li><code>f.seek(offset, whence)</code>：移动文件指针到指定位置。<ul><li><code>offset</code>：偏移量（字节数）。</li><li><code>whence</code>：起始位置，<code>0</code> 表示文件开头，<code>1</code> 表示当前位置，<code>2</code> 表示文件末尾。</li></ul></li><li>文本模式下通常只支持 <code>whence=0</code>（从头开始偏移）。</li><li>示例：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">f.seek(<span class="number">0</span>)      <span class="comment"># 移动到文件开头</span></span><br><span class="line">f.seek(<span class="number">5</span>, <span class="number">0</span>)   <span class="comment"># 从开头向后移动5个字节</span></span><br></pre></td></tr></table></figure></li></ul><h4 id="1-2-6-读取示例"><a href="#1-2-6-读取示例" class="headerlink" title="1.2.6 读取示例"></a>1.2.6 读取示例</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 打开文件（指定编码）</span></span><br><span class="line">f = <span class="built_in">open</span>(<span class="string">&quot;内容.txt&quot;</span>, <span class="string">&quot;r&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取整个文件</span></span><br><span class="line">s = f.read()</span><br><span class="line"><span class="built_in">print</span>(s)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 移动指针到开头</span></span><br><span class="line">f.seek(<span class="number">0</span>)</span><br><span class="line">s1 = f.readline()   <span class="comment"># 读取第一行</span></span><br><span class="line"><span class="built_in">print</span>(s1)</span><br><span class="line"></span><br><span class="line">f.close()</span><br></pre></td></tr></table></figure><h4 id="1-2-7-写入示例"><a href="#1-2-7-写入示例" class="headerlink" title="1.2.7 写入示例"></a>1.2.7 写入示例</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">f = <span class="built_in">open</span>(<span class="string">&quot;news.txt&quot;</span>, <span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>)</span><br><span class="line">f.write(<span class="string">&quot;静夜思\n&quot;</span>)</span><br><span class="line">f.write(<span class="string">&quot;床前明月光，\n&quot;</span>)</span><br><span class="line">f.write(<span class="string">&quot;举头望明月，\n&quot;</span>)</span><br><span class="line">f.write(<span class="string">&quot;低头思故乡。\n&quot;</span>)</span><br><span class="line"></span><br><span class="line">ls = [<span class="string">&quot;静夜思\n&quot;</span>, <span class="string">&quot;床前明月光，\n&quot;</span>, <span class="string">&quot;疑是地上霜。\n&quot;</span>]</span><br><span class="line">f.writelines(ls)</span><br><span class="line"></span><br><span class="line">f.close()</span><br></pre></td></tr></table></figure><blockquote><p><strong>最佳实践</strong>：使用 <code>with</code> 语句自动管理文件上下文，避免忘记关闭：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;内容.txt&quot;</span>, <span class="string">&quot;r&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    data = f.read()</span><br><span class="line"><span class="comment"># 离开 with 块后文件自动关闭</span></span><br></pre></td></tr></table></figure></blockquote><hr><h3 id="1-3-CSV-文件读写"><a href="#1-3-CSV-文件读写" class="headerlink" title="1.3 CSV 文件读写"></a>1.3 CSV 文件读写</h3><h4 id="1-3-1-CSV-文件是什么？"><a href="#1-3-1-CSV-文件是什么？" class="headerlink" title="1.3.1 CSV 文件是什么？"></a>1.3.1 CSV 文件是什么？</h4><ul><li><strong>全称</strong>：逗号分隔值（Comma-Separated Values），纯文本文件。</li><li><strong>一行</strong>是一条记录，<strong>逗号</strong>分隔字段。</li><li>字段内含逗号或换行时，需用<strong>引号</strong>括起来（csv 模块会自动处理）。</li><li>适合存储 <strong>一维数据</strong>（一行或一列）和 <strong>二维表格数据</strong>。</li></ul><h4 id="1-3-2-csv-模块读写二维数据"><a href="#1-3-2-csv-模块读写二维数据" class="headerlink" title="1.3.2 csv 模块读写二维数据"></a>1.3.2 csv 模块读写二维数据</h4><h5 id="1-读取-CSV-文件：csv-reader"><a href="#1-读取-CSV-文件：csv-reader" class="headerlink" title="1. 读取 CSV 文件：csv.reader"></a>1. 读取 CSV 文件：<code>csv.reader</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> csv</span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&#x27;data.csv&#x27;</span>, <span class="string">&#x27;r&#x27;</span>, encoding=<span class="string">&#x27;utf-8&#x27;</span>, newline=<span class="string">&#x27;&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    reader = csv.reader(f)     <span class="comment"># 返回迭代器，每行是一个列表</span></span><br><span class="line">    <span class="keyword">for</span> row <span class="keyword">in</span> reader:</span><br><span class="line">        <span class="built_in">print</span>(row)             <span class="comment"># row 是字段组成的列表</span></span><br></pre></td></tr></table></figure><h5 id="2-写入-CSV-文件：csv-writer"><a href="#2-写入-CSV-文件：csv-writer" class="headerlink" title="2. 写入 CSV 文件：csv.writer"></a>2. 写入 CSV 文件：<code>csv.writer</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">data = [</span><br><span class="line">    [<span class="string">&#x27;姓名&#x27;</span>, <span class="string">&#x27;年龄&#x27;</span>, <span class="string">&#x27;城市&#x27;</span>],</span><br><span class="line">    [<span class="string">&#x27;张三&#x27;</span>, <span class="string">&#x27;20&#x27;</span>, <span class="string">&#x27;北京&#x27;</span>],</span><br><span class="line">    [<span class="string">&#x27;李四&#x27;</span>, <span class="string">&#x27;22&#x27;</span>, <span class="string">&#x27;上海&#x27;</span>]</span><br><span class="line">]</span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&#x27;out.csv&#x27;</span>, <span class="string">&#x27;w&#x27;</span>, encoding=<span class="string">&#x27;utf-8&#x27;</span>, newline=<span class="string">&#x27;&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    writer = csv.writer(f)</span><br><span class="line">    writer.writerows(data)     <span class="comment"># 写入多行（二维列表）</span></span><br><span class="line">    <span class="comment"># writer.writerow([&#x27;王五&#x27;, &#x27;21&#x27;, &#x27;广州&#x27;])   # 写入单行</span></span><br></pre></td></tr></table></figure><h4 id="1-3-3-一维数据的-CSV-读写"><a href="#1-3-3-一维数据的-CSV-读写" class="headerlink" title="1.3.3 一维数据的 CSV 读写"></a>1.3.3 一维数据的 CSV 读写</h4><p><strong>一维数据</strong>（如列表 <code>[1, 2, 3]</code>）在 CSV 中通常存为<strong>一行多列</strong>，或<strong>一列多行</strong>。</p><ul><li>存为一行：<code>writer.writerow([1, 2, 3])</code></li><li>存为一列：每行一个元素<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">data = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line"><span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&#x27;1d.csv&#x27;</span>, <span class="string">&#x27;w&#x27;</span>, newline=<span class="string">&#x27;&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">    writer = csv.writer(f)</span><br><span class="line">    <span class="keyword">for</span> x <span class="keyword">in</span> data:</span><br><span class="line">        writer.writerow([x])</span><br></pre></td></tr></table></figure>读取时，每行是一个单元素列表。</li></ul><h4 id="1-3-4-必会参数与函数总结"><a href="#1-3-4-必会参数与函数总结" class="headerlink" title="1.3.4 必会参数与函数总结"></a>1.3.4 必会参数与函数总结</h4><table><thead><tr><th align="left">函数 &#x2F; 参数</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left"><code>csv.reader(f)</code></td><td align="left">创建 reader 对象，可迭代，每行返回 <strong>列表</strong></td></tr><tr><td align="left"><code>csv.writer(f)</code></td><td align="left">创建 writer 对象，通过 <code>writerow</code>&#x2F;<code>writerows</code> 写入</td></tr><tr><td align="left"><code>writer.writerow(列表)</code></td><td align="left">写入一行（参数为<strong>一维列表</strong>）</td></tr><tr><td align="left"><code>writer.writerows(二维列表)</code></td><td align="left">写入多行（参数为<strong>二维列表</strong>）</td></tr><tr><td align="left"><code>open()</code> 中的 <code>newline=&#39;&#39;</code></td><td align="left"><strong>必须设置</strong>，否则写入时会出现多余空行</td></tr><tr><td align="left"><code>delimiter=&#39;,&#39;</code></td><td align="left">指定分隔符，默认逗号，可改为其他符号</td></tr><tr><td align="left"><code>encoding=&#39;utf-8&#39;</code></td><td align="left">推荐指定编码，避免中文乱码</td></tr></tbody></table><h4 id="1-3-5-易错点与考试陷阱"><a href="#1-3-5-易错点与考试陷阱" class="headerlink" title="1.3.5 易错点与考试陷阱"></a>1.3.5 易错点与考试陷阱</h4><table><thead><tr><th align="left">易错点</th><th align="left">正确做法</th></tr></thead><tbody><tr><td align="left"><strong>忘记写 <code>newline=&#39;&#39;</code></strong></td><td align="left">打开文件必须加上，否则写入会有多余空行</td></tr><tr><td align="left">写一行却用 <code>writerows</code>，把一维列表当二维用</td><td align="left"><code>writerow([1,2,3])</code> 正确；若用 <code>writerows([1,2,3])</code> 会把 1,2,3 当成三个列表，报错</td></tr><tr><td align="left">读写编码不一致</td><td align="left">读写都用 <code>encoding=&#39;utf-8&#39;</code> 或对应编码</td></tr><tr><td align="left">reader 对象只能遍历一次</td><td align="left">如需多次使用，先转为列表：<code>rows = list(reader)</code></td></tr><tr><td align="left">用 <code>split(&#39;,&#39;)</code> 处理复杂 CSV 会出错</td><td align="left">字段内含逗号时，使用 csv 模块才能正确解析</td></tr></tbody></table><hr><h2 id="2-常用第三方库"><a href="#2-常用第三方库" class="headerlink" title="2. 常用第三方库"></a>2. 常用第三方库</h2><h3 id="2-1-jieba-库-——-中文分词"><a href="#2-1-jieba-库-——-中文分词" class="headerlink" title="2.1 jieba 库 —— 中文分词"></a>2.1 jieba 库 —— 中文分词</h3><h4 id="2-1-1-安装"><a href="#2-1-1-安装" class="headerlink" title="2.1.1 安装"></a>2.1.1 安装</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install jieba</span><br></pre></td></tr></table></figure><h4 id="2-1-2-常用方法"><a href="#2-1-2-常用方法" class="headerlink" title="2.1.2 常用方法"></a>2.1.2 常用方法</h4><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>jieba.lcut(s)</code></td><td><strong>精确模式</strong>：返回一个列表，将句子最精确地切开，适合文本分析。</td></tr><tr><td><code>jieba.lcut(s, cut_all=True)</code></td><td><strong>全模式</strong>：返回一个列表，把句子中所有可以成词的词语都扫描出来，速度较快，但存在冗余。</td></tr><tr><td><code>jieba.lcut_for_search(s)</code></td><td><strong>搜索引擎模式</strong>：在精确模式基础上，对长词再次切分，提高召回率，适用于搜索引擎分词。</td></tr><tr><td><code>jieba.add_word(w)</code></td><td>向分词词典中添加新词 <code>w</code>，使分词更准确。</td></tr></tbody></table><p><strong>示例</strong>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> jieba</span><br><span class="line"></span><br><span class="line">s = <span class="string">&quot;大家好，我是阿福课堂的阿福老师，欢迎关注阿福课堂！&quot;</span></span><br><span class="line">jieba.add_word(<span class="string">&quot;阿福课堂&quot;</span>)   <span class="comment"># 添加自定义词</span></span><br><span class="line">jieba.add_word(<span class="string">&quot;阿福老师&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 精确模式</span></span><br><span class="line"><span class="built_in">print</span>(jieba.lcut(s))</span><br><span class="line"><span class="comment"># 全模式</span></span><br><span class="line"><span class="built_in">print</span>(jieba.lcut(s, cut_all=<span class="literal">True</span>))</span><br><span class="line"><span class="comment"># 搜索引擎模式</span></span><br><span class="line"><span class="built_in">print</span>(jieba.lcut_for_search(s))</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong>：<code>lcut</code> 直接返回列表，<code>cut</code> 返回生成器，可按需选择。</p></blockquote><hr><h3 id="2-2-turtle-库-——-海龟绘图"><a href="#2-2-turtle-库-——-海龟绘图" class="headerlink" title="2.2 turtle 库 —— 海龟绘图"></a>2.2 turtle 库 —— 海龟绘图</h3><h4 id="2-2-1-窗体函数"><a href="#2-2-1-窗体函数" class="headerlink" title="2.2.1 窗体函数"></a>2.2.1 窗体函数</h4><ul><li><code>turtle.setup(width, height, startx, starty)</code>  <ul><li><code>width</code>：窗口宽度（像素）  </li><li><code>height</code>：窗口高度（像素）  </li><li><code>startx</code>：窗口与屏幕左侧的距离（像素）  </li><li><code>starty</code>：窗口与屏幕顶部的距离（像素）</li></ul></li></ul><h4 id="2-2-2-画笔状态函数"><a href="#2-2-2-画笔状态函数" class="headerlink" title="2.2.2 画笔状态函数"></a>2.2.2 画笔状态函数</h4><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>pendown()</code></td><td>放下画笔，移动时绘制线条。</td></tr><tr><td><code>penup()</code></td><td>提起画笔，移动时不绘制。</td></tr><tr><td><code>pensize(width)</code></td><td>设置画笔线条粗细（宽度）。</td></tr><tr><td><code>pencolor(color)</code></td><td>设置画笔颜色（颜色名称或RGB值）。</td></tr><tr><td><code>color(pencolor, fillcolor)</code></td><td>同时设置画笔颜色和填充颜色。</td></tr><tr><td><code>begin_fill()</code></td><td>开始填充图形（需配合 <code>end_fill()</code>）。</td></tr><tr><td><code>end_fill()</code></td><td>结束填充，填充起始点到终点的封闭区域。</td></tr><tr><td><code>filling()</code></td><td>返回当前填充状态（<code>True</code> &#x2F; <code>False</code>）。</td></tr><tr><td><code>clear()</code></td><td>清除画布上的图形，海龟状态（位置&#x2F;朝向）保持不变。</td></tr><tr><td><code>reset()</code></td><td>清空画布并将海龟重置到原点，朝向东。</td></tr></tbody></table><h4 id="2-2-3-画笔运动函数"><a href="#2-2-3-画笔运动函数" class="headerlink" title="2.2.3 画笔运动函数"></a>2.2.3 画笔运动函数</h4><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>forward(distance)</code></td><td>沿当前方向前进 <code>distance</code> 像素。</td></tr><tr><td><code>backward(distance)</code></td><td>沿当前相反方向后退 <code>distance</code> 像素。</td></tr><tr><td><code>right(angle)</code></td><td>向右（顺时针）旋转 <code>angle</code> 度。</td></tr><tr><td><code>left(angle)</code></td><td>向左（逆时针）旋转 <code>angle</code> 度。</td></tr><tr><td><code>goto(x, y)</code></td><td>移动到绝对坐标 <code>(x, y)</code> 处。</td></tr><tr><td><code>setx(x)</code></td><td>只修改横坐标，纵坐标不变。</td></tr><tr><td><code>sety(y)</code></td><td>只修改纵坐标，横坐标不变。</td></tr><tr><td><code>setheading(angle)</code></td><td>设置当前朝向为 <code>angle</code> 度（0 为东，90 为北，等）。</td></tr><tr><td><code>home()</code></td><td>回到原点（0,0），朝向正东。</td></tr><tr><td><code>circle(radius)</code></td><td>绘制半径为 <code>radius</code> 的圆（圆心在海龟左侧）。</td></tr></tbody></table><p><strong>简单示例</strong>（画正方形）：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> turtle</span><br><span class="line">turtle.setup(<span class="number">400</span>, <span class="number">300</span>)</span><br><span class="line">turtle.pensize(<span class="number">3</span>)</span><br><span class="line">turtle.pencolor(<span class="string">&quot;red&quot;</span>)</span><br><span class="line"><span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">4</span>):</span><br><span class="line">    turtle.forward(<span class="number">100</span>)</span><br><span class="line">    turtle.right(<span class="number">90</span>)</span><br><span class="line">turtle.done()</span><br></pre></td></tr></table></figure><hr><h3 id="2-3-time-库"><a href="#2-3-time-库" class="headerlink" title="2.3 time 库"></a>2.3 time 库</h3><p>time库是Python标准库之一，提供各种与时间相关的函数，常用于<strong>时间获取</strong>、<strong>时间格式化</strong>和<strong>程序计时</strong>。</p><h4 id="2-3-1-时间的三种表示方式"><a href="#2-3-1-时间的三种表示方式" class="headerlink" title="2.3.1 时间的三种表示方式"></a>2.3.1 时间的三种表示方式</h4><table><thead><tr><th>表示方式</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><strong>时间戳</strong></td><td>浮点数（秒）</td><td>从1970年1月1日00:00:00（UTC）开始到现在的秒数，如 <code>1558741178.5478582</code></td></tr><tr><td><strong>时间元组</strong></td><td>struct_time对象</td><td>包含年、月、日、时、分、秒等信息的结构化对象</td></tr><tr><td><strong>格式化时间</strong></td><td>字符串</td><td>按指定格式显示的时间字符串，如 <code>&#39;Sat May 25 07:39:38 2019&#39;</code></td></tr></tbody></table><h5 id="struct-time-时间元组结构"><a href="#struct-time-时间元组结构" class="headerlink" title="struct_time 时间元组结构"></a>struct_time 时间元组结构</h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">time.struct_time(</span><br><span class="line">    tm_year=<span class="number">2019</span>,    <span class="comment"># 年份</span></span><br><span class="line">    tm_mon=<span class="number">5</span>,        <span class="comment"># 月份 1~12</span></span><br><span class="line">    tm_mday=<span class="number">7</span>,       <span class="comment"># 日期 1~31</span></span><br><span class="line">    tm_hour=<span class="number">7</span>,       <span class="comment"># 小时 0~23</span></span><br><span class="line">    tm_min=<span class="number">5</span>,        <span class="comment"># 分钟 0~59</span></span><br><span class="line">    tm_sec=<span class="number">38</span>,       <span class="comment"># 秒 0~61（闰秒）</span></span><br><span class="line">    tm_wday=<span class="number">1</span>,       <span class="comment"># 星期 0~6（0=周一）</span></span><br><span class="line">    tm_yday=<span class="number">127</span>,     <span class="comment"># 一年中的第几天 1~366</span></span><br><span class="line">    tm_isdst=-<span class="number">1</span>      <span class="comment"># 夏令时标志（1=是，0=否，-1=未知）</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><h4 id="2-3-2-常用时间获取函数"><a href="#2-3-2-常用时间获取函数" class="headerlink" title="2.3.2 常用时间获取函数"></a>2.3.2 常用时间获取函数</h4><table><thead><tr><th>函数</th><th>返回值</th><th>说明</th></tr></thead><tbody><tr><td><code>time.time()</code></td><td>浮点数（时间戳）</td><td>返回当前时间的时间戳</td></tr><tr><td><code>time.localtime([sec])</code></td><td>struct_time（当地时间）</td><td>将时间戳转换为本地时间元组，无参时获取当前本地时间</td></tr><tr><td><code>time.gmtime([sec])</code></td><td>struct_time（UTC时间）</td><td>将时间戳转换为UTC时间元组</td></tr><tr><td><code>time.ctime([sec])</code></td><td>字符串</td><td>将时间戳转换为可读的字符串格式（如 <code>&#39;Sat May 25 07:39:38 2019&#39;</code>）</td></tr><tr><td><code>time.asctime([t])</code></td><td>字符串</td><td>将时间元组转换为可读的字符串格式</td></tr></tbody></table><h4 id="2-3-3-时间格式化"><a href="#2-3-3-时间格式化" class="headerlink" title="2.3.3 时间格式化"></a>2.3.3 时间格式化</h4><h5 id="1-格式化输出（时间元组-→-字符串）"><a href="#1-格式化输出（时间元组-→-字符串）" class="headerlink" title="1. 格式化输出（时间元组 → 字符串）"></a>1. 格式化输出（时间元组 → 字符串）</h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">time.strftime(<span class="built_in">format</span>[, t])</span><br></pre></td></tr></table></figure><p>将时间元组按指定格式转换为字符串，无 t 时格式化当前时间。</p><h5 id="2-格式化解析（字符串-→-时间元组）"><a href="#2-格式化解析（字符串-→-时间元组）" class="headerlink" title="2. 格式化解析（字符串 → 时间元组）"></a>2. 格式化解析（字符串 → 时间元组）</h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">time.strptime(string[, <span class="built_in">format</span>])</span><br></pre></td></tr></table></figure><p>将时间字符串按指定格式解析为时间元组。</p><h5 id="3-格式控制符（二级考试重点）"><a href="#3-格式控制符（二级考试重点）" class="headerlink" title="3. 格式控制符（二级考试重点）"></a>3. 格式控制符（二级考试重点）</h5><table><thead><tr><th>格式符</th><th>含义</th><th>值范围&#x2F;示例</th></tr></thead><tbody><tr><td><code>%Y</code></td><td>完整年份</td><td>0001~9999，如 1900</td></tr><tr><td><code>%y</code></td><td>两位年份</td><td>00~99，如 19</td></tr><tr><td><code>%m</code></td><td>月份（数字）</td><td>01~12，如 10</td></tr><tr><td><code>%B</code></td><td>月份（全称）</td><td>January~December</td></tr><tr><td><code>%b</code></td><td>月份（缩写）</td><td>Jan~Dec，如 Apr</td></tr><tr><td><code>%d</code></td><td>日期</td><td>01~31，如 25</td></tr><tr><td><code>%A</code></td><td>星期（全称）</td><td>Monday~Sunday</td></tr><tr><td><code>%a</code></td><td>星期（缩写）</td><td>Mon~Sun，如 Wed</td></tr><tr><td><code>%H</code></td><td>小时（24小时制）</td><td>00~23，如 12</td></tr><tr><td><code>%I</code></td><td>小时（12小时制）</td><td>01~12，如 7</td></tr><tr><td><code>%M</code></td><td>分钟</td><td>00~59，如 26</td></tr><tr><td><code>%S</code></td><td>秒</td><td>00~59，如 26</td></tr><tr><td><code>%p</code></td><td>上&#x2F;下午</td><td>AM 或 PM</td></tr></tbody></table><h4 id="2-3-4-程序计时相关函数"><a href="#2-3-4-程序计时相关函数" class="headerlink" title="2.3.4 程序计时相关函数"></a>2.3.4 程序计时相关函数</h4><table><thead><tr><th>函数</th><th>说明</th></tr></thead><tbody><tr><td><code>time.sleep(seconds)</code></td><td>让程序暂停执行指定的秒数（常用于延时）</td></tr><tr><td><code>time.perf_counter()</code></td><td>返回一个高精度计时器的值（以秒为单位），起点为随机值，常用于测量短时间间隔</td></tr><tr><td><code>time.monotonic()</code></td><td>返回从开机到当前的时间值（单调递增，不受系统时间修改影响）</td></tr></tbody></table><p><strong>典型用法——计算代码执行时间</strong>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line">start = time.perf_counter()</span><br><span class="line"><span class="comment"># 待测代码...</span></span><br><span class="line">end = time.perf_counter()</span><br><span class="line"><span class="built_in">print</span>(end - start)</span><br></pre></td></tr></table></figure><hr><h3 id="2-4-PyInstaller-库"><a href="#2-4-PyInstaller-库" class="headerlink" title="2.4 PyInstaller 库"></a>2.4 PyInstaller 库</h3><ul><li><strong>作用</strong>：将 <code>.py</code> 源程序打包成<strong>独立的可执行文件</strong>（Windows 下为 <code>.exe</code>），使未安装 Python 解释器的电脑也能直接运行。</li><li><strong>依赖</strong>：打包时会自动分析并捆绑所需的依赖库和 Python 解释器。</li></ul><h4 id="2-4-1-基本打包流程"><a href="#2-4-1-基本打包流程" class="headerlink" title="2.4.1 基本打包流程"></a>2.4.1 基本打包流程</h4><p>在命令行中运行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pyinstaller [选项] 源文件.py</span><br></pre></td></tr></table></figure><p>成功后，会产生几个重要目录&#x2F;文件：</p><ul><li><code>dist</code> 目录：存放<strong>最终打包好的程序</strong>（考试常问“可执行文件在哪个目录下”）。</li><li><code>build</code> 目录：存放临时文件，可删除。</li><li><code>源文件.spec</code>：打包配置文件，可重复使用。</li></ul><h4 id="2-4-2-必考常用参数（重点记忆）"><a href="#2-4-2-必考常用参数（重点记忆）" class="headerlink" title="2.4.2 必考常用参数（重点记忆）"></a>2.4.2 必考常用参数（重点记忆）</h4><table><thead><tr><th align="left">参数</th><th align="left">完整形式</th><th align="left">作用与说明</th></tr></thead><tbody><tr><td align="left"><code>-F</code></td><td align="left"><code>--onefile</code></td><td align="left"><strong>生成单一可执行文件</strong>（考试最常考，所有内容打包进一个 <code>.exe</code>）</td></tr><tr><td align="left"><code>-D</code></td><td align="left"><code>--onedir</code></td><td align="left"><strong>生成包含多个文件的目录</strong>（默认选项，文件之间依赖关系可见）</td></tr><tr><td align="left"><code>-w</code></td><td align="left"><code>--windowed</code> &#x2F; <code>--noconsole</code></td><td align="left"><strong>关闭控制台窗口</strong>（适用于带 GUI 界面的程序，如 tkinter 等）</td></tr><tr><td align="left"><code>-c</code></td><td align="left"><code>--console</code></td><td align="left"><strong>显示控制台窗口</strong>（默认，适合命令行程序）</td></tr><tr><td align="left"><code>-i</code></td><td align="left"><code>--icon</code></td><td align="left"><strong>指定生成 exe 的图标</strong>（后跟图标文件路径，Windows 下常用 <code>.ico</code> 文件）</td></tr><tr><td align="left"><code>-n</code></td><td align="left"><code>--name</code></td><td align="left"><strong>指定生成文件的名字</strong>（不指定则使用源文件名）</td></tr><tr><td align="left"><code>--clean</code></td><td align="left">无简短形式</td><td align="left">打包前清理 <code>build</code> 和临时文件，避免缓存问题</td></tr></tbody></table><blockquote><p><strong>记忆口诀</strong>：<strong>F单D多，w窗c台，i图标n命名。</strong></p></blockquote><hr><h3 id="2-5-基本-NumPy-库"><a href="#2-5-基本-NumPy-库" class="headerlink" title="2.5 基本 NumPy 库"></a>2.5 基本 NumPy 库</h3><ul><li><strong>核心</strong>：NumPy 中的多维数组对象，所有元素<strong>必须是同类型</strong>。</li><li><strong>优点</strong>：比 Python 列表更节省内存，运算速度更快。</li></ul><h4 id="2-5-1-数组的创建（必考，参数要记准）"><a href="#2-5-1-数组的创建（必考，参数要记准）" class="headerlink" title="2.5.1 数组的创建（必考，参数要记准）"></a>2.5.1 数组的创建（必考，参数要记准）</h4><table><thead><tr><th align="left">函数</th><th align="left">说明与示例</th></tr></thead><tbody><tr><td align="left"><code>np.array([列表])</code></td><td align="left">从列表创建，可指定 <code>dtype</code>。<code>np.array([1, 2])</code></td></tr><tr><td align="left"><code>np.arange(起, 止, 步长)</code></td><td align="left">类似 range，<strong>步长可为小数</strong>，不含终值。<code>np.arange(5)</code> → <code>[0 1 2 3 4]</code></td></tr><tr><td align="left"><code>np.linspace(起, 止, 个数)</code></td><td align="left">生成<strong>等差数列</strong>，<strong>默认包含终值</strong>。<code>np.linspace(0, 1, 5)</code> → <code>[0. 0.25 0.5 0.75 1.]</code></td></tr><tr><td align="left"><code>np.zeros(形状)</code></td><td align="left">全 0 数组，形状用<strong>元组</strong>。<code>np.zeros((2, 3))</code></td></tr><tr><td align="left"><code>np.ones(形状)</code></td><td align="left">全 1 数组</td></tr><tr><td align="left"><code>np.eye(n)</code> 或 <code>np.identity(n)</code></td><td align="left">创建 n 阶单位矩阵</td></tr><tr><td align="left"><code>np.full(形状, 值)</code></td><td align="left">用指定值填充数组。<code>np.full((2, 2), 7)</code></td></tr></tbody></table><h4 id="2-5-2-数组属性（选择题高频）"><a href="#2-5-2-数组属性（选择题高频）" class="headerlink" title="2.5.2 数组属性（选择题高频）"></a>2.5.2 数组属性（选择题高频）</h4><table><thead><tr><th align="left">属性</th><th align="left">含义</th></tr></thead><tbody><tr><td align="left"><code>arr.ndim</code></td><td align="left">数组维度数（几维）</td></tr><tr><td align="left"><code>arr.shape</code></td><td align="left">形状，<strong>返回元组</strong> (行, 列)</td></tr><tr><td align="left"><code>arr.size</code></td><td align="left">元素总个数</td></tr><tr><td align="left"><code>arr.dtype</code></td><td align="left">元素数据类型，如 <code>int32</code></td></tr></tbody></table><h4 id="2-5-3-形状变换"><a href="#2-5-3-形状变换" class="headerlink" title="2.5.3 形状变换"></a>2.5.3 形状变换</h4><ul><li><code>arr.reshape(新形状)</code>：返回新形状数组，<strong>元素总数需一致</strong>。可用 <code>-1</code> 表示自动计算，如 <code>arr.reshape(-1, 1)</code>。</li><li><code>arr.flatten()</code>：将多维数组展平为一维，返回<strong>副本</strong>。</li><li><code>arr.ravel()</code>：同样展平，但多数情况返回<strong>视图</strong>（修改会影响原数组）。</li><li><code>arr.T</code> 或 <code>arr.transpose()</code>：转置（行列互换）。</li></ul><h4 id="2-5-4-索引与切片（重点：切片是视图）"><a href="#2-5-4-索引与切片（重点：切片是视图）" class="headerlink" title="2.5.4 索引与切片（重点：切片是视图）"></a>2.5.4 索引与切片（重点：切片是视图）</h4><ul><li><strong>一维</strong>：与列表类似，<code>a[2:5]</code>。</li><li><strong>二维</strong>：<code>a[行, 列]</code>。<ul><li><code>a[1, 2]</code> 定位一个元素。</li><li><code>a[:, 0]</code> 取第一列全部行。</li><li><code>a[0:2, 1:3]</code> 切片取子矩阵。</li></ul></li><li><strong>布尔索引</strong>：<code>a[a &gt; 5]</code> 可筛选出满足条件的元素。</li></ul><blockquote><p><strong>关键</strong>：NumPy 切片得到的<strong>是原数组的视图</strong>，修改切片会改变原数组；Python 列表切片则是副本。这点判断题极爱考。</p></blockquote><h4 id="2-5-5-基本运算与统计函数"><a href="#2-5-5-基本运算与统计函数" class="headerlink" title="2.5.5 基本运算与统计函数"></a>2.5.5 基本运算与统计函数</h4><ul><li><strong>数组与标量运算</strong>：会广播到每个元素（<code>a + 2</code>）。</li><li><strong>数组间运算</strong>：对应位置元素运算，形状需满足<strong>广播规则</strong>。</li><li><strong>矩阵乘法</strong>：<code>A @ B</code> 或 <code>A.dot(B)</code>（对应位置相乘用 <code>*</code>）。</li><li><strong>常用统计函数</strong>（可指定 <code>axis</code>）：<ul><li><code>arr.sum()</code>、<code>arr.mean()</code>、<code>arr.max()</code>、<code>arr.min()</code>、<code>arr.std()</code>。</li><li><code>arr.argmax()</code>（最大值的索引）、<code>arr.argmin()</code>。</li><li><code>np.median(arr)</code> 中位数。</li><li>默认是对所有元素计算，<code>axis=0</code> 沿列方向（行归约），<code>axis=1</code> 沿行方向（列归约）。</li></ul></li></ul><h4 id="2-5-6-随机数模块（np-random）"><a href="#2-5-6-随机数模块（np-random）" class="headerlink" title="2.5.6 随机数模块（np.random）"></a>2.5.6 随机数模块（<code>np.random</code>）</h4><table><thead><tr><th align="left">函数</th><th align="left">作用</th></tr></thead><tbody><tr><td align="left"><code>np.random.rand(d0, d1, ...)</code></td><td align="left">生成 [0, 1) 均匀分布，给定形状</td></tr><tr><td align="left"><code>np.random.randn(d0, d1, ...)</code></td><td align="left">生成标准正态分布（均0方1）</td></tr><tr><td align="left"><code>np.random.randint(low, high, size)</code></td><td align="left">生成指定范围整数，不含 <code>high</code></td></tr><tr><td align="left"><code>np.random.seed(值)</code></td><td align="left">设置随机种子，保证每次随机结果相同</td></tr></tbody></table><hr><h3 id="2-6-random-库"><a href="#2-6-random-库" class="headerlink" title="2.6 random 库"></a>2.6 random 库</h3><ul><li><strong>作用</strong>：生成随机数，或对序列做随机操作。</li><li><strong>导入</strong>：<code>import random</code>，属于 Python 标准库，无需安装。</li><li><strong>原理</strong>：生成的是<strong>伪随机数</strong>，由特定算法（梅森旋转）生成，起始于“种子”值。种子一样，随机序列就完全一样。</li></ul><h4 id="2-6-1-必考函数分类速记"><a href="#2-6-1-必考函数分类速记" class="headerlink" title="2.6.1 必考函数分类速记"></a>2.6.1 必考函数分类速记</h4><h5 id="1-基本随机数函数"><a href="#1-基本随机数函数" class="headerlink" title="1. 基本随机数函数"></a>1. 基本随机数函数</h5><table><thead><tr><th align="left">函数</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left"><code>random.seed(a=None)</code></td><td align="left">设置随机种子。若 <code>a</code> 相同，后续生成的随机数序列完全一致。常用于测试复现。</td></tr><tr><td align="left"><code>random.random()</code></td><td align="left">生成一个 <code>[0.0, 1.0)</code> 范围内的随机浮点数。<strong>无参数</strong>。</td></tr></tbody></table><h5 id="2-整数随机函数"><a href="#2-整数随机函数" class="headerlink" title="2. 整数随机函数"></a>2. 整数随机函数</h5><table><thead><tr><th align="left">函数</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left"><code>random.randint(a, b)</code></td><td align="left">返回一个 <code>[a, b]</code> 范围内的随机整数，<strong>包含两端</strong>。</td></tr><tr><td align="left"><code>random.randrange(start, stop[, step])</code></td><td align="left">从 <code>range(start, stop, step)</code> 中随机选一个整数。<strong>不包含 <code>stop</code></strong>。</td></tr><tr><td align="left"><code>random.getrandbits(k)</code></td><td align="left">返回一个具有 k 个随机比特位的非负整数（考试较少考，了解即可）。</td></tr></tbody></table><h5 id="3-浮点数随机函数"><a href="#3-浮点数随机函数" class="headerlink" title="3. 浮点数随机函数"></a>3. 浮点数随机函数</h5><table><thead><tr><th align="left">函数</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left"><code>random.uniform(a, b)</code></td><td align="left">返回一个 <code>[a, b]</code> 或 <code>[b, a]</code> 范围内的随机浮点数（a、b 大小无关）。</td></tr></tbody></table><h5 id="4-序列操作函数（高频考点）"><a href="#4-序列操作函数（高频考点）" class="headerlink" title="4. 序列操作函数（高频考点）"></a>4. 序列操作函数（高频考点）</h5><table><thead><tr><th align="left">函数</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left"><code>random.choice(seq)</code></td><td align="left">从非空序列 <code>seq</code>（列表、元组、字符串等）中<strong>随机选一个元素</strong>。</td></tr><tr><td align="left"><code>random.shuffle(seq)</code></td><td align="left">将序列 <code>seq</code> <strong>原地随机打乱</strong>，返回 <code>None</code>。<strong>只能用于可变序列（如列表）</strong>。</td></tr><tr><td align="left"><code>random.sample(population, k)</code></td><td align="left">从总体 <code>population</code> 中<strong>不重复抽取 k 个元素</strong>，返回新列表。<strong>原序列不变</strong>。</td></tr></tbody></table><h4 id="2-6-2-关键区别对比"><a href="#2-6-2-关键区别对比" class="headerlink" title="2.6.2 关键区别对比"></a>2.6.2 关键区别对比</h4><table><thead><tr><th align="left">对比项</th><th align="left">不同点</th></tr></thead><tbody><tr><td align="left"><code>randint</code> vs <code>randrange</code></td><td align="left"><code>randint(a, b)</code> 包含 <code>b</code>；<code>randrange(start, stop)</code> 不含 <code>stop</code>，用法和 <code>range</code> 一样。</td></tr><tr><td align="left"><code>shuffle</code> vs <code>sample</code></td><td align="left"><code>shuffle</code> 改变原列表，返回 <code>None</code>；<code>sample</code> 不改变原序列，返回新列表。</td></tr><tr><td align="left"><code>random</code> vs <code>uniform</code></td><td align="left"><code>random()</code> 无参数，返回 <code>[0.0, 1.0)</code>；<code>uniform(a, b)</code> 可指定范围，返回浮点数。</td></tr></tbody></table><h4 id="2-6-3-典型考试示例"><a href="#2-6-3-典型考试示例" class="headerlink" title="2.6.3 典型考试示例"></a>2.6.3 典型考试示例</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> random</span><br><span class="line"></span><br><span class="line">random.seed(<span class="number">10</span>)          <span class="comment"># 设种子，使结果可复现</span></span><br><span class="line"><span class="built_in">print</span>(random.random())   <span class="comment"># 输出 0.5714025946899135（固定）</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(random.randint(<span class="number">1</span>, <span class="number">3</span>))   <span class="comment"># 可能输出 1, 2, 3</span></span><br><span class="line"><span class="built_in">print</span>(random.randrange(<span class="number">1</span>, <span class="number">3</span>)) <span class="comment"># 可能输出 1, 2 （不含3）</span></span><br><span class="line"></span><br><span class="line">list1 = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line">random.shuffle(list1)    <span class="comment"># list1 被原地打乱</span></span><br><span class="line"><span class="built_in">print</span>(list1)             <span class="comment"># 例如 [3, 1, 4, 2]</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(random.sample(list1, <span class="number">2</span>))  <span class="comment"># 随机抽取2个不重复元素，原列表不变</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%94%EF%BC%89%EF%BC%9A%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E5%92%8C%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%93/</id>
    <link href="https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%94%EF%BC%89%EF%BC%9A%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E5%92%8C%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%93/"/>
    <published>2026-07-20T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="1-文件的基本操作"><a href="#1-文件的基本操作" class="headerlink" title="1. 文件的基本操作"></a>1. 文件的基本操作</h2><h3 id="1-1-文件的类型"><a href="#1-1-文件的类型" cla]]>
    </summary>
    <title>Python 二级备考笔记（五）：文件操作和第三方库</title>
    <updated>2026-07-22T01:33:15.025Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="编程入门" scheme="https://qixia.site/categories/%E7%BC%96%E7%A8%8B%E5%85%A5%E9%97%A8/"/>
    <category term="Python二级" scheme="https://qixia.site/tags/Python%E4%BA%8C%E7%BA%A7/"/>
    <category term="Python基础" scheme="https://qixia.site/tags/Python%E5%9F%BA%E7%A1%80/"/>
    <content>
      <![CDATA[<h2 id="1-字符串操作符"><a href="#1-字符串操作符" class="headerlink" title="1. 字符串操作符"></a>1. 字符串操作符</h2><ul><li><strong><code>+</code> 拼接</strong>：连接两个字符串，生成新字符串。注意 <code>+</code> 两侧都必须是字符串，否则抛出 <code>TypeError</code>。</li><li><strong><code>*</code> 重复</strong>：字符串与整数相乘，重复复制该字符串指定次数。</li><li><strong><code>in</code> 和 <code>not in</code></strong>：成员判断，判断子串是否（不）存在于原字符串中，返回布尔值。</li><li><strong>比较运算符</strong>（<code>==</code>、<code>!=</code>、<code>&gt;</code>、<code>&lt;</code>、<code>&gt;=</code>、<code>&lt;=</code>）：按字典序（Unicode 码点）比较两个字符串。逐字符比较，遇到第一个不同的字符即决定大小。</li><li><strong><code>len()</code></strong>：返回字符串长度（字符个数）。</li><li><strong><code>max()</code> &#x2F; <code>min()</code></strong>：返回最大&#x2F;最小字符（按 Unicode 码点）。</li></ul><hr><h2 id="2-字符串索引"><a href="#2-字符串索引" class="headerlink" title="2. 字符串索引"></a>2. 字符串索引</h2><ul><li><strong>正向索引</strong>：从 <code>0</code> 开始，<code>0</code> 表示第一个字符，<code>len(s)-1</code> 表示最后一个字符。</li><li><strong>反向索引</strong>：从 <code>-1</code> 开始，<code>-1</code> 表示最后一个字符，<code>-len(s)</code> 表示第一个字符。</li><li>示例：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">s = <span class="string">&quot;Python&quot;</span></span><br><span class="line"><span class="built_in">print</span>(s[<span class="number">0</span>])    <span class="comment"># &#x27;P&#x27;</span></span><br><span class="line"><span class="built_in">print</span>(s[-<span class="number">1</span>])   <span class="comment"># &#x27;n&#x27;</span></span><br><span class="line"><span class="built_in">print</span>(s[<span class="number">5</span>])    <span class="comment"># &#x27;n&#x27;</span></span><br><span class="line"><span class="built_in">print</span>(s[-<span class="number">6</span>])   <span class="comment"># &#x27;P&#x27;</span></span><br></pre></td></tr></table></figure></li><li><strong>索引越界</strong>：访问超出有效范围会引发 <code>IndexError</code>。</li></ul><hr><h2 id="3-字符串切片（重点）"><a href="#3-字符串切片（重点）" class="headerlink" title="3. 字符串切片（重点）"></a>3. 字符串切片（重点）</h2><ul><li><p><strong>格式</strong>：<code>[开始 : 结束 : 步长]</code></p><ul><li><code>开始</code>、<code>结束</code>、<code>步长</code> 均可省略。</li><li>遵循 <strong>左闭右开</strong> 原则：包含 <code>开始</code> 索引对应的字符，不包含 <code>结束</code> 索引对应的字符。</li><li>省略 <code>开始</code>：默认从字符串开头（正向步长）或结尾（负向步长）。</li><li>省略 <code>结束</code>：默认到字符串末尾（正向步长）或开头（负向步长）。</li><li>省略 <code>步长</code>：默认为 <code>1</code>。</li></ul></li><li><p><strong>常用切法</strong>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">s = <span class="string">&quot;HelloWorld&quot;</span></span><br><span class="line"><span class="built_in">print</span>(s[<span class="number">0</span>:<span class="number">5</span>])     <span class="comment"># &quot;Hello&quot;</span></span><br><span class="line"><span class="built_in">print</span>(s[:<span class="number">5</span>])      <span class="comment"># &quot;Hello&quot;（省略开始）</span></span><br><span class="line"><span class="built_in">print</span>(s[<span class="number">5</span>:])      <span class="comment"># &quot;World&quot;（省略结束）</span></span><br><span class="line"><span class="built_in">print</span>(s[:])       <span class="comment"># &quot;HelloWorld&quot;（全复制）</span></span><br><span class="line"><span class="built_in">print</span>(s[::<span class="number">2</span>])     <span class="comment"># &quot;HloWrd&quot;（步长2）</span></span><br><span class="line"><span class="built_in">print</span>(s[<span class="number">1</span>:<span class="number">8</span>:<span class="number">2</span>])   <span class="comment"># &quot;elWr&quot;（索引1,3,5,7）</span></span><br></pre></td></tr></table></figure></li><li><p><strong>负步长切片</strong>：</p><ul><li>表示从右向左取字符，此时 <code>开始</code> 应大于 <code>结束</code>（否则结果为空）。</li><li>示例：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">print</span>(s[::-<span class="number">1</span>])   <span class="comment"># &quot;dlroWolleH&quot;（反转字符串）</span></span><br><span class="line"><span class="built_in">print</span>(s[<span class="number">5</span>:<span class="number">0</span>:-<span class="number">1</span>]) <span class="comment"># &quot;Wolle&quot;（从索引5到1，步长-1）</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p><strong>切片不会出现索引越界</strong>：若 <code>开始</code> 或 <code>结束</code> 超出范围，Python 会自动截取到有效边界，不会报错（这是切片与索引的重要区别）。</p></li><li><p><strong>切片返回的是新字符串</strong>，不修改原字符串（字符串不可变）。</p></li></ul><hr><h2 id="4-字符串常用内置函数"><a href="#4-字符串常用内置函数" class="headerlink" title="4. 字符串常用内置函数"></a>4. 字符串常用内置函数</h2><table><thead><tr><th>函数</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td><code>len(x)</code></td><td>返回字符串长度（字符个数），也适用于其他序列&#x2F;集合。</td><td><code>len(&quot;Python&quot;)</code> → <code>6</code></td></tr><tr><td><code>str(x)</code></td><td>将任意类型 <code>x</code> 转换为字符串。</td><td><code>str(123)</code> → <code>&quot;123&quot;</code></td></tr><tr><td><code>chr(x)</code></td><td>返回 Unicode 编码 <code>x</code> 对应的单字符。</td><td><code>chr(65)</code> → <code>&#39;A&#39;</code></td></tr><tr><td><code>ord(x)</code></td><td>返回单字符 <code>x</code> 的 Unicode 编码（整数）。</td><td><code>ord(&#39;A&#39;)</code> → <code>65</code></td></tr><tr><td><code>hex(x)</code></td><td>返回整数 <code>x</code> 的十六进制小写形式字符串（带 <code>0x</code> 前缀）。</td><td><code>hex(255)</code> → <code>&#39;0xff&#39;</code></td></tr><tr><td><code>oct(x)</code></td><td>返回整数 <code>x</code> 的八进制小写形式字符串（带 <code>0o</code> 前缀）。</td><td><code>oct(8)</code> → <code>&#39;0o10&#39;</code></td></tr></tbody></table><blockquote><p><strong>注意</strong>：<code>chr()</code> 和 <code>ord()</code> 是互为逆操作；<code>hex()</code> 和 <code>oct()</code> 返回的字符串可用于 Python 代码中的进制表示。</p></blockquote><hr><h2 id="5-字符串常用处理方法"><a href="#5-字符串常用处理方法" class="headerlink" title="5. 字符串常用处理方法"></a>5. 字符串常用处理方法</h2><table><thead><tr><th>方法</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td><code>str.lower()</code></td><td>返回全部字符小写的副本。</td><td><code>&quot;AbC&quot;.lower()</code> → <code>&quot;abc&quot;</code></td></tr><tr><td><code>str.upper()</code></td><td>返回全部字符大写的副本。</td><td><code>&quot;AbC&quot;.upper()</code> → <code>&quot;ABC&quot;</code></td></tr><tr><td><code>str.split(sep=None)</code></td><td>按 <code>sep</code> 分割字符串，返回列表。省略 <code>sep</code> 时按空白（空格&#x2F;换行&#x2F;制表符）分割。</td><td><code>&quot;a,b,c&quot;.split(&quot;,&quot;)</code> → <code>[&#39;a&#39;,&#39;b&#39;,&#39;c&#39;]</code></td></tr><tr><td><code>str.count(sub)</code></td><td>返回子串 <code>sub</code> 在字符串中<strong>非重叠</strong>出现的次数。</td><td><code>&quot;aaa&quot;.count(&quot;aa&quot;)</code> → <code>1</code></td></tr><tr><td><code>str.replace(old, new)</code></td><td>将所有 <code>old</code> 替换为 <code>new</code>，返回新字符串。</td><td><code>&quot;hello&quot;.replace(&quot;l&quot;,&quot;x&quot;)</code> → <code>&quot;hexxo&quot;</code></td></tr><tr><td><code>str.center(width, fillchar)</code></td><td>将字符串居中，用 <code>fillchar</code>（默认为空格）填充至宽度 <code>width</code>。</td><td><code>&quot;hi&quot;.center(5,&quot;-&quot;)</code> → <code>&quot;--hi--&quot;</code></td></tr><tr><td><code>str.strip(chars)</code></td><td>移除字符串<strong>左右两端</strong>出现在 <code>chars</code> 中的字符（默认为空白字符）。</td><td><code>&quot;  abc  &quot;.strip()</code> → <code>&quot;abc&quot;</code>；<code>&quot;xxabcyy&quot;.strip(&quot;xy&quot;)</code> → <code>&quot;abc&quot;</code></td></tr><tr><td><code>str.join(iter)</code></td><td>用 <code>str</code> 作为分隔符，将可迭代对象 <code>iter</code> 中的字符串元素连接成一个新字符串。</td><td><code>&quot;-&quot;.join([&quot;a&quot;,&quot;b&quot;,&quot;c&quot;])</code> → <code>&quot;a-b-c&quot;</code></td></tr></tbody></table><blockquote><p><strong>易错点</strong>：</p><ul><li><code>strip()</code> 不会移除字符串内部的指定字符。</li><li><code>split()</code> 如果不指定分隔符，会连续多个空白视为一个分隔符；如果指定了具体字符，则按该字符逐个拆分（空串可能出现在结果中）。</li></ul></blockquote><hr><h2 id="6-format-方法的使用"><a href="#6-format-方法的使用" class="headerlink" title="6. format() 方法的使用"></a>6. <code>format()</code> 方法的使用</h2><ul><li>通过 <code>{}</code> 占位符进行字符串格式化。</li><li><strong>基本用法</strong>：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&#123;&#125;说今天天气很好&quot;</span>.<span class="built_in">format</span>(<span class="string">&quot;小李&quot;</span>)        <span class="comment"># 小李说今天天气很好</span></span><br><span class="line"><span class="string">&quot;&#123;0&#125;的年龄是&#123;1&#125;&quot;</span>.<span class="built_in">format</span>(<span class="string">&quot;小王&quot;</span>, <span class="number">18</span>)      <span class="comment"># 按位置索引</span></span><br><span class="line"><span class="string">&quot;&#123;name&#125;的年龄是&#123;age&#125;&quot;</span>.<span class="built_in">format</span>(name=<span class="string">&quot;小张&quot;</span>, age=<span class="number">20</span>)  <span class="comment"># 按关键字</span></span><br></pre></td></tr></table></figure></li><li><strong>格式化控制</strong>（二级常考）：<ul><li><code>&quot;{:.2f}&quot;.format(3.14159)</code> → <code>&quot;3.14&quot;</code>（保留两位小数）</li><li><code>&quot;{:&gt;10}&quot;.format(&quot;hi&quot;)</code> → <code>&quot;        hi&quot;</code>（右对齐，宽度10）</li><li><code>&quot;{:&lt;10}&quot;.format(&quot;hi&quot;)</code> → <code>&quot;hi        &quot;</code>（左对齐）</li><li><code>&quot;{:^10}&quot;.format(&quot;hi&quot;)</code> → <code>&quot;    hi    &quot;</code>（居中对齐）</li><li><code>&quot;{:b}&quot;.format(10)</code> → <code>&quot;1010&quot;</code>（二进制）</li><li><code>&quot;{:x}&quot;.format(255)</code> → <code>&quot;ff&quot;</code>（十六进制小写）</li></ul></li></ul><table><thead><tr><th align="left">格式组件</th><th align="left">说明</th><th align="left">可选值 &#x2F; 示例</th></tr></thead><tbody><tr><td align="left"><strong>引导符号</strong></td><td align="left">格式标记的起始</td><td align="left"><code>:</code> (冒号)</td></tr><tr><td align="left"><strong>&lt;填充&gt;</strong></td><td align="left">用于填充的单个字符</td><td align="left">任意字符，如 <code>*</code>, <code>0</code> 等</td></tr><tr><td align="left"><strong>&lt;对齐&gt;</strong></td><td align="left">对齐方式</td><td align="left"><code>&lt;</code> 左对齐，<code>&gt;</code> 右对齐，<code>^</code> 居中对齐</td></tr><tr><td align="left"><strong>&lt;宽度&gt;</strong></td><td align="left">槽设定的输出宽度</td><td align="left">整数，如 <code>10</code></td></tr><tr><td align="left"><strong>&lt;,&gt;</strong></td><td align="left">数字的千位分隔符</td><td align="left"><code>,</code></td></tr><tr><td align="left"><strong>&lt;.精度&gt;</strong></td><td align="left">浮点数小数精度 或 字符串最大输出长度</td><td align="left">如 <code>.2f</code> 中的 <code>2</code></td></tr><tr><td align="left"><strong>&lt;类型&gt;</strong></td><td align="left">输出类型</td><td align="left">整数：<code>b,c,d,o,x,X</code>；浮点：<code>e,E,f,%</code></td></tr></tbody></table><ul><li><strong>Python 3.6+ 支持 f-string</strong>（更简洁，但二级中 <code>format()</code> 仍是重点）：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">name = <span class="string">&quot;小李&quot;</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;name&#125;</span>说今天天气很好&quot;</span>)</span><br></pre></td></tr></table></figure></li></ul><hr><h2 id="7-组合数据类型"><a href="#7-组合数据类型" class="headerlink" title="7. 组合数据类型"></a>7. 组合数据类型</h2><h3 id="7-1-集合类型（Set）"><a href="#7-1-集合类型（Set）" class="headerlink" title="7.1 集合类型（Set）"></a>7.1 集合类型（Set）</h3><h4 id="7-1-1-特点"><a href="#7-1-1-特点" class="headerlink" title="7.1.1 特点"></a>7.1.1 特点</h4><ul><li><strong>无序</strong>：元素没有顺序，不能通过索引或切片访问。</li><li><strong>不重复</strong>：自动去重，元素唯一。</li><li><strong>元素不可变</strong>：只能包含不可变类型（数字、字符串、元组等），不能包含列表、字典等可变类型。</li><li>用 <code>{}</code> 创建集合（但 <code>{}</code> 是空字典，空集合必须用 <code>set()</code>）。</li></ul><h4 id="7-1-2-集合的运算（数学运算）"><a href="#7-1-2-集合的运算（数学运算）" class="headerlink" title="7.1.2 集合的运算（数学运算）"></a>7.1.2 集合的运算（数学运算）</h4><table><thead><tr><th>运算</th><th>符号</th><th>等价方法</th><th>说明</th></tr></thead><tbody><tr><td>并集</td><td><code>|</code></td><td><code>union()</code></td><td>两个集合的所有元素</td></tr><tr><td>交集</td><td><code>&amp;</code></td><td><code>intersection()</code></td><td>两个集合共有的元素</td></tr><tr><td>差集</td><td><code>-</code></td><td><code>difference()</code></td><td>在 A 中但不在 B 中的元素</td></tr><tr><td>对称差集</td><td><code>^</code></td><td><code>symmetric_difference()</code></td><td>仅在 A 或仅在 B 中的元素（并集减交集）</td></tr></tbody></table><p><strong>示例</strong>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">A = &#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;</span><br><span class="line">B = &#123;<span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;</span><br><span class="line"><span class="built_in">print</span>(A | B)   <span class="comment"># &#123;1, 2, 3, 4, 5&#125;</span></span><br><span class="line"><span class="built_in">print</span>(A &amp; B)   <span class="comment"># &#123;3&#125;</span></span><br><span class="line"><span class="built_in">print</span>(A - B)   <span class="comment"># &#123;1, 2&#125;</span></span><br><span class="line"><span class="built_in">print</span>(A ^ B)   <span class="comment"># &#123;1, 2, 4, 5&#125;</span></span><br></pre></td></tr></table></figure><h4 id="7-1-3-集合的常见方法"><a href="#7-1-3-集合的常见方法" class="headerlink" title="7.1.3 集合的常见方法"></a>7.1.3 集合的常见方法</h4><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>S.add(x)</code></td><td>添加元素 <code>x</code>（若已存在则无变化）。</td></tr><tr><td><code>S.discard(x)</code></td><td>移除元素 <code>x</code>，不存在时不报错（静默处理）。</td></tr><tr><td><code>S.remove(x)</code></td><td>移除元素 <code>x</code>，不存在时抛出 <code>KeyError</code>。</td></tr><tr><td><code>S.clear()</code></td><td>清空集合中所有元素。</td></tr><tr><td><code>S.pop()</code></td><td>随机弹出一个元素并返回；若集合为空，抛出 <code>KeyError</code>。</td></tr><tr><td><code>S.copy()</code></td><td>返回集合的浅拷贝副本。</td></tr><tr><td><code>len(S)</code></td><td>返回集合元素个数。</td></tr><tr><td><code>x in S</code> &#x2F; <code>x not in S</code></td><td>判断元素是否在集合中。</td></tr><tr><td><code>set(x)</code></td><td>将其他可迭代对象 <code>x</code> 转换为集合（自动去重）。</td></tr></tbody></table><h4 id="7-1-4-定义空集合"><a href="#7-1-4-定义空集合" class="headerlink" title="7.1.4 定义空集合"></a>7.1.4 定义空集合</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">s = <span class="built_in">set</span>()          <span class="comment"># 正确：空集合</span></span><br><span class="line">s = &#123;&#125;             <span class="comment"># 错误：这是空字典！</span></span><br></pre></td></tr></table></figure><hr><h3 id="7-2-序列类型（列表、元组）"><a href="#7-2-序列类型（列表、元组）" class="headerlink" title="7.2 序列类型（列表、元组）"></a>7.2 序列类型（列表、元组）</h3><h4 id="7-2-1-列表（List）"><a href="#7-2-1-列表（List）" class="headerlink" title="7.2.1 列表（List）"></a>7.2.1 列表（List）</h4><h5 id="7-2-1-1-特点"><a href="#7-2-1-1-特点" class="headerlink" title="7.2.1.1 特点"></a>7.2.1.1 特点</h5><ul><li><strong>有序</strong>：元素按插入顺序存储。</li><li><strong>可重复</strong>：允许重复元素。</li><li><strong>可变</strong>：可增删改元素。</li><li><strong>支持索引和切片</strong>：可通过下标访问和截取子列表。</li></ul><h5 id="7-2-1-2-列表常用的操作符与函数"><a href="#7-2-1-2-列表常用的操作符与函数" class="headerlink" title="7.2.1.2 列表常用的操作符与函数"></a>7.2.1.2 列表常用的操作符与函数</h5><table><thead><tr><th>操作符&#x2F;函数</th><th>说明</th></tr></thead><tbody><tr><td><code>x in s</code> &#x2F; <code>x not in s</code></td><td>判断元素是否在列表中。</td></tr><tr><td><code>len(s)</code></td><td>返回列表长度。</td></tr><tr><td><code>min(s)</code> &#x2F; <code>max(s)</code></td><td>返回列表中的最小&#x2F;最大元素（要求元素可比较）。</td></tr><tr><td><code>s1 + s2</code></td><td>列表拼接，返回新列表。</td></tr><tr><td><code>s * n</code></td><td>列表重复，返回新列表。</td></tr></tbody></table><h5 id="7-2-1-3-列表常用操作方法"><a href="#7-2-1-3-列表常用操作方法" class="headerlink" title="7.2.1.3 列表常用操作方法"></a>7.2.1.3 列表常用操作方法</h5><table><thead><tr><th>方法</th><th>说明</th><th>是否原地修改</th></tr></thead><tbody><tr><td><code>ls.append(x)</code></td><td>在末尾添加一个元素 <code>x</code>。</td><td>是</td></tr><tr><td><code>ls.insert(i, x)</code></td><td>在索引 <code>i</code> 处插入元素 <code>x</code>。</td><td>是</td></tr><tr><td><code>ls.clear()</code></td><td>清空所有元素。</td><td>是</td></tr><tr><td><code>ls.pop(i)</code></td><td>删除并返回索引 <code>i</code> 的元素（默认最后一个）。</td><td>是</td></tr><tr><td><code>ls.remove(x)</code></td><td>删除第一个值为 <code>x</code> 的元素（若不存在则报错）。</td><td>是</td></tr><tr><td><code>ls.reverse()</code></td><td>将列表元素反转（原地）。</td><td>是</td></tr><tr><td><code>ls.index(x)</code></td><td>返回第一个值为 <code>x</code> 的索引；若不存在，抛出 <code>ValueError</code>。</td><td>否（查询）</td></tr><tr><td><code>ls.count(x)</code></td><td>返回 <code>x</code> 在列表中出现的总次数。</td><td>否（查询）</td></tr><tr><td><code>ls.copy()</code></td><td>返回列表的浅拷贝副本。</td><td>否（返回新列表）</td></tr></tbody></table><h5 id="7-2-1-4-列表的-copy-方法示例"><a href="#7-2-1-4-列表的-copy-方法示例" class="headerlink" title="7.2.1.4 列表的 copy() 方法示例"></a>7.2.1.4 列表的 <code>copy()</code> 方法示例</h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">a = [<span class="number">34</span>, <span class="number">3434</span>, [<span class="number">1</span>, <span class="number">2</span>]]   <span class="comment"># 嵌套列表</span></span><br><span class="line">b = a.copy()             <span class="comment"># 浅拷贝：外层列表是新的，内层列表仍是引用</span></span><br><span class="line">b[<span class="number">2</span>].append(<span class="number">3</span>)</span><br><span class="line"><span class="built_in">print</span>(a)                 <span class="comment"># [34, 3434, [1, 2, 3]] —— 原列表被影响！</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>重要</strong>：浅拷贝只复制外层容器，内层可变对象仍共享同一引用。需要深拷贝可使用 <code>copy.deepcopy()</code>。</p></blockquote><hr><h4 id="7-2-2-元组（Tuple）"><a href="#7-2-2-元组（Tuple）" class="headerlink" title="7.2.2 元组（Tuple）"></a>7.2.2 元组（Tuple）</h4><h5 id="7-2-2-1-特点"><a href="#7-2-2-1-特点" class="headerlink" title="7.2.2.1 特点"></a>7.2.2.1 特点</h5><ul><li><strong>有序</strong>、<strong>可重复</strong>、<strong>支持索引和切片</strong>。</li><li><strong>不可变</strong>：一旦创建，不能增删改元素（但如果元素是可变对象，如列表，则该对象的内部状态可以改变）。</li><li>定义单元素元组时需加逗号：<code>t = (1,)</code>，否则会被视为普通括号。</li></ul><h5 id="7-2-2-2-元组常用的操作符与函数"><a href="#7-2-2-2-元组常用的操作符与函数" class="headerlink" title="7.2.2.2 元组常用的操作符与函数"></a>7.2.2.2 元组常用的操作符与函数</h5><table><thead><tr><th>操作符&#x2F;函数</th><th>说明</th></tr></thead><tbody><tr><td><code>x in s</code> &#x2F; <code>x not in s</code></td><td>判断元素是否在元组中。</td></tr><tr><td><code>len(s)</code></td><td>返回元组长度。</td></tr><tr><td><code>min(s)</code> &#x2F; <code>max(s)</code></td><td>返回最小&#x2F;最大元素。</td></tr><tr><td><code>t.index(x)</code></td><td>返回第一个值为 <code>x</code> 的索引；若不存在，抛出 <code>ValueError</code>。</td></tr><tr><td><code>t.count(x)</code></td><td>返回 <code>x</code> 在元组中出现的次数。</td></tr></tbody></table><blockquote><p><strong>与列表的核心区别</strong>：元组不可变，因此没有 <code>append</code>、<code>insert</code>、<code>pop</code>、<code>remove</code> 等修改方法。</p></blockquote><hr><h3 id="7-3-映射类型（字典，Dict）"><a href="#7-3-映射类型（字典，Dict）" class="headerlink" title="7.3 映射类型（字典，Dict）"></a>7.3 映射类型（字典，Dict）</h3><h4 id="7-3-1-特点"><a href="#7-3-1-特点" class="headerlink" title="7.3.1 特点"></a>7.3.1 特点</h4><ul><li><strong>键值对</strong>存储，键（key）唯一，值（value）可重复。</li><li><strong>键必须是不可变类型</strong>（字符串、数字、元组等），值可以是任意类型。</li><li><strong>无序</strong>（Python 3.7 起按插入顺序保留，但二级考试一般不强调有序性）。</li><li><strong>可变</strong>：可增删改键值对。</li></ul><h4 id="7-3-2-字典常用的函数"><a href="#7-3-2-字典常用的函数" class="headerlink" title="7.3.2 字典常用的函数"></a>7.3.2 字典常用的函数</h4><table><thead><tr><th>函数</th><th>说明</th></tr></thead><tbody><tr><td><code>len(d)</code></td><td>返回键值对个数。</td></tr><tr><td><code>min(d)</code> &#x2F; <code>max(d)</code></td><td>返回字典中<strong>键</strong>的最小&#x2F;最大值（按键比较）。</td></tr><tr><td><code>d[key]</code></td><td>通过键访问值；若键不存在，抛出 <code>KeyError</code>。</td></tr></tbody></table><h4 id="7-3-3-字典常用的操作方法"><a href="#7-3-3-字典常用的操作方法" class="headerlink" title="7.3.3 字典常用的操作方法"></a>7.3.3 字典常用的操作方法</h4><table><thead><tr><th>方法</th><th>说明</th><th>返回值</th></tr></thead><tbody><tr><td><code>d.keys()</code></td><td>返回所有键的视图对象（可迭代）。</td><td>视图</td></tr><tr><td><code>d.values()</code></td><td>返回所有值的视图对象（可迭代）。</td><td>视图</td></tr><tr><td><code>d.items()</code></td><td>返回所有键值对的视图对象（元素为元组 <code>(key, value)</code>）。</td><td>视图</td></tr><tr><td><code>d.get(key, default)</code></td><td>键存在返回对应值，否则返回 <code>default</code>（默认 <code>None</code>），<strong>不抛出异常</strong>。</td><td>值 或 default</td></tr><tr><td><code>d.pop(key, default)</code></td><td>键存在则删除并返回对应值；不存在返回 <code>default</code>（若未指定则抛出 <code>KeyError</code>）。</td><td>值 或 default</td></tr><tr><td><code>d.popitem()</code></td><td>弹出并返回一个键值对（元组形式），遵循后进先出（Python 3.7+ 按插入顺序逆序）。</td><td>(key, value)</td></tr><tr><td><code>d.clear()</code></td><td>清空字典所有键值对。</td><td><code>None</code></td></tr><tr><td><code>d.update(other)</code></td><td>用其他字典或键值对更新当前字典（若键已存在则覆盖值）。</td><td><code>None</code></td></tr><tr><td><code>d.setdefault(key, default)</code></td><td>若键存在返回对应值；若不存在则插入键并设为 <code>default</code>（默认为 <code>None</code>）。</td><td>值</td></tr></tbody></table><p><strong>示例</strong>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">d = &#123;<span class="string">&quot;name&quot;</span>: <span class="string">&quot;张三&quot;</span>, <span class="string">&quot;age&quot;</span>: <span class="number">20</span>&#125;</span><br><span class="line"><span class="built_in">print</span>(d.get(<span class="string">&quot;gender&quot;</span>, <span class="string">&quot;未知&quot;</span>))   <span class="comment"># 输出 &quot;未知&quot;</span></span><br><span class="line">d.update(&#123;<span class="string">&quot;age&quot;</span>: <span class="number">21</span>, <span class="string">&quot;city&quot;</span>: <span class="string">&quot;北京&quot;</span>&#125;)</span><br><span class="line"><span class="built_in">print</span>(d)   <span class="comment"># &#123;&#x27;name&#x27;: &#x27;张三&#x27;, &#x27;age&#x27;: 21, &#x27;city&#x27;: &#x27;北京&#x27;&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>易错点</strong>：<code>d[key]</code> 和 <code>d.get(key)</code> 的区别——前者键不存在时报错，后者返回 <code>None</code> 或指定默认值，更安全。</p></blockquote>]]>
    </content>
    <id>https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E5%9B%9B%EF%BC%89%EF%BC%9A%E7%BB%84%E5%90%88%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AD%97%E7%AC%A6%E4%B8%B2/</id>
    <link href="https://qixia.site/2026/07/21/Python%20%E4%BA%8C%E7%BA%A7%E5%A4%87%E8%80%83%E7%AC%94%E8%AE%B0%EF%BC%88%E5%9B%9B%EF%BC%89%EF%BC%9A%E7%BB%84%E5%90%88%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AD%97%E7%AC%A6%E4%B8%B2/"/>
    <published>2026-07-20T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="1-字符串操作符"><a href="#1-字符串操作符" class="headerlink" title="1. 字符串操作符"></a>1. 字符串操作符</h2><ul>
<li><strong><code>+</code> 拼接</strong>：连接两]]>
    </summary>
    <title>Python 二级备考笔记（四）：组合数据类型和字符串</title>
    <updated>2026-07-22T01:33:15.025Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="环境运维" scheme="https://qixia.site/categories/%E7%8E%AF%E5%A2%83%E8%BF%90%E7%BB%B4/"/>
    <category term="Clash" scheme="https://qixia.site/tags/Clash/"/>
    <category term="VMware" scheme="https://qixia.site/tags/VMware/"/>
    <content>
      <![CDATA[<blockquote><p><strong>适用场景</strong>：宿主机（Windows）已配置代理工具，VMware虚拟机（Ubuntu）能Ping通外网或显示已连接，但执行<code>git clone</code>长时间卡顿或报错<code>Failed to connect</code>。</p></blockquote><hr><h2 id="一、-故障现象与环境定性"><a href="#一、-故障现象与环境定性" class="headerlink" title="一、 故障现象与环境定性"></a>一、 故障现象与环境定性</h2><ul><li><strong>宿主环境</strong>：Windows 10&#x2F;11，代理软件（Clash&#x2F;V2Ray）监听端口<code>7897</code>。</li><li><strong>虚拟环境</strong>：Ubuntu 22.04&#x2F;24.04，网络模式为<strong>NAT模式</strong>。</li><li><strong>核心矛盾</strong>：虚拟机显示”已联网”，但访问GitHub等境外服务时无响应或超时。</li></ul><hr><h2 id="二、-核心排障逻辑（三板斧）"><a href="#二、-核心排障逻辑（三板斧）" class="headerlink" title="二、 核心排障逻辑（三板斧）"></a>二、 核心排障逻辑（三板斧）</h2><p>解决此类问题不能只看”有没有网”，而要看”流量能否正确进入代理管道”。</p><h3 id="1-网络基底校验：Ping不通不代表断网"><a href="#1-网络基底校验：Ping不通不代表断网" class="headerlink" title="1. 网络基底校验：Ping不通不代表断网"></a>1. 网络基底校验：Ping不通不代表断网</h3><p>很多开发者习惯先用<code>ping 192.168.118.1</code>测试物理机连通性。但在实战中，Windows防火墙默认屏蔽ICMP协议（回显请求），导致<code>100% packet loss</code>。<strong>这是正常现象</strong>，只要虚拟机网卡（NAT模式）获取了正确IP，底层链路即视为畅通。</p><h3 id="2-代理绑定校验：127-0-0-1-与-0-0-0-0-的陷阱"><a href="#2-代理绑定校验：127-0-0-1-与-0-0-0-0-的陷阱" class="headerlink" title="2. 代理绑定校验：127.0.0.1 与 0.0.0.0 的陷阱"></a>2. 代理绑定校验：127.0.0.1 与 0.0.0.0 的陷阱</h3><ul><li><strong>致命误区</strong>：宿主机代理软件默认只监听<code>127.0.0.1</code>（仅本机回环地址）。</li><li><strong>解决方案</strong>：虚拟机属于”局域网外部设备”，必须在代理软件设置中开启 <strong><code>Allow LAN</code>（允许局域网连接）</strong>，并确认监听地址变为<code>0.0.0.0</code>或具体的VMnet8网卡IP。</li></ul><h3 id="3-防火墙拦截校验：最容易被忽略的”隐形墙”"><a href="#3-防火墙拦截校验：最容易被忽略的”隐形墙”" class="headerlink" title="3. 防火墙拦截校验：最容易被忽略的”隐形墙”"></a>3. 防火墙拦截校验：最容易被忽略的”隐形墙”</h3><p>即便代理开启了局域网监听，Windows防火墙仍会拦截来自虚拟机的陌生TCP握手请求。<strong>必须</strong>在防火墙的”允许应用”列表中，将代理软件的”公用”与”专用”访问权限全部勾选。</p><hr><h2 id="三、-诊断工具链与关键指令"><a href="#三、-诊断工具链与关键指令" class="headerlink" title="三、 诊断工具链与关键指令"></a>三、 诊断工具链与关键指令</h2><h3 id="指令-1：在宿主机（Windows）验证端口绑定"><a href="#指令-1：在宿主机（Windows）验证端口绑定" class="headerlink" title="指令 1：在宿主机（Windows）验证端口绑定"></a>指令 1：在宿主机（Windows）验证端口绑定</h3><p>打开CMD，执行以下命令，确认端口监听地址：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">netstat -ano | findstr 7897</span><br></pre></td></tr></table></figure><ul><li><strong>特征值判断</strong>：<ul><li>若显示 <code>127.0.0.1:7897</code> —— <strong>绑定失败</strong>，需开启代理的LAN功能。</li><li>若显示 <code>0.0.0.0:7897</code> 或 <code>[::]</code> —— <strong>绑定成功</strong>，物理机侧端口已敞开。</li></ul></li></ul><h3 id="指令-2：在虚拟机（Ubuntu）使用Curl进行分层测试"><a href="#指令-2：在虚拟机（Ubuntu）使用Curl进行分层测试" class="headerlink" title="指令 2：在虚拟机（Ubuntu）使用Curl进行分层测试"></a>指令 2：在虚拟机（Ubuntu）使用Curl进行分层测试</h3><p><strong>不要一上来就Git Clone</strong>，使用<code>curl</code>带上<code>-x</code>代理参数进行探测，能更快暴露连接层问题。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设定5秒超时，快速判断连通性</span></span><br><span class="line">curl -x http://[宿主机IP]:[代理端口] https://www.google.com -m 5</span><br></pre></td></tr></table></figure><ul><li><strong>超时（Timeout）</strong>：说明物理机防火墙拦截或路由不可达。</li><li><strong>拒绝连接（Refused）</strong>：说明代理软件未在指定端口监听（未开启服务）。</li><li><strong>成功返回HTML</strong>：说明网络链路与防火墙已全通。</li></ul><hr><h2 id="四、-实战排障复盘（基于真实案例）"><a href="#四、-实战排障复盘（基于真实案例）" class="headerlink" title="四、 实战排障复盘（基于真实案例）"></a>四、 实战排障复盘（基于真实案例）</h2><table><thead><tr><th align="left">步骤</th><th align="left">操作&#x2F;命令</th><th align="left">反馈结果</th><th align="left">诊断结论</th></tr></thead><tbody><tr><td align="left"><strong>1</strong></td><td align="left"><code>ping 192.168.118.1 -c 4</code></td><td align="left"><code>100% packet loss</code></td><td align="left">❌ 误导性信息。ICMP被防火墙屏蔽，<strong>忽略此报错</strong>。</td></tr><tr><td align="left"><strong>2</strong></td><td align="left"><code>netstat -ano</code>（宿主机）</td><td align="left"><code>TCP 0.0.0.0:7897 LISTENING</code></td><td align="left">✅ 宿主机代理监听正常，排除代理软件设置问题。</td></tr><tr><td align="left"><strong>3</strong></td><td align="left"><code>curl -x ... -m 5</code></td><td align="left"><code>Failed to connect after 132959 ms</code></td><td align="left">❌ <strong>症结确认</strong>：流量被宿主机墙或路由阻断。</td></tr><tr><td align="left"><strong>4</strong></td><td align="left"><strong>操作</strong>：临时关闭防火墙</td><td align="left">-</td><td align="left">-</td></tr><tr><td align="left"><strong>5</strong></td><td align="left"><code>curl -x ... -m 5</code></td><td align="left"><code>Returns HTML Data</code></td><td align="left">✅ TCP握手成功，链路打通，确认为防火墙拦截。</td></tr><tr><td align="left"><strong>6</strong></td><td align="left"><strong>操作</strong>：重新开启防火墙 + 添加入站规则</td><td align="left">-</td><td align="left">-</td></tr><tr><td align="left"><strong>7</strong></td><td align="left"><code>curl -x ... -m 5</code></td><td align="left"><code>Returns HTML Data</code></td><td align="left">✅ <strong>永久方案生效</strong>，无需关闭防火墙即可稳定使用。</td></tr><tr><td align="left"><strong>8</strong></td><td align="left"><code>git clone ...</code></td><td align="left"><code>Cloning into ...</code></td><td align="left">✅ 业务正常，代理配置生效。</td></tr></tbody></table><hr><h2 id="五、-永久解决方案（推荐）：精细化的防火墙入站规则"><a href="#五、-永久解决方案（推荐）：精细化的防火墙入站规则" class="headerlink" title="五、 永久解决方案（推荐）：精细化的防火墙入站规则"></a>五、 永久解决方案（推荐）：精细化的防火墙入站规则</h2><p><strong>临时关闭防火墙</strong>虽然能快速验证问题，但会带来安全隐患。正确的做法是<strong>重新开启防火墙</strong>，然后<strong>单独为代理端口添加一条入站规则</strong>，只允许虚拟机访问该端口。</p><h3 id="操作步骤（Windows-宿主机）"><a href="#操作步骤（Windows-宿主机）" class="headerlink" title="操作步骤（Windows 宿主机）"></a>操作步骤（Windows 宿主机）</h3><h4 id="方式一：命令行添加（最快、最可靠）"><a href="#方式一：命令行添加（最快、最可靠）" class="headerlink" title="方式一：命令行添加（最快、最可靠）"></a>方式一：命令行添加（最快、最可靠）</h4><p>以<strong>管理员身份</strong>打开命令提示符（CMD），执行以下命令（<strong>注意将端口号替换为你实际使用的端口</strong>）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">netsh advfirewall firewall add rule name=<span class="string">&quot;Clash Proxy for VM&quot;</span> <span class="built_in">dir</span>=<span class="keyword">in</span> action=allow protocol=TCP localport=7897</span><br></pre></td></tr></table></figure><p><strong>参数说明</strong>：</p><table><thead><tr><th align="left">参数</th><th align="left">含义</th></tr></thead><tbody><tr><td align="left"><code>name</code></td><td align="left">规则名称（可自定义，方便识别）</td></tr><tr><td align="left"><code>dir=in</code></td><td align="left">入站规则（允许外部设备访问本机）</td></tr><tr><td align="left"><code>action=allow</code></td><td align="left">放行动作</td></tr><tr><td align="left"><code>protocol=TCP</code></td><td align="left">TCP协议（代理通常使用TCP）</td></tr><tr><td align="left"><code>localport=7897</code></td><td align="left">监听的端口号（<strong>务必与你代理软件的实际端口一致</strong>）</td></tr></tbody></table><h4 id="方式二：图形界面添加（适合不熟悉命令行的用户）"><a href="#方式二：图形界面添加（适合不熟悉命令行的用户）" class="headerlink" title="方式二：图形界面添加（适合不熟悉命令行的用户）"></a>方式二：图形界面添加（适合不熟悉命令行的用户）</h4><ol><li>打开 <strong>“Windows Defender 防火墙”</strong> → 点击左侧 <strong>“高级设置”</strong>。</li><li>点击 <strong>“入站规则”</strong> → 右侧点击 <strong>“新建规则…”</strong>。</li><li>规则类型选择 <strong>“端口”</strong> → 下一步。</li><li>协议选择 <strong>TCP</strong>，特定本地端口填写你的代理端口号（如 <code>7897</code>）→ 下一步。</li><li>操作选择 <strong>“允许连接”</strong> → 下一步。</li><li>配置文件勾选 <strong>“专用”</strong> 和 <strong>“公用”</strong>（确保所有网络环境生效）→ 下一步。</li><li>输入规则名称（如 <code>Clash Proxy for VM</code>）→ 完成。</li></ol><h3 id="验证规则已生效"><a href="#验证规则已生效" class="headerlink" title="验证规则已生效"></a>验证规则已生效</h3><ul><li>在”高级设置” → “入站规则”列表中，找到你刚刚创建的规则，确保其状态为 <strong>“已启用”</strong>。</li><li>在Ubuntu中再次执行 <code>curl -x http://[宿主机IP]:[端口] https://www.google.com -m 5</code>，确认依然能通。</li></ul><hr><h2 id="六、-最终配置方案（Git代理配置）"><a href="#六、-最终配置方案（Git代理配置）" class="headerlink" title="六、 最终配置方案（Git代理配置）"></a>六、 最终配置方案（Git代理配置）</h2><p>在Ubuntu终端执行以下命令，为Git单独配置代理（推荐，无需影响系统全局环境）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置代理（请替换IP和端口）</span></span><br><span class="line">git config --global http.proxy http://192.168.118.1:7897</span><br><span class="line">git config --global https.proxy http://192.168.118.1:7897</span><br><span class="line"></span><br><span class="line"><span class="comment"># 验证配置</span></span><br><span class="line">git config --global --get http.proxy</span><br><span class="line">git config --global --get https.proxy</span><br></pre></td></tr></table></figure><h3 id="收尾工作：防止”污染”内网"><a href="#收尾工作：防止”污染”内网" class="headerlink" title="收尾工作：防止”污染”内网"></a>收尾工作：防止”污染”内网</h3><p>在代码克隆完成或无需访问外网时，建议清除代理配置，以免影响公司内网GitLab或私有仓库访问：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config --global --<span class="built_in">unset</span> http.proxy</span><br><span class="line">git config --global --<span class="built_in">unset</span> https.proxy</span><br></pre></td></tr></table></figure><hr><h2 id="七、-避坑锦囊"><a href="#七、-避坑锦囊" class="headerlink" title="七、 避坑锦囊"></a>七、 避坑锦囊</h2><ol><li><p><strong>IP地址漂移</strong>：VMware的VMnet8虚拟网卡IP（如本例中的<code>192.168.118.1</code>）可能会随虚拟机重启或DHCP重分配而变化。若下次无法使用，请先在宿主机<code>ipconfig</code>中重新确认该IP。</p></li><li><p><strong>端口号一致性</strong>：添加入站规则时，务必确认端口号与代理软件实际监听的端口完全一致。若代理软件使用多个端口（如HTTP和SOCKS5分开），需分别添加。</p></li><li><p><strong>开启TUN模式（可选）</strong>：若使用Clash Meta等内核，可开启TUN模式让虚拟机流量直接由代理接管，无需配置Git代理环境变量，但需注意系统路由表冲突。</p></li><li><p><strong>大仓库克隆技巧</strong>：若仓库体积较大，建议使用 <code>git clone --depth=1</code>（浅克隆）仅拉取最新代码，避免因代理带宽限制导致长时间无响应。</p></li><li><p><strong>规则维护</strong>：若日后更换代理软件或修改端口号，建议先删除旧规则再添加新规则：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">netsh advfirewall firewall delete rule name=<span class="string">&quot;Clash Proxy for VM&quot;</span></span><br></pre></td></tr></table></figure></li></ol><hr><h2 id="八、-总结：排障心法"><a href="#八、-总结：排障心法" class="headerlink" title="八、 总结：排障心法"></a>八、 总结：排障心法</h2><p>虚拟网络排障的核心在于<strong>分层验证</strong>：</p><ul><li><strong>第1层（链路层）</strong>：虚拟机能否获取IP？NAT模式是否正常？</li><li><strong>第2层（传输层）</strong>：代理端口是否在<code>0.0.0.0</code>上监听？<code>curl</code>能否握手成功？</li><li><strong>第3层（应用层）</strong>：Git代理配置是否正确？DNS解析是否正常？</li></ul><p><strong>物理机Ping不通虚拟机不代表网络故障</strong>，代理连接超时的根源往往不在于虚拟机内部配置，而在于<strong>宿主机的防火墙策略</strong>与<strong>代理软件的监听绑定范围</strong>。</p><p>掌握<code>curl -x</code>测试法 + <code>netsh advfirewall</code>入站规则配置，能让你在面对类似问题时，从被动”开关防火墙”升级为<strong>精细化治理</strong>，一次配置，长期受益。</p><hr><p><strong>适用系统</strong>：Windows 10&#x2F;11 + VMware Workstation&#x2F;Player + Ubuntu 20.04+</p><hr>]]>
    </content>
    <id>https://qixia.site/2026/07/20/VMware%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%BB%A3%E7%90%86%E9%85%8D%E7%BD%AE%E4%B8%8E%E7%BD%91%E7%BB%9C%E6%8E%92%E9%9A%9C%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97%EF%BC%9A%E4%BB%8EGit%20Clone%E8%B6%85%E6%97%B6%E5%88%B0%E6%88%90%E5%8A%9F%E8%A7%A3%E5%86%B3/</id>
    <link href="https://qixia.site/2026/07/20/VMware%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%BB%A3%E7%90%86%E9%85%8D%E7%BD%AE%E4%B8%8E%E7%BD%91%E7%BB%9C%E6%8E%92%E9%9A%9C%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97%EF%BC%9A%E4%BB%8EGit%20Clone%E8%B6%85%E6%97%B6%E5%88%B0%E6%88%90%E5%8A%9F%E8%A7%A3%E5%86%B3/"/>
    <published>2026-07-19T16:00:00.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p><strong>适用场景</strong>：宿主机（Windows）已配置代理工具，VMware虚拟机（Ubuntu）能Ping通外网或显示已连接，但执行<code>git clone</code>长时间卡顿或报错<code>Failed to c]]>
    </summary>
    <title>VMware虚拟机代理配置与网络排障完全指南：从Git Clone超时到成功解决</title>
    <updated>2026-07-22T01:33:15.025Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="AI实践" scheme="https://qixia.site/categories/AI%E5%AE%9E%E8%B7%B5/"/>
    <category term="机器学习" scheme="https://qixia.site/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="机器学习、数据科学与神经网络：给初学者的概念辨析课"><a href="#机器学习、数据科学与神经网络：给初学者的概念辨析课" class="headerlink" title="机器学习、数据科学与神经网络：给初学者的概念辨析课"></a>机器学习、数据科学与神经网络：给初学者的概念辨析课</h1><p>当你初入人工智能的大门，迎面而来的三座大山往往是：<strong>机器学习、数据科学、神经网络</strong>。它们就像三位形影不离的朋友，却又各自独立，让不少初学者犯迷糊。</p><p>今天，我们不求大而全，只求用两个你身边的场景，把这几个概念的边界彻底厘清。</p><hr><h3 id="一、机器学习与数据科学：预测引擎与决策艺术"><a href="#一、机器学习与数据科学：预测引擎与决策艺术" class="headerlink" title="一、机器学习与数据科学：预测引擎与决策艺术"></a>一、机器学习与数据科学：预测引擎与决策艺术</h3><p><strong>先记住一句话：机器学习负责造矛，数据科学负责舞矛。</strong></p><p>我们用电商平台为例。你打开购物 App，它会根据你的浏览记录和购买历史，猜出你可能想买什么并推送给你。这个过程，可以切成两半来看。</p><ul><li><p><strong>机器学习（以监督式学习为例）</strong><br>它干的活儿就是：给你输入 A（用户行为数据），输出 B（未来三天购买某商品的可能性）。机器学习追求的是 <strong>“猜得准”</strong>——这个映射关系越准，预测引擎就越强大。它只关心一个具体的预测任务，而不关心这个预测结果到底要怎么用。</p></li><li><p><strong>数据科学</strong><br>它关注的则是一个完整的旅程：从“我们到底要解决什么业务问题”开始，到“需要收集哪些数据、怎么清洗、怎么分析”，再到“调用机器学习模型做出预测”，最后还要结合库存、用户疲劳度、商业策略等因素，给出一个<strong>可执行的决策</strong>——比如“向用户 A 推送带 8 折券的耳机，时间定在晚上 8 点”。数据科学不光要猜得准，更要 <strong>“做得对”</strong>，它从数据中提炼出能指导行动的见解。</p></li></ul><p>一个更形象的比喻：<strong>如果把从数据到最终利润的过程比作烹调一道菜，那么机器学习就是厨房里那口性能极佳的智能炒锅，它专门负责把原材料（数据）变成菜肴（预测）；而数据科学则是掌管厨房的主厨，他要决定买什么菜、切什么形状、搭配什么风味、菜品如何摆盘，以及最终如何向食客解释今天的创意。</strong> 炒锅解决的是“怎么把菜炒出来”，主厨解决的是“这道菜值不值得炒、要炒成什么味儿”。</p><hr><h3 id="二、神经网络与深度学习：A→B-映射的幕后工厂"><a href="#二、神经网络与深度学习：A→B-映射的幕后工厂" class="headerlink" title="二、神经网络与深度学习：A→B 映射的幕后工厂"></a>二、神经网络与深度学习：A→B 映射的幕后工厂</h3><p>我们已经知道，机器学习（监督式学习）的目标是学会 A→B 的映射。那这个映射是怎么学会的呢？<strong>答案是：通过神经网络和深度学习来学。</strong></p><p>假设你要买房，你向 AI 输入一套房子的特征：面积、房间数量、朝向、楼层、是否学区房……你希望它输出一个靠谱的估价。在模型内部，这个“估价”过程并不是人那样的直觉思考，而是一个静默的数学流水线。</p><ul><li><p><strong>神经网络</strong><br>你可以把它想象成一张布满水管的管网图，上面安装着成千上万个可调节的阀门（参数）。输入数据（房子的特征）是一股水流，流过层层阀门，被不断地放大、缩小、混合，最后从出口流出——也就是预估价。这张网，就叫神经网络。它的“结构”是设计好的，但阀门旋到什么程度，一开始是未知的。</p></li><li><p><strong>深度学习</strong><br>就是教会这张网<strong>自动拧阀门</strong>的方法。我们喂给网络大量真实的成交案例（房子特征 + 实际售价），网络会先胡乱估一个价，然后通过一种叫“反向传播”的计算，从输出端往回一层层检讨：是哪几个阀门拧得太紧，哪几个太松，导致估价偏高？接着，它会微微旋转这些阀门，让下一次估得更准。经过成千上万次这样的微调，阀门们逐渐固定到了一个绝佳的位置——输入一套新房子的特征，流出来的估价已经相当靠谱。这个训练多层神经网络的过程，就是<strong>深度学习</strong>。</p></li></ul><p><strong>请注意：这并不是“思考”。</strong> 它更像是用一个复杂的数学函数，把输入的数字层层变形，最终算出一个结果。人脑的运作伴随着意识、情感和跳跃的联想，而神经网络只是数字的流动与变换——它没有一丝一毫的“理解”，却能在模式识别的单一赛道上跑出惊人的速度。</p><hr><h3 id="三、两个常见的认知陷阱，现在帮你填平"><a href="#三、两个常见的认知陷阱，现在帮你填平" class="headerlink" title="三、两个常见的认知陷阱，现在帮你填平"></a>三、两个常见的认知陷阱，现在帮你填平</h3><p><strong>陷阱一：认为数据科学就是机器学习。</strong><br>错了。数据科学的历史远比机器学习悠久，它包含着统计学、领域知识、可视化、沟通等众多技能。机器学习只是数据科学工具箱里的一件利器。你完全可以在某个项目里不做任何预测模型，只通过描述性统计和可视化就获取重要的商业决策——这依然是数据科学，但并没用到机器学习。</p><p><strong>陷阱二：以为神经网络是在模仿人脑思考。</strong><br>更确切的说法是，它受到了生物神经元连接方式的启发。但今天的深度神经网络，与人脑的运作机制早已分道扬镳。把它叫做“神经网络”，更多是一种早期的命名惯性，而非功能复刻。</p><hr><h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><p>读到这儿，你应该已经可以把这三个概念稳稳地摆正位置了：</p><ul><li>你想从数据里找到驱动商业决策的真知灼见？那是 <strong>数据科学</strong> 的活儿。</li><li>你想让机器接手一个具体的预测任务（比如判断垃圾邮件、估价）？你需要 <strong>机器学习</strong>。</li><li>你想知道机器如何从一个复杂任务里自己学出规律，尤其是处理图像、语音、文本时？那个背后的功臣就是 <strong>深度学习 + 神经网络</strong>。</li></ul><p>心中有这张地图，再踏入吴恩达的课堂，或者翻开任何一本 AI 教材，相信你都能清晰地感知到自己正走在哪一块版图上。一路向深，风光渐阔，祝你旅途愉快。</p><hr><p><strong>优化说明</strong>：这篇博客完全基于你笔记中的两个例子（电商、买房）展开，但重新拆解了逻辑，用“矛与剑”和“水管阀门”等比喻替代了模糊的“思考”说法，并把“数据科学”与“机器学习”从并列关系调整为“整体与工具”的关系。对于本文的任何细节，如果你觉得某个比喻还不够到位，或者想再挖一个维度，我们可以随时继续。</p>]]>
    </content>
    <id>https://qixia.site/2026/06/27/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E3%80%81%E6%95%B0%E6%8D%AE%E7%A7%91%E5%AD%A6%E4%B8%8E%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%EF%BC%9A%E7%BB%99%E5%88%9D%E5%AD%A6%E8%80%85%E7%9A%84%E6%A6%82%E5%BF%B5%E8%BE%A8%E6%9E%90%E8%AF%BE/</id>
    <link href="https://qixia.site/2026/06/27/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E3%80%81%E6%95%B0%E6%8D%AE%E7%A7%91%E5%AD%A6%E4%B8%8E%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%EF%BC%9A%E7%BB%99%E5%88%9D%E5%AD%A6%E8%80%85%E7%9A%84%E6%A6%82%E5%BF%B5%E8%BE%A8%E6%9E%90%E8%AF%BE/"/>
    <published>2026-06-26T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="机器学习、数据科学与神经网络：给初学者的概念辨析课"><a href="#机器学习、数据科学与神经网络：给初学者的概念辨析课" class="headerlink" title="机器学习、数据科学与神经网络：给初学者的概念辨析课"></a>机器学习、数据科学与神]]>
    </summary>
    <title>机器学习、数据科学与神经网络：给初学者的概念辨析课</title>
    <updated>2026-07-22T01:33:15.025Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="计算机基础" scheme="https://qixia.site/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/"/>
    <category term="操作系统" scheme="https://qixia.site/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <content>
      <![CDATA[<h3 id="📍-1-引言：我的懵逼时刻"><a href="#📍-1-引言：我的懵逼时刻" class="headerlink" title="📍 1. 引言：我的懵逼时刻"></a>📍 1. 引言：我的懵逼时刻</h3><p>事情要从最近做的一道题目说起，做这个题目的时候，我的状态大部分时候是处于蒙圈的，因为这个题目很绕，题目中的x29, x30, x19, sp等的符号真的理不清。题目代码如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">//C code in mstore.c</span><br><span class="line">long mult2(long, long);</span><br><span class="line">void multstore(long x,long y, long *dest)</span><br><span class="line">&#123;</span><br><span class="line">    long t = mult2(x, y);</span><br><span class="line">    *dest = t;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">//Assembly file in mstore.s</span><br><span class="line">multstore:</span><br><span class="line">    stp     x29, x30, [sp, -32]!</span><br><span class="line">    mov     x29, sp</span><br><span class="line">    str     x19, [sp, 16]</span><br><span class="line">    mov     x19, x2</span><br><span class="line">    bl      mult2</span><br><span class="line">    str     x0, [x19]</span><br><span class="line">    ldr     x19, [sp, 16]</span><br><span class="line">    ldp     x29, x30, [sp], 32</span><br><span class="line">    ret</span><br><span class="line"></span><br><span class="line">//Binary code in mstore</span><br><span class="line">fd 7b be a9 fd 03 00 91 f3</span><br><span class="line">0b 00 f9 f3 03 02 aa f0 ff</span><br><span class="line">ff 97 60 02 00 f9 f3</span><br></pre></td></tr></table></figure><p>实话说，我没有什么汇编语言的基础，当时学习机组的时候，没怎么学，这下好了在操作系统，又来整我了。后来通过AI的辅助，这里AI也没给我讲清楚，但是也给我一点启发，也就有这篇文章了，希望对你有用。</p><h3 id="📍-2-核心武器：我的“三号理论”（物理坐标法）"><a href="#📍-2-核心武器：我的“三号理论”（物理坐标法）" class="headerlink" title="📍 2. 核心武器：我的“三号理论”（物理坐标法）"></a>📍 2. 核心武器：我的“三号理论”（物理坐标法）</h3><p>我们现在简单讲一下这段代码的主要功能：实现实数X和Y的乘积，返回到*dest指针。<br>我们现在直入主题，逐行代码的讲解三号理论：<br><img src="/img/OS1/p2.png" alt="图解"><br>这张图里有三个位置和一个上级调用者，就是我们的三号理论的基地<br>其中一号是我们的老家，即栈空间，二号是实现乘法功能的位置，三号是乘法结果最终的位置<br><strong>第1行：<code>stp x29, x30, [sp, -32]!</code></strong></p><ul><li><strong>硬件动作</strong>：栈指针 <code>sp</code> 减去32，空出空间。将寄存器 <code>x29</code>、<code>x30</code> 的旧值保存到这段新空出的栈内存中。</li><li><strong>三号对应</strong>：<strong>行动发生在 1号位的栈空间内</strong>。这是为了服务本函数所建立的“保护现场”，把上级的底盘和回家的地图锁进本地抽屉。</li></ul><p><strong>第2行：<code>mov x29, sp</code></strong></p><ul><li><strong>硬件动作</strong>：将 <code>sp</code> 当前的值复制给 <code>x29</code>。</li><li><strong>三号对应</strong>：<strong>行动发生在 1号位（寄存器到寄存器）</strong>。为 1号位的栈空间插下一根绝对位置不变的“标杆（帧指针）”，后续无论是存数据还是取数据，都以 <code>x29</code> 为基准定位。</li></ul><p><strong>第3行：<code>str x19, [sp, 16]</code></strong></p><ul><li><strong>硬件动作</strong>：将寄存器 <code>x19</code> 当前的旧值，保存到 1号位 栈空间偏移16字节的位置。</li><li><strong>三号对应</strong>：<strong>行动发生在 1号位的栈空间内</strong>。因为即将要借用 <code>x19</code> 这个口袋，所以必须先把上一级留给 <code>x19</code> 的数据存起来，这叫“保护被调用者保存寄存器”。</li></ul><p><strong>第4行：<code>mov x19, x2</code></strong></p><ul><li><strong>硬件动作</strong>：将寄存器 <code>x2</code> 的值（目标地址 <code>dest</code>）复制给寄存器 <code>x19</code>。</li><li><strong>三号对应</strong>：<strong>行动发生在 1号位（寄存器到寄存器）</strong>。此刻 <code>x19</code> 里装的不再是旧数据，而是 <strong>3号位（数据最终落脚点）的物理内存地址</strong>。</li></ul><p><strong>第5行：<code>bl mult2</code></strong></p><ul><li><strong>硬件动作</strong>：先将下一条指令的地址写入 <code>x30</code> 寄存器，然后将 PC（程序计数器）指针强制修改为 <code>mult2</code> 的起始地址。</li><li><strong>三号对应</strong>：<strong>控制流离开 1号位，物理跳转到 2号位的代码区</strong>。CPU 在 2号位 执行乘法，算完后将结果放入 <code>x0</code>。因为记下了 <code>x30</code> 的地址，CPU 会自动<strong>跳回 1号位 的下一行</strong>。</li></ul><p><strong>第6行：<code>str x0, [x19]</code></strong></p><ul><li><strong>硬件动作</strong>：将寄存器 <code>x0</code> 的值（乘法结果），写入到内存地址 <code>[x19]</code> 指向的位置。</li><li><strong>三号对应（重点纠正过去混淆的地方）</strong>：<strong>指令在 1号位的代码区执行，但通过数据总线把结果送进了 3号位。</strong> CPU 根本没有“走到” 3号位。它只是站在 1号位，发出了一个“写内存”的物理信号，把数据直接搬运到了 <code>x19</code> 对应的内存格子里。<strong>（C语言 <code>*dest = t;</code> 此刻在物理上落地）。</strong></li></ul><p><strong>第7行：<code>ldr x19, [sp, 16]</code></strong></p><ul><li><strong>硬件动作</strong>：从 1号位栈空间偏移16的位置，把数据取回到寄存器 <code>x19</code>。</li><li><strong>三号对应</strong>：<strong>行动发生在 1号位的栈空间</strong>。送货工作已完成，把第3步存进去的“上一级旧数据”从栈里拿回来，还给 <code>x19</code> 寄存器（原物奉还）。</li></ul><p><strong>第8行：<code>ldp x29, x30, [sp], 32</code></strong></p><ul><li><strong>硬件动作</strong>：从栈顶弹出最开始保存的 <code>x29</code> 和 <code>x30</code> 原值，并将栈指针 <code>sp</code> 加回32（销毁刚刚开辟的本地栈空间）。</li><li><strong>三号对应</strong>：<strong>行动发生在 1号位的栈空间</strong>。把锁在 1号位抽屉里的地图和基准取回来，并将 1号位这个临时搭建的“工作台”彻底撤销。</li></ul><p><strong>第9行：<code>ret</code></strong></p><ul><li><strong>硬件动作</strong>：将寄存器 <code>x30</code> 里的“返回地址”赋值给 PC 指针。</li><li><strong>三号对应</strong>：<strong>彻底离开 1号位的代码区，回到上级调用者</strong>。程序控制权被完整地交还给最初调用 <code>multstore</code> 的地方。</li></ul><h3 id="📍-3-宏观动态推演（地图实战）"><a href="#📍-3-宏观动态推演（地图实战）" class="headerlink" title="📍 3. 宏观动态推演（地图实战）"></a>📍 3. 宏观动态推演（地图实战）</h3><p>结合三号理论和9行汇编指令，我们可以把这套繁琐的物理动作，归纳为<strong>五个严密的执行阶段</strong>，全程都在我们的“三号地图”上跑：</p><ul><li><p>🟢 阶段一：铺摊子（对应第1-2行）<br>在内存栈里划出 32 字节的专属空间。把上一级边界（x29）和最终回家路线（x30）锁进本地栈空间。立下 x29 这根固定标杆，划定1号位的活动范围。</p></li><li><p>🔵 阶段二：保护数据，预存地址（对应第3-4行）<br>为了防止 x19 里的旧数据丢失，先把它存进栈里。腾出口袋后，把3号位的终极地址抄进 x19 中。</p></li><li><p>🔴 阶段三：调兵算数（对应第5行）<br>通过 bl 指令，PC 指针离开 1号位，跳转到 2号位。mult2 算完结果放回 x0，依靠 x30 的标记，CPU 无缝跳回 1号位。</p></li><li><p>🟣 阶段四：终极执行（对应第6行）<br>CPU 仍在 1号位，发出写内存信号。借助 x19 里的目标地址，将 x0 里的乘法结果通过数据总线，物理搬运进 3号位的格子（*dest &#x3D; t;）。</p></li><li><p>🔴 阶段五：扫尾回家（对应第7-9行）<br>取回栈里的旧数据还给 x19，取出 x29 和 x30，撤销 1号位的操作台。最后执行 ret，彻底退出 1号位，回到上级调用者。</p></li></ul><h3 id="📍-4-醍醐灌顶：为什么代码会这么“绕”？"><a href="#📍-4-醍醐灌顶：为什么代码会这么“绕”？" class="headerlink" title="📍 4. 醍醐灌顶：为什么代码会这么“绕”？"></a>📍 4. 醍醐灌顶：为什么代码会这么“绕”？</h3><p>看完上面的推演，你可能会觉得：“为了算个乘法和存个结果，做了这么多杂七杂八的安保动作，这也太麻烦了吧！”</p><p><strong>核心事实在于：</strong><br>上面这 9 行汇编代码里，<strong>真正在完成 C 语言核心任务（算乘法和存结果）的，只有 2 行（<code>bl mult2</code> 和 <code>str x0, [x19]</code>）。</strong> 剩下的 7 行，全都是为了保障“函数调用机制”能够稳定运行而被迫执行的<strong>安保工序</strong>。</p><p>这背后的底层规矩，叫做 <strong>“ARM AAPCS64 函数调用约定”</strong>。</p><ul><li><p><strong>为什么死磕 <code>x19</code> 的安保？</strong><br>在 ARM 64 位架构里，寄存器被明确分成了两类。<code>x0</code> 到 <code>x7</code> 是“<strong>临时工</strong>”，谁调用下一个函数，下一个函数都可以随便覆盖它们，你不用对它们的原始数据负责。但是，<strong><code>x19</code> 到 <code>x28</code> 是“专属特等座”</strong>，遵循**“谁借用，谁恢复”**的铁律。只要你在函数里使用了这些寄存器（比如 <code>x19</code>），你就必须保证这个函数结束后，<code>x19</code> 的值和刚进来时一模一样。这就是为什么必须要有 <code>str x19</code>（存旧）和 <code>ldr x19</code>（取旧）这两个来回倒腾的动作。</p></li><li><p><strong>为什么需要 <code>x29</code> 和 <code>x30</code> 的复杂配合？</strong></p><ul><li><strong><code>x30</code>（链接寄存器）</strong>：解决了“<strong>控制流去哪</strong>”的问题。跳去 2号位 之前记下 1号位 的地址，算完能无缝回来。</li><li><strong><code>x29</code>（帧指针）</strong>：解决了“<strong>本地空间从哪开始</strong>”的问题。因为 <code>sp</code> 在函数执行中会频繁上下移动，有了 <code>x29</code> 这根“标杆”，无论 <code>sp</code> 怎么跑，程序永远能找到 1号位栈空间里存放的那些安保数据。</li></ul></li></ul><hr><h3 id="📍-5-总结：一张图看懂冯·诺依曼的执行"><a href="#📍-5-总结：一张图看懂冯·诺依曼的执行" class="headerlink" title="📍 5. 总结：一张图看懂冯·诺依曼的执行"></a>📍 5. 总结：一张图看懂冯·诺依曼的执行</h3><p>你的“三号理论”不仅成功破解了这段汇编代码，它其实刚好完美对应了经典<strong>冯·诺依曼计算机体系结构</strong>的物理执行本质：</p><ol><li><strong>取指与译码（控制单元）</strong>：CPU 根据 PC 寄存器，从内存（1号位代码区）不断取出 9 行汇编指令并翻译。</li><li><strong>执行阶段（运算与访存）</strong>：通过 ALU（运算器）在 1号位 寄存器和栈之间完成数据保护、寄存器的变更，然后<strong>控制流</strong>去 2号位 算乘法，最后通过<strong>数据总线</strong>把结果物理搬运进 3号位的最终内存格子里。</li><li><strong>写回与跳转</strong>：计算完毕，把保护现场阶段所有锁进栈的旧数据写回寄存器，释放栈空间，PC 指针根据 <code>x30</code> 跳回上级调用者。<br><img src="/img/OS1/p3.png" alt="图解"></li></ol><h3 id="😊6-最后的话"><a href="#😊6-最后的话" class="headerlink" title="😊6.最后的话"></a>😊6.最后的话</h3><p>看到这里，你有看懂我的三号理论吗？我觉得应该是比较清晰的吧。在我看来，所谓的函数调用就是把数据搬来搬去和寄存器保护，只要了解数据的搬运过程和寄存器保护，就不太需要去看那些枯燥乏味的汇编语言了，最后希望我的三号理论能帮你更好理解底层的函数调用。</p>]]>
    </content>
    <id>https://qixia.site/2026/06/24/%E4%B8%89%E5%8F%B7%E7%90%86%E8%AE%BA%E7%90%86%E8%A7%A3%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8/</id>
    <link href="https://qixia.site/2026/06/24/%E4%B8%89%E5%8F%B7%E7%90%86%E8%AE%BA%E7%90%86%E8%A7%A3%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8/"/>
    <published>2026-06-23T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h3 id="📍-1-引言：我的懵逼时刻"><a href="#📍-1-引言：我的懵逼时刻" class="headerlink" title="📍 1. 引言：我的懵逼时刻"></a>📍 1. 引言：我的懵逼时刻</h3><p>事情要从最近做的一道题目说起，做这个题目]]>
    </summary>
    <title>零基础硬核：用“三号理论”彻底看懂ARM汇编里的寄存器保护</title>
    <updated>2026-07-22T01:33:15.025Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="AI实践" scheme="https://qixia.site/categories/AI%E5%AE%9E%E8%B7%B5/"/>
    <category term="机器学习" scheme="https://qixia.site/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h1 id="机器学习初探：从生活中读懂监督式学习"><a href="#机器学习初探：从生活中读懂监督式学习" class="headerlink" title="机器学习初探：从生活中读懂监督式学习"></a>机器学习初探：从生活中读懂监督式学习</h1><p><strong>引言：三个场景，一个共同的秘密</strong></p><p>你有没有想过，为什么手机能自动识别骚扰短信，翻译软件能把英文一键转成中文，语音助手又能把你说的话变成精准的文字？这些我们早已习以为常的便利，背后其实藏着同一个秘密——<strong>监督式学习</strong>。<br>它就像是当代人工智能领域里最基础、也最健壮的一块积木。作为一个同样从零开始的探索者，我想用最简单的话，把这扇门慢慢推开。</p><hr><h3 id="一、什么是监督式学习？A-到-B-的映射"><a href="#一、什么是监督式学习？A-到-B-的映射" class="headerlink" title="一、什么是监督式学习？A 到 B 的映射"></a>一、什么是监督式学习？A 到 B 的映射</h3><p>想象一下你要训练一个新来的实习生帮你做垃圾分类。你手把手地教他：把“废纸”的照片递给他，然后告诉他这张要放进“可回收”桶；把“果皮”的照片给他，告诉他这张要放进“厨余”桶。你给的一对对“照片-答案”，就是<strong>数据</strong>。<br>实习生反复看这些例子，自己的大脑里逐渐建立起一种判断规则，不用你再开口，他就能直接根据照片把垃圾扔对。这个过程，在机器学习的世界里就叫<strong>监督式学习</strong>。</p><p>更抽象一点说，监督式学习要完成的任务就是<strong>学习一个从输入 A 到输出 B 的映射</strong>。我们给模型（那个实习生的数字大脑）大量配对好的例子：如果输入是一封垃圾邮件，就期望它输出“1”（拦截）；如果是一封正常邮件，就输出“0”（放行）。模型自己从这些例子里找规律，等以后遇到它从没见过的新邮件时，也能猜出正确答案。</p><p><strong>数据：机器的唯一“课本”</strong><br>这个实习生之所以能学会，全靠你给的那些“带答案的照片”。如果没有足够的例子，或者你给的答案是错的，他会怎样？他要么根本学不会，要么会学出一套错误百出的判断逻辑。所以，<strong>数据就是监督式学习的燃料</strong>，没有数据，再精巧的算法也无法启动。</p><hr><h3 id="二、让-AI-更聪明：不只是“多多益善”"><a href="#二、让-AI-更聪明：不只是“多多益善”" class="headerlink" title="二、让 AI 更聪明：不只是“多多益善”"></a>二、让 AI 更聪明：不只是“多多益善”</h3><p>你可能会很自然地认为：那是不是我拼命地堆数据，我的 AI 就一定会越来越强？就像练习册刷得越多，成绩就越好一样？</p><p>起初，整个行业都抱着这样的信念。人们画出过一张经典的曲线图：横轴是数据量，纵轴是模型的性能。曲线在很长一段里都昂首向上，似乎在呐喊“数据越多，性能就越强”。一些大公司之所以能迅速转型为 AI 巨头，正是因为它们在过去的业务中积累起了庞大且看似有益的数据集。</p><p>然而，随着时间推移，我们踩平了另一个同样重要的道理：<strong>比起一味求“多”，数据质量才是那个决定上限的“天花板”</strong>。</p><ul><li><strong>垃圾数据的陷阱</strong>：如果你给实习生的答案里，有三分之一都把“玻璃瓶”错标成了“有害垃圾”，他就会变得犹豫不决，甚至学到一个完全错误的规则。在 AI 中，这种充满了错误标签、无意义信息或有偏见样本的数据，被称为“垃圾数据”。你喂给它再多这样的“脏饲料”，也养不出健康的模型，反而会让它把噪音和偏见当成真理死死记住。</li><li><strong>过犹不及的智慧</strong>：真正高效的学习，往往来自于一份<strong>规模适中但清洗得极其干净、覆盖了各种核心情况的数据集</strong>。用这样的优质数据训练出来的模型，反而比那些用千倍万倍杂乱数据喂出来的模型更可靠、更敏捷。这就像吃一碗营养均衡的家常饭，远胜于对着一整座堆满过期食品的仓库狼吞虎咽。</li></ul><p>所以，提高 AI 性能的秘诀其实就拧在两根轴上：<strong>一是不断累积高质量、有代表性的数据；二是用这些纯净的燃料进行缜密、反复的训练。</strong> 两者必须同时在线，任何一条腿短了，都走不远。</p><hr><h3 id="三、为什么监督式学习如此重要？"><a href="#三、为什么监督式学习如此重要？" class="headerlink" title="三、为什么监督式学习如此重要？"></a>三、为什么监督式学习如此重要？</h3><p>答案很朴素：<strong>因为它最直观、最可测，离我们日常的问题最近。</strong></p><p>今天你看到的大多数落地 AI 应用——从医疗影像里揪出病灶，到工厂产线上检测产品瑕疵，再到推荐系统猜你下一只想买的袜子——背后的核心引擎，几乎都是监督式学习。它最大的优势在于，我们可以清楚地定义“什么是对的”，然后用对错分明的例子来引导模型。</p><p>这同时也解释了为何一些掌握独特数据的公司能够一骑绝尘。当一个模型在某个领域（比如医学影像）被喂进了数万张由顶级专家精心标注的影像资料时，它就构建起了一条别人难以复制的认知护城河。这场 AI 浪潮的本质较量，不仅仅在“谁算法更花哨”上，更在于“谁拥有那个领域最干净、最有益的数据库”。</p><p>当然，这只是起点。等你心里这颗“监督式学习”的种子扎稳之后，你自然会看到机器学习还有更辽阔的风景：比如，不需要标准答案、自己就能在数据里找到隐藏结构的<strong>无监督学习</strong>；或者通过不断试错、与环境互搏来成长的<strong>强化学习</strong>。但那些，都是我们站稳脚跟之后再昂首探索的下一站。</p><hr><h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><p>作为初学者，我们未来的路还很长，但这份最初的理解无比珍贵。它让我们看到，AI 并非什么莫测的魔法，而是人类用数据作为语言，教会一个空白大脑去推断规则的系统工程。</p><p>你现在已经比大多数人更懂那个藏在你手机里、每天都在为你服务的智能小精灵了。保管好这份洞察，未来每一次技术的发展，都将变成你手里可以拆解、可以质疑、也可以驾驭的玩具。</p><hr>]]>
    </content>
    <id>https://qixia.site/2026/06/24/%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%EF%BC%9F/</id>
    <link href="https://qixia.site/2026/06/24/%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%EF%BC%9F/"/>
    <published>2026-06-23T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="机器学习初探：从生活中读懂监督式学习"><a href="#机器学习初探：从生活中读懂监督式学习" class="headerlink" title="机器学习初探：从生活中读懂监督式学习"></a>机器学习初探：从生活中读懂监督式学习</h1><p><stron]]>
    </summary>
    <title>机器学习初探：从生活中读懂监督式学习</title>
    <updated>2026-07-22T01:33:15.025Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="环境运维" scheme="https://qixia.site/categories/%E7%8E%AF%E5%A2%83%E8%BF%90%E7%BB%B4/"/>
    <category term="Linux" scheme="https://qixia.site/tags/Linux/"/>
    <content>
      <![CDATA[<h2 id="一、Linux是什么？"><a href="#一、Linux是什么？" class="headerlink" title="一、Linux是什么？"></a>一、Linux是什么？</h2><p>Linux是一种<strong>开源、免费使用</strong>的类Unix操作系统，其内核由芬兰大学生**林纳斯·托瓦兹（Linus Torvalds）**于1991年首次发布。与Windows或macOS不同，Linux并非由单一公司拥有，而是由全球开源社区共同维护和发展。</p><h3 id="Linux系统的层次结构"><a href="#Linux系统的层次结构" class="headerlink" title="Linux系统的层次结构"></a>Linux系统的层次结构</h3><p>Linux系统由以下四个层次组成，自底向上依次为：</p><table><thead><tr><th>层次</th><th>说明</th></tr></thead><tbody><tr><td><strong>内核（Kernel）</strong></td><td>系统的核心和基础，负责管理硬件设备、内存、进程调度等底层服务</td></tr><tr><td><strong>系统库（System Libraries）</strong></td><td>为应用程序提供调用接口的软件库，如glibc</td></tr><tr><td><strong>Shell</strong></td><td>用户与操作系统交互的接口，常见的有Bash、Zsh等</td></tr><tr><td><strong>应用程序（Applications）</strong></td><td>各类软件工具和服务程序，如文本编辑器、Web服务器等</td></tr></tbody></table><h2 id=""><a href="#" class="headerlink" title=""></a><img src="/img/Linux1/p1.png" alt="图解"></h2><h2 id="二、为什么选择Ubuntu作为入门发行版？"><a href="#二、为什么选择Ubuntu作为入门发行版？" class="headerlink" title="二、为什么选择Ubuntu作为入门发行版？"></a>二、为什么选择Ubuntu作为入门发行版？</h2><p>Linux拥有众多发行版（Distribution），如Ubuntu、CentOS、Debian、Fedora、Arch等。本教程选择<strong>Ubuntu</strong>的原因：</p><ul><li><strong>易用性高</strong>：图形界面友好，社区活跃，适合初学者</li><li><strong>文档丰富</strong>：遇到问题时容易找到解决方案</li><li><strong>基础命令通用</strong>：虽然不同发行版有各自特色，但都基于同一Linux内核，<strong>基础命令和操作完全一致</strong></li></ul><table><thead><tr><th>排名</th><th>发行版</th><th>开发者采用率（个人开发环境）</th><th>服务器市场份额</th><th>主要特点</th></tr></thead><tbody><tr><td>1</td><td><strong>Ubuntu</strong></td><td>27.8%</td><td>13.7%</td><td>用户基数最大，生态完善，社区活跃，适合新手入门</td></tr><tr><td>2</td><td><strong>Debian</strong></td><td>11.4%</td><td>7.0%</td><td>以稳定性和自由软件原则著称，是 Ubuntu 的上游基础</td></tr><tr><td>3</td><td><strong>Fedora</strong></td><td>7.7%</td><td>—</td><td>Red Hat 上游发行版，集成最新技术和内核，适合开发者尝鲜</td></tr><tr><td>4</td><td><strong>Arch Linux</strong></td><td>6.4%</td><td>—</td><td>高度可定制，滚动更新，深受极客和高级用户喜爱</td></tr><tr><td>5</td><td><strong>Linux Mint</strong></td><td>3.9%</td><td>—</td><td>基于 Ubuntu，Cinnamon 桌面环境对 Windows 用户友好</td></tr><tr><td>6</td><td><strong>openSUSE</strong></td><td>—</td><td>—</td><td>企业级稳定性，提供 Leap（稳定）和 Tumbleweed（滚动）两种版本</td></tr><tr><td>7</td><td><strong>Manjaro</strong></td><td>—</td><td>—</td><td>Arch 的易用化衍生版，简化安装和配置流程</td></tr><tr><td>8</td><td><strong>Pop!_OS</strong></td><td>—</td><td>—</td><td>System76 开发，优化硬件兼容，适合开发者和深度学习场景</td></tr><tr><td>9</td><td><strong>Red Hat Enterprise Linux (RHEL)</strong></td><td>—</td><td><strong>43.1%</strong></td><td>企业级服务器市场领导者，面向商业部署</td></tr></tbody></table><blockquote><p><strong>说明</strong>：<br>本博客文章中关于发行版排名的内容，系综合Stack Overflow年度开发者调查（针对开发者群体）及W3Techs Web服务器市场份额统计（针对Web服务器场景）等公开可查数据整理而成，反映的是特定统计范围内的相对比例，并非对全球Linux用户总量的精确测量。读者在参考时应结合自身使用场景，理性看待排名数据。</p></blockquote><hr><h2 id="三、Linux环境安装方式对比"><a href="#三、Linux环境安装方式对比" class="headerlink" title="三、Linux环境安装方式对比"></a>三、Linux环境安装方式对比</h2><p>在本地电脑上搭建Linux环境有多种方式，可根据自身需求选择：</p><table><thead><tr><th>方式</th><th>适用场景</th><th>优缺点</th></tr></thead><tbody><tr><td><strong>虚拟机（VMware&#x2F;VirtualBox）</strong></td><td>Windows&#x2F;Mac用户</td><td>不影响原有系统，可灵活开关，资源占用稍高</td></tr><tr><td><strong>WSL（Windows Subsystem for Linux）</strong></td><td>Windows 10&#x2F;11用户</td><td>轻量级，与Windows深度集成，无需图形界面</td></tr><tr><td><strong>MultiPass</strong></td><td>Mac&#x2F;Windows&#x2F;Linux</td><td>轻量级虚拟机管理，命令行操作，启动快速</td></tr><tr><td><strong>Docker容器</strong></td><td>开发测试场景</td><td>秒级启动，资源占用极小，但不适合完整系统学习</td></tr><tr><td><strong>双系统安装</strong></td><td>长期Linux主力用户</td><td>性能最佳，但切换不便，安装复杂</td></tr></tbody></table><hr><h2 id="四、MultiPass快速安装Ubuntu（Mac-Windows）"><a href="#四、MultiPass快速安装Ubuntu（Mac-Windows）" class="headerlink" title="四、MultiPass快速安装Ubuntu（Mac &amp; Windows）"></a>四、MultiPass快速安装Ubuntu（Mac &amp; Windows）</h2><p>MultiPass是Canonical官方出品的轻量级虚拟机管理工具，适合快速创建Ubuntu环境。</p><h3 id="Mac系统安装步骤"><a href="#Mac系统安装步骤" class="headerlink" title="Mac系统安装步骤"></a>Mac系统安装步骤</h3><ol><li>访问MultiPass官网，下载对应安装包</li><li>安装完成后，在终端执行以下命令创建虚拟机：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">multipass launch --name ubuntu --cpus 2 --memory 4G --disk 20G</span><br></pre></td></tr></table></figure><ul><li><code>--name</code>：虚拟机名称</li><li><code>--cpus</code>：分配的CPU核心数</li><li><code>--memory</code>：分配的内存大小</li><li><code>--disk</code>：分配的磁盘空间</li></ul><ol start="3"><li>查看虚拟机列表：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">multipass list</span><br></pre></td></tr></table></figure><ol start="4"><li>进入虚拟机Shell：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">multipass shell ubuntu</span><br></pre></td></tr></table></figure><h3 id="Windows系统安装步骤"><a href="#Windows系统安装步骤" class="headerlink" title="Windows系统安装步骤"></a>Windows系统安装步骤</h3><ol><li>同样通过MultiPass安装，下载对应Windows安装包</li><li>如使用虚拟机软件（如VMware）手动安装：<ul><li>访问Ubuntu官网下载ISO镜像文件</li><li>使用VMware创建新虚拟机，选择“典型”安装</li><li>加载下载好的ISO镜像，按向导完成安装</li></ul></li></ol><hr><h2 id="五、Vim编辑器：服务器环境必备技能"><a href="#五、Vim编辑器：服务器环境必备技能" class="headerlink" title="五、Vim编辑器：服务器环境必备技能"></a>五、Vim编辑器：服务器环境必备技能</h2><p>在Linux服务器环境中，往往没有图形界面，只能通过命令行操作。掌握Vim编辑器对于<strong>查看日志、修改配置文件、排查问题</strong>至关重要。</p><h3 id="Vi与Vim的关系"><a href="#Vi与Vim的关系" class="headerlink" title="Vi与Vim的关系"></a>Vi与Vim的关系</h3><ul><li><strong>Vi</strong>：Unix系统下的标准文本编辑器</li><li><strong>Vim（Vi Improved）</strong>：Vi的增强版，功能更丰富，使用方法与Vi基本一致</li></ul><h3 id="Vim的三种模式"><a href="#Vim的三种模式" class="headerlink" title="Vim的三种模式"></a>Vim的三种模式</h3><table><thead><tr><th>模式</th><th>进入方式</th><th>用途</th></tr></thead><tbody><tr><td><strong>命令模式（Command Mode）</strong></td><td>默认启动模式</td><td>移动光标、复制粘贴、删除文字</td></tr><tr><td><strong>插入模式（Insert Mode）</strong></td><td>按 <code>i</code>、<code>a</code>、<code>o</code> 等键</td><td>编辑和输入文本内容</td></tr><tr><td><strong>尾行模式（Last Line Mode）</strong></td><td>命令模式下按 <code>:</code></td><td>保存文件、退出、查找替换等</td></tr></tbody></table><h3 id="打开或新建文件"><a href="#打开或新建文件" class="headerlink" title="打开或新建文件"></a>打开或新建文件</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim hello.txt</span><br></pre></td></tr></table></figure><p>如果文件不存在，Vim会自动创建。</p><h3 id="插入模式快捷键"><a href="#插入模式快捷键" class="headerlink" title="插入模式快捷键"></a>插入模式快捷键</h3><table><thead><tr><th>快捷键</th><th>作用</th></tr></thead><tbody><tr><td><code>i</code></td><td>在当前光标位置前插入</td></tr><tr><td><code>I</code></td><td>在当前行行首插入</td></tr><tr><td><code>a</code></td><td>在当前光标位置后插入</td></tr><tr><td><code>A</code></td><td>在当前行行尾插入</td></tr><tr><td><code>o</code></td><td>在当前行下方新建一行插入</td></tr><tr><td><code>O</code></td><td>在当前行上方新建一行插入</td></tr></tbody></table><h3 id="光标移动操作（命令模式）"><a href="#光标移动操作（命令模式）" class="headerlink" title="光标移动操作（命令模式）"></a>光标移动操作（命令模式）</h3><table><thead><tr><th>快捷键</th><th>作用</th></tr></thead><tbody><tr><td><code>h</code> &#x2F; <code>l</code></td><td>左移 &#x2F; 右移一个字符</td></tr><tr><td><code>j</code> &#x2F; <code>k</code></td><td>下移 &#x2F; 上移一行</td></tr><tr><td><code>0</code></td><td>跳转到行首</td></tr><tr><td><code>$</code></td><td>跳转到行尾</td></tr><tr><td><code>gg</code></td><td>跳转到文件首行</td></tr><tr><td><code>G</code></td><td>跳转到文件末行</td></tr><tr><td><code>Ctrl+f</code> &#x2F; <code>Ctrl+b</code></td><td>向前 &#x2F; 向后翻页</td></tr><tr><td><code>Ctrl+d</code> &#x2F; <code>Ctrl+u</code></td><td>向下 &#x2F; 向上翻半页</td></tr></tbody></table><h3 id="复制、粘贴与删除"><a href="#复制、粘贴与删除" class="headerlink" title="复制、粘贴与删除"></a>复制、粘贴与删除</h3><table><thead><tr><th>操作</th><th>快捷键</th></tr></thead><tbody><tr><td>复制当前行</td><td><code>yy</code></td></tr><tr><td>粘贴到下一行</td><td><code>p</code></td></tr><tr><td>删除当前行</td><td><code>dd</code></td></tr><tr><td>撤销操作</td><td><code>u</code></td></tr></tbody></table><blockquote><p>在快捷键前加数字可重复执行，如 <code>3yy</code> 复制3行，<code>5dd</code> 删除5行。</p></blockquote><h3 id="查找与替换"><a href="#查找与替换" class="headerlink" title="查找与替换"></a>查找与替换</h3><p><strong>查找操作</strong>（命令模式下）：</p><ul><li><code>/关键词</code>：向下查找</li><li><code>?关键词</code>：向上查找</li><li><code>n</code>：查找下一个</li><li><code>N</code>：查找上一个</li></ul><p><strong>替换操作</strong>（尾行模式下）：</p><ul><li><code>:S /旧内容/新内容/</code>：替换当前行首个匹配</li><li><code>:S /旧内容/新内容/G</code>：替换当前行所有匹配</li><li><code>:%S /旧内容/新内容/G</code>：替换整个文件所有匹配</li><li><code>:1,5 S /旧内容/新内容/</code>：替换第1至第5行的首个匹配</li></ul><h3 id="保存与退出"><a href="#保存与退出" class="headerlink" title="保存与退出"></a>保存与退出</h3><table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>:w</code></td><td>保存文件</td></tr><tr><td><code>:q</code></td><td>退出Vim</td></tr><tr><td><code>:wq</code></td><td>保存并退出</td></tr><tr><td><code>:q!</code></td><td>强制退出（不保存）</td></tr></tbody></table><h3 id="显示行号"><a href="#显示行号" class="headerlink" title="显示行号"></a>显示行号</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">:<span class="built_in">set</span> number   <span class="comment"># 显示行号</span></span><br><span class="line">:<span class="built_in">set</span> nu       <span class="comment"># 同上（简写）</span></span><br></pre></td></tr></table></figure><h3 id="Vim配置文件-vimrc"><a href="#Vim配置文件-vimrc" class="headerlink" title="Vim配置文件 .vimrc"></a>Vim配置文件 <code>.vimrc</code></h3><p>在用户家目录下创建 <code>.vimrc</code> 文件，可保存个性化配置，每次启动Vim时自动加载：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">set</span> number        <span class="comment"># 默认显示行号</span></span><br><span class="line">syntax on         <span class="comment"># 开启语法高亮</span></span><br><span class="line"><span class="built_in">set</span> tabstop=4     <span class="comment"># Tab键宽度设为4</span></span><br></pre></td></tr></table></figure><hr><h2 id="六、Linux常用命令精讲"><a href="#六、Linux常用命令精讲" class="headerlink" title="六、Linux常用命令精讲"></a>六、Linux常用命令精讲</h2><h3 id="1-文件列表：ls"><a href="#1-文件列表：ls" class="headerlink" title="1. 文件列表：ls"></a>1. 文件列表：<code>ls</code></h3><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td><code>ls -l</code></td><td>显示详细信息（权限、所有者、大小、修改时间）</td></tr><tr><td><code>ls -a</code></td><td>显示所有文件（包括以<code>.</code>开头的隐藏文件）</td></tr><tr><td><code>ls -h</code></td><td>以人类可读方式显示文件大小（KB&#x2F;MB&#x2F;GB）</td></tr><tr><td><code>ls -t</code></td><td>按修改时间排序</td></tr><tr><td><code>ls -r</code></td><td>逆序显示</td></tr></tbody></table><p>组合使用：<code>ls -lht</code>（详细、人类可读、按时间逆序）</p><h3 id="2-链接文件：ln"><a href="#2-链接文件：ln" class="headerlink" title="2. 链接文件：ln"></a>2. 链接文件：<code>ln</code></h3><table><thead><tr><th>类型</th><th>命令</th><th>特点</th></tr></thead><tbody><tr><td><strong>软链接（符号链接）</strong></td><td><code>ln -s 源文件 链接名</code></td><td>类似Windows快捷方式，可指向目录，源文件删除则失效</td></tr><tr><td><strong>硬链接</strong></td><td><code>ln 源文件 链接名</code></td><td>指向同一文件数据块，源文件删除后仍可访问，只能指向文件</td></tr></tbody></table><p>验证方法：使用 <code>ls -li</code> 查看文件的<strong>inode节点号</strong>，硬链接与原文件的inode相同。</p><h3 id="3-文件权限管理：chmod"><a href="#3-文件权限管理：chmod" class="headerlink" title="3. 文件权限管理：chmod"></a>3. 文件权限管理：<code>chmod</code></h3><p><strong>权限表示</strong>：<code>-rw-r--r--</code> 共10个字符，第1位表示文件类型（<code>-</code>为文件，<code>d</code>为目录），后9位每3位一组，分别对应<strong>所有者（u）</strong>、<strong>所属组（g）</strong>、<strong>其他用户（o）</strong> 的 <strong>读（r&#x3D;4）写（w&#x3D;2）执行（x&#x3D;1）</strong> 权限。</p><p><strong>字母方式</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> +x script.sh    <span class="comment"># 添加执行权限</span></span><br><span class="line"><span class="built_in">chmod</span> -x script.sh    <span class="comment"># 移除执行权限</span></span><br></pre></td></tr></table></figure><p><strong>数字方式</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> 755 script.sh   <span class="comment"># 所有者：7（rwx），组：5（r-x），其他：5（r-x）</span></span><br></pre></td></tr></table></figure><h3 id="4-文件与目录操作"><a href="#4-文件与目录操作" class="headerlink" title="4. 文件与目录操作"></a>4. 文件与目录操作</h3><table><thead><tr><th>操作</th><th>命令</th><th>示例</th></tr></thead><tbody><tr><td>创建空文件</td><td><code>touch</code></td><td><code>touch file.txt</code></td></tr><tr><td>创建目录</td><td><code>mkdir</code></td><td><code>mkdir folder</code></td></tr><tr><td>创建多级目录</td><td><code>mkdir -p</code></td><td><code>mkdir -p a/b/c</code></td></tr><tr><td>复制文件</td><td><code>cp</code></td><td><code>cp file1 file2</code></td></tr><tr><td>复制目录</td><td><code>cp -r</code></td><td><code>cp -r dir1 dir2</code></td></tr><tr><td>移动&#x2F;重命名</td><td><code>mv</code></td><td><code>mv old new</code></td></tr><tr><td>删除文件</td><td><code>rm</code></td><td><code>rm file.txt</code></td></tr><tr><td>删除目录</td><td><code>rm -r</code></td><td><code>rm -r folder</code></td></tr><tr><td>删除空目录</td><td><code>rmdir</code></td><td><code>rmdir empty_folder</code></td></tr></tbody></table><h3 id="5-查看目录结构"><a href="#5-查看目录结构" class="headerlink" title="5. 查看目录结构"></a>5. 查看目录结构</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">du</span> -h        <span class="comment"># 查看目录大小并展示结构</span></span><br><span class="line">tree         <span class="comment"># 树状图显示目录结构（需安装：sudo apt install tree）</span></span><br></pre></td></tr></table></figure><hr><h2 id="七、安全提示"><a href="#七、安全提示" class="headerlink" title="七、安全提示"></a>七、安全提示</h2><blockquote><p>⚠️ <strong>Linux删除操作不可逆</strong>，使用 <code>rm -r</code> 删除目录前务必确认目标正确，建议先用 <code>ls</code> 检查。</p></blockquote><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文涵盖了Linux从概念到实战的核心内容：</p><table><thead><tr><th>模块</th><th>核心要点</th></tr></thead><tbody><tr><td><strong>系统认知</strong></td><td>Linux内核由林纳斯·托瓦兹发布，系统分为内核→库→Shell→应用四层</td></tr><tr><td><strong>环境搭建</strong></td><td>MultiPass&#x2F;WSL&#x2F;虚拟机&#x2F;Docker多种方式可选，Ubuntu适合新手入门</td></tr><tr><td><strong>Vim编辑器</strong></td><td>三种模式（命令&#x2F;插入&#x2F;尾行），掌握打开、编辑、查找替换、保存退出</td></tr><tr><td><strong>基础命令</strong></td><td>ls、ln、chmod、cp、mv、rm、mkdir等文件与目录操作</td></tr></tbody></table><p>掌握以上内容，即可顺利开启Linux学习和开发之旅。更多高级命令可根据自己的需要自行学习。</p>]]>
    </content>
    <id>https://qixia.site/2026/06/19/Linux%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97%EF%BC%9A%E4%BB%8E%E7%B3%BB%E7%BB%9F%E6%A6%82%E5%BF%B5%E5%88%B0%E5%9F%BA%E7%A1%80%E5%91%BD%E4%BB%A4%E5%AE%9E%E6%88%98/</id>
    <link href="https://qixia.site/2026/06/19/Linux%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97%EF%BC%9A%E4%BB%8E%E7%B3%BB%E7%BB%9F%E6%A6%82%E5%BF%B5%E5%88%B0%E5%9F%BA%E7%A1%80%E5%91%BD%E4%BB%A4%E5%AE%9E%E6%88%98/"/>
    <published>2026-06-18T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="一、Linux是什么？"><a href="#一、Linux是什么？" class="headerlink" title="一、Linux是什么？"></a>一、Linux是什么？</h2><p>Linux是一种<strong>开源、免费使用</strong>的类]]>
    </summary>
    <title>Linux入门指南：从系统概念到基础命令实战</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="环境运维" scheme="https://qixia.site/categories/%E7%8E%AF%E5%A2%83%E8%BF%90%E7%BB%B4/"/>
    <category term="Git" scheme="https://qixia.site/tags/Git/"/>
    <category term="GitHub" scheme="https://qixia.site/tags/GitHub/"/>
    <category term="开发工具" scheme="https://qixia.site/tags/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
    <category term="SSH" scheme="https://qixia.site/tags/SSH/"/>
    <content>
      <![CDATA[<h1 id="Git学习实战篇——Git与GitHub从零到一完整演练"><a href="#Git学习实战篇——Git与GitHub从零到一完整演练" class="headerlink" title="Git学习实战篇——Git与GitHub从零到一完整演练"></a>Git学习实战篇——Git与GitHub从零到一完整演练</h1><blockquote><p>本文是理论篇的实战延续，所有操作均可借助Claude AI Agent辅助完成。从环境搭建到PR合并，全流程覆盖。</p></blockquote><hr><h2 id="一、😊工具准备"><a href="#一、😊工具准备" class="headerlink" title="一、😊工具准备"></a>一、😊工具准备</h2><h3 id="第一步：安装Git（Windows-macOS）"><a href="#第一步：安装Git（Windows-macOS）" class="headerlink" title="第一步：安装Git（Windows &#x2F; macOS）"></a>第一步：安装Git（Windows &#x2F; macOS）</h3><p><strong>Windows环境</strong>：</p><p><strong>方法一：</strong></p><ol><li>访问Git官网下载页面：<a href="https://git-scm.com/download/win">https://git-scm.com/download/win</a></li><li>下载<code>.exe</code>安装包（64位版本）</li><li>运行安装程序，一路保持默认选项（建议将”默认编辑器”选为VS Code）</li><li>安装完成后，在任意文件夹右键选择<strong>Git Bash Here</strong>，输入<code>git --version</code>，显示版本号则成功</li></ol><p><strong>方法二：</strong></p><ul><li><strong>使用 <code>winget</code> 安装 (推荐)</strong>：在“命令提示符”或“PowerShell”中运行：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">winget install --<span class="built_in">id</span> Git.Git -e --<span class="built_in">source</span> winget</span><br></pre></td></tr></table></figure></li></ul><p><strong>macOS环境</strong>：</p><ul><li>方法一（推荐）：打开终端，执行<code>xcode-select --install</code>安装Xcode Command Line Tools（内置Git）</li><li>方法二：使用Homebrew执行<code>brew install git</code></li><li>验证：终端输入<code>git --version</code></li></ul><hr><h2 id="第二步：安装VS-Code及必备插件"><a href="#第二步：安装VS-Code及必备插件" class="headerlink" title="第二步：安装VS Code及必备插件"></a>第二步：安装VS Code及必备插件</h2><ol><li>访问 <a href="https://code.visualstudio.com/">https://code.visualstudio.com/</a> 下载安装VS Code</li><li>启动VS Code，点击左侧扩展图标（或按<code>Ctrl+Shift+X</code> &#x2F; <code>Cmd+Shift+X</code>）</li><li>搜索安装以下插件：<ul><li><strong>GitLens</strong> —— 增强Git历史查看与Blame信息</li><li><strong>GitHub Pull Requests</strong> —— 在VS Code内管理PR</li></ul></li></ol><hr><h2 id="第三步：注册GitHub账号并配置SSH密钥"><a href="#第三步：注册GitHub账号并配置SSH密钥" class="headerlink" title="第三步：注册GitHub账号并配置SSH密钥"></a>第三步：注册GitHub账号并配置SSH密钥</h2><ol><li>访问 <a href="https://github.com/">https://github.com/</a> 注册账号</li><li>生成SSH密钥（免密推送）：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t ed25519 -C <span class="string">&quot;your_email@example.com&quot;</span></span><br></pre></td></tr></table></figure>一路回车（默认路径，不设密码）</li><li>复制公钥内容：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> ~/.ssh/id_ed25519.pub</span><br></pre></td></tr></table></figure></li><li>登录GitHub → 右上角头像 → <strong>Settings</strong> → <strong>SSH and GPG keys</strong> → <strong>New SSH Key</strong>，粘贴保存</li></ol><hr><h2 id="第四步：初始化Git全局配置"><a href="#第四步：初始化Git全局配置" class="headerlink" title="第四步：初始化Git全局配置"></a>第四步：初始化Git全局配置</h2><p>在终端执行以下命令（替换为你的GitHub用户名和邮箱）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git config --global user.name <span class="string">&quot;你的GitHub用户名&quot;</span></span><br><span class="line">git config --global user.email <span class="string">&quot;你的GitHub邮箱&quot;</span></span><br><span class="line">git config --global color.ui auto  <span class="comment"># 启用命令行颜色高亮</span></span><br></pre></td></tr></table></figure><p>验证配置：<code>git config --list</code></p><hr><h2 id="第五步：初始化第一个Git仓库并进行首次提交"><a href="#第五步：初始化第一个Git仓库并进行首次提交" class="headerlink" title="第五步：初始化第一个Git仓库并进行首次提交"></a>第五步：初始化第一个Git仓库并进行首次提交</h2><ol><li>新建文件夹（如<code>my-first-repo</code>），在VS Code中打开</li><li>按<code>Ctrl+Shift+P</code>（Mac: <code>Cmd+Shift+P</code>），输入<code>Git: Initialize Repository</code>，选择当前文件夹</li><li>新建文件<code>index.html</code>，写入<code>&lt;h1&gt;Hello Git&lt;/h1&gt;</code></li><li>点击左侧<strong>Source Control</strong>图标（或按<code>Ctrl+Shift+G</code> &#x2F; <code>Cmd+Shift+G</code>）</li><li>在”消息”框中输入<code>首次提交</code>，点击<strong>Commit</strong>按钮（VS Code自动执行<code>add</code> + <code>commit</code>）</li></ol><hr><h2 id="第六步：在GitHub创建远程仓库并推送"><a href="#第六步：在GitHub创建远程仓库并推送" class="headerlink" title="第六步：在GitHub创建远程仓库并推送"></a>第六步：在GitHub创建远程仓库并推送</h2><ol><li>登录GitHub，点击右上角<code>+</code> → <strong>New repository</strong></li><li>仓库名填写<code>my-first-repo</code>，选择<strong>Public</strong>，<strong>不勾选</strong>“Add a README file”</li><li>点击<strong>Create repository</strong></li><li>复制推送命令（推荐SSH）：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git remote add origin git@github.com:你的用户名/my-first-repo.git</span><br><span class="line">git branch -M main</span><br><span class="line">git push -u origin main</span><br></pre></td></tr></table></figure></li><li>在VS Code终端中执行</li><li>刷新GitHub页面，即可看到上传的文件</li></ol><hr><h2 id="二、🤏多场景实战"><a href="#二、🤏多场景实战" class="headerlink" title="二、🤏多场景实战"></a>二、🤏多场景实战</h2><h3 id="1、实战分支操作"><a href="#1、实战分支操作" class="headerlink" title="1、实战分支操作"></a>1、实战分支操作</h3><p><strong>在终端中执行以下命令序列</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 创建并切换分支</span></span><br><span class="line">git checkout -b feature/hello</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 修改文件并提交</span></span><br><span class="line"><span class="comment"># （手动修改index.html内容为 &lt;h1&gt;Hello from Feature&lt;/h1&gt;）</span></span><br><span class="line">git add index.html</span><br><span class="line">git commit -m <span class="string">&quot;修改首页标题&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 切回主干</span></span><br><span class="line">git checkout main</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 合并分支</span></span><br><span class="line">git merge feature/hello</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 删除分支</span></span><br><span class="line">git branch -d feature/hello</span><br></pre></td></tr></table></figure><hr><h2 id="2、模拟多人协作——Fork与PR实战"><a href="#2、模拟多人协作——Fork与PR实战" class="headerlink" title="2、模拟多人协作——Fork与PR实战"></a>2、模拟多人协作——Fork与PR实战</h2><h3 id="标准开源贡献流程"><a href="#标准开源贡献流程" class="headerlink" title="标准开源贡献流程"></a>标准开源贡献流程</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">flowchart LR</span><br><span class="line">    A[Fork 上游项目] --&gt; B[Clone 到本地]</span><br><span class="line">    B --&gt; C[创建 feature 分支]</span><br><span class="line">    C --&gt; D[修改代码并多次 commit]</span><br><span class="line">    D --&gt; E[Push 到个人远端仓库]</span><br><span class="line">    E --&gt; F[创建 Pull Request]</span><br><span class="line">    F --&gt; G[管理员 Code Review]</span><br><span class="line">    G --&gt; H[Merge 合并]</span><br></pre></td></tr></table></figure><ol><li><strong>Fork</strong>：进入目标仓库，点击右上角<strong>Fork</strong>按钮</li><li><strong>Clone</strong>：在VS Code中按<code>Ctrl+Shift+P</code> → <code>Git: Clone</code>，粘贴Fork后的仓库URL</li><li><strong>创建分支并修改</strong>：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b fix-typo</span><br><span class="line"><span class="comment"># 修改README.md</span></span><br><span class="line">git add README.md</span><br><span class="line">git commit -m <span class="string">&quot;修正拼写错误&quot;</span></span><br><span class="line">git push origin fix-typo</span><br></pre></td></tr></table></figure></li><li><strong>创建PR</strong>：打开GitHub上你的Fork仓库 → <strong>Pull requests</strong> → <strong>New pull request</strong>，选择<code>fix-typo</code> → <code>main</code>，填写标题，点击创建</li></ol><hr><h2 id="3、冲突场景模拟与解决"><a href="#3、冲突场景模拟与解决" class="headerlink" title="3、冲突场景模拟与解决"></a>3、冲突场景模拟与解决</h2><p><strong>场景</strong>：两个分支修改同一文件同一行</p><ol><li>在main分支修改<code>fruit.txt</code>第一行为<code>apple</code>并提交</li><li>在feature分支修改同一文件第一行为<code>banana</code>并提交</li><li>执行合并：<code>git merge feature</code></li><li>Git提示冲突，需手动解决。打开冲突文件，保留需要的内容</li><li>标记冲突已解决并完成合并：<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git add fruit.txt</span><br><span class="line">git commit -m <span class="string">&quot;解决fruit.txt冲突&quot;</span></span><br></pre></td></tr></table></figure></li></ol><hr><h2 id="4、高级操作速查"><a href="#4、高级操作速查" class="headerlink" title="4、高级操作速查"></a>4、高级操作速查</h2><h3 id="撤销操作对比"><a href="#撤销操作对比" class="headerlink" title="撤销操作对比"></a>撤销操作对比</h3><table><thead><tr><th>操作</th><th>适用场景</th><th>命令</th></tr></thead><tbody><tr><td><strong>Discard</strong></td><td>放弃未commit的本地修改</td><td><code>git checkout -- &lt;文件名&gt;</code></td></tr><tr><td><strong>Soft Reset</strong></td><td>撤销commit，保留修改到暂存区</td><td><code>git reset --soft HEAD~1</code></td></tr><tr><td><strong>Hard Reset</strong></td><td>彻底撤销commit，丢弃所有修改（危险）</td><td><code>git reset --hard HEAD~1</code></td></tr><tr><td><strong>Revert</strong></td><td>安全撤销已推送的提交（新增反向提交）</td><td><code>git revert &lt;commit-id&gt;</code></td></tr></tbody></table><h3 id="暂存未完成工作"><a href="#暂存未完成工作" class="headerlink" title="暂存未完成工作"></a>暂存未完成工作</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git stash        <span class="comment"># 暂存当前改动</span></span><br><span class="line">git stash pop    <span class="comment"># 恢复暂存内容</span></span><br></pre></td></tr></table></figure><h3 id="Cherry-pick选择性合并"><a href="#Cherry-pick选择性合并" class="headerlink" title="Cherry-pick选择性合并"></a>Cherry-pick选择性合并</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git cherry-pick &lt;commit-id-1&gt; &lt;commit-id-2&gt;</span><br></pre></td></tr></table></figure><hr><h2 id="三、📌-划重点："><a href="#三、📌-划重点：" class="headerlink" title="三、📌 划重点："></a>三、<strong>📌 划重点：</strong></h2><p>整篇下来，如果你是纯新手，记住这三步就够了：</p><ul><li><p>装好 Git + VS Code + GitLens（跟着第一步走完）</p></li><li><p>学会 add → commit → push（第五、六步）</p></li><li><p>遇到冲突别慌，按第四节的流程慢慢解，实在不行就 Ctrl+Z 重来。</p></li></ul><p>剩下的分支、PR、Cherry-pick，都是你熟练之后”顺便学的”，不用一次性全啃完。Git 这东西，用着用着就会了，不是学会了才用。</p>]]>
    </content>
    <id>https://qixia.site/2026/06/16/Git%E4%B8%8EGitHub%E4%BB%8E%E9%9B%B6%E5%88%B0%E4%B8%80%E5%AE%8C%E6%95%B4%E6%BC%94%E7%BB%83/</id>
    <link href="https://qixia.site/2026/06/16/Git%E4%B8%8EGitHub%E4%BB%8E%E9%9B%B6%E5%88%B0%E4%B8%80%E5%AE%8C%E6%95%B4%E6%BC%94%E7%BB%83/"/>
    <published>2026-06-15T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="Git学习实战篇——Git与GitHub从零到一完整演练"><a href="#Git学习实战篇——Git与GitHub从零到一完整演练" class="headerlink" title="Git学习实战篇——Git与GitHub从零到一完整演练"></a>Gi]]>
    </summary>
    <title>Git与GitHub从零到一完整演练</title>
    <updated>2026-07-22T01:33:15.023Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="Git" scheme="https://qixia.site/tags/Git/"/>
    <category term="GitHub" scheme="https://qixia.site/tags/GitHub/"/>
    <content>
      <![CDATA[<h1 id="Git学习理论篇——AI时代下学习Git与GitHub核心概念"><a href="#Git学习理论篇——AI时代下学习Git与GitHub核心概念" class="headerlink" title="Git学习理论篇——AI时代下学习Git与GitHub核心概念"></a>Git学习理论篇——AI时代下学习Git与GitHub核心概念</h1><blockquote><p>当AI Agent成为编程主力军，理解版本控制的底层逻辑，比记忆命令更重要。本文聚焦概念体系，为后续实战打下坚实基础。</p></blockquote><hr><h2 id="一、为什么AI时代，Git与GitHub变得不可或缺？"><a href="#一、为什么AI时代，Git与GitHub变得不可或缺？" class="headerlink" title="一、为什么AI时代，Git与GitHub变得不可或缺？"></a>一、为什么AI时代，Git与GitHub变得不可或缺？</h2><p>在AI辅助编程的今天，版本控制的价值被重新定义：</p><ul><li><strong>AI Agent的操作中枢</strong>：无论是Claude Code、GitHub Copilot还是各类AI编程工具，其核心能力都围绕着<strong>提交（commit）</strong>、<strong>分支（branch）</strong>、<strong>合并（merge）</strong> 等Git原语运转。没有版本控制，AI的每一次修改都将成为”一次性操作”，无法追溯、无法回滚。</li><li><strong>自然语言驱动Git</strong>：借助AI Agent（如Claude），开发者无需记忆数十条Git命令。只需理解<strong>仓库、提交、分支、远端</strong>四个核心概念，即可用自然语言下达”回退到上一次提交””将feature分支合并到主干””同步远端最新代码”等指令，AI自动执行底层操作。</li><li><strong>协作与信任的基础</strong>：在多人AI协作场景中，每一次代码变更都应有明确的提交记录、作者信息和变更说明。Git提供的不可变历史链路，是团队互信与问题定位的基石。</li></ul><hr><h2 id="二、Git与GitHub的本质区别"><a href="#二、Git与GitHub的本质区别" class="headerlink" title="二、Git与GitHub的本质区别"></a>二、Git与GitHub的本质区别</h2><table><thead><tr><th>维度</th><th>Git</th><th>GitHub</th></tr></thead><tbody><tr><td><strong>本质</strong></td><td>分布式的版本控制软件工具，安装在本地计算机</td><td>基于Web的代码托管平台与服务，运行在云端</td></tr><tr><td><strong>核心功能</strong></td><td>版本控制：追踪文件变更、管理分支、合并代码历史</td><td>代码托管与协作：提供远程仓库、代码审查、项目管理</td></tr><tr><td><strong>运行位置</strong></td><td>本地计算机</td><td>云端服务器</td></tr><tr><td><strong>主要交互方式</strong></td><td>命令行或本地GUI客户端</td><td>Web界面，通过Git命令远程交互</td></tr></tbody></table><p>Git是一个<strong>开源免费的版本控制系统</strong>，它的主要功能是对文件夹中的文件进行版本控制，记录每个文件的历史变更，形成一条可回溯的commit历史链路。而GitHub则是建立在Git之上的<strong>协作平台</strong>，提供了远程仓库托管、Pull Request、Issues、Actions等扩展功能。</p><hr><h2 id="三、Git核心概念：本地仓库的四大支柱"><a href="#三、Git核心概念：本地仓库的四大支柱" class="headerlink" title="三、Git核心概念：本地仓库的四大支柱"></a>三、Git核心概念：本地仓库的四大支柱</h2><p><img src="/img/Git_1/p2.png" alt="图解"></p><h3 id="1-仓库（Repository）——版本控制的基本单元"><a href="#1-仓库（Repository）——版本控制的基本单元" class="headerlink" title="1. 仓库（Repository）——版本控制的基本单元"></a>1. 仓库（Repository）——版本控制的基本单元</h3><p>Git仓库本质上是<strong>一个隐藏的<code>.git</code>目录</strong>，它记录了项目根目录下所有文件的变更历史。仓库分为两类：</p><table><thead><tr><th>类型</th><th>位置</th><th>作用</th></tr></thead><tbody><tr><td><strong>本地仓库</strong></td><td>开发者个人电脑</td><td>日常开发、提交、分支切换</td></tr><tr><td><strong>远程仓库</strong></td><td>远端服务器（如GitHub）</td><td>代码备份、团队共享、协作中枢</td></tr></tbody></table><blockquote><p><strong>分布式特性</strong>：Git是分布式版本控制系统，每个开发者本地拥有代码仓库的<strong>完整副本</strong>，包括所有文件及完整历史记录。即使服务器宕机，本地仍可独立工作；即使服务器数据丢失，也可从任意本地仓库恢复。</p></blockquote><h3 id="2-提交（Commit）——不可变的历史快照"><a href="#2-提交（Commit）——不可变的历史快照" class="headerlink" title="2. 提交（Commit）——不可变的历史快照"></a>2. 提交（Commit）——不可变的历史快照</h3><p>Git采用<strong>快照流（Streams of Snapshot）<strong>方式存储数据，而非传统版本控制系统的增量方式。每次<code>commit</code>保存当前所有文件的完整状态（未修改文件仅链接指向旧对象），并生成一个</strong>唯一的哈希ID</strong>。</p><ul><li><strong>短ID vs 长ID</strong>：短ID（前7位）用于日常标识，长ID（40位）用于精确引用，两者功能等价</li><li><strong>提交信息规范</strong>：建议采用”动词+名词”结构（如”添加用户登录验证”），便于AI理解和自动生成变更日志</li></ul><h3 id="3-分支（Branch）——并行开发的利器"><a href="#3-分支（Branch）——并行开发的利器" class="headerlink" title="3. 分支（Branch）——并行开发的利器"></a>3. 分支（Branch）——并行开发的利器</h3><p>分支本质上是一个<strong>指向某次提交的指针</strong>。创建分支不会复制代码，仅新增一个指针。这使得：</p><ul><li>多个功能可并行开发，互不干扰</li><li>主干（<code>master</code>&#x2F;<code>main</code>）始终保持稳定可发布状态</li><li>合并（<code>merge</code>）时仅需将指针移动，高效且轻量</li></ul><h3 id="4-工作区、暂存区与本地仓库"><a href="#4-工作区、暂存区与本地仓库" class="headerlink" title="4. 工作区、暂存区与本地仓库"></a>4. 工作区、暂存区与本地仓库</h3><p>Git的核心工作流程涉及三个区域：</p><ul><li><strong>工作区（Working Directory）</strong>：当前正在编辑的文件</li><li><strong>暂存区（Staging Area &#x2F; Index）</strong>：执行<code>git add</code>后文件存放处，提交前的”缓冲检查点”</li><li><strong>本地仓库（Repository）</strong>：执行<code>git commit</code>后的永久存储区</li></ul><p>基本工作流：修改文件 → <code>git add</code>添加到暂存区 → <code>git commit</code>生成永久快照。</p><hr><h2 id="四、GitHub：不只是代码托管平台"><a href="#四、GitHub：不只是代码托管平台" class="headerlink" title="四、GitHub：不只是代码托管平台"></a>四、GitHub：不只是代码托管平台</h2><p>GitHub作为全球最大的开发者协作平台，在Git核心功能之上构建了完整的协作生态系统：</p><table><thead><tr><th>核心功能</th><th>应用场景</th></tr></thead><tbody><tr><td><strong>Fork + Pull Request</strong></td><td>开源贡献的标准工作流，支持代码审查</td></tr><tr><td><strong>Issues</strong></td><td>Bug追踪、功能讨论、社区问答</td></tr><tr><td><strong>Projects</strong></td><td>看板式项目管理，任务可视化流转</td></tr><tr><td><strong>Actions</strong></td><td>CI&#x2F;CD自动化流水线</td></tr><tr><td><strong>Codespaces</strong></td><td>云端开发环境，一键启动可调试的远程工作区</td></tr></tbody></table><hr><h2 id="五、Claude使用Git的操作范式"><a href="#五、Claude使用Git的操作范式" class="headerlink" title="五、Claude使用Git的操作范式"></a>五、Claude使用Git的操作范式</h2><table><thead><tr><th>核心概念</th><th>向Claude下达的指令示例</th></tr></thead><tbody><tr><td>提交</td><td>“Ship my current changes to a new PR”</td></tr><tr><td>分支切换</td><td>“切换到feature&#x2F;payment分支”</td></tr><tr><td>合并</td><td>“将feature&#x2F;payment合并到main”</td></tr><tr><td>回退</td><td>“回退到上周三的那个提交”</td></tr><tr><td>同步远端</td><td>“从远端拉取最新代码并合并”</td></tr></tbody></table><p>Claude会将自然语言指令翻译为具体的Git命令，开发者只需关注<strong>业务逻辑与代码质量</strong>。</p><hr><h2 id="六、理论要点速查"><a href="#六、理论要点速查" class="headerlink" title="六、理论要点速查"></a>六、理论要点速查</h2><ol><li><strong>Git是工具，GitHub是平台</strong>：Git负责版本控制，GitHub负责协作托管</li><li><strong>分布式核心</strong>：每个本地仓库都是完整副本，无需网络即可查看历史、回退版本</li><li><strong>快照而非差异</strong>：Git记录每次提交的完整状态快照，而非变更差异</li><li><strong>分支是轻量指针</strong>：创建分支不复制代码，仅新增指针，高效且轻量</li><li><strong>AI Agent降低门槛</strong>：理解核心概念后，可通过自然语言指挥Claude完成Git操作</li></ol><blockquote><p>到这关于Git和Github的基本概念就已经完全了解了,下篇文章我们进一步从实战来体会这些基本概念</p></blockquote>]]>
    </content>
    <id>https://qixia.site/2026/06/16/Git%E4%B8%8EGithub%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/</id>
    <link href="https://qixia.site/2026/06/16/Git%E4%B8%8EGithub%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/"/>
    <published>2026-06-15T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="Git学习理论篇——AI时代下学习Git与GitHub核心概念"><a href="#Git学习理论篇——AI时代下学习Git与GitHub核心概念" class="headerlink" title="Git学习理论篇——AI时代下学习Git与GitHub核心概]]>
    </summary>
    <title>Git与Github基本概念</title>
    <updated>2026-07-22T01:33:15.023Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="JavaSE" scheme="https://qixia.site/tags/JavaSE/"/>
    <content>
      <![CDATA[<h2 id="什么是继承？继承的特点？继承的好处？"><a href="#什么是继承？继承的特点？继承的好处？" class="headerlink" title="什么是继承？继承的特点？继承的好处？"></a>什么是继承？继承的特点？继承的好处？</h2><h3 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h3><p>假如现在需要你开发一个教务管理系统中一个最基础的功能————存储学生和教职工的身份信息，你会怎么做？你的做法可能是这样的：<br>分别创建学生类、教师类和工人类，即</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">学生类：属性（姓名，年龄，学工号）、行为（吃饭，睡觉，学习）</span><br><span class="line">老师类：属性（姓名，年龄，学工号，薪水）、行为（吃饭、睡觉，上课）</span><br><span class="line">工人类：属性（姓名，年龄，学工号，薪水）、行为（吃饭、睡觉，管理）</span><br></pre></td></tr></table></figure><p>很明显，如果这样去设计教务管理系统，这三个类中有着大量重复的代码，这样就导致系统的代码复用率低，整个系统显得臃肿笨重。那么应该如何去解决这样的问题呢？<br>假如多个类中存在相同属性和行为时，我们可以将这些内容抽取到单独一个类中，那么多个类无需再定义这些属性和行为，只要继承那一个类即可。如图所示：<br><img src="/img/JavaSE_4/p1.png" alt="图片描述"></p><p>其中，多个类可以称为子类，单独被继承的那一个类称为父类、超类（superclass）或者基类。</p><h3 id="继承的概念"><a href="#继承的概念" class="headerlink" title="继承的概念"></a>继承的概念</h3><p>继承描述的是事物之间的所属关系，这种关系是：is-a 的关系。类似于数学中的集合A属于集合B，表示集合A是集合B的子集，在这里我们用父类和子类来表示这样的继承关系，我们通过继承，可以使多种事物之间形成一种关系体系。这里引出了继承的概念，<br>继承：就是子类继承父类的属性和行为，使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。</p><h3 id="继承的特点"><a href="#继承的特点" class="headerlink" title="继承的特点"></a>继承的特点</h3><p>Java只支持单继承，不支持多继承。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 一个类只能有一个父类，不可以有多个父类。</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> &#123;&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">C1</span> <span class="keyword">extends</span> <span class="title class_">A</span> &#123;&#125; <span class="comment">// ok</span></span><br><span class="line"><span class="comment">// class C2 extends A, B &#123;&#125; // error</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">一个类可以有多个子类。</span><br><span class="line"><span class="comment">// A可以有多个子类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">C1</span> <span class="keyword">extends</span> <span class="title class_">A</span> &#123;&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">C2</span> <span class="keyword">extends</span>  <span class="title class_">A</span> &#123;&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">可以多层继承。</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">C1</span> <span class="keyword">extends</span> <span class="title class_">A</span> &#123;&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">D</span> <span class="keyword">extends</span> <span class="title class_">C1</span> &#123;&#125;</span><br></pre></td></tr></table></figure><blockquote><p>顶层父类是Object类。所有的类默认继承Object，作为父类。</p></blockquote><h3 id="继承的好处"><a href="#继承的好处" class="headerlink" title="继承的好处"></a>继承的好处</h3><p>回想刚刚的问题以及继承的概念，我们可以总结出继承的好处：<br>1、提高了代码的复用性，避免代码的臃肿和冗余。<br>2、使类与类之间产生了关系</p><h3 id="继承的格式"><a href="#继承的格式" class="headerlink" title="继承的格式"></a>继承的格式</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">class 父类()&#123;</span><br><span class="line">    共用的属性和行为</span><br><span class="line">&#125;</span><br><span class="line">class 子类 extends 父类()&#123;</span><br><span class="line">    子类独有的属性和行为</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>需要注意：Java是单继承的，一个类只能继承一个直接父类，跟现实世界很像，但是Java中的子类是更加强大的。</p><h2 id="子类能从父类继承到什么？"><a href="#子类能从父类继承到什么？" class="headerlink" title="子类能从父类继承到什么？"></a>子类能从父类继承到什么？</h2><p>并不是父类的所有内容都可以给子类继承的：<br>子类不能继承父类的构造方法。<br>值得注意的是子类可以继承父类的私有成员（成员变量，方法），只是子类无法直接访问而已，可以通过getter&#x2F;setter方法访问父类的private成员变量。</p><h2 id="成员变量继承的访问特点"><a href="#成员变量继承的访问特点" class="headerlink" title="成员变量继承的访问特点"></a>成员变量继承的访问特点</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Fu</span> &#123;</span><br><span class="line"><span class="comment">// Fu中的成员变量</span></span><br><span class="line"><span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">5</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Zi</span> <span class="keyword">extends</span> <span class="title class_">Fu</span> &#123;</span><br><span class="line"><span class="comment">// Zi中的成员变量</span></span><br><span class="line"><span class="type">int</span> <span class="variable">num2</span> <span class="operator">=</span> <span class="number">6</span>;</span><br><span class="line">    <span class="comment">//int num=6;</span></span><br><span class="line"><span class="comment">// Zi中的成员方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="comment">// 访问父类中的num</span></span><br><span class="line">System.out.println(<span class="string">&quot;Fu num=&quot;</span>+num); <span class="comment">// 继承而来，所以直接访问。</span></span><br><span class="line"><span class="comment">// 访问子类中的num2</span></span><br><span class="line">System.out.println(<span class="string">&quot;Zi num2=&quot;</span>+num2);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Demo04</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 创建子类对象</span></span><br><span class="line"><span class="type">Zi</span> <span class="variable">z</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Zi</span>(); </span><br><span class="line">      <span class="comment">// 调用子类中的show方法</span></span><br><span class="line">z.show();  </span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="成员变量不重名"><a href="#成员变量不重名" class="headerlink" title="成员变量不重名"></a>成员变量不重名</h3><p>对于上面的代码，如果子类父类中出现不重名的成员变量，这时的访问是没有影响的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">代码运行结果：</span><br><span class="line"><span class="type">Fu</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">5</span></span><br><span class="line"><span class="type">Zi</span> <span class="variable">num2</span> <span class="operator">=</span> <span class="number">6</span></span><br></pre></td></tr></table></figure><h3 id="成员变量重名"><a href="#成员变量重名" class="headerlink" title="成员变量重名"></a>成员变量重名</h3><p>对于上面的代码，如果子类父类中出现重名的成员变量,即子类里的num2改为num,这时的访问是有影响的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">代码运行结果：</span><br><span class="line"><span class="type">Fu</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">6</span></span><br><span class="line"><span class="type">Zi</span> <span class="variable">num2</span> <span class="operator">=</span> <span class="number">6</span></span><br></pre></td></tr></table></figure><p>子父类中出现了同名的成员变量时，子类会优先访问自己对象中的成员变量。如果此时想访问父类成员变量如何解决呢？我们可以使用super关键字。</p><h3 id="super访问父类成员变量"><a href="#super访问父类成员变量" class="headerlink" title="super访问父类成员变量"></a>super访问父类成员变量</h3><p>子父类中出现了同名的成员变量时，在子类中需要访问父类中非私有成员变量时，需要使用super 关键字，修饰父类成员变量，类似于之前学过的 this 。<br>需要注意的是：super代表的是父类对象的引用，this代表的是当前对象的引用。<br>使用格式：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">super</span>.父类成员变量名</span><br></pre></td></tr></table></figure><p>上面的那段代码我们只对show方法进行修改：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">      </span><br><span class="line">        <span class="comment">// 访问方法中的num</span></span><br><span class="line">        System.out.println(<span class="string">&quot;method num=&quot;</span> + num);</span><br><span class="line">        <span class="comment">// 访问子类中的num</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Zi num=&quot;</span> + <span class="built_in">this</span>.num);</span><br><span class="line">        <span class="comment">// 访问父类中的num</span></span><br><span class="line">        System.out.println(<span class="string">&quot;Fu num=&quot;</span> + <span class="built_in">super</span>.num);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">代码运行结果</span><br><span class="line">method num=<span class="number">1</span></span><br><span class="line">Zi num=<span class="number">6</span></span><br><span class="line">Fu num=<span class="number">5</span></span><br></pre></td></tr></table></figure><h2 id="成员方法继承的访问特点"><a href="#成员方法继承的访问特点" class="headerlink" title="成员方法继承的访问特点"></a>成员方法继承的访问特点</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Fu</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span> &#123;</span><br><span class="line">System.out.println(<span class="string">&quot;Fu类中的show方法执行&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Zi</span> <span class="keyword">extends</span> <span class="title class_">Fu</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show2</span><span class="params">()</span> &#123;</span><br><span class="line">System.out.println(<span class="string">&quot;Zi类中的show2方法执行&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span> &#123;</span><br><span class="line">System.out.println(<span class="string">&quot;Zi类中的show方法执行&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span>  <span class="keyword">class</span> <span class="title class_">Demo05</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"><span class="type">Zi</span> <span class="variable">z</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Zi</span>();</span><br><span class="line">     <span class="comment">//子类中没有show方法，但是可以找到父类方法去执行</span></span><br><span class="line">z.show(); </span><br><span class="line">z.show2();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">运行结果</span><br><span class="line">Zi类中的show方法执行</span><br><span class="line">Zi类中的show2方法执行</span><br></pre></td></tr></table></figure><h3 id="成员方法不重名"><a href="#成员方法不重名" class="headerlink" title="成员方法不重名"></a>成员方法不重名</h3><p>如果子类父类中出现不重名的成员方法，这时的调用是没有影响的。对象调用方法时，会先在子类中查找有没有对应的方法，若子类中存在就会执行子类中的方法，若子类中不存在就会执行父类中相应的方法。</p><h3 id="成员方法重名"><a href="#成员方法重名" class="headerlink" title="成员方法重名"></a>成员方法重名</h3><p>如果子类父类中出现重名的成员方法，则创建子类对象调用该方法的时候，子类对象会优先调用自己的方法。</p><h3 id="方法重写"><a href="#方法重写" class="headerlink" title="方法重写"></a>方法重写</h3><p>方法重写 ：子类中出现与父类一模一样的方法时（返回值类型，方法名和参数列表都相同），会出现覆盖效果，也称为重写或者复写。声明不变，重新实现。<br>使用场景 ：发生在子父类之间的关系。子类继承了父类的方法，但是子类觉得父类的这方法不足以满足自己的需求，子类重新写了一个与父类同名的方法，以便覆盖父类的该方法。</p><h4 id="Override重写注解"><a href="#Override重写注解" class="headerlink" title="@Override重写注解"></a>@Override重写注解</h4><p>@Override:注解，重写注解校验！<br>这个注解标记的方法，就说明这个方法必须是重写父类的方法，否则编译阶段报错。<br>建议重写都加上这个注解，一方面可以提高代码的可读性，一方面可以防止重写出错！</p><h4 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h4><p>方法重写是发生在子父类之间的关系。<br>子类方法覆盖父类方法，必须要保证权限大于等于父类权限。<br>子类方法覆盖父类方法，返回值类型、函数名和参数列表都要一模一样。</p><h2 id="构造方法继承的特点"><a href="#构造方法继承的特点" class="headerlink" title="构造方法继承的特点"></a>构造方法继承的特点</h2><p>在学习构造方法继承的特点时，我们需要回忆一下构造方法的的定义和作用：</p><p>构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。<br>构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中，必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ，表示调用父类的构造方法，父类成员变量初始化后，才可以给子类使用。（先有爸爸，才能有儿子）</p><p>由此我们可以得出继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法<br>以下面的这段代码为例：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Person</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;父类无参&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// getter/setter省略</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span> <span class="keyword">extends</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> score;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Student</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">//super(); // 调用父类无参,默认就存在，可以不写，必须再第一行</span></span><br><span class="line">        System.out.println(<span class="string">&quot;子类无参&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">     <span class="keyword">public</span> <span class="title function_">Student</span><span class="params">(<span class="type">double</span> score)</span> &#123;</span><br><span class="line">        <span class="comment">//super();  // 调用父类无参,默认就存在，可以不写，必须再第一行</span></span><br><span class="line">        <span class="built_in">this</span>.score = score;    </span><br><span class="line">        System.out.println(<span class="string">&quot;子类有参&quot;</span>);</span><br><span class="line">     &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo07</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Student</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Student</span>();</span><br><span class="line">        System.out.println(<span class="string">&quot;----------&quot;</span>);</span><br><span class="line">        <span class="type">Student</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Student</span>(<span class="number">99.9</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">输出结果：</span><br><span class="line">父类无参</span><br><span class="line">子类无参</span><br><span class="line">----------</span><br><span class="line">父类无参</span><br><span class="line">子类有参</span><br></pre></td></tr></table></figure><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>子类构造方法执行的时候，都会在第一行默认先调用父类无参数构造方法一次。<br>子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法，**super()**可以省略不写。</p><h3 id="super-…-和this-…"><a href="#super-…-和this-…" class="headerlink" title="super(…)和this(…)"></a>super(…)和this(…)</h3><p>对于下面的这段代码，我们发现，子类有参数构造方法只是初始化了自己对象中的成员变量score，而父类中的成员变量name和age依然是没有数据的，怎么解决这个问题呢，我们可以借助与super(…)去调用父类构造方法，以便初始化继承自父类对象的name和age.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo07</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 调用子类有参数构造方法</span></span><br><span class="line">        <span class="type">Student</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Student</span>(<span class="number">99.9</span>);</span><br><span class="line">        System.out.println(s2.getScore()); <span class="comment">// 99.9</span></span><br><span class="line">        System.out.println(s2.getName()); <span class="comment">// 输出 null</span></span><br><span class="line">        System.out.println(s2.getAge()); <span class="comment">// 输出 0</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="super和this的用法格式"><a href="#super和this的用法格式" class="headerlink" title="super和this的用法格式"></a>super和this的用法格式</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">this</span>.成员变量    --    本类的</span><br><span class="line"><span class="built_in">super</span>.成员变量    --    父类的</span><br><span class="line"></span><br><span class="line"><span class="built_in">this</span>.成员方法名()  --    本类的    </span><br><span class="line"><span class="built_in">super</span>.成员方法名()   --    父类的</span><br><span class="line"></span><br><span class="line"><span class="built_in">super</span>(...) -- 调用父类的构造方法，根据参数匹配确认</span><br><span class="line"><span class="built_in">this</span>(...) -- 调用本类的其他构造方法，根据参数匹配确认</span><br></pre></td></tr></table></figure><h4 id="super-…-用法演示"><a href="#super-…-用法演示" class="headerlink" title="super(…)用法演示"></a>super(…)用法演示</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span><span class="string">&quot;凤姐&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">age</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Person</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;父类无参&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Person</span><span class="params">(String name , <span class="type">int</span> age)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name ;</span><br><span class="line">        <span class="built_in">this</span>.age = age ;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// getter/setter省略</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span> <span class="keyword">extends</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> <span class="variable">score</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Student</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">//super(); // 调用父类无参构造方法,默认就存在，可以不写，必须再第一行</span></span><br><span class="line">        System.out.println(<span class="string">&quot;子类无参&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">     <span class="keyword">public</span> <span class="title function_">Student</span><span class="params">(String name ， <span class="type">int</span> age，<span class="type">double</span> score)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(name ,age);<span class="comment">// 调用父类有参构造方法Person(String name , int age)初始化name和age</span></span><br><span class="line">        <span class="built_in">this</span>.score = score;    </span><br><span class="line">        System.out.println(<span class="string">&quot;子类有参&quot;</span>);</span><br><span class="line">     &#125;</span><br><span class="line">      <span class="comment">// getter/setter省略</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo07</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 调用子类有参数构造方法</span></span><br><span class="line">        <span class="type">Student</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Student</span>(<span class="string">&quot;张三&quot;</span>，<span class="number">20</span>，<span class="number">99</span>);</span><br><span class="line">        System.out.println(s2.getScore()); <span class="comment">// 99</span></span><br><span class="line">        System.out.println(s2.getName()); <span class="comment">// 输出 张三</span></span><br><span class="line">        System.out.println(s2.getAge()); <span class="comment">// 输出 20</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意：<br>子类的每个构造方法中均有默认的super()，调用父类的空参构造。手动调用父类构造会覆盖默认的super()。<br>super() 和 this() 都必须是在构造方法的第一行，所以不能同时出现。<br>super(…)是根据参数去确定调用父类哪个构造方法的。</p><h4 id="this-…-用法演示"><a href="#this-…-用法演示" class="headerlink" title="this(…)用法演示"></a>this(…)用法演示</h4><p>this(…):<br>默认是去找本类中的其他构造方法，根据参数来确定具体调用哪一个构造方法。<br>为了借用其他构造方法的功能。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.itheima._08;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThisDemo01</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Student</span> <span class="variable">xuGan</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Student</span>();</span><br><span class="line">        System.out.println(xuGan.getName()); <span class="comment">// 输出:徐干</span></span><br><span class="line">        System.out.println(xuGan.getAge());<span class="comment">// 输出:21</span></span><br><span class="line">        System.out.println(xuGan.getSex());<span class="comment">// 输出： 男</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> String name ;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age ;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">char</span> sex ;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Student</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="comment">// 很弱，我的兄弟很牛逼啊，我可以调用其他构造方法：Student(String name, int age, char sex)</span></span><br><span class="line">        <span class="built_in">this</span>(<span class="string">&quot;徐干&quot;</span>,<span class="number">21</span>,<span class="string">&#x27;男&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Student</span><span class="params">(String name, <span class="type">int</span> age, <span class="type">char</span> sex)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name ;</span><br><span class="line">        <span class="built_in">this</span>.age = age   ;</span><br><span class="line">        <span class="built_in">this</span>.sex = sex   ;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">char</span> <span class="title function_">getSex</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sex;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSex</span><span class="params">(<span class="type">char</span> sex)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.sex = sex;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h4><p>子类的每个构造方法中均有默认的super()，调用父类的空参构造。手动调用父类构造会覆盖默认的super()。<br>super() 和 this() 都必须是在构造方法的第一行，所以不能同时出现。<br>super(…)和this(…)是根据参数去确定调用父类哪个构造方法的。<br>super(…)可以调用父类构造方法初始化继承自父类的成员变量的数据。<br>this(…)可以调用本类中的其他构造方法。</p><h2 id="最后的话"><a href="#最后的话" class="headerlink" title="最后的话"></a>最后的话</h2><p>现在关于面向对象的特性——继承的所有知识点都学习和总结了，接下来开始总结面向对象最后一个特性多态的学习内容。最后感谢您的查阅！！！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/16/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC4%E7%AF%87%EF%BC%9AJava%E7%BB%A7%E6%89%BF%E2%80%94%E2%80%94%E4%B8%8D%E7%94%A8%E9%87%8D%E5%A4%8D%E9%80%A0%E8%BD%AE%E5%AD%90%EF%BC%8C%E4%B8%80%E7%AF%87%E6%90%9E%E6%87%82/</id>
    <link href="https://qixia.site/2026/06/16/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC4%E7%AF%87%EF%BC%9AJava%E7%BB%A7%E6%89%BF%E2%80%94%E2%80%94%E4%B8%8D%E7%94%A8%E9%87%8D%E5%A4%8D%E9%80%A0%E8%BD%AE%E5%AD%90%EF%BC%8C%E4%B8%80%E7%AF%87%E6%90%9E%E6%87%82/"/>
    <published>2026-06-15T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="什么是继承？继承的特点？继承的好处？"><a href="#什么是继承？继承的特点？继承的好处？" class="headerlink" title="什么是继承？继承的特点？继承的好处？"></a>什么是继承？继承的特点？继承的好处？</h2><h3 id="引]]>
    </summary>
    <title>Java基础·第4篇：Java继承——不用重复造轮子，一篇搞懂</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="JavaSE" scheme="https://qixia.site/tags/JavaSE/"/>
    <content>
      <![CDATA[<h2 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h2><p>现在需要对宠物管理系统增加一个飞行的功能：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">狗不会飞行</span><br><span class="line">老鹰会飞行</span><br><span class="line">麻雀会飞行</span><br><span class="line">企鹅不会飞行</span><br></pre></td></tr></table></figure><p><img src="/img/JavaSE_6/p1.png" alt="图片描述"></p><p>在这个宠物管理系统体系中，狗和企鹅是不会飞行的。那如果只用继承，那么狗和企鹅都会飞行，显然不符合常理。也许你也会想，给老鹰和麻雀分别写个飞行的方法，在创建对象之后分别调用，显然会很麻烦</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">letFly</span><span class="params">(Eagle e)</span> &#123; e.fly(); &#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">letFly</span><span class="params">(Sparrow s)</span> &#123; s.fly(); &#125;</span><br></pre></td></tr></table></figure><p>这时候就需要一个接口，来集中抽象一个系统中只有少部分子类拥有的行为和方法。</p><h2 id="接口的概述"><a href="#接口的概述" class="headerlink" title="接口的概述"></a>接口的概述</h2><h3 id="接口的概念和定义格式"><a href="#接口的概念和定义格式" class="headerlink" title="接口的概念和定义格式"></a>接口的概念和定义格式</h3><ul><li><p>接口：一个用来父类下少部分子类独有功能的方法声明，且只声明，没有方法体</p></li><li><p>定义格式：</p></li></ul> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">//接口的定义格式：</span></span><br><span class="line">interface 接口名称&#123;</span><br><span class="line">    <span class="comment">// 抽象方法</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 接口的声明：interface</span></span><br><span class="line"><span class="comment">// 接口名称：首字母大写，满足“驼峰模式”</span></span><br></pre></td></tr></table></figure><p> 对于引入中出现的问题在我们了解接口后，可以做出以下的改进：</p><p>定义一个包含飞行方法的接口，让拥有飞行能力的动物去继承这个飞行接口，即可解决继承中子类功能矛盾的问题。</p><h3 id="接口的成分"><a href="#接口的成分" class="headerlink" title="接口的成分"></a>接口的成分</h3><p>简单来说，在JDK7以前，接口中的只有包含：抽象方法和常量。</p><ul><li><strong>抽象方法：</strong> 使用abstract 关键字修饰方法，该方法就成了抽象方法，抽象方法只包含一个方法名，而没有方法体。</li><li><strong>常量：</strong> 在接口中定义的成员变量默认会加上： public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后，变量值就不可被修改，并且是静态化的变量可以直接用接口名访问，所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写，多个单词用下划线连接。<br><em><strong>案例演示：</strong></em></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">InterF</span> &#123;</span><br><span class="line">    <span class="comment">// 抽象方法！</span></span><br><span class="line">    <span class="comment">//    public abstract void run();</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//    public abstract String getName();</span></span><br><span class="line">    String <span class="title function_">getName</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//    public abstract int add(int a , int b);</span></span><br><span class="line">    <span class="type">int</span> <span class="title function_">add</span><span class="params">(<span class="type">int</span> a , <span class="type">int</span> b)</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 它的最终写法是：</span></span><br><span class="line">    <span class="comment">// public static final int AGE = 12 ;</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">AGE</span>  <span class="operator">=</span> <span class="number">12</span>; <span class="comment">//常量</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">SCHOOL_NAME</span> <span class="operator">=</span> <span class="string">&quot;我是一只码蚁&quot;</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="接口的实现"><a href="#接口的实现" class="headerlink" title="接口的实现"></a>接口的实现</h2><h3 id="接口实现的概念和格式"><a href="#接口实现的概念和格式" class="headerlink" title="接口实现的概念和格式"></a>接口实现的概念和格式</h3><ul><li><strong>概念：</strong> 类与接口的关系为实现关系，即类实现接口，该类可以称为接口的实现类，也可以称为接口的子类。实现的动作类似继承，格式相仿，只是关键字不同，实现使用 implements关键字。</li><li><strong>格式：</strong></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**接口的实现：</span></span><br><span class="line"><span class="comment">    在Java中接口是被实现的，实现接口的类称为实现类。</span></span><br><span class="line"><span class="comment">    在一个类可以实现多个接口</span></span><br><span class="line"><span class="comment">    实现类的格式:*/</span></span><br><span class="line">class 类名 implements 接口<span class="number">1</span>,接口<span class="number">2</span>,接口<span class="number">3.</span>..&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-4-3-类实现接口的要求和意义"><a href="#2-4-3-类实现接口的要求和意义" class="headerlink" title="2.4.3 类实现接口的要求和意义"></a>2.4.3 类实现接口的要求和意义</h3><ul><li><strong>要求：</strong><br>– 必须重写实现的全部接口中所有抽象方法。<br>– 如果一个类实现了接口，但是没有重写完全部接口的全部抽象方法，这个类也必须定义成抽象类。</li><li><strong>意义：</strong><br>接口体现的是一种规范，接口对实现类是一种强制性的约束，要么全部完成接口申明的功能，要么自己也定义成抽象类。这正是一种强制性的规范。</li></ul><h3 id="案例演示"><a href="#案例演示" class="headerlink" title="案例演示"></a>案例演示</h3><ul><li><strong>定义一个程序员的接口</strong></li></ul> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment">   接口：接口体现的是规范。</span></span><br><span class="line"><span class="comment"> * */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ProGrammer</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">write</span><span class="params">()</span>; <span class="comment">// 抽象方法，写代码。</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">study</span><span class="params">()</span>; <span class="comment">// 抽象方法，学习新技术。</span></span><br><span class="line">    String <span class="title function_">work</span><span class="params">(String project)</span>;  <span class="comment">// 抽象方法，工作。</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><strong>定义一个后端开发工程师类</strong></li></ul>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">  <span class="keyword">package</span> com.itheima._03接口的实现;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 接口的实现：</span></span><br><span class="line"><span class="comment"> *    在Java中接口是被实现的，实现接口的类称为实现类。</span></span><br><span class="line"><span class="comment"> *    实现类的格式:</span></span><br><span class="line"><span class="comment"> *      class 类名 implements 接口1,接口2,接口3...&#123;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *      &#125;</span></span><br><span class="line"><span class="comment"> * */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PingPongMan</span>  <span class="keyword">implements</span> <span class="title class_">SportMan</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">write</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;程序员写了一下午的代码！！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">study</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;程序员学了一晚上的新技术！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">compittion</span><span class="params">(String project)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;完成&quot;</span>+project+<span class="string">&quot;的后端开发流程！&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><strong>测试代码</strong></li></ul>  <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">  <span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestMain</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 创建实现类对象。</span></span><br><span class="line">        <span class="type">PingPongMan</span> <span class="variable">zjk</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PingPongMan</span>();</span><br><span class="line">        zjk.write();</span><br><span class="line">        zjk.study();</span><br><span class="line">        System.out.println(zjk.compittion(<span class="string">&quot;淘宝网购系统&quot;</span>));</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="接口的多继承关系"><a href="#接口的多继承关系" class="headerlink" title="接口的多继承关系"></a>接口的多继承关系</h2><p>  Java中，接口与接口之间是可以多继承的：也就是一个接口可以同时继承多个接口。大家一定要注意：</p><ul><li><p>类与接口是实现关系</p></li><li><p>接口与接口是继承关系</p></li></ul><p>接口继承接口就是把其他接口的抽象方法与本接口进行了合并。<br> <strong>案例演示：</strong><br> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">/** 编写程序：接口*/</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Write</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">write</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** 学习新技术：接口*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Study</span>&#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">study</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"> *</span><br><span class="line"> *  总结：</span><br><span class="line"> *     接口与类之间是多实现的。</span><br><span class="line"> *     接口与接口之间是多继承的。</span><br><span class="line"> * */</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ProGrammer</span> <span class="keyword">extends</span> <span class="title class_">Write</span>, Study &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">work</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="接口的细节"><a href="#接口的细节" class="headerlink" title="接口的细节"></a>接口的细节</h2><p>不需要背，只要当idea报错之后，知道如何修改即可。</p><p>关于接口的使用，以下为语法上要注意的细节，虽然条目较多，但若理解了抽象的本质，无需死记硬背。</p><p>当两个接口中存在相同抽象方法的时候，该怎么办？</p><blockquote><p>只要重写一次即可。此时重写的方法，既表示重写1接口的，也表示重写2接口的。</p></blockquote><p>实现类能不能继承A类的时候，同时实现其他接口呢？</p><blockquote><p>继承的父类，就好比是亲爸爸一样 &gt; 实现的接口，就好比是干爹一样 &gt; 可以继承一个类的同时，再实现多个接口，只不过，要把接口里面所有的抽象方法，全部实现。</p></blockquote><p>实现类能不能继承一个抽象类的时候，同时实现其他接口呢？</p><blockquote><p>实现类可以继承一个抽象类的同时，再实现其他多个接口，只不过要把里面所有的抽象方法全部重写。</p></blockquote><p>实现类Zi，实现了一个接口，还继承了一个Fu类。假设在接口中有一个方法，父类中也有一个相同的方法。子类如何操作呢？</p><blockquote><p>处理办法一：如果父类中的方法体，能满足当前业务的需求，在子类中可以不用重写。 &gt; 处理办法二：如果父类中的方法体，不能满足当前业务的需求，需要在子类中重写。</p></blockquote><p>如果一个接口中，有10个抽象方法，但是我在实现类中，只需要用其中一个，该怎么办?</p><blockquote><p>可以在接口跟实现类中间，新建一个中间类（适配器类） &gt; 让这个适配器类去实现接口，对接口里面的所有的方法做空重写。 &gt; 让子类继承这个适配器类，想要用到哪个方法，就重写哪个方法。 &gt; 因为中间类没有什么实际的意义，所以一般会把中间类定义为抽象的，不让外界创建对象</p></blockquote><h2 id="最后的话"><a href="#最后的话" class="headerlink" title="最后的话"></a>最后的话</h2><p>以上就是关于Java中接口的知识的总结，希望对您的学习有帮助的话，如果觉得有用的话，可以点个关注！！！<br>如果内容上有看不懂或者出现的错误的话，麻烦在评论区中提醒一下，最后，感谢您的查阅！！！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/16/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC6%E7%AF%87%EF%BC%9AJava%E6%8E%A5%E5%8F%A3%E2%80%94%E2%80%94%E8%83%BD%E9%A3%9E%E7%9A%84%E5%B0%B1%E5%AE%9E%E7%8E%B0Flyer%EF%BC%8C%E8%83%BD%E6%B8%B8%E7%9A%84%E5%B0%B1%E5%AE%9E%E7%8E%B0Swimmer/</id>
    <link href="https://qixia.site/2026/06/16/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC6%E7%AF%87%EF%BC%9AJava%E6%8E%A5%E5%8F%A3%E2%80%94%E2%80%94%E8%83%BD%E9%A3%9E%E7%9A%84%E5%B0%B1%E5%AE%9E%E7%8E%B0Flyer%EF%BC%8C%E8%83%BD%E6%B8%B8%E7%9A%84%E5%B0%B1%E5%AE%9E%E7%8E%B0Swimmer/"/>
    <published>2026-06-15T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h2><p>现在需要对宠物管理系统增加一个飞行的功能：</p>
<figure class="highlight plaintext"><table><]]>
    </summary>
    <title>Java基础·第6篇：Java接口——能飞的就实现Flyer，能游的就实现Swimmer</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="JavaSE" scheme="https://qixia.site/tags/JavaSE/"/>
    <content>
      <![CDATA[<h2 id="一、为什么需要抽象类？"><a href="#一、为什么需要抽象类？" class="headerlink" title="一、为什么需要抽象类？"></a>一、为什么需要抽象类？</h2><p>假如我们需要编写一个员工管理系统。系统的要求如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">- 所有员工都有姓名、工号、薪水等基本属性，并且都需要打卡上班。</span><br><span class="line">- 所有员工都需要记录工作内容</span><br></pre></td></tr></table></figure><p><img src="/img/JavaSE_7/p1.png" alt="图片描述"></p><p>对于第一个要求，这些属性和行为都是通用的，如果每一个都写一遍，整个系统的代码会变得冗余；对于第二个要求，这些工作内容每个员工的都不一样，无法统一。为了提高代码的简洁性和高效性，我们提出了抽象类的解决方案。</p><p><strong>抽象类的解决方案：</strong></p><ul><li>通用的属性和方法（姓名、工号、薪水、职位、打卡）→ 父类写好，子类直接继承</li><li>无法统一的方法（工作内容）→ 父类只声明，子类各自实现</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Employee</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Employee</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">clockIn</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(name + <span class="string">&quot;打卡上班&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">work</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="二、抽象方法和抽象类"><a href="#二、抽象方法和抽象类" class="headerlink" title="二、抽象方法和抽象类"></a>二、抽象方法和抽象类</h2><p>为了表示抽象类，我们要用到一个关键字<code>abstract</code>，翻译过来就是抽象的。</p><p><code>abstract</code>修饰的方法是<strong>抽象方法</strong>：只有方法名，没有方法体。<br><code>abstract</code>修饰的类是<strong>抽象类</strong>：如果一个类包含抽象方法，那它必须是抽象类。反过来，抽象类里不一定有抽象方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 抽象方法</span></span><br><span class="line">修饰符 <span class="keyword">abstract</span> 返回值类型 方法名(参数列表);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 抽象类</span></span><br><span class="line"><span class="keyword">abstract</span> class 类名 &#123;</span><br><span class="line">    <span class="comment">// 可以有普通方法，也可以有抽象方法</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="三、抽象类的使用"><a href="#三、抽象类的使用" class="headerlink" title="三、抽象类的使用"></a>三、抽象类的使用</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Employee</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> String position;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> salary;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Employee</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Employee</span><span class="params">(String id, String name,String position, <span class="type">double</span> salary)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.id = id;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.position=position;</span><br><span class="line">        <span class="built_in">this</span>.salary = salary;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getId</span><span class="params">()</span> &#123; <span class="keyword">return</span> id; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setId</span><span class="params">(String id)</span> &#123; <span class="built_in">this</span>.id = id; &#125;</span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123; <span class="keyword">return</span> name; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123; <span class="built_in">this</span>.name = name; &#125;</span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getPosition</span><span class="params">()</span> &#123; <span class="keyword">return</span> position; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setPosition</span><span class="params">(String position)</span> &#123; <span class="built_in">this</span>.position = position;&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">getSalary</span><span class="params">()</span> &#123; <span class="keyword">return</span> salary; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setSalary</span><span class="params">(<span class="type">double</span> salary)</span> &#123; <span class="built_in">this</span>.salary = salary; &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">clockIn</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(name + <span class="string">&quot;打卡上班&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 这个方法子类必须自己实现</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">work</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Manager</span> <span class="keyword">extends</span> <span class="title class_">Employee</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Manager</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Manager</span><span class="params">(String id, String name,String position, <span class="type">double</span> salary)</span>                 &#123;    </span><br><span class="line">        <span class="built_in">super</span>(id, name,position, salary);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">work</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(getName() + <span class="string">&quot;正在管理其他人&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">programmer</span> <span class="keyword">extends</span> <span class="title class_">Employee</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">programmer</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">public</span>  <span class="title function_">programmer</span><span class="params">(String id, String name, <span class="type">double</span> salary)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(id, name, position,salary);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">work</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(getName() + <span class="string">&quot;编写代码，处理业务&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Manager</span> <span class="variable">m</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Manager</span>(<span class="string">&quot;m001&quot;</span>, <span class="string">&quot;张三&quot;</span>,<span class="string">&quot;管理&quot;</span>, <span class="number">20000</span>);</span><br><span class="line">        m.clockIn();</span><br><span class="line">        m.work();</span><br><span class="line">        </span><br><span class="line">        System.out.println(<span class="string">&quot;---&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="type">programmer</span> <span class="variable">p</span> <span class="operator">=</span> <span class="keyword">new</span>  <span class="title class_">programmer</span>(<span class="string">&quot;c001&quot;</span>, <span class="string">&quot;李四&quot;</span>,<span class="string">&quot;程序员&quot;</span>, <span class="number">8000</span>);</span><br><span class="line">        p.clockIn();</span><br><span class="line">        p.work();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这段代码中，我们先定义了一个抽象员工类，里面不仅定义了员工通用的属性和行为，而且还定义了一个抽象工作方法，这些都会在子类中一一继承实现。两者不同的是，通用的属性和行为直接继承使用，抽象方法则要重写方法实现再使用。</p><h2 id="四、抽象类的特征：有得有失"><a href="#四、抽象类的特征：有得有失" class="headerlink" title="四、抽象类的特征：有得有失"></a>四、抽象类的特征：有得有失</h2><table><thead><tr><th>得</th><th>失</th></tr></thead><tbody><tr><td>抽象类可以定义抽象方法，强制子类实现</td><td>抽象类不能创建对象（不能<code>new</code>）</td></tr></tbody></table><p>对于抽象类，你不能写<code>new Employee()</code>创建抽象类对象，但根据我们前面所学的多态，你可以写<code>Employee e = new Manager()</code>，用父类的类型创建子类的对象。</p><hr><h2 id="五、一句话记住抽象类"><a href="#五、一句话记住抽象类" class="headerlink" title="五、一句话记住抽象类"></a>五、一句话记住抽象类</h2><blockquote><p><strong>抽象类 &#x3D; 把共性的先做了，剩下的你自己看着办。</strong></p></blockquote><hr><h2 id="最后的话"><a href="#最后的话" class="headerlink" title="最后的话"></a>最后的话</h2><p>从这篇文章中，我们可以了解到，抽象就是抽取共性的、重复的特征和行为，在整个员工管理系统中讲工作的内容抽象出来，定义一个抽象方法，在子类继承后，依据实际情况来重写抽象方法，提高代码的运行高效性。关于抽象类的知识就讲解完毕，如果有帮助的话，请一键三连。最后感谢您的查阅！！！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/16/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC7%E7%AF%87%EF%BC%9AJava%E6%8A%BD%E8%B1%A1%E7%B1%BB%E2%80%94%E2%80%94%E6%8A%8A%E5%85%B1%E6%80%A7%E7%9A%84%E5%85%88%E5%81%9A%E4%BA%86%EF%BC%8C%E5%89%A9%E4%B8%8B%E7%9A%84%E4%BD%A0%E8%87%AA%E5%B7%B1%E7%9C%8B%E7%9D%80%E5%8A%9E/</id>
    <link href="https://qixia.site/2026/06/16/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC7%E7%AF%87%EF%BC%9AJava%E6%8A%BD%E8%B1%A1%E7%B1%BB%E2%80%94%E2%80%94%E6%8A%8A%E5%85%B1%E6%80%A7%E7%9A%84%E5%85%88%E5%81%9A%E4%BA%86%EF%BC%8C%E5%89%A9%E4%B8%8B%E7%9A%84%E4%BD%A0%E8%87%AA%E5%B7%B1%E7%9C%8B%E7%9D%80%E5%8A%9E/"/>
    <published>2026-06-15T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="一、为什么需要抽象类？"><a href="#一、为什么需要抽象类？" class="headerlink" title="一、为什么需要抽象类？"></a>一、为什么需要抽象类？</h2><p>假如我们需要编写一个员工管理系统。系统的要求如下：</p>
<fig]]>
    </summary>
    <title>Java基础·第7篇：Java抽象类——把共性的先做了，剩下的你自己看着办</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="MyBatis" scheme="https://qixia.site/tags/MyBatis/"/>
    <content>
      <![CDATA[<blockquote><p>适合完全零基础的新手，用最直白的语言讲清楚 MyBatis 是什么、怎么用，以及必须搞懂的 5 个核心问题。</p></blockquote><h2 id="一、为什么要学-MyBatis？"><a href="#一、为什么要学-MyBatis？" class="headerlink" title="一、为什么要学 MyBatis？"></a>一、为什么要学 MyBatis？</h2><p>在我们以前的学习中，我们用Java操作数据库的方法只有JDBC，我们先回顾一下JDBC是怎么操作数据库的，为了操作数据库我们需要编写加载驱动、建立连接、写 SQL、处理结果集、关闭连接…… 十几行甚至几十行模板代码，不仅枯燥，而且代码量上来之后，出错的概率也增大了。</p><p>从这我们就可以看出JDBC的劣势了，基本就是我们全自动的去操作数据库，JDBC只是一个用Java操作数据库的一个桥梁。本身并没有给我们带来任何便捷，经过技术的迭代，我们看到了Mybatis这项新的技术。</p><p><strong>MyBatis 就是来拯救你的</strong>：你只需要定义接口 + 写 SQL，它自动帮你完成剩下的一切（获取连接、预编译、执行、对象封装）。在一定程度上实现了Java操作数据库的半自动流程，省去我们的麻烦。</p><h2 id="二、环境搭建（IDEA-Maven-MySQL）"><a href="#二、环境搭建（IDEA-Maven-MySQL）" class="headerlink" title="二、环境搭建（IDEA + Maven + MySQL）"></a>二、环境搭建（IDEA + Maven + MySQL）</h2><h3 id="1-创建项目（普通-Maven-项目，不要直接用-Spring-Boot）"><a href="#1-创建项目（普通-Maven-项目，不要直接用-Spring-Boot）" class="headerlink" title="1. 创建项目（普通 Maven 项目，不要直接用 Spring Boot）"></a>1. 创建项目（普通 Maven 项目，不要直接用 Spring Boot）</h3><ul><li>IDEA → New Project → 左侧选 <strong>Java</strong> → 右侧构建系统选 <strong>Maven</strong></li><li>点击 Create</li></ul><h3 id="2-添加依赖（pom-xml）"><a href="#2-添加依赖（pom-xml）" class="headerlink" title="2. 添加依赖（pom.xml）"></a>2. 添加依赖（pom.xml）</h3><p>在pom.xml文件中添加Mybatis的依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- MyBatis 核心 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.mybatis<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mybatis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.5.13<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- MySQL 驱动 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>8.0.33<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Lombok（选装，简化实体类） --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.18.30<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>provided<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="3-准备数据库表（MySQL）"><a href="#3-准备数据库表（MySQL）" class="headerlink" title="3. 准备数据库表（MySQL）"></a>3. 准备数据库表（MySQL）</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> <span class="keyword">user</span> (</span><br><span class="line">    id <span class="type">INT</span> <span class="keyword">PRIMARY KEY</span> AUTO_INCREMENT,</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">50</span>),</span><br><span class="line">    age <span class="type">INT</span></span><br><span class="line">);</span><br><span class="line"><span class="keyword">INSERT INTO</span> <span class="keyword">user</span> (name, age) <span class="keyword">VALUES</span> (<span class="string">&#x27;张三&#x27;</span>, <span class="number">25</span>), (<span class="string">&#x27;李四&#x27;</span>, <span class="number">30</span>);</span><br></pre></td></tr></table></figure><h3 id="4-项目结构（最终长这样）"><a href="#4-项目结构（最终长这样）" class="headerlink" title="4. 项目结构（最终长这样）"></a>4. 项目结构（最终长这样）</h3><p><img src="/img/MyBatis_1/p2.png" alt="图片描述"></p><h2 id="三、第一个可运行的-MyBatis-程序"><a href="#三、第一个可运行的-MyBatis-程序" class="headerlink" title="三、第一个可运行的 MyBatis 程序"></a>三、第一个可运行的 MyBatis 程序</h2><h3 id="3-1-实体类-User-java"><a href="#3-1-实体类-User-java" class="headerlink" title="3.1 实体类 User.java"></a>3.1 实体类 User.java</h3><p>这里使用lombok来简化一下代码，降低我们的工作量：<br>Lombok 在编译时根据注解自动生成 getter、setter 等方法的字节码，所以你不需要手写，但最终 .class 文件里它们都存在。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.example.pojo;</span><br><span class="line"><span class="keyword">import</span> lombok.Data;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-2-Mapper-接口-UserMapper-java"><a href="#3-2-Mapper-接口-UserMapper-java" class="headerlink" title="3.2 Mapper 接口 UserMapper.java"></a>3.2 Mapper 接口 UserMapper.java</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.example.mapper;</span><br><span class="line"><span class="keyword">import</span> org.example.pojo.User;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    List&lt;User&gt; <span class="title function_">findAll</span><span class="params">()</span>;</span><br><span class="line">    User <span class="title function_">findById</span><span class="params">(Integer id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-3-核心配置文件-mybatis-config-xml"><a href="#3-3-核心配置文件-mybatis-config-xml" class="headerlink" title="3.3 核心配置文件 mybatis-config.xml"></a>3.3 核心配置文件 mybatis-config.xml</h3><p>放在 <code>src/main/resources</code> 下：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span> ?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">configuration</span></span></span><br><span class="line"><span class="meta">  <span class="keyword">PUBLIC</span> <span class="string">&quot;-//mybatis.org//DTD Config 3.0//EN&quot;</span></span></span><br><span class="line"><span class="meta">  <span class="string">&quot;http://mybatis.org/dtd/mybatis-3-config.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">environments</span> <span class="attr">default</span>=<span class="string">&quot;development&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">environment</span> <span class="attr">id</span>=<span class="string">&quot;development&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">transactionManager</span> <span class="attr">type</span>=<span class="string">&quot;JDBC&quot;</span>/&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">dataSource</span> <span class="attr">type</span>=<span class="string">&quot;POOLED&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;driver&quot;</span> <span class="attr">value</span>=<span class="string">&quot;com.mysql.cj.jdbc.Driver&quot;</span>/&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;url&quot;</span> <span class="attr">value</span>=<span class="string">&quot;jdbc:mysql://localhost:3306/你的库名?useSSL=false<span class="symbol">&amp;amp;</span>serverTimezone=UTC<span class="symbol">&amp;amp;</span>allowPublicKeyRetrieval=true&quot;</span>/&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span> <span class="attr">value</span>=<span class="string">&quot;root&quot;</span>/&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;password&quot;</span> <span class="attr">value</span>=<span class="string">&quot;你的密码&quot;</span>/&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">dataSource</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">environment</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">environments</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mappers</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">mapper</span> <span class="attr">resource</span>=<span class="string">&quot;UserMapper.xml&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">mappers</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：一定要根据自己本地的数据库的配置文件将端口、库名和密码填写好</p></blockquote><h3 id="3-4-SQL-映射文件-UserMapper-xml"><a href="#3-4-SQL-映射文件-UserMapper-xml" class="headerlink" title="3.4 SQL 映射文件 UserMapper.xml"></a>3.4 SQL 映射文件 UserMapper.xml</h3><p>也放在 <code>src/main/resources</code> 下：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span> ?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">mapper</span></span></span><br><span class="line"><span class="meta">  <span class="keyword">PUBLIC</span> <span class="string">&quot;-//mybatis.org//DTD Mapper 3.0//EN&quot;</span></span></span><br><span class="line"><span class="meta">  <span class="string">&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;org.example.mapper.UserMapper&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;org.example.pojo.User&quot;</span>&gt;</span></span><br><span class="line">        SELECT * FROM user</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;org.example.pojo.User&quot;</span>&gt;</span></span><br><span class="line">        SELECT * FROM user WHERE id = #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="3-5-测试类-MyBatisTest-java"><a href="#3-5-测试类-MyBatisTest-java" class="headerlink" title="3.5 测试类 MyBatisTest.java"></a>3.5 测试类 MyBatisTest.java</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.example;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.io.Resources;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.session.SqlSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.session.SqlSessionFactory;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.session.SqlSessionFactoryBuilder;</span><br><span class="line"><span class="keyword">import</span> org.example.mapper.UserMapper;</span><br><span class="line"><span class="keyword">import</span> org.example.pojo.User;</span><br><span class="line"><span class="keyword">import</span> java.io.InputStream;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBatisTest</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">InputStream</span> <span class="variable">is</span> <span class="operator">=</span> Resources.getResourceAsStream(<span class="string">&quot;mybatis-config.xml&quot;</span>);</span><br><span class="line">        <span class="type">SqlSessionFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SqlSessionFactoryBuilder</span>().build(is);</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">SqlSession</span> <span class="variable">session</span> <span class="operator">=</span> factory.openSession()) &#123;</span><br><span class="line">            <span class="type">UserMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> session.getMapper(UserMapper.class);</span><br><span class="line">            List&lt;User&gt; users = mapper.findAll();</span><br><span class="line">            users.forEach(System.out::println);</span><br><span class="line">            <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> mapper.findById(<span class="number">1</span>);</span><br><span class="line">            System.out.println(user);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行 main 方法，如果控制台打印出数据库里的用户数据，恭喜你，MyBatis 已经跑通了！</p><h2 id="四、新手必踩的坑-解决方法"><a href="#四、新手必踩的坑-解决方法" class="headerlink" title="四、新手必踩的坑 &amp; 解决方法"></a>四、新手必踩的坑 &amp; 解决方法</h2><h3 id="坑1：ClassNotFoundException-com-demo-pojo-User"><a href="#坑1：ClassNotFoundException-com-demo-pojo-User" class="headerlink" title="坑1：ClassNotFoundException: com.demo.pojo.User"></a>坑1：<code>ClassNotFoundException: com.demo.pojo.User</code></h3><p><strong>原因</strong>：你在 <code>UserMapper.xml</code> 中写的 <code>resultType</code> 包路径与实体类实际包路径不一致（例如示例中用了 <code>com.demo.pojo.User</code>，但你的实体类在 <code>org.example.pojo.User</code>）。</p><p><strong>解决</strong>：<strong>必须完全一致</strong>，XML 中的所有全限定类名（<code>namespace</code>、<code>resultType</code>、<code>parameterType</code>）都要与 Java 代码中的包名+类名一模一样。</p><h3 id="坑2：Communications-link-failure"><a href="#坑2：Communications-link-failure" class="headerlink" title="坑2：Communications link failure"></a>坑2：<code>Communications link failure</code></h3><p><strong>现象</strong>：IDEA 自带的 Database 工具能连上 MySQL，但 MyBatis 程序报连接失败。</p><p><strong>原因</strong>：MySQL 8.0 需要额外参数才能正常连接，尤其是时区和公钥检索。</p><p><strong>解决</strong>：在 <code>url</code> 中添加：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">?useSSL=false&amp;amp;serverTimezone=UTC&amp;amp;allowPublicKeyRetrieval=true</span><br></pre></td></tr></table></figure><p>注意 XML 中的 <code>&amp;</code> 必须转义为 <code>&amp;amp;</code>。</p><h2 id="五、五个你必须搞懂的核心问题（新手进阶）"><a href="#五、五个你必须搞懂的核心问题（新手进阶）" class="headerlink" title="五、五个你必须搞懂的核心问题（新手进阶）"></a>五、五个你必须搞懂的核心问题（新手进阶）</h2><p>在跑通代码之后，你对 MyBatis 可能还是“一知半解”。下面这五个问题，搞懂了才算真正入门。</p><h3 id="1-SqlSessionFactoryBuilder-→-SqlSessionFactory-→-SqlSession-分别是什么？"><a href="#1-SqlSessionFactoryBuilder-→-SqlSessionFactory-→-SqlSession-分别是什么？" class="headerlink" title="1. SqlSessionFactoryBuilder → SqlSessionFactory → SqlSession 分别是什么？"></a>1. <code>SqlSessionFactoryBuilder</code> → <code>SqlSessionFactory</code> → <code>SqlSession</code> 分别是什么？</h3><ul><li><strong>SqlSessionFactoryBuilder</strong>：读配置文件，造一个工厂（一次性工具）。</li><li><strong>SqlSessionFactory</strong>：工厂，负责生产 SqlSession，整个应用通常只有一个（单例）。</li><li><strong>SqlSession</strong>：代表一次数据库会话，可以执行 SQL、获取 Mapper，用完必须关闭。</li></ul><p><strong>生活类比</strong>：  </p><ul><li>工厂图纸 → SqlSessionFactoryBuilder  </li><li>工厂本身 → SqlSessionFactory  </li><li>工厂生产的一个工人（干完活就下班） → SqlSession</li></ul><h3 id="2-为什么要用-try-SqlSession-session-factory-openSession-？"><a href="#2-为什么要用-try-SqlSession-session-factory-openSession-？" class="headerlink" title="2. 为什么要用 try (SqlSession session = factory.openSession())？"></a>2. 为什么要用 <code>try (SqlSession session = factory.openSession())</code>？</h3><p>这是 try-with-resources 语法，会自动调用 <code>session.close()</code> 释放数据库连接。如果忘记关闭，他会自动关闭数据库资源，不会导致程序卡死。即一个即开即用，不用则闭的一个盒子。</p><h3 id="3-id-和-id-有什么区别？"><a href="#3-id-和-id-有什么区别？" class="headerlink" title="3. #{id} 和 ${id} 有什么区别？"></a>3. <code>#{id}</code> 和 <code>${id}</code> 有什么区别？</h3><ul><li><code>#{id}</code>：预编译占位符，生成 <code>?</code>，安全防 SQL 注入。<strong>99% 的情况都用这个</strong>。</li><li><code>${id}</code>：直接字符串替换，有注入风险。只有极少数场景（如动态表名、动态列名）才用到。</li></ul><h3 id="4-Mapper-接口为什么不用写实现类？namespace-id-是地址吗？"><a href="#4-Mapper-接口为什么不用写实现类？namespace-id-是地址吗？" class="headerlink" title="4. Mapper 接口为什么不用写实现类？namespace + id 是地址吗？"></a>4. Mapper 接口为什么不用写实现类？<code>namespace + id</code> 是地址吗？</h3><p><strong>核心原理</strong>：MyBatis 使用 JDK 动态代理，在内存中自动生成一个实现了该接口的代理对象。当你调用 <code>mapper.findAll()</code> 时，代理对象根据 <code>namespace</code>（接口全限定名）+ <code>id</code>（方法名）找到对应的 SQL 语句并执行。</p><p><strong>所以</strong>：<code>namespace + id</code> 就像门牌号（小区名+楼栋号），MyBatis 通过它精确定位到 XML 中那条 SQL。</p><h3 id="5-为什么-resultType-必须写全限定类名（如-org-example-pojo-User）？"><a href="#5-为什么-resultType-必须写全限定类名（如-org-example-pojo-User）？" class="headerlink" title="5. 为什么 resultType 必须写全限定类名（如 org.example.pojo.User）？"></a>5. 为什么 <code>resultType</code> 必须写全限定类名（如 <code>org.example.pojo.User</code>）？</h3><p>因为 MyBatis 需要通过反射创建该类的实例，并把数据库列的值填充到对象的属性中。如果你只写 <code>User</code>，MyBatis 不知道它在哪个包下，就会报 <code>ClassNotFoundException</code>。</p><h2 id="六、写在最后"><a href="#六、写在最后" class="headerlink" title="六、写在最后"></a>六、写在最后</h2><p>MyBatis 并不难，难的是没有人帮你把那些“理所当然”的底层机制讲清楚。今天我们从零开始，亲手搭建了一个能跑的 MyBatis 程序，也把五个最核心的困惑彻底搞明白了。</p><blockquote><p>如果你按照本文一步步操作，仍然遇到问题，欢迎在评论区留言，我会尽力帮你解决。</p></blockquote>]]>
    </content>
    <id>https://qixia.site/2026/06/16/MyBatis%20%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E4%BB%8E%E9%9B%B6%E6%90%AD%E5%BB%BA%E5%88%B0%E6%9F%A5%E8%AF%A2%E5%AE%9E%E7%8E%B0%EF%BC%8C%E9%99%84%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/</id>
    <link href="https://qixia.site/2026/06/16/MyBatis%20%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E4%BB%8E%E9%9B%B6%E6%90%AD%E5%BB%BA%E5%88%B0%E6%9F%A5%E8%AF%A2%E5%AE%9E%E7%8E%B0%EF%BC%8C%E9%99%84%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/"/>
    <published>2026-06-15T16:00:00.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>适合完全零基础的新手，用最直白的语言讲清楚 MyBatis 是什么、怎么用，以及必须搞懂的 5 个核心问题。</p>
</blockquote>
<h2 id="一、为什么要学-MyBatis？"><a href="#一、为什么要学-MyBatis]]>
    </summary>
    <title>MyBatis 入门系列（一）：从零搭建到查询实现，附踩坑记录</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="MyBatis" scheme="https://qixia.site/tags/MyBatis/"/>
    <content>
      <![CDATA[<h2 id="一、为什么需要动态-SQL？"><a href="#一、为什么需要动态-SQL？" class="headerlink" title="一、为什么需要动态 SQL？"></a>一、为什么需要动态 SQL？</h2><p>在真实项目中，一个查询接口往往要支持多种条件组合：比如用户列表页，可以按姓名模糊查询、按年龄精确查询、两者组合查询，或者什么都不填查询全部。如果用硬编码写 SQL，你可能需要写 4 个不同的方法，代码臃肿且难维护。</p><p><strong>动态 SQL</strong> 让你在 XML 中根据传入的参数“智能拼接” SQL 语句，一个方法搞定所有情况。</p><h2 id="二、核心标签一览"><a href="#二、核心标签一览" class="headerlink" title="二、核心标签一览"></a>二、核心标签一览</h2><table><thead><tr><th>标签</th><th>作用</th><th>类比</th></tr></thead><tbody><tr><td><code>&lt;if&gt;</code></td><td>条件判断，满足时才拼 SQL 片段</td><td>Java 中的 <code>if</code></td></tr><tr><td><code>&lt;where&gt;</code></td><td>自动处理 <code>WHERE</code> 关键字和多余的 <code>AND</code>&#x2F;<code>OR</code></td><td>智能的 <code>WHERE</code> 生成器</td></tr><tr><td><code>&lt;set&gt;</code></td><td>动态生成 <code>SET</code> 子句，自动去掉末尾逗号</td><td>智能的 <code>SET</code> 生成器</td></tr><tr><td><code>&lt;choose&gt;/&lt;when&gt;/&lt;otherwise&gt;</code></td><td>多选一，只执行第一个满足的条件</td><td>Java 中的 <code>switch</code></td></tr><tr><td><code>&lt;foreach&gt;</code></td><td>遍历集合，生成 <code>IN</code> 子句或批量 <code>VALUES</code></td><td>循环拼接</td></tr></tbody></table><h2 id="三、详细讲解与代码示例"><a href="#三、详细讲解与代码示例" class="headerlink" title="三、详细讲解与代码示例"></a>三、详细讲解与代码示例</h2><h3 id="1-：条件判断"><a href="#1-：条件判断" class="headerlink" title="1. &lt;if&gt;：条件判断"></a>1. <code>&lt;if&gt;</code>：条件判断</h3><p>最简单的动态标签，<code>test</code> 里写 OGNL 表达式。</p><p><strong>场景</strong>：查询用户，姓名可选、年龄可选。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUser&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">    SELECT * FROM user WHERE 1=1</span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;name != null and name != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">        AND name LIKE CONCAT(&#x27;%&#x27;, #&#123;name&#125;, &#x27;%&#x27;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;age != null&quot;</span>&gt;</span></span><br><span class="line">        AND age = #&#123;age&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><blockquote><p>⚠️ 为什么有 <code>WHERE 1=1</code>？为了防止所有条件都不满足时 SQL 变成 <code>SELECT * FROM user WHERE</code>（语法错误）。但 MyBatis 提供了更好的 <code>&lt;where&gt;</code> 标签。</p></blockquote><h3 id="2-：智能-WHERE-子句"><a href="#2-：智能-WHERE-子句" class="headerlink" title="2. &lt;where&gt;：智能 WHERE 子句"></a>2. <code>&lt;where&gt;</code>：智能 WHERE 子句</h3><p>自动：</p><ul><li>如果至少有一个条件成立，就添加 <code>WHERE</code> 关键字。</li><li>去除第一个条件前多余的 <code>AND</code> 或 <code>OR</code>。</li></ul><p><strong>改进上面的例子</strong>：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUser&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">    SELECT * FROM user</span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;name != null and name != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">            AND name LIKE CONCAT(&#x27;%&#x27;, #&#123;name&#125;, &#x27;%&#x27;)</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;age != null&quot;</span>&gt;</span></span><br><span class="line">            AND age = #&#123;age&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>无论条件是否成立，最终 SQL 都是正确的。例如只有 <code>name</code> 条件时生成：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="keyword">LIKE</span> CONCAT(<span class="string">&#x27;%&#x27;</span>, <span class="string">&#x27;张&#x27;</span>, <span class="string">&#x27;%&#x27;</span>)</span><br></pre></td></tr></table></figure><h3 id="3-：动态-UPDATE-子句"><a href="#3-：动态-UPDATE-子句" class="headerlink" title="3. &lt;set&gt;：动态 UPDATE 子句"></a>3. <code>&lt;set&gt;</code>：动态 UPDATE 子句</h3><p>只更新传入的非空字段，避免把未传字段覆盖为 <code>NULL</code>。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;updateUser&quot;</span>&gt;</span></span><br><span class="line">    UPDATE user</span><br><span class="line">    <span class="tag">&lt;<span class="name">set</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;name != null and name != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">            name = #&#123;name&#125;,</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;age != null&quot;</span>&gt;</span></span><br><span class="line">            age = #&#123;age&#125;,</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;email != null&quot;</span>&gt;</span></span><br><span class="line">            email = #&#123;email&#125;,</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">set</span>&gt;</span></span><br><span class="line">    WHERE id = #&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">update</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li><code>&lt;set&gt;</code> 会自动去掉末尾多余的逗号。</li><li>如果所有条件都不满足，<code>&lt;set&gt;</code> 不会生成 <code>SET</code> 子句，SQL 会报错，因此业务上要至少保证一个字段更新。</li></ul><h3 id="4-：分支选择"><a href="#4-：分支选择" class="headerlink" title="4. &lt;choose&gt;/&lt;when&gt;/&lt;otherwise&gt;：分支选择"></a>4. <code>&lt;choose&gt;/&lt;when&gt;/&lt;otherwise&gt;</code>：分支选择</h3><p>只执行第一个满足的 <code>&lt;when&gt;</code>，类似 <code>switch</code>。常用于多条件互斥的场景。</p><p><strong>场景</strong>：根据传入的排序字段动态决定 <code>ORDER BY</code>。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUsers&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">    SELECT * FROM user</span><br><span class="line">    <span class="tag">&lt;<span class="name">choose</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot;orderBy == &#x27;name&#x27;&quot;</span>&gt;</span></span><br><span class="line">            ORDER BY name ASC</span><br><span class="line">        <span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot;orderBy == &#x27;age&#x27;&quot;</span>&gt;</span></span><br><span class="line">            ORDER BY age ASC</span><br><span class="line">        <span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">otherwise</span>&gt;</span></span><br><span class="line">            ORDER BY id DESC</span><br><span class="line">        <span class="tag">&lt;/<span class="name">otherwise</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">choose</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="5-：遍历集合"><a href="#5-：遍历集合" class="headerlink" title="5. &lt;foreach&gt;：遍历集合"></a>5. <code>&lt;foreach&gt;</code>：遍历集合</h3><p>最常用的场景：<strong>批量删除</strong>（<code>IN</code> 子句）和<strong>批量插入</strong>。</p><h4 id="5-1-批量删除（IN）"><a href="#5-1-批量删除（IN）" class="headerlink" title="5.1 批量删除（IN）"></a>5.1 批量删除（IN）</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">delete</span> <span class="attr">id</span>=<span class="string">&quot;deleteByIds&quot;</span>&gt;</span></span><br><span class="line">    DELETE FROM user WHERE id IN</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;ids&quot;</span> <span class="attr">item</span>=<span class="string">&quot;id&quot;</span> <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span>&gt;</span></span><br><span class="line">        #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">delete</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li><code>collection</code>：Mapper 方法中参数的名字（需要 <code>@Param(&quot;ids&quot;)</code>）。</li><li><code>item</code>：循环中每个元素的临时变量名。</li><li><code>open</code> &#x2F; <code>close</code>：循环前后拼接的字符。</li><li><code>separator</code>：元素间的分隔符。</li></ul><h4 id="5-2-批量插入（多条-VALUES）"><a href="#5-2-批量插入（多条-VALUES）" class="headerlink" title="5.2 批量插入（多条 VALUES）"></a>5.2 批量插入（多条 VALUES）</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">&quot;insertBatch&quot;</span>&gt;</span></span><br><span class="line">    INSERT INTO user (name, age) VALUES</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span> <span class="attr">item</span>=<span class="string">&quot;user&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span>&gt;</span></span><br><span class="line">        (#&#123;user.name&#125;, #&#123;user.age&#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：<code>separator</code> 是 <code>,</code>，会在两条 <code>(…)</code> 之间添加逗号，最终形成 <code>VALUES (…), (…), (…)</code>。</p></blockquote><h2 id="四、OGNL-表达式小贴士"><a href="#四、OGNL-表达式小贴士" class="headerlink" title="四、OGNL 表达式小贴士"></a>四、OGNL 表达式小贴士</h2><p><code>&lt;if test=&quot;...&quot;&gt;</code> 里的表达式常用写法：</p><table><thead><tr><th>类型</th><th>条件写法</th><th>说明</th></tr></thead><tbody><tr><td>字符串非空</td><td><code>name != null and name != &#39;&#39;</code></td><td>注意空串判断</td></tr><tr><td>数字非空</td><td><code>age != null</code></td><td>数字不会有空串</td></tr><tr><td>字符串比较</td><td><code>name == &#39;admin&#39;</code></td><td>单引号</td></tr><tr><td>逻辑运算</td><td><code>and / or / !</code></td><td>例如 <code>!(age &gt; 100)</code></td></tr><tr><td>调用方法</td><td><code>name.length() &gt; 0</code></td><td>尽量用简单的表达式</td></tr></tbody></table><blockquote><p>注意：XML 中 <code>&lt;</code> 和 <code>&gt;</code> 需要转义：<code>&amp;lt;</code> 和 <code>&amp;gt;</code>。例如 <code>age &amp;lt; 18</code>。</p></blockquote><h2 id="五、完整实战案例：用户动态查询-动态更新"><a href="#五、完整实战案例：用户动态查询-动态更新" class="headerlink" title="五、完整实战案例：用户动态查询 + 动态更新"></a>五、完整实战案例：用户动态查询 + 动态更新</h2><p><strong>Mapper 接口</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line">    <span class="comment">// 动态查询（支持 name, age 可选）</span></span><br><span class="line">    List&lt;User&gt; <span class="title function_">findUser</span><span class="params">(<span class="meta">@Param(&quot;name&quot;)</span> String name, <span class="meta">@Param(&quot;age&quot;)</span> Integer age)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 动态更新（只更新传入的非空字段）</span></span><br><span class="line">    <span class="type">int</span> <span class="title function_">updateUser</span><span class="params">(User user)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>XML 映射</strong>：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;com.example.mapper.UserMapper&quot;</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUser&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.example.User&quot;</span>&gt;</span></span><br><span class="line">        SELECT * FROM user</span><br><span class="line">        <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;name != null and name != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">                AND name LIKE CONCAT(&#x27;%&#x27;, #&#123;name&#125;, &#x27;%&#x27;)</span><br><span class="line">            <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;age != null&quot;</span>&gt;</span></span><br><span class="line">                AND age = #&#123;age&#125;</span><br><span class="line">            <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;updateUser&quot;</span>&gt;</span></span><br><span class="line">        UPDATE user</span><br><span class="line">        <span class="tag">&lt;<span class="name">set</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;name != null and name != &#x27;&#x27;&quot;</span>&gt;</span></span><br><span class="line">                name = #&#123;name&#125;,</span><br><span class="line">            <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;age != null&quot;</span>&gt;</span></span><br><span class="line">                age = #&#123;age&#125;,</span><br><span class="line">            <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">set</span>&gt;</span></span><br><span class="line">        WHERE id = #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">update</span>&gt;</span></span><br><span class="line">    </span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>测试代码</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 只按姓名查询</span></span><br><span class="line">List&lt;User&gt; users = mapper.findUser(<span class="string">&quot;张&quot;</span>, <span class="literal">null</span>);</span><br><span class="line"><span class="comment">// 按姓名+年龄查询</span></span><br><span class="line">List&lt;User&gt; users2 = mapper.findUser(<span class="string">&quot;张&quot;</span>, <span class="number">25</span>);</span><br><span class="line"><span class="comment">// 什么都不传，查询全部</span></span><br><span class="line">List&lt;User&gt; all = mapper.findUser(<span class="literal">null</span>, <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 动态更新：只修改姓名</span></span><br><span class="line"><span class="type">User</span> <span class="variable">u</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">u.setId(<span class="number">1</span>);</span><br><span class="line">u.setName(<span class="string">&quot;新名字&quot;</span>);</span><br><span class="line">mapper.updateUser(u);</span><br><span class="line">session.commit();</span><br></pre></td></tr></table></figure><h2 id="六、常见错误与避坑指南"><a href="#六、常见错误与避坑指南" class="headerlink" title="六、常见错误与避坑指南"></a>六、常见错误与避坑指南</h2><ol><li><p><strong><code>WHERE 1=1</code> 与 <code>&lt;where&gt;</code> 混用</strong><br>如果用 <code>&lt;where&gt;</code> 就不要手写 <code>WHERE 1=1</code>，否则 <code>&lt;where&gt;</code> 无法智能移除 <code>AND</code>，可能导致 <code>WHERE AND name = ...</code> 的语法错误。</p></li><li><p><strong><code>&lt;set&gt;</code> 内没有条件时不生成 SET</strong><br>如果所有 <code>&lt;if&gt;</code> 都不满足，最终 SQL 是 <code>UPDATE user WHERE id = ?</code>（缺少 SET 子句），会报错。业务上应至少保证一个字段被更新。</p></li><li><p><strong><code>&lt;foreach&gt;</code> 的 <code>collection</code> 名字错误</strong>  </p><ul><li>如果不加 <code>@Param</code>，List 参数默认名字是 <code>list</code>，数组是 <code>array</code>。  </li><li>推荐总是用 <code>@Param</code> 显式命名，然后 <code>collection</code> 写这个名字。</li></ul></li><li><p><strong>OGNL 表达式中字符串比较漏掉空串</strong><br>对于字符串参数，除了 <code>!= null</code>，通常还要 <code>!= &#39;&#39;</code>，否则前端传空字符串时条件依然成立。</p></li></ol><h2 id="七、总结：动态-SQL-到底有多重要？"><a href="#七、总结：动态-SQL-到底有多重要？" class="headerlink" title="七、总结：动态 SQL 到底有多重要？"></a>七、总结：动态 SQL 到底有多重要？</h2><ul><li>它让后端接口可以 <strong>一个方法支持无限种查询组合</strong>，无需写多个 Mapper 方法。</li><li>它是 MyBatis 区别于 JPA 的一大优势，尤其适合复杂报表、多条件筛选场景。</li><li>你不需要成为 OGNL 专家，掌握常见的 <code>&lt;if&gt;</code>、<code>&lt;where&gt;</code>、<code>&lt;foreach&gt;</code> 就能覆盖 90% 的需求。</li></ul><p><strong>学完动态 SQL，你已经能写出灵活、可维护的数据库访问层。</strong> 下一步可以学习分页插件或 Spring Boot 整合，将 MyBatis 用到真实项目中。</p><blockquote><p>本文是 MyBatis 学习系列的第二篇。上一篇：[从零开始学 MyBatis：环境搭建 + 第一个查询 + 5个核心问题]（如果你写了可以放链接）</p></blockquote>]]>
    </content>
    <id>https://qixia.site/2026/06/16/MyBatis%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E5%8A%A8%E6%80%81%20SQL%20%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97%EF%BC%9A%E5%91%8A%E5%88%AB%E7%A1%AC%E7%BC%96%E7%A0%81%EF%BC%8C%E4%B8%80%E4%B8%AA%E6%96%B9%E6%B3%95%E6%90%9E%E5%AE%9A%E6%89%80%E6%9C%89%E6%9F%A5%E8%AF%A2/</id>
    <link href="https://qixia.site/2026/06/16/MyBatis%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E5%8A%A8%E6%80%81%20SQL%20%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97%EF%BC%9A%E5%91%8A%E5%88%AB%E7%A1%AC%E7%BC%96%E7%A0%81%EF%BC%8C%E4%B8%80%E4%B8%AA%E6%96%B9%E6%B3%95%E6%90%9E%E5%AE%9A%E6%89%80%E6%9C%89%E6%9F%A5%E8%AF%A2/"/>
    <published>2026-06-15T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="一、为什么需要动态-SQL？"><a href="#一、为什么需要动态-SQL？" class="headerlink" title="一、为什么需要动态 SQL？"></a>一、为什么需要动态 SQL？</h2><p>在真实项目中，一个查询接口往往要支持多种条件]]>
    </summary>
    <title>MyBatis入门系列（三）：动态 SQL 完全指南：告别硬编码，一个方法搞定所有查询</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="JDBC" scheme="https://qixia.site/tags/JDBC/"/>
    <content>
      <![CDATA[<h3 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h3><blockquote><p>这篇文章我们要学习怎么用Java程序来操作数据库，显而易见的是，我们不能直接跟MySQL说话，而是需要通过JDBC这个中间层。JDBC就是Java用来连接数据库的一套标准接口。</p></blockquote><h3 id="案例展示"><a href="#案例展示" class="headerlink" title="案例展示"></a>案例展示</h3><p>在写代码之前，我们需要简单配置一下：<br>1、创建项目和模块<br>2、在模块下面我们需要一个文件夹去存放JDBC驱动包<br>3、最后在src文件夹下写我们的代码</p><p>4、我们需要编写的代码如下：<br>由于关于JDBC这项技术，我们只要做到会用和看的懂的情况下，我们只需要看看知道怎么操作就行</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 注册驱动（JDBC 4.0后可以省略）</span></span><br><span class="line">Class.forName(<span class="string">&quot;com.mysql.cj.jdbc.Driver&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 获取连接（MySQL的数据库、密码和用户名）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">url</span> <span class="operator">=</span> <span class="string">&quot;jdbc:mysql://localhost:3306/test&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> <span class="string">&quot;root&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">password</span> <span class="operator">=</span> <span class="string">&quot;123456&quot;</span>;</span><br><span class="line"><span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> DriverManager.getConnection(url, username, password);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 执行SQL</span></span><br><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;SELECT * FROM user WHERE id = ?&quot;</span>;</span><br><span class="line"><span class="type">PreparedStatement</span> <span class="variable">pstmt</span> <span class="operator">=</span> conn.prepareStatement(sql);</span><br><span class="line">pstmt.setInt(<span class="number">1</span>, <span class="number">1</span>);</span><br><span class="line"><span class="type">ResultSet</span> <span class="variable">rs</span> <span class="operator">=</span> pstmt.executeQuery();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. 处理结果</span></span><br><span class="line"><span class="keyword">while</span> (rs.next()) &#123;</span><br><span class="line">    System.out.println(rs.getString(<span class="string">&quot;name&quot;</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 5. 关闭资源</span></span><br><span class="line">rs.close();</span><br><span class="line">pstmt.close();</span><br><span class="line">conn.close();</span><br></pre></td></tr></table></figure><h3 id="各大API作用"><a href="#各大API作用" class="headerlink" title="各大API作用"></a>各大API作用</h3><table><thead><tr><th>API</th><th>作用</th></tr></thead><tbody><tr><td><code>DriverManager</code></td><td>注册驱动，获取数据库连接</td></tr><tr><td><code>Connection</code></td><td>代表与数据库的连接，用来创建Statement</td></tr><tr><td><code>PreparedStatement</code></td><td>执行SQL（预编译，防注入）</td></tr><tr><td><code>ResultSet</code></td><td>存放查询结果，像一张临时表</td></tr></tbody></table><p>这些API我们只需要知道就可以了，其中我们需要更多了解的是PreparedStatement和Statement。在作用上，两者差不多，但是PreparedStatement是优于Statement的。</p><h3 id="Statement和PreparedStatement的优先级"><a href="#Statement和PreparedStatement的优先级" class="headerlink" title="Statement和PreparedStatement的优先级"></a>Statement和PreparedStatement的优先级</h3><h4 id="Statement的劣势"><a href="#Statement的劣势" class="headerlink" title="Statement的劣势"></a>Statement的劣势</h4><p>Statement的作用就是拼接字符串，案例如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> request.getParameter(<span class="string">&quot;name&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;SELECT * FROM user WHERE name = &#x27;&quot;</span> + name + <span class="string">&quot;&#x27;&quot;</span>;</span><br><span class="line"><span class="type">Statement</span> <span class="variable">stmt</span> <span class="operator">=</span> conn.createStatement();</span><br><span class="line"><span class="type">ResultSet</span> <span class="variable">rs</span> <span class="operator">=</span> stmt.executeQuery(sql);</span><br></pre></td></tr></table></figure><p>这时候，如果有恶意用户输入 <code>&#39; OR &#39;1&#39;=&#39;1</code>，拼出来的SQL就变成：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;&#x27;</span> <span class="keyword">OR</span> <span class="string">&#x27;1&#x27;</span><span class="operator">=</span><span class="string">&#x27;1&#x27;</span></span><br></pre></td></tr></table></figure><p>由于<code>&#39;1&#39;=&#39;1&#39;</code>永远为真，数据库就会返回所有用户，导致数据泄露，这就是我们要防范的SQL注入技术。</p><h4 id="PreparedStatement的优势"><a href="#PreparedStatement的优势" class="headerlink" title="PreparedStatement的优势"></a>PreparedStatement的优势</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="string">&quot;SELECT * FROM user WHERE name = ?&quot;</span>;</span><br><span class="line"><span class="type">PreparedStatement</span> <span class="variable">pstmt</span> <span class="operator">=</span> conn.prepareStatement(sql);</span><br><span class="line">pstmt.setString(<span class="number">1</span>, name);</span><br><span class="line"><span class="type">ResultSet</span> <span class="variable">rs</span> <span class="operator">=</span> pstmt.executeQuery();</span><br></pre></td></tr></table></figure><p><code>?</code>是占位符。PreparedStatement在<strong>预编译阶段</strong>就把SQL结构固定下来了。用户输入的内容（包括<code>&#39; OR &#39;1&#39;=&#39;1</code>）仅仅是个字符串，只能作为参数传递，不会改变SQL结构，所以注入不进去。</p><h3 id="最后的话"><a href="#最后的话" class="headerlink" title="最后的话"></a>最后的话</h3><p>关于JDBC这一技术的学习就到这了，只需要做到看得懂会用就行，不必纠结细节，在实际开发中尽量使用PreparedStatement就行了。</p>]]>
    </content>
    <id>https://qixia.site/2026/06/11/Java%E9%AB%98%E7%BA%A7-%E7%AC%AC%E4%B8%80%E7%AF%87%EF%BC%9AJDBC%E6%93%8D%E4%BD%9C%E6%95%B0%E6%8D%AE%E5%BA%93%E2%80%94%E2%80%94%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E7%94%A8Statement%EF%BC%9F%E2%80%94%E2%80%94PreparedStatement%E9%98%B2SQL%E6%B3%A8%E5%85%A5%E4%B8%80%E7%AF%87%E6%90%9E%E6%87%82/</id>
    <link href="https://qixia.site/2026/06/11/Java%E9%AB%98%E7%BA%A7-%E7%AC%AC%E4%B8%80%E7%AF%87%EF%BC%9AJDBC%E6%93%8D%E4%BD%9C%E6%95%B0%E6%8D%AE%E5%BA%93%E2%80%94%E2%80%94%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E7%94%A8Statement%EF%BC%9F%E2%80%94%E2%80%94PreparedStatement%E9%98%B2SQL%E6%B3%A8%E5%85%A5%E4%B8%80%E7%AF%87%E6%90%9E%E6%87%82/"/>
    <published>2026-06-10T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h3 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h3><blockquote>
<p>这篇文章我们要学习怎么用Java程序来操作数据库，显而易见的是，我们不能直接跟MySQL说话，而是需要通过JDBC]]>
    </summary>
    <title>Java高级-第一篇：JDBC操作数据库——为什么不用Statement？——PreparedStatement防SQL注入一篇搞懂</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="MySQL" scheme="https://qixia.site/tags/MySQL/"/>
    <content>
      <![CDATA[<h3 id="一、事务是什么？"><a href="#一、事务是什么？" class="headerlink" title="一、事务是什么？"></a>一、事务是什么？</h3><p>简单来说，<strong>事务就是把完成同一目标的多个SQL操作打包成一个整体</strong>，举个例子：你给朋友转账100块。</p><p>对于熟悉SQL的我们来说，这个需求很快就可以完成。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> money <span class="operator">=</span> money <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;  <span class="comment">-- 你扣钱</span></span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> money <span class="operator">=</span> money <span class="operator">+</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;  <span class="comment">-- 朋友加钱</span></span><br></pre></td></tr></table></figure><p>在实际的SQL运行环境中，如果第一个UPDATE执行完了，第二个还没执行，系统崩了，结果你的钱扣了，但朋友没收到。为了解决这一问题，我们提出的事务的概念。</p><p>简单来说，<strong>事务就是把完成同一目标的多个SQL操作打包成一个整体</strong>。在这个场景中，就是把转钱这一目标打包成一个整体，<strong>这两个操作要么都成功，要么都失败，不允许中间状态。</strong> 这样就不会出现<strong>钱丢了</strong>的故障。</p><h3 id="二、怎么用？"><a href="#二、怎么用？" class="headerlink" title="二、怎么用？"></a>二、怎么用？</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">START</span> TRANSACTION;           <span class="comment">-- 开启事务</span></span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> money <span class="operator">=</span> money <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> money <span class="operator">=</span> money <span class="operator">+</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;                      <span class="comment">-- 没问题就提交</span></span><br><span class="line"><span class="comment">-- 如果出问题，用 ROLLBACK 回滚</span></span><br><span class="line"><span class="keyword">ROLLBACK</span>;</span><br></pre></td></tr></table></figure><p>简单来说，只要用三个命令就可以控制事务：</p><table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>START TRANSACTION</code></td><td>开启事务 ，执行SQL语句</td></tr><tr><td><code>COMMIT</code></td><td>提交结果，让所有修改生效</td></tr><tr><td><code>ROLLBACK</code></td><td>回滚事务，撤销所有修改</td></tr></tbody></table><p>当事务执行过程中，系统没有发生故障，最后只需要提交运行结果，但如果系统出现故障，需要对事务进行回滚，防止错误的结果上传数据库，导致错误。</p><h3 id="三、事务的四个特性（ACID）"><a href="#三、事务的四个特性（ACID）" class="headerlink" title="三、事务的四个特性（ACID）"></a>三、事务的四个特性（ACID）</h3><table><thead><tr><th>特性</th><th>人话</th></tr></thead><tbody><tr><td><strong>原子性</strong></td><td>要么全成功，要么全失败</td></tr><tr><td><strong>一致性</strong></td><td>钱不会凭空消失或出现</td></tr><tr><td><strong>隔离性</strong></td><td>你转账不会影响别人转账</td></tr><tr><td><strong>持久性</strong></td><td>转完了，钱就在那儿了</td></tr></tbody></table><p>知道这四个词就行，目的是体现事务在完成一个目标的稳定性和独立性。实际写代码时，我们只需要记住：<strong>开启 → 执行 → 提交&#x2F;回滚</strong>的流程。</p><hr><h3 id="四、一个必踩的坑"><a href="#四、一个必踩的坑" class="headerlink" title="四、一个必踩的坑"></a>四、一个必踩的坑</h3><p>由于MySQL默认开启了<strong>自动提交</strong>即每执行一条SQL，就自动帮你<code>COMMIT</code>。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> money <span class="operator">=</span> money <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="comment">-- 这条执行完，自动提交了，改不回来了</span></span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> money <span class="operator">=</span> money <span class="operator">+</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;</span><br></pre></td></tr></table></figure><p><strong>如果你想多条SQL作为一个事务，必须手动<code>START TRANSACTION</code>。</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">START</span> TRANSACTION;</span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> money <span class="operator">=</span> money <span class="operator">-</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">UPDATE</span> account <span class="keyword">SET</span> money <span class="operator">=</span> money <span class="operator">+</span> <span class="number">100</span> <span class="keyword">WHERE</span> id <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure><p>在实际的工作中最好使用手动提交，避免错误数据上传数据库。</p><h3 id="五、隔离级别"><a href="#五、隔离级别" class="headerlink" title="五、隔离级别"></a>五、隔离级别</h3><p>事务之间会互相影响，会造成一些不可避免的问题<br><a href="/img/Mysql_3/p2.png">图解</a><br>为了解决这些问题，我们提出了事务的四个隔离等级。</p><table><thead><tr><th>隔离级别</th><th>能解决</th><th>仍有问题</th><th>性能</th></tr></thead><tbody><tr><td>读未提交</td><td>无</td><td>脏读、不可重复读、幻读</td><td>最好</td></tr><tr><td>读已提交</td><td>脏读</td><td>不可重复读、幻读</td><td>较好</td></tr><tr><td>可重复读（MySQL默认）</td><td>脏读、不可重复读</td><td>幻读</td><td>一般</td></tr><tr><td>串行化</td><td>全部</td><td>无</td><td>最差</td></tr></tbody></table><p>从表格可以明显看到，隔离等级越高，性能越差，对整个数据库的管理效率越低，所以<strong>一句话建议：MySQL默认的“可重复读”就够了，别乱改。</strong></p><h3 id="六、最后的话"><a href="#六、最后的话" class="headerlink" title="六、最后的话"></a>六、最后的话</h3><p>关于数据库的事务的知识总结完毕，如果对你有帮助的话，可以一键三连！！！最后感谢你的查阅！！！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/11/MySQL%E7%AC%94%E8%AE%B0-%E7%AC%AC3%E7%AF%87%EF%BC%9A%E4%B8%80%E7%AF%87%E6%90%9E%E6%87%82%E4%BA%8B%E5%8A%A1%E3%80%81ACID%E5%92%8C%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/</id>
    <link href="https://qixia.site/2026/06/11/MySQL%E7%AC%94%E8%AE%B0-%E7%AC%AC3%E7%AF%87%EF%BC%9A%E4%B8%80%E7%AF%87%E6%90%9E%E6%87%82%E4%BA%8B%E5%8A%A1%E3%80%81ACID%E5%92%8C%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/"/>
    <published>2026-06-10T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h3 id="一、事务是什么？"><a href="#一、事务是什么？" class="headerlink" title="一、事务是什么？"></a>一、事务是什么？</h3><p>简单来说，<strong>事务就是把完成同一目标的多个SQL操作打包成一个整体</stron]]>
    </summary>
    <title>MySQL笔记-第3篇：一篇搞懂事务、ACID和隔离级别</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="MySQL" scheme="https://qixia.site/tags/MySQL/"/>
    <content>
      <![CDATA[<p>SQL（Structured Query Language）结构化查询语言，主要分为四大类别：<strong>DDL（数据定义语言）</strong>、<strong>DML（数据操作语言）</strong>、<strong>DQL（数据查询语言）</strong> 和 <strong>DCL（数据控制语言）</strong>。</p><hr><h2 id="一、数据定义语言-DDL（Data-Definition-Language）"><a href="#一、数据定义语言-DDL（Data-Definition-Language）" class="headerlink" title="一、数据定义语言 DDL（Data Definition Language）"></a>一、数据定义语言 DDL（Data Definition Language）</h2><p><strong>用途</strong>：用来定义数据库对象（数据库、表、字段）。</p><h3 id="1、数据库操作"><a href="#1、数据库操作" class="headerlink" title="1、数据库操作"></a>1、数据库操作</h3><h4 id="查询所有数据库"><a href="#查询所有数据库" class="headerlink" title="查询所有数据库"></a>查询所有数据库</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> DATABASES;</span><br></pre></td></tr></table></figure><h4 id="查询当前数据库"><a href="#查询当前数据库" class="headerlink" title="查询当前数据库"></a>查询当前数据库</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> DATABASE();</span><br></pre></td></tr></table></figure><h4 id="创建数据库"><a href="#创建数据库" class="headerlink" title="创建数据库"></a>创建数据库</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> DATABASE [ IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> ] 数据库名 [ <span class="keyword">DEFAULT</span> CHARSET 字符集 ] [ <span class="keyword">COLLATE</span> 排序规则 ];</span><br></pre></td></tr></table></figure><blockquote><p><strong>说明</strong>：在同一个数据库服务器中，不能创建两个名称相同的数据库，否则将会报错。使用 <code>IF NOT EXISTS</code> 可避免重复创建。</p><p><strong>示例</strong>：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> DATABASE IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> itcast;</span><br></pre></td></tr></table></figure></blockquote><h4 id="删除数据库"><a href="#删除数据库" class="headerlink" title="删除数据库"></a>删除数据库</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DROP</span> DATABASE [ IF <span class="keyword">EXISTS</span> ] 数据库名;</span><br></pre></td></tr></table></figure><h4 id="切换数据库"><a href="#切换数据库" class="headerlink" title="切换数据库"></a>切换数据库</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">USE 数据库名;</span><br></pre></td></tr></table></figure><hr><h3 id="2、表操作"><a href="#2、表操作" class="headerlink" title="2、表操作"></a>2、表操作</h3><h4 id="A、查询与创建"><a href="#A、查询与创建" class="headerlink" title="A、查询与创建"></a>A、查询与创建</h4><h5 id="查询当前数据库所有表"><a href="#查询当前数据库所有表" class="headerlink" title="查询当前数据库所有表"></a>查询当前数据库所有表</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> TABLES;</span><br></pre></td></tr></table></figure><h5 id="查看指定表结构"><a href="#查看指定表结构" class="headerlink" title="查看指定表结构"></a>查看指定表结构</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DESC</span> 表名;</span><br></pre></td></tr></table></figure><h5 id="查询指定表的建表语句"><a href="#查询指定表的建表语句" class="headerlink" title="查询指定表的建表语句"></a>查询指定表的建表语句</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> <span class="keyword">CREATE TABLE</span> 表名;</span><br></pre></td></tr></table></figure><h5 id="创建表结构"><a href="#创建表结构" class="headerlink" title="创建表结构"></a>创建表结构</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> 表名(</span><br><span class="line">    字段<span class="number">1</span> 字段<span class="number">1</span>类型 [ COMMENT 字段<span class="number">1</span>注释 ],</span><br><span class="line">    字段<span class="number">2</span> 字段<span class="number">2</span>类型 [ COMMENT 字段<span class="number">2</span>注释 ],</span><br><span class="line">    字段<span class="number">3</span> 字段<span class="number">3</span>类型 [ COMMENT 字段<span class="number">3</span>注释 ],</span><br><span class="line">    ...</span><br><span class="line">    字段n 字段n类型 [ COMMENT 字段n注释 ]</span><br><span class="line">) [ COMMENT 表注释 ];</span><br></pre></td></tr></table></figure><hr><h4 id="B、数据类型"><a href="#B、数据类型" class="headerlink" title="B、数据类型"></a>B、数据类型</h4><h5 id="1、数值型"><a href="#1、数值型" class="headerlink" title="1、数值型"></a>1、数值型</h5><table><thead><tr><th>类型</th><th>大小</th><th>有符号（SIGNED）范围</th><th>无符号（UNSIGNED）范围</th><th>描述</th></tr></thead><tbody><tr><td>TINYINT</td><td>1 byte</td><td>(-128, 127)</td><td>(0, 255)</td><td>小整数值</td></tr><tr><td>SMALLINT</td><td>2 bytes</td><td>(-32768, 32767)</td><td>(0, 65535)</td><td>大整数值</td></tr><tr><td>MEDIUMINT</td><td>3 bytes</td><td>(-8388608, 8388607)</td><td>(0, 16777215)</td><td>大整数值</td></tr><tr><td>INT&#x2F;INTEGER</td><td>4 bytes</td><td>(-2147483648, 2147483647)</td><td>(0, 4294967295)</td><td>大整数值</td></tr><tr><td>BIGINT</td><td>8 bytes</td><td>(-2^63, 2^63-1)</td><td>(0, 2^64-1)</td><td>极大整数值</td></tr><tr><td>FLOAT</td><td>4 bytes</td><td>(-3.402823466 E+38, 3.402823466 E+38)</td><td>0 和 (1.175494351 E-38, 3.402823466 E+38)</td><td>单精度浮点数值</td></tr><tr><td>DOUBLE</td><td>8 bytes</td><td>(-1.7976931348623157 E+308, 1.7976931348623157 E+308)</td><td>0 和 (2.2250738585072014 E-308, 1.7976931348623157 E+308)</td><td>双精度浮点数值</td></tr><tr><td>DECIMAL</td><td>依赖于M和D</td><td>依赖于M（精度）和D（标度）的值</td><td>依赖于M（精度）和D（标度）的值</td><td>小数值（精确定点数）</td></tr></tbody></table><hr><h5 id="2、字符串类型"><a href="#2、字符串类型" class="headerlink" title="2、字符串类型"></a>2、字符串类型</h5><table><thead><tr><th>类型</th><th>大小</th><th>描述</th></tr></thead><tbody><tr><td>CHAR</td><td>0-255 bytes</td><td>定长字符串（需指定长度）</td></tr><tr><td>VARCHAR</td><td>0-65535 bytes</td><td>变长字符串（需指定长度）</td></tr><tr><td>TINYBLOB</td><td>0-255 bytes</td><td>不超过255个字符的二进制数据</td></tr><tr><td>TINYTEXT</td><td>0-255 bytes</td><td>短文本字符串</td></tr><tr><td>BLOB</td><td>0-65,535 bytes</td><td>二进制形式的长文本数据</td></tr><tr><td>TEXT</td><td>0-65,535 bytes</td><td>长文本数据</td></tr><tr><td>MEDIUMBLOB</td><td>0-16,777,215 bytes</td><td>二进制形式的中等长度文本数据</td></tr><tr><td>MEDIUMTEXT</td><td>0-16,777,215 bytes</td><td>中等长度文本数据</td></tr><tr><td>LONGBLOB</td><td>0-4,294,967,295 bytes</td><td>二进制形式的极大文本数据</td></tr><tr><td>LONGTEXT</td><td>0-4,294,967,295 bytes</td><td>极大文本数据</td></tr></tbody></table><hr><h5 id="3、日期时间类型"><a href="#3、日期时间类型" class="headerlink" title="3、日期时间类型"></a>3、日期时间类型</h5><table><thead><tr><th>类型</th><th>大小</th><th>范围</th><th>格式</th><th>描述</th></tr></thead><tbody><tr><td>DATE</td><td>3</td><td>1000-01-01 至 9999-12-31</td><td>YYYY-MM-DD</td><td>日期值</td></tr><tr><td>TIME</td><td>3</td><td>-838:59:59 至 838:59:59</td><td>HH:MM:SS</td><td>时间值或持续时间</td></tr><tr><td>YEAR</td><td>1</td><td>1901 至 2155</td><td>YYYY</td><td>年份值</td></tr><tr><td>DATETIME</td><td>8</td><td>1000-01-01 00:00:00 至 9999-12-31 23:59:59</td><td>YYYY-MM-DD HH:MM:SS</td><td>混合日期和时间值</td></tr><tr><td>TIMESTAMP</td><td>4</td><td>1970-01-01 00:00:01 至 2038-01-19 03:14:07</td><td>YYYY-MM-DD HH:MM:SS</td><td>混合日期和时间值，时间戳</td></tr></tbody></table><hr><h4 id="C、修改表结构"><a href="#C、修改表结构" class="headerlink" title="C、修改表结构"></a>C、修改表结构</h4><h5 id="添加字段"><a href="#添加字段" class="headerlink" title="添加字段"></a>添加字段</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER TABLE</span> 表名 <span class="keyword">ADD</span> 字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];</span><br></pre></td></tr></table></figure><h5 id="修改数据类型"><a href="#修改数据类型" class="headerlink" title="修改数据类型"></a>修改数据类型</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER TABLE</span> 表名 MODIFY 字段名 新数据类型 (长度);</span><br></pre></td></tr></table></figure><h5 id="修改字段名和字段类型"><a href="#修改字段名和字段类型" class="headerlink" title="修改字段名和字段类型"></a>修改字段名和字段类型</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER TABLE</span> 表名 CHANGE 旧字段名 新字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];</span><br></pre></td></tr></table></figure><h5 id="删除字段"><a href="#删除字段" class="headerlink" title="删除字段"></a>删除字段</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER TABLE</span> 表名 <span class="keyword">DROP</span> 字段名;</span><br></pre></td></tr></table></figure><h5 id="修改表名"><a href="#修改表名" class="headerlink" title="修改表名"></a>修改表名</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER TABLE</span> 表名 RENAME <span class="keyword">TO</span> 新表名;</span><br></pre></td></tr></table></figure><hr><h4 id="D、删除表"><a href="#D、删除表" class="headerlink" title="D、删除表"></a>D、删除表</h4><h5 id="删除表"><a href="#删除表" class="headerlink" title="删除表"></a>删除表</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DROP</span> <span class="keyword">TABLE</span> [ IF <span class="keyword">EXISTS</span> ] 表名;</span><br></pre></td></tr></table></figure><h5 id="删除指定表，并重新创建表（清空数据，重置自增）"><a href="#删除指定表，并重新创建表（清空数据，重置自增）" class="headerlink" title="删除指定表，并重新创建表（清空数据，重置自增）"></a>删除指定表，并重新创建表（清空数据，重置自增）</h5><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">TRUNCATE</span> <span class="keyword">TABLE</span> 表名;</span><br></pre></td></tr></table></figure><hr><h2 id="二、数据操作语言-DML（Data-Manipulation-Language）"><a href="#二、数据操作语言-DML（Data-Manipulation-Language）" class="headerlink" title="二、数据操作语言 DML（Data Manipulation Language）"></a>二、数据操作语言 DML（Data Manipulation Language）</h2><p><strong>用途</strong>：用来对数据库表中的数据进行增删改。</p><h3 id="一、添加数据"><a href="#一、添加数据" class="headerlink" title="一、添加数据"></a>一、添加数据</h3><h4 id="给指定字段添加数据"><a href="#给指定字段添加数据" class="headerlink" title="给指定字段添加数据"></a>给指定字段添加数据</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT INTO</span> 表名 (字段名<span class="number">1</span>, 字段名<span class="number">2</span>, ...) <span class="keyword">VALUES</span> (值<span class="number">1</span>, 值<span class="number">2</span>, ...);</span><br></pre></td></tr></table></figure><h4 id="给全部字段添加数据"><a href="#给全部字段添加数据" class="headerlink" title="给全部字段添加数据"></a>给全部字段添加数据</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT INTO</span> 表名 <span class="keyword">VALUES</span> (值<span class="number">1</span>, 值<span class="number">2</span>, ...);</span><br></pre></td></tr></table></figure><h4 id="批量添加数据"><a href="#批量添加数据" class="headerlink" title="批量添加数据"></a>批量添加数据</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT INTO</span> 表名 (字段名<span class="number">1</span>, 字段名<span class="number">2</span>, ...) <span class="keyword">VALUES</span> </span><br><span class="line">(值<span class="number">1</span>, 值<span class="number">2</span>, ...), </span><br><span class="line">(值<span class="number">1</span>, 值<span class="number">2</span>, ...), </span><br><span class="line">(值<span class="number">1</span>, 值<span class="number">2</span>, ...);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 或插入所有字段</span></span><br><span class="line"><span class="keyword">INSERT INTO</span> 表名 <span class="keyword">VALUES</span> </span><br><span class="line">(值<span class="number">1</span>, 值<span class="number">2</span>, ...), </span><br><span class="line">(值<span class="number">1</span>, 值<span class="number">2</span>, ...), </span><br><span class="line">(值<span class="number">1</span>, 值<span class="number">2</span>, ...);</span><br></pre></td></tr></table></figure><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li>插入数据时，指定的字段顺序需要与值的顺序一一对应。</li><li>字符串和日期型数据应包含在引号中。</li><li>插入的数据大小应在字段规定范围内。</li></ul></blockquote><hr><h3 id="二、修改数据"><a href="#二、修改数据" class="headerlink" title="二、修改数据"></a>二、修改数据</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> 表名 <span class="keyword">SET</span> 字段名<span class="number">1</span> <span class="operator">=</span> 值<span class="number">1</span>, 字段名<span class="number">2</span> <span class="operator">=</span> 值<span class="number">2</span>, ... [ <span class="keyword">WHERE</span> 条件 ];</span><br></pre></td></tr></table></figure><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li>修改语句的条件可有可无。若无条件，则会修改整张表的所有数据。</li></ul></blockquote><hr><h3 id="三、删除数据"><a href="#三、删除数据" class="headerlink" title="三、删除数据"></a>三、删除数据</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> 表名 [ <span class="keyword">WHERE</span> 条件 ]</span><br></pre></td></tr></table></figure><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li>DELETE 语句的条件可有可无。若无条件，则会删除整张表的所有数据。</li><li>DELETE 不能删除某一个字段的值（可使用 UPDATE 将该字段置为 NULL）。</li></ul></blockquote><hr><h2 id="三、数据查询语言-DQL（Data-Query-Language）"><a href="#三、数据查询语言-DQL（Data-Query-Language）" class="headerlink" title="三、数据查询语言 DQL（Data Query Language）"></a>三、数据查询语言 DQL（Data Query Language）</h2><p><strong>用途</strong>：用来查询数据库中表的记录。</p><h3 id="一、基础查询"><a href="#一、基础查询" class="headerlink" title="一、基础查询"></a>一、基础查询</h3><h4 id="1、查询多个字段"><a href="#1、查询多个字段" class="headerlink" title="1、查询多个字段"></a>1、查询多个字段</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> 字段<span class="number">1</span>, 字段<span class="number">2</span>, 字段<span class="number">3</span> ... <span class="keyword">FROM</span> 表名;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> 表名;</span><br></pre></td></tr></table></figure><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li><code>*</code> 代表查询所有字段，实际开发中尽量少用（不直观、影响效率）。</li></ul></blockquote><h4 id="2、字段设置别名"><a href="#2、字段设置别名" class="headerlink" title="2、字段设置别名"></a>2、字段设置别名</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> 字段<span class="number">1</span> [ <span class="keyword">AS</span> 别名<span class="number">1</span> ], 字段<span class="number">2</span> [ <span class="keyword">AS</span> 别名<span class="number">2</span> ] ... <span class="keyword">FROM</span> 表名;</span><br><span class="line"><span class="comment">-- 或</span></span><br><span class="line"><span class="keyword">SELECT</span> 字段<span class="number">1</span> 别名<span class="number">1</span>, 字段<span class="number">2</span> 别名<span class="number">2</span> ... <span class="keyword">FROM</span> 表名;</span><br></pre></td></tr></table></figure><h4 id="3、去除重复记录"><a href="#3、去除重复记录" class="headerlink" title="3、去除重复记录"></a>3、去除重复记录</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> 字段列表 <span class="keyword">FROM</span> 表名;</span><br></pre></td></tr></table></figure><hr><h3 id="二、条件查询"><a href="#二、条件查询" class="headerlink" title="二、条件查询"></a>二、条件查询</h3><h4 id="1、语法"><a href="#1、语法" class="headerlink" title="1、语法"></a>1、语法</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> 字段列表 <span class="keyword">FROM</span> 表名 <span class="keyword">WHERE</span> 条件列表;</span><br></pre></td></tr></table></figure><h4 id="2、常用比较运算符"><a href="#2、常用比较运算符" class="headerlink" title="2、常用比较运算符"></a>2、常用比较运算符</h4><table><thead><tr><th>运算符</th><th>功能</th></tr></thead><tbody><tr><td><code>&gt;</code></td><td>大于</td></tr><tr><td><code>&gt;=</code></td><td>大于等于</td></tr><tr><td><code>&lt;</code></td><td>小于</td></tr><tr><td><code>&lt;=</code></td><td>小于等于</td></tr><tr><td><code>=</code></td><td>等于</td></tr><tr><td><code>&lt;&gt;</code> 或 <code>!=</code></td><td>不等于</td></tr><tr><td><code>BETWEEN ... AND ...</code></td><td>在某个范围之内（含最小、最大值）</td></tr><tr><td><code>IN (...)</code></td><td>在列表中的值，多选一</td></tr><tr><td><code>LIKE 占位符</code></td><td>模糊匹配（_匹配单个字符，%匹配任意个字符）</td></tr><tr><td><code>IS NULL</code></td><td>是 NULL</td></tr></tbody></table><h4 id="常用逻辑运算符"><a href="#常用逻辑运算符" class="headerlink" title="常用逻辑运算符"></a>常用逻辑运算符</h4><table><thead><tr><th>运算符</th><th>功能</th></tr></thead><tbody><tr><td><code>AND</code> 或 <code>&amp;&amp;</code></td><td>并且（多个条件同时成立）</td></tr><tr><td><code>OR</code> 或 &#96;</td><td></td></tr><tr><td><code>NOT</code> 或 <code>!</code></td><td>非，不是</td></tr></tbody></table><hr><h3 id="三、聚合函数"><a href="#三、聚合函数" class="headerlink" title="三、聚合函数"></a>三、聚合函数</h3><h4 id="1、定义"><a href="#1、定义" class="headerlink" title="1、定义"></a>1、定义</h4><p>将一列数据作为一个整体，进行纵向计算。</p><h4 id="常见聚合函数"><a href="#常见聚合函数" class="headerlink" title="常见聚合函数"></a>常见聚合函数</h4><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td><code>COUNT</code></td><td>统计数量</td></tr><tr><td><code>MAX</code></td><td>最大值</td></tr><tr><td><code>MIN</code></td><td>最小值</td></tr><tr><td><code>AVG</code></td><td>平均值</td></tr><tr><td><code>SUM</code></td><td>求和</td></tr></tbody></table><h4 id="2、语法"><a href="#2、语法" class="headerlink" title="2、语法"></a>2、语法</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> 聚合函数(字段列表) <span class="keyword">FROM</span> 表名;</span><br></pre></td></tr></table></figure><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li><code>NULL</code> 值不参与所有聚合函数运算。</li></ul></blockquote><hr><h3 id="四、分组查询"><a href="#四、分组查询" class="headerlink" title="四、分组查询"></a>四、分组查询</h3><h4 id="1、语法-1"><a href="#1、语法-1" class="headerlink" title="1、语法"></a>1、语法</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> 字段列表 </span><br><span class="line"><span class="keyword">FROM</span> 表名 </span><br><span class="line">[ <span class="keyword">WHERE</span> 条件 ] </span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> 分组字段名 </span><br><span class="line">[ <span class="keyword">HAVING</span> 分组后过滤条件 ];</span><br></pre></td></tr></table></figure><h4 id="WHERE-与-HAVING-区别"><a href="#WHERE-与-HAVING-区别" class="headerlink" title="WHERE 与 HAVING 区别"></a><code>WHERE</code> 与 <code>HAVING</code> 区别</h4><ul><li><strong>执行时机不同</strong>：<code>WHERE</code> 是分组前过滤，不满足则不参与分组；<code>HAVING</code> 是分组后对结果过滤。</li><li><strong>判断条件不同</strong>：<code>WHERE</code> 不能对聚合函数判断，<code>HAVING</code> 可以。</li></ul><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li>分组后，查询字段一般为聚合函数和分组字段，其他字段无意义。</li><li>执行顺序：<code>WHERE</code> &gt; 聚合函数 &gt; <code>HAVING</code>。</li><li>支持多字段分组：<code>GROUP BY columnA, columnB</code>。</li><li>在 <code>GROUP BY</code> 查询中，<code>SELECT</code> 的非聚合字段必须出现在 <code>GROUP BY</code> 子句中，否则报错。</li></ul></blockquote><hr><h3 id="五、排序查询"><a href="#五、排序查询" class="headerlink" title="五、排序查询"></a>五、排序查询</h3><h4 id="1、语法-2"><a href="#1、语法-2" class="headerlink" title="1、语法"></a>1、语法</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> 字段列表 </span><br><span class="line"><span class="keyword">FROM</span> 表名 </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> 字段<span class="number">1</span> 排序方式<span class="number">1</span>, 字段<span class="number">2</span> 排序方式<span class="number">2</span>;</span><br></pre></td></tr></table></figure><h4 id="2、排序方式"><a href="#2、排序方式" class="headerlink" title="2、排序方式"></a>2、排序方式</h4><ul><li><code>ASC</code>：升序（默认）</li><li><code>DESC</code>：降序</li></ul><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li>升序可省略 <code>ASC</code>。</li><li>多字段排序时，当前一字段值相同时，才按下一字段排序。</li></ul></blockquote><hr><h3 id="六、分页查询"><a href="#六、分页查询" class="headerlink" title="六、分页查询"></a>六、分页查询</h3><h4 id="1、语法-3"><a href="#1、语法-3" class="headerlink" title="1、语法"></a>1、语法</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> 字段列表 <span class="keyword">FROM</span> 表名 LIMIT 起始索引, 查询记录数;</span><br></pre></td></tr></table></figure><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li>起始索引从 0 开始，计算公式：<code>(查询页码 - 1) * 每页显示记录数</code>。</li><li>分页查询是数据库方言，MySQL 使用 <code>LIMIT</code>。</li><li>查询第一页时，可省略起始索引，简写为 <code>LIMIT 10</code>。</li></ul></blockquote><hr><h3 id="七、执行顺序与编写顺序"><a href="#七、执行顺序与编写顺序" class="headerlink" title="七、执行顺序与编写顺序"></a>七、执行顺序与编写顺序</h3><h4 id="1、执行顺序"><a href="#1、执行顺序" class="headerlink" title="1、执行顺序"></a>1、执行顺序</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">graph TD</span><br><span class="line">    A[FROM &lt;br&gt;表名列表] --&gt; B[WHERE &lt;br&gt;条件列表]</span><br><span class="line">    B --&gt; C[GROUP BY &lt;br&gt;分组字段列表]</span><br><span class="line">    C --&gt; D[HAVING &lt;br&gt;分组后条件列表]</span><br><span class="line">    D --&gt; E[SELECT &lt;br&gt;字段列表]</span><br><span class="line">    E --&gt; F[ORDER BY &lt;br&gt;排序字段列表]</span><br><span class="line">    F --&gt; G[LIMIT &lt;br&gt;分页参数]</span><br><span class="line">    G --&gt; H[执行顺序]</span><br></pre></td></tr></table></figure><h4 id="2、编写顺序"><a href="#2、编写顺序" class="headerlink" title="2、编写顺序"></a>2、编写顺序</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">graph TD</span><br><span class="line">    A[SELECT &lt;br&gt;字段列表] --&gt; B[FROM &lt;br&gt;表名列表]</span><br><span class="line">    B --&gt; C[WHERE &lt;br&gt;条件列表]</span><br><span class="line">    C --&gt; D[GROUP BY &lt;br&gt;分组字段列表]</span><br><span class="line">    D --&gt; E[HAVING &lt;br&gt;分组后条件列表]</span><br><span class="line">    E --&gt; F[ORDER BY &lt;br&gt;排序字段列表]</span><br><span class="line">    F --&gt; G[LIMIT &lt;br&gt;分页参数]</span><br><span class="line">    G --&gt; H[编写顺序]</span><br></pre></td></tr></table></figure><hr><h2 id="四、数据控制语言-DCL（Data-Control-Language）"><a href="#四、数据控制语言-DCL（Data-Control-Language）" class="headerlink" title="四、数据控制语言 DCL（Data Control Language）"></a>四、数据控制语言 DCL（Data Control Language）</h2><p><strong>用途</strong>：用来管理数据库用户、控制数据库的访问权限。</p><h3 id="一、管理用户"><a href="#一、管理用户" class="headerlink" title="一、管理用户"></a>一、管理用户</h3><h4 id="1、查询用户"><a href="#1、查询用户" class="headerlink" title="1、查询用户"></a>1、查询用户</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> mysql.user;</span><br></pre></td></tr></table></figure><h4 id="2、创建用户"><a href="#2、创建用户" class="headerlink" title="2、创建用户"></a>2、创建用户</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span> IDENTIFIED <span class="keyword">BY</span> <span class="string">&#x27;密码&#x27;</span>;</span><br></pre></td></tr></table></figure><h4 id="3、修改用户密码"><a href="#3、修改用户密码" class="headerlink" title="3、修改用户密码"></a>3、修改用户密码</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">USER</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span> IDENTIFIED <span class="keyword">WITH</span> mysql_native_password <span class="keyword">BY</span> <span class="string">&#x27;新密码&#x27;</span>;</span><br></pre></td></tr></table></figure><h4 id="4、删除用户"><a href="#4、删除用户" class="headerlink" title="4、删除用户"></a>4、删除用户</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DROP</span> <span class="keyword">USER</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span>;</span><br></pre></td></tr></table></figure><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li>MySQL 中通过 <code>用户名@主机名</code> 唯一标识一个用户。</li><li>主机名可使用 <code>%</code> 通配。</li><li>此类操作多由 DBA（数据库管理员）执行，开发人员较少操作。</li></ul></blockquote><hr><h3 id="二、权限管理"><a href="#二、权限管理" class="headerlink" title="二、权限管理"></a>二、权限管理</h3><h4 id="常用权限"><a href="#常用权限" class="headerlink" title="常用权限"></a>常用权限</h4><table><thead><tr><th>权限</th><th>说明</th></tr></thead><tbody><tr><td><code>ALL</code>, <code>ALL PRIVILEGES</code></td><td>所有权限</td></tr><tr><td><code>SELECT</code></td><td>查询数据</td></tr><tr><td><code>INSERT</code></td><td>插入数据</td></tr><tr><td><code>UPDATE</code></td><td>修改数据</td></tr><tr><td><code>DELETE</code></td><td>删除数据</td></tr><tr><td><code>ALTER</code></td><td>修改表</td></tr><tr><td><code>DROP</code></td><td>删除数据库&#x2F;表&#x2F;视图</td></tr><tr><td><code>CREATE</code></td><td>创建数据库&#x2F;表</td></tr></tbody></table><h4 id="1、查询权限"><a href="#1、查询权限" class="headerlink" title="1、查询权限"></a>1、查询权限</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SHOW</span> GRANTS <span class="keyword">FOR</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span>;</span><br></pre></td></tr></table></figure><h4 id="2、授予权限"><a href="#2、授予权限" class="headerlink" title="2、授予权限"></a>2、授予权限</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">GRANT</span> 权限列表 <span class="keyword">ON</span> 数据库名.表名 <span class="keyword">TO</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span>;</span><br></pre></td></tr></table></figure><h4 id="3、撤销权限"><a href="#3、撤销权限" class="headerlink" title="3、撤销权限"></a>3、撤销权限</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">REVOKE</span> 权限列表 <span class="keyword">ON</span> 数据库名.表名 <span class="keyword">FROM</span> <span class="string">&#x27;用户名&#x27;</span>@<span class="string">&#x27;主机名&#x27;</span>;</span><br></pre></td></tr></table></figure><blockquote><p><strong>⚠️ 注意事项</strong>：</p><ul><li>多个权限间用逗号分隔。</li><li>授权时，数据库名和表名可用 <code>*</code> 通配，代表所有。</li></ul></blockquote><hr><h2 id="最后的话"><a href="#最后的话" class="headerlink" title="最后的话"></a>最后的话</h2><p>以上就是SQL数据库语言的全部内容，希望对您有帮助！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/11/MySQL%E7%AC%94%E8%AE%B0%C2%B7%E7%AC%AC1%E7%AF%87%EF%BC%9ADDL%E3%80%81DML%E3%80%81DQL%E3%80%81DCL%E4%B8%80%E7%AF%87%E6%90%9E%E5%AE%9A/</id>
    <link href="https://qixia.site/2026/06/11/MySQL%E7%AC%94%E8%AE%B0%C2%B7%E7%AC%AC1%E7%AF%87%EF%BC%9ADDL%E3%80%81DML%E3%80%81DQL%E3%80%81DCL%E4%B8%80%E7%AF%87%E6%90%9E%E5%AE%9A/"/>
    <published>2026-06-10T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>SQL（Structured Query Language）结构化查询语言，主要分为四大类别：<strong>DDL（数据定义语言）</strong>、<strong>DML（数据操作语言）</strong>、<strong>DQL（数据查询语言）</strong> 和 <]]>
    </summary>
    <title>MySQL笔记·第1篇：DDL、DML、DQL、DCL一篇搞定</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="MySQL" scheme="https://qixia.site/tags/MySQL/"/>
    <content>
      <![CDATA[<h3 id="一、外键是什么？"><a href="#一、外键是什么？" class="headerlink" title="一、外键是什么？"></a>一、外键是什么？</h3><p>简单说，外键就是用来保证“两张表的数据能对上”。比如订单表的user_id必须指向用户表里存在的id。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FOREIGN KEY</span> (user_id) <span class="keyword">REFERENCES</span> <span class="keyword">user</span>(id)</span><br></pre></td></tr></table></figure><p>加了外键，数据库会自动帮你检查：插入订单时，用户必须存在；删除用户时，要么拒绝，要么连带删订单。</p><p>听起来很完美，对吧？那为什么阿里巴巴开发手册明确写着“不得使用外键”？这篇文章讲清楚外键的坑和替代方案</p><h3 id="二、为什么不推荐用外键？"><a href="#二、为什么不推荐用外键？" class="headerlink" title="二、为什么不推荐用外键？"></a>二、为什么不推荐用外键？</h3><p>外键像个负责任的保安，每次有人进出都查证件——安全是安全了，但门口排起了长队。</p><p><strong>1. 性能损耗</strong></p><p>每次插入、更新、删除订单，数据库都要去<code>user</code>表查一下这个<code>user_id</code>存不存在。这条<code>select</code>语句看着简单，但在高并发下，数据库累死。</p><p>一个订单操作变成：插入订单 + 查询用户。数据库要多干一份活。</p><p><strong>2. 分库分表不友好</strong></p><p>外键只能在一个数据库实例内生效。</p><p>业务大到一定程度，<code>user</code>表在一台机器，<code>orders</code>表在另一台机器。外键直接废了，代码还得大改——把所有数据完整性检查从数据库搬到代码里。</p><p>与其到时候哭着改代码，不如一开始就别用外键。</p><p><strong>3. 耦合太紧</strong></p><p>想删<code>user</code>表的一条数据？先看看有没有订单引用它。想改表结构？先拆外键。想迁移数据？外键关系像蜘蛛网一样缠着。</p><p>一个表的变动，牵一发动全身。</p><p><strong>4. 开发测试麻烦</strong></p><p>造测试数据的时候，得先插<code>user</code>，再插<code>orders</code>，顺序不能乱。清数据的时候，得先删<code>orders</code>，再删<code>user</code>，顺序也不能乱。</p><p>每次测试都得小心翼翼地按顺序来，很烦。</p><hr><h3 id="三、不用外键，怎么保证数据完整性？"><a href="#三、不用外键，怎么保证数据完整性？" class="headerlink" title="三、不用外键，怎么保证数据完整性？"></a>三、不用外键，怎么保证数据完整性？</h3><p>大厂的做法是：不设保安，装一套门禁系统。</p><table><thead><tr><th>方案</th><th>做法</th></tr></thead><tbody><tr><td><strong>应用层保证</strong></td><td>在代码里写逻辑：插入订单前，先查一下用户存不存在。</td></tr><tr><td><strong>事务</strong></td><td>用数据库事务保证一系列操作“要么全成功，要么全失败”，防止中间状态不一致。</td></tr><tr><td><strong>唯一索引&#x2F;检查约束</strong></td><td>用这些轻量级的约束代替外键，成本低很多。</td></tr><tr><td><strong>定期巡检</strong></td><td>跑定时任务，扫出“孤儿数据”（比如查无此人的订单），修掉或报警。</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 应用层保证示例</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createOrder</span><span class="params">(<span class="type">int</span> userId, BigDecimal amount)</span> &#123;</span><br><span class="line">    <span class="comment">// 先查用户存不存在</span></span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userMapper.selectById(userId);</span><br><span class="line">    <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BusinessException</span>(<span class="string">&quot;用户不存在&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 再插入订单</span></span><br><span class="line">    orderMapper.insert(<span class="keyword">new</span> <span class="title class_">Order</span>(userId, amount));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代码多写几行，但数据库轻松了，系统也能水平扩展了。</p><hr><h3 id="四、什么时候还可以用外键？"><a href="#四、什么时候还可以用外键？" class="headerlink" title="四、什么时候还可以用外键？"></a>四、什么时候还可以用外键？</h3><table><thead><tr><th>场景</th><th>判断</th></tr></thead><tbody><tr><td><strong>小项目、并发低</strong></td><td>可以用。外键的便利性大于性能损失。</td></tr><tr><td><strong>单机部署、不拆分</strong></td><td>可以用。没有分库分表的烦恼。</td></tr><tr><td><strong>财务系统、对账系统</strong></td><td>可以用。数据一致性要求极高，宁愿慢也不能错。</td></tr><tr><td><strong>内部管理系统</strong></td><td>可以用。并发不高，外键省事。</td></tr></tbody></table><p>阿里那个规范说的是“不得使用”，那是针对他们那种千万级并发的系统。如果需要维护的是小项目，则可以无视这些</p><hr><h3 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h3><table><thead><tr><th>方案</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><strong>用外键</strong></td><td>省事、数据库自动保证完整性</td><td>性能差、分库分表难、耦合紧</td></tr><tr><td><strong>不用外键</strong></td><td>性能好、扩展灵活、解耦</td><td>代码多写几行、需要自己保证完整性</td></tr></tbody></table><p>外键像个负责任的保安，安全但效率低。大厂不要保安，而是装门禁系统，每个人自己刷卡，系统只记录谁没刷。安全靠制度和技术，不靠一个人堵在门口。</p><p><strong>不是外键不好，是它的代价超出了它的价值。</strong></p>]]>
    </content>
    <id>https://qixia.site/2026/06/11/MySQL%E7%AC%94%E8%AE%B0%C2%B7%E7%AC%AC2%E7%AF%87%EF%BC%9AMySQL%E5%A4%96%E9%94%AE%E2%80%94%E2%80%94%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A6%81%E6%AD%A2%E4%BD%BF%E7%94%A8%EF%BC%9F%E5%A4%96%E9%94%AE%E7%9A%84%E5%9D%91%E4%B8%8E%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88/</id>
    <link href="https://qixia.site/2026/06/11/MySQL%E7%AC%94%E8%AE%B0%C2%B7%E7%AC%AC2%E7%AF%87%EF%BC%9AMySQL%E5%A4%96%E9%94%AE%E2%80%94%E2%80%94%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A6%81%E6%AD%A2%E4%BD%BF%E7%94%A8%EF%BC%9F%E5%A4%96%E9%94%AE%E7%9A%84%E5%9D%91%E4%B8%8E%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88/"/>
    <published>2026-06-10T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h3 id="一、外键是什么？"><a href="#一、外键是什么？" class="headerlink" title="一、外键是什么？"></a>一、外键是什么？</h3><p>简单说，外键就是用来保证“两张表的数据能对上”。比如订单表的user_id必须指向用户表里存]]>
    </summary>
    <title>MySQL笔记·第2篇：MySQL外键——阿里巴巴为什么禁止使用？外键的坑与替代方案</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="MySQL" scheme="https://qixia.site/tags/MySQL/"/>
    <content>
      <![CDATA[<h3 id="一、多表查询是什么？"><a href="#一、多表查询是什么？" class="headerlink" title="一、多表查询是什么？"></a>一、多表查询是什么？</h3><p>简单来说，在实际的项目管理中，数据库的查询不仅仅是查询一张表的数据，是两张或两张以上的表，这时候是要用到多表查询。</p><p>我们在这建立两张表，一张是学生表，记录学生的学号和姓名，一张是成绩表，记录学生的成绩。接下来用这两张表为例，讲解多表查询。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 学生表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> student (</span><br><span class="line">    id <span class="type">INT</span> <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">50</span>)</span><br><span class="line">);</span><br><span class="line"><span class="keyword">INSERT INTO</span> student <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="string">&#x27;张三&#x27;</span>), (<span class="number">2</span>, <span class="string">&#x27;李四&#x27;</span>), (<span class="number">3</span>, <span class="string">&#x27;王五&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 成绩表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> score (</span><br><span class="line">    id <span class="type">INT</span> <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    student_id <span class="type">INT</span>,</span><br><span class="line">    score <span class="type">INT</span></span><br><span class="line">);</span><br><span class="line"><span class="keyword">INSERT INTO</span> score <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="number">1</span>, <span class="number">90</span>), (<span class="number">2</span>, <span class="number">1</span>, <span class="number">85</span>), (<span class="number">3</span>, <span class="number">2</span>, <span class="number">78</span>);</span><br></pre></td></tr></table></figure><p>用表格表示如下：</p><table><thead><tr><th>student表</th><th></th><th>score表</th><th></th><th></th></tr></thead><tbody><tr><td>id</td><td>name</td><td>id</td><td>student_id</td><td>score</td></tr><tr><td>1</td><td>张三</td><td>1</td><td>1</td><td>90</td></tr><tr><td>2</td><td>李四</td><td>2</td><td>1</td><td>85</td></tr><tr><td>3</td><td>王五</td><td>3</td><td>2</td><td>78</td></tr></tbody></table><hr><h3 id="二、三种常用的多表查询"><a href="#二、三种常用的多表查询" class="headerlink" title="二、三种常用的多表查询"></a>二、三种常用的多表查询</h3><p><img src="/img/Mysql_4/p1.png"></p><h4 id="1、内连接（INNER-JOIN）：两边都要有"><a href="#1、内连接（INNER-JOIN）：两边都要有" class="headerlink" title="1、内连接（INNER JOIN）：两边都要有"></a>1、内连接（INNER JOIN）：两边都要有</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student <span class="keyword">INNER</span> <span class="keyword">JOIN</span> score <span class="keyword">ON</span> student.id <span class="operator">=</span> score.student_id;</span><br></pre></td></tr></table></figure><p>结果：</p><table><thead><tr><th>name</th><th>score</th></tr></thead><tbody><tr><td>张三</td><td>90</td></tr><tr><td>张三</td><td>85</td></tr><tr><td>李四</td><td>78</td></tr></tbody></table><p>从结果来看，<strong>王五没有出现。</strong> 原因是虽然王五在学生表中有记录，但是他没有成绩，综合两张表的数据，除了王五都会被查询到</p><blockquote><p><strong>一句话记忆：内连接 &#x3D; 两边都对得上才留</strong></p></blockquote><hr><h4 id="2、左连接（LEFT-JOIN）：左边全要"><a href="#2、左连接（LEFT-JOIN）：左边全要" class="headerlink" title="2、左连接（LEFT JOIN）：左边全要"></a>2、左连接（LEFT JOIN）：左边全要</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> student <span class="keyword">LEFT</span> <span class="keyword">JOIN</span> score <span class="keyword">ON</span> student.id <span class="operator">=</span> score.student_id;</span><br></pre></td></tr></table></figure><p>结果：</p><table><thead><tr><th>name</th><th>score</th></tr></thead><tbody><tr><td>张三</td><td>90</td></tr><tr><td>张三</td><td>85</td></tr><tr><td>李四</td><td>78</td></tr><tr><td>王五</td><td>NULL</td></tr></tbody></table><p>从结果来看，与内连接不同的是<strong>王五出现了，只是成绩是NULL。</strong> 原因是在左连接查询中，左表的数据都会被查询到，右表有能匹配上的显示，没有就显示空值。</p><blockquote><p><strong>一句话记忆：左连接 &#x3D; 左表全留，右表有就带，没有就空</strong></p></blockquote><p>与左连接相似的是，右连接 &#x3D; 右表全留。想用的时候，把表的顺序换一下，用左连接就行。实际开发中很少用右连接。</p><hr><h4 id="3、自连接：一张表自己连自己"><a href="#3、自连接：一张表自己连自己" class="headerlink" title="3、自连接：一张表自己连自己"></a>3、自连接：一张表自己连自己</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 员工表</span></span><br><span class="line"><span class="keyword">CREATE TABLE</span> employee (</span><br><span class="line">    id <span class="type">INT</span> <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    name <span class="type">VARCHAR</span>(<span class="number">50</span>),</span><br><span class="line">    manager_id <span class="type">INT</span></span><br><span class="line">);</span><br><span class="line"><span class="keyword">INSERT INTO</span> employee <span class="keyword">VALUES</span> (<span class="number">1</span>, <span class="string">&#x27;张三&#x27;</span>, <span class="keyword">NULL</span>), (<span class="number">2</span>, <span class="string">&#x27;李四&#x27;</span>, <span class="number">1</span>), (<span class="number">3</span>, <span class="string">&#x27;王五&#x27;</span>, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 查每个员工的上级</span></span><br><span class="line"><span class="keyword">SELECT</span> e.name <span class="keyword">AS</span> 员工, m.name <span class="keyword">AS</span> 上级</span><br><span class="line"><span class="keyword">FROM</span> employee e</span><br><span class="line"><span class="keyword">LEFT</span> <span class="keyword">JOIN</span> employee m <span class="keyword">ON</span> e.manager_id <span class="operator">=</span> m.id;</span><br></pre></td></tr></table></figure><p>自连接主要用于处理同一张表内的层级或比较逻辑，相比其他方案，它能更高效地利用数据库资源。</p><blockquote><p>❗注意<div style="background-color: #616161; border-left: 4px solid #ffc107; padding: 10px; > margin: 10px 0;"> <strong>✅为了区分同一张表，需要对这一张表起两个别名</strong></div></p></blockquote><p>结果：</p><table><thead><tr><th>员工</th><th>上级</th></tr></thead><tbody><tr><td>张三</td><td>NULL</td></tr><tr><td>李四</td><td>张三</td></tr><tr><td>王五</td><td>张三</td></tr></tbody></table><blockquote><p><strong>一句话记忆：自连接 &#x3D; 一张表当两张表用</strong></p></blockquote><hr><h3 id="三、最后的话"><a href="#三、最后的话" class="headerlink" title="三、最后的话"></a>三、最后的话</h3><p>关于多表查询的内容就总结学习完毕了，如果对您有帮助的话，请一键三连！！！ 最后感谢你的查阅！！！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/11/MySQL%E7%AC%94%E8%AE%B0%C2%B7%E7%AC%AC4%E7%AF%87%EF%BC%9A%E5%A4%9A%E8%A1%A8%E6%9F%A5%E8%AF%A2%E8%AF%A6%E8%A7%A3%E2%80%94%E2%80%94%E5%86%85%E8%BF%9E%E6%8E%A5%E3%80%81%E5%B7%A6%E8%BF%9E%E6%8E%A5%E3%80%81%E8%87%AA%E8%BF%9E%E6%8E%A5%E4%B8%80%E7%AF%87%E6%90%9E%E5%AE%9A/</id>
    <link href="https://qixia.site/2026/06/11/MySQL%E7%AC%94%E8%AE%B0%C2%B7%E7%AC%AC4%E7%AF%87%EF%BC%9A%E5%A4%9A%E8%A1%A8%E6%9F%A5%E8%AF%A2%E8%AF%A6%E8%A7%A3%E2%80%94%E2%80%94%E5%86%85%E8%BF%9E%E6%8E%A5%E3%80%81%E5%B7%A6%E8%BF%9E%E6%8E%A5%E3%80%81%E8%87%AA%E8%BF%9E%E6%8E%A5%E4%B8%80%E7%AF%87%E6%90%9E%E5%AE%9A/"/>
    <published>2026-06-10T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h3 id="一、多表查询是什么？"><a href="#一、多表查询是什么？" class="headerlink" title="一、多表查询是什么？"></a>一、多表查询是什么？</h3><p>简单来说，在实际的项目管理中，数据库的查询不仅仅是查询一张表的数据，是两张或]]>
    </summary>
    <title>MySQL笔记·第4篇：多表查询详解——内连接、左连接、自连接一篇搞定</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="Spring Boot" scheme="https://qixia.site/tags/Spring-Boot/"/>
    <content>
      <![CDATA[<h2 id="1-项目搭建与第一个接口"><a href="#1-项目搭建与第一个接口" class="headerlink" title="1. 项目搭建与第一个接口"></a>1. 项目搭建与第一个接口</h2><p>使用 IDEA 的 Spring Initializr 创建项目，依赖仅选择 <strong>Spring Web</strong>。<br>启动类 <code>HttpStuApplication</code> 自动生成。在 <code>controller</code> 包下创建 <code>HelloController</code>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloController</span> &#123;</span><br><span class="line">    <span class="meta">@RequestMapping(&quot;/hello&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">sayHello</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Hello, 请求响应学习开始！&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行后浏览器访问 <code>http://localhost:8080/hello</code>，输出文本 —— 标志着环境验证通过。</p><hr><h2 id="2-请求响应：参数接收全解析"><a href="#2-请求响应：参数接收全解析" class="headerlink" title="2. 请求响应：参数接收全解析"></a>2. 请求响应：参数接收全解析</h2><h3 id="2-1-哪些参数必须掌握？"><a href="#2-1-哪些参数必须掌握？" class="headerlink" title="2.1 哪些参数必须掌握？"></a>2.1 哪些参数必须掌握？</h3><table><thead><tr><th>优先级</th><th>参数类型</th><th>注解</th><th>使用频率</th></tr></thead><tbody><tr><td>🔥 必须掌握</td><td>简单参数</td><td><code>@RequestParam</code>（可省略）</td><td>每天</td></tr><tr><td>🔥 必须掌握</td><td>路径参数</td><td><code>@PathVariable</code></td><td>每天</td></tr><tr><td>🔥 必须掌握</td><td>JSON 参数</td><td><code>@RequestBody</code></td><td>每天</td></tr><tr><td>✅ 常用</td><td>实体参数</td><td>无注解，自动封装</td><td>经常</td></tr><tr><td>⚠️ 了解即可</td><td>日期参数</td><td><code>@DateTimeFormat</code></td><td>偶尔</td></tr><tr><td>⚠️ 了解即可</td><td>数组&#x2F;集合参数</td><td><code>@RequestParam</code> + <code>List</code></td><td>偶尔</td></tr></tbody></table><h3 id="2-2-不用-Postman，如何高效测试？"><a href="#2-2-不用-Postman，如何高效测试？" class="headerlink" title="2.2 不用 Postman，如何高效测试？"></a>2.2 不用 Postman，如何高效测试？</h3><p>IDEA 内置 HTTP Client：在项目根目录re新建 <code>test.http</code> 文件，写入请求，点击绿色箭头即可发送。</p><p><strong>示例</strong>（JSON 参数测试）：</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">POST http://localhost:8080/json</span><br><span class="line"><span class="attribute">Content-Type</span><span class="punctuation">: </span>application/json</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">  &quot;name&quot;: &quot;张三&quot;,</span><br><span class="line">  &quot;age&quot;: 18</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方法零安装、可保存、支持团队共享，非常适合学习阶段。</p><h3 id="2-3-常见参数示例代码"><a href="#2-3-常见参数示例代码" class="headerlink" title="2.3 常见参数示例代码"></a>2.3 常见参数示例代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ParamController</span> &#123;</span><br><span class="line">    <span class="comment">// 简单参数</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/simple&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">simple</span><span class="params">(<span class="meta">@RequestParam</span> String name, Integer age)</span> &#123; ... &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 路径参数</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/path/&#123;id&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">path</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> &#123; ... &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// JSON 参数</span></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/json&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">json</span><span class="params">(<span class="meta">@RequestBody</span> User user)</span> &#123; ... &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 实体参数（自动封装）</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/user&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> User <span class="title function_">user</span><span class="params">(User user)</span> &#123; <span class="keyword">return</span> user; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong>：返回类型为 <code>String</code> 时 Spring 输出纯文本；返回对象&#x2F;集合&#x2F;Map 时自动转为 JSON。</p></blockquote><hr><h2 id="4-分层解耦：三层架构设计"><a href="#4-分层解耦：三层架构设计" class="headerlink" title="4. 分层解耦：三层架构设计"></a>4. 分层解耦：三层架构设计</h2><h3 id="4-1-为什么需要分层？"><a href="#4-1-为什么需要分层？" class="headerlink" title="4.1 为什么需要分层？"></a>4.1 为什么需要分层？</h3><p>最初将所有代码写在 Controller 中，导致：</p><ul><li>重复代码（多个接口都要查用户）</li><li>业务逻辑和请求处理混在一起</li><li>更换数据源（例如从 Map 改为 MySQL）需要改动多处</li></ul><h3 id="4-2-标准三层职责"><a href="#4-2-标准三层职责" class="headerlink" title="4.2 标准三层职责"></a>4.2 标准三层职责</h3><table><thead><tr><th>层级</th><th>包名示例</th><th>职责</th><th>对象类型</th></tr></thead><tbody><tr><td>表示层</td><td><code>controller</code></td><td>接收请求、参数校验、返回响应</td><td><code>XxxController</code></td></tr><tr><td>业务层</td><td><code>service</code></td><td>业务逻辑、事务管理、调用 DAO</td><td><code>XxxService</code> + <code>XxxServiceImpl</code></td></tr><tr><td>数据访问层</td><td><code>dao</code> &#x2F; <code>repository</code></td><td>数据库 CRUD、模拟数据</td><td><code>XxxDao</code> + <code>XxxDaoImpl</code></td></tr></tbody></table><p><strong>调用链</strong>：<br><code>Controller</code> → <code>Service</code> → <code>DAO</code> → 数据 → 原路返回。</p><hr><h2 id="5-IoC-DI：彻底告别-new-关键字"><a href="#5-IoC-DI：彻底告别-new-关键字" class="headerlink" title="5. IoC + DI：彻底告别 new 关键字"></a>5. IoC + DI：彻底告别 <code>new</code> 关键字</h2><h3 id="5-1-IoC-与-DI-概念"><a href="#5-1-IoC-与-DI-概念" class="headerlink" title="5.1 IoC 与 DI 概念"></a>5.1 IoC 与 DI 概念</h3><table><thead><tr><th>概念</th><th>含义</th><th>比喻</th></tr></thead><tbody><tr><td><strong>IoC</strong> (控制反转)</td><td>对象的创建控制权从程序员反转到 Spring 容器</td><td>你不必自己 <code>new</code>，告诉容器“我需要什么”即可</td></tr><tr><td><strong>DI</strong> (依赖注入)</td><td>容器在创建对象时，自动将依赖的对象赋值给字段</td><td>容器帮你“接线”</td></tr></tbody></table><h3 id="5-2-注解驱动的改造步骤"><a href="#5-2-注解驱动的改造步骤" class="headerlink" title="5.2 注解驱动的改造步骤"></a>5.2 注解驱动的改造步骤</h3><p><strong>原代码</strong>（耦合）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">UserDao</span> <span class="variable">userDao</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserDaoImpl</span>();   <span class="comment">// 主动 new</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>改造后</strong>（解耦）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> UserDao userDao;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span>   <span class="comment">// Spring 4.3+ 单构造器可省略</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserServiceImpl</span><span class="params">(UserDao userDao)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.userDao = userDao;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同时为 <code>UserDaoImpl</code> 添加 <code>@Repository</code>，为 <code>UserController</code> 中注入 <code>UserService</code>。</p><h3 id="5-3-为什么推荐构造器注入？"><a href="#5-3-为什么推荐构造器注入？" class="headerlink" title="5.3 为什么推荐构造器注入？"></a>5.3 为什么推荐构造器注入？</h3><table><thead><tr><th>注入方式</th><th>代码量</th><th>不可变性 (<code>final</code>)</th><th>单元测试</th><th>循环依赖检测</th><th>推荐度</th></tr></thead><tbody><tr><td>字段注入</td><td>最少</td><td>❌</td><td>困难（需反射）</td><td>容器内可能掩盖</td><td>❌ 不推荐</td></tr><tr><td>Setter 注入</td><td>中等</td><td>❌</td><td>一般</td><td>无</td><td>⚠️ 可选</td></tr><tr><td><strong>构造器注入</strong></td><td>稍多</td><td>✅</td><td>简单 <code>new</code> 传参</td><td>启动时报错</td><td>✅ <strong>强烈推荐</strong></td></tr></tbody></table><p>构造器注入让依赖显式化、对象不可变、测试更友好。</p><hr><h2 id="6-统一响应格式（Result-封装）"><a href="#6-统一响应格式（Result-封装）" class="headerlink" title="6. 统一响应格式（Result 封装）"></a>6. 统一响应格式（Result 封装）</h2><p>企业级接口通常不直接返回实体对象，而是返回统一结构的 JSON，方便前端全局拦截。</p><p><strong>标准结构</strong>：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;msg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;success&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;张三&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;age&quot;</span><span class="punctuation">:</span> <span class="number">18</span> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>实现</strong>：创建 <code>utils/Result</code> 类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Result</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer code;</span><br><span class="line">    <span class="keyword">private</span> String msg;</span><br><span class="line">    <span class="keyword">private</span> Object data;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Result <span class="title function_">success</span><span class="params">(Object data)</span> &#123;</span><br><span class="line">        <span class="type">Result</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Result</span>();</span><br><span class="line">        r.setCode(<span class="number">1</span>);</span><br><span class="line">        r.setMsg(<span class="string">&quot;success&quot;</span>);</span><br><span class="line">        r.setData(data);</span><br><span class="line">        <span class="keyword">return</span> r;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Result <span class="title function_">error</span><span class="params">(String msg)</span> &#123;</span><br><span class="line">        <span class="type">Result</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Result</span>();</span><br><span class="line">        r.setCode(<span class="number">0</span>);</span><br><span class="line">        r.setMsg(msg);</span><br><span class="line">        <span class="keyword">return</span> r;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// getter / setter 必须存在</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Controller 使用</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/user/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> Result <span class="title function_">getUser</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> &#123;</span><br><span class="line">    <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> userService.getUserById(id);</span><br><span class="line">    <span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> Result.error(<span class="string">&quot;用户不存在&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> Result.success(user);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong>：Result 类必须有 getter&#x2F;setter，否则 Spring 无法将字段序列化为 JSON。</p></blockquote><hr><h2 id="7-总结和最后的话"><a href="#7-总结和最后的话" class="headerlink" title="7. 总结和最后的话"></a>7. 总结和最后的话</h2><table><thead><tr><th>主题</th><th>关键点</th></tr></thead><tbody><tr><td>请求响应</td><td><code>@RequestParam</code>、<code>@PathVariable</code>、<code>@RequestBody</code>；测试用 <code>.http</code></td></tr><tr><td>三层架构</td><td>Controller（请求&#x2F;响应）、Service（业务）、DAO（数据）</td></tr><tr><td>面向接口编程</td><td>接口定义契约，实现类可替换，上层只依赖接口</td></tr><tr><td>IoC&#x2F;DI</td><td><code>@Service</code>&#x2F;<code>@Repository</code> + <code>@Autowired</code>；推荐构造器注入</td></tr><tr><td>统一响应</td><td><code>Result</code> 封装 code&#x2F;msg&#x2F;data，提供静态工厂方法</td></tr></tbody></table>]]>
    </content>
    <id>https://qixia.site/2026/06/11/Spring%20Boot%20%E4%BB%8E%E9%9B%B6%E5%85%A5%E9%97%A8%EF%BC%9A%E8%AF%B7%E6%B1%82%E5%93%8D%E5%BA%94%E3%80%81%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84%E4%B8%8E%20IOC%E3%80%81DI%20%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93/</id>
    <link href="https://qixia.site/2026/06/11/Spring%20Boot%20%E4%BB%8E%E9%9B%B6%E5%85%A5%E9%97%A8%EF%BC%9A%E8%AF%B7%E6%B1%82%E5%93%8D%E5%BA%94%E3%80%81%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84%E4%B8%8E%20IOC%E3%80%81DI%20%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93/"/>
    <published>2026-06-10T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="1-项目搭建与第一个接口"><a href="#1-项目搭建与第一个接口" class="headerlink" title="1. 项目搭建与第一个接口"></a>1. 项目搭建与第一个接口</h2><p>使用 IDEA 的 Spring Initializr]]>
    </summary>
    <title>Spring Boot 从零入门：请求响应、三层架构与 IOC、DI 实践总结</title>
    <updated>2026-07-22T01:33:15.025Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="Spring Boot" scheme="https://qixia.site/tags/Spring-Boot/"/>
    <content>
      <![CDATA[<h1 id="从UUID到JWT再到Filter-Interceptor：Spring-Boot登录认证进阶之路"><a href="#从UUID到JWT再到Filter-Interceptor：Spring-Boot登录认证进阶之路" class="headerlink" title="从UUID到JWT再到Filter&#x2F;Interceptor：Spring Boot登录认证进阶之路"></a>从UUID到JWT再到Filter&#x2F;Interceptor：Spring Boot登录认证进阶之路</h1><p>这篇文章要带你从零实现一个Spring Boot登录接口，并一步步将它从“临时UUID令牌”演变成<strong>无状态的JWT</strong>，再通过<strong>Filter → Interceptor → 统一异常处理</strong>，最终得到一个规范、可维护的认证架构。我们不依赖前端，只使用IDEA内置的HTTP Client做所有测试。所有代码都会给出，你可以复制即用。</p><hr><h2 id="1-基础登录：模拟数据-UUID令牌"><a href="#1-基础登录：模拟数据-UUID令牌" class="headerlink" title="1. 基础登录：模拟数据 + UUID令牌"></a>1. 基础登录：模拟数据 + UUID令牌</h2><p>我们先从最简单的入手：接收用户名密码，验证后返回一个临时令牌。所有用户数据先用<code>HashMap</code>硬编码在内存里，令牌就用<code>UUID</code>随机生成。</p><h3 id="1-1-项目结构"><a href="#1-1-项目结构" class="headerlink" title="1.1 项目结构"></a>1.1 项目结构</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">src/main/java/com/example/demo</span><br><span class="line">├── DemoApplication.java                     // 启动类</span><br><span class="line">├── config</span><br><span class="line">│   └── WebConfig.java                       // 配置拦截器、跨域等</span><br><span class="line">├── controller</span><br><span class="line">│   └── UserController.java                  // 登录、用户接口</span><br><span class="line">├── dto</span><br><span class="line">│   └── LoginRequest.java                    // 登录请求体</span><br><span class="line">├── exception</span><br><span class="line">│   ├── GlobalExceptionHandler.java          // 全局异常处理</span><br><span class="line">│   └── UnauthorizedException.java           // 自定义未授权异常</span><br><span class="line">├── filter</span><br><span class="line">│   └── LoginCheckFilter.java                // 登录校验过滤器（可选）</span><br><span class="line">├── interceptor</span><br><span class="line">│   └── LoginCheckInterceptor.java           // 登录校验拦截器</span><br><span class="line">├── service</span><br><span class="line">│   └── UserService.java                     // 用户服务（验证逻辑）</span><br><span class="line">└── util</span><br><span class="line">    └── JwtUtil.java                         // JWT 工具类</span><br></pre></td></tr></table></figure><h3 id="1-2-请求DTO"><a href="#1-2-请求DTO" class="headerlink" title="1.2 请求DTO"></a>1.2 请求DTO</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// LoginRequest.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginRequest</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="comment">// 必须有无参构造，Spring才能把JSON转成对象</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">LoginRequest</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    <span class="comment">// getter/setter 略</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong>：如果只有全参构造而没有无参构造，Spring反序列化时会直接报400，这是一个新手非常容易踩的坑。</p></blockquote><h3 id="1-3-Service——模拟用户与令牌管理"><a href="#1-3-Service——模拟用户与令牌管理" class="headerlink" title="1.3 Service——模拟用户与令牌管理"></a>1.3 Service——模拟用户与令牌管理</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="comment">// 模拟数据库中的用户</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;String, String&gt; MOCK_USERS = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        MOCK_USERS.put(<span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123456&quot;</span>);</span><br><span class="line">        MOCK_USERS.put(<span class="string">&quot;user&quot;</span>, <span class="string">&quot;password&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 临时存储已登录的令牌（有状态方案）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Set&lt;String&gt; TOKEN_STORE = ConcurrentHashMap.newKeySet();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">login</span><span class="params">(LoginRequest request)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">pwd</span> <span class="operator">=</span> MOCK_USERS.get(request.getUsername());</span><br><span class="line">        <span class="keyword">if</span> (pwd != <span class="literal">null</span> &amp;&amp; pwd.equals(request.getPassword())) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> UUID.randomUUID().toString();</span><br><span class="line">            TOKEN_STORE.add(token);           <span class="comment">// 记住这个令牌</span></span><br><span class="line">            <span class="keyword">return</span> token;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValidToken</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> token != <span class="literal">null</span> &amp;&amp; TOKEN_STORE.contains(token);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><!-- 插图建议：流程图 “客户端 → Controller → Service → 查MOCK_USERS → 生成UUID → 存入TOKEN_STORE → 返回token” --><h3 id="1-4-Controller"><a href="#1-4-Controller" class="headerlink" title="1.4 Controller"></a>1.4 Controller</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping(&quot;/api/login&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;?&gt; login(<span class="meta">@RequestBody</span> LoginRequest request) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> userService.login(request);</span><br><span class="line">        <span class="keyword">if</span> (token != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> ResponseEntity.ok(Map.of(<span class="string">&quot;token&quot;</span>, token));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> ResponseEntity.status(HttpStatus.UNAUTHORIZED)</span><br><span class="line">                    .body(Map.of(<span class="string">&quot;msg&quot;</span>, <span class="string">&quot;用户名或密码错误&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-5-测试"><a href="#1-5-测试" class="headerlink" title="1.5 测试"></a>1.5 测试</h3><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">POST http://localhost:8080/api/login</span><br><span class="line"><span class="attribute">Content-Type</span><span class="punctuation">: </span>application/json</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">  &quot;username&quot;: &quot;admin&quot;,</span><br><span class="line">  &quot;password&quot;: &quot;123456&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>成功返回 <code>200</code> 和一个随机的UUID。虽然跑通了，但这个方案有两大问题：</p><ul><li><strong>令牌随机，不携带任何用户信息</strong>，服务端必须维护一个 <code>TOKEN_STORE</code> 才知道谁是谁。</li><li><strong>有状态</strong>：一旦重启应用，所有登录状态全丢，扩展多实例时还需要共享存储。</li></ul><hr><h2 id="2-从UUID到JWT：让令牌自带“身份证”"><a href="#2-从UUID到JWT：让令牌自带“身份证”" class="headerlink" title="2. 从UUID到JWT：让令牌自带“身份证”"></a>2. 从UUID到JWT：让令牌自带“身份证”</h2><p>我们希望令牌自己能“说话”，携带用户名和有效期，服务端不用再记——这就是<strong>无状态</strong>的JWT（Json Web Token）。</p><h3 id="2-1-有状态-vs-无状态对比"><a href="#2-1-有状态-vs-无状态对比" class="headerlink" title="2.1 有状态 vs 无状态对比"></a>2.1 有状态 vs 无状态对比</h3><table><thead><tr><th>方案</th><th>状态</th><th>存储位置</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>UUID令牌</td><td>有状态</td><td>服务器内存&#x2F;Redis</td><td>实现简单</td><td>扩展性差，内存占用</td></tr><tr><td><strong>JWT</strong></td><td>无状态</td><td>客户端本地</td><td>服务端无需存储，自带用户信息，防篡改</td><td>无法主动注销(需配合黑名单)，payload仅Base64不加密</td></tr></tbody></table><h3 id="2-2-添加JWT依赖"><a href="#2-2-添加JWT依赖" class="headerlink" title="2.2 添加JWT依赖"></a>2.2 添加JWT依赖</h3><p><code>pom.xml</code> 中加入：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt-api<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.11.5<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt-impl<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.11.5<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>runtime<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt-jackson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.11.5<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>runtime<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="2-3-编写JwtUtil工具类"><a href="#2-3-编写JwtUtil工具类" class="headerlink" title="2.3 编写JwtUtil工具类"></a>2.3 编写JwtUtil工具类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtUtil</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Key</span> <span class="variable">KEY</span> <span class="operator">=</span> Keys.secretKeyFor(SignatureAlgorithm.HS256); <span class="comment">// 随机密钥</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">EXPIRATION_MS</span> <span class="operator">=</span> <span class="number">3600_000</span>; <span class="comment">// 1小时</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">generateToken</span><span class="params">(String username)</span> &#123;</span><br><span class="line">        <span class="type">Date</span> <span class="variable">now</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>();</span><br><span class="line">        <span class="type">Date</span> <span class="variable">expiration</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>(now.getTime() + EXPIRATION_MS);</span><br><span class="line">        <span class="keyword">return</span> Jwts.builder()</span><br><span class="line">                .setSubject(username)      <span class="comment">// 主题放用户名</span></span><br><span class="line">                .setIssuedAt(now)</span><br><span class="line">                .setExpiration(expiration)</span><br><span class="line">                .signWith(KEY)</span><br><span class="line">                .compact();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Claims <span class="title function_">parseToken</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Jwts.parserBuilder()</span><br><span class="line">                .setSigningKey(KEY)</span><br><span class="line">                .build()</span><br><span class="line">                .parseClaimsJws(token)</span><br><span class="line">                .getBody();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-4-精简Service"><a href="#2-4-精简Service" class="headerlink" title="2.4 精简Service"></a>2.4 精简Service</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;String, String&gt; MOCK_USERS = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        MOCK_USERS.put(<span class="string">&quot;admin&quot;</span>, <span class="string">&quot;123456&quot;</span>);</span><br><span class="line">        MOCK_USERS.put(<span class="string">&quot;user&quot;</span>, <span class="string">&quot;password&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 不再需要 TOKEN_STORE !</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">login</span><span class="params">(LoginRequest request)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">pwd</span> <span class="operator">=</span> MOCK_USERS.get(request.getUsername());</span><br><span class="line">        <span class="keyword">if</span> (pwd != <span class="literal">null</span> &amp;&amp; pwd.equals(request.getPassword())) &#123;</span><br><span class="line">            <span class="keyword">return</span> JwtUtil.generateToken(request.getUsername());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValidJwt</span><span class="params">(String token)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            JwtUtil.parseToken(token);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Controller也相应调整：<code>/api/info</code> 接口从请求头提取JWT并解析，获取用户名。此时我们会遇到一个重要的HTTP细节：<code>Bearer</code> 前缀。</p><hr><h2 id="3-踩坑：Bearer前缀与测试那些事"><a href="#3-踩坑：Bearer前缀与测试那些事" class="headerlink" title="3. 踩坑：Bearer前缀与测试那些事"></a>3. 踩坑：Bearer前缀与测试那些事</h2><p>我们测试 <code>/api/info</code> 时，要求请求头写：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...</span><br></pre></td></tr></table></figure><p>如果你只写了 <code>Authorization: 你的token</code>，服务器会认为格式错误，返回401。<strong>Bearer</strong> 是一种认证方案标识，告诉服务器“后面跟的是持有者令牌”。解析时我们用 <code>substring(7)</code> 跳过了“Bearer ”这7个字符。</p><h3 id="3-1-另一个坑：JWT立即过期"><a href="#3-1-另一个坑：JWT立即过期" class="headerlink" title="3.1 另一个坑：JWT立即过期"></a>3.1 另一个坑：JWT立即过期</h3><p>测试时我们故意把 <code>EXPIRATION_MS</code> 改成了10秒，想验证过期效果，结果发现怎么快都提示过期。排查后发现是过早复制了错误单位（比如写了1毫秒）。后来改成 <code>10_000</code> 就正常了。过期时间的单位必须是<strong>毫秒</strong>。</p><!-- 插图建议：对比图 左边UUID有状态，右边JWT无状态 --><hr><h2 id="4-过滤器Filter：第一道防线"><a href="#4-过滤器Filter：第一道防线" class="headerlink" title="4. 过滤器Filter：第一道防线"></a>4. 过滤器Filter：第一道防线</h2><p>现在我们想统一校验所有需要登录的请求，而不是在每个Controller里重复写解析代码。首先想到的就是<strong>Servlet Filter</strong>。</p><h3 id="4-1-Filter的作用"><a href="#4-1-Filter的作用" class="headerlink" title="4.1 Filter的作用"></a>4.1 Filter的作用</h3><p>Filter运行在Servlet容器层，在请求进入Spring MVC的DispatcherServlet之前执行，可以拦截任何资源。</p><h3 id="4-2-创建LoginCheckFilter（Spring-Boot-3-x-版本）"><a href="#4-2-创建LoginCheckFilter（Spring-Boot-3-x-版本）" class="headerlink" title="4.2 创建LoginCheckFilter（Spring Boot 3.x 版本）"></a>4.2 创建LoginCheckFilter（Spring Boot 3.x 版本）</h3><p><strong>注意</strong>：Spring Boot 3.x 使用 <code>jakarta.servlet.*</code>，2.x 是 <code>javax.servlet.*</code>，下面的代码基于3.x。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginCheckFilter</span> <span class="keyword">implements</span> <span class="title class_">Filter</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFilter</span><span class="params">(ServletRequest req, ServletResponse res, FilterChain chain)</span></span><br><span class="line">            <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">        <span class="type">HttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> (HttpServletRequest) req;</span><br><span class="line">        <span class="type">HttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> (HttpServletResponse) res;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">url</span> <span class="operator">=</span> request.getRequestURL().toString();</span><br><span class="line">        <span class="keyword">if</span> (url.endsWith(<span class="string">&quot;/api/login&quot;</span>)) &#123;</span><br><span class="line">            chain.doFilter(req, res);    <span class="comment">// 登录接口直接放行</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">authHeader</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Authorization&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (!StringUtils.hasText(authHeader) || !authHeader.startsWith(<span class="string">&quot;Bearer &quot;</span>)) &#123;</span><br><span class="line">            response.setContentType(<span class="string">&quot;application/json;charset=UTF-8&quot;</span>);</span><br><span class="line">            response.getWriter().write(<span class="string">&quot;&#123;\&quot;code\&quot;:401,\&quot;msg\&quot;:\&quot;未登录\&quot;&#125;&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> authHeader.substring(<span class="number">7</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> JwtUtil.parseToken(token);</span><br><span class="line">            request.setAttribute(<span class="string">&quot;username&quot;</span>, claims.getSubject());</span><br><span class="line">            chain.doFilter(req, res);    <span class="comment">// 校验通过放行</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            response.setContentType(<span class="string">&quot;application/json;charset=UTF-8&quot;</span>);</span><br><span class="line">            response.getWriter().write(<span class="string">&quot;&#123;\&quot;code\&quot;:401,\&quot;msg\&quot;:\&quot;Token无效\&quot;&#125;&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样Controller里的校验代码就可以删掉了，直接从 <code>request.getAttribute(&quot;username&quot;)</code> 取用户信息。</p><h3 id="4-3-Filter的尴尬：异常无法被Spring全局捕获"><a href="#4-3-Filter的尴尬：异常无法被Spring全局捕获" class="headerlink" title="4.3 Filter的尴尬：异常无法被Spring全局捕获"></a>4.3 Filter的尴尬：异常无法被Spring全局捕获</h3><p>Filter中一旦校验失败，我们只能手动拼接JSON并用 <code>response.getWriter()</code> 写回。这样不但繁琐，而且<strong>抛出的异常不会被Spring的 <code>@RestControllerAdvice</code> 捕获</strong>，因为Filter在Spring MVC的外层。这就引出了更优雅的方案：<strong>拦截器（Interceptor）</strong>。</p><hr><h2 id="5-Interceptor登场：纳入Spring的异常体系"><a href="#5-Interceptor登场：纳入Spring的异常体系" class="headerlink" title="5. Interceptor登场：纳入Spring的异常体系"></a>5. Interceptor登场：纳入Spring的异常体系</h2><p>Interceptor是Spring MVC提供的拦截器，它位于DispatcherServlet之后、Controller之前，所以<strong>其抛出的异常可以被Spring的全局异常处理器捕获</strong>。</p><h3 id="5-1-Filter-vs-Interceptor"><a href="#5-1-Filter-vs-Interceptor" class="headerlink" title="5.1 Filter vs Interceptor"></a>5.1 Filter vs Interceptor</h3><table><thead><tr><th>对比项</th><th>Filter</th><th>Interceptor</th></tr></thead><tbody><tr><td>所处层次</td><td>Servlet容器</td><td>Spring MVC</td></tr><tr><td>能否被Spring异常处理</td><td>❌</td><td>✅</td></tr><tr><td>适用场景</td><td>编码过滤、安全过滤</td><td>登录校验、日志、权限</td></tr></tbody></table><!-- 插图建议：请求流程图 “Tomcat → Filter → DispatcherServlet → Interceptor → Controller” --><h3 id="5-2-自定义未授权异常"><a href="#5-2-自定义未授权异常" class="headerlink" title="5.2 自定义未授权异常"></a>5.2 自定义未授权异常</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UnauthorizedException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">code</span> <span class="operator">=</span> <span class="number">401</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UnauthorizedException</span><span class="params">(String msg)</span> &#123; <span class="built_in">super</span>(msg); &#125;</span><br><span class="line">    <span class="comment">// getter</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-3-编写LoginCheckInterceptor"><a href="#5-3-编写LoginCheckInterceptor" class="headerlink" title="5.3 编写LoginCheckInterceptor"></a>5.3 编写LoginCheckInterceptor</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginCheckInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request,</span></span><br><span class="line"><span class="params">                             HttpServletResponse response, Object handler)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">url</span> <span class="operator">=</span> request.getRequestURL().toString();</span><br><span class="line">        <span class="keyword">if</span> (url.endsWith(<span class="string">&quot;/api/login&quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;   <span class="comment">// 放行</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">authHeader</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Authorization&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (!StringUtils.hasText(authHeader) || !authHeader.startsWith(<span class="string">&quot;Bearer &quot;</span>)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnauthorizedException</span>(<span class="string">&quot;未登录或Token格式错误&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> authHeader.substring(<span class="number">7</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> JwtUtil.parseToken(token);</span><br><span class="line">            request.setAttribute(<span class="string">&quot;username&quot;</span>, claims.getSubject());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnauthorizedException</span>(<span class="string">&quot;Token无效或已过期&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;   <span class="comment">// 放行</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-4-配置拦截器白名单"><a href="#5-4-配置拦截器白名单" class="headerlink" title="5.4 配置拦截器白名单"></a>5.4 配置拦截器白名单</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> LoginCheckInterceptor loginCheckInterceptor;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line">        registry.addInterceptor(loginCheckInterceptor)</span><br><span class="line">                .addPathPatterns(<span class="string">&quot;/**&quot;</span>)</span><br><span class="line">                .excludePathPatterns(<span class="string">&quot;/api/login&quot;</span>);   <span class="comment">// 登录接口不拦截</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样我们将LoginCheckFilter注释掉，完全由拦截器接管JWT校验，并且校验失败时抛出的 <code>UnauthorizedException</code> 会被接下来要写的全局异常处理器兜底。</p><hr><h2 id="6-统一异常处理：-RestControllerAdvice"><a href="#6-统一异常处理：-RestControllerAdvice" class="headerlink" title="6. 统一异常处理：@RestControllerAdvice"></a>6. 统一异常处理：@RestControllerAdvice</h2><p>有了自定义异常，我们就可以集中管理所有错误响应，确保前端收到统一的JSON结构。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestControllerAdvice</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GlobalExceptionHandler</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExceptionHandler(UnauthorizedException.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;Map&lt;String, Object&gt;&gt; <span class="title function_">handleUnauthorized</span><span class="params">(UnauthorizedException e)</span> &#123;</span><br><span class="line">        Map&lt;String, Object&gt; result = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        result.put(<span class="string">&quot;code&quot;</span>, e.getCode());</span><br><span class="line">        result.put(<span class="string">&quot;msg&quot;</span>, e.getMessage());</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResponseEntity</span>&lt;&gt;(result, HttpStatus.UNAUTHORIZED);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExceptionHandler(Exception.class)</span></span><br><span class="line">    <span class="keyword">public</span> ResponseEntity&lt;Map&lt;String, Object&gt;&gt; <span class="title function_">handleOther</span><span class="params">(Exception e)</span> &#123;</span><br><span class="line">        Map&lt;String, Object&gt; result = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        result.put(<span class="string">&quot;code&quot;</span>, <span class="number">500</span>);</span><br><span class="line">        result.put(<span class="string">&quot;msg&quot;</span>, <span class="string">&quot;服务器内部错误：&quot;</span> + e.getMessage());</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResponseEntity</span>&lt;&gt;(result, HttpStatus.INTERNAL_SERVER_ERROR);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在再访问不带token的 <code>/api/info</code>，你会看到响应状态码是401，而JSON内容也规范了。我们不再需要手动拼接JSON字符串，Interceptor只需抛出异常，一切交给全局处理器。</p><hr><h2 id="7-总结：一张清单回顾所有要点"><a href="#7-总结：一张清单回顾所有要点" class="headerlink" title="7. 总结：一张清单回顾所有要点"></a>7. 总结：一张清单回顾所有要点</h2><table><thead><tr><th>主题</th><th>关键点</th></tr></thead><tbody><tr><td>基础登录</td><td>接收 <code>@RequestBody</code>，用 <code>HashMap</code> 模拟用户，返回UUID令牌</td></tr><tr><td>JWT无状态令牌</td><td><code>jjwt</code> 依赖，生成&#x2F;解析JWT，<code>setSubject(username)</code> 存储用户标识</td></tr><tr><td><code>Bearer</code> 前缀</td><td>HTTP认证方案标识，提取时需 <code>substring(7)</code> 去除</td></tr><tr><td>Filter</td><td>Servlet层拦截，手动 <code>response.getWriter()</code>，异常无法被Spring全局捕获</td></tr><tr><td>Interceptor</td><td>Spring MVC层拦截，可抛出异常交 <code>@RestControllerAdvice</code> 处理</td></tr><tr><td>统一异常处理</td><td><code>@RestControllerAdvice</code> + <code>@ExceptionHandler</code> 定义统一JSON错误响应</td></tr><tr><td>包版本适配</td><td>Spring Boot 3.x 用 <code>jakarta.servlet.*</code>，2.x 用 <code>javax.servlet.*</code></td></tr></tbody></table><h2 id="最后的话："><a href="#最后的话：" class="headerlink" title="最后的话："></a>最后的话：</h2><p>我们从一段简单的登录接口出发，经历了UUID的临时方案，演化到JWT无状态认证，再通过Filter和Interceptor的对比实践，最终用全局异常处理收尾。现在你不但会写登录，更理解了背后分层与拦截器的设计思想。建议你把代码自己敲一遍，改一改白名单，尝试加入密码加密（BCrypt），这会是你成为后端熟手的重要一步。欢迎在评论区分享你的练习心得！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/11/%E6%90%9E%E6%87%82Spring%20Boot%E7%99%BB%E5%BD%95%E8%AE%A4%E8%AF%81%EF%BC%9A%E4%BB%8EUUID%E5%88%B0JWT%EF%BC%8C%E4%B8%80%E6%AC%A1%E5%AE%8C%E6%95%B4%E7%9A%84%E6%9E%B6%E6%9E%84%E6%8E%A8%E6%BC%94/</id>
    <link href="https://qixia.site/2026/06/11/%E6%90%9E%E6%87%82Spring%20Boot%E7%99%BB%E5%BD%95%E8%AE%A4%E8%AF%81%EF%BC%9A%E4%BB%8EUUID%E5%88%B0JWT%EF%BC%8C%E4%B8%80%E6%AC%A1%E5%AE%8C%E6%95%B4%E7%9A%84%E6%9E%B6%E6%9E%84%E6%8E%A8%E6%BC%94/"/>
    <published>2026-06-10T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="从UUID到JWT再到Filter-Interceptor：Spring-Boot登录认证进阶之路"><a href="#从UUID到JWT再到Filter-Interceptor：Spring-Boot登录认证进阶之路" class="headerlink" t]]>
    </summary>
    <title>搞懂Spring Boot登录认证：从UUID到JWT，一次完整的架构推演</title>
    <updated>2026-07-22T01:33:15.025Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="JavaSE" scheme="https://qixia.site/tags/JavaSE/"/>
    <content>
      <![CDATA[<h2 id="编程语言的分类"><a href="#编程语言的分类" class="headerlink" title="编程语言的分类"></a>编程语言的分类</h2><p>面向对象（Java）和面向过程（C语言）是两种主流编程范式。 它们到底有什么区别？本文用锤子和咖啡两个例子，讲清楚编程范式的核心思想。</p><h3 id="1、面向过程的编程语言"><a href="#1、面向过程的编程语言" class="headerlink" title="1、面向过程的编程语言"></a>1、面向过程的编程语言</h3><p>面向对象的编程范式其实上是将我们需要解决的问题拆解，变成一个一个的步骤，先做什么，后做什么。<br>典型的面向过程的编程语言是我们编程学习的启蒙语言————C语言，我们在学习的过程中大部分时候都是在编写函数，各种各样的函数，在主程序中不断的根据需求依次的去调用函数，将需要处理的数据给函数，函数返回一个处理后的结果。<br>这个过程就像在制造工具来解决实际问题，比如制造一把锤子，你需要先砍树造木柄，然后挖铁矿冶炼铁，紧接着锻造锤头，最后组装，那么你就拥有了一把锤子，你可以拿他钉钉子或是其他的需要。</p><p>以下面的一个简单的C语言程序为例：在程序运行过程中，我们将需要计算的数据a和b，传给做加法运算的函数sum()，函数sum()将计算后的结果赋值给变量sum，然后通过printf()函数输出出来，至此程序结束。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">int</span> a=<span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> b=<span class="number">2</span>;</span><br><span class="line">    <span class="type">int</span> sum=sum(a,b);</span><br><span class="line">    <span class="built_in">printf</span>(sum);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> <span class="title function_">sum</span><span class="params">(<span class="type">int</span> a,<span class="type">int</span> b)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a+b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由程序运行逻辑来看，面向过程的特点：<br> • 程序&#x3D;数据+函数<br> • 线性执行流程<br> • 适合：脚本、简单工具、一次性任务<br> • 缺点：当需求变化时，需要大量修改</p><h3 id="2、面向对象的编程语言"><a href="#2、面向对象的编程语言" class="headerlink" title="2、面向对象的编程语言"></a>2、面向对象的编程语言</h3><p>面向对象的编程范式不再是将我们需要解决的问题拆分成一个一个的步骤，而是为了解决这一问题，我需要用到哪些工具，先用哪个工具。再用哪个工具。<br>典型的面向对象的编程语言是现在在IT行业火爆的Java，我们在学习的过程中大部分时候都是在编写对象的属性和行为，各种各样的对象，在主程序中不断的根据需求依次的去调用对象里的属性或行为来解决问题，将需要处理的数据传给对象，对象根据其定义的属性和行为给予我们反馈。<br>这个过程就像是用工具去解决实际问题，比如说我需要喝一杯咖啡，我需要一些咖啡豆、一个咖啡机和一个杯子，我利用这些工具使得我喝到一杯咖啡，开启元气满满的一天。<br><img src="/img/JavaSE_1/coffee.png" alt="喝咖啡"></p><p>以下面的一个简单的Java程序为例：创建三个对象，分别是咖啡类、咖啡机类和人类。先创建一个人类的对象，再依次调用咖啡类、咖啡机类和人类里的行为和属性完成喝咖啡的行为。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 咖啡类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Coffee</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String type;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> temperature;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Coffee</span><span class="params">(String type, <span class="type">int</span> temperature)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.type = type;</span><br><span class="line">        <span class="built_in">this</span>.temperature = temperature;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">drink</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;喝&quot;</span> + type + <span class="string">&quot;咖啡，温度：&quot;</span> + temperature + <span class="string">&quot;°C&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">coolDown</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.temperature -= <span class="number">5</span>;</span><br><span class="line">        System.out.println(type + <span class="string">&quot;咖啡降温到：&quot;</span> + temperature + <span class="string">&quot;°C&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 咖啡机制作咖啡</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CoffeeMachine</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> Coffee <span class="title function_">makeCoffee</span><span class="params">(String type)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;咖啡机正在制作&quot;</span> + type + <span class="string">&quot;咖啡...&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Coffee</span>(type, <span class="number">85</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 人类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Person</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">drinkCoffee</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(name + <span class="string">&quot;说：我想喝咖啡&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 创建咖啡机对象</span></span><br><span class="line">        <span class="type">CoffeeMachine</span> <span class="variable">machine</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CoffeeMachine</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 咖啡机制作咖啡</span></span><br><span class="line">        <span class="type">Coffee</span> <span class="variable">myCoffee</span> <span class="operator">=</span> machine.makeCoffee(<span class="string">&quot;拿铁&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 咖啡太烫，先降温</span></span><br><span class="line">        myCoffee.coolDown();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 喝咖啡</span></span><br><span class="line">        myCoffee.drink();</span><br><span class="line">        </span><br><span class="line">        System.out.println(name + <span class="string">&quot;喝完了咖啡&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 主程序</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DrinkCoffeeExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 创建一个人对象</span></span><br><span class="line">        <span class="type">Person</span> <span class="variable">me</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;小明&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 这个人要喝咖啡</span></span><br><span class="line">        me.drinkCoffee();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由程序运行逻辑来看，面向对象的特点：<br> • 程序&#x3D;对象+类<br> • 非线性执行流程<br> • 适合：复杂工具、非一次性任务<br> • 缺点：当需求变化时，不需要大量修改</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p> 面向过程和面向对象这两种编程范式是现在最为主流的，理解这两种编程范式的区别有助于更好的理解各个编程语言的特点和应用场景，简单的来说面向过程的编程语言适合硬件开发，面向对象的编程语言适合软件开发。</p><table><thead><tr><th align="left">维度</th><th align="left">面向过程 (Procedure-Oriented)</th><th align="left">面向对象 (Object-Oriented)</th></tr></thead><tbody><tr><td align="left"><strong>核心思想</strong></td><td align="left">执行步骤：怎么做 (How to do)</td><td align="left">对象交互：谁来做 (Who to do)</td></tr><tr><td align="left"><strong>典型语言</strong></td><td align="left">C, Pascal, Fortran</td><td align="left">Java, C++, Python, C#</td></tr><tr><td align="left"><strong>代码单元</strong></td><td align="left">函数 (Function)</td><td align="left">类 (Class) 和 对象 (Object)</td></tr><tr><td align="left"><strong>数据关系</strong></td><td align="left">数据与函数分离</td><td align="left">数据（属性）与函数（方法）封装在一起</td></tr><tr><td align="left"><strong>扩展难度</strong></td><td align="left">需修改大量函数调用</td><td align="left">通过继承和多态扩展，改动较小</td></tr><tr><td align="left"><strong>适配场景</strong></td><td align="left"><strong>硬件驱动、操作系统内核、简单脚本</strong><br>（强调性能、直接控制硬件）</td><td align="left"><strong>GUI开发、大型企业级应用、游戏开发</strong><br>（强调维护性、复用性、复杂业务逻辑）</td></tr></tbody></table><p>下篇文章我们以Java为例，从面向对象的三大特性出发，更加深入的了解面向对象。不仅可以加深对面向对象的理解，而且能提高代码的高效性。如果可以的话，留下你的一件三连，往后给你更加高质量的文章，点击下面的跳转入口，更快学到新知识。最后感谢您的查阅！！！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/10/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC1%E7%AF%87%EF%BC%9A%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%20vs%20%E9%9D%A2%E5%90%91%E8%BF%87%E7%A8%8B%E2%80%94%E2%80%94Java%E5%92%8CC%E8%AF%AD%E8%A8%80%E5%88%B0%E5%BA%95%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB%EF%BC%9F/</id>
    <link href="https://qixia.site/2026/06/10/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC1%E7%AF%87%EF%BC%9A%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%20vs%20%E9%9D%A2%E5%90%91%E8%BF%87%E7%A8%8B%E2%80%94%E2%80%94Java%E5%92%8CC%E8%AF%AD%E8%A8%80%E5%88%B0%E5%BA%95%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB%EF%BC%9F/"/>
    <published>2026-06-09T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="编程语言的分类"><a href="#编程语言的分类" class="headerlink" title="编程语言的分类"></a>编程语言的分类</h2><p>面向对象（Java）和面向过程（C语言）是两种主流编程范式。 它们到底有什么区别？本文用锤子和咖啡两]]>
    </summary>
    <title>Java基础·第1篇：面向对象 vs 面向过程——Java和C语言到底有什么区别？</title>
    <updated>2026-07-22T01:33:15.023Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="JavaSE" scheme="https://qixia.site/tags/JavaSE/"/>
    <content>
      <![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>在我们学习面向过程的编程语言，比如说C语言时,一定会出现下面的情况：<br>假设你定义了一个全局变量 int balance &#x3D; 1000;（余额）。在C语言里，程序中任何一个函数都可以直接访问并修改这个 balance。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 面向过程风格（C语言）</span></span><br><span class="line"><span class="type">int</span> balance = <span class="number">1000</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">buyCoffee</span><span class="params">()</span> &#123;</span><br><span class="line">    balance -= <span class="number">20</span>; <span class="comment">//直接修改</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">hackFunction</span><span class="params">()</span> &#123;</span><br><span class="line">    balance = <span class="number">0</span>; <span class="comment">// 任何函数都能随意篡改，没有任何阻拦！</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因为没有数据的保护机制，数据对所有函数都是公开的，任何地方写错了一行代码，都可能导致数据被意外修改。如果在编写程序的时候，意外的写错了数据，造成bug出现，就要在几百行代码里找问题，很麻烦。</p><p>而在面向对象的编程语言中，可以对数据进行封装来实现对数据的保护。</p><h4 id="为什么需要封装？从数据的“裸奔”说起"><a href="#为什么需要封装？从数据的“裸奔”说起" class="headerlink" title="为什么需要封装？从数据的“裸奔”说起"></a>为什么需要封装？从数据的“裸奔”说起</h4><p>在接触封装之前，我们定义的类通常长这样：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Student</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> String name;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> age;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">double</span> score;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种设计看似简单直接，外部调用者可以随心所欲地访问和修改这些字段。但问题也随之而来：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Student</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Student</span>();</span><br><span class="line">s.age = -<span class="number">20</span>; <span class="comment">// 年龄怎么可能是负数？</span></span><br><span class="line">s.score = <span class="number">150</span>; <span class="comment">// 满分100，考150分？</span></span><br></pre></td></tr></table></figure><p>编译器不会报错，但程序产生了违背现实逻辑的“脏数据”。这种直接暴露内部数据的方式，就像是让数据在“裸奔”，没有任何安全性可言。一旦数据出错，排查起来非常困难，因为任何地方都可能修改了它。</p><h4 id="封装的本质：信息隐藏与访问控制"><a href="#封装的本质：信息隐藏与访问控制" class="headerlink" title="封装的本质：信息隐藏与访问控制"></a>封装的本质：信息隐藏与访问控制</h4><p>封装的核心思想其实就两点：<strong>信息隐藏</strong>和<strong>提供公共接口</strong>。</p><ul><li><strong>信息隐藏</strong>：把不想让外界知道的内部细节（比如具体的变量存储）藏起来。</li><li><strong>提供公共接口</strong>：只暴露必要的方法给外界使用，并在这些方法里加上“安检”。</li></ul><p>在Java中，我们通过访问修饰符来实现这一机制。目前我们主要用到了两个：</p><ul><li><code>private</code>：私有权限，只有类自己能访问。</li><li><code>public</code>：公共权限，谁都可以访问。</li></ul><h4 id="代码实战：如何正确实现封装"><a href="#代码实战：如何正确实现封装" class="headerlink" title="代码实战：如何正确实现封装"></a>代码实战：如何正确实现封装</h4><p>实现封装的标准动作分两步：</p><ol><li>把所有属性（字段）都用<code>private</code>修饰，切断外部直接访问的路径。</li><li>提供<code>public</code>的<code>getter</code>和<code>setter</code>方法，作为外界访问数据的唯一通道。</li></ol><p>让我们重构上面的<code>Student</code>类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Student</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 属性私有化</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">double</span> score;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2. 提供公共的 getter 和 setter</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在 setter 中加入逻辑判断</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (age &gt; <span class="number">0</span> &amp;&amp; age &lt; <span class="number">150</span>) &#123;</span><br><span class="line">            <span class="built_in">this</span>.age = age;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;年龄输入不合法！&quot;</span>);</span><br><span class="line">            <span class="comment">// 实际开发中可能会抛出异常</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">getScore</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> score;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setScore</span><span class="params">(<span class="type">double</span> score)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (score &gt;= <span class="number">0</span> &amp;&amp; score &lt;= <span class="number">100</span>) &#123;</span><br><span class="line">            <span class="built_in">this</span>.score = score;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;分数必须在0-100之间！&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在，外部代码如果想修改年龄，必须调用<code>setAge()</code>。如果我们尝试传入<code>-20</code>，就会被方法内部的逻辑拦截。这样，我们就保证了<code>Student</code>对象中数据的完整性和安全</p><h4 id="封装带来的好处"><a href="#封装带来的好处" class="headerlink" title="封装带来的好处"></a>封装带来的好处</h4><ul><li><strong>提高安全性</strong>：这是最直观的。通过隐藏属性和控制访问，防止了数据被非法或意外地修改。</li><li><strong>隐藏实现细节</strong>：使用者只需要知道“怎么用”（调用哪个方法），而不需要知道“怎么实现的”。比如以后我想把<code>age</code>改成<code>String</code>类型存储，只要<code>getAge()</code>返回的还是<code>int</code>，外面的代码就不需要改动。</li><li><strong>提高可维护性</strong>：如果规则变了（比如年龄上限改了），我只需要修改<code>setAge</code>方法内部的逻辑，所有调用这个方法的地方都会自动生效，不用满世界去找代码修改。</li></ul><h4 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h4><p>封装不仅仅是把变量改成<code>private</code>再自动生成方法那么简单，它是一种“防御性编程”的思维。它教会我们在设计代码时就要考虑到数据的安全和未来的变化。</p><p>虽然目前我只学到了封装，还没接触到继承和多态，接下来的两篇文章依次给大家讲解面向对象的其他特性——继承与多态。最后感想您的查阅！！！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/10/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC3%E7%AF%87%EF%BC%9AJava%E5%B0%81%E8%A3%85%E2%80%94%E2%80%94%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%8A%8A%E6%95%B0%E6%8D%AE%E8%97%8F%E8%B5%B7%E6%9D%A5%EF%BC%9F/</id>
    <link href="https://qixia.site/2026/06/10/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC3%E7%AF%87%EF%BC%9AJava%E5%B0%81%E8%A3%85%E2%80%94%E2%80%94%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%8A%8A%E6%95%B0%E6%8D%AE%E8%97%8F%E8%B5%B7%E6%9D%A5%EF%BC%9F/"/>
    <published>2026-06-09T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>在我们学习面向过程的编程语言，比如说C语言时,一定会出现下面的情况：<br>假设你定义了一个全局变量 int balance &#x3D;]]>
    </summary>
    <title>Java基础·第3篇：Java封装——为什么要把数据藏起来？</title>
    <updated>2026-07-22T01:33:15.023Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="JavaSE" scheme="https://qixia.site/tags/JavaSE/"/>
    <content>
      <![CDATA[<h2 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h2><p>Java数据类型分为基本数据类型和引用数据类型。本文整理了8种基本数据类型的表格，用“藏宝图”讲清引用数据类型。后半部分介绍Java的输入输出：System.out和Scanner的用法。</p><h2 id="Java的数据类型"><a href="#Java的数据类型" class="headerlink" title="Java的数据类型"></a>Java的数据类型</h2><h3 id="一、基本数据类型"><a href="#一、基本数据类型" class="headerlink" title="一、基本数据类型"></a>一、基本数据类型</h3><p>为了让你一目了然，我将这8种基本数据类型整理成了如下表格，涵盖了它们的存储大小、取值范围及核心特性。</p><table><thead><tr><th>数据类型</th><th>关键字</th><th>占用空间</th><th>取值范围</th><th>默认值</th><th>核心特性与注意事项</th></tr></thead><tbody><tr><td><strong>字节型</strong></td><td><code>byte</code></td><td>1字节</td><td>-128 ~ 127</td><td>0</td><td>节省内存，常用于文件IO或网络传输。</td></tr><tr><td><strong>短整型</strong></td><td><code>short</code></td><td>2字节</td><td>-32,768 ~ 32,767</td><td>0</td><td>较少直接使用，用于特定内存优化场景。</td></tr><tr><td><strong>整型</strong></td><td><code>int</code></td><td>4字节</td><td>约±21亿</td><td>0</td><td><strong>最常用</strong>的整数类型，默认整数字面量。</td></tr><tr><td><strong>长整型</strong></td><td><code>long</code></td><td>8字节</td><td>约±922亿亿</td><td>0L</td><td>存储超大整数，赋值时数值后需加<code>L</code>。</td></tr><tr><td><strong>单精度浮点</strong></td><td><code>float</code></td><td>4字节</td><td>约±3.4E38</td><td>0.0f</td><td>精度较低（6-7位有效数字），赋值需加<code>F</code>。</td></tr><tr><td><strong>双精度浮点</strong></td><td><code>double</code></td><td>8字节</td><td>约±1.8E308</td><td>0.0d</td><td><strong>最常用</strong>的小数类型，默认浮点字面量。</td></tr><tr><td><strong>字符型</strong></td><td><code>char</code></td><td>2字节</td><td>0 ~ 65,535</td><td>‘\u0000’</td><td>采用Unicode编码，<strong>支持中文</strong>，用单引号。</td></tr><tr><td><strong>布尔型</strong></td><td><code>boolean</code></td><td>未定义*</td><td>true &#x2F; false</td><td>false</td><td>仅用于逻辑判断，<strong>不可</strong>与数字互转。</td></tr></tbody></table><p>注：boolean类型在JVM规范中未明确规定大小，通常视具体实现而定，但在数组中通常占1字节。</p><h3 id="二、引用数据类型"><a href="#二、引用数据类型" class="headerlink" title="二、引用数据类型"></a>二、引用数据类型</h3><p>与上述直接存储数值的基本类型不同，引用数据类型存储的是数据在内存中的“地址”。你可以把它理解为一张“藏宝图”，变量手里拿的不是宝藏（数据）本身，而是指向宝藏埋藏地点（堆内存）的坐标。</p><p>最常见的引用类型包括：</p><ul><li><strong>类</strong>：如<code>String</code>、自定义的对象。虽然<code>String</code>的使用看起来和基本类型很像，但它本质上是一个包含字符数组的复杂对象。</li><li><strong>接口</strong>：如<code>List</code>、<code>Map</code>等，它们定义了规范，实际存储的是实现类的对象地址。</li><li><strong>数组</strong>：用于存储一组相同类型的数据，变量存储的是数组首地址。</li></ul><p>以C语言为例，数组名就是指向第一个元素的指针。你定义一个数组int arr[3] &#x3D; {1,2,3}，arr本身存的是地址，不是那三个数。你把arr赋值给另一个变量，两个变量指向同一块内存。</p><p>Java的数组和C的指针本质是一样的——存地址。只是Java不让你做指针运算，也不叫“指针”，叫“引用”。</p><h2 id="输入与输出"><a href="#输入与输出" class="headerlink" title="输入与输出"></a>输入与输出</h2><h3 id="一、标准输出：与用户对话"><a href="#一、标准输出：与用户对话" class="headerlink" title="一、标准输出：与用户对话"></a>一、标准输出：与用户对话</h3><p>程序运行后，我们需要看到结果。Java通过<code>System.out</code>对象来实现标准输出。</p><ul><li><strong>基础输出</strong>：<code>print()</code>方法输出后不换行，光标停留在末尾；<code>println()</code>则会在输出后自动换行，这是最常用的方式。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">System.out.print(<span class="string">&quot;Hello World!&quot;</span>);</span><br><span class="line">System.out.println(<span class="string">&quot;Hello World!&quot;</span>);</span><br></pre></td></tr></table></figure><ul><li><strong>格式化输出</strong>：如果你需要像财务报表那样精准控制格式（例如保留两位小数），可以使用<code>printf()</code>。它使用占位符，和C语言和格式控制差不多，如<code>%d</code>代表整数，<code>%.2f</code>代表保留两位小数的浮点数，<code>%s</code>代表字符串。</li></ul><h3 id="二、标准输入：获取外部数据"><a href="#二、标准输入：获取外部数据" class="headerlink" title="二、标准输入：获取外部数据"></a>二、标准输入：获取外部数据</h3><p>为了让程序具有交互性，我们需要从控制台获取用户的输入。Java提供了<code>java.util.Scanner</code>类来处理这一需求。</p><p><strong>使用流程</strong>：</p><ol><li><strong>导包</strong>：<code>import java.util.Scanner;</code></li><li><strong>创建对象</strong>：<code>Scanner sc = new Scanner(System.in);</code></li><li><strong>调用方法</strong>：根据需要的数据类型调用相应方法，如<code>nextInt()</code>（整数）、<code>nextDouble()</code>（小数）、<code>next()</code>（字符串，遇空格停止）或<code>nextLine()</code>（整行字符串）。</li></ol><h4 id="最后的话"><a href="#最后的话" class="headerlink" title="最后的话"></a>最后的话</h4><p>好了，关于Java数据类型和输入输出的分享就先到这里。<br>别急着一下子全记住，编程是一场马拉松，而不是百米冲刺。试着多动手敲几遍代码，当你在控制台成功输入并打印出结果的那一刻，你会发现这一切都是值得的。</p><p>保持好奇，多动手尝试，感谢您的查阅！我们下一篇博客见！</p>]]>
    </content>
    <id>https://qixia.site/2026/06/10/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC2%E7%AF%87%EF%BC%9A%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%EF%BC%88%E5%9F%BA%E6%9C%AC+%E5%BC%95%E7%94%A8%EF%BC%89%E5%92%8C%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%EF%BC%88Scanner%EF%BC%89/</id>
    <link href="https://qixia.site/2026/06/10/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC2%E7%AF%87%EF%BC%9A%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%EF%BC%88%E5%9F%BA%E6%9C%AC+%E5%BC%95%E7%94%A8%EF%BC%89%E5%92%8C%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%EF%BC%88Scanner%EF%BC%89/"/>
    <published>2026-06-09T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h2><p>Java数据类型分为基本数据类型和引用数据类型。本文整理了8种基本数据类型的表格，用“藏宝图”讲清引用数据类型。后半部分介绍Java的输入输]]>
    </summary>
    <title>Java基础·第2篇：数据类型（基本+引用）和输入输出（Scanner）</title>
    <updated>2026-07-22T01:33:15.023Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="JavaSE" scheme="https://qixia.site/tags/JavaSE/"/>
    <content>
      <![CDATA[<h2 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h2><p> 假设你要写一个宠物管理系统。系统里有一个“投喂”功能：</p><ul><li>狗：吃狗粮</li><li>猫：吃猫粮</li><li>兔子：吃兔粮</li></ul><p> 如果没有多态，你可能需要这样写：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">feed</span><span class="params">(Dog d)</span> &#123; d.eat(); &#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">feed</span><span class="params">(Cat c)</span> &#123; c.eat(); &#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">feed</span><span class="params">(Rabbit r)</span> &#123; r.eat(); &#125;</span><br></pre></td></tr></table></figure><p> 三种动物，三个方法。那如果是十种动物呢？写十个长得一模一样的方法？</p><p> 多态要解决的问题就是：<strong>用一个方法，接收所有动物</strong>。<br> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">feed</span><span class="params">(Animal a)</span> &#123; a.eat(); &#125;</span><br></pre></td></tr></table></figure></p><p> 不管你传进来的是狗、猫还是兔子，只要它是Animal，就能调eat()。至于具体怎么吃，每个动物自己说了算。</p><p> 这就是多态——<strong>同一个方法调用，表现出不同的行为</strong>。</p><p>下面我们来看多态在Java中怎么实现。</p><hr><h2 id="多态的定义与前提"><a href="#多态的定义与前提" class="headerlink" title="多态的定义与前提"></a>多态的定义与前提</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line">父类：</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line"></span><br><span class="line">    空参构造</span><br><span class="line">    带全部参数的构造</span><br><span class="line">    get和set方法</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(name + <span class="string">&quot;, &quot;</span> + age);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">子类<span class="number">1</span>：</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Administrator</span> <span class="keyword">extends</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;管理员的信息为：&quot;</span> + getName() + <span class="string">&quot;, &quot;</span> + getAge());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">子类<span class="number">2</span>：</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Student</span> <span class="keyword">extends</span> <span class="title class_">Person</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;学生的信息为：&quot;</span> + getName() + <span class="string">&quot;, &quot;</span> + getAge());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">子类<span class="number">3</span>：</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Teacher</span> <span class="keyword">extends</span> <span class="title class_">Person</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;老师的信息为：&quot;</span> + getName() + <span class="string">&quot;, &quot;</span> + getAge());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">测试类：</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">//创建三个对象，并调用register方法</span></span><br><span class="line"></span><br><span class="line">        <span class="type">Student</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Student</span>();</span><br><span class="line">        s.setName(<span class="string">&quot;张三&quot;</span>);</span><br><span class="line">        s.setAge(<span class="number">18</span>);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="type">Teacher</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Teacher</span>();</span><br><span class="line">        t.setName(<span class="string">&quot;王建国&quot;</span>);</span><br><span class="line">        t.setAge(<span class="number">30</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">Administrator</span> <span class="variable">admin</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Administrator</span>();</span><br><span class="line">        admin.setName(<span class="string">&quot;管理员&quot;</span>);</span><br><span class="line">        admin.setAge(<span class="number">35</span>);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        register(s);</span><br><span class="line">        register(t);</span><br><span class="line">        register(admin);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//这个方法既能接收老师，又能接收学生，还能接收管理员</span></span><br><span class="line">    <span class="comment">//只能把参数写成这三个类型的父类</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(Person p)</span>&#123;</span><br><span class="line">        p.show();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em>多态：</em> 是指同一行为，具有多个不同表现形式。<br>从上面案例可以看出，学生、老师和管理员都是人，都是show这一行为，但是出现的效果（表现形式）是不一样的。</p><p><em>前提【重点】</em><br>有继承或者实现关系<br>方法的重写【意义体现：不重写，无意义】<br>父类引用指向子类对象【格式体现】</p><blockquote><p>父类类型：指子类对象继承的父类类型，或者实现的父接口类型。</p></blockquote><h2 id="多态的形式"><a href="#多态的形式" class="headerlink" title="多态的形式"></a>多态的形式</h2><p>多态体现的格式：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">父类类型 变量名 = <span class="keyword">new</span> 子类/实现类构造器;</span><br><span class="line">变量名.方法名();</span><br></pre></td></tr></table></figure><h2 id="多态的使用场景"><a href="#多态的使用场景" class="headerlink" title="多态的使用场景"></a>多态的使用场景</h2><p>如果没有多态，在上面的代码中register方法只能传递学生对象，其他的Teacher和administrator对象是无法传递给register方法方法的，在这种情况下，只能定义三个不同的register方法分别接收学生，老师和管理员。有了多态之后，方法的形参就可以定义为共同的父类Person。</p><p>要注意的是：<br>当一个方法的形参是一个类，我们可以传递这个类所有的子类对象。<br>当一个方法的形参是一个接口，我们可以传递这个接口所有的实现类对象（后面会学）。<br>而且多态还可以根据传递的不同对象来调用不同类中的方法。</p><h2 id="多态的运行特点"><a href="#多态的运行特点" class="headerlink" title="多态的运行特点"></a>多态的运行特点</h2><p>调用成员变量时：编译看左边，运行看左边<br>调用成员方法时：编译看左边，运行看右边</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Fu</span> <span class="variable">f</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Zi</span>()；</span><br><span class="line"><span class="comment">//编译看左边的父类中有没有name这个属性，没有就报错</span></span><br><span class="line"><span class="comment">//在实际运行的时候，把父类name属性的值打印出来</span></span><br><span class="line">System.out.println(f.name);</span><br><span class="line"><span class="comment">//编译看左边的父类中有没有show这个方法，没有就报错</span></span><br><span class="line"><span class="comment">//在实际运行的时候，运行的是子类中的show方法</span></span><br><span class="line">f.show();</span><br></pre></td></tr></table></figure><p>由此，也得出多态的弊端，无论是调用成员变量还是成员方法，都需要查看父类是否有这个成员变量和成员方法，这样就导致无法执行子类特有的属性和行为。</p><h2 id="引用数据类型的强转"><a href="#引用数据类型的强转" class="headerlink" title="引用数据类型的强转"></a>引用数据类型的强转</h2><p>由于多态的运行逻辑，导致我们无法调用子类的独有方法。所以我们想到类可以看作是一种自定义的数据类型，在类是一种数据类型的情况下，当出现数据类型冲突时，我们可以尝试强转。<br>回顾基本数据类型转换<br>自动转换: 范围小的赋值给范围大的.自动完成:double d &#x3D; 5;<br>强制转换: 范围大的赋值给范围小的,强制转换:int i &#x3D; (int)3.14</p><p>多态的转型分为向上转型（自动转换）与向下转型（强制转换）两种。</p><p><em>向上转型：</em> 多态本身是子类类型向父类类型向上转换（自动转换）的过程，这个过程是默认的。 当父类引用指向一个子类对象时，便是向上转型。 使用格式：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">父类类型  变量名 = <span class="keyword">new</span> 子类类型();</span><br><span class="line">如：<span class="type">Animal</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Cat</span>();</span><br></pre></td></tr></table></figure><p><em>向下转型：</em> 父类类型向子类类型向下转换的过程，这个过程是强制的。 一个已经向上转型的子类对象，将父类引用转为子类引用，可以使用强制类型转换的格式，便是向下转型。<br>使用格式：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">子类类型 变量名 = (子类类型) 父类变量名;</span><br><span class="line">如:<span class="type">Aniaml</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Cat</span>();</span><br><span class="line">   <span class="type">Cat</span> <span class="variable">c</span> <span class="operator">=</span>(Cat) a;  </span><br></pre></td></tr></table></figure><h3 id="强转的异常"><a href="#强转的异常" class="headerlink" title="强转的异常"></a>强转的异常</h3><p>在我们进行多态的强转时，一定会出现下面的情况：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 向上转型  </span></span><br><span class="line">        <span class="type">Animal</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Cat</span>();  </span><br><span class="line">        a.eat();               <span class="comment">// 调用的是 Cat 的 eat</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 向下转型  </span></span><br><span class="line">        <span class="type">Dog</span> <span class="variable">d</span> <span class="operator">=</span> (Dog)a;       </span><br><span class="line">        d.watchHouse();        <span class="comment">// 调用的是 Dog 的 watchHouse 【运行报错】</span></span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码可以通过编译，但是运行时，却报出了 ClassCastException ，类型转换异常！这是因为，明明创建了Cat类型对象，运行时，当然不能转换成Dog对象的,出现牛头不对马嘴的情况。</p><h3 id="避免异常"><a href="#避免异常" class="headerlink" title="避免异常"></a>避免异常</h3><p>这时候我们需要对强转的情况进行判断，避免出现ClassCastException 的错误</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 向上转型  </span></span><br><span class="line">        <span class="type">Animal</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Cat</span>();  </span><br><span class="line">        a.eat();               <span class="comment">// 调用的是 Cat 的 eat</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 向下转型  </span></span><br><span class="line">        <span class="keyword">if</span> (a <span class="keyword">instanceof</span> Cat)&#123;</span><br><span class="line">            <span class="type">Cat</span> <span class="variable">c</span> <span class="operator">=</span> (Cat)a;       </span><br><span class="line">            c.catchMouse();        <span class="comment">// 调用的是 Cat 的 catchMouse</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (a <span class="keyword">instanceof</span> Dog)&#123;</span><br><span class="line">            <span class="type">Dog</span> <span class="variable">d</span> <span class="operator">=</span> (Dog)a;       </span><br><span class="line">            d.watchHouse();       <span class="comment">// 调用的是 Dog 的 watchHouse</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中instanceof是Java的一个关键字，给引用变量做类型的校验，格式如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">变量名 <span class="keyword">instanceof</span> 数据类型 </span><br><span class="line">如果变量属于该数据类型或者其子类类型，返回<span class="literal">true</span>。</span><br><span class="line">如果变量不属于该数据类型或者其子类类型，返回<span class="literal">false</span>。</span><br></pre></td></tr></table></figure><h2 id="最后的话"><a href="#最后的话" class="headerlink" title="最后的话"></a>最后的话</h2><p>至此，面向对象的三大特性——封装、继承和多态都已经学习完毕</p>]]>
    </content>
    <id>https://qixia.site/2026/06/10/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC5%E7%AF%87%EF%BC%9AJava%E5%A4%9A%E6%80%81%E2%80%94%E2%80%94%E4%B8%8D%E7%94%A8%E5%86%8D%E5%86%99%E4%B8%89%E4%B8%AA%E9%87%8D%E8%BD%BD%E6%96%B9%E6%B3%95%E4%BA%86%EF%BC%81/</id>
    <link href="https://qixia.site/2026/06/10/Java%E5%9F%BA%E7%A1%80%C2%B7%E7%AC%AC5%E7%AF%87%EF%BC%9AJava%E5%A4%9A%E6%80%81%E2%80%94%E2%80%94%E4%B8%8D%E7%94%A8%E5%86%8D%E5%86%99%E4%B8%89%E4%B8%AA%E9%87%8D%E8%BD%BD%E6%96%B9%E6%B3%95%E4%BA%86%EF%BC%81/"/>
    <published>2026-06-09T16:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h2><p> 假设你要写一个宠物管理系统。系统里有一个“投喂”功能：</p>
<ul>
<li>狗：吃狗粮</li>
<li>猫：吃猫粮</li>
<l]]>
    </summary>
    <title>Java基础·第5篇：Java多态——不用再写三个重载方法了！</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
  <entry>
    <author>
      <name>七夏</name>
    </author>
    <category term="后端开发" scheme="https://qixia.site/categories/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="MyBatis" scheme="https://qixia.site/tags/MyBatis/"/>
    <content>
      <![CDATA[<p>学习完Mybatis的基本操作流程之后，因为Mybatis本身就是用Java操作数据库的一项技术，我们躲不掉的要学习在Mybatis中怎么对数据库进行增删改查。接下来直接上实战：</p><h2 id="一、Mapper-接口（UserMapper-java）"><a href="#一、Mapper-接口（UserMapper-java）" class="headerlink" title="一、Mapper 接口（UserMapper.java）"></a>一、Mapper 接口（<code>UserMapper.java</code>）</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.example.mapper;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.example.pojo.User;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.annotations.Param;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserMapper</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 查询所有</span></span><br><span class="line">    List&lt;User&gt; <span class="title function_">findAll</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据 id 查询单个</span></span><br><span class="line">    User <span class="title function_">findById</span><span class="params">(Integer id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 新增（返回影响行数，并自动填充自增 id）</span></span><br><span class="line">    <span class="type">int</span> <span class="title function_">insert</span><span class="params">(User user)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 修改</span></span><br><span class="line">    <span class="type">int</span> <span class="title function_">update</span><span class="params">(User user)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 单条删除</span></span><br><span class="line">    <span class="type">int</span> <span class="title function_">deleteById</span><span class="params">(Integer id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 批量删除（根据 id 列表）</span></span><br><span class="line">    <span class="type">int</span> <span class="title function_">deleteByIds</span><span class="params">(<span class="meta">@Param(&quot;ids&quot;)</span> List&lt;Integer&gt; ids)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong>：  </p><ul><li>返回值类型 <code>int</code> 表示影响的行数，有助于判断操作是否成功。  </li><li>批量删除的参数用 <code>@Param(&quot;ids&quot;)</code> 注解，供 XML 中的 <code>foreach</code> 引用。</li></ul></blockquote><hr><h2 id="二、XML-映射文件（UserMapper-xml）"><a href="#二、XML-映射文件（UserMapper-xml）" class="headerlink" title="二、XML 映射文件（UserMapper.xml）"></a>二、XML 映射文件（<code>UserMapper.xml</code>）</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span> ?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">mapper</span></span></span><br><span class="line"><span class="meta">  <span class="keyword">PUBLIC</span> <span class="string">&quot;-//mybatis.org//DTD Mapper 3.0//EN&quot;</span></span></span><br><span class="line"><span class="meta">  <span class="string">&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;org.example.mapper.UserMapper&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 查询所有 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;org.example.pojo.User&quot;</span>&gt;</span></span><br><span class="line">        SELECT * FROM user</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 根据 id 查询 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findById&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;org.example.pojo.User&quot;</span>&gt;</span></span><br><span class="line">        SELECT * FROM user WHERE id = #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 新增：useGeneratedKeys 获取数据库自增主键 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">&quot;insert&quot;</span> <span class="attr">useGeneratedKeys</span>=<span class="string">&quot;true&quot;</span> <span class="attr">keyProperty</span>=<span class="string">&quot;id&quot;</span>&gt;</span></span><br><span class="line">        INSERT INTO user (name, age) VALUES (#&#123;name&#125;, #&#123;age&#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 修改 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;update&quot;</span>&gt;</span></span><br><span class="line">        UPDATE user SET name = #&#123;name&#125;, age = #&#123;age&#125; WHERE id = #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">update</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 单条删除 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">delete</span> <span class="attr">id</span>=<span class="string">&quot;deleteById&quot;</span>&gt;</span></span><br><span class="line">        DELETE FROM user WHERE id = #&#123;id&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">delete</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- 批量删除：使用 foreach 生成 IN 子句 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">delete</span> <span class="attr">id</span>=<span class="string">&quot;deleteByIds&quot;</span>&gt;</span></span><br><span class="line">        DELETE FROM user WHERE id IN</span><br><span class="line">        <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;ids&quot;</span> <span class="attr">item</span>=<span class="string">&quot;id&quot;</span> <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span>&gt;</span></span><br><span class="line">            #&#123;id&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">delete</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>foreach</code> 标签说明</p><ul><li><code>collection=&quot;ids&quot;</code>：对应接口中 <code>@Param(&quot;ids&quot;)</code> 的名字。</li><li><code>item=&quot;id&quot;</code>：循环中每个元素的临时变量名。</li><li><code>open=&quot;(&quot;</code> <code>close=&quot;)&quot;</code>：生成 <code>IN ( ... )</code> 的括号。</li><li><code>separator=&quot;,&quot;</code>：多个值之间的逗号。</li></ul><hr><h2 id="三、测试类（MyBatisTest-java）"><a href="#三、测试类（MyBatisTest-java）" class="headerlink" title="三、测试类（MyBatisTest.java）"></a>三、测试类（<code>MyBatisTest.java</code>）</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.example;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.io.Resources;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.session.SqlSession;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.session.SqlSessionFactory;</span><br><span class="line"><span class="keyword">import</span> org.apache.ibatis.session.SqlSessionFactoryBuilder;</span><br><span class="line"><span class="keyword">import</span> org.example.mapper.UserMapper;</span><br><span class="line"><span class="keyword">import</span> org.example.pojo.User;</span><br><span class="line"><span class="keyword">import</span> java.io.InputStream;</span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBatisTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> SqlSessionFactory factory;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">InputStream</span> <span class="variable">is</span> <span class="operator">=</span> Resources.getResourceAsStream(<span class="string">&quot;mybatis-config.xml&quot;</span>);</span><br><span class="line">            factory = <span class="keyword">new</span> <span class="title class_">SqlSessionFactoryBuilder</span>().build(is);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 新增</span></span><br><span class="line">        testInsert();</span><br><span class="line">        <span class="comment">// 2. 修改</span></span><br><span class="line">        testUpdate();</span><br><span class="line">        <span class="comment">// 3. 单条删除</span></span><br><span class="line">        testDeleteById();</span><br><span class="line">        <span class="comment">// 4. 批量删除（只删除指定的几个 id）</span></span><br><span class="line">        testDeleteByIds();</span><br><span class="line">        <span class="comment">// 5. 查询所有查看结果</span></span><br><span class="line">        testFindAll();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 新增并获取自增id</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testInsert</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">SqlSession</span> <span class="variable">session</span> <span class="operator">=</span> factory.openSession()) &#123;</span><br><span class="line">            <span class="type">UserMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> session.getMapper(UserMapper.class);</span><br><span class="line">            <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">            user.setName(<span class="string">&quot;王五&quot;</span>);</span><br><span class="line">            user.setAge(<span class="number">28</span>);</span><br><span class="line">            <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> mapper.insert(user);</span><br><span class="line">            session.commit();</span><br><span class="line">            System.out.println(<span class="string">&quot;插入影响行数：&quot;</span> + rows);</span><br><span class="line">            System.out.println(<span class="string">&quot;自动生成的id：&quot;</span> + user.getId());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 修改 id=1 的用户</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testUpdate</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">SqlSession</span> <span class="variable">session</span> <span class="operator">=</span> factory.openSession()) &#123;</span><br><span class="line">            <span class="type">UserMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> session.getMapper(UserMapper.class);</span><br><span class="line">            <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">            user.setId(<span class="number">1</span>);</span><br><span class="line">            user.setName(<span class="string">&quot;张三丰&quot;</span>);</span><br><span class="line">            user.setAge(<span class="number">99</span>);</span><br><span class="line">            <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> mapper.update(user);</span><br><span class="line">            session.commit();</span><br><span class="line">            System.out.println(<span class="string">&quot;修改影响行数：&quot;</span> + rows);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 单条删除（删除 id=2）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testDeleteById</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">SqlSession</span> <span class="variable">session</span> <span class="operator">=</span> factory.openSession()) &#123;</span><br><span class="line">            <span class="type">UserMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> session.getMapper(UserMapper.class);</span><br><span class="line">            <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> mapper.deleteById(<span class="number">2</span>);</span><br><span class="line">            session.commit();</span><br><span class="line">            System.out.println(<span class="string">&quot;单条删除影响行数：&quot;</span> + rows);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 批量删除：只删除 id 为 1 和 3 的记录（不是全部删除）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testDeleteByIds</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">SqlSession</span> <span class="variable">session</span> <span class="operator">=</span> factory.openSession()) &#123;</span><br><span class="line">            <span class="type">UserMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> session.getMapper(UserMapper.class);</span><br><span class="line">            <span class="comment">// 你想要删除哪些 id，就写在这个列表中</span></span><br><span class="line">            List&lt;Integer&gt; idsToDelete = Arrays.asList(<span class="number">1</span>, <span class="number">3</span>);</span><br><span class="line">            <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> mapper.deleteByIds(idsToDelete);</span><br><span class="line">            session.commit();</span><br><span class="line">            System.out.println(<span class="string">&quot;批量删除影响行数：&quot;</span> + rows);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 查询所有并打印</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testFindAll</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">SqlSession</span> <span class="variable">session</span> <span class="operator">=</span> factory.openSession()) &#123;</span><br><span class="line">            <span class="type">UserMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> session.getMapper(UserMapper.class);</span><br><span class="line">            List&lt;User&gt; list = mapper.findAll();</span><br><span class="line">            System.out.println(<span class="string">&quot;当前用户列表：&quot;</span>);</span><br><span class="line">            list.forEach(System.out::println);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><blockquote><p><strong>注意</strong>：  </p><ul><li>此处的测试类与上次的不同的是，main方法上有一段静态代码块，与第一篇文章的代码功能一样，环境准备，由于在这篇文章中对数据库的操作很多，为了提高性能，只做一次的环境准备。</li></ul></blockquote><p>将三个文件的内容编写好后，运行测试类，运行结果如下：<br><img src="/img/MyBatis_2/p2.png" alt="图片描述"></p><h2 id="四、核心知识点回顾"><a href="#四、核心知识点回顾" class="headerlink" title="四、核心知识点回顾"></a>四、核心知识点回顾</h2><h3 id="1-参数传递：-Param"><a href="#1-参数传递：-Param" class="headerlink" title="1. 参数传递：@Param"></a>1. 参数传递：<code>@Param</code></h3><ul><li>当 Mapper 方法有多个参数，或者参数在 XML 中需要明确命名时，使用 <code>@Param(&quot;别名&quot;)</code>。</li><li>例如：<code>int deleteByIds(@Param(&quot;ids&quot;) List&lt;Integer&gt; ids);</code>，XML 中用 <code>#{ids}</code> 引用。<br>这个注解的意思就是给一个装id的盒子命名，提高代码的可读性。</li></ul><h3 id="2-批量操作："><a href="#2-批量操作：" class="headerlink" title="2. 批量操作：&lt;foreach&gt;"></a>2. 批量操作：<code>&lt;foreach&gt;</code></h3><ul><li>遍历集合生成动态 SQL，常用于 <code>IN</code> 子句或批量插入。</li><li>属性：<code>collection</code>（集合名）、<code>item</code>（元素变量）、<code>open</code>、<code>close</code>、<code>separator</code>。<br>这些属性就做一个事，把SQL拼接出来，比如<code>DELETE FROM user WHERE id IN (1, 3, 5)</code></li></ul><h3 id="3-获取自增主键"><a href="#3-获取自增主键" class="headerlink" title="3. 获取自增主键"></a>3. 获取自增主键</h3><ul><li><code>&lt;insert useGeneratedKeys=&quot;true&quot; keyProperty=&quot;实体类属性名&quot;&gt;</code></li><li>插入后，原实体对象会自动被赋予生成的主键值。</li></ul><h3 id="4-事务提交"><a href="#4-事务提交" class="headerlink" title="4. 事务提交"></a>4. 事务提交</h3><ul><li>增、删、改必须调用 <code>session.commit()</code> 才会真正写入数据库。</li><li>如果忘记提交，数据不会变化，且不会报错。</li></ul><h2 id="五、最后的话"><a href="#五、最后的话" class="headerlink" title="五、最后的话"></a>五、最后的话</h2><p>关于在Mybatis上怎么对数据库进行增删改查，我们就学到这了，感谢您的查阅！！！</p><hr>]]>
    </content>
    <id>https://qixia.site/2026/06/10/MyBatis%20%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E5%A2%9E%E5%88%A0%E6%94%B9%E6%9F%A5%20+%20%E6%89%B9%E9%87%8F%E5%88%A0%E9%99%A4%E5%AE%8C%E6%95%B4%E6%80%BB%E7%BB%93%EF%BC%88%E5%90%AB%20@Param%20%E5%92%8C%20foreach%EF%BC%89/</id>
    <link href="https://qixia.site/2026/06/10/MyBatis%20%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E5%A2%9E%E5%88%A0%E6%94%B9%E6%9F%A5%20+%20%E6%89%B9%E9%87%8F%E5%88%A0%E9%99%A4%E5%AE%8C%E6%95%B4%E6%80%BB%E7%BB%93%EF%BC%88%E5%90%AB%20@Param%20%E5%92%8C%20foreach%EF%BC%89/"/>
    <published>2026-06-09T16:00:00.000Z</published>
    <summary>
      <![CDATA[<p>学习完Mybatis的基本操作流程之后，因为Mybatis本身就是用Java操作数据库的一项技术，我们躲不掉的要学习在Mybatis中怎么对数据库进行增删改查。接下来直接上实战：</p>
<h2 id="一、Mapper-接口（UserMapper-java）"><a hr]]>
    </summary>
    <title>MyBatis 入门系列（二）：增删改查 + 批量删除完整总结（含 @Param 和 foreach）</title>
    <updated>2026-07-22T01:33:15.024Z</updated>
  </entry>
</feed>
