<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="/rss/atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>RF极客日志</title>
  
  <subtitle>技术引领，智慧践行</subtitle>
  <link href="https://blog.itrf.cn/atom.xml" rel="self"/>
  
  <link href="https://blog.itrf.cn/"/>
  <updated>2026-09-08T14:15:22.932Z</updated>
  <id>https://blog.itrf.cn/</id>
  
  <author>
    <name>LynxFrost</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>使用EdgeOne加速CloudFlareWorkers</title>
    <link href="https://blog.itrf.cn/posts/6a9f/"/>
    <id>https://blog.itrf.cn/posts/6a9f/</id>
    <published>2026-09-08T13:41:56.000Z</published>
    <updated>2026-09-08T14:15:22.932Z</updated>
    
    <content type="html"><![CDATA[<h2 id="问题复现"><a href="#问题复现" class="headerlink" title="问题复现"></a>问题复现</h2><p>使用CloudFlareWorkers部署图床系统的时候，想使用EdgeOne加速Workers，但是直接使用EdgeOne加速Workers给的域名会出现522的问题，我一开始以为和Vercle一样是回源HOST的问题，但实际上不是。</p><p>Workers是会检测域名的，如果域名没有托管到CF就是直接522</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>我发现可以使用一个托管在CF的域名作为中转，实现使用EO加速</p><ol><li>给在CF托管的域名添加一个解析记录，值随便填写，但是一定要开启小云盾</li><li>添加Workers路由绑定要加速的Workers，注意是添加路由，不是在Workers里面直接绑定域名，一定要使用路由！！！</li><li>使用EdgeOne加速中转域名，注意回源方式HTTPS，回源地址和回源HOST要填写中转域名</li></ol><p>其他解决方案，我在网上查的好像可以使用自定义主机名功能实现，但是我试了半天页面成功，感兴趣的可以研究一下，这个方案就是在试的过程中发现的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>CFWorkers会检测域名，但我们可以使用中转域名的方式绕过</p><p>必须要有一个托管在CF的域名，我用的eu.org 免费域名</p><p>至于会不会出现双层cdn的问题，我感觉不会，因为达到CF的节点就返回Workers的内容了，应该不会优选吧。</p><p>在EO配置好缓存可以减少CF的请求次数</p>]]></content>
    
    
    <summary type="html">使用EdgeOne加速CloudFlareWorkers，减少CF请求次数</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=65cb7ebf-bec9-a40b-b889-510458f346e4" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>安知鱼主题顶部美化教程</title>
    <link href="https://blog.itrf.cn/posts/cb83/"/>
    <id>https://blog.itrf.cn/posts/cb83/</id>
    <published>2026-09-08T13:38:57.000Z</published>
    <updated>2026-09-08T16:23:50.193Z</updated>
    
    <content type="html"><![CDATA[<p>给安知鱼主题的顶部导航栏添加猫毛玻璃效果并分段</p><p>具体效果可以看我的博客</p><h2 id="必看"><a href="#必看" class="headerlink" title="必看"></a>必看</h2><ol><li>美化之前必须备份</li><li>我不是在最原始的主题上美化的，但大差不差</li></ol><h2 id="教程"><a href="#教程" class="headerlink" title="教程"></a>教程</h2><p>在 <code>themes\anzhiyu\source\css\_layout\nav.styl</code>直接覆盖</p><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line"><span class="selector-id">#sidebar</span> <span class="selector-id">#sidebar-menus</span> <span class="selector-class">.menus_items</span> <span class="selector-class">.menus_item_child</span></span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">0px</span></span><br><span class="line">  <span class="attribute">list-style</span>: none</span><br><span class="line">  <span class="attribute">display</span>: flex</span><br><span class="line">  <span class="attribute">flex-direction</span>: row</span><br><span class="line">  <span class="attribute">flex-wrap</span>: wrap</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#sidebar</span> <span class="selector-id">#sidebar-menus</span> <span class="selector-class">.menus_item_child</span> <span class="selector-tag">li</span></span><br><span class="line">  <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="number">50%</span> - <span class="number">8px</span>)</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">4px</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#sidebar</span> <span class="selector-id">#sidebar-menus</span> <span class="selector-class">.menus_item_child</span> <span class="selector-class">.site-page</span><span class="selector-class">.child</span></span><br><span class="line">  <span class="attribute">display</span>: flex</span><br><span class="line">  <span class="attribute">flex-direction</span>: column</span><br><span class="line">  <span class="attribute">align-items</span>: center</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">8px</span></span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">12px</span></span><br><span class="line">  <span class="attribute">border</span>: <span class="built_in">var</span>(--style-border-always)</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">var</span>(--anzhiyu-card-bg)</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">14px</span></span><br><span class="line">  <span class="attribute">height</span>: <span class="number">4.2rem</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">7rem</span>;</span><br><span class="line">  <span class="attribute">justify-content</span>: space-between;</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#sidebar</span> <span class="selector-class">.menus_item</span> <span class="selector-tag">ul</span></span><br><span class="line">  <span class="attribute">padding-left</span>: <span class="number">0px</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav-group</span></span><br><span class="line">  <span class="attribute">max-width</span>: <span class="number">1400px</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">  <span class="attribute">position</span>: relative;</span><br><span class="line">  <span class="attribute">align-items</span>: center;</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span><span class="selector-class">.nav-fixed</span> <span class="selector-id">#center-console</span></span><br><span class="line">  &amp; + <span class="selector-tag">label</span> <span class="selector-tag">i</span></span><br><span class="line">    <span class="attribute">background</span>: <span class="built_in">var</span>(--font-color);</span><br><span class="line"><span class="selector-id">#center-console</span></span><br><span class="line">  <span class="attribute">display</span>: none</span><br><span class="line">  &amp; + <span class="selector-tag">label</span></span><br><span class="line">    <span class="attr">--icon-size</span>: <span class="number">1.375rem</span>;</span><br><span class="line">    <span class="attribute">position</span>: relative;</span><br><span class="line">    <span class="attribute">right</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">bottom</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="built_in">var</span>(--icon-size);</span><br><span class="line">    <span class="attribute">width</span>: <span class="built_in">var</span>(--icon-size);</span><br><span class="line">    <span class="attribute">cursor</span>: pointer;</span><br><span class="line">    <span class="attribute">transition</span>: <span class="number">1s</span>;</span><br><span class="line">    <span class="attribute">margin-left</span>: <span class="number">1rem</span></span><br><span class="line">    <span class="selector-pseudo">&amp;:hover</span></span><br><span class="line">      <span class="selector-tag">i</span></span><br><span class="line">        &amp;<span class="selector-class">.left</span></span><br><span class="line">          <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/<span class="number">2.5</span>);</span><br><span class="line">        &amp;<span class="selector-class">.center</span></span><br><span class="line">          <span class="attribute">opacity</span>: <span class="number">0.5</span></span><br><span class="line">          <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/<span class="number">2.5</span>);</span><br><span class="line">        &amp;<span class="selector-class">.right</span></span><br><span class="line">          <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/<span class="number">2.5</span>);</span><br><span class="line">          <span class="attribute">height</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/<span class="number">1.15</span>);</span><br><span class="line">          <span class="attribute">transform</span>: none;</span><br><span class="line">    <span class="selector-tag">i</span></span><br><span class="line">      <span class="attribute">background</span>: <span class="built_in">var</span>(--light-grey);</span><br><span class="line">      <span class="attribute">position</span>: absolute;</span><br><span class="line">      <span class="attribute">border-radius</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)*.<span class="number">15</span>);</span><br><span class="line">      <span class="attribute">transition</span>: .<span class="number">5s</span> <span class="built_in">var</span>(--animation-on);</span><br><span class="line">      <span class="attribute">inset</span>: <span class="number">0</span>;</span><br><span class="line">      <span class="attribute">margin</span>: auto;</span><br><span class="line">      <span class="attribute">right</span>: auto;</span><br><span class="line">      <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/<span class="number">3</span>);</span><br><span class="line">      <span class="attribute">height</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/<span class="number">3</span>);</span><br><span class="line">      <span class="attribute">transform</span>: <span class="built_in">translateY</span>(<span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/<span class="number">4</span>));</span><br><span class="line">      &amp;<span class="selector-class">.left</span></span><br><span class="line">        <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">        <span class="attribute">transform</span>: <span class="built_in">translateY</span>(<span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/-<span class="number">4</span>));</span><br><span class="line">      &amp;<span class="selector-class">.right</span></span><br><span class="line">        <span class="attribute">left</span>: auto;</span><br><span class="line">        <span class="attribute">right</span>: <span class="number">0</span>;</span><br><span class="line">        <span class="attribute">width</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/<span class="number">2</span>);</span><br><span class="line">  <span class="selector-pseudo">&amp;:checked</span> + <span class="selector-tag">label</span></span><br><span class="line">    <span class="attribute">right</span>: <span class="number">0</span></span><br><span class="line">    <span class="attribute">top</span>: <span class="number">0.5rem</span>;</span><br><span class="line">    <span class="attribute">z-index</span>: <span class="number">31</span>;</span><br><span class="line">    <span class="selector-pseudo">&amp;:hover</span></span><br><span class="line">      <span class="selector-pseudo">&amp;::after</span></span><br><span class="line">        <span class="attribute">background</span>: <span class="built_in">var</span>(--anzhiyu-main) <span class="meta">!important</span>;</span><br><span class="line">    <span class="selector-pseudo">&amp;::after</span></span><br><span class="line">      <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">      <span class="attribute">width</span>: <span class="number">35px</span>;</span><br><span class="line">      <span class="attribute">height</span>: <span class="number">35px</span>;</span><br><span class="line">      <span class="attribute">display</span>: block;</span><br><span class="line">      <span class="attribute">position</span>: absolute;</span><br><span class="line">      <span class="attribute">z-index</span>: -<span class="number">1</span>;</span><br><span class="line">      <span class="attribute">top</span>: -<span class="number">6px</span>;</span><br><span class="line">      <span class="attribute">left</span>: -<span class="number">6.3px</span>;</span><br><span class="line">      <span class="attribute">background</span>: <span class="built_in">var</span>(--anzhiyu-fontcolor) <span class="meta">!important</span>;</span><br><span class="line">      <span class="attribute">border-radius</span>: <span class="number">50px</span>;</span><br><span class="line">    <span class="selector-pseudo">&amp;:is</span>(<span class="selector-class">.widget</span>, <span class="selector-class">.widget</span>:hover) <span class="selector-tag">i</span></span><br><span class="line">      <span class="attribute">height</span>: <span class="built_in">calc</span>(<span class="built_in">var</span>(--icon-size)/<span class="number">4.5</span>);</span><br><span class="line">      <span class="attribute">background</span>: <span class="built_in">var</span>(--anzhiyu-white) <span class="meta">!important</span>;</span><br><span class="line">    <span class="selector-tag">i</span><span class="selector-class">.left</span></span><br><span class="line">      <span class="attribute">width</span>: <span class="number">100%</span> <span class="meta">!important</span>;</span><br><span class="line">      <span class="attribute">transform</span>: <span class="built_in">rotate</span>(-<span class="number">45deg</span>) <span class="meta">!important</span>;</span><br><span class="line">    <span class="selector-tag">i</span><span class="selector-class">.center</span></span><br><span class="line">      <span class="attribute">width</span>: <span class="number">0</span> <span class="meta">!important</span>;</span><br><span class="line">    <span class="selector-tag">i</span><span class="selector-class">.right</span></span><br><span class="line">      <span class="attribute">width</span>: <span class="number">100%</span> <span class="meta">!important</span>;</span><br><span class="line">      <span class="attribute">transform</span>: <span class="built_in">rotate</span>(<span class="number">45deg</span>) <span class="meta">!important</span>;</span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-id">#center-console</span><span class="selector-pseudo">:checked</span> + <span class="selector-tag">label</span><span class="selector-pseudo">:is</span>(<span class="selector-class">.widget</span>, <span class="selector-id">#center-console</span><span class="selector-pseudo">:checked</span> + <span class="selector-tag">label</span> <span class="selector-class">.widget</span>:hover) <span class="selector-tag">i</span></span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">var</span>(--anzhiyu-black) <span class="meta">!important</span>;</span><br><span class="line"><span class="selector-id">#body-wrap</span></span><br><span class="line">  <span class="selector-class">.nav-fixed</span> <span class="selector-id">#travellings_button</span></span><br><span class="line">    <span class="selector-pseudo">&amp;::after</span></span><br><span class="line">      <span class="attribute">background</span>: <span class="built_in">var</span>(--anzhiyu-main) <span class="meta">!important</span>;</span><br><span class="line"></span><br><span class="line">  <span class="selector-id">#travellings_button</span></span><br><span class="line">    <span class="attribute">display</span>: flex</span><br><span class="line">    <span class="selector-pseudo">&amp;:hover</span><span class="selector-pseudo">::after</span></span><br><span class="line">      <span class="attribute">opacity</span>: <span class="number">1</span>;</span><br><span class="line">      <span class="attribute">transform</span>: none;</span><br><span class="line">      <span class="attribute">visibility</span>: visible;</span><br><span class="line">    <span class="selector-pseudo">&amp;:hover</span></span><br><span class="line">      <span class="selector-tag">a</span></span><br><span class="line">        <span class="attribute">background</span>: <span class="built_in">var</span>(--anzhiyu-main);</span><br><span class="line">        -webkit-<span class="attribute">box-shadow</span>: <span class="built_in">var</span>(--anzhiyu-shadow-main);</span><br><span class="line">        <span class="attribute">box-shadow</span>: <span class="built_in">var</span>(--anzhiyu-shadow-main);</span><br><span class="line">        <span class="selector-tag">i</span></span><br><span class="line">          <span class="attribute">color</span>: <span class="built_in">var</span>(--anzhiyu-white);</span><br><span class="line">    <span class="selector-pseudo">&amp;::after</span></span><br><span class="line">      <span class="attr">--height</span>: <span class="number">2rem</span>;</span><br><span class="line">      <span class="attribute">content</span>: none;</span><br><span class="line">      <span class="attribute">position</span>: fixed;</span><br><span class="line">      <span class="attribute">display</span>: flex;</span><br><span class="line">      <span class="attribute">justify-content</span>: center;</span><br><span class="line">      <span class="attribute">align-items</span>: center;</span><br><span class="line">      <span class="attribute">color</span>: <span class="built_in">var</span>(--anzhiyu-white);</span><br><span class="line">      <span class="attribute">background</span>: <span class="built_in">var</span>(--anzhiyu-white-op) <span class="meta">!important</span>;</span><br><span class="line">      <span class="attribute">box-shadow</span>: <span class="built_in">var</span>(--anzhiyu-shadow-nav);</span><br><span class="line">      <span class="attribute">border-radius</span>: <span class="built_in">var</span>(--anzhiyu-radius-full);</span><br><span class="line">      <span class="attribute">width</span>: fit-content;</span><br><span class="line">      <span class="attribute">height</span>: <span class="number">40px</span>;</span><br><span class="line">      <span class="attribute">font-size</span>: <span class="built_in">var</span>(--global-font-size);</span><br><span class="line">      <span class="attribute">white-space</span>: nowrap;</span><br><span class="line">      <span class="attribute">margin</span>: auto;</span><br><span class="line">      <span class="attribute">padding</span>: <span class="number">0</span> <span class="number">4em</span>;</span><br><span class="line">      <span class="attribute">top</span>: <span class="number">10px</span>;</span><br><span class="line">      <span class="attribute">right</span>: <span class="number">0</span>;</span><br><span class="line">      <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">      <span class="attribute">line-height</span>: <span class="number">40px</span>;</span><br><span class="line">      <span class="attribute">opacity</span>: <span class="number">0</span>;</span><br><span class="line">      <span class="attribute">z-index</span>: <span class="number">1</span>;</span><br><span class="line">      <span class="attribute">visibility</span>: hidden;</span><br><span class="line">      <span class="attribute">transform</span>: <span class="built_in">scaleX</span>(<span class="number">1.1</span>);</span><br><span class="line">      <span class="attribute">pointer-events</span>: none;</span><br><span class="line">      <span class="attribute">transition</span>: .<span class="number">5s</span>;</span><br><span class="line">      +<span class="built_in">maxWidth900</span>()</span><br><span class="line">        <span class="attribute">padding</span>: <span class="number">0</span> <span class="number">2em</span>;</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#body-wrap</span><span class="selector-pseudo">:has</span>(<span class="selector-id">#travellings_button</span>:hover) </span><br><span class="line">  <span class="selector-id">#page-name</span>,</span><br><span class="line">  <span class="selector-id">#nav</span> <span class="selector-id">#menus</span></span><br><span class="line">    <span class="attribute">display</span>: none</span><br><span class="line"><span class="selector-id">#body-wrap</span> <span class="selector-class">.page</span> <span class="selector-id">#travellings_button</span><span class="selector-pseudo">:hover</span></span><br><span class="line">  <span class="selector-pseudo">&amp;::after</span></span><br><span class="line">    <span class="attribute">background</span>: <span class="built_in">var</span>(--anzhiyu-main) <span class="meta">!important</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> screen <span class="keyword">and</span> (<span class="attribute">max-width</span>: <span class="number">768px</span>)</span><br><span class="line">  #page-header #nav</span><br><span class="line">    padding: <span class="number">20px</span></span><br><span class="line"></span><br><span class="line">  .cardHover,</span><br><span class="line">  .error404 #error-wrap .error-content,</span><br><span class="line">  .layout &gt; div:first-child:<span class="keyword">not</span>(.recent-posts),</span><br><span class="line">  #recent-posts &gt; .recent-post-item,</span><br><span class="line">  #aside-content .card-widget,</span><br><span class="line">  .layout &gt; .recent-posts .pagination &gt; *:<span class="keyword">not</span>(.space)</span><br><span class="line">    box-shadow: none !important</span><br><span class="line"></span><br><span class="line">  #sidebar #sidebar-menus .menus_items .site-page:<span class="attribute">hover</span></span><br><span class="line">    background: none</span><br><span class="line"></span><br><span class="line">  div#travellings_button</span><br><span class="line">    display: none !important</span><br><span class="line"></span><br><span class="line">#nav </span><br><span class="line">  #blog_name,</span><br><span class="line">  .mask-name-container,</span><br><span class="line">  #menus,</span><br><span class="line">  #nav-right .nav-button,</span><br><span class="line">  #nav-right #toggle-menu</span><br><span class="line">    a:<span class="attribute">hover</span></span><br><span class="line">      background var(--anzhiyu-main)</span><br><span class="line">      transition <span class="number">0.3s</span></span><br><span class="line"></span><br><span class="line">#page-header.not-top-img</span><br><span class="line">  margin-bottom <span class="number">0px</span></span><br><span class="line">#page-header:<span class="keyword">not</span>(.nav-fixed) #percent</span><br><span class="line">  transition <span class="number">0.3s</span></span><br><span class="line"></span><br><span class="line">#page-header #nav #nav-right &gt; div</span><br><span class="line">  margin-left <span class="number">1rem</span></span><br><span class="line">  padding <span class="number">0</span></span><br><span class="line"></span><br><span class="line">.nav-button</span><br><span class="line">  cursor <span class="attribute">pointer</span></span><br><span class="line"></span><br><span class="line">#nav #menus</span><br><span class="line">  padding <span class="number">0</span> calc((<span class="number">100%</span> - <span class="number">1400px</span> + <span class="number">3rem</span>) / <span class="number">2</span>)</span><br><span class="line">  display flex</span><br><span class="line">  justify-content center</span><br><span class="line">  <span class="attribute">width</span> <span class="number">100%</span></span><br><span class="line">  position absolute</span><br><span class="line">  <span class="attribute">height</span> <span class="number">60px</span></span><br><span class="line">  left <span class="number">0</span></span><br><span class="line">  margin <span class="number">0</span></span><br><span class="line">  transform: translateZ(<span class="number">0</span>);</span><br><span class="line">  <span class="attribute">will-change</span>: auto</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav</span> <span class="selector-class">.site-page</span></span><br><span class="line">  <span class="attribute">padding-bottom</span> <span class="number">0px</span></span><br><span class="line"><span class="selector-id">#nav</span> *<span class="selector-pseudo">::after</span></span><br><span class="line">  <span class="attribute">background-color</span> transparent <span class="meta">!important</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 顶栏修改 */</span></span><br><span class="line"><span class="selector-id">#nav</span> <span class="selector-class">.menus_items</span> <span class="selector-class">.menus_item</span> <span class="selector-tag">a</span></span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">50px</span></span><br><span class="line"><span class="selector-id">#nav</span> <span class="selector-class">.menus_items</span> <span class="selector-class">.menus_item</span> <span class="selector-class">.menus_item_child</span> <span class="selector-tag">li</span> <span class="selector-tag">a</span></span><br><span class="line">  <span class="attribute">padding</span> <span class="number">2px</span> <span class="number">16px</span></span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">50px</span></span><br><span class="line">  <span class="attribute">transition</span>: .<span class="number">3s</span> <span class="meta">!important</span></span><br><span class="line"><span class="selector-id">#nav</span> <span class="selector-class">.menus_items</span> <span class="selector-class">.menus_item</span> <span class="selector-class">.menus_item_child</span> <span class="selector-tag">li</span><span class="selector-pseudo">:hover</span> <span class="selector-tag">a</span></span><br><span class="line">  <span class="attribute">color</span> white <span class="meta">!important</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav</span> <span class="selector-class">.menus_items</span> <span class="selector-class">.menus_item</span> <span class="selector-class">.menus_item_child</span> <span class="selector-tag">li</span></span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">50px</span></span><br><span class="line">  <span class="attribute">transition</span> all <span class="number">0.3s</span></span><br><span class="line">  <span class="attribute">display</span> inline-block</span><br><span class="line">  <span class="attribute">margin</span> <span class="number">0</span> <span class="number">3px</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#site-name</span>, <span class="selector-class">.shuoshuo</span></span><br><span class="line">  <span class="attribute">white-space</span> nowrap</span><br><span class="line">  <span class="attribute">overflow</span> hidden</span><br><span class="line"><span class="selector-id">#site-name</span></span><br><span class="line">  <span class="attribute">padding</span> <span class="number">0</span> <span class="number">2px</span></span><br><span class="line">  <span class="attribute">height</span>: <span class="number">35px</span>;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">35px</span>;</span><br><span class="line">  <span class="attribute">position</span> relative</span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">align-items</span> center</span><br><span class="line">  <span class="attribute">justify-content</span> center</span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 一级菜单居中 */</span></span><br><span class="line"><span class="selector-id">#nav</span> <span class="selector-class">.menus_items</span></span><br><span class="line">  <span class="attribute">position</span> relative</span><br><span class="line">  <span class="attribute">width</span> fit-content</span><br><span class="line">  <span class="attribute">text-align</span> center</span><br><span class="line">  <span class="attribute">left</span> <span class="number">0px</span></span><br><span class="line">  <span class="attribute">right</span> <span class="number">0px</span></span><br><span class="line">  <span class="attribute">top</span> <span class="number">0px</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">flex-direction</span> row</span><br><span class="line">  <span class="attribute">justify-content</span> center</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav</span> </span><br><span class="line">  <span class="selector-id">#blog_name</span>,</span><br><span class="line">  <span class="selector-class">.mask-name-container</span>,</span><br><span class="line">  <span class="selector-id">#menus</span>,</span><br><span class="line">  <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span>,</span><br><span class="line">  <span class="selector-id">#nav-right</span> <span class="selector-id">#toggle-menu</span></span><br><span class="line">    <span class="selector-tag">a</span></span><br><span class="line">      <span class="attribute">border-radius</span> <span class="number">50px</span>;</span><br><span class="line"><span class="selector-class">.page</span> <span class="selector-id">#nav</span> </span><br><span class="line">  <span class="selector-id">#blog_name</span>,</span><br><span class="line">  <span class="selector-class">.mask-name-container</span>,</span><br><span class="line">  <span class="selector-id">#menus</span>,</span><br><span class="line">  <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span>,</span><br><span class="line">  <span class="selector-id">#nav-right</span> <span class="selector-id">#toggle-menu</span></span><br><span class="line">    <span class="selector-tag">a</span><span class="selector-pseudo">:hover</span></span><br><span class="line">      <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-white)</span><br><span class="line">      <span class="attribute">background</span> <span class="built_in">var</span>(--anzhiyu-main)</span><br><span class="line">      <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">      <span class="attribute">box-shadow</span> <span class="built_in">var</span>(--anzhiyu-shadow-main)</span><br><span class="line"><span class="selector-class">.post</span> <span class="selector-id">#nav</span> </span><br><span class="line">  <span class="selector-id">#blog_name</span> <span class="selector-class">.back-home-button</span>,</span><br><span class="line">  <span class="selector-id">#blog_name</span> <span class="selector-id">#site-name</span>,</span><br><span class="line">  <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span>,</span><br><span class="line">  <span class="selector-id">#nav-right</span> <span class="selector-id">#toggle-menu</span></span><br><span class="line">    <span class="selector-pseudo">&amp;:hover</span></span><br><span class="line">      <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-white)</span><br><span class="line">      <span class="attribute">background</span> <span class="built_in">var</span>(--anzhiyu-white-op)</span><br><span class="line">      <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line"><span class="selector-class">.post</span> <span class="selector-id">#nav</span> </span><br><span class="line">  <span class="selector-id">#menus</span> <span class="selector-class">.menus_item</span></span><br><span class="line">    <span class="selector-pseudo">&amp;:hover</span> &gt; <span class="selector-tag">a</span></span><br><span class="line">      <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-white)</span><br><span class="line">      <span class="attribute">background</span> <span class="built_in">var</span>(--anzhiyu-white-op)</span><br><span class="line">      <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line"></span><br><span class="line"><span class="selector-class">.post</span> <span class="selector-class">.nav-fixed</span> <span class="selector-id">#nav</span> </span><br><span class="line">  <span class="selector-id">#menus</span> <span class="selector-class">.menus_item</span></span><br><span class="line">    <span class="selector-pseudo">&amp;:hover</span></span><br><span class="line">      &gt; <span class="selector-tag">a</span><span class="selector-class">.site-page</span></span><br><span class="line">        <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-white)</span><br><span class="line">        <span class="attribute">background</span> <span class="built_in">var</span>(--anzhiyu-main)</span><br><span class="line">        <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">        <span class="attribute">box-shadow</span> <span class="built_in">var</span>(--anzhiyu-shadow-main)</span><br><span class="line">  <span class="selector-id">#blog_name</span> <span class="selector-id">#site-name</span>,</span><br><span class="line">  <span class="selector-id">#blog_name</span> <span class="selector-class">.back-home-button</span>,</span><br><span class="line">  <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span>,</span><br><span class="line">  <span class="selector-id">#nav-right</span> <span class="selector-id">#toggle-menu</span></span><br><span class="line">    <span class="selector-pseudo">&amp;:hover</span></span><br><span class="line">      <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-white)</span><br><span class="line">      <span class="attribute">background</span> <span class="built_in">var</span>(--anzhiyu-main)</span><br><span class="line">      <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">      <span class="attribute">box-shadow</span> <span class="built_in">var</span>(--anzhiyu-shadow-main)</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span><span class="selector-class">.nav-fixed</span> <span class="selector-id">#nav</span> <span class="selector-id">#page-name</span></span><br><span class="line">  <span class="attribute">background</span> none</span><br><span class="line">  <span class="attribute">text-shadow</span> none</span><br><span class="line">  <span class="attribute">box-shadow</span> none</span><br><span class="line">  <span class="attribute">font-weight</span> bold</span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">100px</span></span><br><span class="line">  <span class="attribute">min-width</span> <span class="number">100px</span></span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">1.5rem</span>;</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#name-container</span></span><br><span class="line">  <span class="attribute">align-items</span> center</span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">12px</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">40px</span></span><br><span class="line">  <span class="attribute">position</span> absolute</span><br><span class="line">  <span class="attribute">top</span> <span class="number">62px</span></span><br><span class="line">  <span class="attribute">left</span> <span class="number">0px</span></span><br><span class="line">  <span class="attribute">right</span> <span class="number">0px</span></span><br><span class="line">  <span class="attribute">margin</span> auto</span><br><span class="line">  <span class="attribute">justify-content</span> center</span><br><span class="line">  <span class="attribute">animation-timing-function</span> ease-out</span><br><span class="line">  -webkit-<span class="attribute">animation-timing-function</span> ease-out</span><br><span class="line"><span class="selector-id">#page-header</span><span class="selector-class">.nav-fixed</span><span class="selector-class">.nav-visible</span> <span class="selector-id">#name-container</span></span><br><span class="line">  <span class="attribute">z-index</span> <span class="number">100</span></span><br><span class="line">  <span class="attribute">top</span> <span class="number">60px</span></span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span><span class="selector-class">.nav-fixed</span> <span class="selector-id">#nav</span> <span class="selector-id">#page-name</span><span class="selector-pseudo">:hover</span></span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-main)</span><br><span class="line"><span class="keyword">@media</span> screen <span class="keyword">and</span> (<span class="attribute">min-width</span>: <span class="number">900px</span>)</span><br><span class="line">  #page-header.nav-fixed #nav #page-name::after</span><br><span class="line">    opacity <span class="number">0</span></span><br><span class="line">    transform scale(<span class="number">1</span>)</span><br><span class="line">    content &quot;回到顶部&quot;</span><br><span class="line">    transition <span class="number">0.2s</span></span><br><span class="line">    position absolute</span><br><span class="line">    left <span class="number">0</span></span><br><span class="line">    right <span class="number">0</span></span><br><span class="line">    top <span class="number">50%</span></span><br><span class="line">    transform translateY(-<span class="number">50%</span>)</span><br><span class="line">    margin auto</span><br><span class="line">    <span class="attribute">color</span> var(--anzhiyu-white) !important</span><br><span class="line">    font-weight <span class="number">700</span></span><br><span class="line">    line-height <span class="number">2</span></span><br><span class="line">@media screen <span class="keyword">and</span> (<span class="attribute">min-width</span>: <span class="number">1200px</span>)</span><br><span class="line">  #page-header.nav-fixed #nav #page-name:<span class="attribute">hover</span>:after</span><br><span class="line">    opacity <span class="number">1</span></span><br><span class="line">  #page-header.nav-fixed #nav #page-name:<span class="attribute">hover</span></span><br><span class="line">    background var(--anzhiyu-main)</span><br><span class="line"></span><br><span class="line">#page-header #nav #page-name</span><br><span class="line">  display inline</span><br><span class="line">  font-weight bold</span><br><span class="line">  padding <span class="number">4px</span> <span class="number">8px</span></span><br><span class="line">  opacity <span class="number">0</span></span><br><span class="line">  transition <span class="number">0.1s</span></span><br><span class="line">  text-overflow ellipsis</span><br><span class="line">  overflow hidden</span><br><span class="line">  white-space nowrap</span><br><span class="line">  position relative</span><br><span class="line">  text-align center</span><br><span class="line">  cursor <span class="attribute">pointer</span></span><br><span class="line">  top <span class="number">0</span></span><br><span class="line">  font-size <span class="number">1.1rem</span></span><br><span class="line">  animation-timing-function ease-in</span><br><span class="line">  -webkit-animation-timing-function ease-in</span><br><span class="line"></span><br><span class="line">#menus &gt; div.menus_items &gt; div &gt; a</span><br><span class="line">  letter-spacing <span class="number">0.3rem</span></span><br><span class="line">  font-weight <span class="number">700</span></span><br><span class="line">  padding <span class="number">0em</span> <span class="number">0.8em</span> <span class="number">0em</span> <span class="number">1em</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">35px</span></span><br><span class="line">  line-height <span class="number">35px</span></span><br><span class="line">  transition: <span class="attribute">color</span> <span class="number">0s</span> !important</span><br><span class="line"></span><br><span class="line">#nav div#toggle-menu</span><br><span class="line">  padding <span class="number">2px</span> <span class="number">0</span> <span class="number">4px</span> <span class="number">6px</span></span><br><span class="line"></span><br><span class="line">#page-name:<span class="attribute">hover</span>:before</span><br><span class="line">  opacity <span class="number">1</span></span><br><span class="line"></span><br><span class="line">::backdrop</span><br><span class="line">  display block</span><br><span class="line">  position fixed</span><br><span class="line">  top <span class="number">0px</span></span><br><span class="line"></span><br><span class="line">.mask-name-container</span><br><span class="line">  <span class="attribute">width</span> <span class="number">100%</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">100%</span></span><br><span class="line">  position absolute</span><br><span class="line">  overflow hidden</span><br><span class="line">  left <span class="number">0</span></span><br><span class="line"></span><br><span class="line">#nav .menus_items .menus_item &gt; a &gt; i:last-child</span><br><span class="line">  display none</span><br><span class="line"></span><br><span class="line">#nav #search-button</span><br><span class="line">  font-size <span class="number">1.3em</span></span><br><span class="line"></span><br><span class="line">.back-home-button:<span class="attribute">hover</span></span><br><span class="line">  background var(--anzhiyu-main)</span><br><span class="line">  <span class="attribute">color</span> var(--anzhiyu-white) !important</span><br><span class="line"></span><br><span class="line">.back-home-button</span><br><span class="line">  display flex</span><br><span class="line">  <span class="attribute">width</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">35px</span></span><br><span class="line">  padding <span class="number">0</span> !important</span><br><span class="line">  align-items center</span><br><span class="line">  justify-content center</span><br><span class="line">  margin-right <span class="number">4px</span></span><br><span class="line">  transition <span class="number">0.3s</span></span><br><span class="line">  border-radius <span class="number">8px</span></span><br><span class="line"></span><br><span class="line">.back-home-button:<span class="attribute">hover</span> .back-menu-list-groups</span><br><span class="line">  display: flex;</span><br><span class="line">  <span class="attribute">opacity</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">transition</span>: .<span class="number">3s</span>;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">55px</span>;</span><br><span class="line">  <span class="attribute">pointer-events</span>: auto;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0rem</span>;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">scale</span>(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list-groups</span></span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">45px</span>;</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">scale</span>(.<span class="number">8</span>);</span><br><span class="line">  <span class="attribute">transform-origin</span>: top left;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0rem</span>;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="built_in">var</span>(--anzhiyu-maskbgdeep);</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">12px</span>;</span><br><span class="line">  <span class="attribute">border</span>: <span class="built_in">var</span>(--style-border);</span><br><span class="line">  <span class="attribute">flex-direction</span>: column;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">12px</span>;</span><br><span class="line">  <span class="attribute">color</span>: <span class="built_in">var</span>(--anzhiyu-secondtext);</span><br><span class="line">  <span class="attribute">box-shadow</span>: <span class="built_in">var</span>(--anzhiyu-shadow-border);</span><br><span class="line">  <span class="attribute">transition</span>: .<span class="number">1s</span>;</span><br><span class="line">  <span class="attribute">opacity</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">pointer-events</span>: none;</span><br><span class="line">  <span class="attribute">backdrop-filter</span>: <span class="built_in">blur</span>(<span class="number">20px</span>);</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list-group</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">flex-direction</span> column</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list-group</span> <span class="selector-class">.back-menu-list-title</span></span><br><span class="line">  <span class="attribute">margin</span> <span class="number">8px</span> <span class="number">0</span> <span class="number">0</span> <span class="number">16px</span></span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">flex-direction</span> row</span><br><span class="line">  <span class="attribute">flex-wrap</span> wrap</span><br><span class="line">  <span class="attribute">width</span> <span class="number">340px</span></span><br><span class="line">  <span class="attribute">justify-content</span> space-between</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list</span><span class="selector-pseudo">::before</span></span><br><span class="line">  <span class="attribute">position</span> absolute</span><br><span class="line">  <span class="attribute">top</span> -<span class="number">24px</span></span><br><span class="line">  <span class="attribute">left</span> <span class="number">0px</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">100%</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">25px</span></span><br><span class="line">  <span class="attribute">content</span> <span class="string">&quot;&quot;</span></span><br><span class="line">  <span class="attribute">transition</span>: <span class="number">0s</span></span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list-groups</span> <span class="selector-class">.back-menu-list-group</span><span class="selector-pseudo">:last-child</span> <span class="selector-class">.back-menu-list</span></span><br><span class="line">  <span class="attribute">margin</span> <span class="number">0</span> <span class="number">0</span> <span class="number">8px</span></span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list-group</span><span class="selector-pseudo">:hover</span> <span class="selector-class">.back-menu-list-title</span></span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-main)</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list-groups</span><span class="selector-pseudo">:hover</span></span><br><span class="line">  <span class="attribute">border</span> <span class="built_in">var</span>(--style-border-hover)</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list</span> <span class="selector-class">.back-menu-item</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">align-items</span> center</span><br><span class="line">  <span class="attribute">margin</span> <span class="number">4px</span> <span class="number">8px</span></span><br><span class="line">  <span class="attribute">padding</span> <span class="number">4px</span> <span class="number">8px</span> <span class="meta">!important</span></span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">150px</span></span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">8px</span> <span class="meta">!important</span></span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.2s</span> <span class="meta">!important</span></span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-menu-list-groups</span> <span class="selector-class">.back-menu-list</span> <span class="selector-class">.back-menu-item</span> <span class="selector-class">.back-menu-item-text</span></span><br><span class="line">  <span class="attribute">font-size</span> <span class="built_in">var</span>(--global-font-size)</span><br><span class="line">  <span class="attribute">margin-left</span> <span class="number">0.5rem</span></span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-fontcolor)</span><br><span class="line">  <span class="attribute">white-space</span> nowrap</span><br><span class="line">  +<span class="built_in">maxWidth768</span>()</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">14px</span>;</span><br><span class="line">    <span class="attribute">text-overflow</span>: ellipsis;</span><br><span class="line">    <span class="attribute">overflow</span>: hidden;</span><br><span class="line">    <span class="attribute">white-space</span>: nowrap;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-menu-list-groups</span> <span class="selector-class">.back-menu-list</span> <span class="selector-class">.back-menu-item</span> <span class="selector-class">.back-menu-item-icon</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">24px</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">24px</span></span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">24px</span></span><br><span class="line">  <span class="attribute">background</span> <span class="built_in">var</span>(--anzhiyu-secondbg)</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-class">.back-home-button</span></span><br><span class="line">  <span class="attribute">cursor</span> pointer</span><br><span class="line">  <span class="attribute">position</span> relative</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span><span class="selector-pseudo">:not</span>(.not-<span class="attribute">top</span>-img) #nav .back-home-button</span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--light-grey)</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span><span class="selector-class">.nav-fixed</span><span class="selector-pseudo">:not</span>(.not-<span class="attribute">top</span>-img) #nav .back-home-button</span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--font-color)</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-home-button</span> <span class="selector-class">.back-menu-list</span> <span class="selector-class">.back-menu-item</span><span class="selector-pseudo">:hover</span> <span class="selector-class">.back-menu-item-text</span></span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-white)</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.back-menu-item-icon</span><span class="selector-class">.loading</span> <span class="selector-tag">img</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">25px</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#menus</span> <span class="selector-class">.nav-button</span><span class="selector-class">.long</span> <span class="selector-tag">a</span><span class="selector-class">.totopbtn</span> <span class="selector-tag">span</span></span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">justify-content</span> center</span><br><span class="line">  <span class="attribute">align-items</span> center</span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">  <span class="attribute">white-space</span> nowrap</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#menus</span> <span class="selector-class">.nav-button</span><span class="selector-class">.long</span> <span class="selector-tag">a</span><span class="selector-class">.totopbtn</span><span class="selector-pseudo">:hover</span></span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">30px</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav</span> <span class="selector-id">#search-button</span></span><br><span class="line">  <span class="attribute">padding-left</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#search-button</span> <span class="selector-tag">a</span><span class="selector-class">.site-page</span><span class="selector-class">.social-icon</span><span class="selector-class">.search</span> <span class="selector-tag">span</span></span><br><span class="line">  <span class="attribute">display</span> none</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span><span class="selector-pseudo">:hover</span></span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-white) <span class="meta">!important</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#menus</span> &gt; <span class="selector-tag">div</span><span class="selector-class">.menus_items</span> <span class="selector-tag">a</span></span><br><span class="line">  <span class="attribute">font-weight</span> bold</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#menus</span> <span class="selector-class">.menus_item</span> <span class="selector-class">.menus_item_child</span> <span class="selector-class">.site-page</span><span class="selector-class">.child</span><span class="selector-class">.faa-parent</span><span class="selector-class">.animated-hover</span> <span class="selector-class">.anzhiyu-icon-fan</span><span class="selector-class">.faa-tada</span></span><br><span class="line">  <span class="attribute">animation</span> rotate <span class="number">1.6s</span> linear infinite</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav-right</span></span><br><span class="line">  <span class="attribute">z-index</span> <span class="number">102</span></span><br><span class="line">  <span class="attribute">position</span> absolute</span><br><span class="line">  <span class="attribute">right</span> <span class="number">0</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">flex-direction</span> row</span><br><span class="line">  <span class="attribute">height</span> <span class="number">100%</span></span><br><span class="line">  <span class="attribute">align-items</span> center</span><br><span class="line"></span><br><span class="line"><span class="comment">/* nav left */</span></span><br><span class="line"><span class="selector-id">#nav</span> <span class="selector-id">#blog_name</span></span><br><span class="line">  <span class="attribute">flex-wrap</span> nowrap</span><br><span class="line">  <span class="attribute">height</span> <span class="number">60px</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">align-items</span> center</span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">  <span class="attribute">z-index</span> <span class="number">102</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#blog_name</span> <span class="selector-id">#site-name</span> <span class="selector-tag">i</span></span><br><span class="line">  <span class="attribute">position</span> absolute</span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">1rem</span></span><br><span class="line">  <span class="attribute">opacity</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#blog_name</span> <span class="selector-id">#site-name</span><span class="selector-pseudo">:hover</span> <span class="selector-class">.title</span></span><br><span class="line">  <span class="attribute">opacity</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#blog_name</span> <span class="selector-id">#site-name</span> <span class="selector-class">.title</span></span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">  <span class="attribute">letter-spacing</span> normal</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">1.2rem</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span> <span class="number">5px</span>;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">2rem</span>;</span><br><span class="line">  <span class="attribute">transition</span>: color <span class="number">0s</span>;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.post</span> <span class="selector-id">#blog_name</span> <span class="selector-id">#site-name</span> <span class="selector-class">.title</span></span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0s</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#blog_name</span> <span class="selector-id">#site-name</span><span class="selector-pseudo">:hover</span> <span class="selector-tag">i</span></span><br><span class="line">  <span class="attribute">color</span> white</span><br><span class="line">  <span class="attribute">opacity</span> <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span> <span class="selector-tag">i</span>,</span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-left</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span> <span class="selector-tag">i</span></span><br><span class="line">  <span class="attribute">line-height</span> <span class="number">1</span></span><br><span class="line">  <span class="attribute">transition</span> .<span class="number">3s</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span><span class="selector-pseudo">:hover</span> <span class="selector-tag">i</span>,</span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-left</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span><span class="selector-pseudo">:hover</span> <span class="selector-tag">i</span></span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-white)</span><br><span class="line">  <span class="attribute">transition</span> background <span class="number">0.35s</span> ease-in-out, color <span class="number">0.2s</span> ease-in-out</span><br><span class="line"></span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span> <span class="selector-tag">i</span>,</span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-left</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span> <span class="selector-tag">i</span>,</span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span> <span class="selector-tag">i</span>,</span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-left</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span> <span class="selector-tag">i</span>,</span><br><span class="line"><span class="selector-attr">[data-theme=<span class="string">&quot;dark&quot;</span>]</span> <span class="selector-id">#page-header</span><span class="selector-class">.nav-fixed</span><span class="selector-pseudo">:not</span>(.not-<span class="attribute">top</span>-img) #nav .back-home-button</span><br><span class="line">  +<span class="built_in">maxWidth768</span>()</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#c9c9c9</span> <span class="meta">!important</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 返回顶部按钮样式 */</span></span><br><span class="line"><span class="selector-id">#nav-totop</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">50px</span></span><br><span class="line">  <span class="attribute">align-items</span> center</span><br><span class="line">  <span class="attribute">justify-content</span> space-around</span><br><span class="line">  <span class="attribute">transition</span>: all <span class="number">0.3s</span> ease-in-out;</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span><span class="selector-class">.totopbtn</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">25px</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">25px</span></span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">40px</span></span><br><span class="line">  <span class="attribute">background</span> <span class="built_in">var</span>(--font-color)</span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--card-bg)</span><br><span class="line">  <span class="attribute">transition</span> all <span class="number">0.3s</span> ease-in-out</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span><span class="selector-pseudo">:not</span>(.long) <span class="selector-tag">a</span><span class="selector-class">.totopbtn</span><span class="selector-pseudo">:hover</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">35px</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span><span class="selector-pseudo">:not</span>(.long)<span class="selector-pseudo">:hover</span> <span class="selector-tag">a</span><span class="selector-class">.totopbtn</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">top</span> <span class="number">0</span></span><br><span class="line">  <span class="attribute">right</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-class">.nav-button</span> <span class="selector-tag">a</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">align-items</span> center</span><br><span class="line">  <span class="attribute">justify-content</span> center</span><br><span class="line">  <span class="attribute">transition</span>: background <span class="number">0.3s</span> ease-in-out, color <span class="number">0s</span> ease-in-out;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.nav-fixed</span> <span class="selector-id">#nav-totop</span> <span class="selector-id">#percent</span>,</span><br><span class="line"><span class="selector-class">.page</span> <span class="selector-id">#nav-totop</span> <span class="selector-id">#percent</span></span><br><span class="line">  <span class="attribute">font-size</span> <span class="number">13px</span></span><br><span class="line">  <span class="attribute">border-radius</span> <span class="number">35px</span></span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">justify-content</span> center</span><br><span class="line">  <span class="attribute">align-items</span> center</span><br><span class="line">  <span class="attribute">white-space</span> nowrap</span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--card-bg)</span><br><span class="line">  <span class="attribute">width</span> <span class="number">25px</span></span><br><span class="line">  <span class="attribute">height</span> <span class="number">25px</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav-totop</span> <span class="selector-id">#percent</span></span><br><span class="line">  <span class="attribute">font-weight</span> <span class="number">700</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span><span class="selector-pseudo">:hover</span> <span class="selector-tag">a</span><span class="selector-class">.totopbtn</span></span><br><span class="line">  <span class="attribute">background</span> <span class="built_in">var</span>(--anzhiyu-main)</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav-totop</span><span class="selector-pseudo">:hover</span> <span class="selector-class">.totopbtn</span> <span class="selector-tag">i</span></span><br><span class="line">  <span class="attribute">opacity</span> <span class="number">1</span></span><br><span class="line">  <span class="attribute">color</span> <span class="built_in">var</span>(--anzhiyu-white)</span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav-totop</span> <span class="selector-class">.totopbtn</span> <span class="selector-tag">i</span></span><br><span class="line">  <span class="attribute">position</span> absolute</span><br><span class="line">  <span class="attribute">display</span> flex</span><br><span class="line">  <span class="attribute">opacity</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav-totop</span><span class="selector-class">.long</span> <span class="selector-class">.totopbtn</span> <span class="selector-tag">i</span></span><br><span class="line">  <span class="attribute">font-size</span> <span class="number">1rem</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#nav-totop</span><span class="selector-pseudo">:hover</span> <span class="selector-id">#percent</span></span><br><span class="line">  <span class="attribute">opacity</span> <span class="number">0</span></span><br><span class="line">  <span class="attribute">transform</span> <span class="built_in">scale</span>(<span class="number">1.5</span>)</span><br><span class="line">  <span class="attribute">font-weight</span> <span class="number">700</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span><span class="selector-pseudo">:not</span>(.nav-fixed) <span class="selector-id">#nav-totop</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">0</span></span><br><span class="line">  <span class="attribute">transform</span> <span class="built_in">scale</span>(<span class="number">0</span>)</span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span></span><br><span class="line">  <span class="attribute">margin-left</span> <span class="number">0</span> <span class="meta">!important</span></span><br><span class="line">  <span class="attribute">overflow</span> hidden</span><br><span class="line">  <span class="attribute">transition</span> <span class="number">0.3s</span> ease-in</span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span><span class="selector-class">.long</span>,</span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span><span class="selector-class">.long</span> <span class="selector-tag">a</span><span class="selector-class">.totopbtn</span>,</span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> <span class="selector-class">.nav-button</span><span class="selector-class">.long</span> <span class="selector-tag">a</span><span class="selector-class">.totopbtn</span> <span class="selector-tag">span</span></span><br><span class="line">  <span class="attribute">width</span> <span class="number">70px</span></span><br><span class="line"></span><br><span class="line"><span class="selector-id">#page-header</span> <span class="selector-id">#nav</span> <span class="selector-id">#nav-right</span> .long<span class="selector-id">#nav-totop</span><span class="selector-pseudo">:hover</span></span><br><span class="line">  <span class="attribute">background-color</span> transparent</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 媒体查询适配 */</span></span><br><span class="line"><span class="keyword">@media</span> screen <span class="keyword">and</span> (<span class="attribute">max-width</span>: <span class="number">1390px</span>)</span><br><span class="line">  #nav</span><br><span class="line">    padding <span class="number">0</span> <span class="number">1.5rem</span></span><br><span class="line">  #nav .mask-name-container</span><br><span class="line">    padding <span class="number">0</span> <span class="number">1.5rem</span></span><br><span class="line">  div#menus</span><br><span class="line">    padding <span class="number">0</span> <span class="number">1.5rem</span></span><br><span class="line"></span><br><span class="line">@media screen <span class="keyword">and</span> (<span class="attribute">min-width</span>: <span class="number">900px</span>)</span><br><span class="line">  #nav .back-home-button:<span class="attribute">hover</span></span><br><span class="line">    box-shadow var(--anzhiyu-shadow-main)</span><br><span class="line"></span><br><span class="line">@media screen <span class="keyword">and</span> (<span class="attribute">max-width</span>: <span class="number">768px</span>)</span><br><span class="line">  .mask-name-container</span><br><span class="line">    display none</span><br><span class="line">  #menus</span><br><span class="line">    padding <span class="number">20px</span></span><br><span class="line">  #page-header.not-top-img</span><br><span class="line">    margin-bottom <span class="number">10px</span></span><br><span class="line">  #nav</span><br><span class="line">    border-bottom none</span><br><span class="line">    background var(--anzhiyu-background)</span><br><span class="line">  #page-header #nav #nav-right div</span><br><span class="line">    margin-left .<span class="number">5rem</span></span><br><span class="line"></span><br><span class="line">@media screen <span class="keyword">and</span> (<span class="attribute">min-width</span>: <span class="number">768px</span>)</span><br><span class="line">  .menus_item:<span class="attribute">hover</span> &gt; a.site-page</span><br><span class="line">    <span class="attribute">color</span> var(--anzhiyu-white) !important</span><br><span class="line">    background var(--anzhiyu-main)</span><br><span class="line">    transition <span class="number">0.3s</span></span><br><span class="line">    box-shadow var(--anzhiyu-shadow-main)</span><br><span class="line">@media screen <span class="keyword">and</span> (<span class="attribute">min-width</span>: <span class="number">769px</span>)</span><br><span class="line">  /* 修复：滚动回顶部瞬间 nav 底部黑边（合成层/边框状态切换伪影）</span><br><span class="line">     所有状态下 #nav 一律透明无边框；恒建合成层避免切换闪烁 */</span><br><span class="line">  #nav</span><br><span class="line">    background: transparent !important</span><br><span class="line">    border-bottom: none !important</span><br><span class="line">    outline: none !important</span><br><span class="line">    box-shadow: none !important</span><br><span class="line">    -webkit-backdrop-filter: none</span><br><span class="line">    backdrop-filter: none</span><br><span class="line"></span><br><span class="line">  /* 关闭毛玻璃过渡：左右段 + 菜单 + 页面名 瞬间切换 */</span><br><span class="line">  #nav #blog_name,</span><br><span class="line">  #nav #nav-right</span><br><span class="line">    transition: none</span><br><span class="line">  #page-header.nav-fixed #nav #menus div.menus_items</span><br><span class="line">    transition: none !important</span><br><span class="line">  #page-header.nav-fixed #name-container,</span><br><span class="line">  #page-header.nav-fixed #nav #page-name</span><br><span class="line">    transition: none</span><br><span class="line"></span><br><span class="line">  /* ==================================================</span><br><span class="line">     导航栏三段式背景（自定义 <span class="number">2026</span>-<span class="number">09</span>-<span class="number">07</span>）</span><br><span class="line">     滚动固定后不再整条上背景色，</span><br><span class="line">     改为 左(站名) / 中(菜单) / 右(按钮) 三个独立圆角色块，其余区域透明</span><br><span class="line">     ================================================== */</span><br><span class="line">  #page-header.nav-fixed #nav,</span><br><span class="line">  #page-header.post-bg.nav-fixed #nav</span><br><span class="line">    background: transparent !important</span><br><span class="line">    outline: none !important</span><br><span class="line">    box-shadow: none !important</span><br><span class="line"></span><br><span class="line">  /* 左段：站名区（毛玻璃） */</span><br><span class="line">  #page-header.nav-fixed #nav #blog_name</span><br><span class="line">    background: rgba(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.68</span>)</span><br><span class="line">    -webkit-backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    border: <span class="number">1px</span> solid rgba(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.10</span>)</span><br><span class="line">    border-radius: <span class="number">50px</span></span><br><span class="line">    padding: <span class="number">0</span> <span class="number">16px</span></span><br><span class="line">    <span class="attribute">height</span>: <span class="number">40px</span></span><br><span class="line"></span><br><span class="line">  /* 中段：菜单区（毛玻璃） */</span><br><span class="line">  #page-header.nav-fixed #nav #menus .menus_items</span><br><span class="line">    background: rgba(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.68</span>)</span><br><span class="line">    -webkit-backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    border: <span class="number">1px</span> solid rgba(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.10</span>)</span><br><span class="line">    border-radius: <span class="number">50px</span></span><br><span class="line">    padding: <span class="number">0</span> <span class="number">6px</span></span><br><span class="line"></span><br><span class="line">  /* 右段：右侧按钮区（毛玻璃） */</span><br><span class="line">  #page-header.nav-fixed #nav #nav-right</span><br><span class="line">    background: rgba(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.68</span>)</span><br><span class="line">    -webkit-backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    border: <span class="number">1px</span> solid rgba(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.10</span>)</span><br><span class="line">    border-radius: <span class="number">50px</span></span><br><span class="line">    padding: <span class="number">0</span> <span class="number">10px</span></span><br><span class="line">    <span class="attribute">height</span>: <span class="number">40px</span></span><br><span class="line"></span><br><span class="line">  /* 暗色模式毛玻璃 */</span><br><span class="line">  [data-theme=&quot;dark&quot;] #page-header.nav-fixed #nav #blog_name,</span><br><span class="line">  [data-theme=&quot;dark&quot;] #page-header.nav-fixed #nav #menus .menus_items,</span><br><span class="line">  [data-theme=&quot;dark&quot;] #page-header.nav-fixed #nav #nav-right</span><br><span class="line">    background: rgba(<span class="number">29</span>, <span class="number">30</span>, <span class="number">34</span>, <span class="number">0.55</span>)</span><br><span class="line">    border-color: rgba(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.12</span>)</span><br><span class="line"></span><br><span class="line">  /* 页面名区（毛玻璃，自定义 <span class="number">2026</span>-<span class="number">09</span>-<span class="number">07</span>）</span><br><span class="line">     与其他三个色块统一：高 <span class="number">40px</span>、垂直居中于导航栏 */</span><br><span class="line">  #page-header.nav-fixed #nav #name-container</span><br><span class="line">    top: <span class="number">0</span> !important</span><br><span class="line"></span><br><span class="line">  #page-header.nav-fixed #nav #page-name</span><br><span class="line">    background: rgba(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.68</span>)</span><br><span class="line">    -webkit-backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    border: <span class="number">1px</span> solid rgba(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.10</span>)</span><br><span class="line">    border-radius: <span class="number">50px</span></span><br><span class="line">    padding: <span class="number">0</span> <span class="number">14px</span></span><br><span class="line">    <span class="attribute">height</span>: <span class="number">40px</span> !important</span><br><span class="line">    line-height: <span class="number">40px</span> !important</span><br><span class="line">    display: flex !important</span><br><span class="line">    align-items: center !important</span><br><span class="line">    justify-content: center !important</span><br><span class="line"></span><br><span class="line">  [data-theme=&quot;dark&quot;] #page-header.nav-fixed #nav #page-name</span><br><span class="line">    background: rgba(<span class="number">29</span>, <span class="number">30</span>, <span class="number">34</span>, <span class="number">0.55</span>)</span><br><span class="line">    border-color: rgba(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.12</span>)</span><br><span class="line"></span><br><span class="line">  /* 修复：暗色模式下 <span class="attribute">hover</span> 页面名时背景变主色，完全覆盖原文字 */</span><br><span class="line">  [data-theme=&quot;dark&quot;] #page-header.nav-fixed #nav #page-name:<span class="attribute">hover</span></span><br><span class="line">    background: var(--anzhiyu-main)</span><br><span class="line"></span><br><span class="line">  /* 跃迁按钮顶部居中提示（独立元素版）</span><br><span class="line">     ::after 因 #nav-right 的 backdrop-filter 创建包含块导致 fixed 失效，</span><br><span class="line">     改用独立元素绝对定位于 #nav，实现与页面名同位置同尺寸的顶部居中 */</span><br><span class="line">  #travellings_tip</span><br><span class="line">    position: absolute</span><br><span class="line">    left: <span class="number">0</span></span><br><span class="line">    right: <span class="number">0</span></span><br><span class="line">    margin: auto</span><br><span class="line">    <span class="attribute">width</span>: fit-content</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">40px</span></span><br><span class="line">    line-height: <span class="number">40px</span></span><br><span class="line">    display: flex</span><br><span class="line">    align-items: center</span><br><span class="line">    justify-content: center</span><br><span class="line">    <span class="attribute">color</span>: var(--anzhiyu-white)</span><br><span class="line">    background: var(--anzhiyu-main)</span><br><span class="line">    -webkit-backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    backdrop-filter: saturate(<span class="number">180%</span>) blur(<span class="number">20px</span>)</span><br><span class="line">    border: <span class="number">1px</span> solid rgba(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.35</span>)</span><br><span class="line">    box-shadow: var(--anzhiyu-shadow-nav)</span><br><span class="line">    border-radius: <span class="number">50px</span></span><br><span class="line">    padding: <span class="number">0</span> <span class="number">4em</span></span><br><span class="line">    font-size: var(--global-font-size)</span><br><span class="line">    white-space: nowrap</span><br><span class="line">    z-index: <span class="number">102</span></span><br><span class="line">    opacity: <span class="number">0</span></span><br><span class="line">    visibility: hidden</span><br><span class="line">    transform: scaleX(<span class="number">1.1</span>)</span><br><span class="line">    pointer-events: none</span><br><span class="line">    transition: .<span class="number">5s</span></span><br><span class="line"></span><br><span class="line">  /* 提示与其他三段毛玻璃块保持一致的暗色适配 */</span><br><span class="line">  [data-theme=&quot;dark&quot;] #travellings_tip</span><br><span class="line">    background: var(--anzhiyu-main)</span><br><span class="line">    border-color: rgba(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.35</span>)</span><br><span class="line"></span><br><span class="line">  #body-wrap:has(#travellings_button:<span class="attribute">hover</span>) #travellings_tip</span><br><span class="line">    opacity: <span class="number">1</span></span><br><span class="line">    visibility: visible</span><br><span class="line">    transform: none</span><br><span class="line"></span><br><span class="line">  /* 修复：过渡动画期间毛玻璃黑边（backdrop-filter 合成层边缘伪影）</span><br><span class="line">     预建层 + backface 隐藏，消除动画帧的边缘黑线 */</span><br><span class="line">  #page-header.nav-fixed #nav #blog_name,</span><br><span class="line">  #page-header.nav-fixed #nav #nav-right,</span><br><span class="line">  #page-header.nav-fixed #nav #menus .menus_items,</span><br><span class="line">  #page-header.nav-fixed #nav #page-name,</span><br><span class="line">  #travellings_tip</span><br><span class="line">    will-change: backdrop-filter</span><br><span class="line">    backface-visibility: hidden</span><br><span class="line">    -webkit-backface-visibility: hidden</span><br><span class="line"></span><br><span class="line">/* 移动端：隐藏跃迁提示（电脑端专属） */</span><br><span class="line">@media screen <span class="keyword">and</span> (<span class="attribute">max-width</span>: <span class="number">768px</span>)</span><br><span class="line">  #travellings_tip</span><br><span class="line">    display: none !important</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在 <code>themes/anzhiyu/layout/includes/header/nav.pug</code>文件底部添加</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">if theme.nav.travelling</span><br><span class="line">  span#travellings_tip 随机前往一个异次元之旅-跃迁项目网站</span><br></pre></td></tr></table></figure><p>这里的文字可以改原始主题的，我这里进行了修改</p><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><p>请务必备份完成之后在改，我的配置不一定适应你的，因为我的是在改了很多功能的基础上改的，如果无法使用，可以让ai修复一下</p>]]></content>
    
    
    <summary type="html">对安知鱼主题顶部导航栏美化，添加毛玻璃效果</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=17965fb8-e9de-0229-c5fe-9ede544a88c2" type="image"/>
    
    
    <category term="主题美化" scheme="https://blog.itrf.cn/tags/%E4%B8%BB%E9%A2%98%E7%BE%8E%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>零成本搭建高速图床</title>
    <link href="https://blog.itrf.cn/posts/17a1/"/>
    <id>https://blog.itrf.cn/posts/17a1/</id>
    <published>2026-09-08T10:01:30.000Z</published>
    <updated>2026-09-08T16:23:58.301Z</updated>
    
    <content type="html"><![CDATA[<h2 id="需求分析"><a href="#需求分析" class="headerlink" title="需求分析"></a>需求分析</h2><p>我目前博客的图片都是存在博客的images文件夹里面的，每次在Typora写完文章要发布的时候都要重新整理图片路径太麻烦了，并且直接把图片都放到博客里面也不是长久之计。</p><p>我的需求</p><ol><li>主打白嫖，能省就省</li><li>数据自主可控</li><li>有密码只能我一个人使用</li><li>在Typora里面添加图片可以自动上传到图床并替换好链接</li><li>有网页管理页面，我可以通过网页上传图片</li></ol><h2 id="方案"><a href="#方案" class="headerlink" title="方案"></a>方案</h2><p>我想到的方案是使用 Cloudflare R2 对象存储 +CloudflareWorkers + EdgeOne +PicList</p><p> Cloudflare R2 对象存储会给每个用户10G空间，用来存图片绰绰有余。</p><p>EdgeOne 用来缓存照片，可以有效降低 Cloudflare R2 的请求命中率。</p><p>CloudflareWorkers 用来构建网页，有现成的项目可以直接修改实现。</p><p>PicList软件用于在Typora里面添加图片自动上传到图床并替换好链接。</p><h2 id="部署教程"><a href="#部署教程" class="headerlink" title="部署教程"></a>部署教程</h2><p>准备三个域名(三个子域名也可以，但主域名必须托管到CF)，其中一个必须托管到CF(免费域名也可以)</p><h3 id="一-准备CF存储桶并且配置加速链接"><a href="#一-准备CF存储桶并且配置加速链接" class="headerlink" title="一.准备CF存储桶并且配置加速链接"></a>一.准备CF存储桶并且配置加速链接</h3><p>第一步添加R2对象存储</p><p><img src="https://i.itrf.cn/img/26/09/459.png" alt="添加存储"></p><p>存储桶名称：随便写要记住之后要用</p><p>位置：选择亚太或者用默认的</p><p>第二步启用公共开发url</p><p><img src="https://i.itrf.cn/img/26/09/366.png" alt="启用公共开发url"></p><p>第三步使用EO加速</p><p><img src="https://i.itrf.cn/img/26/09/053.png" alt="使用EO加速"></p><blockquote><p><strong>源站地址</strong>和<strong>回源 HOST 头</strong>都要填写CF给你的公共开发url，回源协议HTTPS</p></blockquote><p>配置网站缓存<br><img src="https://i.itrf.cn/img/26/09/754.png" alt="配置网站缓存"></p><p>修改默认的404页面</p><p><img src="https://i.itrf.cn/img/26/09/140.png" alt="修改默认的404页面"></p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh-CN&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;icon&quot;</span> <span class="attr">href</span>=<span class="string">&quot;https://i.itrf.cn/favicon.ico&quot;</span> <span class="attr">type</span>=<span class="string">&quot;image/x-icon&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>404 - 图片走丢了<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">        * &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">padding</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">box-sizing</span>: border-box;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-tag">body</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">height</span>: <span class="number">100vh</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">background</span>: <span class="number">#0f172a</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">font-family</span>: -apple-system, BlinkMacSystemFont, <span class="string">&quot;Segoe UI&quot;</span>, Roboto, <span class="string">&quot;Helvetica Neue&quot;</span>, Arial, sans-serif;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">color</span>: <span class="number">#f8fafc</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">overflow</span>: hidden;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">position</span>: relative;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="comment">/* 动态渐变背景光球 */</span></span></span><br><span class="line"><span class="language-css">        <span class="selector-class">.bg-glow</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">position</span>: absolute;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">width</span>: <span class="number">400px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">height</span>: <span class="number">400px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">135deg</span>, <span class="number">#3b82f6</span> <span class="number">0%</span>, <span class="number">#8b5cf6</span> <span class="number">100%</span>);</span></span><br><span class="line"><span class="language-css">            <span class="attribute">filter</span>: <span class="built_in">blur</span>(<span class="number">120px</span>);</span></span><br><span class="line"><span class="language-css">            <span class="attribute">border-radius</span>: <span class="number">50%</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">z-index</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">animation</span>: float <span class="number">10s</span> ease-in-out infinite alternate;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="keyword">@keyframes</span> float &#123;</span></span><br><span class="line"><span class="language-css">            <span class="number">0%</span> &#123; <span class="attribute">transform</span>: <span class="built_in">translate</span>(-<span class="number">100px</span>, -<span class="number">100px</span>); &#125;</span></span><br><span class="line"><span class="language-css">            <span class="number">100%</span> &#123; <span class="attribute">transform</span>: <span class="built_in">translate</span>(<span class="number">100px</span>, <span class="number">100px</span>); &#125;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="comment">/* 毛玻璃卡片容器 */</span></span></span><br><span class="line"><span class="language-css">        <span class="selector-class">.card</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">position</span>: relative;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">z-index</span>: <span class="number">1</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">background</span>: <span class="built_in">rgba</span>(<span class="number">30</span>, <span class="number">41</span>, <span class="number">59</span>, <span class="number">0.7</span>);</span></span><br><span class="line"><span class="language-css">            <span class="attribute">backdrop-filter</span>: <span class="built_in">blur</span>(<span class="number">16px</span>);</span></span><br><span class="line"><span class="language-css">            -webkit-<span class="attribute">backdrop-filter</span>: <span class="built_in">blur</span>(<span class="number">16px</span>);</span></span><br><span class="line"><span class="language-css">            <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="built_in">rgba</span>(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">0.1</span>);</span></span><br><span class="line"><span class="language-css">            <span class="attribute">padding</span>: <span class="number">40px</span> <span class="number">50px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">border-radius</span>: <span class="number">24px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">text-align</span>: center;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">20px</span> <span class="number">25px</span> -<span class="number">5px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.3</span>);</span></span><br><span class="line"><span class="language-css">            <span class="attribute">max-width</span>: <span class="number">420px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">width</span>: <span class="number">90%</span>;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-class">.error-code</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">font-size</span>: <span class="number">6rem</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">font-weight</span>: <span class="number">800</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(to right, <span class="number">#60a5fa</span>, <span class="number">#c084fc</span>);</span></span><br><span class="line"><span class="language-css">            -webkit-<span class="attribute">background-clip</span>: text;</span></span><br><span class="line"><span class="language-css">            -webkit-<span class="selector-tag">text</span>-<span class="attribute">fill</span>-<span class="attribute">color</span>: transparent;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">line-height</span>: <span class="number">1</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">margin-bottom</span>: <span class="number">15px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">letter-spacing</span>: -<span class="number">2px</span>;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-tag">h2</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">font-size</span>: <span class="number">1.25rem</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">font-weight</span>: <span class="number">600</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">margin-bottom</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">color</span>: <span class="number">#e2e8f0</span>;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-tag">p</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">color</span>: <span class="number">#94a3b8</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">font-size</span>: <span class="number">0.95rem</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">margin-bottom</span>: <span class="number">25px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">line-height</span>: <span class="number">1.5</span>;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-class">.btn</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">display</span>: inline-block;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">135deg</span>, <span class="number">#3b82f6</span> <span class="number">0%</span>, <span class="number">#6366f1</span> <span class="number">100%</span>);</span></span><br><span class="line"><span class="language-css">            <span class="attribute">color</span>: white;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">font-weight</span>: <span class="number">500</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">padding</span>: <span class="number">12px</span> <span class="number">28px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">border-radius</span>: <span class="number">9999px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">text-decoration</span>: none;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">transition</span>: all <span class="number">0.3s</span> ease;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">4px</span> <span class="number">12px</span> <span class="built_in">rgba</span>(<span class="number">59</span>, <span class="number">130</span>, <span class="number">246</span>, <span class="number">0.4</span>);</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-class">.btn</span><span class="selector-pseudo">:hover</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">2px</span>);</span></span><br><span class="line"><span class="language-css">            <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">6px</span> <span class="number">16px</span> <span class="built_in">rgba</span>(<span class="number">59</span>, <span class="number">130</span>, <span class="number">246</span>, <span class="number">0.6</span>);</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">        <span class="selector-class">.countdown</span> &#123;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">margin-top</span>: <span class="number">15px</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">font-size</span>: <span class="number">0.85rem</span>;</span></span><br><span class="line"><span class="language-css">            <span class="attribute">color</span>: <span class="number">#64748b</span>;</span></span><br><span class="line"><span class="language-css">        &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;bg-glow&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;error-code&quot;</span>&gt;</span>404<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h2</span>&gt;</span>哎呀，图片走丢了<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>你访问的链接可能已经失效，或者输入的地址有误。<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;占位符&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn&quot;</span> <span class="attr">id</span>=<span class="string">&quot;homeLink&quot;</span>&gt;</span>返回主页<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;countdown&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">span</span> <span class="attr">id</span>=<span class="string">&quot;sec&quot;</span>&gt;</span>5<span class="tag">&lt;/<span class="name">span</span>&gt;</span> 秒后自动返回主页<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// 倒计时自动跳转逻辑</span></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">let</span> seconds = <span class="number">5</span>;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> secTag = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;sec&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> targetUrl = <span class="string">&quot;占位符&quot;</span>; </span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> timer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">            seconds--;</span></span><br><span class="line"><span class="language-javascript">            secTag.<span class="property">innerText</span> = seconds;</span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">if</span> (seconds &lt;= <span class="number">0</span>) &#123;</span></span><br><span class="line"><span class="language-javascript">                <span class="built_in">clearInterval</span>(timer);</span></span><br><span class="line"><span class="language-javascript">                <span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">href</span> = targetUrl;</span></span><br><span class="line"><span class="language-javascript">            &#125;</span></span><br><span class="line"><span class="language-javascript">        &#125;, <span class="number">1000</span>);</span></span><br><span class="line"><span class="language-javascript">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><blockquote><p>可以参考我的，占位符可以改成你想跳转的网站，可以填写之后要部署的网页的域名</p></blockquote><p>在EO里面开启HTTPS，等待网站配置完成<br>现在就可以使用自己的域名来代替公共访问url了<br>只会要访问图床里面的图片直接使用这个域名就可以了<br>利用EO的缓存机制，可以有效减少CF的请求数</p><h3 id="二-配置PicList实现自动上传图床"><a href="#二-配置PicList实现自动上传图床" class="headerlink" title="二.配置PicList实现自动上传图床"></a>二.配置PicList实现自动上传图床</h3><p>访问官网，安装软件，并完成相关配置<br><div calss='anzhiyu-tag-link'><a class="tag-Link" target="_blank" href="https://piclist.cn/">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" style="background-image: url(https://piclist.cn/favicon.png)">          <i class="anzhiyufont anzhiyu-icon-link" style="display: none"></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">PicList</div>            <div class="tag-link-sitename"> 图片上传与云存储管理</div>        </div>        <i class="anzhiyufont anzhiyu-icon-angle-right"></i>    </div>    </a></div></p><p>返回到CF R2对象存储 获取密钥</p><p><img src="https://i.itrf.cn/img/26/09/014.png" alt="获取密钥1"><br>选择 对象读和写<br><img src="https://i.itrf.cn/img/26/09/362.png" alt="获取密钥2"></p><p>注意密钥只会出现一次，请保存好再关闭网页</p><p>参考PicList的官方教程添加存图床，选择S3类型</p><p><img src="https://i.itrf.cn/img/26/09/226.png" alt="添加存图床1"><br><img src="https://i.itrf.cn/img/26/09/b4cc.png" alt="添加存图床2"></p><p>下面的按照自己的需求配置吧，可以去看看官方文档，配置太多了</p><p><img src="https://i.itrf.cn/img/26/09/908.png" alt="云端同步删除"></p><p><img src="https://i.itrf.cn/img/26/09/742.png" alt="移除图片基础信息"></p><p><img src="https://i.itrf.cn/img/26/09/183.png" alt="自定义上传名称"></p><p>配置Typora实现自动上传，按照我的配置就可以了</p><p>文件-&gt;偏好设置-&gt;图像</p><p><img src="https://i.itrf.cn/img/26/09/669.png" alt="Typora配置"></p><h3 id="三-部署网页"><a href="#三-部署网页" class="headerlink" title="三.部署网页"></a>三.部署网页</h3><p>使用我二开的PixR2部署网页，新增支持图床自定义域名功能<br><div calss='anzhiyu-tag-link'><a class="tag-Link" target="_blank" href="https://github.com/itrfcn/PixR2">    <div class="tag-link-tips">引用站外地址</div>    <div class="tag-link-bottom">        <div class="tag-link-left" style="">          <i class="anzhiyufont anzhiyu-icon-link" style=""></i>        </div>        <div class="tag-link-right">            <div class="tag-link-title">PixR2</div>            <div class="tag-link-sitename"> 基于 Cloudflare Workers + R2 的多入口图床与图片管理平台</div>        </div>        <i class="anzhiyufont anzhiyu-icon-angle-right"></i>    </div>    </a></div></p><p>项目文档里面有详细部署教程，下面我只做演示，推荐直接看项目文档</p><p>第一步创建CFWorkers</p><p>复制仓库里面的_worker.js里面的代码</p><p><img src="https://i.itrf.cn/img/26/09/491.png" alt="1"></p><p><img src="https://i.itrf.cn/img/26/09/548.png" alt="2"></p><p><img src="https://i.itrf.cn/img/26/09/221.png" alt="3"></p><p><img src="https://i.itrf.cn/img/26/09/930.png" alt="4"></p><p><img src="https://i.itrf.cn/img/26/09/af98.png" alt="5"></p><p><img src="https://i.itrf.cn/img/26/09/7f46.png" alt="6"></p><p>设置完成环境变量之后需要重新部署一下Workers</p><p>第二步给网页添加自己的域名并使用EO加速</p><p>因为CFWorkers限制，我们没有办法直接加速Workers里面的添加的域名，但是经过我的研究，发现使用Workers路由可以完美解决这个问题，我们需要准备一个域名做中转，这个域名必须托管到CF</p><p>给这个域名开启小云盾，解析地址随便填写，可以填我这个</p><p><img src="https://i.itrf.cn/img/26/09/70f9.png" alt="配置中转域名"></p><p>添加Workers路由，不要直接在Workers里面直接添加域名，一定要用路由！！！</p><p><img src="https://i.itrf.cn/img/26/09/0334.png" alt="添加路由"></p><p>打开EO添加站点</p><p><strong>源站地址</strong>和<strong>回源 HOST 头</strong>都要填写上面设置的中转域名</p><p><img src="https://i.itrf.cn/img/26/09/b9b8.png" alt="使用EO加速"></p><p>等待部署完成，开启HTTPS，并且配置缓存，可以参考上面的缓存策略</p><p>站点加速 -&gt; 规则引擎 -&gt; 配置缓存</p><p>配置完成缓存策略可以减少CF命中次数</p><p>注意：网页加速和图床加速配置一个就可以了，当然你也可以都配置，环境变量IMAGE_DOMAIN填写就走图床地址，不填写就走网页地址，按照需求填写。<br>我推荐填写IMAGE_DOMAIN，这样软件上传和网页上传的返回的图片链接是一致的，详细请看项目里面对这个参数的介绍</p><h2 id="注意点"><a href="#注意点" class="headerlink" title="注意点"></a>注意点</h2><ol><li><strong>源站地址</strong>和<strong>回源 HOST 头</strong> 必须一致，并且回源协议选择HTTPS</li><li>不能直接用EO加速Workers给你的域名或者在Workers里面添加的域名</li><li>必须利用Workers路由绕开限制，直接加速会出现522错误码</li></ol>]]></content>
    
    
    <summary type="html">用 Cloudflare R2 对象存储 +CloudflareWorkers + EdgeOne搭建免费高速图床</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=5c603244-1167-6355-3b94-9f9cd1153a18" type="image"/>
    
    
    <category term="搭建教程" scheme="https://blog.itrf.cn/tags/%E6%90%AD%E5%BB%BA%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>Hexo 使用 patch‑package 保存 node_modules 修改</title>
    <link href="https://blog.itrf.cn/posts/8836/"/>
    <id>https://blog.itrf.cn/posts/8836/</id>
    <published>2026-09-07T09:02:51.000Z</published>
    <updated>2026-09-07T11:05:26.905Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>适用场景：你手动修改了 <code>node_modules</code> 内部第三方包源码，重装依赖、换电脑迁移会丢失改动。 </p></blockquote><h2 id="什么是-patch‑package"><a href="#什么是-patch‑package" class="headerlink" title="什么是 patch‑package"></a>什么是 patch‑package</h2><p>直接修改 <code>node_modules</code> 只是临时效果，执行 <code>npm install</code> 就会被官方原版覆盖。 <code>patch‑package</code> 对比你修改前后代码，生成 <code>patches/*.patch</code> 补丁文件，提交进仓库；执行 <code>npm install</code> 自动把你的修改打回去，实现换电脑、重装依赖改动不丢失。</p><blockquote><p>Hexo 博客迁移电脑不再复制庞大、容易出错的 <code>node_modules</code>。</p></blockquote><h2 id="一、旧电脑生成补丁步骤"><a href="#一、旧电脑生成补丁步骤" class="headerlink" title="一、旧电脑生成补丁步骤"></a>一、旧电脑生成补丁步骤</h2><h3 id="1、安装-patch‑package"><a href="#1、安装-patch‑package" class="headerlink" title="1、安装 patch‑package"></a>1、安装 patch‑package</h3><p>进入你的 Hexo 项目根目录（<code>package.json</code>所在文件夹）执行：</p><figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">npm install patch-package --save-dev</span><br></pre></td></tr></table></figure><h3 id="2、修改-package-json-scripts，增加-postinstall"><a href="#2、修改-package-json-scripts，增加-postinstall" class="headerlink" title="2、修改 package.json scripts，增加 postinstall"></a>2、修改 package.json scripts，增加 postinstall</h3><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo-site&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;private&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo generate&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;clean&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo clean&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;deploy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo clean &amp;&amp; hexo generate &amp;&amp; hexo deploy &amp;&amp; python push_baidu.py &amp;&amp; hexo clean&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;server&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo server&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;postinstall&quot;</span><span class="punctuation">:</span> <span class="string">&quot;patch-package&quot;</span>  <span class="comment">//这里添加</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;hexo&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;8.1.1&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="comment">// 你的原有依赖不动</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;patch-package&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8.0.1&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p><code>&quot;postinstall&quot;: &quot;patch-package&quot;</code>：每次<code>npm install</code>结束自动执行补丁应用，<strong>不要手动运行这条命令</strong>，npm 会自动调用。</p></blockquote><h3 id="3、确认你的修改还在"><a href="#3、确认你的修改还在" class="headerlink" title="3、确认你的修改还在"></a>3、确认你的修改还在</h3><p><strong>关键前提，千万不要执行 <code>npm install</code>！</strong> 打开你改动过的 <code>node_modules/包名/xxx.js</code>，确认你的手动修改已经保存。</p><blockquote><p>一旦运行<code>npm install</code>，node_modules 恢复原版，改动直接消失，无法生成补丁。</p></blockquote><h3 id="4、生成补丁文件"><a href="#4、生成补丁文件" class="headerlink" title="4、生成补丁文件"></a>4、生成补丁文件</h3><p>项目根目录执行，把<code>包名</code>替换成你修改的包文件夹名字（<code>node_modules/</code>后面的文件夹名）。</p><figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">npx patch-package 包名</span><br></pre></td></tr></table></figure><p>示例：修改了 <code>node_modules/hexo-renderer-kramed</code></p><figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">npx patch-package hexo-renderer-kramed</span><br></pre></td></tr></table></figure><h4 id="成功输出"><a href="#成功输出" class="headerlink" title="成功输出"></a>成功输出</h4><figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">created patch file patches/hexo-renderer-kramed+<span class="number">0</span>.<span class="number">1</span>.<span class="number">4</span>.patch</span><br></pre></td></tr></table></figure><p>项目根目录出现全新 <code>patches</code> 文件夹，补丁文件就在这里。</p><h4 id="报错：There-don-39-t-appear-to-be-any-changes"><a href="#报错：There-don-39-t-appear-to-be-any-changes" class="headerlink" title="报错：There don&#39;t appear to be any changes"></a>报错：<code>There don&#39;t appear to be any changes</code></h4><blockquote><p>含义：没有检测到任何改动。 原因：</p></blockquote><ol><li>你的修改已经丢失（执行过 npm install），需要重新手动改 node_modules 里面文件，保存，立刻执行上面命令；</li><li>包名字写错；</li><li>不在项目根目录执行命令。</li></ol><h3 id="5、git-提交文件清单"><a href="#5、git-提交文件清单" class="headerlink" title="5、git 提交文件清单"></a>5、git 提交文件清单</h3><p><strong>提交到 git</strong></p><ul><li><code>patches/</code>整个文件夹</li><li><code>package.json</code></li><li><code>package‑lock.json</code></li><li><code>source/</code>、<code>themes/</code>、<code>_config.yml</code>等博客源码配置</li></ul><p><strong>绝对不要提交</strong></p><ul><li><code>node_modules/</code></li></ul><p><code>.gitignore</code>确保有这几行</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">node_modules/</span><br><span class="line">public/</span><br><span class="line">.deploy_git/</span><br><span class="line">db.json</span><br></pre></td></tr></table></figure><p>提交示例：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git add patches package.json package-lock.json</span><br><span class="line">git commit -m &quot;patch:保存第三方依赖自定义修改&quot;</span><br></pre></td></tr></table></figure><h2 id="二、新电脑迁移完整步骤"><a href="#二、新电脑迁移完整步骤" class="headerlink" title="二、新电脑迁移完整步骤"></a>二、新电脑迁移完整步骤</h2><ol><li>新电脑安装 <strong>和旧电脑一致大版本 Node.js</strong>，安装 Git。</li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 旧电脑查看版本</span><br><span class="line">node -v</span><br></pre></td></tr></table></figure><ol><li>获取博客源码 两种方式任选其一</li></ol><ul><li>git clone 你的仓库；</li><li>拷贝本地项目文件夹（<strong>不要拷贝 node_modules</strong>）。</li></ul><ol><li>Hexo 全局命令（如需要）</li></ol><figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">npm install -g hexo-cli</span><br></pre></td></tr></table></figure><ol><li>安装项目依赖（核心）</li></ol><figure class="highlight cmd"><table><tr><td class="code"><pre><span class="line">npm install</span><br></pre></td></tr></table></figure><blockquote><p>npm install 完成之后自动触发<code>postinstall</code>，控制台会打印<code>Applying patches...</code>，自动应用你写的修改，无需手动操作GitHub。</p></blockquote><ol><li>测试运行博客</li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">npm run server</span><br></pre></td></tr></table></figure><p>访问 <code>http://localhost:4000</code>，验证你对依赖的自定义修改已经生效。</p><h2 id="三、本地测试补丁是否有效"><a href="#三、本地测试补丁是否有效" class="headerlink" title="三、本地测试补丁是否有效"></a>三、本地测试补丁是否有效</h2><p>生成补丁完成后，本地模拟迁移环境：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 删除本地node_modules</span><br><span class="line">rm -rf node_modules</span><br><span class="line"># 重新安装依赖，看补丁自动打上</span><br><span class="line">npm install</span><br><span class="line">npm run server</span><br></pre></td></tr></table></figure><p>如果功能正常，代表补丁完全没问题，可以放心迁移电脑。</p><h2 id="四、应急临时迁移方案"><a href="#四、应急临时迁移方案" class="headerlink" title="四、应急临时迁移方案"></a>四、应急临时迁移方案</h2><blockquote><p>仅用于临时搬家，<strong>不要提交 node_modules 到 git 仓库</strong></p></blockquote><ol><li>旧电脑复制<strong>整个项目文件夹，包含已经修改好的 node_modules</strong>；</li><li>拷贝到新电脑，直接运行<code>npm run server</code>；</li><li>新电脑跑通之后，立刻按照教程生成<code>patches</code>补丁，后续不再依赖拷贝 node_modules。</li></ol><h2 id="五、常见踩坑"><a href="#五、常见踩坑" class="headerlink" title="五、常见踩坑"></a>五、常见踩坑</h2><ol><li><strong>升级被打补丁的 npm 包</strong></li></ol><blockquote><p>补丁文件记录的是特定版本差异，一旦升级该依赖包，补丁大概率失效。 需要：重新修改新版本<code>node_modules</code>源码 → 再次执行<code>npx patch-package 包名</code>重新生成补丁。</p></blockquote><ol><li>部署 CI/GitHub Actions</li></ol><blockquote><p>只需要正常执行<code>npm install</code>，postinstall 钩子会自动打补丁，不需要额外脚本。</p></blockquote><ol><li>区分主题修改和依赖修改</li></ol><ul><li>修改<code>themes/xxx</code>：你的博客源码，直接 git 提交，<strong>不需要 patch‑package</strong>；</li><li>修改<code>node_modules/xxx</code>第三方库源码：必须使用 patch‑package。</li></ul><ol><li>查看补丁是否执行成功 运行<code>npm install</code>，看控制台输出是否打印 <code>Applying patches... xxx ✔</code>，代表补丁成功应用。</li></ol><h2 id="六、命令速查表"><a href="#六、命令速查表" class="headerlink" title="六、命令速查表"></a>六、命令速查表</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># 安装工具</span><br><span class="line">npm install patch-package --save-dev</span><br><span class="line"></span><br><span class="line"># 生成补丁</span><br><span class="line">npx patch-package 包名</span><br><span class="line"></span><br><span class="line"># 测试补丁（模拟新电脑环境）</span><br><span class="line">rm -rf node_modules</span><br><span class="line">npm install</span><br><span class="line">npm run server</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Hexo 使用 patch‑package 保存 node_modules 修改</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=0a79794e-8577-8e30-c36a-0aa6352e4549" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>JSONP一种解决跨域的古老方法</title>
    <link href="https://blog.itrf.cn/posts/4d2b/"/>
    <id>https://blog.itrf.cn/posts/4d2b/</id>
    <published>2026-09-07T07:20:13.000Z</published>
    <updated>2026-09-07T07:30:42.697Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>在修复博客欢迎卡片api接口的时候，想使用腾讯地图的接口，但是直接使用会出现跨域问题，搭建中转又太麻烦，在网上找到了一个很老的技术JSONP，完全符合我的需求。</p></blockquote><p><strong>JSONP（JSON with Padding</strong>）是一种非常经典、通用的前端跨域“黑客技巧”（Hack）。</p><h3 id="被逼出来的方案：同源策略与漏洞"><a href="#被逼出来的方案：同源策略与漏洞" class="headerlink" title="被逼出来的方案：同源策略与漏洞"></a>被逼出来的方案：同源策略与漏洞</h3><p>浏览器有一个铁律叫<strong>同源策略 (Same-Origin Policy)</strong>：如果两个页面的域名、协议或端口不同，前端直接用 <code>fetch</code> 或 <code>AJAX</code> 去请求对方的数据，浏览器就会报错拦截（也就是我们常说的 CORS 跨域报错）。</p><p>但在早期，前端工程师们发现了一个“官方漏洞”： 虽然 AJAX 被限制了，但是 HTML 里带有 <code>src</code> 属性的标签是<strong>不受跨域限制的</strong>！</p><ul><li>你可以毫无阻碍地加载别人域名的图片：<code>&lt;img src=&quot;别人家的.jpg&quot;&gt;</code></li><li>也可以加载别人域名的 CSS：<code>&lt;link href=&quot;别人家的.css&quot;&gt;</code></li><li><strong>最关键的是，你可以加载并执行别人域名的 JS 脚本：<code>&lt;script src=&quot;别人家的.js&quot;&gt;&lt;/script&gt;</code></strong></li></ul><p>JSONP 就是利用这个 <code>&lt;script&gt;</code> 标签可以跨域下载并执行代码的特性，巧妙地把数据“偷”回来的。</p><h3 id="JSONP-是怎么运作的？"><a href="#JSONP-是怎么运作的？" class="headerlink" title="JSONP 是怎么运作的？"></a>JSONP 是怎么运作的？</h3><p>假设你的网站 <code>a.com</code> 想向 <code>api.b.com</code> 获取用户数据：</p><p><strong>第一步：前端先在全局准备一个“接收器”</strong> 你先在自己的代码里定义好一个全局函数，等着接收数据。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="variable language_">window</span>.<span class="property">receiveData</span> = <span class="keyword">function</span>(<span class="params">data</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;太棒了，拿到了跨域数据：&quot;</span>, data);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>第二步：前端动态创建 <code>&lt;script&gt;</code> 标签发请求</strong> 你用 JS 动态创建一个 script 标签，把目标接口写在 <code>src</code> 里，<strong>并且通过参数把刚才的函数名告诉后端</strong>。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> script = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;script&#x27;</span>);</span><br><span class="line"><span class="comment">// 注意这里的 ?callback=receiveData</span></span><br><span class="line">script.<span class="property">src</span> = <span class="string">&#x27;https://api.b.com/get_user?callback=receiveData&#x27;</span>; </span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(script); <span class="comment">// 插入页面，浏览器立即发出请求</span></span><br></pre></td></tr></table></figure><p><strong>第三步：服务端配合“穿衣服”（最关键）</strong> <code>api.b.com</code> 的服务器收到请求，看到了 <code>callback=receiveData</code> 这个参数。 如果它只是返回纯粹的 JSON 数据 <code>{&quot;name&quot;: &quot;张三&quot;}</code>，浏览器的 script 标签是不认识的，会报错。 所以，服务端会把 JSON 数据当作参数，<strong>塞进你传过来的函数名里包裹起来</strong>（这就是 JSONP 中 Padding 的意思——填充/包裹），然后返回一段纯文本的JavaScript 代码：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="title function_">receiveData</span>(&#123;<span class="string">&quot;name&quot;</span>: <span class="string">&quot;张三&quot;</span>&#125;);</span><br></pre></td></tr></table></figure><p><strong>第四步：浏览器自动执行代码</strong> 你的浏览器下载完这段文本后，把它当成正常的 JS 代码直接执行。这一执行，正好调用了你在第一步里定义好的 <code>receiveData</code> 函数，数据就被完美地传进了你的系统里！</p><h3 id="为什么现在很少用-JSONP-了？"><a href="#为什么现在很少用-JSONP-了？" class="headerlink" title="为什么现在很少用 JSONP 了？"></a>为什么现在很少用 JSONP 了？</h3><p>虽然 JSONP 巧妙地绕过了浏览器的安全封锁，但它有几个致命的硬伤，导致现在正规开发中已经被边缘化：</p><ul><li><strong>只能发 GET 请求：</strong> 因为它本质上是给 <code>&lt;script&gt;</code> 标签赋值 <code>src</code> 来发起的，这就注定了它无法发送 POST、PUT、DELETE 等请求。如果你想上传一张图片或提交一大段表单，JSONP 毫无办法。</li><li><strong>巨大的安全隐患 (XSS 风险)：</strong> JSONP 的本质是<strong>无条件信任并执行别人服务器返回的代码</strong>。如果那个 API 被黑客劫持，或者由于漏洞被注入了恶意代码，它返回的不再是数据，而是一句 <code>alert(&#39;你被黑了&#39;)</code> 甚至窃取你网站用户密码的逻辑，你的网站也会直接执行。</li><li><strong>错误处理极差：</strong> 使用现代的 <code>fetch</code>，你可以轻松拿到 404、500 等 HTTP 状态码并处理错误。但用 JSONP，如果请求失败了，你只能靠 <code>script.onerror</code> 知道“它没连上”，根本无法获取具体的错误原因。</li></ul><p><strong>总结：</strong> JSONP 是特定历史时期的产物，是一种靠前后端默契配合绕开规则的技巧。如今，标准的 <strong>CORS（跨域资源共享）</strong> 才是王道——也就是服务端在响应头里明确告诉浏览器 <code>Access-Control-Allow-Origin: *</code>，允许前端直接跨域请求。</p>]]></content>
    
    
    <summary type="html">JSONP一种解决跨域的古老方法</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=3e4cc765-62fd-c40f-e14a-38e98960e595" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>EdgeOne加速Vercel获取真实用户IP</title>
    <link href="https://blog.itrf.cn/posts/2fd6/"/>
    <id>https://blog.itrf.cn/posts/2fd6/</id>
    <published>2026-09-07T06:53:00.000Z</published>
    <updated>2026-09-07T07:30:42.696Z</updated>
    
    <content type="html"><![CDATA[<h2 id="问题复现"><a href="#问题复现" class="headerlink" title="问题复现"></a>问题复现</h2><p>使用Vercel部署的Twikoo并且启用了EdgeOne加速，开启了海内外分流，出现无法获取用户真实IP的问题，获取到的是CDN的IP。</p><h2 id="问题根源"><a href="#问题根源" class="headerlink" title="问题根源"></a>问题根源</h2><p>Vercel Hobby/Pro 会过滤、重写外部传入的 HTTP 头：</p><ol><li><code>EO‑Connecting‑IP</code>：EO 内置头，Vercel 直接丢弃</li><li><code>X‑Forwarded‑For</code>：Vercel 会重写覆盖，只能拿到 EO 节点 IP，所以 TwikooIP 属地错误</li></ol><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><h3 id="EdgeOne-控制台配置"><a href="#EdgeOne-控制台配置" class="headerlink" title="EdgeOne 控制台配置"></a>EdgeOne 控制台配置</h3><p>使用「真实客户端 IP 头部」：</p><ul><li>开关打开</li><li><strong>头部名称</strong>：<code>Eo‑Client‑Ip</code></li><li>保存，等待 2‑5 分钟配置全网生效</li></ul><blockquote><p>EO 会自动把访客真实 IP 填入这个自定义头部，不需要手动写变量。</p></blockquote><h3 id="Vercel‑Twikoo-配置"><a href="#Vercel‑Twikoo-配置" class="headerlink" title="Vercel‑Twikoo 配置"></a>Vercel‑Twikoo 配置</h3><ol><li>环境变量 <code>TWIKOO_IP_HEADERS</code> 值（严格复制，小写 + 合法 JSON）</li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[&quot;headers.eo-client-ip&quot;]</span><br></pre></td></tr></table></figure><ol><li><strong>保存环境变量后必须点 Redeploy 重新部署 Vercel 项目</strong>，只保存不部署不会生效。</li></ol><h2 id="场景：分流"><a href="#场景：分流" class="headerlink" title="场景：分流"></a>场景：分流</h2><blockquote><p>架构：</p><ol><li><strong>国内用户 → EdgeOne CDN → Vercel</strong>：请求携带自定义头 <code>eo‑client‑ip</code></li><li><strong>海外用户：绕过 EdgeOne，直接访问 Vercel</strong>：不会携带 <code>eo‑client‑ip</code>，此时 Vercel 原生提供 <code>x‑forwarded‑for</code> 拿到访客真实 IPVercel</li></ol></blockquote><p>Twikoo 的<code>TWIKOO_IP_HEADERS</code>支持<strong>数组多表头，按顺序优先读取</strong>：数组写两个，优先读第一个；为空就自动往下读第二个。</p><ol><li><strong>国内访问域名（走 EdgeOne）</strong></li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&#123;&quot;eo-client-ip&quot;:&quot;你的国内公网IP&quot;,&quot;x-forwarded-for&quot;:&quot;EO节点IP&quot;&#125;</span><br></pre></td></tr></table></figure><p>Twikoo 优先读取<code>eo‑client‑ip</code>正确 IP。</p><ol><li><strong>海外环境直连 Vercel 域名（不走 EdgeOne）</strong></li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&#123;&quot;eo-client-ip&quot;:undefined,&quot;x-forwarded-for&quot;:&quot;海外访客真实IP&quot;&#125;</span><br></pre></td></tr></table></figure><p><code>eo‑client‑ip</code>为空，自动降级读取<code>x‑forwarded‑for</code>拿到真实 IP。</p><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ol><li>只有<strong>海外请求绕过 EdgeOne 直接访问 Vercel</strong>的时候，<code>x‑forwarded‑for</code>才是可信真实 IP； 国内走 EdgeOne 的流量，Vercel 会重写 XFF，XFF 拿到的是 EO 节点 IP，所以<strong>数组顺序必须 eo‑client‑ip 放第一位</strong>。</li><li>修改 Vercel 环境变量，<strong>必须 Redeploy 重新构建</strong>。</li><li>EdgeOne 配置修改要等待 2‑5 分钟生效。</li></ol>]]></content>
    
    
    <summary type="html">EdgeOne加速Vercel获取真实用户IP</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=1eadce25-8160-163e-eff9-ff1e3627c111" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>EdgeOne不自动部署</title>
    <link href="https://blog.itrf.cn/posts/6931/"/>
    <id>https://blog.itrf.cn/posts/6931/</id>
    <published>2026-09-07T06:46:26.000Z</published>
    <updated>2026-09-07T07:30:42.695Z</updated>
    
    <content type="html"><![CDATA[<h2 id="现象复现"><a href="#现象复现" class="headerlink" title="现象复现"></a>现象复现</h2><p>绑定 GitHub 仓库，推送到<code>page</code>分支(非主分支)，EdgeOne 不会自动触发部署；更新仓库内容，EdgeOne不会自动部署。</p><blockquote><p>注意在项目设置中正确配置域名所对应的分支</p></blockquote><h2 id="修复方案"><a href="#修复方案" class="headerlink" title="修复方案"></a>修复方案</h2><h3 id="方案-1：原生-Git-集成（Webhook-方式，优先）"><a href="#方案-1：原生-Git-集成（Webhook-方式，优先）" class="headerlink" title="方案 1：原生 Git 集成（Webhook 方式，优先）"></a>方案 1：原生 Git 集成（Webhook 方式，优先）</h3><ol><li>EdgeOne 断开 Git 关联</li><li>GitHub 应用<code>EdgeOne Makers</code>授权，确保该仓库拥有权限</li><li>EdgeOne 重新绑定仓库，生产分支指定<code>page</code>，开启自动部署</li><li>确认 GitHub 仓库出现 EdgeOne Webhook</li><li>push 代码到<code>page</code>分支，观察<code>Recent Deliveries</code>和 EdgeOne 部署列表</li></ol><h3 id="方案-2：兜底方案（原生-Webhook-持续异常时使用）"><a href="#方案-2：兜底方案（原生-Webhook-持续异常时使用）" class="headerlink" title="方案 2：兜底方案（原生 Webhook 持续异常时使用）"></a>方案 2：兜底方案（原生 Webhook 持续异常时使用）</h3><p>绕过 GitHub 仓库 Webhook，使用<strong>部署钩子 + GitHub Action</strong></p><ol><li>EdgeOne 项目设置 → 部署钩子，创建钩子，复制 POST 地址</li><li>在仓库新建<code>.github/workflows/edgeone-deploy.yml</code>，监听<code>page</code>分支 push，调用该 POST 地址触发部署。</li></ol><blockquote><p>由 GitHub Action 主动调用接口触发部署，不再依赖仓库 webhook。</p></blockquote>]]></content>
    
    
    <summary type="html">EdgeOne 不会自动触发部署，更新仓库内容，EdgeOne不会自动部署。</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=782efef0-2d3b-e84a-869c-4fba326a4ad4" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>Git 仓库嵌套（嵌套 git / 子模块坑）</title>
    <link href="https://blog.itrf.cn/posts/1aa1/"/>
    <id>https://blog.itrf.cn/posts/1aa1/</id>
    <published>2026-09-07T03:32:24.000Z</published>
    <updated>2026-09-07T07:22:27.298Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>今天在备份博客的时候遇到了一个问题，主题目录无法提交到github仓库，最终发现是git仓库嵌套问题</p></blockquote><h2 id="什么是-Git-仓库嵌套"><a href="#什么是-Git-仓库嵌套" class="headerlink" title="什么是 Git 仓库嵌套?"></a>什么是 Git 仓库嵌套?</h2><p><strong>一个 git 仓库里面，放了另一个完整 git 仓库</strong>。 也就是父仓库文件夹里面，某个子文件夹内部藏了一个 <code>.git</code> 文件夹。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">你的主仓库(父git，有/.git)</span><br><span class="line">└── themes/anzhiyu   ← 这个文件夹里面又有一个 /.git</span><br></pre></td></tr></table></figure><blockquote><p>这就是仓库嵌套。父 git 会把这个子文件夹识别成一个<strong>特殊的 git 指针</strong>，<strong>不会读取子文件夹里面任何文件</strong>。</p></blockquote><h2 id="怎么产生的？"><a href="#怎么产生的？" class="headerlink" title="怎么产生的？"></a>怎么产生的？</h2><ol><li><code>git clone 地址 themes/anzhiyu</code> clone 下来的项目自带内部<code>.git</code></li><li>复制粘贴另一个 git 项目的整个文件夹过来，没删掉里面的<code>.git</code></li></ol><blockquote><p>重点：<strong>哪怕你后来把子文件夹内部的 <code>.git</code> 删除了，父 git 的索引缓存还会记住它曾经是嵌套仓库，问题依旧存在！GitHub 显示蓝色箭头文件夹</strong>。</p></blockquote><h2 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h2><ol><li>本地文件全都在，磁盘上看得见；</li><li><code>git add 子文件夹</code>，执行后 <code>git commit</code> → <code>nothing to commit, working tree clean</code>，提交不上文件；</li><li><code>git status</code>会显示：<code>xxx(modified content)</code>；</li><li>推上 GitHub：文件夹出现<strong>蓝色箭头</strong>，点不开，看不到里面真实源码，只存一个 commit 哈希指针。</li></ol><blockquote><p>GitHub 蓝色箭头 = git 子模块标记，仓库<strong>没有保存子文件夹的真实代码</strong>。</p></blockquote><h2 id="两种处理方案"><a href="#两种处理方案" class="headerlink" title="两种处理方案"></a>两种处理方案</h2><h3 id="方案-A：把子仓库变成普通文件夹"><a href="#方案-A：把子仓库变成普通文件夹" class="headerlink" title="方案 A：把子仓库变成普通文件夹"></a>方案 A：把子仓库变成普通文件夹</h3><p>彻底解除嵌套，把子文件夹当做普通文件交给父 git 管理。</p><ol><li>删除子文件夹内部隐藏的 <code>.git</code></li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"># windows</span><br><span class="line">Remove-Item -Recurse -Force themes/anzhiyu\.git</span><br></pre></td></tr></table></figure><ol><li><strong>清除父 git 残留的缓存记录（必做！）</strong></li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git rm --cached themes/anzhiyu</span><br></pre></td></tr></table></figure><blockquote><p><code>--cached</code>：<strong>只删 git 索引，本地文件完全不动，千万不要丢掉这个参数</strong></p></blockquote><ol><li>如果有 <code>.gitmodules</code> 文件，删掉（子模块配置文件）</li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Remove-Item -Force .gitmodules</span><br></pre></td></tr></table></figure><ol><li>重新加入版本管理</li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git add themes/anzhiyu</span><br><span class="line">git commit -m &quot;转为普通目录&quot;</span><br><span class="line">git push</span><br></pre></td></tr></table></figure><p>优点：简单，克隆仓库不需要额外命令。 缺点：子项目不能用 git 更新，后续更新只能手动下载覆盖。</p><hr><h3 id="方案-B：正式使用-Git-Submodule（网上查到的没试）"><a href="#方案-B：正式使用-Git-Submodule（网上查到的没试）" class="headerlink" title="方案 B：正式使用 Git Submodule（网上查到的没试）"></a>方案 B：正式使用 Git Submodule（网上查到的没试）</h3><p>如果你想保留子仓库的 git 版本，可以用子模块，而不是直接 clone 丢进去。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git submodule add https://github.com/xxx/xxx-theme.git themes/anzhiyu</span><br></pre></td></tr></table></figure><p>会自动生成 <code>.gitmodules</code> 文件，记录子仓库地址。</p><p>子模块的坑：</p><ol><li><strong>别人克隆你的完整仓库，必须多执行一步才能拿到子模块文件</strong></li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">git clone 你的仓库地址</span><br><span class="line">git submodule update --init --recursive</span><br></pre></td></tr></table></figure><p>不执行，拉下来的<code>themes/anzhiyu</code>是空文件夹。 2. 更新子模块、提交、切换分支都有额外命令，新手极易踩坑。</p><blockquote><p>Hexo 博客场景<strong>不推荐新手用 submodule</strong>。</p></blockquote><h2 id="避坑指南"><a href="#避坑指南" class="headerlink" title="避坑指南"></a>避坑指南</h2><ol><li>下载第三方项目 / 主题，优先下载 zip 压缩包解压，不要 git clone，从根源避免嵌套 git；</li><li>看到 GitHub 蓝色箭头文件夹，就代表这是子模块，没有存真实文件；</li><li>仅仅删除子文件夹内部<code>.git</code>不够，<strong>一定要执行<code>git rm --cached</code>清理父 git 旧缓存</strong>，否则旧记录还在；</li><li><code>.gitignore</code>不要写<code>themes/*</code>，会直接忽略整个主题目录。</li></ol><h2 id="快速排查命令"><a href="#快速排查命令" class="headerlink" title="快速排查命令"></a>快速排查命令</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#查看文件夹下全部文件，包含隐藏的.git</span><br><span class="line">ls themes/anzhiyu -Force</span><br><span class="line"></span><br><span class="line">#查看当前git状态，看是否存在嵌套仓库标记</span><br><span class="line">git status</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">解决git仓库嵌套的问题</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=2c7f4708-9e61-6682-062c-63e89badc4e3" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>Java学习笔记04</title>
    <link href="https://blog.itrf.cn/posts/83ea/"/>
    <id>https://blog.itrf.cn/posts/83ea/</id>
    <published>2026-06-30T16:00:03.000Z</published>
    <updated>2026-09-04T02:15:21.650Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Java-反射-Reflection"><a href="#Java-反射-Reflection" class="headerlink" title="Java 反射 Reflection"></a>Java 反射 Reflection</h2><h3 id="一、什么是反射"><a href="#一、什么是反射" class="headerlink" title="一、什么是反射"></a>一、什么是反射</h3><p><strong>反射机制：程序运行期间，可以获取类的完整信息、操作对象（构造方法、成员变量、成员方法）。</strong></p><p>正常正向调用：<code>new 对象()</code> → 编译期确定类型</p><p>反射逆向操作：<strong>运行期才解析类信息</strong>，动态创建对象、调用方法。</p><blockquote><p>包：<code>java.lang.reflect.*</code>；入口：<code>Class</code>类</p></blockquote><h3 id="二、反射核心作用"><a href="#二、反射核心作用" class="headerlink" title="二、反射核心作用"></a>二、反射核心作用</h3><ol><li>运行时获取类名、父类、接口、属性、方法、构造器</li><li>运行时动态创建类实例</li><li>运行时调用任意对象的方法、读写成员变量（包括私有 private）</li><li>主流框架底层原理：Spring IoC、MyBatis、注解、JUnit 都大量使用反射</li></ol><h3 id="三、获取-Class-对象的-4-种方式"><a href="#三、获取-Class-对象的-4-种方式" class="headerlink" title="三、获取 Class 对象的 4 种方式"></a>三、获取 Class 对象的 4 种方式</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span>&#123;&#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_">GetClassDemo</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> ClassNotFoundException &#123;</span><br><span class="line">        <span class="comment">//方式1：类名.class 【推荐，不会创建对象】</span></span><br><span class="line">        Class&lt;Student&gt; clazz1 = Student.class;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//方式2：对象.getClass()</span></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">        Class&lt;? <span class="keyword">extends</span> <span class="title class_">Student</span>&gt; clazz2 = s.getClass();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//方式3：Class.forName(&quot;全限定类名&quot;)【框架最常用！】</span></span><br><span class="line">        Class&lt;?&gt; clazz3 = Class.forName(<span class="string">&quot;Student&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//方式4：类加载器</span></span><br><span class="line">        Class&lt;?&gt; clazz4 = ClassLoader.getSystemClassLoader().loadClass(<span class="string">&quot;Student&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>一个类在 JVM 中只会加载一次，4 种方式拿到的是<strong>同一个 Class 对象</strong>。</p></blockquote><h3 id="四、Class-五大核心组件"><a href="#四、Class-五大核心组件" class="headerlink" title="四、Class 五大核心组件"></a>四、Class 五大核心组件</h3><p>通过 Class 可以获取：</p><ol><li><code>Constructor</code>：构造方法</li><li><code>Method</code>：成员方法</li><li><code>Field</code>：成员变量</li><li><code>Annotation</code>：注解</li><li><code>Superclass/Interface</code>：父类、实现接口</li></ol><h3 id="五、全套代码-Demo"><a href="#五、全套代码-Demo" class="headerlink" title="五、全套代码 Demo"></a>五、全套代码 Demo</h3><p>准备实体类</p><figure class="highlight java"><table><tr><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">//私有成员变量</span></span><br><span class="line">    <span class="keyword">private</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><br><span class="line">    <span class="comment">//无参构造</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Student</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    <span class="comment">//有参构造</span></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>&#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">//私有方法</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">privateTest</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">//普通成员方法</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">(String msg)</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;show方法：&quot;</span> + msg);</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_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Student&#123;name=&#x27;&quot;</span> + name + <span class="string">&quot;&#x27;, age=&quot;</span> + age + <span class="string">&quot;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Demo1：反射通过构造器创建对象</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.lang.reflect.Constructor;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReflectConstructorDemo</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">        Class&lt;Student&gt; clazz = Student.class;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//1. 使用无参构造创建对象</span></span><br><span class="line">        Constructor&lt;Student&gt; con1 = clazz.getConstructor();</span><br><span class="line">        <span class="type">Student</span> <span class="variable">s1</span> <span class="operator">=</span> con1.newInstance();</span><br><span class="line">        System.out.println(s1);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//2. 获取有参构造，创建对象</span></span><br><span class="line">        Constructor&lt;Student&gt; con2 = clazz.getConstructor(String.class, <span class="type">int</span>.class);</span><br><span class="line">        <span class="type">Student</span> <span class="variable">s2</span> <span class="operator">=</span> con2.newInstance(<span class="string">&quot;张三&quot;</span>,<span class="number">18</span>);</span><br><span class="line">        System.out.println(s2);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><code>getConstructor()</code> 只能获取<strong>public 构造器</strong></p><p><code>getDeclaredConstructor()</code> 可以获取<strong>所有权限（private/public）构造器</strong></p></blockquote><p>Demo2：反射操作成员变量 Field（突破私有）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.lang.reflect.Field;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReflectFieldDemo</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">        Class&lt;Student&gt; clazz = Student.class;</span><br><span class="line">        <span class="type">Student</span> <span class="variable">student</span> <span class="operator">=</span> clazz.getConstructor().newInstance();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//getField()：只能获取public字段</span></span><br><span class="line">        <span class="type">Field</span> <span class="variable">ageField</span> <span class="operator">=</span> clazz.getField(<span class="string">&quot;age&quot;</span>);</span><br><span class="line">        ageField.set(student, <span class="number">20</span>);</span><br><span class="line">        System.out.println(ageField.get(student));</span><br><span class="line"></span><br><span class="line">        <span class="comment">//getDeclaredField() 获取任意权限字段（private name）</span></span><br><span class="line">        <span class="type">Field</span> <span class="variable">nameField</span> <span class="operator">=</span> clazz.getDeclaredField(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">        nameField.setAccessible(<span class="literal">true</span>); <span class="comment">//暴力反射：关闭权限检查！</span></span><br><span class="line">        nameField.set(student, <span class="string">&quot;李四&quot;</span>);</span><br><span class="line"></span><br><span class="line">        System.out.println(student);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Demo3：反射调用方法 Method</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReflectMethodDemo</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">        Class&lt;Student&gt; clazz = Student.class;</span><br><span class="line">        <span class="type">Student</span> <span class="variable">stu</span> <span class="operator">=</span> clazz.getConstructor().newInstance();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//1. 获取public方法</span></span><br><span class="line">        <span class="type">Method</span> <span class="variable">showMethod</span> <span class="operator">=</span> clazz.getMethod(<span class="string">&quot;show&quot;</span>, String.class);</span><br><span class="line">        <span class="comment">//调用方法：对象，方法参数</span></span><br><span class="line">        showMethod.invoke(stu, <span class="string">&quot;反射调用show&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//2. 获取私有方法</span></span><br><span class="line">        <span class="type">Method</span> <span class="variable">privateMethod</span> <span class="operator">=</span> clazz.getDeclaredMethod(<span class="string">&quot;privateTest&quot;</span>);</span><br><span class="line">        privateMethod.setAccessible(<span class="literal">true</span>); <span class="comment">//开启暴力反射</span></span><br><span class="line">        privateMethod.invoke(stu);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="六、关键-API-区分"><a href="#六、关键-API-区分" class="headerlink" title="六、关键 API 区分"></a>六、关键 API 区分</h3><div class="table-container"><table><thead><tr><th style="text-align:center">方法</th><th style="text-align:center">作用</th><th style="text-align:center">范围</th></tr></thead><tbody><tr><td style="text-align:center">getConstructor / getMethod / getField</td><td style="text-align:center">获取<strong>public</strong></td><td style="text-align:center">只能访问公共成员</td></tr><tr><td style="text-align:center">getDeclaredConstructor / getDeclaredMethod / getDeclaredField</td><td style="text-align:center">获取<strong>所有权限</strong>(public/private/protected)</td><td style="text-align:center">包含私有，但默认不能访问</td></tr></tbody></table></div><p><strong>setAccessible(true)</strong></p><p>俗称：暴力反射；作用：<strong>取消 Java 访问权限检查</strong>，可以读写私有属性、调用私有方法。</p><blockquote><p>安全管理器可以禁止暴力反射。</p></blockquote><h3 id="七、反射优缺点"><a href="#七、反射优缺点" class="headerlink" title="七、反射优缺点"></a>七、反射优缺点</h3><p>优点：</p><ol><li><strong>动态性极高</strong>，框架实现核心；运行期灵活创建对象、调用方法</li><li>解耦，无需编译期硬编码 new 对象</li></ol><p>缺点：</p><ol><li><strong>性能较差</strong>：绕过编译优化，需要解析 Class 元数据</li><li>破坏封装：暴力反射可以操作私有成员，违背面向对象封装</li><li>代码可读性差、复杂；编译期无法校验，异常只能运行时抛出</li></ol><h3 id="八、常见问题"><a href="#八、常见问题" class="headerlink" title="八、常见问题"></a>八、常见问题</h3><p>Q1：Class.forName () 作用？</p><ol><li>加载类，执行静态代码块；</li><li>JDBC 经典用法：<code>Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;)</code>，驱动类自动注册。</li></ol><p>Q2：newInstance () 过时了怎么办？</p><p>JDK9 之后 <code>clazz.newInstance()</code> 被废弃；推荐<strong>获取无参 Constructor 再调用 newInstance ()</strong></p><p>Q3：反射和注解怎么配合？</p><p>反射可以读取类 / 方法上注解信息，很多框架（Spring）依靠【反射 + 注解】实现功能。</p><p>Q4：什么是暴力反射？</p><p><code>setAccessible(true)</code>，跳过权限校验访问私有成员。</p><h3 id="九、反射典型应用场景"><a href="#九、反射典型应用场景" class="headerlink" title="九、反射典型应用场景"></a>九、反射典型应用场景</h3><ol><li>JDBC 加载数据库驱动</li><li>Spring：IOC 容器反射创建 Bean 对象、依赖注入</li><li>MyBatis：反射实例化实体类，封装数据库结果集</li><li>JUnit：反射执行 @Test 标记的测试方法</li><li>通用工具：对象属性拷贝</li></ol><h2 id="JDBC-全套知识点"><a href="#JDBC-全套知识点" class="headerlink" title="JDBC 全套知识点"></a>JDBC 全套知识点</h2><h3 id="一、JDBC-概述"><a href="#一、JDBC-概述" class="headerlink" title="一、JDBC 概述"></a>一、JDBC 概述</h3><p>定义</p><p><strong>JDBC（Java Database Connectivity）</strong>：Java 访问数据库的<strong>统一规范接口</strong>，定义了一套访问数据库的标准 API。</p><ul><li>接口：<code>java.sql.*</code>、<code>javax.sql.*</code>，由 JDK 提供</li><li>实现：各大数据库厂商提供<strong>数据库驱动</strong>（MySQL、Oracle、SQLServer 等），实现 JDBC 接口</li></ul><p>核心优势</p><ol><li>一套 Java 代码，更换数据库仅需更换驱动与连接参数，无需大量改业务代码</li><li>统一屏蔽不同数据库底层差异</li></ol><p>核心组件</p><ol><li><strong>Driver 驱动</strong>：数据库厂商实现，桥接 Java 与数据库</li><li><strong>DriverManager</strong>：驱动管理器，注册驱动、获取数据库连接</li><li><strong>Connection</strong>：数据库连接对象（会话）</li><li><strong>Statement / PreparedStatement / CallableStatement</strong>：执行 SQL 语句</li><li><strong>ResultSet</strong>：查询结果集，封装查询返回的表格数据</li></ol><h3 id="二、JDBC-完整执行-6-大固定步骤"><a href="#二、JDBC-完整执行-6-大固定步骤" class="headerlink" title="二、JDBC 完整执行 6 大固定步骤"></a>二、JDBC 完整执行 6 大固定步骤</h3><ol><li>导入数据库驱动依赖</li><li>注册数据库驱动</li><li>获取数据库连接 Connection</li><li>获取 SQL 执行对象 Statement/PreparedStatement</li><li>执行 SQL，处理结果集 ResultSet</li><li>关闭所有资源（ResultSet、Statement、Connection）</li></ol><h3 id="三、环境准备（MySQL-为例）"><a href="#三、环境准备（MySQL-为例）" class="headerlink" title="三、环境准备（MySQL 为例）"></a>三、环境准备（MySQL 为例）</h3><p>Maven 依赖（MySQL8.0+）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">&lt;!-- MySQL驱动 <span class="number">8.</span>x --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;mysql&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;<span class="number">8.0</span><span class="number">.33</span>&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><blockquote><p>MySQL5.x 驱动类：<code>com.mysql.jdbc.Driver</code></p><p>MySQL8.x 驱动类：<code>com.mysql.cj.jdbc.Driver</code></p></blockquote><p>连接 URL 模板</p><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">jdbc</span>:<span class="string">mysql://ip:端口/数据库名?参数</span></span><br><span class="line"><span class="attr">常用参数：</span></span><br><span class="line"><span class="attr">useSSL</span>=<span class="string">false 关闭SSL</span></span><br><span class="line"><span class="attr">serverTimezone</span>=<span class="string">Asia/Shanghai 时区（8.0必须加，否则时区报错）</span></span><br><span class="line"><span class="attr">useUnicode</span>=<span class="string">true&amp;characterEncoding=utf8 指定编码</span></span><br></pre></td></tr></table></figure><p>示例：</p><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">jdbc</span>:<span class="string">mysql://127.0.0.1:3306/testdb?serverTimezone=Asia/Shanghai&amp;useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8</span></span><br></pre></td></tr></table></figure><h3 id="四、原生-JDBC-代码-Demo"><a href="#四、原生-JDBC-代码-Demo" class="headerlink" title="四、原生 JDBC 代码 Demo"></a>四、原生 JDBC 代码 Demo</h3><p>Demo1：Statement（不推荐，存在 SQL 注入）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.sql.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdbcStatementDemo</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">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">Statement</span> <span class="variable">stmt</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">ResultSet</span> <span class="variable">rs</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//1.注册驱动（MySQL8.0驱动内部自动注册，可省略Class.forName）</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 class="comment">//2.获取连接</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://127.0.0.1:3306/testdb?serverTimezone=Asia/Shanghai&quot;</span>;</span><br><span class="line">            <span class="type">String</span> <span class="variable">user</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">pwd</span> <span class="operator">=</span> <span class="string">&quot;123456&quot;</span>;</span><br><span class="line">            conn = DriverManager.getConnection(url, user, pwd);</span><br><span class="line">            <span class="comment">//3.创建执行对象</span></span><br><span class="line">            stmt = conn.createStatement();</span><br><span class="line">            <span class="comment">//4.执行查询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 id,name,age from user&quot;</span>;</span><br><span class="line">            rs = stmt.executeQuery(sql);</span><br><span class="line">            <span class="comment">//5.遍历结果集</span></span><br><span class="line">            <span class="keyword">while</span> (rs.next()) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">id</span> <span class="operator">=</span> rs.getInt(<span class="string">&quot;id&quot;</span>);</span><br><span class="line">                <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> rs.getString(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">                <span class="type">int</span> <span class="variable">age</span> <span class="operator">=</span> rs.getInt(<span class="string">&quot;age&quot;</span>);</span><br><span class="line">                System.out.println(id + <span class="string">&quot; &quot;</span> + name + <span class="string">&quot; &quot;</span> + age);</span><br><span class="line">            &#125;</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 class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">//6.关闭资源，逆序关闭</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (rs != <span class="literal">null</span>) rs.close();</span><br><span class="line">                <span class="keyword">if</span> (stmt != <span class="literal">null</span>) stmt.close();</span><br><span class="line">                <span class="keyword">if</span> (conn != <span class="literal">null</span>) conn.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (SQLException 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">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Statement 致命缺点：SQL 注入</p><p>拼接字符串 SQL，恶意输入可篡改 SQL 逻辑，<strong>项目禁止使用 Statement</strong>。</p><p>Demo2：PreparedStatement（标准推荐，预编译防注入）</p><p>原理：SQL 模板预编译，使用<code>?</code>占位符，参数单独赋值，SQL 结构固定，杜绝注入。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.sql.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdbcPreparedDemo</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">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">PreparedStatement</span> <span class="variable">pstmt</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">ResultSet</span> <span class="variable">rs</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Class.forName(<span class="string">&quot;com.mysql.cj.jdbc.Driver&quot;</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://127.0.0.1:3306/testdb?serverTimezone=Asia/Shanghai&quot;</span>;</span><br><span class="line">            conn = DriverManager.getConnection(url, <span class="string">&quot;root&quot;</span>, <span class="string">&quot;123456&quot;</span>);</span><br><span class="line">            <span class="comment">//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 name = ? and age = ?&quot;</span>;</span><br><span class="line">            pstmt = conn.prepareStatement(sql);</span><br><span class="line">            <span class="comment">//给占位符赋值：下标从1开始</span></span><br><span class="line">            pstmt.setString(<span class="number">1</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line">            pstmt.setInt(<span class="number">2</span>, <span class="number">18</span>);</span><br><span class="line">            <span class="comment">//执行查询，无需传入SQL</span></span><br><span class="line">            rs = pstmt.executeQuery();</span><br><span class="line">            <span class="keyword">while</span> (rs.next()) &#123;</span><br><span class="line">                System.out.println(rs.getInt(<span class="string">&quot;id&quot;</span>) + <span class="string">&quot; &quot;</span> + rs.getString(<span class="string">&quot;name&quot;</span>));</span><br><span class="line">            &#125;</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 class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (rs != <span class="literal">null</span>) rs.close();</span><br><span class="line">                <span class="keyword">if</span> (pstmt != <span class="literal">null</span>) pstmt.close();</span><br><span class="line">                <span class="keyword">if</span> (conn != <span class="literal">null</span>) conn.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (SQLException 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">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Demo3：增删改（executeUpdate）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//新增、修改、删除统一使用 executeUpdate()，返回受影响行数</span></span><br><span class="line"><span class="type">String</span> <span class="variable">insertSql</span> <span class="operator">=</span> <span class="string">&quot;insert into user(name,age) values(?,?)&quot;</span>;</span><br><span class="line">pstmt = conn.prepareStatement(insertSql);</span><br><span class="line">pstmt.setString(<span class="number">1</span>, <span class="string">&quot;李四&quot;</span>);</span><br><span class="line">pstmt.setInt(<span class="number">2</span>, <span class="number">22</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> pstmt.executeUpdate();</span><br><span class="line">System.out.println(<span class="string">&quot;影响行数：&quot;</span>+rows);</span><br></pre></td></tr></table></figure><h3 id="五、三大执行对象对比"><a href="#五、三大执行对象对比" class="headerlink" title="五、三大执行对象对比"></a>五、三大执行对象对比</h3><div class="table-container"><table><thead><tr><th style="text-align:center">对象</th><th style="text-align:center">特点</th><th style="text-align:center">安全性</th><th style="text-align:center">适用场景</th></tr></thead><tbody><tr><td style="text-align:center">Statement</td><td style="text-align:center">字符串拼接 SQL，每次重新编译</td><td style="text-align:center">极易 SQL 注入</td><td style="text-align:center">几乎淘汰</td></tr><tr><td style="text-align:center">PreparedStatement</td><td style="text-align:center">预编译，? 占位符，可复用 SQL</td><td style="text-align:center">防注入，安全</td><td style="text-align:center">日常 CRUD 首选</td></tr><tr><td style="text-align:center">CallableStatement</td><td style="text-align:center">专门调用存储过程</td><td style="text-align:center">安全</td><td style="text-align:center">数据库存储过程调用</td></tr></tbody></table></div><h3 id="六、核心-API-详解"><a href="#六、核心-API-详解" class="headerlink" title="六、核心 API 详解"></a>六、核心 API 详解</h3><ol><li>Connection</li></ol><ul><li><code>createStatement()</code>：创建 Statement</li><li><code>prepareStatement(sql)</code>：创建预编译对象</li><li><code>setAutoCommit(false)</code>：关闭自动提交，开启事务</li><li><code>commit()</code>：事务提交</li><li><code>rollback()</code>：事务回滚</li></ul><ol><li>PreparedStatement 常用方法</li></ol><ul><li><code>setXxx(下标,值)</code>：给？赋值，下标从<strong>1</strong>开始</li><li><code>executeQuery()</code>：执行查询，返回 ResultSet</li><li><code>executeUpdate()</code>：执行增删改，返回 int（影响行数）</li></ul><ol><li>ResultSet 结果集</li></ol><ul><li><code>next()</code>：指针下移，有数据返回 true，末尾 false</li><li><code>getXxx(列名/列下标)</code>：获取值，列下标从 1 开始</li><li><code>beforeFirst()</code>：指针回到最开头</li></ul><h3 id="七、JDBC-事务管理（核心考点）"><a href="#七、JDBC-事务管理（核心考点）" class="headerlink" title="七、JDBC 事务管理（核心考点）"></a>七、JDBC 事务管理（核心考点）</h3><p>JDBC 默认<strong>自动提交</strong>：单条 SQL 执行完成立刻提交。</p><p>多 SQL 原子操作必须手动事务：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span>&#123;</span><br><span class="line">    conn.setAutoCommit(<span class="literal">false</span>); <span class="comment">//关闭自动提交，开启事务</span></span><br><span class="line">    <span class="comment">//多条DML操作</span></span><br><span class="line">    pstmt1.executeUpdate();</span><br><span class="line">    pstmt2.executeUpdate();</span><br><span class="line">    conn.commit(); <span class="comment">//全部正常，提交事务</span></span><br><span class="line">&#125;<span class="keyword">catch</span>(Exception e)&#123;</span><br><span class="line">    conn.rollback(); <span class="comment">//异常，回滚</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="八、工具类封装（开发常用，简化重复代码）"><a href="#八、工具类封装（开发常用，简化重复代码）" class="headerlink" title="八、工具类封装（开发常用，简化重复代码）"></a>八、工具类封装（开发常用，简化重复代码）</h3><p>把连接、关闭资源抽取为工具类，配合 properties 配置文件</p><p>db.properties</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">driver=com.mysql.cj.jdbc.Driver</span><br><span class="line">url=jdbc:mysql:<span class="comment">//127.0.0.1:3306/testdb?serverTimezone=Asia/Shanghai</span></span><br><span class="line">user=root</span><br><span class="line">password=<span class="number">123456</span></span><br></pre></td></tr></table></figure><p>JdbcUtil.java</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.InputStream;</span><br><span class="line"><span class="keyword">import</span> java.sql.*;</span><br><span class="line"><span class="keyword">import</span> java.util.Properties;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JdbcUtil</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Properties</span> <span class="variable">prop</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line">    <span class="comment">//静态加载配置文件</span></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        <span class="keyword">try</span>(<span class="type">InputStream</span> <span class="variable">is</span> <span class="operator">=</span> JdbcUtil.class.getResourceAsStream(<span class="string">&quot;/db.properties&quot;</span>))&#123;</span><br><span class="line">            prop.load(is);</span><br><span class="line">            Class.forName(prop.getProperty(<span class="string">&quot;driver&quot;</span>));</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 class="comment">//获取连接</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Connection <span class="title function_">getConnection</span><span class="params">()</span> <span class="keyword">throws</span> SQLException &#123;</span><br><span class="line">        <span class="keyword">return</span> DriverManager.getConnection(</span><br><span class="line">                prop.getProperty(<span class="string">&quot;url&quot;</span>),</span><br><span class="line">                prop.getProperty(<span class="string">&quot;user&quot;</span>),</span><br><span class="line">                prop.getProperty(<span class="string">&quot;password&quot;</span>)</span><br><span class="line">        );</span><br><span class="line">    &#125;</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_">close</span><span class="params">(Connection conn, PreparedStatement pstmt, ResultSet rs)</span>&#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="keyword">if</span>(rs != <span class="literal">null</span>) rs.close();</span><br><span class="line">            <span class="keyword">if</span>(pstmt != <span class="literal">null</span>) pstmt.close();</span><br><span class="line">            <span class="keyword">if</span>(conn != <span class="literal">null</span>) conn.close();</span><br><span class="line">        &#125;<span class="keyword">catch</span> (SQLException 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 class="comment">//增删改没有ResultSet，重载方法</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_">close</span><span class="params">(Connection conn, PreparedStatement pstmt)</span>&#123;</span><br><span class="line">        close(conn,pstmt,<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="九、常见问题与优化"><a href="#九、常见问题与优化" class="headerlink" title="九、常见问题与优化"></a>九、常见问题与优化</h3><ol><li>SQL 注入原因与解决方案</li></ol><p>原因：Statement 字符串拼接；解决：强制使用 PreparedStatement 占位符预编译。</p><ol><li><p>原生 JDBC 痛点</p></li><li><p>频繁创建销毁连接，性能极差</p></li><li><p>代码冗余，大量重复模板代码</p><p> 解决方案：</p><p>数据库连接池（Druid、HikariCP）、MyBatis/MyBatis-Plus、JPA</p></li><li><p>驱动自动注册原理</p></li></ol><p>JDBC4.0+ 无需手动<code>Class.forName()</code>：</p><p>驱动包<code>META-INF/services/java.sql.Driver</code>文件记录驱动类，JVM 自动加载。</p><h3 id="十、高频知识点"><a href="#十、高频知识点" class="headerlink" title="十、高频知识点"></a>十、高频知识点</h3><ol><li><p><strong>PreparedStatement 为什么防注入？</strong></p><p>SQL 结构提前预编译固化，? 仅做参数，不会解析为 SQL 关键字，恶意输入只会当成普通字符串。</p></li><li><p><strong>占位符下标为什么从 1 开始？</strong></p><p>JDBC 规范定义，和数据库列下标保持一致。</p></li><li><p><strong>事务四大特性 ACID？</strong></p><p>原子性、一致性、隔离性、持久性。</p></li><li><p><strong>连接池作用？</strong></p><p>复用数据库连接，减少创建销毁开销，控制最大连接数防止数据库崩溃。</p></li></ol><h2 id="Java-正则表达式"><a href="#Java-正则表达式" class="headerlink" title="Java 正则表达式"></a>Java 正则表达式</h2><h3 id="一、基础介绍"><a href="#一、基础介绍" class="headerlink" title="一、基础介绍"></a>一、基础介绍</h3><ol><li>定义</li></ol><p>正则表达式（Regex）：一套<strong>字符串匹配规则</strong>，用来检索、替换、截取、校验文本。</p><p>Java 正则核心包：<code>java.util.regex</code></p><p>核心两个类：</p><ul><li><code>Pattern</code>：正则表达式编译对象，封装匹配规则</li><li><code>Matcher</code>：匹配器，执行匹配、查找、分组提取</li></ul><ol><li>使用场景</li></ol><p>手机号 / 邮箱 / 身份证校验、日志提取、敏感词过滤、文本截取、字符串替换。</p><hr><h3 id="二、正则语法速查表"><a href="#二、正则语法速查表" class="headerlink" title="二、正则语法速查表"></a>二、正则语法速查表</h3><ol><li>普通元字符（单字符匹配）</li></ol><div class="table-container"><table><thead><tr><th style="text-align:center">符号</th><th style="text-align:center">含义</th></tr></thead><tbody><tr><td style="text-align:center"><code>.</code></td><td style="text-align:center">匹配<strong>任意单个字符</strong>（换行<code>\n</code>除外）</td></tr><tr><td style="text-align:center"><code>\d</code></td><td style="text-align:center">数字 [0-9]</td></tr><tr><td style="text-align:center"><code>\D</code></td><td style="text-align:center">非数字</td></tr><tr><td style="text-align:center"><code>\s</code></td><td style="text-align:center">空白字符（空格、制表符、换行）</td></tr><tr><td style="text-align:center"><code>\S</code></td><td style="text-align:center">非空白字符</td></tr><tr><td style="text-align:center"><code>\w</code></td><td style="text-align:center">单词字符 [a-z,A-Z,0-9,_]</td></tr><tr><td style="text-align:center"><code>\W</code></td><td style="text-align:center">非单词字符</td></tr><tr><td style="text-align:center"><code>[]</code></td><td style="text-align:center">字符集，匹配括号内任意一个字符，<code>[abc]</code>匹配 a/b/c</td></tr><tr><td style="text-align:center"><code>[^ ]</code></td><td style="text-align:center">取反，<code>[^0-9]</code>匹配非数字</td></tr></tbody></table></div><ol><li>量词（限定前面字符出现次数）</li></ol><div class="table-container"><table><thead><tr><th style="text-align:center">符号</th><th style="text-align:center">含义</th></tr></thead><tbody><tr><td style="text-align:center"><code>?</code></td><td style="text-align:center">0 次 或 1 次</td></tr><tr><td style="text-align:center"><code>+</code></td><td style="text-align:center">至少 1 次（1~ 无数）</td></tr><tr><td style="text-align:center"><code>*</code></td><td style="text-align:center">0 次及以上</td></tr><tr><td style="text-align:center"><code>{n}</code></td><td style="text-align:center">固定 n 次</td></tr><tr><td style="text-align:center"><code>{n,}</code></td><td style="text-align:center">最少 n 次</td></tr><tr><td style="text-align:center"><code>{n,m}</code></td><td style="text-align:center">n~m 次</td></tr></tbody></table></div><ol><li>边界匹配</li></ol><div class="table-container"><table><thead><tr><th style="text-align:center">符号</th><th style="text-align:center">含义</th></tr></thead><tbody><tr><td style="text-align:center"><code>^</code></td><td style="text-align:center">字符串开头</td></tr><tr><td style="text-align:center"><code>$</code></td><td style="text-align:center">字符串结尾</td></tr><tr><td style="text-align:center"><code>\b</code></td><td style="text-align:center">单词边界（空格、标点、首尾）</td></tr></tbody></table></div><ol><li>分组与或</li></ol><ul><li><code>()</code>：分组，可单独捕获内容、反向引用</li><li><code>|</code>：或，<code>a|b</code>匹配 a 或者 b</li><li><code>(?:)</code>：非捕获分组，只分组不提取</li></ul><ol><li>转义说明（Java 独有重点）</li></ol><p>正则里<code>\</code>是转义符，<strong>Java 字符串中<code>\</code>需要写成<code>\\</code></strong></p><p>例：正则<code>\d</code> → Java 代码必须写成<code>&quot;\\d&quot;</code></p><ol><li>贪婪 / 非贪婪</li></ol><ul><li>默认：贪婪匹配，尽可能多匹配；</li><li><code>?</code>放在量词后开启非贪婪（懒惰匹配）：<code>.*?</code>尽可能少匹配。</li></ul><hr><h3 id="三、Java-三种常用使用方式"><a href="#三、Java-三种常用使用方式" class="headerlink" title="三、Java 三种常用使用方式"></a>三、Java 三种常用使用方式</h3><p>方式 1：String 自带简便方法（日常快速校验）</p><ol><li><code>boolean matches(String regex)</code>：<strong>全串严格匹配</strong>（等价于<code>^正则$</code>）</li><li><code>String replaceAll(String regex,String replacement)</code>：正则全局替换</li><li><code>String[] split(String regex)</code>：按正则分割字符串</li></ol><p>Demo：字符串自带方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StringRegexDemo</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">//1.手机号校验 1开头，第二位3-9，后面9位数字</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">phoneReg</span> <span class="operator">=</span> <span class="string">&quot;^1[3-9]\\d&#123;9&#125;$&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">phone</span> <span class="operator">=</span> <span class="string">&quot;13812345678&quot;</span>;</span><br><span class="line">        System.out.println(phone.matches(phoneReg)); <span class="comment">//true</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">//2.替换所有数字为空</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;a1b2c3d4&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">res</span> <span class="operator">=</span> str.replaceAll(<span class="string">&quot;\\d&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">        System.out.println(res); <span class="comment">//abcd</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">//3.按非字母分割</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;java,python;c++ go&quot;</span>;</span><br><span class="line">        String[] arr = s.split(<span class="string">&quot;[,; ]&quot;</span>);</span><br><span class="line">        <span class="keyword">for</span> (String item : arr) &#123;</span><br><span class="line">            System.out.print(item + <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><p>方式 2：Pattern + Matcher 精准查找、分组提取（最常用）</p><p>核心方法：</p><ul><li><code>Pattern.compile(reg)</code>：编译正则</li><li><code>matcher.find()</code>：查找下一个匹配片段，找到返回 true</li><li><code>matcher.group()</code>：获取匹配到的完整内容</li><li><code>matcher.group(n)</code>：获取第 n 个分组内容（括号分组）</li></ul><p>Demo1：提取所有数字</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.regex.Matcher;</span><br><span class="line"><span class="keyword">import</span> java.util.regex.Pattern;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PatternMatcherDemo</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">String</span> <span class="variable">content</span> <span class="operator">=</span> <span class="string">&quot;订单1001，金额99.9，编号20260802&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">reg</span> <span class="operator">=</span> <span class="string">&quot;\\d+&quot;</span>;</span><br><span class="line">        <span class="type">Pattern</span> <span class="variable">pattern</span> <span class="operator">=</span> Pattern.compile(reg);</span><br><span class="line">        <span class="type">Matcher</span> <span class="variable">matcher</span> <span class="operator">=</span> pattern.matcher(content);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (matcher.find()) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;匹配：&quot;</span> + matcher.group());</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>Demo2：分组捕获（提取邮箱用户名、域名）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupDemo</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">String</span> <span class="variable">email</span> <span class="operator">=</span> <span class="string">&quot;zhangsan@qq.com&quot;</span>;</span><br><span class="line">        <span class="comment">//分组1：用户名  分组2：域名</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">reg</span> <span class="operator">=</span> <span class="string">&quot;^(\\w+)@(.+)$&quot;</span>;</span><br><span class="line">        <span class="type">Pattern</span> <span class="variable">p</span> <span class="operator">=</span> Pattern.compile(reg);</span><br><span class="line">        <span class="type">Matcher</span> <span class="variable">m</span> <span class="operator">=</span> p.matcher(email);</span><br><span class="line">        <span class="keyword">if</span> (m.matches()) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;用户名：&quot;</span> + m.group(<span class="number">1</span>));</span><br><span class="line">            System.out.println(<span class="string">&quot;域名：&quot;</span> + m.group(<span class="number">2</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>方式 3：替换分组内容</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//隐藏手机号中间4位</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_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">phone</span> <span class="operator">=</span> <span class="string">&quot;13812345678&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">res</span> <span class="operator">=</span> phone.replaceAll(<span class="string">&quot;(\\d&#123;3&#125;)\\d&#123;4&#125;(\\d&#123;4&#125;)&quot;</span>, <span class="string">&quot;$1****$2&quot;</span>);</span><br><span class="line">    System.out.println(res); <span class="comment">//138****5678</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><code>$1</code>代表第 1 分组内容，<code>$2</code>代表第 2 分组内容</p></blockquote><hr><h3 id="四、高频实用正则模板（可直接复制使用）"><a href="#四、高频实用正则模板（可直接复制使用）" class="headerlink" title="四、高频实用正则模板（可直接复制使用）"></a>四、高频实用正则模板（可直接复制使用）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//1.手机号</span></span><br><span class="line"><span class="type">String</span> <span class="variable">phone</span> <span class="operator">=</span> <span class="string">&quot;^1[3-9]\\d&#123;9&#125;$&quot;</span>;</span><br><span class="line"><span class="comment">//2.邮箱</span></span><br><span class="line"><span class="type">String</span> <span class="variable">email</span> <span class="operator">=</span> <span class="string">&quot;^\\w+@[a-zA-Z0-9]+(\\.[a-zA-Z]&#123;2,&#125;)+$&quot;</span>;</span><br><span class="line"><span class="comment">//3.身份证18位</span></span><br><span class="line"><span class="type">String</span> <span class="variable">idCard</span> <span class="operator">=</span> <span class="string">&quot;^[1-9]\\d&#123;5&#125;(18|19|20)\\d&#123;2&#125;(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d&#123;3&#125;[\\dXx]$&quot;</span>;</span><br><span class="line"><span class="comment">//4.纯数字</span></span><br><span class="line"><span class="type">String</span> <span class="variable">number</span> <span class="operator">=</span> <span class="string">&quot;^\\d+$&quot;</span>;</span><br><span class="line"><span class="comment">//5.6-16位密码：字母+数字，至少包含1个数字1个字母</span></span><br><span class="line"><span class="type">String</span> <span class="variable">pwd</span> <span class="operator">=</span> <span class="string">&quot;^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z]&#123;6,16&#125;$&quot;</span>;</span><br></pre></td></tr></table></figure><hr><h3 id="五、贪婪匹配与非贪婪匹配案例"><a href="#五、贪婪匹配与非贪婪匹配案例" class="headerlink" title="五、贪婪匹配与非贪婪匹配案例"></a>五、贪婪匹配与非贪婪匹配案例</h3><figure class="highlight java"><table><tr><td class="code"><pre><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">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;&lt;div&gt;内容1&lt;/div&gt;&lt;div&gt;内容2&lt;/div&gt;&quot;</span>;</span><br><span class="line">    <span class="comment">//贪婪匹配 .* 一次性匹配到最后</span></span><br><span class="line">    <span class="type">Matcher</span> <span class="variable">m1</span> <span class="operator">=</span> Pattern.compile(<span class="string">&quot;&lt;div&gt;.*&lt;/div&gt;&quot;</span>).matcher(str);</span><br><span class="line">    m1.find();</span><br><span class="line">    System.out.println(m1.group()); <span class="comment">//整串全部匹配</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//非贪婪 .*? 最短匹配</span></span><br><span class="line">    <span class="type">Matcher</span> <span class="variable">m2</span> <span class="operator">=</span> Pattern.compile(<span class="string">&quot;&lt;div&gt;.*?&lt;/div&gt;&quot;</span>).matcher(str);</span><br><span class="line">    <span class="keyword">while</span> (m2.find())&#123;</span><br><span class="line">        System.out.println(m2.group());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="六、重要知识点"><a href="#六、重要知识点" class="headerlink" title="六、重要知识点"></a>六、重要知识点</h3><ol><li><strong>matches () 和 find () 区别</strong></li></ol><ul><li><code>matches()</code>：<strong>整串完全匹配</strong>，默认带<code>^$</code>；</li><li><code>find()</code>：片段查找，只要字符串存在匹配片段就返回 true。</li></ul><ol><li>正则性能问题</li></ol><ul><li>复杂正则易出现<strong>回溯爆炸</strong>，大量文本下卡顿；</li><li>能用普通字符串方法就不用复杂正则。</li></ul><ol><li>分组编号规则</li></ol><p>从左到右，左括号<code>(</code>顺序依次为 1、2、3；group (0) 永远代表完整匹配内容。</p><ol><li>非捕获分组<code>(?:xxx)</code></li></ol><p>只用来分组限定范围，不会占用分组编号，适合不需要提取、只做规则划分的场景。</p><h2 id="Lambda表达式"><a href="#Lambda表达式" class="headerlink" title="Lambda表达式"></a>Lambda表达式</h2><p>Lambda表达式的基础语法：Java8中引入了一个新的操作符”-&gt;“ 该操作符称为箭头操作符或Lambda操作符</p><h3 id="基础介绍"><a href="#基础介绍" class="headerlink" title="基础介绍"></a>基础介绍</h3><p>概念</p><p>Lambda 表达式是<strong>Java 8 新增</strong>的语法，本质是<strong>简洁的匿名函数</strong>，用来简化<strong>函数式接口</strong>的实现，替代臃肿的匿名内部类，把代码当成参数传递。</p><blockquote><p>核心定位：<strong>行为参数化</strong>，把一段逻辑当做方法入参。</p></blockquote><p>作用</p><ol><li><p>简化匿名内部类写法，代码更精简；</p></li><li><p>配合流式编程<code>Stream</code>，实现集合便捷遍历、筛选、排序；</p></li><li><p>用于线程、回调、Comparator 比较器等场景。</p></li><li><p>语法格式</p></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">(参数列表) -&gt; &#123;方法体&#125;</span><br></pre></td></tr></table></figure><ul><li><code>-&gt;</code>：Lambda 运算符，分为左侧参数、右侧执行体</li></ul><p>四种简写规则（重点）</p><ol><li><strong>参数只有 1 个</strong>：小括号<code>()</code>可以省略</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">s -&gt; System.out.println(s);</span><br></pre></td></tr></table></figure><ol><li><strong>无参数</strong>：必须保留空括号<code>()</code></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">() -&gt; System.out.println(<span class="string">&quot;无参&quot;</span>);</span><br></pre></td></tr></table></figure><ol><li><strong>参数类型可省略</strong>：编译器自动类型推断，无需声明参数类型</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 简写</span></span><br><span class="line">(a,b)-&gt;a+b;</span><br><span class="line"><span class="comment">// 完整写法</span></span><br><span class="line">(Integer a,Integer b)-&gt;a+b;</span><br></pre></td></tr></table></figure><ol><li><strong>方法体只有一行代码</strong>：大括号<code>{}</code>、<code>return</code>、分号可全部省略</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 单行有返回值，省略&#123;&#125;和return</span></span><br><span class="line">(a,b)-&gt;a+b;</span><br><span class="line"><span class="comment">// 多行代码必须加&#123;&#125;，return不能省略</span></span><br><span class="line">(a,b)-&gt;&#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> a+b;</span><br><span class="line">    <span class="keyword">return</span> sum*<span class="number">2</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Lambda-使用前提：函数式接口"><a href="#Lambda-使用前提：函数式接口" class="headerlink" title="Lambda 使用前提：函数式接口"></a>Lambda 使用前提：函数式接口</h3><ol><li>定义</li></ol><p><strong>函数式接口</strong>：<strong>有且仅有一个抽象方法</strong>的接口，可以有默认方法、静态方法、Object 重写方法。 Lambda 只能绑定函数式接口，用来实现那唯一的抽象方法。</p><ol><li>注解标识</li></ol><p><code>@FunctionalInterface</code>：强制校验当前接口是否为函数式接口，编译报错提醒，推荐所有函数式接口都加。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@FunctionalInterface</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">MyFunc</span>&#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span>; <span class="comment">// 唯一抽象方法</span></span><br><span class="line">    <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">hello</span><span class="params">()</span>&#123;&#125; <span class="comment">// 默认方法允许存在</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">staticFunc</span><span class="params">()</span>&#123;&#125; <span class="comment">// 静态方法允许存在</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    String <span class="title function_">toString</span><span class="params">()</span>; <span class="comment">// Object方法不计入抽象方法</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>Java 内置四大核心函数式接口（高频使用）</li></ol><div class="table-container"><table><thead><tr><th>接口</th><th>抽象方法</th><th>作用</th><th>适用场景</th></tr></thead><tbody><tr><td><code>Consumer&lt;T&gt;</code>消费型</td><td><code>void accept(T t)</code></td><td>接收参数，无返回值</td><td>遍历、打印、修改对象</td></tr><tr><td><code>Supplier&lt;T&gt;</code>供给型</td><td><code>T get()</code></td><td>无参数，返回对象</td><td>生成对象、随机数</td></tr><tr><td><code>Predicate&lt;T&gt;</code>断定型</td><td><code>boolean test(T t)</code></td><td>入参，返回布尔值</td><td>集合筛选、条件判断</td></tr><tr><td><code>Function&lt;T,R&gt;</code>函数型</td><td><code>R apply(T t)</code></td><td>T 入参，R 返回值</td><td>类型转换、数据处理</td></tr></tbody></table></div><p>示例</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Consumer 消费</span></span><br><span class="line">Consumer&lt;String&gt; con = s-&gt;System.out.println(s);</span><br><span class="line">con.accept(<span class="string">&quot;测试&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Supplier 供给</span></span><br><span class="line">Supplier&lt;Integer&gt; sup = ()-&gt;<span class="keyword">new</span> <span class="title class_">Random</span>().nextInt(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Predicate 判断</span></span><br><span class="line">Predicate&lt;Integer&gt; pre = n-&gt;n&gt;<span class="number">5</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Function转换</span></span><br><span class="line">Function&lt;String,Integer&gt; fun = str-&gt;Integer.parseInt(str);</span><br></pre></td></tr></table></figure><h3 id="Lambda-三大引用（方法引用、构造器引用、数组引用）"><a href="#Lambda-三大引用（方法引用、构造器引用、数组引用）" class="headerlink" title="Lambda 三大引用（方法引用、构造器引用、数组引用）"></a>Lambda 三大引用（方法引用、构造器引用、数组引用）</h3><p>属于 Lambda 进一步简写，<strong>当 Lambda 只是调用已有方法时</strong>使用，格式更简洁。</p><p>方法引用 3 种形式</p><ol><li><strong>对象：：实例方法</strong>：Lambda 调用某个对象的普通成员方法</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Consumer&lt;String&gt; c = str-&gt;System.out.println(str);</span><br><span class="line"><span class="comment">// 简写</span></span><br><span class="line">Consumer&lt;String&gt; c = System.out::println;</span><br></pre></td></tr></table></figure><ol><li><strong>类：：静态方法</strong>：调用类静态方法</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Comparator&lt;Integer&gt; com = (a,b)-&gt;Integer.compare(a,b);</span><br><span class="line">Comparator&lt;Integer&gt; com = Integer::compare;</span><br></pre></td></tr></table></figure><ol><li><strong>类：：实例方法</strong>：参数 1 作为调用者，参数 2 作为方法入参</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BiPredicate&lt;String,String&gt; bp = (s1,s2)-&gt;s1.equals(s2);</span><br><span class="line">BiPredicate&lt;String,String&gt; bp = String::equals;</span><br></pre></td></tr></table></figure><p>构造器引用</p><p>格式：<code>类名::new</code>，适配 Supplier/Function</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Supplier&lt;User&gt; sup = ()-&gt;<span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">Supplier&lt;User&gt; sup = User::<span class="keyword">new</span>;</span><br><span class="line"></span><br><span class="line">Function&lt;String,User&gt; fun = name-&gt;<span class="keyword">new</span> <span class="title class_">User</span>(name);</span><br><span class="line">Function&lt;String,User&gt; fun = User::<span class="keyword">new</span>;</span><br></pre></td></tr></table></figure><p>数组引用</p><p>格式：<code>类型[]::new</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Function&lt;Integer,String[]&gt; fun = len-&gt;<span class="keyword">new</span> <span class="title class_">String</span>[len];</span><br><span class="line">Function&lt;Integer,String[]&gt; fun = String[]::<span class="keyword">new</span>;</span><br></pre></td></tr></table></figure><h3 id="Lambda-变量作用域"><a href="#Lambda-变量作用域" class="headerlink" title="Lambda 变量作用域"></a>Lambda 变量作用域</h3><p>可访问范围</p><ol><li><strong>静态变量、成员变量</strong>：可读可修改；</li><li><strong>局部变量</strong>：<strong>隐式 final</strong>，Java8 之后局部变量即使不加 final，也不允许二次修改；</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"><span class="comment">// 合法，num未修改</span></span><br><span class="line"><span class="type">Runnable</span> <span class="variable">run</span> <span class="operator">=</span> ()-&gt;System.out.println(num);</span><br><span class="line"><span class="comment">// 报错，Lambda引用局部变量不能重新赋值</span></span><br><span class="line">num = <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p>禁止使用</p><p>Lambda 内部<strong>不能定义与外部重名的局部变量</strong>；不允许使用<code>this</code>指向外部类以外对象，Lambda 没有独立 this。</p><h3 id="Lambda-与匿名内部类区别"><a href="#Lambda-与匿名内部类区别" class="headerlink" title="Lambda 与匿名内部类区别"></a>Lambda 与匿名内部类区别</h3><div class="table-container"><table><thead><tr><th>对比项</th><th>Lambda</th><th>匿名内部类</th></tr></thead><tbody><tr><td>结构</td><td>只能绑定函数式接口</td><td>普通接口、抽象类都能用</td></tr><tr><td>this 指向</td><td>外层类对象</td><td>匿名内部类自身</td></tr><tr><td>变量</td><td>局部变量隐式 final</td><td>局部变量必须显式 final (JDK7)</td></tr><tr><td>编译</td><td>生成 invokedynamic 动态调用</td><td>生成独立 class 文件</td></tr><tr><td>可读性</td><td>极简，逻辑复杂可读性差</td><td>结构完整，复杂逻辑清晰</td></tr></tbody></table></div><h3 id="Lambda-Stream-流式编程（最常用场景）"><a href="#Lambda-Stream-流式编程（最常用场景）" class="headerlink" title="Lambda+Stream 流式编程（最常用场景）"></a>Lambda+Stream 流式编程（最常用场景）</h3><p>Lambda 最大价值就是配合 Stream 处理集合，核心中间操作 + 终止操作</p><p>常用中间操作（返回 Stream，可链式调用）</p><ul><li>filter (Predicate)：过滤元素</li><li>map (Function)：元素转换</li><li>sorted (Comparator)：排序</li><li>distinct ()：去重</li><li>limit (n)：截取前 n 个</li><li>skip (n)：跳过前 n 个</li></ul><p>常用终止操作（关闭流，不可继续调用）</p><ul><li>collect ()：转为 List、Set、Map</li><li>forEach (Consumer)：遍历</li><li>count ()：统计数量</li><li>max/min：最值</li><li>anyMatch/allMatch/noneMatch：匹配判断</li></ul><p>示例</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt; list = Arrays.asList(<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"><span class="comment">// 筛选偶数、平方、升序、转为集合</span></span><br><span class="line">List&lt;Integer&gt; res = list.stream()</span><br><span class="line">        .filter(n-&gt;n%<span class="number">2</span>==<span class="number">0</span>)</span><br><span class="line">        .map(n-&gt;n*n)</span><br><span class="line">        .sorted()</span><br><span class="line">        .collect(Collectors.toList());</span><br></pre></td></tr></table></figure><h3 id="Lambda-优缺点"><a href="#Lambda-优缺点" class="headerlink" title="Lambda 优缺点"></a>Lambda 优缺点</h3><p>优点</p><ol><li>代码极度精简，消除模板代码；</li><li>便于流式编程，集合处理优雅；</li><li>延迟执行，性能在大批量集合更优。</li></ol><p>缺点</p><ol><li>调试困难，无单独类文件，堆栈不直观；</li><li>复杂逻辑可读性差，多层嵌套难以理解；</li><li>无法重载，仅支持单抽象方法接口；</li><li>递归实现繁琐。</li></ol><h3 id="高频易错点总结"><a href="#高频易错点总结" class="headerlink" title="高频易错点总结"></a>高频易错点总结</h3><ol><li>无抽象方法、多个抽象方法的接口<strong>不能用 Lambda</strong>；</li><li>多行方法体必须加大括号，有返回值必须写 return；</li><li>局部变量一旦被 Lambda 引用，就不可二次赋值；</li><li>Lambda 不能抛出受检异常，需要手动 try-catch；</li><li>不要滥用 Lambda，超长复杂逻辑优先普通方法。</li></ol><h2 id="Java-Stream-API"><a href="#Java-Stream-API" class="headerlink" title="Java Stream API"></a>Java Stream API</h2><p>Stream 是 Java8 引入的<strong>流式编程</strong>，用于集合、数组等数据源的<strong>遍历、过滤、映射、聚合、排序</strong>等操作，核心思想：<strong>把集合转为流式流水线，链式调用，延迟执行</strong>，大幅简化循环代码，配合 Lambda、方法引用使用。</p><h3 id="一、Stream-核心定位"><a href="#一、Stream-核心定位" class="headerlink" title="一、Stream 核心定位"></a>一、Stream 核心定位</h3><ol><li><strong>用途</strong>：对集合数据做复杂计算，替代传统 for/foreach 循环；</li><li><strong>特点</strong><ul><li><strong>单向一次性</strong>：流只能遍历一次，消费完毕就关闭，不可重复使用；</li><li><strong>惰性求值</strong>：中间操作只记录逻辑，不执行，<strong>只有终止操作触发才真正计算</strong>；</li><li><strong>不修改原数据源</strong>：所有操作产生新流 / 新结果，原集合不变；</li><li><strong>支持串行、并行流</strong>：<code>stream()</code>串行，<code>parallelStream()</code>多线程并行。</li></ul></li><li><strong>和集合区别</strong><ul><li>集合：侧重<strong>存储数据</strong>；</li><li>Stream：侧重<strong>处理数据</strong>。</li></ul></li></ol><h3 id="二、Stream-三要素"><a href="#二、Stream-三要素" class="headerlink" title="二、Stream 三要素"></a>二、Stream 三要素</h3><ol><li><strong>数据源</strong>：集合、数组、IO 流、生成器；</li><li><strong>中间操作</strong>：链式调用，返回新 Stream，惰性执行；</li><li><strong>终止操作</strong>：关闭流，执行计算，返回结果 /void。</li></ol><h4 id="1-如何创建-Stream（数据源）"><a href="#1-如何创建-Stream（数据源）" class="headerlink" title="1.如何创建 Stream（数据源）"></a>1.如何创建 Stream（数据源）</h4><p>1）集合创建（最常用）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">Stream&lt;String&gt; stream = list.stream();        <span class="comment">// 串行流</span></span><br><span class="line">Stream&lt;String&gt; parallelStream = list.parallelStream(); <span class="comment">// 并行流</span></span><br></pre></td></tr></table></figure><p>2）数组创建</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>&#125;;</span><br><span class="line"><span class="type">IntStream</span> <span class="variable">stream</span> <span class="operator">=</span> Arrays.stream(arr);</span><br><span class="line">Stream&lt;Integer&gt; stream2 = Stream.of(arr);</span><br></pre></td></tr></table></figure><p>3）静态方法 Stream.of ()</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Stream&lt;Integer&gt; stream = Stream.of(<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></pre></td></tr></table></figure><p>4）无限流（生成无限序列）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 迭代生成：从0开始，每次+2</span></span><br><span class="line">Stream.iterate(<span class="number">0</span>, n -&gt; n+<span class="number">2</span>).limit(<span class="number">10</span>).forEach(System.out::println);</span><br><span class="line"><span class="comment">// 随机数生成</span></span><br><span class="line">Stream.generate(Math::random).limit(<span class="number">5</span>).forEach(System.out::println);</span><br></pre></td></tr></table></figure><p>5）其他数据源</p><p>文件：<code>Files.lines(Paths.get(&quot;xxx.txt&quot;))</code>； </p><p>枚举：<code>Enum.stream()</code>。</p><h4 id="2-中间操作（Intermediate）"><a href="#2-中间操作（Intermediate）" class="headerlink" title="2. 中间操作（Intermediate）"></a>2. 中间操作（Intermediate）</h4><blockquote><p>调用后返回 Stream，<strong>不执行计算</strong>，可链式拼接，常用分类：过滤、映射、排序、去重、截断、合并。</p></blockquote><p>一.筛选与切片</p><div class="table-container"><table><thead><tr><th>方法</th><th>作用</th></tr></thead><tbody><tr><td><code>filter(Predicate)</code></td><td>过滤，保留满足条件元素</td></tr><tr><td><code>limit(n)</code></td><td>截取前 n 个元素</td></tr><tr><td><code>skip(n)</code></td><td>跳过前 n 个元素</td></tr><tr><td><code>distinct()</code></td><td>去重（依赖 equals/hashCode）</td></tr></tbody></table></div><p>示例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt; nums = Arrays.asList(<span class="number">1</span>,<span class="number">2</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">nums.stream()</span><br><span class="line">    .filter(x -&gt; x&gt;<span class="number">2</span>)</span><br><span class="line">    .distinct()</span><br><span class="line">    .skip(<span class="number">1</span>)</span><br><span class="line">    .limit(<span class="number">2</span>)</span><br><span class="line">    .forEach(System.out::println); <span class="comment">// 输出4、5</span></span><br></pre></td></tr></table></figure><p>二.映射 map 系列（元素类型转换）</p><ol><li><strong>map(Function)</strong>：一对一转换，每个元素映射成一个新元素</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 字符串转长度</span></span><br><span class="line">Stream.of(<span class="string">&quot;java&quot;</span>,<span class="string">&quot;stream&quot;</span>).map(String::length).forEach(System.out::println);</span><br></pre></td></tr></table></figure><ol><li><strong>flatMap(Function)</strong>：一对多，把流中每个元素拆解为流，<strong>扁平化</strong></li></ol><blockquote><p>解决：集合嵌套、拆分字符串、拆分数组</p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 拆分字符串 [&quot;a,b&quot;,&quot;c,d&quot;] → 平铺为 a,b,c,d</span></span><br><span class="line">List&lt;String&gt; list = Arrays.asList(<span class="string">&quot;a,b&quot;</span>,<span class="string">&quot;c,d&quot;</span>);</span><br><span class="line">list.stream()</span><br><span class="line">    .flatMap(s -&gt; Arrays.stream(s.split(<span class="string">&quot;,&quot;</span>)))</span><br><span class="line">    .forEach(System.out::println);</span><br></pre></td></tr></table></figure><p>基础类型映射：<code>mapToInt/mapToLong/mapToDouble</code> 作用：拆箱，避免频繁包装类，提升性能，对应<code>IntStream/LongStream/DoubleStream</code>。</p><ol><li>排序 sorted </li></ol><p>​    <code>sorted()</code>：自然排序，元素实现 Comparable</p><p>​    <code>sorted(Comparator)</code>：自定义比较器</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Stream.of(<span class="number">3</span>,<span class="number">1</span>,<span class="number">2</span>).sorted().forEach(System.out::println);</span><br><span class="line"><span class="comment">// 降序</span></span><br><span class="line">Stream.of(<span class="number">3</span>,<span class="number">1</span>,<span class="number">2</span>).sorted((a,b)-&gt;b-a).forEach(System.out::println);</span><br></pre></td></tr></table></figure><ol><li>peek 调试</li></ol><p><code>peek(Consumer)</code>：遍历查看元素，<strong>不改变流</strong>，多用于打印日志调试：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Stream.of(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>).peek(System.out::println).map(x-&gt;x*<span class="number">2</span>).collect(Collectors.toList());</span><br></pre></td></tr></table></figure><h4 id="3-终止操作（Terminal）"><a href="#3-终止操作（Terminal）" class="headerlink" title="3.终止操作（Terminal）"></a>3.终止操作（Terminal）</h4><blockquote><p>一旦调用，流关闭，触发所有中间操作执行，<strong>不可再使用流</strong>。 分为：遍历、查找匹配、规约、收集、统计。</p></blockquote><ol><li>遍历 forEach /forEachOrdered</li></ol><ul><li><code>forEach</code>：无序遍历，并行流乱序；</li><li><code>forEachOrdered</code>：保证顺序遍历。</li></ul><ol><li>查找与匹配（返回布尔 / Optional）</li></ol><div class="table-container"><table><thead><tr><th>方法</th><th>作用</th></tr></thead><tbody><tr><td><code>allMatch(Predicate)</code></td><td>全部匹配返回 true</td></tr><tr><td><code>anyMatch(Predicate)</code></td><td>任意一个匹配返回 true</td></tr><tr><td><code>noneMatch(Predicate)</code></td><td>全都不匹配返回 true</td></tr><tr><td><code>findFirst()</code></td><td>获取第一个元素 Optional</td></tr><tr><td><code>findAny()</code></td><td>随机获取一个，并行流效率高</td></tr></tbody></table></div><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="variable">all</span> <span class="operator">=</span> Stream.of(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>).allMatch(x-&gt;x&gt;<span class="number">0</span>);</span><br><span class="line">Optional&lt;Integer&gt; first = Stream.of(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>).findFirst();</span><br></pre></td></tr></table></figure><ol><li>规约 reduce（聚合计算，核心）</li></ol><p>将流中元素反复结合，得到一个值，求和、最值、拼接。</p><ol><li>单参数：<code>reduce(BinaryOperator)</code> 返回 Optional</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Optional&lt;Integer&gt; sum = Stream.of(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>).reduce((a,b)-&gt;a+b);</span><br></pre></td></tr></table></figure><ol><li>双参数：<code>reduce(初始值, BinaryOperator)</code> 返回具体类型</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">sum</span> <span class="operator">=</span> Stream.of(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>).reduce(<span class="number">0</span>,Integer::sum);</span><br></pre></td></tr></table></figure><ol><li>三参数：并行规约专用。</li></ol><ol><li>收集 collect（最常用，Collectors 工具类）</li></ol><p><code>collect(Collector)</code>：把流转为集合、数组、字符串、分组、分区、统计。</p><p>（1）转为集合</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt; list = stream.collect(Collectors.toList());</span><br><span class="line">Set&lt;Integer&gt; set = stream.collect(Collectors.toSet());</span><br><span class="line"><span class="comment">// 指定ArrayList</span></span><br><span class="line">ArrayList&lt;Integer&gt; arrayList = stream.collect(Collectors.toCollection(ArrayList::<span class="keyword">new</span>));</span><br></pre></td></tr></table></figure><p>（2）转数组</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Integer[] arr = stream.toArray(Integer[]::<span class="keyword">new</span>);</span><br></pre></td></tr></table></figure><p>（3）拼接字符串 joining</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> Stream.of(<span class="string">&quot;a&quot;</span>,<span class="string">&quot;b&quot;</span>,<span class="string">&quot;c&quot;</span>).collect(Collectors.joining(<span class="string">&quot;-&quot;</span>,<span class="string">&quot;[&quot;</span>,<span class="string">&quot;]&quot;</span>));</span><br><span class="line"><span class="comment">// [a-b-c]</span></span><br></pre></td></tr></table></figure><p>（4）统计类 Collectors</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">IntSummaryStatistics</span> <span class="variable">stat</span> <span class="operator">=</span> Stream.of(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>).collect(Collectors.summarizingInt(x-&gt;x));</span><br><span class="line">stat.getMax(); stat.getMin(); stat.getSum(); stat.getAverage(); stat.getCount();</span><br></pre></td></tr></table></figure><p>快捷方法：<code>count()、max()、min()</code>。</p><p>（5）分组 groupingBy</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;User&gt; users = ...;</span><br><span class="line"><span class="comment">// 按年龄分组</span></span><br><span class="line">Map&lt;Integer,List&lt;User&gt;&gt; group = users.stream().collect(Collectors.groupingBy(User::getAge));</span><br><span class="line"><span class="comment">// 多级分组</span></span><br><span class="line">Map&lt;Integer,Map&lt;String,List&lt;User&gt;&gt;&gt; group2 = users.stream()</span><br><span class="line">    .collect(Collectors.groupingBy(User::getAge, Collectors.groupingBy(User::getGender)));</span><br></pre></td></tr></table></figure><p>（6）分区 partitioningBy</p><p>按条件分为 true/false 两组</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Map&lt;Boolean,List&lt;User&gt;&gt; part = users.stream()</span><br><span class="line">    .collect(Collectors.partitioningBy(u-&gt;u.getAge()&gt;<span class="number">18</span>));</span><br></pre></td></tr></table></figure><p>（7）聚合收集：求和、平均数、最值</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Collectors.summingInt()、averagingInt()、maxBy()、minBy()</span><br></pre></td></tr></table></figure><h3 id="三、并行流-parallelStream"><a href="#三、并行流-parallelStream" class="headerlink" title="三、并行流 parallelStream"></a>三、并行流 parallelStream</h3><p>开启方式</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">stream.parallel() <span class="comment">// 串行流转并行</span></span><br><span class="line">list.parallelStream() <span class="comment">// 直接创建并行流</span></span><br></pre></td></tr></table></figure><p>底层基于 <strong>Fork/Join 框架</strong>，多线程拆分合并。</p><p>注意事项</p><ol><li>并行不一定更快：数据量小、简单计算，线程开销 &gt; 计算耗时；</li><li>并行无序，需要顺序用 <code>forEachOrdered</code>；</li><li><strong>禁止在并行流操作非线程安全变量</strong>（ArrayList、普通成员变量会并发异常）；</li><li>有状态操作（limit、sorted、distinct）并行效率会大幅下降。</li></ol><h3 id="四、Optional-配合-Stream（空指针防护）"><a href="#四、Optional-配合-Stream（空指针防护）" class="headerlink" title="四、Optional 配合 Stream（空指针防护）"></a>四、Optional 配合 Stream（空指针防护）</h3><p>findFirst、findAny、reduce 都会返回 Optional，避免 NPE：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">num</span> <span class="operator">=</span> Stream.of(<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>).filter(x-&gt;x&gt;<span class="number">10</span>).findFirst().orElse(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h3 id="五、常用实操经典案例"><a href="#五、常用实操经典案例" class="headerlink" title="五、常用实操经典案例"></a>五、常用实操经典案例</h3><p>案例 1：去重、过滤、求和</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt; list = Arrays.asList(<span class="number">1</span>,<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="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> list.stream().distinct().filter(x-&gt;x%<span class="number">2</span>==<span class="number">0</span>).reduce(<span class="number">0</span>,Integer::sum);</span><br></pre></td></tr></table></figure><p>案例 2：对象集合分组统计</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span>&#123;String name;Integer age;&#125;</span><br><span class="line"><span class="comment">// 统计各年龄人数</span></span><br><span class="line">Map&lt;Integer,Long&gt; ageCount = studentList.stream()</span><br><span class="line">    .collect(Collectors.groupingBy(Student::getAge, Collectors.counting()));</span><br></pre></td></tr></table></figure><p>案例 3：扁平化集合</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;List&lt;Integer&gt;&gt; nested = Arrays.asList(Arrays.asList(<span class="number">1</span>,<span class="number">2</span>),Arrays.asList(<span class="number">3</span>,<span class="number">4</span>));</span><br><span class="line">List&lt;Integer&gt; flat = nested.stream().flatMap(List::stream).collect(Collectors.toList());</span><br></pre></td></tr></table></figure><h3 id="六、重要知识点-1"><a href="#六、重要知识点-1" class="headerlink" title="六、重要知识点"></a>六、重要知识点</h3><ol><li><strong>流只能用一次</strong>：中间操作后流消费完毕，重复调用抛 <code>IllegalStateException</code>；</li><li>惰性求值：中间操作写了异常逻辑，不调用终止操作不会报错；</li><li>map 与 flatMap 混淆：普通映射用 map，拆分层级用 flatMap；</li><li>并行流线程不安全：不要修改外部变量；</li><li>distinct 依赖 equals，自定义对象重写 equals+hashCode；</li><li>基础流（IntStream）无装箱开销，大数据优先使用。</li></ol><h3 id="七、Java8-Java11-Stream-新增小特性"><a href="#七、Java8-Java11-Stream-新增小特性" class="headerlink" title="七、Java8~Java11 Stream 新增小特性"></a>七、Java8~Java11 Stream 新增小特性</h3><ol><li><code>Stream.ofNullable()</code>：支持 null 数据源，null 会生成空流，防空指针；</li><li><code>takeWhile(predicate)</code>：从头截取直到不满足条件；</li><li><code>dropWhile(predicate)</code>：从头丢弃直到不满足条件。</li></ol>]]></content>
    
    
    <summary type="html">Java学习笔记补充知识点</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=ce294980-9d52-9ca9-aef9-aca88fa07ec2" type="image"/>
    
    
    <category term="Java学习笔记" scheme="https://blog.itrf.cn/tags/Java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>Java学习笔记03</title>
    <link href="https://blog.itrf.cn/posts/41ab/"/>
    <id>https://blog.itrf.cn/posts/41ab/</id>
    <published>2026-06-30T16:00:02.000Z</published>
    <updated>2026-09-04T02:15:17.030Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Scanner-类"><a href="#Scanner-类" class="headerlink" title="Scanner 类"></a>Scanner 类</h2><p><code>Scanner</code> 是 <strong>java.util</strong> 包下的工具类，用来读取控制台输入、读取文件、字符串数据，最常用于接收键盘输入。</p><blockquote><p>需要导包：<code>import java.util.Scanner;</code></p></blockquote><h3 id="1-基本使用"><a href="#1-基本使用" class="headerlink" title="1. 基本使用"></a>1. 基本使用</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Scanner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">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">// 1. 创建Scanner对象，接收键盘输入</span></span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line"></span><br><span class="line">        System.out.print(<span class="string">&quot;请输入一个整数：&quot;</span>);</span><br><span class="line">        <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> sc.nextInt();   <span class="comment">// 读取int整数</span></span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;你输入的数字：&quot;</span> + num);</span><br><span class="line"></span><br><span class="line">        sc.close(); <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="2-常用方法"><a href="#2-常用方法" class="headerlink" title="2. 常用方法"></a>2. 常用方法</h3><div class="table-container"><table><thead><tr><th>方法</th><th>作用</th></tr></thead><tbody><tr><td><code>nextInt()</code></td><td>读取<strong>int 整数</strong></td></tr><tr><td><code>nextDouble()</code></td><td>读取小数 double</td></tr><tr><td><code>nextBoolean()</code></td><td>读取布尔 true/false</td></tr><tr><td><code>next()</code></td><td>读取<strong>字符串，遇到空格停止</strong></td></tr><tr><td><code>nextLine()</code></td><td>读取<strong>一整行字符串，包含空格</strong></td></tr></tbody></table></div><h4 id="next-和-nextLine-重点区别"><a href="#next-和-nextLine-重点区别" class="headerlink" title="next() 和 nextLine() 重点区别"></a>next() 和 nextLine() 重点区别</h4><ul><li><code>next()</code>：读到<strong>空格、Tab、回车</strong>就结束，不能获取带空格句子</li><li><code>nextLine()</code>：读到<strong>回车</strong>结束，可以读取带空格完整一行</li></ul><blockquote><p>坑：<code>nextInt()</code>之后直接调用<code>nextLine()</code>会读到空字符串！ 原因：<code>nextInt()</code>只读取数字，<strong>遗留回车符留在缓冲区</strong>，nextLine 直接读到回车返回空。</p></blockquote><p><strong>解决办法：</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> sc.nextInt();</span><br><span class="line">sc.nextLine(); <span class="comment">// 吃掉遗留的回车</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> sc.nextLine();</span><br></pre></td></tr></table></figure><h3 id="3-hasNext-判断是否还有输入"><a href="#3-hasNext-判断是否还有输入" class="headerlink" title="3. hasNext() 判断是否还有输入"></a>3. hasNext() 判断是否还有输入</h3><p>循环读取输入，判断是否有数据：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Scanner</span> <span class="variable">sc</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line"><span class="keyword">while</span>(sc.hasNext())&#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> sc.next();</span><br><span class="line">    System.out.println(str);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-Scanner-读取文件（拓展）"><a href="#4-Scanner-读取文件（拓展）" class="headerlink" title="4. Scanner 读取文件（拓展）"></a>4. Scanner 读取文件（拓展）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Scanner sc = new Scanner(new File(&quot;test.txt&quot;));</span></span><br></pre></td></tr></table></figure><h3 id="5-常见报错"><a href="#5-常见报错" class="headerlink" title="5. 常见报错"></a>5. 常见报错</h3><ol><li><code>InputMismatchException</code>：输入的数据类型不匹配，比如要 int，你输入字母。</li><li>忘记<code>sc.close()</code>：不会报错，但会资源泄漏。</li><li>nextInt 后 nextLine 得到空字符串：上面说的缓冲区回车问题。</li></ol><h2 id="枚举和注解"><a href="#枚举和注解" class="headerlink" title="枚举和注解"></a>枚举和注解</h2><h3 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h3><p><strong>枚举是一种特殊类，用来固定、列出有限、确定的常量。</strong></p><p>适合场景：季节、星期、订单状态、性别、方向。</p><p>解决普通常量<code>public static final</code>的缺陷（无法限制传入非法值）。</p><p>语法定义：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Season</span> &#123;</span><br><span class="line">    <span class="comment">// 枚举常量（必须写在最前面）</span></span><br><span class="line">    SPRING, SUMMER, AUTUMN, WINTER;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>等价理解：每个常量都是 <code>Season</code> 的<strong>静态常量实例对象</strong>。</p><p>枚举的实现方式</p><ol><li><p>自定义类实现枚举</p><p>1) 构造器私有化<br>2) 本类内部创建一组对象<br>3) 对外暴露对象（通过为对象添加 public final static 修饰符）<br>4) 可以提供 get 方法，但是不要提供 set</p></li><li><p>使用enum关键字实现枚举</p><p>1) 当我们使用 enum 关键字开发一个枚举类时，默认会继承 Enum 类, 而且是一个 final 类</p><p>2) 传统的 <code>public static final Season2 SPRING = new Season2(&quot;春天&quot;, &quot;温暖&quot;)</code>; 简化成 <code>SPRING(&quot;春天&quot;, &quot;温暖&quot;)</code>， 这里必须知道，它调用的是哪个构造器. </p><p>3) 如果使用无参构造器 创建 枚举对象，则实参列表和小括号都可以省略<br>4) 当有多个枚举对象时，使用,间隔，最后有一个分号结尾<br>5) 枚举对象必须放在枚举类的行首</p><p><code>enum</code> 定义的类，编译器自动处理：</p><ol><li>隐式继承 <code>java.lang.Enum</code>，<strong>不能再继承其他类</strong></li><li>所有枚举实例：<code>public static final</code></li><li>构造方法<strong>强制私有 private</strong>，就算不写也是 private，禁止外部<code>new</code></li></ol><blockquote><p>误区：不能手动写 <code>extends</code>；构造方法不能用 public /protected</p></blockquote><p>示例</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Season</span> &#123;</span><br><span class="line">    <span class="comment">//常量对象，调用构造方法</span></span><br><span class="line">    SPRING(<span class="string">&quot;春天&quot;</span>, <span class="string">&quot;温暖&quot;</span>),</span><br><span class="line">    SUMMER(<span class="string">&quot;夏天&quot;</span>, <span class="string">&quot;炎热&quot;</span>),</span><br><span class="line">    AUTUMN(<span class="string">&quot;秋天&quot;</span>, <span class="string">&quot;凉爽&quot;</span>),</span><br><span class="line">    WINTER(<span class="string">&quot;冬天&quot;</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">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> String desc;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//【构造方法：默认private，不能写public】</span></span><br><span class="line">    Season(String name, String desc) &#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>.desc = desc;</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">    <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 class="keyword">public</span> String <span class="title function_">getDesc</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> desc;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Enum 自带核心方法</p><ol><li><code>values()</code></li></ol><p>返回<strong>枚举所有常量的数组</strong>，用于遍历</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Season[] arr = Season.values();</span><br><span class="line"><span class="keyword">for</span>(Season s : arr)&#123;</span><br><span class="line">    System.out.println(s);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li><code>valueOf(String name)</code></li></ol><p>根据名字字符串获取枚举常量；<strong>名字不存在抛出异常<code>IllegalArgumentException</code></strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Season</span> <span class="variable">s</span> <span class="operator">=</span> Season.valueOf(<span class="string">&quot;SPRING&quot;</span>);</span><br></pre></td></tr></table></figure><ol><li><code>ordinal()</code></li></ol><p>返回常量序号，从 <strong>0 开始</strong>（定义顺序）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.out.println(Season.SPRING.ordinal()); <span class="comment">//0</span></span><br></pre></td></tr></table></figure><ol><li><code>name()</code> / <code>toString()</code></li></ol><ul><li><code>name()</code>：返回常量定义名称（SPRING），final 无法重写</li><li><code>toString()</code>：默认和 name() 一样，<strong>可以重写自定义输出</strong></li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.out.println(Season.SPRING.name()); <span class="comment">//SPRING</span></span><br></pre></td></tr></table></figure><p>枚举可以拥有的特性</p><p>构造方法（只能 private）</p><p>成员变量、普通方法、静态方法</p><p>抽象方法（每个枚举常量必须实现！）</p><p>可以实现接口</p><p>不能继承其他类（隐式继承 Enum，Java 单继承）</p><p>外部无法 new 枚举对象</p><p>每个枚举实例单独实现方法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">Season</span> &#123;</span><br><span class="line">    SPRING&#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>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    SUMMER&#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>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</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_">show</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>高频易错总结</p><ol><li>枚举构造器<strong>只能 private，省略不写也是 private</strong></li><li>枚举常量必须放在类最开头，分号可选（后面有代码必须加分号）</li><li>枚举不能被继承；不能继承别的类，可以实现接口</li><li><code>valueOf()</code> 参数名称错误会抛运行异常</li><li>ordinal() 顺序依赖代码定义顺序，业务不要重度依赖！</li><li>枚举常量是单例，全局只有一个对象</li></ol></li></ol><h3 id="注解"><a href="#注解" class="headerlink" title="注解"></a>注解</h3><p>1) 注解(Annotation)也被称为元数据(Metadata)，用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。<br>2) 和注释一样，注解不影响程序逻辑，但注解可以被编译或运行，相当于嵌入在代码中的补充信息。<br>3) 在 JavaSE 中，注解的使用目的比较简单，例如标记过时的功能，忽略警告等。在 JavaEE 中注解占据了更重要的角色，例如用来配置应用程序的任何切面，代替 java EE 旧版中所遗留的繁冗代码和 XML 配置等。</p><div class="table-container"><table><thead><tr><th>注解</th><th>作用</th></tr></thead><tbody><tr><td><code>@Override</code></td><td>标记方法是重写父类 / 接口方法；编译器校验，如果不是重写直接编译报错</td></tr><tr><td><code>@Deprecated</code></td><td>标记【已过时】元素，使用时编译器弹出警告</td></tr><tr><td><code>@SuppressWarnings(&quot;all&quot;)</code></td><td>压制编译警告；all 代表抑制全部警告，也可以指定 “unchecked” 等</td></tr><tr><td><code>@SafeVarargs</code></td><td>JDK7，抑制可变泛型参数警告（了解）</td></tr><tr><td><code>@FunctionalInterface</code></td><td>JDK8，标记函数式接口：接口只能有<strong>一个抽象方法</strong>，供 Lambda 使用</td></tr></tbody></table></div><p>元注解（用来修饰【自定义注解】的注解）</p><p>元注解：<strong>注解的注解</strong>，专门定义自定义注解的行为，一共 5 个，重点 4 个</p><ol><li><p><strong>@Target</strong></p><p>可选值（ElementType 枚举）：</p></li></ol><ul><li><code>TYPE</code>：类、接口、枚举</li><li><code>METHOD</code>：方法</li><li><code>FIELD</code>：成员变量</li><li><code>PARAMETER</code>：方法参数</li><li><code>CONSTRUCTOR</code>：构造方法</li><li><code>LOCAL_VARIABLE</code>：局部变量</li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Target(&#123;ElementType.METHOD,ElementType.TYPE&#125;)</span></span><br></pre></td></tr></table></figure><ol><li><strong>@Retention</strong></li></ol><p>指定注解，生命周期（保留到哪个阶段）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">SOURCE  → 源码阶段，编译后丢弃（注释级别，仅编译器看见，class不存在）</span><br><span class="line">CLASS   → 编译保留在class，运行时JVM丢弃【默认策略】</span><br><span class="line">RUNTIME → 运行时仍然保留，可以通过反射读取（自定义框架注解几乎都用这个！）</span><br></pre></td></tr></table></figure><blockquote><p>想在运行时通过反射获取注解信息，必须写：<code>@Retention(RetentionPolicy.RUNTIME)</code></p></blockquote><ol><li><strong>@Documented</strong></li></ol><p>使用 javadoc 生成 API 文档时，把注解信息纳入文档</p><ol><li><strong>@Inherited</strong></li></ol><p><strong>可继承</strong>：父类标记该注解，子类自动继承这个注解</p><p>注意：只对【类】有效；方法、属性无效</p><ol><li>@Repeatable（JDK8）：可重复注解（拓展了解）</li></ol><hr><p>自定义注解（自己创建注解）</p><p>语法：<code>@interface</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 元注解约束</span></span><br><span class="line"><span class="meta">@Target(ElementType.METHOD)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> MyAnnotation &#123;</span><br><span class="line">    <span class="comment">// 注解的【属性】，格式：类型 名称() [default 默认值];</span></span><br><span class="line">    String <span class="title function_">value</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="type">int</span> <span class="title function_">age</span><span class="params">()</span> <span class="keyword">default</span> <span class="number">18</span>;</span><br><span class="line">    String[] tags() ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用自定义注解</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 只有一个属性名叫value，可以省略属性名</span></span><br><span class="line"><span class="meta">@MyAnnotation(&quot;测试&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">func</span><span class="params">()</span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 多个属性</span></span><br><span class="line"><span class="meta">@MyAnnotation(value=&quot;demo&quot;,age=20,tags=&#123;&quot;a&quot;,&quot;b&quot;&#125;)</span></span><br></pre></td></tr></table></figure><p>关键语法规则</p><ol><li><p>属性支持类型：基本类型、String、枚举、注解、以上类型数组</p></li><li><p>如果属性没有设置<code>default</code>默认值，使用注解时<strong>必须显式赋值</strong></p></li><li><p>特殊约定：属性名叫做<code>value</code>，并且只有这一个属性，赋值时可以省略<code>value=</code></p></li></ol><p>注解如何生效？</p><p>注解本身只是标记！<strong>必须配合反射机制解析</strong></p><p>示例：通过反射读取运行时注解</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取方法</span></span><br><span class="line"><span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> Test.class.getMethod(<span class="string">&quot;func&quot;</span>);</span><br><span class="line"><span class="comment">// 判断是否存在注解</span></span><br><span class="line"><span class="keyword">if</span>(method.isAnnotationPresent(MyAnnotation.class))&#123;</span><br><span class="line">    <span class="type">MyAnnotation</span> <span class="variable">anno</span> <span class="operator">=</span> method.getAnnotation(MyAnnotation.class);</span><br><span class="line">    System.out.println(anno.value());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>Spring、Mybatis 框架底层大量使用：自定义注解 + 反射实现功能。</p></blockquote><h2 id="异常-Exception"><a href="#异常-Exception" class="headerlink" title="异常-Exception"></a>异常-Exception</h2><p>一、什么是异常</p><p>程序运行过程中出现的<strong>不正常情况（错误）</strong>，会打断正常指令流。</p><p>Java 使用<strong>面向对象方式</strong>管理异常：异常本身就是一个对象。</p><blockquote><p>区分：</p><ul><li><strong>编译期异常</strong>：写代码编译器就报错</li><li><strong>运行时异常</strong>：编译正常，运行程序崩溃</li></ul></blockquote><p>二、异常体系结构图（核心）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">java.lang.Throwable</span><br><span class="line">├─ Error 【错误】系统级严重问题，程序无力处理，不用捕获</span><br><span class="line">│    StackOverflowError、OutOfMemoryError</span><br><span class="line">└─ Exception 【异常】程序层面，可以捕获处理</span><br><span class="line">     ├─ RuntimeException 运行时异常（非受检异常）</span><br><span class="line">     │    NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException</span><br><span class="line">     └─ 其他Exception：编译期异常（受检异常 CheckedException）</span><br><span class="line">          IOException、FileNotFoundException、SQLException</span><br></pre></td></tr></table></figure><p>概念区分</p><ol><li><p><strong>Error（错误）</strong></p><p>虚拟机内部故障、资源耗尽。程序代码无法修复，不需要 try-catch 捕获。</p></li><li><p><strong>Exception（异常）</strong></p><p>程序逻辑问题，可以处理。分为两类：</p><p>运行时异常（RuntimeException）【非受检异常】</p><p>编译不强制要求捕获 / 声明；一般由代码书写 bug 导致。</p><p>编译期异常（受检异常 CheckedException）</p><p>编译器强制要求：要么<code>try-catch</code>捕获，要么<code>throws</code>抛出，否则编译报错。</p></li></ol><hr><p>三、异常处理两大方案</p><p>方案 1：try-catch-finally 捕获处理</p><p>语法模板</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span>&#123;</span><br><span class="line">    <span class="comment">// 可能出现异常的代码</span></span><br><span class="line">&#125;<span class="keyword">catch</span>(异常类型 e)&#123;</span><br><span class="line">    <span class="comment">// 异常发生后的处理逻辑</span></span><br><span class="line">&#125;<span class="keyword">finally</span>&#123;</span><br><span class="line">    <span class="comment">// 无论是否异常，【一定会执行】，唯一例外：System.exit(0)</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>细节考点</p><ol><li><code>try</code>不能单独存在，必须搭配 catch 或 finally</li><li>多个 catch：<strong>小范围异常写前面，大范围异常写后面</strong>（子类在前，父类在后）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;<span class="keyword">catch</span>(NullPointerException e)&#123;</span><br><span class="line"></span><br><span class="line">&#125;<span class="keyword">catch</span>(Exception e)&#123; <span class="comment">//父类放最后</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li><strong>finally 典型用途：释放资源</strong>（文件流、数据库连接、IO 流）</li></ol><p>try-catch-resources（JDK7 新特性）</p><p>自动实现资源关闭，不用手写 finally 关闭流</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span>(<span class="type">InputStream</span> <span class="variable">is</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;a.txt&quot;</span>))&#123;</span><br><span class="line"></span><br><span class="line">&#125;<span class="keyword">catch</span>(IOException e)&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>方案 2：throws 向上抛出异常</p><p>如果当前方法不想处理异常，可以把异常<strong>抛给调用者</strong></p><figure class="highlight java"><table><tr><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_">readFile</span><span class="params">()</span> <span class="keyword">throws</span> IOException&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>区别：</p><ul><li><code>try-catch</code>：自己处理异常，异常到此结束</li><li><code>throws</code>：不处理，甩锅给上层调用方法</li></ul><hr><p>四、主动抛出异常 throw</p><p>手动创建异常对象并抛出</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">age</span> <span class="operator">=</span> -<span class="number">5</span>;</span><br><span class="line"><span class="keyword">if</span>(age &lt; <span class="number">0</span>)&#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;年龄不能为负数&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>throw（小写）：方法内部主动抛出对象</p><p>throws（复数）：方法声明上，标识该方法可能抛出的异常</p></blockquote><div class="table-container"><table><thead><tr><th style="text-align:center">关键字</th><th style="text-align:center">位置</th><th style="text-align:center">作用</th></tr></thead><tbody><tr><td style="text-align:center">throw</td><td style="text-align:center">方法内部</td><td style="text-align:center">主动抛出一个异常实例</td></tr><tr><td style="text-align:center">throws</td><td style="text-align:center">方法签名后</td><td style="text-align:center">声明该方法可能产生哪些异常，向上传递</td></tr></tbody></table></div><hr><p>五、自定义异常</p><p>业务场景内置异常不够用时，自己定义异常类</p><ol><li><p>自定义<strong>受检异常</strong>：继承 <code>Exception</code></p></li><li><p>自定义<strong>运行时异常</strong>：继承 <code>RuntimeException</code>（项目最常用）</p><p>示例：</p></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><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_">AgeIllegalException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AgeIllegalException</span><span class="params">(String msg)</span>&#123;</span><br><span class="line">        <span class="built_in">super</span>(msg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span>(age &lt; <span class="number">0</span>)&#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AgeIllegalException</span>(<span class="string">&quot;年龄非法&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p>六、重点知识点</p><p>1.finally 一定会执行吗？</p><p>绝大多数情况一定会执行</p><p>唯一打断情况：<code>System.exit(0);</code> 终止 JVM，finally 不再执行</p><p>2.try 里有 return，finally 执行时机</p><p><strong>先执行 finally 代码，再执行 return</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">test</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="keyword">try</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">10</span>;</span><br><span class="line">    &#125;<span class="keyword">finally</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;finally执行&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//输出：finally执行  然后返回10</span></span><br></pre></td></tr></table></figure><p>注意：不要在 finally 中写 return！会覆盖 try 的返回值，屏蔽异常，属于不规范代码。</p><ol><li>受检异常 vs 运行时异常总结</li></ol><ul><li>运行时异常 (RuntimeException)：编译不强制处理；属于代码 bug，尽量提前判断避免</li><li>受检异常：编译器强制处理；属于外部不可控情况（文件不存在、网络故障）</li></ul><ol><li>常用异常举例</li></ol><p>运行时异常：</p><p><code>NullPointerException</code>空指针</p><p>当应用程序试图在需要对象的地方使用 null 时</p><p><code>ArrayIndexOutOfBoundsException</code>数组越界</p><p>用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小，则该索引为非法索引</p><p><code>ClassCastException</code>类型转换异常</p><p>当试图将对象强制转换为不是实例的子类时，抛出该异常。</p><p><code>ArithmeticException</code>数学运算异常</p><p>当出现异常的运算条件时，抛出此异常。</p><p><code>NumberFormatException</code>数字格式不正确异常</p><p>编译异常：</p><p><code>IOException</code>、<code>FileNotFoundException</code>、<code>SQLException</code></p><ol><li>异常链</li></ol><p>捕获异常后抛出新异常时，可以把原始异常作为 cause 保存，保留完整堆栈信息</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> 业务异常(<span class="string">&quot;操作失败&quot;</span>,e);</span><br></pre></td></tr></table></figure><p><strong>异常链（Exception Chaining）</strong> 是一种<strong>将一个异常传递给另一个异常</strong>的技术。它允许我们在捕获一个异常后，抛出另一个新的异常，同时<strong>把原始异常（底层原因）完整地保留并记录在新的异常内部</strong>。</p><ol><li>为什么需要异常链？（痛点）</li></ol><p>在复杂的系统中，异常通常具有层次性。假设这样一个场景：</p><ol><li><p><strong>底层（数据库层）</strong>：执行 SQL 时抛出了 <code>SQLException</code>。</p></li><li><p><strong>中层（服务层）</strong>：捕捉到 <code>SQLException</code>，如果直接把它往外抛，会显得太底层、太专业（业务调用方不关心你是不是 SQL 错误）。或者如果你把它吞掉，换成抛出一个自定义的 <code>ServiceException</code>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ServiceException</span>(<span class="string">&quot;用户注册失败&quot;</span>); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>问题来了</strong>：虽然外层成功抛出了符合业务的 <code>ServiceException</code>，但<strong>原本的 <code>SQLException</code> 被丢弃了</strong>。如果数据库连接串写错或者表名写错，由于丢失了底层异常的堆栈信息（Stack Trace），排查错误会变得异常困难。</p></li></ol><p><strong>异常链完美解决了这个问题</strong>：它在抛出新异常的同时，把旧异常“栓”在新异常身上，形成一条链条。</p><ol><li>Java 是如何支持异常链的？</li></ol><p>在 Java 中，所有的异常最终都继承自 <code>Throwable</code> 类。<code>Throwable</code> 及其子类在设计时就内置了对异常链的支持：</p><ol><li><p><strong>构造方法支持传入 <code>cause</code>（原因）</strong>： 几乎所有标准异常类和自定义异常类，都提供了一个接收 <code>Throwable</code> 参数的构造方法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">MyException</span><span class="params">(String message, Throwable cause)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(message, cause); <span class="comment">// 传给父类 Throwable 保存</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p><strong><code>initCause()</code> 方法</strong>： 如果使用的异常类没有提供带 <code>cause</code> 参数的构造方法，可以通过调用对象的 <code>initCause(Throwable cause)</code> 方法来手动绑定。</p></li><li><p>代码示例</p></li></ol><p>步骤 1：定义一个自定义业务异常</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserServiceException</span> <span class="keyword">extends</span> <span class="title class_">RuntimeException</span> &#123;</span><br><span class="line">    <span class="comment">// 提供带有 cause 参数的构造器</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">UserServiceException</span><span class="params">(String message, Throwable cause)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(message, cause);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>步骤 2：在业务代码中捕获并链式抛出</p><figure class="highlight java"><table><tr><td class="code"><pre><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">public</span> <span class="keyword">void</span> <span class="title function_">registerUser</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 模拟底层代码，抛出了底层的 SQLException</span></span><br><span class="line">            <span class="comment">// throw new java.sql.SQLException(&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">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span> / <span class="number">0</span>; </span><br><span class="line">            </span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// 捕获底层异常，包装成高层的业务异常抛出，并传入 e 作为 cause</span></span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UserServiceException</span>(<span class="string">&quot;用户注册模块发生内部错误&quot;</span>, e);</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>步骤 3：查看运行效果</p><p>当外层程序捕获到 <code>UserServiceException</code> 并打印堆栈信息（<code>e.printStackTrace()</code>）时，你会看到类似下面这样的输出：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">com.example.UserServiceException: 用户注册模块发生内部错误</span><br><span class="line">    at com.example.UserService.registerUser(UserService.java:15)</span><br><span class="line">    ... (省略中间调用栈)</span><br><span class="line">Caused by: java.lang.ArithmeticException: / by zero   &lt;--- 底层原因清晰可见</span><br><span class="line">    at com.example.UserService.registerUser(UserService.java:11)</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure><ul><li><strong>注意看 <code>Caused by:</code></strong>：这就是异常链的核心威力所在。它不仅告诉调用者上层业务出错了，还顺藤摸瓜地把底层的罪魁祸首完整展示了出来。</li></ul><ol><li>总结与应用场景</li></ol><ul><li><strong>核心作用</strong>：<strong>屏蔽底层实现细节，同时保留底层错误根源</strong>，实现“高层抛出业务异常，底层保留技术异常”。</li><li><strong>常见应用</strong>：在现代企业级框架（如 Spring、MyBatis 等）中非常普遍。例如，当你使用 MyBatis 查询数据库出错时，它捕获到底层的 <code>SQLException</code>，并会将其包装并抛出它自己的 <code>DataAccessException</code>（带有异常链），方便开发者通过日志一眼看出是底层 SQL 语法错了、主键冲突了还是连接断了。</li></ul><h2 id="集合"><a href="#集合" class="headerlink" title="集合"></a>集合</h2><p>一、集合概述</p><p>集合：<code>java.util</code>包下，<strong>容器</strong>，用来存储多个对象。</p><blockquote><p>区分数组和集合：</p></blockquote><ol><li>数组：长度固定；可以存基本类型 + 引用类型</li><li>集合：长度可变；<strong>只能存储引用类型（对象）</strong>，基本类型会自动装箱</li></ol><p>集合两大根接口：</p><ol><li><strong>Collection 单列集合</strong>：一次存一个对象</li><li><strong>Map 双列集合</strong>：一次存一对对象（key=value 键值对）</li></ol><p>集合整体架构图</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Collection(单列)</span><br><span class="line">├─ List【有序、可重复、有索引】</span><br><span class="line">│   ├─ ArrayList   数组实现</span><br><span class="line">│   ├─ LinkedList  双向链表</span><br><span class="line">│   └─ Vector      数组实现(古老、同步)</span><br><span class="line">├─ Set【无序、不可重复、无索引】</span><br><span class="line">│   ├─ HashSet         哈希表</span><br><span class="line">│   │   └─ LinkedHashSet 哈希表+双向链表(有序)</span><br><span class="line">│   └─ TreeSet         红黑树(自然排序)</span><br><span class="line">└─ Queue【队列】</span><br><span class="line">    └─ ArrayDeque、PriorityQueue</span><br><span class="line"></span><br><span class="line"><span class="title function_">Map</span><span class="params">(双列 key-value)</span></span><br><span class="line">├─ HashMap         哈希表</span><br><span class="line">│   └─ LinkedHashMap 哈希表+链表(有序)</span><br><span class="line">├─ TreeMap         红黑树，按键排序</span><br><span class="line">├─ Hashtable       古老，同步（淘汰）</span><br><span class="line">└─ Properties      键值对，用于配置文件</span><br></pre></td></tr></table></figure><p>第一部分：Collection 接口</p><p>通用常用方法（所有 List、Set 都拥有）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span>                添加元素</span><br><span class="line"><span class="type">boolean</span> <span class="title function_">remove</span><span class="params">(Object o)</span>        删除指定元素</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">clear</span><span class="params">()</span>                    清空集合</span><br><span class="line"><span class="type">boolean</span> <span class="title function_">contains</span><span class="params">(Object o)</span>      判断是否包含元素</span><br><span class="line"><span class="type">boolean</span> <span class="title function_">isEmpty</span><span class="params">()</span>               判断是否为空</span><br><span class="line"><span class="type">int</span> <span class="title function_">size</span><span class="params">()</span>                      获取元素个数</span><br><span class="line">Object[] toArray()              转数组</span><br></pre></td></tr></table></figure><ol><li>List 接口</li></ol><p>特点：<strong>有序、可重复、带有索引</strong>，支持根据索引增删查改。</p><p>特有方法（带索引操作）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">get(<span class="type">int</span> index)</span><br><span class="line">set(<span class="type">int</span> index, E element)</span><br><span class="line">add(<span class="type">int</span> index, E element)</span><br><span class="line">remove(<span class="type">int</span> index)</span><br></pre></td></tr></table></figure><p>① ArrayList（重点）</p><p>底层：<strong>可变 Object 数组</strong></p><ul><li><p>默认初始容量：10</p></li><li><p>扩容机制：原容量 <code>1.5倍</code></p></li><li><p>查询快、随机访问快；<strong>中间增删慢</strong>（需要移动元素）</p></li><li><p><strong>线程不安全</strong>；效率高</p><p>适用：大量查询，少量增删</p></li></ul><p>② LinkedList</p><p>底层：<strong>双向链表</strong></p><ul><li><p>没有扩容概念，按需创建节点</p></li><li><p>查询慢（需要遍历）；首尾增删速度快</p></li><li><p>线程不安全</p><p>特有方法（队列 / 栈操作）</p></li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">addFirst() </span><br><span class="line">addLast()</span><br><span class="line">getFirst()</span><br><span class="line">getLast()</span><br><span class="line">removeFirst() </span><br><span class="line">removeLast()</span><br></pre></td></tr></table></figure><p>③ Vector（了解，基本淘汰）</p><p>底层数组，扩容 2 倍；方法加<code>synchronized</code>，线程安全，性能差。</p><div class="table-container"><table><thead><tr><th style="text-align:center">特性</th><th style="text-align:center">ArrayList</th><th style="text-align:center">LinkedList</th></tr></thead><tbody><tr><td style="text-align:center">底层</td><td style="text-align:center">动态数组</td><td style="text-align:center">双向链表</td></tr><tr><td style="text-align:center">随机访问</td><td style="text-align:center">快（下标直接定位）</td><td style="text-align:center">慢（遍历）</td></tr><tr><td style="text-align:center">中间插入删除</td><td style="text-align:center">慢（移动大量元素）</td><td style="text-align:center">快（修改指针）</td></tr><tr><td style="text-align:center">内存</td><td style="text-align:center">连续内存，有预留容量</td><td style="text-align:center">每个节点额外存前后指针</td></tr></tbody></table></div><blockquote><p>开发 90% 场景优先 ArrayList！</p></blockquote><ol><li>Set 接口</li></ol><p>特点：<strong>无序、不可重复、无索引</strong>，不能用普通 for 循环遍历，只能迭代器 / 增强 for。</p><p>① HashSet</p><p>底层：<strong>哈希表（数组 + 链表 + 红黑树）</strong></p><p>元素唯一依靠：<code>hashCode() + equals()</code></p><p>判断重复规则：</p><ol><li>先比较哈希值 hashCode()</li><li>哈希值不同 → 直接不重复</li><li>哈希值相同 → 再调用 equals()，true 判定重复</li></ol><blockquote><p>自定义类存入 HashSet，<strong>必须重写 hashCode 和 equals！</strong></p></blockquote><p>特性：无序，不保证存储顺序；线程不安全。</p><p>② LinkedHashSet</p><p>继承 HashSet；底层哈希表 + <strong>双向链表</strong></p><p><strong>存取有序</strong>；依然不能重复。</p><p>适合：需要去重同时保留添加顺序。</p><p>③ TreeSet</p><p>底层：红黑树</p><p>自动排序（自然排序）</p><p>要求：存储元素实现 <code>Comparable</code> 接口，或者构造传入<code>Comparator</code></p><p>元素不能为 null；根据比较规则去重，不是 equals。</p><ol><li>Queue 队列（简单了解）</li></ol><p>先进先出 FIFO</p><ul><li>ArrayDeque：数组双端队列</li><li>PriorityQueue：优先队列（自动排序）</li></ul><p>Collection 遍历方式</p><ol><li>增强 for 循环（底层迭代器）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span>(Object obj : list)&#123;&#125;</span><br></pre></td></tr></table></figure><ol><li>Iterator 迭代器</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Iterator&lt;String&gt; it = list.iterator();</span><br><span class="line"><span class="keyword">while</span>(it.hasNext())&#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> it.next();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>并发异常：迭代遍历集合时，不要用集合 add/remove；想要删除用迭代器<code>it.remove()</code></p></blockquote><ol><li>List 额外支持普通 for 循环（有索引）</li></ol><p>第二部分：Map 双列集合</p><p>存储格式：<code>key=value</code>，键唯一，值可重复；</p><p>一个 key 只能对应一个 value；后存入的 value 覆盖旧 value。</p><p>Map 通用方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">put(k,v)        添加/覆盖键值对</span><br><span class="line">remove(key)     根据key删除</span><br><span class="line">get(key)        根据key获取value</span><br><span class="line"><span class="title function_">containsKey</span><span class="params">(key)</span> 判断是否存在键</span><br><span class="line">containsValue(value) 判断是否存在值</span><br><span class="line">keySet()        获取所有key【重点】</span><br><span class="line">entrySet()      获取所有键值对对象【重点】</span><br><span class="line">clear()</span><br><span class="line">size()</span><br></pre></td></tr></table></figure><p>Map 三种遍历方式</p><ol><li>keySet() 先拿所有键，再 get 取值</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;String&gt; keys = map.keySet();</span><br><span class="line"><span class="keyword">for</span>(String key : keys)&#123;</span><br><span class="line">    map.get(key);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>entrySet() 获取 Entry 键值对（效率更高，推荐）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Set&lt;Map.Entry&lt;String,Integer&gt;&gt; entries = map.entrySet();</span><br><span class="line"><span class="keyword">for</span>(Map.Entry entry : entries)&#123;</span><br><span class="line">    entry.getKey();</span><br><span class="line">    entry.getValue();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>values() 只获取所有 value（无法拿到 key）</li></ol><p>常用实现类</p><p>① HashMap（重中之重）</p><p>底层：<strong>哈希表（数组 + 链表 + 红黑树）JDK1.8 优化</strong></p><p>参数：</p><ul><li><p>默认容量：16</p></li><li><p>负载因子：0.75</p></li><li><p>扩容条件：元素个数 &gt; 容量 * 负载因子 → 容量扩大 2 倍</p></li><li><p>树化阈值：链表长度 ≥8，数组长度≥64 → 链表转为红黑树；</p><p>元素≤6 退化为链表。</p></li></ul><p>特点：</p><ul><li><p>key 无序；key 允许一个 null，value 允许多个 null</p></li><li><p><strong>线程不安全</strong></p><p>去重规则：key 对象重写 </p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">hashCode() + equals()</span><br></pre></td></tr></table></figure></li></ul><p>② LinkedHashMap</p><p>继承 HashMap；哈希表 + 双向链表</p><p><strong>存取有序</strong>；其他特性和 HashMap 一致。</p><p>可用来实现 LRU 缓存。</p><p>③ TreeMap</p><p>底层红黑树；按键自动排序</p><p>key 必须实现 Comparable 或传入比较器；key 不能为 null。</p><p>④ Hashtable（淘汰）</p><p>线程安全；key 和 value 都不允许 null；扩容 2 倍；性能差，基本不用。</p><p>⑤ Properties</p><p>继承 Hashtable；专门读取配置文件 xxx.properties；key、value 只能是字符串。</p><p>三、重要对比</p><p>HashMap vs HashTable</p><ol><li>HashMap 线程不安全；Hashtable 方法加 synchronized，安全</li><li>HashMap 允许 key 一个 null；Hashtable 键值都不能 null</li><li>HashMap 初始容量 16，扩容 ×2；Hashtable 初始 11，扩容 ×2+1</li><li>Hashtable 古老，不推荐使用；多线程可用 ConcurrentHashMap</li></ol><p>HashSet 和 HashMap 关系</p><ol><li><p>HashSet 底层就是 HashMap！</p></li><li><p>HashSet 存储元素，作为 HashMap 的 key，value 统一是一个静态 Object 常量。</p></li></ol><p>四、Collections 工具类（区分 Collection 接口！）</p><p><code>java.util.Collections</code> 操作集合的静态工具类</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Collections.sort(list);        <span class="comment">//排序</span></span><br><span class="line">Collections.shuffle(list);     <span class="comment">//打乱顺序</span></span><br><span class="line">Collections.max/min(list);</span><br><span class="line">Collections.unmodifiableList() <span class="comment">//返回不可变集合</span></span><br><span class="line"><span class="comment">//线程安全集合</span></span><br><span class="line">Collections.synchronizedList(list);</span><br></pre></td></tr></table></figure><p>注意：只是方法加锁，性能一般；高并发优先 JUC 工具。</p><p>五、高频坑 &amp; 考点总结</p><ol><li><strong>迭代遍历集合时，不能直接集合 add/remove → ConcurrentModificationException 并发修改异常</strong></li><li>HashSet/HashMap 自定义对象作为 key，<strong>必须重写 hashCode 和 equals</strong></li><li>ArrayList 初始容量 10，第一次 add 才初始化数组；扩容 1.5 倍</li><li>HashMap 负载因子 0.75，树化阈值 8，链表转红黑树</li><li>List 有序可重复；Set 无序不可重复；Map 键唯一</li><li>TreeSet/TreeMap 依靠比较器排序，不是 equals 去重</li><li>集合存储的是<strong>对象引用地址</strong>，不是对象本身</li><li>自动装箱：集合不能存基本类型 <code>List&lt;int&gt;</code> 报错，只能包装类 <code>List&lt;Integer&gt;</code></li></ol><p>六、线程安全小结</p><p>不安全：ArrayList、LinkedList、HashMap、HashSet</p><p>安全方案：</p><ol><li>Collections.synchronizedXxx() 包装</li><li>JUC 并发包：CopyOnWriteArrayList、ConcurrentHashMap</li></ol><h2 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h2><p>一、什么是泛型</p><p><strong>泛型：参数化类型</strong>，把<strong>类型当作参数</strong>传入类、接口、方法。</p><p><code>&lt;&gt;</code>内的标识符代表未知类型，编译期确定。</p><p>核心目标：</p><ol><li><strong>编译期类型检查</strong>，防止类型转换异常</li><li><strong>消除强制类型转换</strong></li><li>代码复用，一套代码支持多种数据类型</li></ol><blockquote><p>诞生：JDK 1.5 引入；<strong>泛型只存在编译期，运行时被擦除（类型擦除）</strong></p></blockquote><p>二、为什么需要泛型</p><p>没有泛型时，集合默认存储 Object：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;abc&quot;</span>);</span><br><span class="line">list.add(<span class="number">123</span>); <span class="comment">//编译不报错</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> (String) list.get(<span class="number">1</span>); <span class="comment">//运行时报 ClassCastException</span></span><br></pre></td></tr></table></figure><p>使用泛型：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;abc&quot;</span>);</span><br><span class="line"><span class="comment">// list.add(123); 编译直接报错！</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> list.get(<span class="number">0</span>); <span class="comment">//无需强转</span></span><br></pre></td></tr></table></figure><p>三、泛型的三种使用形式</p><ol><li>泛型类</li></ol><p>格式：<code>类名&lt;泛型标识&gt;</code></p><p>常用标识符约定（只是名字，无语法要求）</p><ul><li><code>E</code> Element 元素（集合）</li><li><code>T</code> Type 类型</li><li><code>K</code> Key 键 、<code>V</code> Value 值（Map）</li><li><code>R</code> Return 返回值</li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBox</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> T data;</span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">getData</span><span class="params">()</span>&#123; <span class="keyword">return</span> data; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setData</span><span class="params">(T data)</span>&#123; <span class="built_in">this</span>.data = data; &#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">MyBox&lt;String&gt; box = <span class="keyword">new</span> <span class="title class_">MyBox</span>&lt;&gt;();</span><br><span class="line">box.setData(<span class="string">&quot;hello&quot;</span>);</span><br></pre></td></tr></table></figure><blockquote><p>注意：创建对象时指定类型，这个类里面所有 T 都会被替换为指定类型。</p></blockquote><ol><li>泛型接口</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">IStorage</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">save</span><span class="params">(T data)</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//方式1：实现类确定类型</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">StringStorage</span> <span class="keyword">implements</span> <span class="title class_">IStorage</span>&lt;String&gt;&#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_">save</span><span class="params">(String data)</span> &#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//方式2：实现类继续保留泛型</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AnyStorage</span>&lt;T&gt; <span class="keyword">implements</span> <span class="title class_">IStorage</span>&lt;T&gt;&#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_">save</span><span class="params">(T data)</span> &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>泛型方法（重点：和泛型类区分开）</li></ol><p><strong>泛型方法可以定义在普通类 / 泛型类上，方法自己拥有独立泛型参数</strong></p><p>格式：<strong>返回值前声明泛型 <code>&lt;T&gt;</code></strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//泛型方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; T <span class="title function_">getFirst</span><span class="params">(List&lt;T&gt; list)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> list.get(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键区分：</p><ul><li>泛型类的 T：创建对象时确定</li><li>泛型方法的 T：<strong>调用方法时自动推导类型</strong></li></ul><p>四、类型擦除</p><blockquote><p><strong>泛型是编译期概念，.class 字节码中不存在泛型信息！</strong></p><p>编译流程：</p></blockquote><ol><li>编译器利用泛型做类型校验</li><li><strong>擦除泛型参数</strong>，替换为原始类型（上限 Object / 指定边界类型）</li><li>自动插入强制类型转换代码</li></ol><p>示例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">list.add(<span class="string">&quot;aaa&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>编译后字节码等价于：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">list.add(<span class="string">&quot;aaa&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> (String) list.get(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>原始类型 RawType</p><p>不加泛型参数的集合：<code>List list = new ArrayList();</code></p><p>原始类型会关闭泛型检查，尽量不要使用。</p><p>擦除规则</p><ol><li>无边界泛型 <code>&lt;T&gt;</code> → 擦除为 <code>Object</code></li><li>有上限 <code>&lt;T extends Number&gt;</code> → 擦除为 <code>Number</code></li><li>有下限不能单独用于类泛型（只能通配符）</li></ol><p>五、通配符 <code>?</code> Wildcard</p><p><code>?</code> 代表<strong>任意未知类型</strong>，主要用于方法参数，不能用于定义泛型类 / 泛型方法。</p><ol><li>无界通配符 <code>&lt;?&gt;</code></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">printList</span><span class="params">(List&lt;?&gt; list)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span>(Object o : list) System.out.println(o);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>限制：</p><p>可以读取元素，读到 Object</p><p><strong>不能 add 除 null 以外的任何元素</strong>（编译器不知道真实类型，禁止写入）</p><ol><li>上限通配符 <code>? extends T</code></li></ol><p><code>? extends Animal</code>：代表<strong>T 及其所有子类</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">(List&lt;? extends Animal&gt; list)</span>&#123;</span><br><span class="line">    <span class="type">Animal</span> <span class="variable">a</span> <span class="operator">=</span> list.get(<span class="number">0</span>); <span class="comment">//可读，向上转型</span></span><br><span class="line">    <span class="comment">// list.add(new Dog()); //编译报错！禁止写入</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>读取安全，写入不安全。<strong>生产者场景（读数据）</strong></p><ol><li>下限通配符 <code>? super T</code></li></ol><p><code>? super Dog</code>：代表<strong>Dog 及其所有父类</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">(List&lt;? <span class="built_in">super</span> Dog&gt; list)</span>&#123;</span><br><span class="line">    list.add(<span class="keyword">new</span> <span class="title class_">Dog</span>());    <span class="comment">//可以存入Dog及子类</span></span><br><span class="line">    <span class="comment">//Dog dog = list.get(0); //读取只能拿到Object，无法向下转型</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>写入安全，读取不安全。<strong>消费者场景（写入数据）</strong></p><p>PECS 原则（高频面试）</p><p><strong>Producer Extends，Consumer Super</strong></p><ul><li>只读取数据（生产者）：<code>? extends T</code></li><li>只写入数据（消费者）：<code>? super T</code></li><li>既要读又要写：不能使用通配符，直接确定泛型<code>&lt;T&gt;</code></li></ul><p>六、泛型边界（限定类型范围）</p><p>上限限定 <code>&lt;T extends 类/接口&gt;</code></p><p>T 只能是该类或子类</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// T必须是Number或者Number子类(Integer/Double等)</span></span><br><span class="line"><span class="keyword">public</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Number</span>&gt; <span class="keyword">void</span> <span class="title function_">method</span><span class="params">(T num)</span>&#123;&#125;</span><br></pre></td></tr></table></figure><blockquote><p>泛型<strong>没有多上限直接写法</strong>；想要同时多个约束，需要 <code>T extends A &amp; B</code></p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">&lt;T <span class="keyword">extends</span> <span class="title class_">Serializable</span> &amp; Comparable&gt;</span><br></pre></td></tr></table></figure><p>注意：<strong>没有 <code>&lt;T super xxx&gt;</code> 写法！</strong></p><p>下限<strong>只能用于通配符？</strong>，不能用于泛型声明。</p><p>七、泛型常见限制</p><ol><li><strong>不能使用泛型创建实例</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">T</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">T</span>(); <span class="comment">//编译报错！擦除后无法确定类型</span></span><br></pre></td></tr></table></figure><ol><li><strong>不能定义泛型数组</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">T[] arr = <span class="keyword">new</span> <span class="title class_">T</span>[<span class="number">10</span>]; <span class="comment">//报错</span></span><br><span class="line"><span class="comment">//可以通过反射创建：Array.newInstance</span></span><br></pre></td></tr></table></figure><ol><li><strong>静态方法不能使用类上定义的泛型</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span>&lt;T&gt;&#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_">fun</span><span class="params">(T data)</span>&#123;&#125; <span class="comment">//报错！</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>原因：类泛型在创建对象时确定，静态方法不需要对象。</p><p>解决方案：使用<strong>独立泛型方法</strong><code>&lt;E&gt;</code></p><ol><li><p><strong>基本类型不能作为泛型参数</strong></p><p><code>List&lt;int&gt;</code> ❌；必须使用包装类 <code>List&lt;Integer&gt;</code> ✅</p></li><li><p><strong>泛型不支持继承多态误区</strong></p></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list1 = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">List&lt;Object&gt; list2 = list1; <span class="comment">//编译报错！</span></span><br></pre></td></tr></table></figure><p>虽然 String 是 Object 子类，但是 <code>List&lt;String&gt;</code><strong>不是 <code>List&lt;Object&gt;</code> 的子类</strong>。泛型容器之间不存在继承关系！</p><ol><li>重载不能依靠泛型区分（擦除后方法签名一样）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">fun</span><span class="params">(List&lt;String&gt; list)</span>&#123;&#125;</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">fun</span><span class="params">(List&lt;Integer&gt; list)</span>&#123;&#125; <span class="comment">//编译冲突，擦除后都是List</span></span><br></pre></td></tr></table></figure><p>八、泛型方法 vs 通配符怎么选？</p><p>示例需求：把 list 所有元素添加到另一个集合</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//方案1：泛型方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">copy</span><span class="params">(List&lt;T&gt; src, List&lt;T&gt; dest)</span>&#123;</span><br><span class="line">    dest.addAll(src);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//方案2：通配符</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">copy2</span><span class="params">(List&lt;? extends T&gt; src, List&lt;T&gt; dest)</span></span><br></pre></td></tr></table></figure><ul><li>需要<strong>同时读写集合</strong>、类型互相约束 → 使用泛型<code>&lt;T&gt;</code></li><li>只单纯读取或写入 → 使用通配符</li></ul><p>九、易错知识点</p><ol><li><p>泛型实现原理？</p><p>答：<strong>类型擦除</strong>，编译期校验，运行时无泛型信息。</p></li><li><p><code>List&lt;String&gt;</code> 和 <code>List&lt;Object&gt;</code> 有继承关系吗？</p><p>没有，泛型容器不存在多态，擦除后都是 List。</p></li></ol><p>十、知识点总结</p><p>泛型参数化类型，JDK1.5 推出；</p><p>编译检查免强转，运行类型被擦除；</p><p>三类用法：泛型类、泛型接口、泛型方法；</p><p>任意未知类型，extends上界super下界；</p><p>PECS 口诀：读用 extends，写用 super；</p><p>不能 new 泛型对象、不能泛型数组；</p><p>静态方法不能使用类泛型；</p><p>容器泛型无继承，<code>List&lt;String&gt;</code>不是 <code>List&lt;Object&gt;</code>；</p><p>基本类型不能做泛型，需要包装类。</p><h2 id="JUnit"><a href="#JUnit" class="headerlink" title="JUnit"></a>JUnit</h2><p>一、JUnit 是什么</p><p>JUnit 是 Java <strong>单元测试框架</strong>，用于对方法 / 模块进行自动化单元测试，属于 xUnit 体系。</p><p>作用：</p><ol><li>独立测试单个方法，验证代码逻辑是否正确</li><li>自动化执行，快速回归，避免手动 main 方法测试</li><li>支持断言、前置 / 后置操作、忽略测试、异常测试、超时测试</li></ol><p>两大主流版本：</p><ol><li><strong>JUnit4</strong>：JDK1.5+，核心注解 <code>@Test</code>，基于方法</li><li><strong>JUnit5（推荐新项目）</strong>，别名 Jupiter，JDK8+，架构重构，功能更强</li></ol><blockquote><p>Maven 坐标区分：</p></blockquote><ul><li>JUnit4：<code>junit:junit:4.13.2</code></li><li>JUnit5：由多个模块组成 <code>junit-jupiter-api</code>（编写）、<code>junit-jupiter-engine</code>（运行）</li></ul><p>二、JUnit4 核心知识点（老项目大量使用）</p><ol><li>核心注解</li></ol><div class="table-container"><table><thead><tr><th style="text-align:center">注解</th><th style="text-align:center">作用</th></tr></thead><tbody><tr><td style="text-align:center"><code>@Test</code></td><td style="text-align:center">标记该方法为<strong>测试方法</strong>；方法必须 public void，无参数</td></tr><tr><td style="text-align:center"><code>@Before</code></td><td style="text-align:center"><strong>每个测试方法执行前运行</strong>（每个 @Test 都会触发一次）</td></tr><tr><td style="text-align:center"><code>@After</code></td><td style="text-align:center"><strong>每个测试方法执行完成后运行</strong></td></tr><tr><td style="text-align:center"><code>@BeforeClass</code></td><td style="text-align:center"><strong>所有测试方法执行前只执行一次</strong>；方法必须 <code>public static void</code></td></tr><tr><td style="text-align:center"><code>@AfterClass</code></td><td style="text-align:center"><strong>所有测试执行完毕后执行一次</strong>；方法必须 <code>public static void</code></td></tr><tr><td style="text-align:center"><code>@Ignore</code></td><td style="text-align:center">忽略该测试方法，不执行</td></tr><tr><td style="text-align:center"><code>@Test(expected = 异常.class)</code></td><td style="text-align:center">测试预期抛出指定异常</td></tr><tr><td style="text-align:center"><code>@Test(timeout = 毫秒)</code></td><td style="text-align:center">设置测试超时时间，超时判定失败</td></tr></tbody></table></div><p>执行顺序示例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@BeforeClass</span> →</span><br><span class="line">    <span class="meta">@Before</span> → <span class="meta">@Test</span> → <span class="meta">@After</span></span><br><span class="line">    <span class="meta">@Before</span> → <span class="meta">@Test</span> → <span class="meta">@After</span></span><br><span class="line"><span class="meta">@AfterClass</span></span><br></pre></td></tr></table></figure><ol><li>Assert 断言（单元测试核心）</li></ol><p><code>org.junit.Assert</code>，断言失败测试直接报错</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Assert.assertEquals(预期值,实际值);    <span class="comment">//相等</span></span><br><span class="line">Assert.assertNotEquals();</span><br><span class="line">Assert.assertTrue(布尔条件);</span><br><span class="line">Assert.assertFalse();</span><br><span class="line">Assert.assertNull(obj);</span><br><span class="line">Assert.assertNotNull(obj);</span><br><span class="line">Assert.assertSame(对象<span class="number">1</span>,对象<span class="number">2</span>);    <span class="comment">//== 引用相同</span></span><br><span class="line">Assert.assertNotSame();</span><br></pre></td></tr></table></figure><blockquote><p>规则：单元测试<strong>必须使用断言</strong>，不能靠 System.out 打印判断结果！</p></blockquote><ol><li>JUnit4 代码示例</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.junit.*;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DemoTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@BeforeClass</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_">initAll</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">@Before</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</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">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testAdd</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">1</span>+<span class="number">2</span>;</span><br><span class="line">        Assert.assertEquals(<span class="number">3</span>,res);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test(expected = ArithmeticException.class)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testException</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>/<span class="number">0</span>; <span class="comment">//预期抛出算术异常</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">close</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">@AfterClass</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_">destroyAll</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">&#125;</span><br></pre></td></tr></table></figure><p>三、JUnit5</p><p>重要变化</p><ol><li>包名更换：<code>org.junit.jupiter.api</code></li><li>注解名称大量调整（去掉旧版部分歧义命名）</li><li>不再强制要求方法 public（private 以外都可以）</li><li>提供更灵活断言、嵌套测试、参数化测试</li><li>支持扩展模型替代 JUnit4 的 Rule</li></ol><p>JUnit5 核心注解</p><div class="table-container"><table><thead><tr><th style="text-align:center">JUnit5</th><th style="text-align:center">等效 JUnit4</th><th style="text-align:center">说明</th></tr></thead><tbody><tr><td style="text-align:center"><code>@Test</code></td><td style="text-align:center"><code>@Test</code></td><td style="text-align:center">测试方法</td></tr><tr><td style="text-align:center"><code>@BeforeEach</code></td><td style="text-align:center"><code>@Before</code></td><td style="text-align:center">每个测试方法前执行</td></tr><tr><td style="text-align:center"><code>@AfterEach</code></td><td style="text-align:center"><code>@After</code></td><td style="text-align:center">每个测试方法后执行</td></tr><tr><td style="text-align:center"><code>@BeforeAll</code></td><td style="text-align:center"><code>@BeforeClass</code></td><td style="text-align:center">所有测试前执行一次，建议 static</td></tr><tr><td style="text-align:center"><code>@AfterAll</code></td><td style="text-align:center"><code>@AfterClass</code></td><td style="text-align:center">所有测试后执行一次</td></tr><tr><td style="text-align:center"><code>@Disabled</code></td><td style="text-align:center"><code>@Ignore</code></td><td style="text-align:center">禁用 / 忽略测试</td></tr><tr><td style="text-align:center"><code>@DisplayName(&quot;测试名称&quot;)</code></td><td style="text-align:center">无</td><td style="text-align:center">自定义测试显示名称，可读性强</td></tr><tr><td style="text-align:center"><code>@Timeout</code></td><td style="text-align:center">@Test(timeout)</td><td style="text-align:center">超时限制</td></tr><tr><td style="text-align:center"><code>@ExtendWith</code></td><td style="text-align:center">@Rule/@ClassRule</td><td style="text-align:center">扩展机制</td></tr></tbody></table></div><p>JUnit5 断言：Assertions</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">org.junit.jupiter.api.Assertions</span><br></pre></td></tr></table></figure><p>支持 lambda，失败信息更友好</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Assertions.assertEquals(<span class="number">3</span>, <span class="number">1</span>+<span class="number">2</span>,<span class="string">&quot;相加结果错误&quot;</span>);</span><br><span class="line">Assertions.assertTrue(<span class="number">2</span>&gt;<span class="number">1</span>);</span><br><span class="line"><span class="comment">//分组断言，全部执行后统一报错，不会遇到第一个失败就终止</span></span><br><span class="line">Assertions.assertAll(</span><br><span class="line">    ()-&gt;Assertions.assertEquals(<span class="number">2</span>,<span class="number">1</span>+<span class="number">1</span>),</span><br><span class="line">    ()-&gt;Assertions.assertTrue(<span class="number">5</span>&gt;<span class="number">2</span>)</span><br><span class="line">);</span><br><span class="line"><span class="comment">//断言异常</span></span><br><span class="line">Assertions.assertThrows(ArithmeticException.class,()-&gt;&#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>/<span class="number">0</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>JUnit5 简单示例</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.junit.jupiter.api.*;</span><br><span class="line"><span class="meta">@DisplayName(&quot;计算器单元测试&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CalcTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@BeforeAll</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">beforeAll</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">@BeforeEach</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">beforeEach</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">@Test</span></span><br><span class="line">    <span class="meta">@DisplayName(&quot;测试加法&quot;)</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testPlus</span><span class="params">()</span>&#123;</span><br><span class="line">        Assertions.assertEquals(<span class="number">10</span>,<span class="number">5</span>+<span class="number">5</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Disabled</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">skipTest</span><span class="params">()</span>&#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="meta">@AfterEach</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">afterEach</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">@AfterAll</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">afterAll</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">&#125;</span><br></pre></td></tr></table></figure><p>四、重要概念区分</p><ol><li><strong>单元测试 vs 集成测试</strong></li></ol><ul><li>单元测试：测试<strong>单个类 / 单个方法</strong>，尽量不依赖数据库、网络；可以 Mock 依赖（Mockito）</li><li>集成测试：多个模块、数据库、接口联调</li></ul><ol><li>测试规范（开发通用规范）</li></ol><ul><li>测试类命名：<code>XxxTest</code></li><li>测试方法命名：<code>test方法名</code> / <code>shouldXXXWhenXXX</code></li><li>一个测试方法只测一个场景</li><li>尽量独立：测试方法之间不能互相依赖，执行顺序不影响结果</li></ul><p>五、配套常用扩展</p><ol><li><strong>Mockito</strong>：配合 JUnit，模拟对象（Mock），解除外部依赖</li><li><strong>ParameterizedTest 参数化测试</strong>（JUnit5）：一组多组输入批量测试，不用重复写多个 @Test</li></ol><p>六、重要知识点区分</p><ol><li><p><code>@Before</code> 和 <code>@BeforeClass</code> 区别？</p><p>Before：每个测试方法执行前；BeforeClass 全局只执行一次，必须 static。</p><p>JUnit5 对应：BeforeEach / BeforeAll。</p></li><li><p>单元测试中 Assert 断言作用？</p><p>验证预期结果；断言失败测试用例标记失败。不能只用打印输出。</p></li><li><p>JUnit4 和 JUnit5 主要区别？</p><p>包路径不同、注解改名、方法不必 public、强大的分组断言、扩展机制替代 Rule。</p></li><li><p>单元测试方法有什么要求？</p><p>JUnit4：public void、无参数；</p><p>JUnit5：返回 void，无参，访问修饰符不限。</p></li><li><p>@Test(expected=xxx) 缺点？</p><p>只能捕获任意位置该异常，无法精准定位哪行代码抛出；JUnit5 推荐 assertThrows。</p></li></ol><h2 id="多线程编程"><a href="#多线程编程" class="headerlink" title="多线程编程"></a>多线程编程</h2><h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><ul><li><strong>进程</strong>：操作系统资源分配的最小单位（独立内存、CPU 资源），一个程序就是一个进程。</li><li><strong>线程</strong>：进程内独立执行路径，<strong>CPU 调度最小单位</strong>，共享进程资源。</li><li>关系：一个进程至少包含 1 个主线程；多线程 = 一个进程多条执行路径。</li></ul><h3 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h3><p>优点：</p><ol><li>充分利用多核 CPU，提高程序吞吐量</li><li>异步处理，提升响应速度（GUI、接口）</li></ol><p>缺点：</p><ol><li>线程切换消耗 CPU 资源</li><li><strong>共享资源竞争 → 线程安全问题</strong></li><li>并发带来死锁、可见性、有序性问题</li></ol><h3 id="并发-amp-并行"><a href="#并发-amp-并行" class="headerlink" title="并发 &amp; 并行"></a>并发 &amp; 并行</h3><ul><li><strong>并发</strong>：单核 CPU，时间片轮转，宏观同时执行，微观交替执行</li><li><strong>并行</strong>：多核 CPU，多个线程真正同时运行</li></ul><h3 id="创建线程的-4-种方式"><a href="#创建线程的-4-种方式" class="headerlink" title="创建线程的 4 种方式"></a>创建线程的 4 种方式</h3><p>方式 1：继承 Thread 类</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</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_">run</span><span class="params">()</span> &#123;</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 class="comment">//启动</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">MyThread</span>().start();</span><br></pre></td></tr></table></figure><p><strong>调用 start()，不是 run()！</strong></p><p>run() 只是普通方法；start() 向操作系统申请创建新线程，自动调用 run()</p><p>方式 2：实现 Runnable 接口（推荐）</p><p>解耦：线程类和任务分离；避免单继承限制</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Task</span> <span class="keyword">implements</span> <span class="title class_">Runnable</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_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Task</span>()).start();</span><br></pre></td></tr></table></figure><p>方式 3：Callable + FutureTask（有返回值、可抛异常）</p><p>Runnable 无返回值、不能抛出受检异常；Callable 弥补该缺陷</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Callable&lt;Integer&gt; call = ()-&gt;&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">100</span>;</span><br><span class="line">&#125;;</span><br><span class="line">FutureTask&lt;Integer&gt; task = <span class="keyword">new</span> <span class="title class_">FutureTask</span>&lt;&gt;(call);</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(task).start();</span><br><span class="line"><span class="type">Integer</span> <span class="variable">res</span> <span class="operator">=</span> task.get(); <span class="comment">//阻塞获取结果</span></span><br></pre></td></tr></table></figure><p>方式 4：线程池 ExecutorService（生产环境强制使用）</p><p>频繁创建销毁线程开销巨大，池化复用线程。</p><p><code>Executors</code>工具类 / <code>ThreadPoolExecutor</code>手动创建（阿里规范推荐）</p><blockquote><p>面试重点区分：</p><p>Thread 是线程载体；Runnable/Callable 是<strong>任务</strong></p></blockquote><h3 id="线程的五大状态（操作系统层面）"><a href="#线程的五大状态（操作系统层面）" class="headerlink" title="线程的五大状态（操作系统层面）"></a>线程的五大状态（操作系统层面）</h3><ol><li><strong>新建 NEW</strong>：new Thread()，尚未调用 start()</li><li><strong>就绪 RUNNABLE</strong>：调用 start()，等待 CPU 时间片</li><li><strong>运行 RUNNING</strong>：拿到 CPU，执行 run 方法</li><li><strong>阻塞 BLOCKED / WAITING / TIMED_WAITING</strong><ul><li>BLOCKED：等待 synchronized 锁</li><li>WAITING：无限等待 wait()</li><li>TIMED_WAITING：限时等待 sleep(time)、wait(time)</li></ul></li><li><strong>终止 TERMINATED</strong>：run 执行完毕 / 异常退出，线程结束</li></ol><blockquote><p>Java Thread.State 将【就绪 + 运行】合并为 <strong>RUNNABLE</strong>，JVM 层面看不到操作系统细分状态！</p></blockquote><h3 id="线程常用方法"><a href="#线程常用方法" class="headerlink" title="线程常用方法"></a>线程常用方法</h3><p>Thread 类实例方法</p><ol><li><code>start()</code>：启动线程</li><li><code>run()</code>：任务逻辑</li><li><code>interrupt()</code>：中断线程（<strong>设置中断标记，不是立刻终止</strong>）</li><li><code>isInterrupted()</code>：判断是否被中断（不清空标记）</li><li><code>join()</code>：等待其他线程执行完成（底层 wait）</li></ol><p>Thread 静态方法</p><ol><li><p><code>Thread.sleep(ms)</code>：<strong>限时阻塞，不释放锁</strong></p><p>主动让出 CPU，时间到自动恢复就绪；会抛出 InterruptedException，捕获异常时中断标记会被清除</p></li><li><p><code>Thread.yield()</code>：礼让 CPU，当前线程回到就绪态（不一定生效）</p></li><li><p><code>Thread.currentThread()</code>：获取当前执行线程</p></li></ol><p>Object 监视器方法（必须在 synchronized 内调用！）</p><ol><li><code>wait()</code>：无限等待，<strong>释放锁</strong></li><li><code>wait(long)</code>：限时等待，释放锁</li><li><code>notify()</code>：随机唤醒一个等待线程</li><li><code>notifyAll()</code>：唤醒全部等待线程</li></ol><h3 id="synchronized"><a href="#synchronized" class="headerlink" title="synchronized"></a>synchronized</h3><p>基础概念</p><p><code>synchronized</code> 是 Java <strong>内置监视器锁（互斥锁）</strong>，属于 JVM 层面实现，用来解决多线程<strong>原子性、可见性、有序性</strong>问题，保证同一时刻最多只有一个线程执行同步代码。</p><blockquote><p>别称：隐式锁、内置锁、monitor 锁。</p></blockquote><p>作用：</p><ol><li><strong>原子性</strong>：同步代码块同一时间仅一个线程执行</li><li><strong>可见性</strong>：解锁前强制把共享变量刷新到主内存；加锁清空本地缓存</li><li><strong>有序性</strong>：禁止临界区内指令重排序</li></ol><h4 id="三种使用方式"><a href="#三种使用方式" class="headerlink" title="三种使用方式"></a>三种使用方式</h4><ol><li>修饰【实例方法】</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>锁对象：<strong>当前实例对象 this</strong></p><p>同一个对象多个同步方法互斥；不同对象互不影响。</p><ol><li>修饰【静态方法】</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>锁对象：<strong>当前类的 Class 对象（类锁）</strong></p><p>所有实例共用同一把锁，不管创建多少对象，串行执行。</p><blockquote><p>重点区分：</p><p><code>synchronized静态方法</code> 和 <code>synchronized实例方法</code><strong>互不阻塞！两把不同的锁</strong></p></blockquote><ol><li>修饰【同步代码块】（推荐，粒度可控）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//锁任意对象</span></span><br><span class="line"><span class="keyword">synchronized</span> (obj) &#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//类锁写法</span></span><br><span class="line"><span class="keyword">synchronized</span> (ClassName.class) &#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//当前对象锁</span></span><br><span class="line"><span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>优势：缩小锁范围，提高并发效率。</p><h3 id="什么是线程同步"><a href="#什么是线程同步" class="headerlink" title="什么是线程同步"></a>什么是线程同步</h3><p><strong>多个线程同时访问共享资源，控制访问顺序，保证数据安全，避免并发冲突。</strong></p><p>根本问题：多线程并发操作共享变量，产生<strong>数据竞争</strong>。</p><p>三大并发问题：<strong>原子性、可见性、有序性</strong>。</p><blockquote><p>同步本质：<strong>让并发操作串行化（部分串行）</strong>，牺牲吞吐量换取正确性。</p></blockquote><p>二、什么时候需要同步？</p><p>同时满足下面 3 点，一定会出现线程安全问题：</p><ol><li><p><strong>多线程</strong></p></li><li><p><strong>共享变量（共享资源）</strong></p></li><li><p><strong>存在写操作（修改）</strong></p><p>只读场景不需要同步。</p></li></ol><p>示例不安全代码：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Count</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span>&#123;</span><br><span class="line">        num++; <span class="comment">//读取、+1、回写，三步，不具备原子性</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>三、线程同步的四大实现方案</p><p>方案 1：synchronized 内置同步锁（监视器锁）</p><p>前面详细讲过，简单回顾同步用法</p><ol><li>同步实例方法：锁 this</li><li>同步静态方法：锁 Class 对象</li><li>同步代码块：自定义锁对象</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//标准写法，锁对象建议 private final</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">        num++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>特性：可重入、自动释放锁、异常自动解锁；非公平锁。</p><blockquote><p>配套等待唤醒机制：<code>wait() / notify() / notifyAll()</code></p><p>典型模型：生产者消费者。</p></blockquote><p>方案 2：Lock 显式锁（AQS）</p><p><code>ReentrantLock</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Lock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">()</span>&#123;</span><br><span class="line">    lock.lock();</span><br><span class="line">    <span class="keyword">try</span>&#123;</span><br><span class="line">        num++;</span><br><span class="line">    &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">        lock.unlock(); <span class="comment">//必须finally释放</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>扩展能力：</p><ul><li>公平锁 / 非公平锁</li><li><code>tryLock()</code> 非阻塞尝试获取锁</li><li><code>lockInterruptibly()</code> 支持中断等待</li><li>多个 Condition 条件变量，精准唤醒线程</li></ul><p>方案 3：volatile（轻量级同步手段）</p><p><strong>volatile ≠ 锁，不能实现完整同步！</strong></p><p>作用：</p><p>保证可见性、禁止指令重排序</p><p><strong>无法保证原子性（不能解决 num++）</strong></p><p>适用场景：状态标记、DCL 单例。</p><p>方案 4：无锁同步：CAS 原子类（乐观同步）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">java.util.concurrent.atomic</span><br><span class="line">AtomicInteger、AtomicLong</span><br></pre></td></tr></table></figure><p>底层：CAS（CompareAndSwap）乐观锁，不阻塞线程。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">AtomicInteger</span> <span class="variable">num</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line">num.incrementAndGet(); <span class="comment">//原子自增</span></span><br></pre></td></tr></table></figure><p>优点：无锁、性能高；</p><p>问题：存在 <strong>ABA 问题</strong>，解决方案：<code>AtomicStampedReference</code>增加版本号。</p><p>四、同步两大模型：悲观锁 VS 乐观锁</p><ol><li><p><strong>悲观锁</strong></p><p>默认一定会发生竞争，访问资源先上锁。</p><p>代表：<code>synchronized、ReentrantLock</code></p><p>线程竞争失败阻塞。</p></li><li><p><strong>乐观锁</strong></p><p>默认竞争很少，不加锁；修改时校验数据是否被别人修改，冲突则重试。</p><p>代表：CAS 原子类、数据库版本号机制。</p></li></ol><p>五、线程间同步通信（等待唤醒机制）</p><ol><li>synchronized + wait/notify</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">synchronized</span>(lock)&#123;</span><br><span class="line">    <span class="comment">//必须while判断，防止虚假唤醒！！</span></span><br><span class="line">    <span class="keyword">while</span>(条件不满足)&#123;</span><br><span class="line">        lock.wait(); <span class="comment">//释放锁，等待</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//业务操作</span></span><br><span class="line">    lock.notifyAll(); <span class="comment">//唤醒等待线程</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键点：</p><ul><li>wait <strong>释放锁</strong>；sleep 不释放锁</li><li>必须在同步代码块中调用</li><li>推荐 notifyAll，避免信号丢失</li></ul><ol><li>Lock + Condition</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Condition</span> <span class="variable">condition</span> <span class="operator">=</span> lock.newCondition();</span><br><span class="line">lock.lock();</span><br><span class="line"><span class="keyword">try</span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(条件不满足)&#123;</span><br><span class="line">        condition.await();</span><br><span class="line">    &#125;</span><br><span class="line">    condition.signalAll();</span><br><span class="line">&#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">    lock.unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>优势：可以创建多个 Condition，实现精准分组唤醒，粒度更细。</p><h3 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h3><ol><li><p>死锁四大必要条件（全部满足才会死锁）</p><ol><li><p><strong>互斥条件</strong>：锁同一时间只能一个线程持有</p></li><li><p><strong>请求并持有</strong>：线程持有锁，同时申请其他锁</p></li><li><p><strong>不可剥夺</strong>：锁不能被其他线程强行抢走</p></li><li><p><strong>循环等待</strong>：线程互相持有对方需要的锁</p></li></ol></li><li><p>解决思路：破坏任意一个条件</p></li></ol><p>最常用：<strong>破坏循环等待</strong> → 统一锁获取顺序</p><p>释放锁</p><p>一、会释放 synchronized 锁的 4 种情况</p><ol><li><p><strong>同步代码 / 同步方法正常执行完毕</strong></p><p>线程执行走出 <code>synchronized{}</code>代码块，或者同步方法运行结束，自动释放监视器锁。</p></li><li><p><strong>同步区域内遇到 break、return</strong></p><p>提前退出同步代码块 / 方法，锁依旧释放。</p></li><li><p><strong>同步区域抛出未捕获的 Exception / Error</strong></p><p>异常导致线程终止，JVM 自动释放锁（这是 synchronized 优势，不会死锁）。</p></li><li><p><strong>同步区域调用 <code>wait()</code></strong></p><p>线程立刻释放当前 monitor 锁，进入等待池；后续被<code>notify()</code>唤醒后，需要重新竞争锁才能继续执行。</p><p>二、不会释放 synchronized 锁的场景</p></li><li><p><strong>调用 <code>Thread.sleep()</code> / <code>Thread.yield()</code></strong>线程暂停执行，但仍然持有锁，其他线程无法进入同步区。</p><ul><li>sleep：休眠固定时长，抱着锁睡觉</li><li>yield：让出 CPU 时间片，回到就绪态，不释放锁</li></ul></li><li><p><strong>其他线程调用 <code>suspend()</code> 挂起当前线程</strong></p><p>线程被暂停，但持续持有锁。</p></li></ol><blockquote><p>官方废弃 API！不推荐使用，容易引发死锁。</p></blockquote><div class="table-container"><table><thead><tr><th>操作</th><th>是否释放 synchronized 锁</th></tr></thead><tbody><tr><td>正常执行完同步代码</td><td>释放</td></tr><tr><td>return /break 退出同步区</td><td>释放</td></tr><tr><td>同步代码抛出异常</td><td>释放</td></tr><tr><td><code>wait()</code></td><td>释放</td></tr><tr><td><code>sleep()</code> / <code>yield()</code></td><td>不释放</td></tr><tr><td><code>notify()/notifyAll()</code></td><td>不释放</td></tr><tr><td><code>suspend()</code>挂起线程</td><td>不释放</td></tr></tbody></table></div><h2 id="IO-流"><a href="#IO-流" class="headerlink" title="IO 流"></a>IO 流</h2><p>一、IO 概述</p><p>IO：Input (输入) / Output (输出)，<strong>以程序 (内存) 为参照物</strong></p><ul><li><p><strong>输入流</strong>：外部 → 内存（读数据）</p></li><li><p><strong>输出流</strong>：内存 → 外部（写数据）</p><p>外部数据源：文件、网络、内存数组、控制台等</p></li></ul><p>Java IO 分为三大体系：</p><ol><li><strong>BIO 同步阻塞 IO</strong>（传统 java.io，重点学习）</li><li><strong>NIO 同步非阻塞 IO</strong>（java.nio，通道、缓冲区）</li><li>AIO 异步非阻塞 IO（NIO.2）</li></ol><hr><p>二、BIO 四大核心抽象父类（java.io）</p><ol><li>字节流（处理一切文件：视频、图片、音频、文档）</li></ol><p>基类：<code>InputStream</code>（输入） / <code>OutputStream</code>（输出）</p><p>单位：<strong>byte 字节，8 位</strong></p><p>常用实现类：</p><ul><li>FileInputStream / FileOutputStream：文件原始字节流</li><li>BufferedInputStream / BufferedOutputStream：<strong>缓冲字节流（提升性能）</strong></li><li>ByteArrayInputStream / ByteArrayOutputStream：内存数组流</li><li>ObjectInputStream / ObjectOutputStream：对象序列化流</li></ul><ol><li>字符流（<strong>仅处理文本文件</strong>：txt、java 等）</li></ol><p>基类：<code>Reader</code>（输入） / <code>Writer</code>（输出）</p><p>单位：<strong>char 字符，适配编码（UTF-8/GBK）</strong></p><p>常用实现类：</p><ul><li>FileReader / FileWriter：文件字符流（底层依然包装字节流）</li><li>BufferedReader / BufferedWriter：缓冲字符流，BufferedReader 独有<code>readLine()</code></li><li>InputStreamReader / OutputStreamWriter：<strong>转换流（字节↔字符桥梁）</strong></li><li>PrintWriter：打印流，自动换行、自动刷新</li></ul><blockquote><p>核心区分：</p><p>图片、视频、压缩包 → <strong>字节流</strong></p><p>纯文本 → 字符流（更方便）</p></blockquote><p>三、流的分类方式</p><ol><li><strong>流向</strong>：输入流、输出流</li><li><strong>数据单位</strong>：字节流、字符流</li><li><strong>功能</strong>：<ul><li>节点流：直接对接数据源（FileInputStream、FileReader）</li><li>处理流（包装流）：包裹其他流，增加功能（BufferedXX、ObjectXX）</li></ul></li></ol><blockquote><p>装饰器设计模式：创建流时，层层包装。</p></blockquote><p>示例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//节点流</span></span><br><span class="line"><span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;a.txt&quot;</span>);</span><br><span class="line"><span class="comment">//缓冲处理流包装节点流</span></span><br><span class="line"><span class="type">BufferedInputStream</span> <span class="variable">bis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedInputStream</span>(fis);</span><br></pre></td></tr></table></figure><p>四、重要编码问题 </p><p><code>FileReader/FileWriter</code><strong>默认使用系统编码，无法手动指定编码，极易乱码！</strong></p><p>规范做法：使用<strong>转换流</strong>指定编码</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//字节流 + 转换流 = 指定编码读取文本</span></span><br><span class="line"><span class="type">InputStreamReader</span> <span class="variable">isr</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(<span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;a.txt&quot;</span>),<span class="string">&quot;UTF-8&quot;</span>);</span><br></pre></td></tr></table></figure><p>五、缓冲流原理</p><p>普通流：每次读写直接访问磁盘，频繁 IO，性能差</p><p>缓冲流：内置<strong>缓冲区数组</strong></p><p>读取：一次性从磁盘读取一批数据存入缓冲区，程序优先读缓冲区；</p><p>写入：数据先存入缓冲区，缓冲区满 / 手动 flush ()/ 关闭流时，一次性写入磁盘。</p><ul><li><code>flush()</code>：刷新缓冲区，强制把缓冲数据输出</li><li><code>close()</code>：内部会自动调用 flush ()</li></ul><p>六、对象流 &amp; 序列化</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ObjectInputStream / ObjectOutputStream</span><br></pre></td></tr></table></figure><p>作用：把 Java 对象写入文件 / 网络传输（序列化）</p><p>序列化要求</p><ol><li>对象所属类必须实现接口：<code>java.io.Serializable</code>（标记接口，无方法）</li><li><code>transient</code>修饰的成员变量<strong>不会参与序列化</strong>（临时数据，不保存）</li><li>静态变量不属于对象，不会序列化</li></ol><blockquote><p>反序列化不需要调用构造方法创建对象。</p></blockquote><p>七、IO 流标准操作步骤</p><ol><li>创建流对象（可能抛出 FileNotFoundException）</li><li>读写操作（read/write，IOException）</li><li><strong>关闭流！释放资源</strong></li></ol><blockquote><p>流占用操作系统文件句柄，不关闭会资源泄漏。</p></blockquote><p>资源关闭写法演进</p><ol><li><p>原始：<code>try-catch-finally</code> 手动 close</p></li><li><p><strong>推荐：try-with-resources（JDK1.7+）</strong></p><p><code>AutoCloseable</code>接口的流，代码执行完毕自动关闭，无需手动 finally</p></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span>(<span class="type">BufferedReader</span> <span class="variable">br</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(<span class="keyword">new</span> <span class="title class_">FileReader</span>(<span class="string">&quot;a.txt&quot;</span>)))&#123;</span><br><span class="line">    String line;</span><br><span class="line">    <span class="keyword">while</span>((line = br.readLine()) != <span class="literal">null</span>)&#123;</span><br><span class="line">        System.out.println(line);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;<span class="keyword">catch</span> (IOException e)&#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>八、常用方法汇总</p><p>字节流 InputStream</p><ul><li><code>read()</code>：读取 1 字节，返回 int；末尾返回 - 1</li><li><code>read(byte[] buf)</code>：批量读取到字节数组，返回读取长度</li></ul><p>字符流 Reader</p><ul><li><code>read()</code>：读取单个字符</li><li><code>read(char[] buf)</code></li><li>BufferedReader：<code>readLine()</code> 读取一行，读到末尾返回 null</li></ul><p>输出通用</p><ul><li>write ()：写入数据</li><li>flush ()：刷新缓冲区</li><li>close ()：关闭流</li></ul><p>九、IO 常见坑</p><ol><li>文件复制使用字符流：图片视频损坏</li><li>FileReader 不指定编码，跨平台乱码</li><li>忘记关闭流 / 忘记 flush，缓冲数据丢失</li><li>读取循环判断条件错误（没有判断返回 - 1/null，死循环）</li><li>序列化类新增属性，未定义<code>serialVersionUID</code>导致反序列化失败</li></ol><p>十、BIO、NIO 简单区分</p><div class="table-container"><table><thead><tr><th style="text-align:center">类型</th><th style="text-align:center">模型</th><th style="text-align:center">特点</th></tr></thead><tbody><tr><td style="text-align:center">BIO</td><td style="text-align:center">同步阻塞</td><td style="text-align:center">一连接一线程；代码简单，高并发性能差</td></tr><tr><td style="text-align:center">NIO</td><td style="text-align:center">同步非阻塞</td><td style="text-align:center">通道 + 缓冲区；单线程轮询，多路复用</td></tr><tr><td style="text-align:center">AIO</td><td style="text-align:center">异步非阻塞</td><td style="text-align:center">操作系统通知回调，适合高并发长连接</td></tr></tbody></table></div><ol><li>基础字节流 文件复制（普通 FileStream）</li></ol><p>适用：任意文件（图片 / 视频 / 压缩包）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FileStreamDemo</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">String</span> <span class="variable">src</span> <span class="operator">=</span> <span class="string">&quot;test.jpg&quot;</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">dest</span> <span class="operator">=</span> <span class="string">&quot;copy.jpg&quot;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> (<span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(src);</span><br><span class="line">             <span class="type">FileOutputStream</span> <span class="variable">fos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(dest)) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="type">byte</span>[] buffer = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span>]; <span class="comment">// 缓冲区数组</span></span><br><span class="line">            <span class="type">int</span> len; <span class="comment">// 本次读到的字节数量</span></span><br><span class="line">            <span class="comment">// len=-1 代表读取到文件末尾</span></span><br><span class="line">            <span class="keyword">while</span> ((len = fis.read(buffer)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                fos.write(buffer, <span class="number">0</span>, len);</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(<span class="string">&quot;文件复制完成&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException 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">&#125;</span><br></pre></td></tr></table></figure><ol><li>缓冲字节流复制（性能更高，开发常用）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.BufferedInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.BufferedOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BufferedByteDemo</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="keyword">try</span> (<span class="type">BufferedInputStream</span> <span class="variable">bis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedInputStream</span>(<span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;test.mp4&quot;</span>));</span><br><span class="line">             <span class="type">BufferedOutputStream</span> <span class="variable">bos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedOutputStream</span>(<span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;copy.mp4&quot;</span>))) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="type">byte</span>[] buf = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">4096</span>];</span><br><span class="line">            <span class="type">int</span> len;</span><br><span class="line">            <span class="keyword">while</span> ((len = bis.read(buf)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                bos.write(buf, <span class="number">0</span>, len);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//缓冲流close()内部自动flush；如果不关闭流，需要手动bos.flush()</span></span><br><span class="line">            System.out.println(<span class="string">&quot;缓冲流复制成功&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException 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">&#125;</span><br></pre></td></tr></table></figure><ol><li>字符流：读取文本文件 FileReader</li></ol><p>缺陷：<strong>无法指定编码，容易乱码！</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.FileReader;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FileReaderDemo</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="keyword">try</span> (<span class="type">FileReader</span> <span class="variable">fr</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileReader</span>(<span class="string">&quot;demo.txt&quot;</span>)) &#123;</span><br><span class="line">            <span class="type">char</span>[] chars = <span class="keyword">new</span> <span class="title class_">char</span>[<span class="number">1024</span>];</span><br><span class="line">            <span class="type">int</span> len;</span><br><span class="line">            <span class="keyword">while</span> ((len = fr.read(chars)) != -<span class="number">1</span>) &#123;</span><br><span class="line">                System.out.print(<span class="keyword">new</span> <span class="title class_">String</span>(chars, <span class="number">0</span>, len));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>缓冲字符流 BufferedReader（readLine 读取一行）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.BufferedReader;</span><br><span class="line"><span class="keyword">import</span> java.io.FileReader;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BufferedReadDemo</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="keyword">try</span> (<span class="type">BufferedReader</span> <span class="variable">br</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(<span class="keyword">new</span> <span class="title class_">FileReader</span>(<span class="string">&quot;demo.txt&quot;</span>))) &#123;</span><br><span class="line">            String line;</span><br><span class="line">            <span class="comment">//读到文件末尾返回 null</span></span><br><span class="line">            <span class="keyword">while</span> ((line = br.readLine()) != <span class="literal">null</span>) &#123;</span><br><span class="line">                System.out.println(line);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>转换流（重点！解决文本乱码，可以指定 UTF-8 编码）</li></ol><p><code>InputStreamReader / OutputStreamWriter</code> 字节→字符桥梁</p><p>项目标准读写文本写法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.BufferedReader;</span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.io.InputStreamReader;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConvertStreamDemo</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">//指定UTF-8编码读取文本</span></span><br><span class="line">        <span class="keyword">try</span> (<span class="type">BufferedReader</span> <span class="variable">br</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(<span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;demo.txt&quot;</span>), <span class="string">&quot;UTF-8&quot;</span>)</span><br><span class="line">        )) &#123;</span><br><span class="line">            String line;</span><br><span class="line">            <span class="keyword">while</span> ((line = br.readLine()) != <span class="literal">null</span>) &#123;</span><br><span class="line">                System.out.println(line);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>对象序列化 &amp; 反序列化 ObjectStream Demo</li></ol><p>第一步：实体类（实现 Serializable）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.Serializable;</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_">User</span> <span class="keyword">implements</span> <span class="title class_">Serializable</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> <span class="type">long</span> <span class="variable">serialVersionUID</span> <span class="operator">=</span> <span class="number">1L</span>;</span><br><span class="line"></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="comment">//transient修饰：不会序列化保存</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> String tempData;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">User</span><span class="params">(String name, <span class="type">int</span> age, String tempData)</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>.tempData = tempData;</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_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;User&#123;name=&#x27;&quot;</span> + name + <span class="string">&quot;&#x27;, age=&quot;</span> + age + <span class="string">&quot;, tempData=&#x27;&quot;</span> + tempData + <span class="string">&quot;&#x27;&#125;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第二步：序列化（对象写入文件）+ 反序列化（读取对象）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.io.ObjectInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.ObjectOutputStream;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ObjectIoDemo</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="keyword">try</span> (<span class="type">ObjectOutputStream</span> <span class="variable">oos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectOutputStream</span>(<span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;user.dat&quot;</span>))) &#123;</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 class="string">&quot;张三&quot;</span>, <span class="number">20</span>, <span class="string">&quot;临时信息&quot;</span>);</span><br><span class="line">            oos.writeObject(user);</span><br><span class="line">            System.out.println(<span class="string">&quot;对象序列化完成&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</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">try</span> (<span class="type">ObjectInputStream</span> <span class="variable">ois</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectInputStream</span>(<span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;user.dat&quot;</span>))) &#123;</span><br><span class="line">            <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> (User) ois.readObject();</span><br><span class="line">            System.out.println(<span class="string">&quot;读取对象：&quot;</span> + user);</span><br><span class="line">            <span class="comment">//输出 tempData=null，transient数据丢失</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException | ClassNotFoundException 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">&#125;</span><br></pre></td></tr></table></figure><ol><li>PrintWriter 打印流（方便输出文本，自动换行刷新）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.PrintWriter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PrintWriterDemo</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="keyword">try</span> (<span class="type">PrintWriter</span> <span class="variable">pw</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PrintWriter</span>(<span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;out.txt&quot;</span>))) &#123;</span><br><span class="line">            pw.println(<span class="string">&quot;第一行文字&quot;</span>);</span><br><span class="line">            pw.print(<span class="string">&quot;不换行内容&quot;</span>);</span><br><span class="line">            pw.println(<span class="number">666</span>);</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">&#125;</span><br></pre></td></tr></table></figure><h2 id="Properties"><a href="#Properties" class="headerlink" title="Properties"></a>Properties</h2><p>一、概述</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">java.util.Properties</span><br></pre></td></tr></table></figure><ol><li><strong>继承 <code>Hashtable</code>，属于 Map 集合</strong>，是线程安全的</li><li><strong>键和值只能是 String 字符串</strong></li><li>专门用来读取 <strong>properties 配置文件</strong>（<code>.properties</code>）</li><li>文件格式：<code>key=value</code>，注释 <code>#</code>开头</li></ol><blockquote><p>区别普通 Map：</p><p>Map 可以存任意 Object；Properties 强制 key/value 为字符串，自带 IO 加载配置文件方法。</p></blockquote><p>二、核心常用方法</p><ol><li>存取方法（推荐，自带类型约束）</li></ol><ul><li><code>setProperty(String key, String value)</code>：存入键值对</li><li><code>getProperty(String key)</code>：根据 key 获取 value，找不到返回 null</li><li><code>getProperty(String key, String defaultValue)</code>：找不到 key 返回默认值</li></ul><blockquote><p>不推荐直接使用父类 <code>put() / get()</code>：可以存入非字符串，运行时异常！</p></blockquote><ol><li>IO 加载配置文件（重点）</li></ol><ul><li><code>load(InputStream inStream)</code>：加载 properties 文件（字节流，推荐）</li><li><code>load(Reader reader)</code>：字符流加载</li><li><code>store(OutputStream out, String comments)</code>：把集合数据写出到配置文件</li></ul><ol><li>遍历</li></ol><ul><li><code>propertyNames()</code> 返回所有 key 枚举</li><li><code>stringPropertyNames()</code> 返回 Set<String>（JDK1.6 + 推荐）</li></ul><p>三、文件格式示例（db.properties）</p><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment">#数据库配置</span></span><br><span class="line"><span class="attr">username</span>=<span class="string">root</span></span><br><span class="line"><span class="attr">password</span>=<span class="string">123456</span></span><br><span class="line"><span class="attr">url</span>=<span class="string">jdbc:mysql://localhost:3306/test</span></span><br><span class="line"><span class="attr">driver</span>=<span class="string">com.mysql.cj.jdbc.Driver</span></span><br></pre></td></tr></table></figure><p>四、完整 Demo</p><p>Demo1：加载配置文件（最常用）</p><p>把 <code>db.properties</code> 放在 <strong>项目根目录</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.util.Properties;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PropertiesLoadDemo</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">Properties</span> <span class="variable">prop</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span>(<span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;db.properties&quot;</span>))&#123;</span><br><span class="line">            <span class="comment">//加载配置文件</span></span><br><span class="line">            prop.load(fis);</span><br><span class="line"></span><br><span class="line">            <span class="comment">//读取数据</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> prop.getProperty(<span class="string">&quot;username&quot;</span>);</span><br><span class="line">            <span class="type">String</span> <span class="variable">password</span> <span class="operator">=</span> prop.getProperty(<span class="string">&quot;password&quot;</span>);</span><br><span class="line">            <span class="comment">//不存在的key，使用默认值</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">test</span> <span class="operator">=</span> prop.getProperty(<span class="string">&quot;noneKey&quot;</span>, <span class="string">&quot;默认值&quot;</span>);</span><br><span class="line"></span><br><span class="line">            System.out.println(username);</span><br><span class="line">            System.out.println(password);</span><br><span class="line">            System.out.println(test);</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Demo2：存入数据 + 写出到配置文件</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.util.Properties;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PropertiesStoreDemo</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">Properties</span> <span class="variable">prop</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//设置键值对</span></span><br><span class="line">        prop.setProperty(<span class="string">&quot;name&quot;</span>,<span class="string">&quot;张三&quot;</span>);</span><br><span class="line">        prop.setProperty(<span class="string">&quot;age&quot;</span>,<span class="string">&quot;20&quot;</span>);</span><br><span class="line">        prop.setProperty(<span class="string">&quot;gender&quot;</span>,<span class="string">&quot;男&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span>(<span class="type">FileOutputStream</span> <span class="variable">fos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;user.properties&quot;</span>))&#123;</span><br><span class="line">            <span class="comment">//参数2：文件注释</span></span><br><span class="line">            prop.store(fos, <span class="string">&quot;用户信息配置&quot;</span>);</span><br><span class="line">        &#125;<span class="keyword">catch</span> (IOException e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Demo3：遍历 Properties</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Properties;</span><br><span class="line"><span class="keyword">import</span> java.util.Set;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PropertiesLoopDemo</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">Properties</span> <span class="variable">prop</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line">        prop.setProperty(<span class="string">&quot;k1&quot;</span>,<span class="string">&quot;v1&quot;</span>);</span><br><span class="line">        prop.setProperty(<span class="string">&quot;k2&quot;</span>,<span class="string">&quot;v2&quot;</span>);</span><br><span class="line"></span><br><span class="line">        Set&lt;String&gt; keys = prop.stringPropertyNames();</span><br><span class="line">        <span class="keyword">for</span>(String key : keys)&#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> prop.getProperty(key);</span><br><span class="line">            System.out.println(key + <span class="string">&quot; = &quot;</span> + value);</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>高频注意事项</p><ol><li><strong>不要使用 put/get</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">prop.put(<span class="string">&quot;a&quot;</span>,<span class="number">123</span>); <span class="comment">//错误！存入Integer</span></span><br><span class="line">prop.getProperty(<span class="string">&quot;a&quot;</span>); <span class="comment">//会返回null，埋下bug</span></span><br></pre></td></tr></table></figure><ol><li>配置文件默认编码：<strong>ISO-8859-1</strong></li></ol><p>文件包含中文会乱码！</p><p>解决方案：</p><ul><li>使用 <code>Reader</code> 配合转换流指定 UTF-8</li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">InputStreamReader</span> <span class="variable">isr</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(<span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;db.properties&quot;</span>),<span class="string">&quot;UTF-8&quot;</span>);</span><br><span class="line">prop.load(isr);</span><br></pre></td></tr></table></figure><ol><li>路径问题</li></ol><ul><li><code>new FileInputStream(&quot;xxx.properties&quot;)</code>：相对项目根目录</li><li>如果放在 resources 资源文件夹（Maven 项目），使用类加载器读取（标准开发写法）</li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//读取resources下的配置文件</span></span><br><span class="line"><span class="type">Properties</span> <span class="variable">prop</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line">prop.load(Properties.class.getResourceAsStream(<span class="string">&quot;/db.properties&quot;</span>));</span><br></pre></td></tr></table></figure><ol><li>properties 文件写法</li></ol><ul><li><code>key=value</code> 也可以 <code>key:value</code></li><li><code>#</code> 注释，不能放在键值同一行右侧（部分版本识别异常）</li><li>等号两边不要随意加空格</li></ul><h2 id="网络编程"><a href="#网络编程" class="headerlink" title="网络编程"></a>网络编程</h2><h3 id="一、基础概念"><a href="#一、基础概念" class="headerlink" title="一、基础概念"></a>一、基础概念</h3><p>网络通信三要素</p><ol><li><p><strong>IP 地址</strong>：定位网络上一台主机</p><ul><li>IPv4：4 字节；IPv6：16 字节</li><li>本机地址：<code>127.0.0.1</code> / <code>localhost</code></li></ul></li><li><p><strong>端口号</strong>：定位主机上<strong>进程</strong>，范围 <code>0~65535</code></p><ul><li>0~1023：公认端口（80 http，21 ftp，22 ssh）</li><li>1024~65535：自定义端口</li></ul><blockquote><p>一个进程可以占用多个端口；<strong>一个端口同一时间只能被一个进程占用</strong></p></blockquote></li><li><p><strong>通信协议</strong>：数据传输规则</p><ul><li><strong>TCP</strong>：面向连接、可靠、有序、流式、三次握手四次挥手；不丢包；适合文件传输、聊天</li><li><strong>UDP</strong>：无连接、不可靠、数据报、速度快；可能丢包；适合直播、语音通话</li></ul></li></ol><p>两个核心协议对比</p><div class="table-container"><table><thead><tr><th style="text-align:center">特性</th><th style="text-align:center">TCP</th><th style="text-align:center">UDP</th></tr></thead><tbody><tr><td style="text-align:center">连接</td><td style="text-align:center">面向连接（通信前建立连接）</td><td style="text-align:center">无连接，直接发数据包</td></tr><tr><td style="text-align:center">可靠性</td><td style="text-align:center">可靠，重传机制、确认应答</td><td style="text-align:center">不可靠，不确认、不重传</td></tr><tr><td style="text-align:center">数据形式</td><td style="text-align:center">字节流</td><td style="text-align:center">数据包（Datagram）</td></tr><tr><td style="text-align:center">流量 / 拥塞控制</td><td style="text-align:center">有</td><td style="text-align:center">无</td></tr><tr><td style="text-align:center">适用场景</td><td style="text-align:center">文件上传下载、网页、数据库</td><td style="text-align:center">直播、视频通话、游戏</td></tr></tbody></table></div><h3 id="二、核心类"><a href="#二、核心类" class="headerlink" title="二、核心类"></a>二、核心类</h3><p>Java 网络包：<code>java.net</code></p><ol><li>InetAddress 代表 IP 地址</li></ol><blockquote><p>不能 new 对象，使用静态方法获取实例</p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//获取本机</span></span><br><span class="line"><span class="type">InetAddress</span> <span class="variable">local</span> <span class="operator">=</span> InetAddress.getLocalHost();</span><br><span class="line"><span class="comment">//根据域名/ip获取</span></span><br><span class="line"><span class="type">InetAddress</span> <span class="variable">server</span> <span class="operator">=</span> InetAddress.getByName(<span class="string">&quot;127.0.0.1&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">hostIp</span> <span class="operator">=</span> server.getHostAddress(); <span class="comment">//获取ip字符串</span></span><br><span class="line"><span class="type">String</span> <span class="variable">hostName</span> <span class="operator">=</span> server.getHostName();  <span class="comment">//获取主机名</span></span><br></pre></td></tr></table></figure><ol><li>Socket 编程（BIO 阻塞通信，重点）</li></ol><p>模型：<strong>客户端 Socket ｜ 服务端 ServerSocket</strong></p><ul><li><strong>ServerSocket</strong>：服务端，监听指定端口，等待客户端连接</li><li><strong>Socket</strong>：代表一端通信套接字，连接建立后，两端都持有 Socket</li><li>Socket 底层封装 <strong>InputStream / OutputStream</strong>，使用 IO 流收发数据</li></ul><p>通信流程（TCP）：</p><ol><li>服务端创建<code>ServerSocket(端口)</code>，调用<code>accept()</code> <strong>阻塞等待客户端连接</strong></li><li>客户端创建<code>Socket(ip,端口)</code>发起连接请求</li><li>服务端<code>accept()</code>收到连接，返回一个 Socket 对象，代表与当前客户端通道</li><li>两端通过 Socket 的输入输出流收发数据</li><li>通信完毕关闭流、关闭 Socket</li></ol><blockquote><p> <code>accept()</code>、<code>read()</code> 都是<strong>阻塞方法</strong>，没有连接 / 没有数据时线程卡住。</p></blockquote><p>TCP 代码 Demo</p><p><strong>服务端 ServerSocket</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.BufferedReader;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.io.InputStreamReader;</span><br><span class="line"><span class="keyword">import</span> java.net.ServerSocket;</span><br><span class="line"><span class="keyword">import</span> java.net.Socket;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TcpServer</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> IOException &#123;</span><br><span class="line">        <span class="type">ServerSocket</span> <span class="variable">serverSocket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerSocket</span>(<span class="number">8888</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;服务端启动，监听8888端口...&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//阻塞等待客户端连接</span></span><br><span class="line">        <span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> serverSocket.accept();</span><br><span class="line">        System.out.println(<span class="string">&quot;客户端已连接：&quot;</span> + socket.getInetAddress());</span><br><span class="line"></span><br><span class="line">        <span class="comment">//获取输入流，读取客户端消息</span></span><br><span class="line">        <span class="type">BufferedReader</span> <span class="variable">br</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(<span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(socket.getInputStream()));</span><br><span class="line">        <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> br.readLine();</span><br><span class="line">        System.out.println(<span class="string">&quot;收到客户端：&quot;</span> + msg);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//关闭资源</span></span><br><span class="line">        br.close();</span><br><span class="line">        socket.close();</span><br><span class="line">        serverSocket.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>客户端 Socket</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.OutputStreamWriter;</span><br><span class="line"><span class="keyword">import</span> java.io.PrintWriter;</span><br><span class="line"><span class="keyword">import</span> java.net.Socket;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TcpClient</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="comment">//连接服务端 ip+端口</span></span><br><span class="line">        <span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Socket</span>(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">8888</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//输出流向服务端发送消息</span></span><br><span class="line">        <span class="type">PrintWriter</span> <span class="variable">pw</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PrintWriter</span>(<span class="keyword">new</span> <span class="title class_">OutputStreamWriter</span>(socket.getOutputStream()));</span><br><span class="line">        pw.println(<span class="string">&quot;Hello 服务端！&quot;</span>);</span><br><span class="line">        pw.flush(); <span class="comment">//缓冲流必须刷新</span></span><br><span class="line"></span><br><span class="line">        pw.close();</span><br><span class="line">        socket.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>UDP 编程：DatagramSocket + DatagramPacket</li></ol><p>UDP 没有建立连接概念，<strong>数据包为单位传输</strong></p><ul><li><code>DatagramSocket</code>：收发数据包的套接字</li><li><code>DatagramPacket</code>：数据包（存放字节数据、目标地址端口）</li></ul><p>UDP Demo</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//UDP 发送端</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UDPSender</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> IOException &#123;</span><br><span class="line">        <span class="type">DatagramSocket</span> <span class="variable">socket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DatagramSocket</span>();</span><br><span class="line">        <span class="type">byte</span>[] data = <span class="string">&quot;udp消息&quot;</span>.getBytes();</span><br><span class="line">        <span class="comment">//封装数据包：数据，长度，目标ip，端口</span></span><br><span class="line">        <span class="type">DatagramPacket</span> <span class="variable">packet</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DatagramPacket</span>(</span><br><span class="line">                data,data.length,</span><br><span class="line">                InetAddress.getByName(<span class="string">&quot;127.0.0.1&quot;</span>),<span class="number">9999</span></span><br><span class="line">        );</span><br><span class="line">        socket.send(packet);</span><br><span class="line">        socket.close();</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">//UDP接收端</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UDPReceiver</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> IOException &#123;</span><br><span class="line">        <span class="type">DatagramSocket</span> <span class="variable">socket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DatagramSocket</span>(<span class="number">9999</span>);</span><br><span class="line">        <span class="type">byte</span>[] buf = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span>];</span><br><span class="line">        <span class="type">DatagramPacket</span> <span class="variable">packet</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DatagramPacket</span>(buf,buf.length);</span><br><span class="line">        socket.receive(packet); <span class="comment">//阻塞等待数据包</span></span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(packet.getData(),<span class="number">0</span>,packet.getLength());</span><br><span class="line">        System.out.println(<span class="string">&quot;收到：&quot;</span>+msg);</span><br><span class="line">        socket.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h3 id="三、BIO-网络编程存在的问题"><a href="#三、BIO-网络编程存在的问题" class="headerlink" title="三、BIO 网络编程存在的问题"></a>三、BIO 网络编程存在的问题</h3><p>原生 BIO：<strong>一个客户端连接占用一个独立线程</strong></p><ul><li><p>客户端很多 → 创建大量线程，内存暴涨、线程切换开销大</p></li><li><p>线程大部分时间阻塞在<code>accept()</code>/<code>read()</code>，资源浪费</p><p>解决方案：</p></li></ul><ol><li>NIO（同步非阻塞 <code>java.nio</code>）多路复用（Selector、Channel、Buffer）</li><li>框架：Netty（封装 NIO，企业主流）</li></ol><blockquote><p>区分：</p></blockquote><ul><li>BIO：同步阻塞 IO</li><li>NIO：同步非阻塞（JDK1.4+）</li><li>AIO：异步非阻塞（NIO.2 JDK1.7+）</li></ul><h3 id="四、重要常见问题"><a href="#四、重要常见问题" class="headerlink" title="四、重要常见问题"></a>四、重要常见问题</h3><p>三次握手（TCP 建立连接）</p><ol><li><p>客户端 → 服务端：SYN（请求连接）</p></li><li><p>服务端 → 客户端：SYN+ACK（同意连接，确认）</p></li><li><p>客户端 → 服务端：ACK（确认收到）</p><p>目的：协商收发能力，防止旧连接报文干扰</p></li></ol><p>四次挥手（TCP 断开连接）</p><ol><li>客户端发 FIN：我不再发送数据</li><li>服务端回复 ACK：收到关闭请求（服务端还可以继续发剩余数据）</li><li>服务端数据发完，发送 FIN：服务端也不再发送</li><li>客户端回复 ACK 确认断开</li></ol><blockquote><p>为什么挥手四次？TCP 是全双工，两个方向通道独立关闭。</p></blockquote><p>Socket 关闭注意事项</p><ol><li>shutdownOutput()：<strong>半关闭</strong>，只关闭输出流，仍然可以接收对方数据</li><li>close ()：同时关闭输入输出流</li></ol><p>粘包问题</p><p>TCP 是<strong>字节流</strong>，无消息边界：</p><p>多条数据被操作系统合并发送，接收方无法区分第一条、第二条消息。</p><p>解决办法：</p><ol><li>固定消息长度</li><li>特殊分隔符（换行）</li><li>消息头部携带<strong>数据长度</strong>（Netty 常用方案）</li></ol><blockquote><p>UDP 不存在粘包，UDP 基于独立数据包</p></blockquote><h3 id="五、URL-amp-URLConnection（访问网络资源）"><a href="#五、URL-amp-URLConnection（访问网络资源）" class="headerlink" title="五、URL &amp; URLConnection（访问网络资源）"></a>五、URL &amp; URLConnection（访问网络资源）</h3><p><code>java.net.URL</code> 定位网络资源（http/https/file）</p><p><code>URLConnection</code> 发起连接，获取网页流</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">URL</span>(<span class="string">&quot;https://www.baidu.com&quot;</span>);</span><br><span class="line"><span class="type">URLConnection</span> <span class="variable">conn</span> <span class="operator">=</span> url.openConnection();</span><br><span class="line"><span class="comment">//获取网页输入流</span></span><br><span class="line"><span class="type">InputStream</span> <span class="variable">is</span> <span class="operator">=</span> conn.getInputStream();</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Java学习笔记常用类</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=7476c763-b31d-cf2e-9364-9e93b78e872b" type="image"/>
    
    
    <category term="Java学习笔记" scheme="https://blog.itrf.cn/tags/Java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>Java学习笔记02</title>
    <link href="https://blog.itrf.cn/posts/9f32/"/>
    <id>https://blog.itrf.cn/posts/9f32/</id>
    <published>2026-06-30T16:00:01.000Z</published>
    <updated>2026-09-04T02:15:13.796Z</updated>
    
    <content type="html"><![CDATA[<h2 id="面向对象基础"><a href="#面向对象基础" class="headerlink" title="面向对象基础"></a>面向对象基础</h2><p>对象组成：属性和方法</p><ol><li><p>属性</p><p>对象名.属性名</p></li><li><p>行为</p><p>对象名.方法名</p></li></ol><h3 id="对象与类的关系"><a href="#对象与类的关系" class="headerlink" title="对象与类的关系"></a>对象与类的关系</h3><ol><li>类是抽象的，概念的，代表一类事物</li><li>对象是具体的，实际的，代表一个具体事务</li><li>类是对象的模板，对象是类的一个个体</li></ol><h3 id="类和对象的内存分配机制"><a href="#类和对象的内存分配机制" class="headerlink" title="类和对象的内存分配机制"></a>类和对象的内存分配机制</h3><p>Java 内存的结构分析</p><ol><li>栈：一般存放基本数据类型(局部变量)</li><li>堆：存放对象(Cat cat ， 数组等)</li><li>方法区：常量池(常量，比如字符串)，类加载信息</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Person</span> <span class="variable">p</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line">p.name = <span class="string">&quot;jack&quot;</span>;</span><br><span class="line">p.age = <span class="number">10</span>;</span><br></pre></td></tr></table></figure><ol><li>先加载Person类信息(属性和方法信息，只会加载一次)</li><li>再堆中分配空间，进行默认初始化</li><li>把地址赋给p ，p就指向对象</li><li>进行指定初始化，比如 p.name=”jack”   p.age = 10</li></ol><h3 id="包"><a href="#包" class="headerlink" title="包"></a>包</h3><h4 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h4><ol><li>区分相同名字的类</li><li>方便管理类</li><li>控制访问范围</li></ol><p>基本语法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.itrf</span><br></pre></td></tr></table></figure><p>package 关键词，表示打包</p><p>cn.itrf  表示包名</p><h4 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h4><ol><li>package的作用是声明当前类所在的包，需要放在类的最上面，一个类中最多只有一句package</li><li>import指令 位置放在package的下面，在类定义前面，可以有多句且没有顺序要求</li></ol><h3 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h3><h4 id="方法的调用机制原理"><a href="#方法的调用机制原理" class="headerlink" title="方法的调用机制原理"></a>方法的调用机制原理</h4><ol><li>当程序执行到方法时，就会开辟一个独立的空间(栈空间)</li><li>当方法执行完毕，或者执行到return语句时，就会返回</li><li>返回到调用方法的地方</li><li>返回后，继续执行方法后面的代码</li><li>当main方法(栈)执行完毕，这个程序退出</li></ol><h4 id="方法的访问修饰符"><a href="#方法的访问修饰符" class="headerlink" title="方法的访问修饰符"></a>方法的访问修饰符</h4><ol><li><strong><code>public</code></strong>：公开的。无论是哪个类、哪个包、还是外部项目，都可以直接访问。</li><li><strong><code>protected</code></strong>：受保护的。同一个包内的类可以访问；不同包中的<strong>子类</strong>也可以访问。</li><li><strong>默认（无修饰符）</strong>：包级私有（package-private）。只有<strong>同一个包</strong>内的类可以访问，跨包了就不能访问。</li><li><strong><code>private</code></strong>：私有的。只有在<strong>定义它的类内部</strong>才能访问，体现了面向对象的封装性。通常属性会设为 <code>private</code>，然后通过 <code>get/set</code> 方法来间接访问。</li></ol><h4 id="方法的返回数据类型"><a href="#方法的返回数据类型" class="headerlink" title="方法的返回数据类型"></a>方法的返回数据类型</h4><p>1) 一个方法最多有一个返回值 [多个结果 返回数组 ]<br>2) 返回类型可以为任意类型，包含基本类型或引用类型(数组，对象)<br>3) 如果方法要求有返回数据类型，则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的值类型一致或兼容</p><p>4) 如果方法是 void，则方法体中可以没有 return 语句，或者 只写 return ;</p><h5 id="注意事项-1"><a href="#注意事项-1" class="headerlink" title="注意事项"></a>注意事项</h5><ol><li>方法不能嵌套定义，方法体中不能再定义方法</li><li>基本数据类型，传递的是值(值拷贝)，形参的任何变化不影响实参</li><li>引用数据类型，传递的是地址(传递也是值，但是值是地址)，可以通过形参影响实参</li></ol><h4 id="递归重要规则"><a href="#递归重要规则" class="headerlink" title="递归重要规则"></a>递归重要规则</h4><ol><li>执行一个方法时，就创建一个新的受保护的独立空间(栈空间)</li><li>方法的局部变量时独立的，不会相互影响</li><li>如果方法中使用的是引用类型变量(比如数组，对象)，就会共享引用类型的数据</li><li>递归必须向退出递归条件逼近，否则就是无限递归</li><li>当一个方法执行完毕，或者遇到return，就会返回，遵守谁调用，就将结果返回给谁，同时当方法执行完毕或者返回时，该方法也就执行完毕</li></ol><h4 id="方法重载"><a href="#方法重载" class="headerlink" title="方法重载"></a>方法重载</h4><ol><li>方法名：必须相同</li><li>参数列表：必须不同</li><li>返回类型：无要求</li></ol><h4 id="可变参数"><a href="#可变参数" class="headerlink" title="可变参数"></a>可变参数</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">sum</span><span class="params">(<span class="type">int</span>... nums)</span> &#123;</span><br><span class="line"><span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">res += nums[i];</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li><p>int… 表示接受的是可变参数，类型是 int ,即可以接收多个 int(0-多)</p></li><li><p>使用可变参数时，可以当做数组来使用 即 nums 可以当做数组</p></li><li><p>遍历 nums 求和即可</p></li></ol><h5 id="注意事项-2"><a href="#注意事项-2" class="headerlink" title="注意事项"></a>注意事项</h5><ol><li>可变参数的实参可以为0个或者任意多个</li><li>可变参数的实参可以为数组</li><li>可变参数的本质就是数组</li><li>可变参数可以和普通类型的参数一起放在形参列表，但是必须保证可变参数在最后</li><li>一个形参列表中只能出现一个可变参数</li></ol><h3 id="作用域"><a href="#作用域" class="headerlink" title="作用域"></a>作用域</h3><ol><li>Java编程中，主要的变量就是属性(成员变量)和局部变量</li><li>局部变量一般是指在成员方法中定义的变量</li><li>Java中作用域的分类<ol><li>全局变量：也就是属性，作用域为整个类</li><li>局部变量：除了属性之外的其他变量，作用域是定义他的代码块</li></ol></li><li>全局变量可以不赋值，直接使用，因为有默认值，局部变量必须赋值，才能使用，因为没有默认值</li></ol><h4 id="注意事项-3"><a href="#注意事项-3" class="headerlink" title="注意事项"></a>注意事项</h4><ol><li>属性和局部变量可以重名，访问时遵循就近原则</li><li>在同一个作用域中，两个变量不能重名</li><li>属性生命周期较长，便随着对象的创建而创建，伴随着对象的销毁而销毁。局部变量，生命周期较短，伴随着它的代码块的执行而创建，伴随着代码块的结束而销毁</li><li>作用域范围不同<ol><li>全局变量：可以被本类使用，或其他类使用</li><li>局部变量：只能在本类中对应的方法中使用</li></ol></li><li>修饰符不同<ol><li>全局变量可以加修饰符</li><li>局部变量不可用加修饰符</li></ol></li></ol><h3 id="构造方法"><a href="#构造方法" class="headerlink" title="构造方法"></a>构造方法</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">[修饰符] 方法名(形参列表)&#123;</span><br><span class="line">方法体;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>构造方法又叫构造器(constructor)，是类的一种特殊的方法，它的主要作用是完成对新对象的初始化。它有几个特点：</p><p>1) 方法名和类名相同<br>2) 没有返回值<br>3) 在创建对象时，系统会自动的调用该类的构造器完成对象的初始化。<br>4) 构造器没有返回值, 也不能写 void</p><h4 id="注意事项-4"><a href="#注意事项-4" class="headerlink" title="注意事项"></a>注意事项</h4><ol><li>一个类可以定义多个不同的构造器，即构造器重载</li><li>构造器名要和类名相同</li><li>构造器没有返回值</li><li>构造器是完成对象的初始化，并不是创建对象</li><li>在创建对象时，系统自动的调用该类的构造方法</li><li>如果程序员没有定义构造器，系统会自动给类生成一个默认无参构造器(默认构造器)</li><li>一旦定义了自己的构造器，默认的构造器就被覆盖了，就不能再使用默认无参构成器了，除非显式的定义一下</li></ol><h3 id="this关键字"><a href="#this关键字" class="headerlink" title="this关键字"></a>this关键字</h3><p>Java虚拟机会给每个对象分配this代表当前对象</p><h4 id="注意事项-5"><a href="#注意事项-5" class="headerlink" title="注意事项"></a>注意事项</h4><ol><li>this 关键字可以用来访问本类的属性、方法、构造器</li></ol><p>2) this 用于区分当前类的属性和局部变量<br>3) 访问成员方法的语法：this.方法名(参数列表);</p><p>4) 访问构造器语法：this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)</p><p>5) this 不能在类定义的外部使用，只能在类定义的方法中使用</p><h2 id="和equals-的区别"><a href="#和equals-的区别" class="headerlink" title="==和equals()的区别"></a>==和equals()的区别</h2><ol><li><strong><code>==</code>（双等号）</strong><ul><li><strong>基本数据类型（如 <code>int</code>, <code>double</code>, <code>boolean</code>）</strong>：比较的是它们的<strong>数值</strong>是否相等。</li><li><strong>引用数据类型（如对象、数组、字符串）</strong>：比较的是它们的<strong>内存地址</strong>是否相同（即是否指向堆内存中的同一个对象）。</li></ul></li><li><strong><code>equals()</code> 方法</strong><ul><li><strong>对象专属</strong>：它是 <code>Object</code> 类的一个方法，所有类都继承了它。</li><li><strong>默认行为</strong>：如果没有重写（Override），它的底层逻辑和 <code>==</code> 一样，比较的还是内存地址。</li><li><strong>常用重写</strong>：像 <code>String</code>、<code>Integer</code> 等类都重写了 <code>equals()</code> 方法，用来比较它们的内容（值）是否相同。</li></ul></li></ol><h2 id="面向对象的三大特征"><a href="#面向对象的三大特征" class="headerlink" title="面向对象的三大特征"></a>面向对象的三大特征</h2><p>封装、继承、多态</p><h3 id="封装"><a href="#封装" class="headerlink" title="封装"></a>封装</h3><p>封装就是把抽象出的数据和对数据的操作封装在一起，数据被保护在内部，程序的其他部分只能通过授权的操作，才能对数据进行操作</p><p>将属性设置为private  创建setXXX和getXXX的方法 public</p><h3 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h3><figure class="highlight java"><table><tr><td class="code"><pre><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>子类就是会自动拥有父类定义的属性和方法</p><p>父类又叫  超类，基类</p><p>子类又叫派生类</p><h4 id="注意事项-6"><a href="#注意事项-6" class="headerlink" title="注意事项"></a>注意事项</h4><p>1) 子类继承了所有的属性和方法，非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问，要通过父类提供公共的方法去访问</p><p>2) 子类必须调用父类的构造器， 完成父类的初始化<br>3) 当创建子类对象时，不管使用子类的哪个构造器，默认情况下总会去调用父类的无参构造器，如果父类没有提供无参构造器，则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作，否则，编译不会通过。</p><p>4) 如果希望指定去调用父类的某个构造器，则显式的调用一下 : super(参数列表)<br>5) super 在使用时，必须放在构造器第一行(super 只能在构造器中使用)<br>6) super() 和 this() 都只能放在构造器第一行，因此这两个方法不能共存在一个构造器<br>7) Java 所有类都是 Object 类的子类, Object 是所有类的基类.<br>8) 父类构造器的调用不限于直接父类！将一直往上追溯直到 Object 类(顶级父类)<br>9) 子类最多只能继承一个父类(指直接继承)，即 Java 中是单继承机制。思考：如何让 A 类继承 B 类和 C 类？ 【A 继承 B， B 继承 C】</p><p>10) 不能滥用继承，子类和父类之间必须满足 is-a 的逻辑关系</p><h4 id="super关键字"><a href="#super关键字" class="headerlink" title="super关键字"></a>super关键字</h4><p>super代表父类的引用，用于访问父类的属性、方法、构造器</p><p>不能访问父类的private属性和方法</p><p>当子类中有何父类中的成员(属性和方法)重名时，为了访问父类成员，必须通过super。如果没有重名，使用super、this、直接访问的结果一样</p><p><code>super.成员</code> 的查找起点是直接父类，若直接父类没有对应成员，会继续沿着继承链向上查找（可找到爷爷类等上层父类）；查找遵循<strong>继承链就近原则</strong>，同时受访问权限约束遇到private就断了报错；<strong>无法主动跳过直接父类，强制直接访问爷爷类中被父类重写 / 覆盖的同名成员</strong>。</p><h4 id="this和super的区别"><a href="#this和super的区别" class="headerlink" title="this和super的区别"></a>this和super的区别</h4><div class="table-container"><table><thead><tr><th>对比项</th><th>this</th><th>super</th></tr></thead><tbody><tr><td>含义</td><td>当前类对象</td><td>父类存储空间入口</td></tr><tr><td>成员查找起点</td><td>当前类</td><td>直接父类（跳过当前类）</td></tr><tr><td>调用构造</td><td>this () 调用本类构造</td><td>super () 调用父类构造</td></tr><tr><td>使用位置</td><td>实例方法、构造器</td><td>实例方法、构造器</td></tr><tr><td>static 中</td><td>不允许</td><td>不允许</td></tr><tr><td>单独输出</td><td>this 可行</td><td>super 不能直接打印</td></tr></tbody></table></div><h4 id="方法的重写-覆盖"><a href="#方法的重写-覆盖" class="headerlink" title="方法的重写/覆盖"></a>方法的重写/覆盖</h4><p>方法的覆盖就是子类的一个方法和父类的某个方法的名称、返回类型、参数一样，那么我们就说子类的这个方法覆盖了父类的方法</p><p>子类重写方法的返回值类型和父类返回值类型一致，或者是其子类</p><p>子类的方法不能缩小父类方法的访问范围</p><h3 id="多态"><a href="#多态" class="headerlink" title="多态"></a>多态</h3><p>方法或对象具有多种形态。是面向对象的第三大特征，多态是建立在封装和继承基础之上的。</p><h4 id="重点"><a href="#重点" class="headerlink" title="重点"></a>重点</h4><ol><li>一个对象的编译类型和运行类型可以不一致</li><li>编译类型在定义对象时，就确定了，不能改变</li><li>运行类型是可以变化的</li><li>编译类型看定义时 = 号 的左边，运行类型看 = 号的 右边</li></ol><h4 id="多态前提（四个条件缺一不可）"><a href="#多态前提（四个条件缺一不可）" class="headerlink" title="多态前提（四个条件缺一不可）"></a>多态前提（四个条件缺一不可）</h4><ol><li><strong>继承 / 实现</strong>：子类继承父类（类继承）或者类实现接口</li><li><strong>方法重写</strong>：子类重写父类方法</li><li><strong>父类引用指向子类对象</strong>（向上转型）</li><li><strong>调用重写方法</strong></li></ol><h4 id="向上转型（自动转型，隐式转型）"><a href="#向上转型（自动转型，隐式转型）" class="headerlink" title="向上转型（自动转型，隐式转型）"></a>向上转型（自动转型，隐式转型）</h4><p>格式：<code>父类类型 引用 = new 子类();</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Animal</span> <span class="variable">animal</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Dog</span>(); <span class="comment">// 向上转型</span></span><br></pre></td></tr></table></figure><p>特点：</p><ol><li><strong>自动转换，不需要强制</strong></li><li>可以调用：父类所有方法 + <strong>子类重写的方法</strong>，只有方法哦！！！</li><li><strong>不能调用子类独有的方法</strong></li></ol><h4 id="向下转型（强制转型，显式转型）"><a href="#向下转型（强制转型，显式转型）" class="headerlink" title="向下转型（强制转型，显式转型）"></a>向下转型（强制转型，显式转型）</h4><p>作用：向上转型后，想要调用<strong>子类独有方法</strong>，必须向下转回子类类型</p><p>格式：<code>子类类型 引用 = (子类)父类引用;</code></p><figure class="highlight java"><table><tr><td class="code"><pre><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_">Dog</span>(); <span class="comment">//向上转型</span></span><br><span class="line"><span class="type">Dog</span> <span class="variable">dog</span> <span class="operator">=</span> (Dog) a;    <span class="comment">//向下转型</span></span><br><span class="line">dog.watchDoor();      <span class="comment">// 正常执行</span></span><br></pre></td></tr></table></figure><p>巨大坑：<strong>类型转换异常 ClassCastException</strong></p><p>只有原本就是该子类对象，才能强转；</p><figure class="highlight java"><table><tr><td class="code"><pre><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"><span class="type">Dog</span> <span class="variable">d</span> <span class="operator">=</span> (Dog)a; <span class="comment">//运行报错！</span></span><br></pre></td></tr></table></figure><p>解决方案：<code>instanceof</code> 判断</p><p>判断<strong>引用变量指向的实际对象</strong>是不是【某个类 / 它的子类 / 实现类】的实例</p><p>转型前先判断引用实际对象类型</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><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.watchDoor();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th>类型</th><th>写法</th><th>是否强制</th><th>风险</th><th>能力</th></tr></thead><tbody><tr><td>向上转型</td><td>父类 引用 = new 子类 ()</td><td>自动</td><td>无风险</td><td>只能访问父类方法，执行子类重写方法；不能访问子类独有方法</td></tr><tr><td>向下转型</td><td>子类 引用 = (子类) 父类引用</td><td>需要强制</td><td>可能抛 ClassCastException，配合 instanceof</td><td>可以调用子类独有方法</td></tr></tbody></table></div><h4 id="易错总结"><a href="#易错总结" class="headerlink" title="易错总结"></a>易错总结</h4><p>方法有多态，成员变量没有多态！</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>&#123; <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">10</span>; &#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> <span class="keyword">extends</span> <span class="title class_">A</span>&#123; <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">20</span>; &#125;</span><br><span class="line"><span class="type">A</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">B</span>();</span><br><span class="line">System.out.println(a.num); <span class="comment">//输出10，看左边父类</span></span><br></pre></td></tr></table></figure><ol><li>静态方法重写不叫重写，叫<strong>隐藏</strong>，不存在多态；调用看左边</li><li>向上转型本质：多态的基础写法</li><li>向下转型不是多态，只是为了访问子类特有功能</li></ol><p>Java的动态类型绑定</p><ol><li><p><strong>调用对象【方法】：存在动态绑定</strong></p><p>调用方法时，看对象真实运行类型（堆里 new 出来的类型），执行子类重写后的方法。</p></li></ol><blockquote><p>通俗：<strong>方法 → 运行看右边</strong></p></blockquote><ol><li><strong>调用对象【成员属性 (变量)】：没有动态绑定</strong></li></ol><p>访问变量时，看引用声明类型（等号左边是什么类型），哪里声明，就取哪里的属性。</p><blockquote><p>通俗：<strong>属性 → 编译 / 运行都看左边</strong></p></blockquote><h2 id="类变量"><a href="#类变量" class="headerlink" title="类变量"></a>类变量</h2><p>类变量也叫静态变量/静态属性，是该类的所有对象共享的变量，任何一个该类的对象去访问它时，取到的都是相同的值，同样任何一个该类的对象去修改它时，修改的也是同一个变量。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span> &#123; <span class="comment">//类</span></span><br><span class="line"><span class="keyword">private</span> String name;</span><br><span class="line"><span class="comment">//定义一个变量 count ,是一个类变量(静态变量) static 静态</span></span><br><span class="line"><span class="comment">//该变量最大的特点就是会被 Child 类的所有的对象实例共享</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="title function_">Child</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">&#125;</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><ol><li>类变量是在类加载时就完成初始化了，也就是说，即使你没有创建对象，只要类加载了，就可以使用类变量</li><li>类变量的生命周期是随类的加载开始，随着类消亡而销毁</li></ol><h2 id="类方法"><a href="#类方法" class="headerlink" title="类方法"></a>类方法</h2><ol><li><p>类方法和普通方法都是随着类的加载而加载，将结构信息存储在方法区：</p><p>类方法中无 this 的参数</p><p>普通方法中隐含着 this 的参数</p></li><li><p>类方法可以通过类名调用，也可以通过对象名调用。</p></li><li><p>普通方法和对象有关，需要通过对象名调用，比如对象名.方法名 (参数)，不能通过类名调用。</p></li><li><p>类方法中不允许使用和对象有关的关键字，比如 this 和 super。普通方法 (成员方法) 可以。</p></li><li><p>类方法 (静态方法) 中 只能访问 静态变量 或静态方法 。</p></li><li><p>普通成员方法，既可以访问 非静态成员，也可以访问静态成员。</p></li></ol><p><strong>小结：</strong> 静态方法，只能访问静态的成员，非静态的方法，可以访问静态成员和非静态成员 (必须遵守访问权限)</p><h2 id="mian方法"><a href="#mian方法" class="headerlink" title="mian方法"></a>mian方法</h2><figure class="highlight java"><table><tr><td class="code"><pre><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><br></pre></td></tr></table></figure><ol><li><p><strong>public</strong>：公共访问权限。JVM 需要跨类调用 main 方法，必须对外公开可访问。</p></li><li><p><strong>static</strong>：静态方法。JVM 启动时还没有创建类的实例对象，<strong>直接通过类名调用方法</strong>，不需要 new 对象。</p></li><li><p><strong>void</strong>：无返回值。JVM 不接收 main 函数的返回结果。</p></li><li><p><strong>main</strong>：固定方法名，JVM 识别的程序入口名称，不能修改。</p></li><li><p><strong>String[] args</strong>：命令行参数数组</p><ul><li><p>在控制台执行命令示例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">java TestDemo 张三 <span class="number">20</span> 男</span><br></pre></td></tr></table></figure><p><code>args[0]=&quot;张三&quot;</code> <code>args[1]=&quot;20&quot;</code> <code>args[2]=&quot;男&quot;</code></p></li><li><p>args 名称可以修改（例如<code>String[] params</code>），但数组类型不能变。</p></li></ul></li></ol><h2 id="三种代码块"><a href="#三种代码块" class="headerlink" title="三种代码块"></a>三种代码块</h2><h3 id="态代码块"><a href="#态代码块" class="headerlink" title="态代码块"></a>态代码块</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> &#123;</span><br><span class="line">    <span class="comment">//代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>位置：<strong>类内部，方法外面</strong>，加 <code>static</code></p></li><li><p>触发时机：<strong>类加载的时候执行，只执行 1 次</strong></p></li><li><p>作用：初始化<strong>静态变量</strong></p></li><li><p>限制：</p><p>只能访问静态成员</p><p>不能使用 this、super</p><p>不可以写 return</p></li></ul><h3 id="构造代码块（实例代码块-普通代码块【类内】）"><a href="#构造代码块（实例代码块-普通代码块【类内】）" class="headerlink" title="构造代码块（实例代码块 / 普通代码块【类内】）"></a>构造代码块（实例代码块 / 普通代码块【类内】）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="comment">//代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>位置：<strong>类内部、方法外面，没有 static</strong></li><li>触发时机：<strong>每次创建对象 new 的时候执行</strong></li><li>执行时机：<strong>在构造方法之前执行</strong></li><li>作用：提取多个构造方法的公共重复代码</li><li>可以访问静态、非静态成员，可以使用 this</li></ul><h3 id="局部代码块（方法内普通代码块）"><a href="#局部代码块（方法内普通代码块）" class="headerlink" title="局部代码块（方法内普通代码块）"></a>局部代码块（方法内普通代码块）</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span>&#123;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//局部代码块</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><li>作用：限定局部变量生命周期，缩小作用域</li></ul><blockquote><p><strong>区分「构造代码块」和「局部代码块」</strong></p></blockquote><h3 id="执行顺序"><a href="#执行顺序" class="headerlink" title="执行顺序"></a>执行顺序</h3><p>情况 1：只创建<strong>单个对象</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="number">1.</span> 静态代码块（类加载，最先执行，只运行一次）</span><br><span class="line">↓</span><br><span class="line"><span class="number">2.</span> 构造代码块（每次<span class="keyword">new</span>对象都执行）</span><br><span class="line">↓</span><br><span class="line"><span class="number">3.</span> 构造方法</span><br></pre></td></tr></table></figure><p>示例演示：</p><figure class="highlight java"><table><tr><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">static</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">    &#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 class="keyword">public</span> <span class="title function_">Test</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="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="keyword">new</span> <span class="title class_">Test</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><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>情况 2：<strong>多次 new 对象</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">new Test();</span><br><span class="line">new Test();</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">静态代码块    //类加载，仅第1次执行</span><br><span class="line">构造代码块</span><br><span class="line">构造方法</span><br><span class="line">构造代码块    //第二次new，静态不再执行！</span><br><span class="line">构造方法</span><br></pre></td></tr></table></figure><blockquote><p>关键结论：<strong>静态代码块随类加载，全局只执行一次；构造代码块每次 new 对象都会执行。</strong></p></blockquote><p>情况 3：存在父子继承关系</p><p>继承场景完整顺序口诀：</p><p><strong>父静态 → 子静态 → 父构造代码块 → 父构造方法 → 子构造代码块 → 子构造方法</strong></p><p>演示代码：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Father</span>&#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;System.out.println(<span class="string">&quot;父静态&quot;</span>);&#125;</span><br><span class="line">    &#123;System.out.println(<span class="string">&quot;父构造代码块&quot;</span>);&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Father</span><span class="params">()</span>&#123;System.out.println(<span class="string">&quot;父构造器&quot;</span>);&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Son</span> <span class="keyword">extends</span> <span class="title class_">Father</span>&#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;System.out.println(<span class="string">&quot;子静态&quot;</span>);&#125;</span><br><span class="line">    &#123;System.out.println(<span class="string">&quot;子构造代码块&quot;</span>);&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Son</span><span class="params">()</span>&#123;System.out.println(<span class="string">&quot;子构造器&quot;</span>);&#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_">Main</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="keyword">new</span> <span class="title class_">Son</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行结果：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><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><blockquote><p>易错提醒：</p><p>静态代码块<strong>只看类加载顺序</strong>，优先父类静态；静态代码块互不依赖实例，优先全部执行完所有静态，再执行实例相关代码块、构造器。</p></blockquote><div class="table-container"><table><thead><tr><th>类型</th><th>格式</th><th>位置</th><th>执行时机</th><th>执行次数</th></tr></thead><tbody><tr><td>静态代码块</td><td><code>static {}</code></td><td>类内，方法外</td><td>类加载阶段</td><td><strong>整个程序只 1 次</strong></td></tr><tr><td>构造代码块</td><td><code>{}</code></td><td>类内，方法外</td><td>每次 new 创建实例</td><td><strong>每 new 一次执行一次</strong></td></tr><tr><td>局部代码块</td><td><code>{}</code></td><td>方法内部</td><td>方法运行到该位置</td><td>运行到就执行</td></tr></tbody></table></div><h2 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h2><ol><li>静态方法和属性的经典使用</li><li>设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。</li></ol><h3 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h3><p>单例模式：保证一个类在整个程序中，只有<strong>唯一的一个实例对象</strong>，对外提供统一获取该对象的入口。</p><p>使用场景：工具类、线程池、数据库连接池、配置文件对象等，不需要多个实例，节约内存。</p><p>核心三大要点：</p><ol><li><strong>私有构造方法</strong>：<code>private 类名(){}</code>，禁止外部<code>new</code>创建对象</li><li><strong>私有静态成员变量</strong>：保存唯一实例</li><li><strong>公共静态方法</strong>：对外获取这个唯一实例</li></ol><h4 id="方式-1：饿汉式（静态常量写法，【线程安全】）"><a href="#方式-1：饿汉式（静态常量写法，【线程安全】）" class="headerlink" title="方式 1：饿汉式（静态常量写法，【线程安全】）"></a>方式 1：饿汉式（静态常量写法，【线程安全】）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SingletonHungry</span> &#123;</span><br><span class="line">    <span class="comment">// 1.类加载时直接创建实例</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">SingletonHungry</span> <span class="variable">instance</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SingletonHungry</span>();</span><br><span class="line">    <span class="comment">// 2.私有构造，阻止外部new</span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">SingletonHungry</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    <span class="comment">// 3.对外获取实例的方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> SingletonHungry <span class="title function_">getInstance</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="执行时机"><a href="#执行时机" class="headerlink" title="执行时机"></a>执行时机</h5><p><strong>类加载的时候直接创建对象</strong>。</p><p>优点：</p><ul><li>写法简单，天然线程安全（类加载只执行一次，不存在多线程冲突）</li></ul><p>缺点：</p><ul><li><strong>类一旦加载，立刻创建对象</strong>。即使程序全程没有用到这个实例，对象依然创建，造成内存浪费。</li></ul><blockquote><p>饿汉：肚子一饿立刻吃饭 → 类加载直接实例化。</p></blockquote><hr><h4 id="方式-2：懒汉式（基础版，多线程不安全！）"><a href="#方式-2：懒汉式（基础版，多线程不安全！）" class="headerlink" title="方式 2：懒汉式（基础版，多线程不安全！）"></a>方式 2：懒汉式（基础版，多线程不安全！）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SingletonLazy</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> SingletonLazy instance;</span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">SingletonLazy</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    <span class="comment">// 调用方法时，才创建对象</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> SingletonLazy <span class="title function_">getInstance</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 第一次调用才new</span></span><br><span class="line">        <span class="keyword">if</span>(instance == <span class="literal">null</span>)&#123;</span><br><span class="line">            instance = <span class="keyword">new</span> <span class="title class_">SingletonLazy</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="执行时机-1"><a href="#执行时机-1" class="headerlink" title="执行时机"></a>执行时机</h5><p><strong>调用<code>getInstance()</code>方法的时候，才创建实例</strong>（延迟加载）</p><p>优点：<strong>懒加载</strong>，不用就不创建，节省内存</p><p>致命缺陷：<strong>多线程环境下会创建多个对象，线程不安全</strong></p><p>线程安全优化：双重检查锁 DCL（面试标准懒汉写法）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SingletonLazyDCL</span> &#123;</span><br><span class="line">    <span class="comment">// volatile 禁止指令重排，必须加上</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> SingletonLazyDCL instance;</span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">SingletonLazyDCL</span><span class="params">()</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> SingletonLazyDCL <span class="title function_">getInstance</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 第一层判断：避免每次都竞争锁，提高效率</span></span><br><span class="line">        <span class="keyword">if</span>(instance == <span class="literal">null</span>)&#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (SingletonLazyDCL.class)&#123;</span><br><span class="line">                <span class="comment">// 第二层判断：防止多个线程同时等待锁，重复创建</span></span><br><span class="line">                <span class="keyword">if</span>(instance == <span class="literal">null</span>)&#123;</span><br><span class="line">                    instance = <span class="keyword">new</span> <span class="title class_">SingletonLazyDCL</span>();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>懒汉：不到饭点不吃饭 → 需要用到的时候才创建对象</p></blockquote><div class="table-container"><table><thead><tr><th style="text-align:center">对比</th><th style="text-align:center">饿汉式</th><th style="text-align:center">懒汉式 (DCL 双重检查锁)</th></tr></thead><tbody><tr><td style="text-align:center">创建时机</td><td style="text-align:center">类加载阶段</td><td style="text-align:center">第一次调用 getInstance ()</td></tr><tr><td style="text-align:center">懒加载</td><td style="text-align:center">❌不支持</td><td style="text-align:center">✅支持，节省内存</td></tr><tr><td style="text-align:center">线程安全</td><td style="text-align:center">✅天然安全</td><td style="text-align:center">需要加锁 + volatile 保证安全</td></tr><tr><td style="text-align:center">使用场景</td><td style="text-align:center">确定一定会使用这个对象</td><td style="text-align:center">不确定是否会使用，希望延迟创建</td></tr></tbody></table></div><h4 id="拓展知识点"><a href="#拓展知识点" class="headerlink" title="拓展知识点"></a>拓展知识点</h4><ol><li><strong>私有构造方法依然可以被反射暴力破解</strong>，两种基础实现都不能防止反射创建多个实例；</li><li>还有第三种写法：静态内部类单例（推荐，兼顾懒加载与线程安全）；</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Singleton</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Inner</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">Singleton</span> <span class="variable">INSTANCE</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Singleton</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getInstance</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> Inner.INSTANCE;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="final关键字"><a href="#final关键字" class="headerlink" title="final关键字"></a>final关键字</h2><p><strong>final 表示「最终的、不可修改」，可以修饰：变量、方法、类。</strong></p><h3 id="一、final-修饰【变量】"><a href="#一、final-修饰【变量】" class="headerlink" title="一、final 修饰【变量】"></a>一、final 修饰【变量】</h3><p>变量一旦赋值成功，<strong>不能重新赋值</strong>（常量）</p><ol><li>局部变量</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> a;</span><br><span class="line">    a = <span class="number">10</span>;</span><br><span class="line">    <span class="comment">// a = 20;  // 编译报错，不能二次赋值</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>final 局部变量可以先声明、后面一次赋值，但是只能赋值<strong>一次</strong>。</p></blockquote><ol><li>成员变量（实例变量）</li></ol><p>两种赋值时机，二选一：</p><ol><li>定义时直接初始化</li><li>在构造方法中赋值</li></ol><blockquote><p>不能不赋值！final 成员变量必须保证对象创建完成时有值。</p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>&#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    <span class="comment">// final int num; // 合法，可以在构造器赋值</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">A</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// num = 20;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>静态成员变量（static final）</li></ol><p>俗称<strong>常量</strong>，命名规范：全部大写，下划线分隔 <code>MAX_VALUE</code></p><p>赋值时机：</p><ol><li>声明处初始化</li><li><strong>静态代码块</strong>初始化</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">double</span> <span class="variable">PI</span> <span class="operator">=</span> <span class="number">3.1415926</span>;</span><br></pre></td></tr></table></figure><p>极易混淆重点：final 修饰引用类型变量</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">Person</span> <span class="variable">p</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Person</span>();</span><br><span class="line">p.name = <span class="string">&quot;张三&quot;</span>;  <span class="comment">// 合法！！</span></span><br><span class="line">p = <span class="keyword">new</span> <span class="title class_">Person</span>(); <span class="comment">// 报错！</span></span><br></pre></td></tr></table></figure><ul><li><p><code>final</code> 限制的是<strong>引用地址不能变</strong></p></li><li><p><strong>对象内部的属性依然可以修改！</strong></p><p>形象理解：遥控器不能换电视，但是可以换频道。</p></li></ul><hr><h3 id="二、final-修饰【方法】"><a href="#二、final-修饰【方法】" class="headerlink" title="二、final 修饰【方法】"></a>二、final 修饰【方法】</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span>&#123;&#125;</span><br></pre></td></tr></table></figure><p>规则：</p><p><strong>该方法不能被子类重写（override）</strong></p><blockquote><p>仍然可以被继承，子类可以调用，只是不能改写方法实现。</p></blockquote><p>补充：</p><p>private 方法<strong>隐式带有 final 特性</strong>，子类无法重写私有方法。</p><hr><h3 id="三、final-修饰【类】"><a href="#三、final-修饰【类】" class="headerlink" title="三、final 修饰【类】"></a>三、final 修饰【类】</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Animal</span>&#123;&#125;</span><br></pre></td></tr></table></figure><p>规则：</p><p><strong>这个类不能被继承，没有子类</strong></p><p>例子：<code>String</code>、<code>Integer</code> 都是 final 类，你无法写 <code>class MyStr extends String</code></p><hr><h3 id="四、易错知识点汇总"><a href="#四、易错知识点汇总" class="headerlink" title="四、易错知识点汇总"></a>四、易错知识点汇总</h3><ol><li><strong>final 和 static 区别？</strong></li></ol><ul><li>static：属于类，所有对象共享一份</li><li>final：值不可修改；两者经常组合 <code>static final</code> 定义常量</li></ul><ol><li>基本类型 final vs 引用类型 final</li></ol><ul><li>基本类型：数值不能变</li><li>引用类型：对象地址不能变，对象内部内容可变</li></ul><ol><li>final 方法可以重载吗？</li></ol><p>可以重载！只是禁止重写，重载不受影响。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">(<span class="type">int</span> a)</span>&#123;&#125; <span class="comment">// 重载合法</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>final 可以修饰构造方法吗？</li></ol><p>不允许！构造方法本身不能被继承重写，语法不支持。</p><h3 id="五、速记总结"><a href="#五、速记总结" class="headerlink" title="五、速记总结"></a>五、速记总结</h3><ul><li>final 变量：赋值一次，终身不改（引用变量地址不变，对象内容可变）</li><li>final 方法：禁止子类重写</li><li>final 类：断绝继承，不能有子类</li></ul><div class="table-container"><table><thead><tr><th style="text-align:center">修饰目标</th><th style="text-align:center">作用</th></tr></thead><tbody><tr><td style="text-align:center">final 变量</td><td style="text-align:center">只能赋值一次，不可重新赋值</td></tr><tr><td style="text-align:center">final 方法</td><td style="text-align:center">不能被子类重写</td></tr><tr><td style="text-align:center">final 类</td><td style="text-align:center">不能被继承</td></tr></tbody></table></div><h2 id="Java-抽象类（abstract-class）"><a href="#Java-抽象类（abstract-class）" class="headerlink" title="Java 抽象类（abstract class）"></a>Java 抽象类（abstract class）</h2><h3 id="一、基础概念"><a href="#一、基础概念" class="headerlink" title="一、基础概念"></a>一、基础概念</h3><p>用 <strong><code>abstract</code></strong> 关键字修饰的类，叫做<strong>抽象类</strong>。</p><blockquote><p>抽象类用来提取多个子类的公共模板，允许定义<strong>没有方法体的抽象方法</strong>，强制子类去实现。</p></blockquote><p>语法格式：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">abstract</span> class 类名 &#123;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="二、抽象方法"><a href="#二、抽象方法" class="headerlink" title="二、抽象方法"></a>二、抽象方法</h3><p>语法：<code>abstract 返回值 方法名();</code></p><ul><li>只有方法声明，<strong>没有方法体（没有 {}）</strong></li><li>必须写在<strong>抽象类 / 接口</strong>当中</li><li>作用：<strong>规范子类必须实现这个方法</strong></li></ul><p>示例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Animal</span> &#123;</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_">shout</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="三、核心硬性规则"><a href="#三、核心硬性规则" class="headerlink" title="三、核心硬性规则"></a>三、核心硬性规则</h3><ol><li><p>抽象类不能 new 创建对象</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Animal</span>(); <span class="comment">// 编译报错</span></span><br></pre></td></tr></table></figure><p>原因：类中有未实现的抽象方法，对象不完整。</p></li><li><p>如果一个类继承抽象类：</p><ul><li>方案①：普通子类 → <strong>必须重写父类所有抽象方法</strong></li><li>方案②：子类也定义为 <code>abstract</code> 抽象类 → 可以不用实现抽象方法</li></ul></li></ol><figure class="highlight java"><table><tr><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_">Dog</span> <span class="keyword">extends</span> <span class="title class_">Animal</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_">shout</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">&#125;</span><br></pre></td></tr></table></figure><ol><li>抽象类里可以包含什么？</li></ol><p>普通成员变量、静态变量</p><p>普通实例方法、static 静态方法</p><p>构造方法（供子类创建对象时调用）</p><p>抽象方法</p><blockquote><p>重点：抽象类<strong>有构造方法</strong>！只是不能自己实例化，子类 <code>new</code> 的时候通过 <code>super()</code> 调用。</p></blockquote><ol><li><p>几个语法禁忌</p><p><code>abstract</code> 和 <code>final</code>不能一起修饰类</p></li></ol><p>final 类不能被继承，abstract 类要求必须被继承实现，互相矛盾。</p><p><code>abstract</code> 和 <code>private</code> 不能一起修饰方法</p><p>private 方法子类看不见，无法重写实现，冲突。</p><p> <code>abstract</code> 和 <code>static</code> 不能一起修饰方法</p><p>静态方法可以直接类名调用，不需要子类实现，冲突。</p><div class="table-container"><table><thead><tr><th style="text-align:center">类型</th><th style="text-align:center">能否实例化</th><th style="text-align:center">能否拥有抽象方法</th></tr></thead><tbody><tr><td style="text-align:center">普通类</td><td style="text-align:center">可以 new</td><td style="text-align:center">不能有抽象方法</td></tr><tr><td style="text-align:center">抽象类</td><td style="text-align:center">不能直接 new</td><td style="text-align:center">可以定义抽象方法</td></tr></tbody></table></div><h3 id="四、易错知识点"><a href="#四、易错知识点" class="headerlink" title="四、易错知识点"></a>四、易错知识点</h3><ol><li><p>抽象类一定包含抽象方法？</p><p>错误！抽象类可以没有任何抽象方法，只是很少这么写。</p></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Test</span>&#123;</span><br><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_">hello</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>有抽象方法的类，一定是抽象类？</li></ol><p>正确！普通类存在抽象方法直接编译报错。</p><h2 id="Java-接口（interface）"><a href="#Java-接口（interface）" class="headerlink" title="Java 接口（interface）"></a>Java 接口（interface）</h2><p><strong>接口是行为规范、标准契约，定义一组功能（方法声明），本身不关心怎么实现。</strong></p><p>语法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">interface 接口名 &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>类<strong>实现</strong>接口，关键字：<code>implements</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">class 类名 implements 接口<span class="number">1</span>,接口<span class="number">2</span> &#123;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="不同-JDK-版本接口内允许的成员"><a href="#不同-JDK-版本接口内允许的成员" class="headerlink" title="不同 JDK 版本接口内允许的成员"></a>不同 JDK 版本接口内允许的成员</h3><h4 id="1）JDK7-及以前（经典老式接口）"><a href="#1）JDK7-及以前（经典老式接口）" class="headerlink" title="1）JDK7 及以前（经典老式接口）"></a>1）JDK7 及以前（经典老式接口）</h4><p>接口里面<strong>只能有：全局常量、抽象方法</strong></p><ol><li>常量：<code>public static final</code> 修饰，<strong>默认隐藏</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Usb</span>&#123;</span><br><span class="line">    <span class="comment">// 默认：public static final int MAX_SPEED = 480;</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">MAX_SPEED</span> <span class="operator">=</span> <span class="number">480</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>必须初始化，不能修改。</p></blockquote><ol><li>抽象方法：<code>public abstract</code> 修饰，<strong>默认隐藏</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Usb</span>&#123;</span><br><span class="line">    <span class="comment">// public abstract void work();</span></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><h4 id="2）JDK-8-新增"><a href="#2）JDK-8-新增" class="headerlink" title="2）JDK 8 新增"></a>2）JDK 8 新增</h4><p><strong>默认方法（default）</strong>：带方法体，实现类可以选择重写，不重写就用默认实现</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">default void show()&#123;</span><br><span class="line">    System.out.println(&quot;默认方法&quot;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>静态方法（static）</strong>：接口。静态方法 () 调用，<strong>实现类不能继承</strong></p><h4 id="3）JDK-9-新增"><a href="#3）JDK-9-新增" class="headerlink" title="3）JDK 9 新增"></a>3）JDK 9 新增</h4><p><strong>私有方法 private</strong>：供接口内部默认方法复用</p><blockquote><p>面试常记：日常开发核心掌握【常量、抽象方法、default 默认方法、static 静态方法】</p></blockquote><hr><h3 id="核心语法规则"><a href="#核心语法规则" class="headerlink" title="核心语法规则"></a>核心语法规则</h3><ol><li><strong>接口不能 new 实例化</strong>，没有构造方法！</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Usb</span>(); <span class="comment">// 报错</span></span><br></pre></td></tr></table></figure><ol><li>类和接口：<code>implements</code> <strong>多实现</strong>（一个类可以同时实现多个接口）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Phone</span> <span class="keyword">implements</span> <span class="title class_">Usb</span>, Bluetooth&#123;&#125;</span><br></pre></td></tr></table></figure><ol><li>接口与接口：<code>extends</code> <strong>多继承</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">A</span>&#123;&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">B</span>&#123;&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">C</span> <span class="keyword">extends</span> <span class="title class_">A</span>,B&#123;&#125;</span><br></pre></td></tr></table></figure><ol><li>实现类要求：</li></ol><ul><li><strong>普通类实现接口：必须重写接口中所有抽象方法</strong></li><li><strong>抽象类实现接口：可以不实现抽象方法，交给它的子类完成</strong></li></ul><ol><li>方法访问权限：</li></ol><p>接口抽象方法强制 public，重写的时候不能缩小权限（不能写 protected/private）</p><div class="table-container"><table><thead><tr><th style="text-align:center">对比项</th><th style="text-align:center">抽象类 abstract class</th><th style="text-align:center">接口 interface</th></tr></thead><tbody><tr><td style="text-align:center">关键字</td><td style="text-align:center">class</td><td style="text-align:center">interface</td></tr><tr><td style="text-align:center">构造方法</td><td style="text-align:center">有构造方法（供子类 super 调用）</td><td style="text-align:center">无构造方法，不能实例化</td></tr><tr><td style="text-align:center">成员变量</td><td style="text-align:center">可以普通变量、静态变量</td><td style="text-align:center">只能<code>public static final</code>常量</td></tr><tr><td style="text-align:center">方法 (JDK7)</td><td style="text-align:center">普通方法 + 抽象方法</td><td style="text-align:center">只能抽象方法、常量</td></tr><tr><td style="text-align:center">继承关系</td><td style="text-align:center">单继承 <code>extends</code></td><td style="text-align:center">多实现<code>implements</code>；接口之间多继承<code>extends</code></td></tr><tr><td style="text-align:center">设计角度</td><td style="text-align:center"><strong>模板，描述事物是什么</strong></td><td style="text-align:center"><strong>行为标准，描述事物能做什么</strong></td></tr></tbody></table></div><blockquote><p>设计思想区分：</p><p>Animal（抽象类）：动物<strong>是什么</strong></p><p>Run（接口）：动物<strong>能跑</strong></p><p>Dog extends Animal implements Run</p></blockquote><h3 id="易错知识点"><a href="#易错知识点" class="headerlink" title="易错知识点"></a>易错知识点</h3><ol><li><p>default 默认方法冲突问题</p><p>一个类实现多个接口，如果多个接口存在<strong>同名同参数 default 方法</strong>，实现类<strong>必须手动重写该方法</strong>，否则编译报错。</p></li><li><p>接口静态方法不会被继承,普通类的可以被继承但不能重写</p></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">A</span>&#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span>&#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> <span class="keyword">implements</span> <span class="title class_">A</span>&#123;&#125;</span><br><span class="line">B.test(); <span class="comment">// 错误，只能 A.test()</span></span><br></pre></td></tr></table></figure><ol><li>接口常量默认<code>public static final</code>，写不写都一样，不能修改。</li></ol><h2 id="Java-内部类"><a href="#Java-内部类" class="headerlink" title="Java 内部类"></a>Java 内部类</h2><h3 id="一、什么是内部类"><a href="#一、什么是内部类" class="headerlink" title="一、什么是内部类"></a>一、什么是内部类</h3><p><strong>把一个类定义在另一个类的内部，这个类就叫内部类；外面的类称为外部类（外围类）。</strong></p><figure class="highlight java"><table><tr><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_">Outer</span>&#123;</span><br><span class="line">    <span class="comment">//内部类</span></span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Inner</span>&#123;</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="分类（四大类）"><a href="#分类（四大类）" class="headerlink" title="分类（四大类）"></a>分类（四大类）</h3><ol><li><strong>成员内部类（实例内部类）</strong></li><li><strong>静态内部类（静态嵌套类）</strong></li><li><strong>局部内部类（方法内定义）</strong></li><li><strong>匿名内部类（重点，经常和接口 / 抽象类搭配）</strong></li></ol><hr><h4 id="成员内部类（没有-static，写在外部类成员位置）"><a href="#成员内部类（没有-static，写在外部类成员位置）" class="headerlink" title="成员内部类（没有 static，写在外部类成员位置）"></a>成员内部类（没有 static，写在外部类成员位置）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Outer</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="comment">//成员内部类</span></span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Inner</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">            <span class="comment">//内部类可以直接访问外部类所有成员，包括private私有！</span></span><br><span class="line">            System.out.println(name);</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>核心规则</p><ol><li><strong>成员内部类依赖外部类对象，不能直接 new</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Outer</span> <span class="variable">outer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Outer</span>();</span><br><span class="line">Outer.<span class="type">Inner</span> <span class="variable">inner</span> <span class="operator">=</span> outer.<span class="keyword">new</span> <span class="title class_">Inner</span>();</span><br><span class="line">inner.show();</span><br></pre></td></tr></table></figure><p>简写：<code>Outer.Inner inner = new Outer().new Inner();</code></p><ol><li><strong>内外类出现同名变量怎么区分？</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Outer</span>&#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">a</span> <span class="operator">=</span> <span class="string">&quot;外部a&quot;</span>;</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Inner</span>&#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">a</span> <span class="operator">=</span> <span class="string">&quot;内部a&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span>&#123;</span><br><span class="line">            System.out.println(a);            <span class="comment">//内部a</span></span><br><span class="line">            System.out.println(<span class="built_in">this</span>.a);       <span class="comment">//内部a</span></span><br><span class="line">            System.out.println(Outer.<span class="built_in">this</span>.a); <span class="comment">//外部类的a！</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><ol><li><p>限制</p><p>内部类里面不能定义静态成员（static 变量、静态方法）</p></li></ol><blockquote><p>因为成员内部类必须依托外部实例，静态属于类，冲突</p></blockquote><ol><li><p>访问权限</p><p>成员内部类可以使用 <code>private / protected / public</code>修饰；</p><p>如果内部类被 private 修饰，外部其他类无法访问这个内部类。</p></li></ol><hr><h4 id="静态内部类（static-修饰，也叫嵌套类）"><a href="#静态内部类（static-修饰，也叫嵌套类）" class="headerlink" title="静态内部类（static 修饰，也叫嵌套类）"></a>静态内部类（static 修饰，也叫嵌套类）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Outer</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="string">&quot;静态外部变量&quot;</span>;</span><br><span class="line">    <span class="comment">//静态内部类</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">StaticInner</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">            <span class="comment">//只能访问外部类【静态成员】</span></span><br><span class="line">            System.out.println(msg);</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>核心规则</p><ol><li><strong>不依赖外部类对象，直接通过外部类类名创建</strong></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Outer.<span class="type">StaticInner</span> <span class="variable">inner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Outer</span>.StaticInner();</span><br></pre></td></tr></table></figure><blockquote><p>不能写 outer.new StaticInner ()</p></blockquote><ol><li><p>访问范围</p><p>只能直接访问外部类 <strong>static 静态属性、静态方法</strong>；</p><p>想要访问外部实例成员，必须传入外部类对象。</p></li><li><p>可以拥有自己的静态成员。</p></li></ol><p>成员内部类 vs 静态内部类 关键区别</p><div class="table-container"><table><thead><tr><th style="text-align:center">类型</th><th style="text-align:center">创建方式</th><th style="text-align:center">依赖外部对象</th><th style="text-align:center">可访问外部成员</th></tr></thead><tbody><tr><td style="text-align:center">成员内部类</td><td style="text-align:center"><code>外部对象.new 内部类()</code></td><td style="text-align:center">必须有外部实例</td><td style="text-align:center">所有成员（静态 + 实例 + 私有）</td></tr><tr><td style="text-align:center">静态内部类</td><td style="text-align:center"><code>new 外部类.内部类()</code></td><td style="text-align:center">不需要外部实例</td><td style="text-align:center">仅外部静态成员</td></tr></tbody></table></div><hr><h4 id="局部内部类（定义在【方法里面】）"><a href="#局部内部类（定义在【方法里面】）" class="headerlink" title="局部内部类（定义在【方法里面】）"></a>局部内部类（定义在【方法里面】）</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Outer</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">//局部内部类，作用域仅限于当前方法</span></span><br><span class="line">        <span class="keyword">class</span> <span class="title class_">LocalInner</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;局部内部类方法&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//只能在方法内创建使用</span></span><br><span class="line">        <span class="type">LocalInner</span> <span class="variable">li</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LocalInner</span>();</span><br><span class="line">        li.show();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>规则</p><ol><li><strong>作用域仅限当前方法</strong>，方法外面无法使用；</li><li>不能使用访问修饰符 <code>public private protected</code>；</li><li>JDK8 开始，如果访问方法里的局部变量，变量默认隐式 <code>final</code>（不能修改）；</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</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">10</span>;</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Inner</span>&#123;</span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span>&#123;</span><br><span class="line">            System.out.println(num);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// num = 20; //报错！局部内部类访问后，num等同于final</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h4 id="匿名内部类（重中之重）"><a href="#匿名内部类（重中之重）" class="headerlink" title="匿名内部类（重中之重）"></a>匿名内部类（重中之重）</h4><p><strong>没有类名的内部类，一般用来一次性实现接口 / 抽象类，简化代码。</strong></p><p>语法格式：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">new</span> 接口/抽象类/父类()&#123;</span><br><span class="line">    <span class="comment">//重写方法</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>示例：基于接口</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Usb</span>&#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><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">//匿名内部类，相当于创建接口的实现类对象</span></span><br><span class="line">        <span class="type">Usb</span> <span class="variable">usb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Usb</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_">work</span><span class="params">()</span> &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;usb设备工作&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        usb.work();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>核心特点</p><ol><li><strong>匿名内部类只能使用一次</strong>，只能创建一个对象；下次创建对象的时候必须重新写一边,没有名字不能重复使用;</li><li>没有构造方法；</li><li>本质：隐式继承父类 / 实现接口；</li><li>属于局部内部类的一种；</li></ol><p>常用场景：方法传参、线程 <code>new Thread(new Runnable(){})</code></p><blockquote><p>拓展：JDK8 Lambda 表达式可以简化只有一个抽象方法的接口，替代匿名内部类。</p></blockquote><hr><h3 id="通用核心考点：内部类访问外部类"><a href="#通用核心考点：内部类访问外部类" class="headerlink" title="通用核心考点：内部类访问外部类"></a>通用核心考点：内部类访问外部类</h3><ol><li>内部类天生持有外部类的隐式引用：<code>外部类名.this</code></li><li>外部类想要访问内部类成员：<strong>必须先创建内部类对象</strong>，不能直接访问</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Outer</span>&#123;</span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Inner</span>&#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">100</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_">test</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// System.out.println(num); //不行！</span></span><br><span class="line">        <span class="type">Inner</span> <span class="variable">in</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Inner</span>();</span><br><span class="line">        System.out.println(in.num); <span class="comment">//需要对象</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><div class="table-container"><table><thead><tr><th style="text-align:center">类别</th><th style="text-align:center">定义位置</th><th style="text-align:center">是否 static</th><th style="text-align:center">创建对象方式</th><th style="text-align:center">核心特点</th></tr></thead><tbody><tr><td style="text-align:center">成员内部类</td><td style="text-align:center">外部类中，方法外</td><td style="text-align:center">无</td><td style="text-align:center"><code>new Outer().new Inner()</code></td><td style="text-align:center">依赖外部对象，可访问所有外部成员，不能定义 static</td></tr><tr><td style="text-align:center">静态内部类</td><td style="text-align:center">外部类中，方法外</td><td style="text-align:center">static</td><td style="text-align:center"><code>new Outer.StaticInner()</code></td><td style="text-align:center">无需外部对象，仅访问外部静态成员</td></tr><tr><td style="text-align:center">局部内部类</td><td style="text-align:center">方法体内</td><td style="text-align:center">无</td><td style="text-align:center">方法内 new</td><td style="text-align:center">仅限方法内使用，不能加权限修饰符</td></tr><tr><td style="text-align:center">匿名内部类</td><td style="text-align:center">方法 / 参数中</td><td style="text-align:center">无</td><td style="text-align:center"><code>new 父类/接口(){}</code></td><td style="text-align:center">无类名，一次性使用，实现抽象方法</td></tr></tbody></table></div>]]></content>
    
    
    <summary type="html">Java面向对象编程相关知识点</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=35f8cdd6-246b-3ce8-e08d-c25798c086c8" type="image"/>
    
    
    <category term="Java学习笔记" scheme="https://blog.itrf.cn/tags/Java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>Java学习笔记01</title>
    <link href="https://blog.itrf.cn/posts/9e72/"/>
    <id>https://blog.itrf.cn/posts/9e72/</id>
    <published>2026-06-30T16:00:00.000Z</published>
    <updated>2026-09-03T07:49:26.305Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Java概述"><a href="#Java概述" class="headerlink" title="Java概述"></a>Java概述</h2><p>Java是由Sun Microststems 公司于1995年5月推出的面向对象的程序设计语言，作者 詹姆斯•高斯林。</p><h3 id="Java技术体系平台"><a href="#Java技术体系平台" class="headerlink" title="Java技术体系平台"></a>Java技术体系平台</h3><ul><li>Java SE 标准版</li><li>Java EE 企业版    主要学习这个</li><li>Java ME 小型版</li></ul><h3 id="Java重要特点"><a href="#Java重要特点" class="headerlink" title="Java重要特点"></a>Java重要特点</h3><ul><li>语言简单</li><li>面向对象 oop</li><li>分布式</li><li>语言健壮</li><li>安全</li><li>体系结构中立</li><li>可移植</li><li>跨平台   </li><li>解释型语言  需要解释器</li><li>高性能</li><li>动态</li></ul><h3 id="Java虚拟机-JVM"><a href="#Java虚拟机-JVM" class="headerlink" title="Java虚拟机 JVM"></a>Java虚拟机 JVM</h3><p>用于在不同的设备上运行.class字节码文件，屏蔽系统差异</p><h3 id="JDK、JRE"><a href="#JDK、JRE" class="headerlink" title="JDK、JRE"></a>JDK、JRE</h3><p>JDK java开发工具包 Java Development Kit   开发者用</p><p>JDK=JRE+java开发工具</p><p>JRE java运行环境  Java Runtime Environment  普通用户用</p><p>JRE=JVM+Java核心类库</p><h4 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h4><ol><li>文件名必须与public类名一致</li><li>一个源文件中最多只能有一个public类</li><li>编译之后每一个类对应一个.class文件</li><li>Java语言严格区分大小写</li><li>每个语句以 “;” 结束</li><li>java 命令之后跟类名</li><li>main 方法就是 Java 程序的唯一合法入口</li><li>原则：一个文件只写一个类</li></ol><h2 id="Java基础语法"><a href="#Java基础语法" class="headerlink" title="Java基础语法"></a>Java基础语法</h2><h3 id="命名规范"><a href="#命名规范" class="headerlink" title="命名规范"></a>命名规范</h3><div class="table-container"><table><thead><tr><th><strong>标识符类型</strong></th><th><strong>命名范式</strong></th><th><strong>示例</strong></th><th><strong>备注</strong></th></tr></thead><tbody><tr><td><strong>包 (Package)</strong></td><td>全小写</td><td><code>com.apple.ios</code></td><td>建议使用反向域名</td></tr><tr><td><strong>类 (Class)</strong></td><td>大驼峰</td><td><code>StudentInfo</code></td><td>名词</td></tr><tr><td><strong>方法 (Method)</strong></td><td>小驼峰</td><td><code>getScore()</code></td><td>动词</td></tr><tr><td><strong>变量 (Variable)</strong></td><td>小驼峰</td><td><code>studentAge</code></td><td>描述性名词</td></tr><tr><td><strong>常量 (Constant)</strong></td><td>大写 + 下划线</td><td><code>MAX_AGE</code></td><td><code>static final</code> 修饰</td></tr><tr><td><strong>抽象类</strong></td><td>大驼峰</td><td><code>AbstractService</code></td><td>通常以 Abstract 开头</td></tr><tr><td><strong>异常类</strong></td><td>大驼峰</td><td><code>LoginException</code></td><td>必须以 Exception 结尾</td></tr></tbody></table></div><h3 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h3><p>变量是程序的基本组成单位</p><p>三要素  ：类型+名称+值</p><p>变量必须先声明再使用</p><p>同一个作用域之内变量不能重名</p><h3 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h3><ul><li>基本数据类型<ul><li>数值型<ul><li>整数类型，存放整数：<code>byte[1]</code>、<code>short[2]</code>、<code>int[4]</code>、<code>long[8]</code></li><li>浮点 (小数) 类型：<code>float[4]</code>、<code>double[8]</code></li></ul></li><li>字符型：<code>char[2]</code>，存放单个字符 <code>&#39;a&#39;</code></li><li>布尔型：<code>boolean[1]</code>，存放 <code>true</code>、<code>false</code></li></ul></li><li>引用数据类型<ul><li>类 (class)</li><li>接口 (interface)</li><li>数组 ([])</li></ul></li></ul><h4 id="注意事项-1"><a href="#注意事项-1" class="headerlink" title="注意事项"></a>注意事项</h4><ol><li>整型常量默认为 int 型，声明 long 型常量须后加‘l’或‘L’</li><li>浮点型常量默认为 double 型，声明 float 型常量，须后加‘f’或‘F’ </li><li>“小向大”是顺水推舟，自动完成；“大向小”是强人所难，必须强转。</li><li>浮点数=符号位+指数位+尾数位  尾数部分可能丢失，造成精度损失(小数都是近似值)</li><li>永远不要用 <code>==</code> 来比较两个浮点数。应该判断它们的差值是否小于一个极小的数值（如 <code>1e-9</code>）</li><li>当我们对运算结果是小数的进行相等判断是，要小心应该是以两个数的差值的绝对值，在某个精度范围类判断</li><li>char字符常量只能用单引号<code>&#39;&#39;</code>引用，只能存单个字符或者是数字，本质是存储的是一个整数unicode对映的编码，可以进行计算</li></ol><h4 id="补充知识浮点数的计算机存储"><a href="#补充知识浮点数的计算机存储" class="headerlink" title="补充知识浮点数的计算机存储"></a>补充知识浮点数的计算机存储</h4><p>计算机里的浮点数就像一个精密的<strong>二进制科学计数法</strong>，其存储结构固定为：</p><script type="math/tex; mode=display">\text{Value} = (-1)^S \times (1.M) \times 2^{E-Bias}</script><ul><li><strong>符号位 (Sign)</strong>：1位。<code>0</code>正<code>1</code>负。</li><li><strong>指数位 (Exponent)</strong>：<ul><li><strong>作用</strong>：决定数值范围（小数点移动多少位）。</li><li><strong>黑科技</strong>：使用<strong>偏移码（Bias）</strong>，通过给真实指数加一个固定值（如127），强行把负指数变成正整数，方便硬件直接比较大小。</li></ul></li><li><strong>尾数位 (Mantissa)</strong>：<ul><li><strong>作用</strong>：决定数值精度（长什么样）。</li><li><strong>黑科技</strong>：<strong>隐藏整数位的 <code>1</code></strong>。既然二进制规格化后第一位必然是1，干脆省掉不存，多赚1位精度。</li></ul></li></ul><h4 id="基本数据类型转换"><a href="#基本数据类型转换" class="headerlink" title="基本数据类型转换"></a>基本数据类型转换</h4><p><strong>路径 A：</strong> <code>byte</code> → <code>short</code> → <code>int</code> → <code>long</code> → <code>float</code> → <code>double</code></p><p><strong>路径 B：</strong> <code>char</code> → <code>int</code> → <code>long</code> → <code>float</code> → <code>double</code></p><h4 id="为什么long是8个字节，float是4个字节，但是long会自动转为float？"><a href="#为什么long是8个字节，float是4个字节，但是long会自动转为float？" class="headerlink" title="为什么long是8个字节，float是4个字节，但是long会自动转为float？"></a>为什么long是8个字节，float是4个字节，但是long会自动转为float？</h4><p>它们底层的二进制存储方式完全不同。数值范围的大小并不取决于它占用了多少字节，而取决于它的结构设计</p><p><strong><code>long</code>（64 位定点数）</strong>：1 位符号位 + 63 位数值位，纯精确整数。</p><p><strong><code>float</code>（32 位 IEEE 754 浮点数）</strong>：1 位符号位 + <strong>8 位指数位</strong> + 23 位尾数位。</p><p><code>long</code>（8 字节）：最大值约 $9.22 \times 10^{18}$</p><p><code>float</code>（4 字节）：最大值约 $3.4 \times 10^{38}$（范围远大于 <code>long</code>）</p><p>引入指数位后，表达范围被几何级放大。</p><h5 id="注意看："><a href="#注意看：" class="headerlink" title="注意看："></a><strong>注意看：</strong></h5><p><code>byte</code> 可以自动转 <code>short</code>。<code>short</code> <strong>不能</strong>直接转 <code>char</code>。<code>byte</code> 也 <strong>不能</strong>直接转 <code>char</code>。但是它们都可以统一自动转为 <strong><code>int</code></strong>再计算。原因是<code>short</code>和<code>byte</code>有符号。</p><p>提升原则：表达式结果的类型自动提升为操作数中最大的类型，最小是int类型</p><h4 id="注意事项："><a href="#注意事项：" class="headerlink" title="注意事项："></a>注意事项：</h4><p><code>byte</code> <code>short</code> <code>char</code> 类型的变量进行计算都会转换成 <code>int</code> 类型</p><p><code>byte</code> <code>short</code> <code>char</code> 类型的常量(字面常量)进行计算会进行优化机制</p><p>常量计算不溢出时，类型由左侧接收变量决定（直通不升格）；一旦溢出，则被视为 <code>int</code> 类型（导致报错）。</p><p>只要公式中<strong>出现了一个变量</strong>，整个公式的“常量优化”特权就会立即消失，全面进入<strong>变量运算规则</strong>。</p><h4 id="强制类型转化"><a href="#强制类型转化" class="headerlink" title="强制类型转化"></a>强制类型转化</h4><p><strong>将容量大的数据类型转换为容量小的数据类型</strong>。使用时要加上强制转换符 ( )，但可能造成<strong>精度降低或溢出</strong>,格外要注意。</p><p>Java基本数据类型中，数值类型和boolean<strong>不能互相强制转换</strong></p><h4 id="基本数据类型和String类型的转换"><a href="#基本数据类型和String类型的转换" class="headerlink" title="基本数据类型和String类型的转换"></a>基本数据类型和String类型的转换</h4><p>基本数据类型转String类型：将基本类型的值+“”</p><p>String类型转基本数据类型：利用包装类  xxx.parsexxx(“”)</p><p>包装类的作用：让基本数据类型能够像对象一样工作，具备属性和方法</p><div class="table-container"><table><thead><tr><th><strong>基本数据类型</strong></th><th><strong>对应的包装类</strong></th></tr></thead><tbody><tr><td><code>byte</code></td><td><code>Byte</code></td></tr><tr><td><code>short</code></td><td><code>Short</code></td></tr><tr><td><strong><code>int</code></strong></td><td><strong><code>Integer</code></strong></td></tr><tr><td><code>long</code></td><td><code>Long</code></td></tr><tr><td><code>float</code></td><td><code>Float</code></td></tr><tr><td><code>double</code></td><td><code>Double</code></td></tr><tr><td><strong><code>char</code></strong></td><td><strong><code>Character</code></strong></td></tr><tr><td><code>boolean</code></td><td><code>Boolean</code></td></tr></tbody></table></div><h5 id="号的作用"><a href="#号的作用" class="headerlink" title="+号的作用"></a>+号的作用</h5><ol><li>当左右两边都是数值类型时，则做加法运算</li><li>当左右两边有一方为字符串，则做拼接运算</li><li>运算顺序，从左到右</li></ol><h3 id="Java-转义字符"><a href="#Java-转义字符" class="headerlink" title="Java 转义字符"></a>Java 转义字符</h3><div class="table-container"><table><thead><tr><th>转义字符</th><th>含义</th></tr></thead><tbody><tr><td><code>\&#39;</code></td><td>单引号 <code>&#39;</code></td></tr><tr><td><code>\&quot;</code></td><td>双引号 <code>&quot;</code></td></tr><tr><td><code>\\</code></td><td>反斜杠 <code>\</code></td></tr><tr><td><code>\n</code></td><td>换行</td></tr><tr><td><code>\r</code></td><td>回车</td></tr><tr><td><code>\t</code></td><td>Tab 制表符（缩进）</td></tr><tr><td><code>\b</code></td><td>退格</td></tr><tr><td><code>\f</code></td><td>换页（很少用）</td></tr></tbody></table></div><h4 id="特殊转义字符-r"><a href="#特殊转义字符-r" class="headerlink" title="特殊转义字符\r"></a>特殊转义字符\r</h4><p>功能：将光标移动到当前行的开头</p><p>不同的操作系统对“换行”的定义<strong>曾</strong>各不相同：</p><ul><li><strong>Windows</strong>: 使用 <code>\r\n</code>（回车 + 换行）代表一行结束。</li><li><strong>Unix / Linux / macOS</strong>: 统一使用 <code>\n</code>（换行）代表一行结束。</li></ul><p><code>\r</code> 有一个非常酷的用途：<strong>制作简单的命令行进度条。</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= <span class="number">100</span>; i++) &#123;</span><br><span class="line">    System.out.print(<span class="string">&quot;进度: &quot;</span> + i + <span class="string">&quot;%\r&quot;</span>); <span class="comment">// 利用 \r 回到行首不断覆盖</span></span><br><span class="line">    Thread.sleep(<span class="number">100</span>); <span class="comment">// 暂停一小会儿</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h3><ul><li>单行注释  //</li><li>多行注释  /<em> </em>/</li><li>文档注释  /<em>*  </em>/</li></ul><p>被注释的文字，不会被JVM解释执行</p><h3 id="代码规范"><a href="#代码规范" class="headerlink" title="代码规范"></a>代码规范</h3><ol><li>类、方法的注释，要以Javadoc的方式来写</li><li>非Java Doc 的注释，往往是给代码维护者看的</li><li>使用Tab进行右移，shift+tab 进行左移</li><li>运算符和=两边习惯性各加一个空格</li><li>源文件使用utf-8编码</li><li>行宽度不要超过80个字符</li><li>代码编写推荐行尾风格</li></ol><h2 id="修饰符"><a href="#修饰符" class="headerlink" title="修饰符"></a>修饰符</h2><ol><li><strong><code>public</code></strong>：公开的。无论是哪个类、哪个包、还是外部项目，都可以直接访问。</li><li><strong><code>protected</code></strong>：受保护的。同一个包内的类可以访问；不同包中的<strong>子类</strong>也可以访问。</li><li><strong>默认（无修饰符）</strong>：包级私有（package-private）。只有<strong>同一个包</strong>内的类可以访问，跨包了就不能访问。</li><li><strong><code>private</code></strong>：私有的。只有在<strong>定义它的类内部</strong>才能访问，体现了面向对象的封装性。通常属性会设为 <code>private</code>，然后通过 <code>get/set</code> 方法来间接访问。</li></ol><h2 id="运算符"><a href="#运算符" class="headerlink" title="运算符"></a>运算符</h2><h3 id="算术运算符"><a href="#算术运算符" class="headerlink" title="算术运算符"></a>算术运算符</h3><p>示例</p><figure class="highlight java"><table><tr><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><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">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">     <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line">     <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> <span class="number">25</span>;</span><br><span class="line">     <span class="type">int</span> <span class="variable">d</span> <span class="operator">=</span> <span class="number">25</span>;</span><br><span class="line">     System.out.println(<span class="string">&quot;a + b = &quot;</span> + (a + b) );</span><br><span class="line">     System.out.println(<span class="string">&quot;a - b = &quot;</span> + (a - b) );</span><br><span class="line">     System.out.println(<span class="string">&quot;a * b = &quot;</span> + (a * b) );</span><br><span class="line">     System.out.println(<span class="string">&quot;b / a = &quot;</span> + (b / a) );</span><br><span class="line">     System.out.println(<span class="string">&quot;b % a = &quot;</span> + (b % a) );</span><br><span class="line">     System.out.println(<span class="string">&quot;c % a = &quot;</span> + (c % a) );</span><br><span class="line">     System.out.println(<span class="string">&quot;a++   = &quot;</span> +  (a++) );</span><br><span class="line">     System.out.println(<span class="string">&quot;a--   = &quot;</span> +  (a--) );</span><br><span class="line">     <span class="comment">// 查看  d++ 与 ++d 的不同</span></span><br><span class="line">     System.out.println(<span class="string">&quot;d++   = &quot;</span> +  (d++) );</span><br><span class="line">     System.out.println(<span class="string">&quot;d     = &quot;</span> +  d);</span><br><span class="line">     System.out.println(<span class="string">&quot;++d   = &quot;</span> +  (++d) );</span><br><span class="line">  &#125;</span><br><span class="line">&#125; </span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">a + b = <span class="number">30</span></span><br><span class="line">a - b = -<span class="number">10</span></span><br><span class="line">a * b = <span class="number">200</span></span><br><span class="line">b / a = <span class="number">2</span></span><br><span class="line">b % a = <span class="number">0</span></span><br><span class="line">c % a = <span class="number">5</span></span><br><span class="line">a++   = <span class="number">10</span></span><br><span class="line">a--   = <span class="number">11</span></span><br><span class="line">d++   = <span class="number">25</span></span><br><span class="line">d     =<span class="number">26</span></span><br><span class="line">++d   = <span class="number">27</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="注意事项-2"><a href="#注意事项-2" class="headerlink" title="注意事项"></a>注意事项</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.out.println(<span class="number">10</span> / <span class="number">4</span>); <span class="comment">//从数学来看是 2.5, java 中 2</span></span><br><span class="line">System.out.println(<span class="number">10.0</span> / <span class="number">4</span>); <span class="comment">//java 是 2.5</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">score</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"><span class="type">double</span> <span class="variable">result</span> <span class="operator">=</span> score / total; <span class="comment">// 猜猜结果是多少？ 2.0</span></span><br><span class="line">System.out.println(result);</span><br></pre></td></tr></table></figure><p><strong>原因：</strong> <code>score / total</code> 的计算先发生，因为两个都是 <code>int</code>，所以先算出了 <code>2</code>。</p><p><strong>赋值：</strong> 算出 <code>2</code> 之后，才把这个整数赋值给 <code>double result</code>。这时 <code>2</code> 变成了 <code>2.0</code>。</p><p>在 % 的本质 看一个公式<code>a % b = a - a / b * b</code></p><p>由公式可以知道：在 Java 的取模运算中，结果的符号只取决于左操作数（被除数 $a$）的符号，而与右操作数（除数 $b$）无关。</p><h5 id="面试题"><a href="#面试题" class="headerlink" title="面试题"></a>面试题</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>；</span><br><span class="line">i = i++；</span><br><span class="line">System.out.println(i);</span><br></pre></td></tr></table></figure><p>答案是 1</p><p>过程 <code>temp = i    i=i+1   i=temp</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>；</span><br><span class="line">i = ++i；</span><br><span class="line">System.out.println(i);</span><br></pre></td></tr></table></figure><p>答案是 2</p><p>过程 <code>i=i+1  temp=i  i=temp</code></p><h4 id="补充知识点"><a href="#补充知识点" class="headerlink" title="补充知识点"></a>补充知识点</h4><p>java和python在处理/的时候采用不同的处理方法</p><h5 id="Java：向零取整-Truncate"><a href="#Java：向零取整-Truncate" class="headerlink" title="Java：向零取整 (Truncate)"></a>Java：向零取整 (Truncate)</h5><p>Java 的整数除法采用的是 <strong>截断除法</strong>。简单来说，就是直接把小数点后面的部分“扔掉”。</p><ul><li><strong>计算 <code>-7 / 3</code>：</strong> 结果是 <code>-2.333...</code>，截断后变成 <code>-2</code>。</li><li><strong>推导 <code>%</code>：</strong> $-7 - (3 \times -2) = -7 + 6 = -1$。</li></ul><h5 id="Python：向下取整-Floor"><a href="#Python：向下取整-Floor" class="headerlink" title="Python：向下取整 (Floor)"></a>Python：向下取整 (Floor)</h5><p>Python 的整数除法（<code>//</code>）采用的是 <strong>地板除</strong>。它向数轴的左侧（负无穷方向）取整。</p><ul><li><strong>计算 <code>-7 // 3</code>：</strong> 结果是 <code>-2.333...</code>，向左取整变成 <code>-3</code>。</li><li><strong>推导 <code>%</code>：</strong> $-7 - (3 \times -3) = -7 + 9 = 2$。</li></ul><h3 id="关系运算符"><a href="#关系运算符" class="headerlink" title="关系运算符"></a>关系运算符</h3><p>示例</p><figure class="highlight java"><table><tr><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><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">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">     <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line">     System.out.println(<span class="string">&quot;a == b = &quot;</span> + (a == b) );</span><br><span class="line">     System.out.println(<span class="string">&quot;a != b = &quot;</span> + (a != b) );</span><br><span class="line">     System.out.println(<span class="string">&quot;a &gt; b = &quot;</span> + (a &gt; b) );</span><br><span class="line">     System.out.println(<span class="string">&quot;a &lt; b = &quot;</span> + (a &lt; b) );</span><br><span class="line">     System.out.println(<span class="string">&quot;b &gt;= a = &quot;</span> + (b &gt;= a) );</span><br><span class="line">     System.out.println(<span class="string">&quot;b &lt;= a = &quot;</span> + (b &lt;= a) );</span><br><span class="line">   &#125; </span><br><span class="line">&#125;  </span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">a == b = <span class="literal">false</span></span><br><span class="line">a != b = <span class="literal">true</span></span><br><span class="line">a &gt; b = <span class="literal">false</span></span><br><span class="line">a &lt; b = <span class="literal">true</span> b &gt;= a = <span class="literal">true</span></span><br><span class="line">b &lt;= a = <span class="literal">false</span> </span><br><span class="line"></span><br></pre></td></tr></table></figure><p>关系运算符的结果都是boolean类型，也是就是要么true要么false</p><p>instanceof <strong>判断对象是不是某个类 / 子类 / 接口的实例</strong>，返回布尔值 <code>true / false</code> 判断运行类型</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">对象  <span class="keyword">instanceof</span>  类型</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="comment">// 判断obj是不是String的实例</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">res</span> <span class="operator">=</span> obj <span class="keyword">instanceof</span> String;</span><br><span class="line">System.out.println(res); <span class="comment">// true</span></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="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="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">60</span>; <span class="comment">/* 60 = 0011 1100 */</span> </span><br><span class="line">     <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">13</span>; <span class="comment">/* 13 = 0000 1101 */</span></span><br><span class="line">     <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">     c = a &amp; b;       <span class="comment">/* 12 = 0000 1100 */</span></span><br><span class="line">     System.out.println(<span class="string">&quot;a &amp; b = &quot;</span> + c );</span><br><span class="line"></span><br><span class="line">     c = a | b;       <span class="comment">/* 61 = 0011 1101 */</span></span><br><span class="line">     System.out.println(<span class="string">&quot;a | b = &quot;</span> + c );</span><br><span class="line"></span><br><span class="line">     c = a ^ b;       <span class="comment">/* 49 = 0011 0001 */</span></span><br><span class="line">     System.out.println(<span class="string">&quot;a ^ b = &quot;</span> + c );</span><br><span class="line"></span><br><span class="line">     c = ~a;          <span class="comment">/*-61 = 1100 0011 */</span></span><br><span class="line">     System.out.println(<span class="string">&quot;~a = &quot;</span> + c );</span><br><span class="line"></span><br><span class="line">     c = a &lt;&lt; <span class="number">2</span>;     <span class="comment">/* 240 = 1111 0000 */</span></span><br><span class="line">     System.out.println(<span class="string">&quot;a &lt;&lt; 2 = &quot;</span> + c );</span><br><span class="line">     c = a &gt;&gt; <span class="number">2</span>;     <span class="comment">/* 215 = 1111 */</span></span><br><span class="line">     System.out.println(<span class="string">&quot;a &gt;&gt; 2  = &quot;</span> + c );</span><br><span class="line">  </span><br><span class="line">     c = a &gt;&gt;&gt; <span class="number">2</span>;     <span class="comment">/* 215 = 0000 1111 */</span></span><br><span class="line">     System.out.println(<span class="string">&quot;a &gt;&gt;&gt; 2 = &quot;</span> + c );</span><br><span class="line">  &#125;</span><br><span class="line">&#125; </span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">a &amp; b = <span class="number">12</span></span><br><span class="line">a | b = <span class="number">61</span></span><br><span class="line">a ^ b = <span class="number">49</span></span><br><span class="line">~a = -<span class="number">61</span></span><br><span class="line">a &lt;&lt; <span class="number">2</span> = <span class="number">240</span> </span><br><span class="line">a &gt;&gt; <span class="number">2</span> = <span class="number">15</span></span><br><span class="line">a &gt;&gt;&gt; <span class="number">2</span> = <span class="number">15</span></span><br></pre></td></tr></table></figure><h3 id="逻辑运算符"><a href="#逻辑运算符" class="headerlink" title="逻辑运算符"></a>逻辑运算符</h3><p>1) a&amp;b : &amp; 叫逻辑与：规则：当 a 和 b 同时为 true ,则结果为 true, 否则为 false<br>2) a&amp;&amp;b : &amp;&amp; 叫短路与：规则：当 a 和 b 同时为 true ,则结果为 true,否则为 false<br>3) a|b : | 叫逻辑或，规则：当 a 和 b ，有一个为 true ,则结果为 true,否则为 false<br>4) a||b : || 叫短路或，规则：当 a 和 b ，有一个为 true ,则结果为 true,否则为 false<br>5) !a : 叫取反，或者非运算。当 a 为 true, 则结果为 false, 当 a 为 false 是，结果为 true<br>6) a^b: 叫逻辑异或，当 a 和 b 不同时，则结果为 true, 否则为 false</p><p>对于&amp;&amp;短路与而言，如果第一个条件为 false ,后面的条件不再判断，效率高</p><p>对于&amp;逻辑与而言，如果第一个条件为 false ,后面的条件仍然会判断，效率低</p><p>||短路或：如果第一个条件为 true，则第二个条件不会判断，最终结果为 true，效率高</p><p>| 逻辑或：不管第一个条件是否为 true，第二个条件都要判断，效率低</p><h3 id="赋值运算符"><a href="#赋值运算符" class="headerlink" title="赋值运算符"></a>赋值运算符</h3><p>示例</p><figure class="highlight java"><table><tr><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="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">     <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line">     <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">     c = a + b;</span><br><span class="line">     System.out.println(<span class="string">&quot;c = a + b = &quot;</span> + c );</span><br><span class="line">     c += a ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c += a  = &quot;</span> + c );</span><br><span class="line">     c -= a ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c -= a = &quot;</span> + c );</span><br><span class="line">     c *= a ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c *= a = &quot;</span> + c );</span><br><span class="line">     a = <span class="number">10</span>;</span><br><span class="line">     c = <span class="number">15</span>;</span><br><span class="line">     c /= a ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c /= a = &quot;</span> + c );</span><br><span class="line">     a = <span class="number">10</span>;</span><br><span class="line">     c = <span class="number">15</span>;</span><br><span class="line">     c %= a ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c %= a  = &quot;</span> + c );</span><br><span class="line">     c &lt;&lt;= <span class="number">2</span> ; </span><br><span class="line">     System.out.println(<span class="string">&quot;c &lt;&lt;= 2 = &quot;</span> + c ); </span><br><span class="line">     c &gt;&gt;= <span class="number">2</span> ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c &gt;&gt;= 2 = &quot;</span> + c );</span><br><span class="line">     c &gt;&gt;= <span class="number">2</span> ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c &gt;&gt;= a = &quot;</span> + c );</span><br><span class="line">     c &amp;= a ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c &amp;= a = &quot;</span> + c );</span><br><span class="line">     c ^= a ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c ^= a = &quot;</span> + c );</span><br><span class="line">     c |= a ;</span><br><span class="line">     System.out.println(<span class="string">&quot;c |= a = &quot;</span> + c );</span><br><span class="line">  &#125;</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">c = a + b = <span class="number">30</span></span><br><span class="line">c += a  = <span class="number">40</span></span><br><span class="line">c -= a = <span class="number">30</span></span><br><span class="line">c *= a = <span class="number">300</span></span><br><span class="line">c /= a = <span class="number">1</span></span><br><span class="line">c %= a  = <span class="number">5</span></span><br><span class="line">c &lt;&lt;= <span class="number">2</span> = <span class="number">20</span> c &gt;&gt;= <span class="number">2</span> = <span class="number">5</span></span><br><span class="line">c &gt;&gt;= <span class="number">2</span> = <span class="number">1</span></span><br><span class="line">c &amp;= a  = <span class="number">0</span></span><br><span class="line">c ^= a   = <span class="number">10</span></span><br><span class="line">c |= a   = <span class="number">10</span></span><br></pre></td></tr></table></figure><p>赋值运算符特点</p><p>1) 运算顺序从右往左 </p><p>2) 赋值运算符的左边 只能是变量,右边 可以是变量、表达式、常量值</p><p>3) 复合赋值运算符等价于下面的效果比如：a+=3;等价于 a=a+3;<br>4) 复合赋值运算符会进行类型转换。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">byte</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">b += <span class="number">2</span>; <span class="comment">// 等价 b = (byte)(b + 2);</span></span><br><span class="line">b++; <span class="comment">// b = (byte)(b+1);</span></span><br></pre></td></tr></table></figure><h3 id="三元运算符"><a href="#三元运算符" class="headerlink" title="三元运算符"></a>三元运算符</h3><p>条件表达式 ? 表达式 1: 表达式 2;</p><p>运算规则：</p><ol><li>如果条件表达式为 true，运算后的结果是表达式 1；</li><li>如果条件表达式为 false，运算后的结果是表达式 2；</li></ol><p>注意事项</p><p>1) 表达式 1 和表达式 2 要为可以赋给接收变量的类型(或可以自动转换)<br>2) 三元运算符可以转成 if—else 语句</p><h3 id="运算优先级"><a href="#运算优先级" class="headerlink" title="运算优先级"></a>运算优先级</h3><div class="table-container"><table><thead><tr><th>优先级</th><th>运算符</th><th>类别</th><th>结合性</th></tr></thead><tbody><tr><td>1（最高）</td><td><code>expr++</code> <code>expr--</code></td><td>后置自增、后置自减</td><td>从左→右</td></tr><tr><td>2</td><td><code>++expr</code> <code>--expr</code> <code>+expr(正)</code> <code>-expr(负)</code> <code>~</code> <code>!</code> <code>(类型)</code></td><td>前置单目（自增自减、正负、取反、强制类型转换）</td><td><strong>从右→左</strong></td></tr><tr><td>3</td><td><code>*</code> <code>/</code> <code>%</code></td><td>乘、除、取模</td><td>从左→右</td></tr><tr><td>4</td><td><code>+</code> <code>-</code></td><td>加、减（含字符串拼接）</td><td>从左→右</td></tr><tr><td>5</td><td><code>&lt;&lt;</code> <code>&gt;&gt;</code> <code>&gt;&gt;&gt;</code></td><td>移位运算符</td><td>从左→右</td></tr><tr><td>6</td><td><code>&lt;</code> <code>&lt;=</code> <code>&gt;</code> <code>&gt;=</code> <code>instanceof</code></td><td>关系比较、类型判断</td><td>从左→右</td></tr><tr><td>7</td><td><code>==</code> <code>!=</code></td><td>相等、不相等</td><td>从左→右</td></tr><tr><td>8</td><td><code>&amp;</code></td><td>按位与 / 非短路逻辑与</td><td>从左→右</td></tr><tr><td>9</td><td><code>^</code></td><td>按位异或</td><td>从左→右</td></tr><tr><td>10</td><td>`</td><td>`</td><td>按位或 / 非短路逻辑或</td><td>从左→右</td></tr><tr><td>11</td><td><code>&amp;&amp;</code></td><td>短路逻辑与</td><td>从左→右</td></tr><tr><td>12</td><td>`</td><td></td><td>`</td><td>短路逻辑或</td><td>从左→右</td></tr><tr><td>13</td><td><code>? :</code></td><td>三元条件运算符</td><td><strong>从右→左</strong></td></tr><tr><td>14（最低）</td><td><code>=</code> <code>+=</code> <code>-=</code> <code>*=</code> <code>/=</code> <code>%=</code> <code>&amp;=</code> <code>^=</code> `</td><td>=<code>`&lt;&lt;=</code> <code>&gt;&gt;=</code> <code>&gt;&gt;&gt;=</code></td><td>全部赋值复合赋值</td><td><strong>从右→左</strong></td></tr></tbody></table></div><h3 id="补充知识点，原码反码补码"><a href="#补充知识点，原码反码补码" class="headerlink" title="补充知识点，原码反码补码"></a>补充知识点，原码反码补码</h3><p><strong>原码 (Sign-Magnitude)</strong>：最高位为符号位（0正1负），其余位是数值。</p><ul><li><em>直观但难算，0有两套写法。</em></li></ul><p><strong>反码 (Ones’ Complement)</strong>：正数不变；负数符号位不变，数值位按位取反。</p><ul><li><em>进化的中间产物，解决了符号位参与运算的问题。</em></li></ul><p><strong>补码 (Two’s Complement)</strong>：正数不变；负数在反码基础上 <strong>+1</strong>。</p><ul><li><em>计算机真正采用的格式。</em></li></ul><p>所有运算都在补码（Two’s Complement）基础上进行</p><h2 id="程序控制结构"><a href="#程序控制结构" class="headerlink" title="程序控制结构"></a>程序控制结构</h2><ol><li><p>顺序控制</p><p>程序从上到下逐行执行，中间没有任何判断和跳转</p></li><li><p>分支控制</p><ol><li><p>单分支  if</p></li><li><p>双分支  if-else</p></li><li><p>多分支  if-else  if-  else</p></li><li><p>switch分支</p><p>表达式数据类型应和case后的变量类型一致，或者可以自动转成可以比较的类型</p><p>switch(表达式)中的表达式的返回值必须是：byte short int char enum String</p><p>case子句中的值必须是常量，而不能是变量</p><p>default子句是可选的，当没有匹配的case时，执行default</p><p>break语句用来在执行完成一个case分支后使程序跳switch语句块，如果没有break，程序会顺序执行，直到遇到break</p></li></ol></li><li><p>循环控制</p><ol><li><p>for循环</p><p>for循环四个要素  循环变量初始化  循环条件  循环操作  循环变量迭代</p><p>注意 循环里面创建的变量都是临时的，用完就销毁</p><p>循环初始值可以有多条初始化语句，但要求类型一样，并且中间用逗号隔开，循环变量迭代也可以有多条变量迭代语句，中间用逗号隔开。</p></li><li><p>while循环</p></li><li><p>do while循环</p><p>先执行再判断，最少会执行一次</p><p>最后  有一个;</p></li></ol></li><li><p>多重循环控制</p><ol><li>break   终止循环</li><li>continue  跳出本次循环</li></ol></li><li><p>跳转控制语句 - return</p><p>跳出所在的方法</p></li></ol><h2 id="数组、排序、查找"><a href="#数组、排序、查找" class="headerlink" title="数组、排序、查找"></a>数组、排序、查找</h2><p>数组定义方式</p><ol><li><p>动态分配   </p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">数据类型 数组名[] = <span class="keyword">new</span> 数组类型[大小];</span><br></pre></td></tr></table></figure></li><li><p>先声明再分配  </p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">数据类型 数组名[];</span><br><span class="line">数组名 = <span class="keyword">new</span> 数组类型[大小];</span><br></pre></td></tr></table></figure></li><li><p>数组名.length   表示数组的大小/长度</p></li><li><p>数据类型 数组名[]; 也可以 数据类型[] 数组名;</p></li></ol><p>注意事项</p><ol><li><p>数组是多个<strong>相同类型</strong>数据的组合，实现对这些数据的统一管理</p></li><li><p>数组中的元素可以是任何数据类型，包括基本类型和引用类型，但是不能混用。</p></li><li><p>数组创建后，如果没有赋值，有默认值</p><p>| 类型    | 默认值 |<br>| ———- | ——— |<br>| byte    | 0      |<br>| short   | 0      |<br>| int     | 0      |<br>| long    | 0      |<br>| float   | 0.0    |<br>| double  | 0.0    |<br>| char    | \u0000 |<br>| boolean | false  |<br>| String  | null   |</p></li><li><p>使用数组的步骤</p><ol><li><p>声明数组并开辟空间 </p></li><li><p>给数组各个元素赋值 </p></li><li>使用数组</li></ol></li><li><p>数组的<strong>下标是从</strong> <strong>0</strong> <strong>开始的</strong>。</p></li><li><p>数组下标必须在指定范围内使用，否则报：下标越界异常，</p></li><li><p>数组属引用类型，数组型数据是对象(object)</p></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>；</span><br><span class="line"><span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> a；</span><br><span class="line">a和b是单独的空间，互不影响     基本数据类型</span><br><span class="line"><span class="type">int</span>[] a = [<span class="number">1</span>,<span class="number">2</span>];</span><br><span class="line"><span class="type">int</span>[] b = a;</span><br><span class="line">a和b是引用同一个地址，相互影响    引用数据类型</span><br></pre></td></tr></table></figure><h3 id="Arrays类"><a href="#Arrays类" class="headerlink" title="Arrays类"></a>Arrays类</h3><h4 id="一-概述"><a href="#一-概述" class="headerlink" title="一.概述"></a>一.概述</h4><p><code>java.util.Arrays</code>：数组工具类</p><ol><li>包：<code>java.util</code>，使用需要导包</li><li>所有方法都是 <strong>static 静态方法</strong>，直接 <code>Arrays.方法名()</code> 调用</li><li>构造方法私有，不能 new Arrays 对象</li><li>作用：提供数组打印、排序、查找、复制、填充、比较等工具方法</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br></pre></td></tr></table></figure><h4 id="二、常用核心方法"><a href="#二、常用核心方法" class="headerlink" title="二、常用核心方法"></a>二、常用核心方法</h4><ol><li>toString () 打印数组</li></ol><p>普通数组直接打印输出地址，<strong>Arrays.toString 打印数组内容</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = &#123;<span class="number">1</span>,<span class="number">3</span>,<span class="number">2</span>&#125;;</span><br><span class="line">System.out.println(Arrays.toString(arr));</span><br><span class="line"><span class="comment">//输出：[1, 3, 2]</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：只支持<strong>一维数组</strong></p><p>多维数组使用：<code>deepToString()</code></p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[][] arr2 = &#123;&#123;<span class="number">1</span>,<span class="number">2</span>&#125;,&#123;<span class="number">3</span>,<span class="number">4</span>&#125;&#125;;</span><br><span class="line">System.out.println(Arrays.deepToString(arr2));</span><br></pre></td></tr></table></figure><ol><li>sort () 数组排序</li></ol><p>① 基本类型数组：<strong>升序排序（快速排序）</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = &#123;<span class="number">5</span>,<span class="number">2</span>,<span class="number">9</span>,<span class="number">1</span>&#125;;</span><br><span class="line">Arrays.sort(arr);</span><br><span class="line">System.out.println(Arrays.toString(arr)); <span class="comment">// [1, 2, 5, 9]</span></span><br></pre></td></tr></table></figure><p>② 对象数组 / 自定义排序（引用类型）</p><p><code>Arrays.sort(数组, Comparator)</code> 支持自定义顺序（<strong>只能用于包装类型数组，不能用 int []，要用 Integer []</strong>）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Integer[] arr = &#123;<span class="number">5</span>,<span class="number">2</span>,<span class="number">9</span>,<span class="number">1</span>&#125;;</span><br><span class="line"><span class="comment">//降序</span></span><br><span class="line">Arrays.sort(arr, (a,b)-&gt;b-a);</span><br></pre></td></tr></table></figure><blockquote><p>重点：基本类型 sort 不支持直接传入比较器！</p><p>拓展：可以指定区间排序 <code>Arrays.sort(arr,from,to)</code> 左闭右开</p></blockquote><ol><li>binarySearch () 二分查找</li></ol><p><strong>前提条件：数组必须先升序排序！否则结果不可预测</strong></p><ul><li>找到：返回元素索引</li><li>找不到：返回 <code>-(插入点)-1</code></li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">5</span>,<span class="number">9</span>&#125;;</span><br><span class="line"><span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> Arrays.binarySearch(arr,<span class="number">5</span>); <span class="comment">//返回2</span></span><br><span class="line"><span class="type">int</span> <span class="variable">index2</span> <span class="operator">=</span> Arrays.binarySearch(arr,<span class="number">3</span>); <span class="comment">//找不到，返回负数</span></span><br></pre></td></tr></table></figure><ol><li>copyOf () /copyOfRange () 数组复制</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">copyOf (数组，新长度)</span><br></pre></td></tr></table></figure><p>从头复制，可扩容 / 缩容</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] old = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>&#125;;</span><br><span class="line"><span class="type">int</span>[] newArr = Arrays.copyOf(old,<span class="number">5</span>);</span><br><span class="line"><span class="comment">// [1,2,3,0,0]</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">copyOfRange (数组，起始索引，结束索引)</span><br></pre></td></tr></table></figure><p>区间复制，<strong>左闭右开 [start,end)</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = &#123;<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>,<span class="number">40</span>&#125;;</span><br><span class="line"><span class="type">int</span>[] sub = Arrays.copyOfRange(arr,<span class="number">1</span>,<span class="number">3</span>);</span><br><span class="line"><span class="comment">// [20,30]</span></span><br></pre></td></tr></table></figure><p>区别：数组自身 clone () 和 Arrays.copyOf 效果类似；copyOf 底层调用 System.arraycopy。</p><ol><li>fill () 数组填充</li></ol><p>给数组全部 / 指定区间统一赋值</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">5</span>];</span><br><span class="line">Arrays.fill(arr,<span class="number">8</span>); <span class="comment">//全部填充8</span></span><br><span class="line"><span class="comment">// [8,8,8,8,8]</span></span><br><span class="line"></span><br><span class="line">Arrays.fill(arr,<span class="number">1</span>,<span class="number">3</span>,<span class="number">6</span>); <span class="comment">//下标1~2填充6</span></span><br></pre></td></tr></table></figure><ol><li>equals () 判断两个数组内容是否相同</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] a = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>&#125;;</span><br><span class="line"><span class="type">int</span>[] b = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>&#125;;</span><br><span class="line">System.out.println(Arrays.equals(a,b)); <span class="comment">//true</span></span><br></pre></td></tr></table></figure><p>误区：</p><p><code>a==b</code> 比较数组地址；</p><p><code>Arrays.equals()</code> 比较数组中每个元素的值。</p><p>多维数组比较使用：<code>deepEquals()</code></p><h4 id="三、一维数组-vs-多维数组配套方法汇总"><a href="#三、一维数组-vs-多维数组配套方法汇总" class="headerlink" title="三、一维数组 vs 多维数组配套方法汇总"></a>三、一维数组 vs 多维数组配套方法汇总</h4><div class="table-container"><table><thead><tr><th style="text-align:center">功能</th><th style="text-align:center">一维数组</th><th style="text-align:center">多维数组</th></tr></thead><tbody><tr><td style="text-align:center">打印内容</td><td style="text-align:center">toString()</td><td style="text-align:center">deepToString()</td></tr><tr><td style="text-align:center">比较内容</td><td style="text-align:center">equals()</td><td style="text-align:center">deepEquals()</td></tr></tbody></table></div><h4 id="四、高频易错考点"><a href="#四、高频易错考点" class="headerlink" title="四、高频易错考点"></a>四、高频易错考点</h4><ol><li><code>binarySearch</code> 二分查找<strong>必须先排序</strong>，无序数组结果错误</li><li>Arrays.sort 基本类型只能升序；想要降序必须使用包装数组 + Comparator</li><li>copyOf 产生<strong>新数组</strong>，原数组不会改变</li><li><code>Arrays.toString</code> 不能打印多维数组，只会输出地址，要用 deepToString</li><li>数组是引用类型，<code>equals()</code> 默认比较地址，<strong>一定要用 Arrays.equals 比较元素</strong></li></ol><h5 id="五、拓展：数组转集合（asList）"><a href="#五、拓展：数组转集合（asList）" class="headerlink" title="五、拓展：数组转集合（asList）"></a>五、拓展：数组转集合（asList）</h5><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Integer[] nums = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>&#125;;</span><br><span class="line">List&lt;Integer&gt; list = Arrays.asList(nums);</span><br></pre></td></tr></table></figure><p>重要坑：</p><ol><li>asList 接收<strong>引用类型数组</strong>；不能直接传基本类型数组<code>int[]</code></li><li>返回的 List 是 Arrays 内部静态类，<strong>固定大小！不能 add/remove</strong>，会抛出异常</li><li>集合和原数组<strong>互相引用</strong>，修改数组元素，集合同步变化</li></ol><h2 id="常用类"><a href="#常用类" class="headerlink" title="常用类"></a>常用类</h2><h3 id="包装类"><a href="#包装类" class="headerlink" title="包装类"></a>包装类</h3><p>1) 针对八种基本数据类型相应的引用类型—包装类<br>2) 有了类的特点，就可以调用类中的方法</p><p>java 有 <strong>8 种基本数据类型</strong>，不是对象；</p><p><strong>包装类</strong>：把基本类型包装成【对象】，位于<code>java.lang</code>包</p><div class="table-container"><table><thead><tr><th style="text-align:center">基本类型</th><th style="text-align:center">对应包装类</th></tr></thead><tbody><tr><td style="text-align:center">byte</td><td style="text-align:center">Byte</td></tr><tr><td style="text-align:center">short</td><td style="text-align:center">Short</td></tr><tr><td style="text-align:center">int</td><td style="text-align:center">Integer</td></tr><tr><td style="text-align:center">long</td><td style="text-align:center">Long</td></tr><tr><td style="text-align:center">float</td><td style="text-align:center">Float</td></tr><tr><td style="text-align:center">double</td><td style="text-align:center">Double</td></tr><tr><td style="text-align:center">char</td><td style="text-align:center">Character</td></tr><tr><td style="text-align:center">boolean</td><td style="text-align:center">Boolean</td></tr></tbody></table></div><blockquote><p>记忆：除了 <code>int→Integer</code>、<code>char→Character</code>，其余基本类型<strong>首字母大写</strong>就是包装类。</p></blockquote><h4 id="装箱-amp-拆箱"><a href="#装箱-amp-拆箱" class="headerlink" title="装箱 &amp; 拆箱"></a>装箱 &amp; 拆箱</h4><ol><li>手动装箱 / 拆箱（JDK5 之前写法）</li></ol><p><strong>装箱</strong>：基本类型 → 包装对象</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"><span class="type">Integer</span> <span class="variable">num</span> <span class="operator">=</span> Integer.valueOf(a); <span class="comment">//装箱</span></span><br></pre></td></tr></table></figure><p><strong>拆箱</strong>：包装对象 → 基本类型</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">num</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(<span class="number">10</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> num.intValue(); <span class="comment">//拆箱</span></span><br></pre></td></tr></table></figure><ol><li>自动装箱、自动拆箱（JDK5 + 新特性，编译器自动转换）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//自动装箱</span></span><br><span class="line"><span class="type">Integer</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">10</span>; <span class="comment">//等价 Integer i = Integer.valueOf(10);</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="variable">n</span> <span class="operator">=</span> i; <span class="comment">//等价 int n = i.intValue();</span></span><br></pre></td></tr></table></figure><p>底层本质：自动装箱底层调用<code>valueOf()</code>；自动拆箱调用<code>xxxValue()</code>。</p><h5 id="Integer-缓存机制"><a href="#Integer-缓存机制" class="headerlink" title="Integer 缓存机制"></a>Integer 缓存机制</h5><p><code>Integer.valueOf(int i)</code> 存在缓存数组 <code>cache[]</code></p><ul><li>默认缓存范围：<strong>-128 ~ 127</strong></li><li>范围内数字：直接复用缓存对象，<code>==</code>结果 true</li><li>超出范围：新建<code>Integer</code>对象，<code>==</code>结果 false</li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line"><span class="type">Integer</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">System.out.println(a == b); <span class="comment">//true，在缓存区间</span></span><br><span class="line"></span><br><span class="line"><span class="type">Integer</span> <span class="variable">c</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line"><span class="type">Integer</span> <span class="variable">d</span> <span class="operator">=</span> <span class="number">200</span>;</span><br><span class="line">System.out.println(c == d); <span class="comment">//false，超出缓存，两个不同对象</span></span><br></pre></td></tr></table></figure><blockquote><p>重要提醒：</p><p><code>==</code> 比较：</p><p>两边如果是包装对象 → <strong>比较地址</strong></p><p>一边包装、一边基本类型 → 触发自动拆箱，比较数值</p><p>开发比较数值一律使用 <code>.equals()</code>，不要直接<code>==</code></p></blockquote><p>拓展缓存范围</p><p>只有 <code>Byte、Short、Integer、Long</code> 缓存区间固定<code>[-128,127]</code></p><p><code>Character</code>缓存 <code>0~127</code></p><p><code>Float、Double</code><strong>没有缓存</strong>！</p><h4 id="字符串-和-基本类型互相转换"><a href="#字符串-和-基本类型互相转换" class="headerlink" title="字符串 和 基本类型互相转换"></a>字符串 和 基本类型互相转换</h4><ol><li>基本类型 → String</li></ol><p>方式 1：<code>变量 + &quot;&quot;</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">99</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> num + <span class="string">&quot;&quot;</span>;</span><br></pre></td></tr></table></figure><p>方式 2：<code>String.valueOf()</code>（推荐）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> String.valueOf(num);</span><br></pre></td></tr></table></figure><ol><li>String → 基本类型</li></ol><p>使用包装类的静态方法 <code>parseXxx()</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;123&quot;</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> Integer.parseInt(s);</span><br><span class="line"><span class="type">double</span> <span class="variable">d</span> <span class="operator">=</span> Double.parseDouble(s);</span><br></pre></td></tr></table></figure><p>注意：字符串格式不对，抛出 <code>NumberFormatException</code> 数字格式异常。</p><h4 id="包装类与基本类型区别汇总"><a href="#包装类与基本类型区别汇总" class="headerlink" title="包装类与基本类型区别汇总"></a>包装类与基本类型区别汇总</h4><ol><li>默认值不同</li></ol><ul><li>基本类型：有默认值（int=0，boolean=false）</li><li>包装类：默认值 <code>null</code></li></ul><blockquote><p>坑：包装类变量为 null 时，执行自动拆箱 → <strong>NullPointerException 空指针异常</strong></p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">i</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> i; <span class="comment">//运行报错！空指针</span></span><br></pre></td></tr></table></figure><ol><li>存储位置</li></ol><ul><li>基本类型：局部变量存在<strong>栈</strong></li><li>包装类对象：对象本体在<strong>堆</strong></li></ul><ol><li>使用场景</li></ol><ul><li>集合 <code>List/Map</code> <strong>不能存放基本类型，只能存包装类</strong></li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;Integer&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(); <span class="comment">//正确</span></span><br><span class="line">List&lt;<span class="type">int</span>&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(); <span class="comment">//编译报错</span></span><br></pre></td></tr></table></figure><h3 id="String-类"><a href="#String-类" class="headerlink" title="String 类"></a>String 类</h3><p>一、String 基本介绍</p><ol><li><code>java.lang.String</code>，<strong>代表字符串</strong>。</li><li><strong>字符串本质：字符序列</strong>，底层：<ul><li>JDK8：<code>private final char[] value</code></li><li>JDK9+：<code>private final byte[] value</code>（优化存储）</li></ul></li><li><strong>重点：String 对象不可变（immutable）</strong><ul><li><code>value</code> 数组被 <code>final</code> 修饰，数组引用不能指向新数组</li><li>一旦字符串创建，内容<strong>无法修改</strong></li></ul></li></ol><blockquote><p>误区：</p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;abc&quot;</span>;</span><br><span class="line">s = <span class="string">&quot;def&quot;</span>;</span><br></pre></td></tr></table></figure><p>不是修改原来对象！只是让<code>s</code>引用指向<strong>新字符串对象</strong>，旧对象等待 GC 回收。</p><p>二、字符串创建两种方式</p><p>方式 1：直接赋值（字面量）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">str1</span> <span class="operator">=</span> <span class="string">&quot;Java&quot;</span>;</span><br></pre></td></tr></table></figure><p>先去<strong>字符串常量池（堆内存）</strong>查找 “Java”</p><ul><li>找到：直接复用常量池中的对象</li><li>没找到：在常量池中创建，引用指向常量池对象</li></ul><p>方式 2：new String ()</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">str2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;Java&quot;</span>);</span><br></pre></td></tr></table></figure><ol><li>先检查常量池有没有<code>&quot;Java&quot;</code>，没有则放入常量池；</li><li><strong>在堆中创建新 String 对象</strong>，引用指向堆对象。</li></ol><p>经典面试对比</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="string">&quot;abc&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="string">&quot;abc&quot;</span>;</span><br><span class="line">System.out.println(s1 == s2); <span class="comment">// true  都指向常量池</span></span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">s3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;abc&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">s4</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;abc&quot;</span>);</span><br><span class="line">System.out.println(s3 == s4); <span class="comment">// false 堆中两个不同对象</span></span><br></pre></td></tr></table></figure><p>三、字符串拼接规则</p><p>规则 1：编译期常量优化</p><p><strong>两个字面量直接相加，编译器自动合并</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="string">&quot;a&quot;</span>+<span class="string">&quot;b&quot;</span>+<span class="string">&quot;c&quot;</span>;</span><br><span class="line"><span class="comment">// 编译器优化等价于 String s1 = &quot;abc&quot;;</span></span><br></pre></td></tr></table></figure><p>规则 2：存在变量参与拼接 → 在堆创建新对象</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">a</span> <span class="operator">=</span> <span class="string">&quot;a&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> a + <span class="string">&quot;b&quot;</span>;</span><br><span class="line"><span class="comment">//底层：new StringBuilder().append(a).append(&quot;b&quot;).toString()</span></span><br><span class="line"><span class="comment">//toString()底层 new String()，对象在堆，不进入常量池</span></span><br></pre></td></tr></table></figure><p>intern () 方法</p><p><code>str.intern()</code>：主动把字符串放入常量池</p><blockquote><p>如果常量池已经存在相同内容，返回常量池引用；不存在则将对象放入常量池并返回池内引用。</p></blockquote><p>四、== 和 equals ()</p><ol><li><p><code>==</code></p><ul><li>基本类型：比较数值</li><li><strong>String 引用：比较对象地址</strong></li></ul></li><li><p><code>equals()</code></p><ul><li><p>String 重写了 Object 的 equals，<strong>比较字符串内容</strong></p><p>开发规范：判断字符串内容相等一律使用 <code>equals()</code></p></li></ul></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">System.out.println(s1 == s2);      <span class="comment">// false</span></span><br><span class="line">System.out.println(s1.equals(s2)); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>五、常用 API 方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;HelloJava&quot;</span>;</span><br><span class="line">str.length();                <span class="comment">// 获取字符长度</span></span><br><span class="line">str.charAt(索引);            <span class="comment">// 获取指定位置字符</span></span><br><span class="line">str.indexOf(<span class="string">&quot;Java&quot;</span>);         <span class="comment">// 查找子串起始索引，找不到返回-1</span></span><br><span class="line">str.substring(begin,end);    <span class="comment">// 截取字符串 [左闭右开)</span></span><br><span class="line">str.trim();                  <span class="comment">// 去除首尾空格(JDK11:strip支持全角空格)</span></span><br><span class="line">str.toUpperCase();           <span class="comment">// 转大写</span></span><br><span class="line">str.toLowerCase();           <span class="comment">// 转小写</span></span><br><span class="line">str.contains(<span class="string">&quot;He&quot;</span>);          <span class="comment">// 是否包含子串</span></span><br><span class="line">str.startsWith(<span class="string">&quot;He&quot;</span>);        <span class="comment">// 是否以指定开头</span></span><br><span class="line">str.endsWith(<span class="string">&quot;va&quot;</span>);          <span class="comment">// 是否以指定结尾</span></span><br><span class="line">str.split(<span class="string">&quot;,&quot;</span>);              <span class="comment">// 按照分隔符切割 → String数组</span></span><br><span class="line">str.replace(<span class="string">&quot;a&quot;</span>,<span class="string">&quot;b&quot;</span>);        <span class="comment">// 字符/字符串替换</span></span><br></pre></td></tr></table></figure><p>六、不可变性带来的优缺点</p><p>优点：</p><ol><li>字符串常量池实现复用，节省内存</li><li>哈希值只需要计算一次，适合作为 HashMap 的 key</li><li>线程安全，多线程并发访问无问题</li></ol><p>缺点：</p><p>频繁字符串拼接，不断产生新对象，产生大量垃圾。</p><p>大量拼接解决方案：<strong>StringBuilder / StringBuffer</strong></p><h3 id="Stringbuffer类"><a href="#Stringbuffer类" class="headerlink" title="Stringbuffer类"></a>Stringbuffer类</h3><p>一、基础概念</p><p><code>java.lang.StringBuffer</code>，<strong>可变字符序列</strong></p><p>底层：<code>char[]</code>（JDK9 之后<code>byte[]</code>），数组没有被<code>final</code>修饰，可以<strong>直接扩容修改内容，不会频繁创建新对象</strong>。</p><p>对比：</p><ul><li><code>String</code>：不可变，每次拼接产生新对象，大量拼接效率极低</li><li><code>StringBuffer</code>：可变容器，原地修改字符数组</li></ul><p>二、构造方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 空参，默认容量 16</span></span><br><span class="line"><span class="type">StringBuffer</span> <span class="variable">sb1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 指定初始容量</span></span><br><span class="line"><span class="type">StringBuffer</span> <span class="variable">sb2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>(<span class="number">50</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据字符串创建，容量 = 字符串长度 + 16</span></span><br><span class="line"><span class="type">StringBuffer</span> <span class="variable">sb3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>(<span class="string">&quot;hello&quot;</span>);</span><br></pre></td></tr></table></figure><blockquote><p>默认容量 16：预留空间，减少频繁扩容。</p></blockquote><p>三、核心常用方法（全部支持<strong>链式调用</strong>）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuffer</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>();</span><br><span class="line"></span><br><span class="line">sb.append(<span class="string">&quot;java&quot;</span>);       <span class="comment">// 末尾追加（最常用）</span></span><br><span class="line">sb.insert(<span class="number">1</span>,<span class="string">&quot;A&quot;</span>);        <span class="comment">// 指定索引位置插入</span></span><br><span class="line">sb.delete(<span class="number">0</span>,<span class="number">2</span>);          <span class="comment">// 删除 [左闭右开区间]</span></span><br><span class="line">sb.replace(<span class="number">0</span>,<span class="number">1</span>,<span class="string">&quot;Hi&quot;</span>);    <span class="comment">// 区间替换</span></span><br><span class="line">sb.reverse();            <span class="comment">// 字符串反转</span></span><br><span class="line">sb.setCharAt(<span class="number">0</span>,<span class="string">&#x27;X&#x27;</span>);     <span class="comment">// 修改指定位置字符</span></span><br><span class="line"></span><br><span class="line">sb.length();             <span class="comment">// 当前实际字符长度</span></span><br><span class="line">sb.capacity();           <span class="comment">// 获取底层数组总容量</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 转换成String</span></span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> sb.toString();</span><br></pre></td></tr></table></figure><p>四、扩容机制（重点）</p><ol><li>当 <code>append</code> 后字符超出容量，触发扩容</li><li><strong>扩容公式：新容量 = 原有容量 * 2 + 2</strong></li><li>如果扩容后依然不够，则直接使用<strong>所需最小容量</strong></li><li>底层：新建更大数组，复制旧数组内容</li></ol><p>示例：初始容量 16 → 扩容后 <code>16*2+2=34</code></p><p>五、StringBuffer 和 String 的互相转换</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// String → StringBuffer</span></span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;hello&quot;</span>;</span><br><span class="line"><span class="type">StringBuffer</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>(str);</span><br><span class="line"></span><br><span class="line"><span class="comment">// StringBuffer → String</span></span><br><span class="line"><span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> sb.toString();</span><br></pre></td></tr></table></figure><p>六、高频易错知识点</p><ol><li><strong>append 返回的是自身 this 对象</strong>，所以支持链式调用</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">sb.append(<span class="string">&quot;a&quot;</span>).append(<span class="string">&quot;b&quot;</span>).append(<span class="string">&quot;c&quot;</span>);</span><br></pre></td></tr></table></figure><ol><li><code>toString()</code> 会新建 String 对象，不会复用底层数组</li><li><strong>不要频繁 new StringBuffer</strong>，预估长度尽量指定初始容量，避免多次扩容</li><li>线程不安全场景误用 StringBuffer 不能带来收益，反而降低速度；单线程优先<code>StringBuilder</code></li></ol><p>七、经典误区</p><p>错误理解：</p><blockquote><p>“StringBuffer 比 String 快”</p><p>准确说法：</p><p>少量拼接差距不大；<strong>大量循环拼接时，String 不会产生大量临时对象，性能差距巨大</strong>。</p></blockquote><h3 id="StringBuilder-类"><a href="#StringBuilder-类" class="headerlink" title="StringBuilder 类"></a>StringBuilder 类</h3><p>一、基础介绍</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">java.lang.StringBuilder</span><br></pre></td></tr></table></figure><p><strong>可变字符序列，用于字符串拼接，JDK1.5 推出</strong></p><p>底层：字符数组（JDK8 char [] / JDK9 byte []），数组无 final 修饰，可以直接修改，不会频繁创建新字符串对象。</p><p>和 StringBuffer API 几乎完全一致，<strong>唯一区别：没有 synchronized 同步锁</strong>。</p><p>二、构造方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1.无参构造：初始容量 16</span></span><br><span class="line"><span class="type">StringBuilder</span> <span class="variable">sb1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2.指定初始容量</span></span><br><span class="line"><span class="type">StringBuilder</span> <span class="variable">sb2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">30</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3.传入字符串，容量 = 字符串长度 + 16</span></span><br><span class="line"><span class="type">StringBuilder</span> <span class="variable">sb3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="string">&quot;hello&quot;</span>);</span><br></pre></td></tr></table></figure><p>三、核心常用方法（支持链式调用）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"></span><br><span class="line">sb.append(<span class="string">&quot;Java&quot;</span>);          <span class="comment">// 末尾追加（最常用）</span></span><br><span class="line">sb.insert(<span class="number">1</span>,<span class="string">&quot;_&quot;</span>);           <span class="comment">// 指定位置插入</span></span><br><span class="line">sb.delete(<span class="number">0</span>,<span class="number">2</span>);             <span class="comment">// 删除 [start,end) 左闭右开</span></span><br><span class="line">sb.replace(<span class="number">0</span>,<span class="number">1</span>,<span class="string">&quot;Hi&quot;</span>);       <span class="comment">// 区间替换</span></span><br><span class="line">sb.reverse();               <span class="comment">// 字符反转</span></span><br><span class="line">sb.setCharAt(<span class="number">0</span>,<span class="string">&#x27;A&#x27;</span>);        <span class="comment">// 修改指定下标字符</span></span><br><span class="line"></span><br><span class="line">sb.length();    <span class="comment">// 实际字符个数</span></span><br><span class="line">sb.capacity();  <span class="comment">// 底层数组总容量</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 转为String（生成新字符串对象）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> sb.toString();</span><br></pre></td></tr></table></figure><blockquote><p>原理：所有修改操作<strong>直接操作底层数组</strong>，不产生新对象。</p></blockquote><p>四、扩容机制</p><p>触发时机：追加内容后，字符总数超过当前容量</p><p>默认扩容公式：</p><p><strong>新容量 = 原有容量 * 2 + 2</strong></p><p>如果扩容后的容量依旧不够存放，直接采用<strong>所需最小容量</strong>。</p><p>示例：</p><p>初始容量 16 → 扩容：16×2+2 = 34</p><p>优化建议：如果你提前预估字符串长度，创建时指定容量，避免多次扩容、数组复制，提升性能。</p><p>五、String / StringBuilder / StringBuffer 三者终极对比</p><div class="table-container"><table><thead><tr><th style="text-align:center">类</th><th style="text-align:center">可变性</th><th style="text-align:center">线程安全</th><th style="text-align:center">锁</th><th style="text-align:center">性能</th><th style="text-align:center">适用场景</th></tr></thead><tbody><tr><td style="text-align:center">String</td><td style="text-align:center">不可变</td><td style="text-align:center">线程安全</td><td style="text-align:center">无</td><td style="text-align:center">低</td><td style="text-align:center">少量拼接、字符串常量</td></tr><tr><td style="text-align:center">StringBuffer</td><td style="text-align:center">可变</td><td style="text-align:center">线程安全</td><td style="text-align:center">方法加<code>synchronized</code></td><td style="text-align:center">较低</td><td style="text-align:center"><strong>多线程并发拼接</strong></td></tr><tr><td style="text-align:center">StringBuilder</td><td style="text-align:center">可变</td><td style="text-align:center">线程不安全</td><td style="text-align:center">无锁</td><td style="text-align:center"><strong>最高</strong></td><td style="text-align:center"><strong>单线程拼接（开发首选）</strong></td></tr></tbody></table></div><blockquote><p>开发准则：</p><p>绝大多数业务代码（单线程） → <strong>优先 StringBuilder</strong></p></blockquote><p>六、相互转换</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// String → StringBuilder</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;demo&quot;</span>;</span><br><span class="line"><span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(s);</span><br><span class="line"></span><br><span class="line"><span class="comment">// StringBuilder → String</span></span><br><span class="line"><span class="type">String</span> <span class="variable">res</span> <span class="operator">=</span> sb.toString();</span><br></pre></td></tr></table></figure><p>七、高频考点 &amp; 易错点</p><ol><li><p><strong>线程不安全典型场景</strong></p><p>多个线程同时操作同一个 StringBuilder 对象，会出现数据错乱、数组越界异常。</p></li></ol><blockquote><p>解决方案：多线程改用 StringBuffer，或者每个线程新建独立 StringBuilder。</p></blockquote><ol><li>循环拼接大坑</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 性能极差！每次循环产生大量中间String对象</span></span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;<span class="number">10000</span>;i++)&#123;</span><br><span class="line">    str += i;</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="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>;i&lt;<span class="number">10000</span>;i++)&#123;</span><br><span class="line">    sb.append(i);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> sb.toString();</span><br></pre></td></tr></table></figure><ol><li>编译器优化误区</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;a&quot;</span>+<span class="string">&quot;b&quot;</span>+<span class="string">&quot;c&quot;</span>;</span><br><span class="line"><span class="comment">// 编译期优化为 String s = &quot;abc&quot;;</span></span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">a</span> <span class="operator">=</span> <span class="string">&quot;a&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> a + <span class="string">&quot;b&quot;</span>;</span><br><span class="line"><span class="comment">// 底层自动 new StringBuilder 拼接</span></span><br><span class="line"><span class="comment">// 但是！只限于**单行简单拼接，循环内不会优化！**</span></span><br></pre></td></tr></table></figure><h3 id="Math-类"><a href="#Math-类" class="headerlink" title="Math 类"></a>Math 类</h3><p>一、基础介绍</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">java.lang.Math</span><br></pre></td></tr></table></figure><ol><li>提供大量<strong>数学计算静态方法</strong>，全部是 <code>static</code>，<strong>不需要 new 对象，直接类名。方法调用</strong></li><li>构造方法被 <code>private</code> 修饰，无法实例化</li><li>包含两个常用静态常量</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Math.PI    <span class="comment">//圆周率 π ≈ 3.1415926535...</span></span><br><span class="line">Math.E     <span class="comment">//自然常数 e ≈ 2.71828...</span></span><br></pre></td></tr></table></figure><p>调用方式示例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="variable">num</span> <span class="operator">=</span> Math.abs(-<span class="number">10</span>);</span><br></pre></td></tr></table></figure><p>二、常用方法分类</p><ol><li>绝对值 abs ()</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Math.abs(-<span class="number">5</span>);        <span class="comment">//int 绝对值 → 5</span></span><br><span class="line">Math.abs(-<span class="number">3.14</span>);     <span class="comment">//double 绝对值 →3.14</span></span><br></pre></td></tr></table></figure><ol><li>取最值 max /min</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Math.max(<span class="number">10</span>,<span class="number">20</span>);     <span class="comment">//返回较大值 20</span></span><br><span class="line">Math.min(<span class="number">10</span>,<span class="number">20</span>);     <span class="comment">//返回较小值 10</span></span><br><span class="line"><span class="comment">//支持int long float double</span></span><br></pre></td></tr></table></figure><ol><li>四舍五入、向上取整、向下取整</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//1.四舍五入 → long</span></span><br><span class="line">Math.round(<span class="number">3.4</span>);   <span class="comment">//3</span></span><br><span class="line">Math.round(<span class="number">3.5</span>);   <span class="comment">//4</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//2.向上取整（天花板），返回double</span></span><br><span class="line">Math.ceil(<span class="number">3.1</span>);    <span class="comment">//4.0</span></span><br><span class="line">Math.ceil(-<span class="number">3.1</span>);   <span class="comment">//-3.0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//3.向下取整（地板），返回double</span></span><br><span class="line">Math.floor(<span class="number">3.9</span>);   <span class="comment">//3.0</span></span><br><span class="line">Math.floor(-<span class="number">3.1</span>);  <span class="comment">//-4.0</span></span><br></pre></td></tr></table></figure><p>极易踩坑：负数的 ceil、floor 不要凭直觉判断！</p><ol><li>幂运算、开平方</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Math.pow(<span class="number">2</span>,<span class="number">3</span>);    <span class="comment">//2^3 = 8.0  参数、返回值都是double</span></span><br><span class="line">Math.sqrt(<span class="number">16</span>);    <span class="comment">//平方根 √16 =4.0</span></span><br></pre></td></tr></table></figure><ol><li>随机数 random ()</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Math.random(); </span><br><span class="line"><span class="comment">// 返回 [0.0 , 1.0) 之间的随机小数，包含0.0，不包含1.0</span></span><br></pre></td></tr></table></figure><p>业务常用公式：获取 <code>[a,b]</code> 的随机整数</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 范围 [min,max]</span></span><br><span class="line"><span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> (<span class="type">int</span>)(Math.random() * (max - min + <span class="number">1</span>)) + min;</span><br><span class="line"></span><br><span class="line"><span class="comment">//示例：1~10随机整数</span></span><br><span class="line"><span class="type">int</span> <span class="variable">r</span> <span class="operator">=</span> (<span class="type">int</span>)(Math.random() * <span class="number">10</span>) + <span class="number">1</span>;</span><br></pre></td></tr></table></figure><blockquote><p>拓展：JDK 推荐也可以使用 <code>java.util.Random</code> 类，功能更强。</p></blockquote><ol><li>三角函数（参数是<strong>弧度</strong>，不是角度）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Math.sin(弧度);</span><br><span class="line">Math.cos(弧度);</span><br><span class="line">Math.tan(弧度);</span><br><span class="line">Math.toRadians(角度); <span class="comment">//角度 → 弧度</span></span><br><span class="line">Math.toDegrees(弧度); <span class="comment">//弧度 → 角度</span></span><br></pre></td></tr></table></figure><p>三、Math vs StrictMath（了解）</p><ul><li><p>Math：部分方法平台相关，底层可以使用 CPU 优化，计算结果微小误差</p></li><li><p>StrictMath：严格遵循浮点运算标准，所有平台计算结果完全一致；速度略慢。</p><p>日常开发几乎只用 Math。</p></li></ul><p>四、高频易错点</p><ol><li><code>round()</code> 返回 long；<code>ceil()</code> / <code>floor()</code> 返回 double</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">double</span> <span class="variable">d</span> <span class="operator">=</span> Math.ceil(<span class="number">2.2</span>); <span class="comment">//d=3.0 是小数类型，不是int</span></span><br></pre></td></tr></table></figure><ol><li>Math.random () 左闭右开 <code>[0.0,1.0)</code></li><li>pow 返回 double，即使整数次方，不能直接当 int 使用，需要强转</li><li>三角函数传入的是<strong>弧度</strong>，直接传入角度结果错误</li></ol><h3 id="Arrays-类"><a href="#Arrays-类" class="headerlink" title="Arrays 类"></a>Arrays 类</h3><p>一、概述</p><p><code>java.util.Arrays</code>：数组工具类</p><ol><li>包：<code>java.util</code>，使用需要导包</li><li>所有方法都是 <strong>static 静态方法</strong>，直接 <code>Arrays.方法名()</code> 调用</li><li>构造方法私有，不能 new Arrays 对象</li><li>作用：提供数组打印、排序、查找、复制、填充、比较等工具方法</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br></pre></td></tr></table></figure><p>二、常用核心方法</p><ol><li>toString () 打印数组</li></ol><p>普通数组直接打印输出地址，<strong>Arrays.toString 打印数组内容</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = &#123;<span class="number">1</span>,<span class="number">3</span>,<span class="number">2</span>&#125;;</span><br><span class="line">System.out.println(Arrays.toString(arr));</span><br><span class="line"><span class="comment">//输出：[1, 3, 2]</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：只支持<strong>一维数组</strong></p><p>多维数组使用：<code>deepToString()</code></p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[][] arr2 = &#123;&#123;<span class="number">1</span>,<span class="number">2</span>&#125;,&#123;<span class="number">3</span>,<span class="number">4</span>&#125;&#125;;</span><br><span class="line">System.out.println(Arrays.deepToString(arr2));</span><br></pre></td></tr></table></figure><ol><li>sort () 数组排序</li></ol><p>① 基本类型数组：<strong>升序排序（快速排序）</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = &#123;<span class="number">5</span>,<span class="number">2</span>,<span class="number">9</span>,<span class="number">1</span>&#125;;</span><br><span class="line">Arrays.sort(arr);</span><br><span class="line">System.out.println(Arrays.toString(arr)); <span class="comment">// [1, 2, 5, 9]</span></span><br></pre></td></tr></table></figure><p>② 对象数组 / 自定义排序（引用类型）</p><p><code>Arrays.sort(数组, Comparator)</code> 支持自定义顺序（<strong>只能用于包装类型数组，不能用 int []，要用 Integer []</strong>）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Integer[] arr = &#123;<span class="number">5</span>,<span class="number">2</span>,<span class="number">9</span>,<span class="number">1</span>&#125;;</span><br><span class="line"><span class="comment">//降序</span></span><br><span class="line">Arrays.sort(arr, (a,b)-&gt;b-a);</span><br></pre></td></tr></table></figure><blockquote><p>重点：基本类型 sort 不支持直接传入比较器！</p><p>拓展：可以指定区间排序 <code>Arrays.sort(arr,from,to)</code> 左闭右开</p></blockquote><ol><li>binarySearch () 二分查找</li></ol><p><strong>前提条件：数组必须先升序排序！否则结果不可预测</strong></p><ul><li>找到：返回元素索引</li><li>找不到：返回 <code>-(插入点)-1</code></li></ul><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">5</span>,<span class="number">9</span>&#125;;</span><br><span class="line"><span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> Arrays.binarySearch(arr,<span class="number">5</span>); <span class="comment">//返回2</span></span><br><span class="line"><span class="type">int</span> <span class="variable">index2</span> <span class="operator">=</span> Arrays.binarySearch(arr,<span class="number">3</span>); <span class="comment">//找不到，返回负数</span></span><br></pre></td></tr></table></figure><ol><li>copyOf () /copyOfRange () 数组复制</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">copyOf (数组，新长度)</span><br></pre></td></tr></table></figure><p>从头复制，可扩容 / 缩容</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] old = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>&#125;;</span><br><span class="line"><span class="type">int</span>[] newArr = Arrays.copyOf(old,<span class="number">5</span>);</span><br><span class="line"><span class="comment">// [1,2,3,0,0]</span></span><br></pre></td></tr></table></figure><p>copyOfRange (数组，起始索引，结束索引)</p><p>区间复制，<strong>左闭右开 [start,end)</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = &#123;<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>,<span class="number">40</span>&#125;;</span><br><span class="line"><span class="type">int</span>[] sub = Arrays.copyOfRange(arr,<span class="number">1</span>,<span class="number">3</span>);</span><br><span class="line"><span class="comment">// [20,30]</span></span><br></pre></td></tr></table></figure><p>区别：</p><p>数组自身 clone () 和 Arrays.copyOf 效果类似；copyOf 底层调用 System.arraycopy。</p><ol><li>fill () 数组填充</li></ol><p>给数组全部 / 指定区间统一赋值</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] arr = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">5</span>];</span><br><span class="line">Arrays.fill(arr,<span class="number">8</span>); <span class="comment">//全部填充8</span></span><br><span class="line"><span class="comment">// [8,8,8,8,8]</span></span><br><span class="line"></span><br><span class="line">Arrays.fill(arr,<span class="number">1</span>,<span class="number">3</span>,<span class="number">6</span>); <span class="comment">//下标1~2填充6</span></span><br></pre></td></tr></table></figure><ol><li>equals () 判断两个数组内容是否相同</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] a = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>&#125;;</span><br><span class="line"><span class="type">int</span>[] b = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>&#125;;</span><br><span class="line">System.out.println(Arrays.equals(a,b)); <span class="comment">//true</span></span><br></pre></td></tr></table></figure><p>误区：</p><p><code>a==b</code> 比较数组地址；</p><p><code>Arrays.equals()</code> 比较数组中每个元素的值。</p><p>多维数组比较使用：<code>deepEquals()</code></p><p>三、一维数组 vs 多维数组配套方法汇总</p><div class="table-container"><table><thead><tr><th style="text-align:center">功能</th><th style="text-align:center">一维数组</th><th style="text-align:center">多维数组</th></tr></thead><tbody><tr><td style="text-align:center">打印内容</td><td style="text-align:center">toString()</td><td style="text-align:center">deepToString()</td></tr><tr><td style="text-align:center">比较内容</td><td style="text-align:center">equals()</td><td style="text-align:center">deepEquals()</td></tr></tbody></table></div><p>四、高频易错考点</p><ol><li><code>binarySearch</code> 二分查找<strong>必须先排序</strong>，无序数组结果错误</li><li>Arrays.sort 基本类型只能升序；想要降序必须使用包装数组 + Comparator</li><li>copyOf 产生<strong>新数组</strong>，原数组不会改变</li><li><code>Arrays.toString</code> 不能打印多维数组，只会输出地址，要用 deepToString</li><li>数组是引用类型，<code>equals()</code> 默认比较地址，<strong>一定要用 Arrays.equals 比较元素</strong></li></ol><p>五、拓展：数组转集合（asList）大坑</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Integer[] nums = &#123;<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>&#125;;</span><br><span class="line">List&lt;Integer&gt; list = Arrays.asList(nums);</span><br></pre></td></tr></table></figure><p>重要坑：</p><ol><li>asList 接收<strong>引用类型数组</strong>；不能直接传基本类型数组<code>int[]</code></li><li>返回的 List 是 Arrays 内部静态类，<strong>固定大小！不能 add/remove</strong>，会抛出异常</li><li>集合和原数组<strong>互相引用</strong>，修改数组元素，集合同步变化</li></ol><h3 id="System-类"><a href="#System-类" class="headerlink" title="System 类"></a>System 类</h3><p>一、概述</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">java.lang.System</span><br></pre></td></tr></table></figure><ol><li><strong>全部成员都是静态</strong>，直接 <code>System.xxx</code> 调用，无需实例化</li><li>构造方法 <code>private</code>，禁止 <code>new System()</code></li><li>代表程序运行平台系统资源，提供标准输入输出、时间、数组复制、退出程序、环境变量等操作</li></ol><p>二、三大静态常量（流对象）</p><figure class="highlight java"><table><tr><td class="code"><pre><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">final</span> InputStream in;</span><br><span class="line"><span class="comment">// 标准输出流：控制台打印（常用 System.out.println()）</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> PrintStream out;</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">final</span> PrintStream err;</span><br></pre></td></tr></table></figure><p>区分 out 和 err：</p><ul><li>out：普通信息输出，缓冲区缓存，顺序不一定立刻打印</li><li>err：错误信息，立即输出，控制台一般红色</li></ul><p>三、核心常用方法</p><ol><li>arraycopy () 数组复制，底层非常重要</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">arraycopy</span><span class="params">(</span></span><br><span class="line"><span class="params">Object src,     //源数组</span></span><br><span class="line"><span class="params"><span class="type">int</span> srcPos,     //源数组起始索引</span></span><br><span class="line"><span class="params">Object dest,    //目标数组</span></span><br><span class="line"><span class="params"><span class="type">int</span> destPos,    //目标数组起始索引</span></span><br><span class="line"><span class="params"><span class="type">int</span> length      //复制元素个数</span></span><br><span class="line"><span class="params">)</span></span><br></pre></td></tr></table></figure><p>示例：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] a = &#123;<span class="number">10</span>,<span class="number">20</span>,<span class="number">30</span>,<span class="number">40</span>,<span class="number">50</span>&#125;;</span><br><span class="line"><span class="type">int</span>[] b = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">5</span>];</span><br><span class="line">System.arraycopy(a,<span class="number">1</span>,b,<span class="number">0</span>,<span class="number">3</span>);</span><br><span class="line"><span class="comment">// b = [20,30,40,0,0]</span></span><br></pre></td></tr></table></figure><p>特点：</p><ul><li><p><strong>native 本地方法，由 C 实现，速度极快</strong></p></li><li><p>Arrays.copyOf/copyOfRange 底层本质调用 <code>System.arraycopy</code></p><p>注意：自己复制自己、索引越界会抛出 <code>ArrayIndexOutOfBoundsException</code></p></li></ul><ol><li>获取时间</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 当前时间毫秒数：从【1970-01-01 00:00:00 UTC】到现在的毫秒</span></span><br><span class="line"><span class="type">long</span> <span class="variable">time1</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line"><span class="comment">// JDK1.8新增：纳秒！用于计算代码执行耗时，不代表系统日期</span></span><br><span class="line"><span class="type">long</span> <span class="variable">time2</span> <span class="operator">=</span> System.nanoTime();</span><br></pre></td></tr></table></figure><p>使用场景：计算程序运行耗时</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"><span class="comment">//执行代码...</span></span><br><span class="line"><span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">System.out.println(<span class="string">&quot;耗时：&quot;</span>+(end-start)+<span class="string">&quot;ms&quot;</span>);</span><br></pre></td></tr></table></figure><blockquote><p>区别：</p><p>currentTimeMillis：基于系统时钟，可以手动修改系统时间，数值会跳变</p><p>nanoTime：基于虚拟机计时器，适合计算时间间隔，不适合获取日期</p></blockquote><ol><li>exit (状态码) 终止 JVM</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.exit(<span class="number">0</span>);  <span class="comment">//正常退出</span></span><br><span class="line">System.exit(<span class="number">1</span>);  <span class="comment">//异常退出，非0代表程序异常终止</span></span><br></pre></td></tr></table></figure><p>重点考点：</p><p>执行 <code>System.exit()</code> 直接关闭虚拟机，<strong>finally 代码块不会执行！</strong></p><ol><li>gc () 建议垃圾回收</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.gc();</span><br></pre></td></tr></table></figure><p>作用：向 JVM<strong>建议</strong>执行垃圾回收；</p><p><strong>只是建议，不保证立刻执行 GC，什么时候回收由虚拟机决定。</strong></p><ol><li>获取环境变量、系统属性</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取单个系统属性</span></span><br><span class="line"><span class="type">String</span> <span class="variable">path</span> <span class="operator">=</span> System.getProperty(<span class="string">&quot;user.dir&quot;</span>); <span class="comment">//当前项目工作目录</span></span><br><span class="line"><span class="type">String</span> <span class="variable">os</span> <span class="operator">=</span> System.getProperty(<span class="string">&quot;os.name&quot;</span>);    <span class="comment">//操作系统名称</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//获取环境变量</span></span><br><span class="line"><span class="type">String</span> <span class="variable">javaHome</span> <span class="operator">=</span> System.getenv(<span class="string">&quot;JAVA_HOME&quot;</span>);</span><br></pre></td></tr></table></figure><p>常用属性：</p><p><code>user.name</code> 当前用户名</p><p><code>java.version</code> Java 版本</p><p>四、输入输出重定向（了解）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//把输出写入文件，不再打印控制台</span></span><br><span class="line">System.setOut(<span class="keyword">new</span> <span class="title class_">PrintStream</span>(<span class="string">&quot;log.txt&quot;</span>));</span><br></pre></td></tr></table></figure><p>五、易错知识点</p><ol><li><code>System.arraycopy</code> 是 native 方法，效率高于手动 for 循环复制数组</li><li><code>System.exit(0)</code> 停止 JVM，会跳过 finally 执行</li><li><code>System.gc()</code> 只是通知虚拟机，不能强制垃圾回收</li><li><code>currentTimeMillis()</code> 毫秒时间戳；<code>nanoTime()</code> 仅用于计算间隔</li><li><code>System.out</code> 和 <code>System.err</code> 是两个不同流，打印顺序无法保证</li></ol><p>六、拓展对比</p><p><code>Arrays.copyOf</code> → 底层调用 <code>System.arraycopy</code></p><p><code>System.arraycopy</code> 是底层原生方法，开发中很少直接手写，框架大量使用。</p><h3 id="BigInteger-和-BigDecimal类"><a href="#BigInteger-和-BigDecimal类" class="headerlink" title="BigInteger 和 BigDecimal类"></a>BigInteger 和 BigDecimal类</h3><p>一、背景引入</p><ol><li><strong>基本数据类型局限</strong><code>long</code>、<code>int</code>有取值上限；<code>float/double</code> 浮点运算存在精度丢失（二进制存储小数缺陷）。</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.out.println(<span class="number">0.1</span> + <span class="number">0.2</span>);</span><br><span class="line"><span class="comment">//输出 0.30000000000000004，不是0.3</span></span><br></pre></td></tr></table></figure><ol><li>解决方案</li></ol><ul><li><strong>BigInteger</strong>：超大整数（无上限）</li><li><strong>BigDecimal</strong>：高精度小数，金融、金额计算<strong>必须使用</strong>包：<code>java.math</code></li></ul><p>二、BigInteger（超大整数）</p><ol><li>创建对象</li></ol><p>不能直接使用 + - * / 运算符！只能调用方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//方式1：字符串构造【推荐】</span></span><br><span class="line"><span class="type">BigInteger</span> <span class="variable">num1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigInteger</span>(<span class="string">&quot;12345678901234567890&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//方式2：long转（数值不能超过long范围）</span></span><br><span class="line"><span class="type">BigInteger</span> <span class="variable">num2</span> <span class="operator">=</span> BigInteger.valueOf(<span class="number">100</span>);</span><br></pre></td></tr></table></figure><ol><li>常用运算方法</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">BigInteger</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigInteger</span>(<span class="string">&quot;20&quot;</span>);</span><br><span class="line"><span class="type">BigInteger</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigInteger</span>(<span class="string">&quot;7&quot;</span>);</span><br><span class="line"></span><br><span class="line">a.add(b);      <span class="comment">//加法 a+b</span></span><br><span class="line">a.subtract(b); <span class="comment">//减法 a-b</span></span><br><span class="line">a.multiply(b); <span class="comment">//乘法 a*b</span></span><br><span class="line">a.divide(b);   <span class="comment">//除法（整除，不能有余数，否则报错）</span></span><br><span class="line">a.remainder(b);<span class="comment">//取余数</span></span><br><span class="line">a.mod(b);      <span class="comment">//模运算</span></span><br><span class="line"></span><br><span class="line">a.abs();       <span class="comment">//绝对值</span></span><br><span class="line">a.max(b);      <span class="comment">//最大值</span></span><br><span class="line">a.min(b);      <span class="comment">//最小值</span></span><br></pre></td></tr></table></figure><blockquote><p>重点：所有运算<strong>返回新 BigInteger 对象</strong>，原对象不可变！</p></blockquote><ol><li>带余数除法（divideAndRemainder）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BigInteger[] res = a.divideAndRemainder(b);</span><br><span class="line">res[<span class="number">0</span>] 商，res[<span class="number">1</span>]余数</span><br></pre></td></tr></table></figure><p>三、BigDecimal（高精度小数 ）</p><ol><li>对象创建</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不推荐！double构造依然存在精度损失</span></span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">num</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="number">0.1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 推荐两种写法</span></span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">num1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.1&quot;</span>);    <span class="comment">//字符串构造</span></span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">num2</span> <span class="operator">=</span> BigDecimal.valueOf(<span class="number">0.1</span>);   <span class="comment">//valueOf底层自动转字符串</span></span><br></pre></td></tr></table></figure><ol><li>四则运算</li></ol><p>同样<strong>不可变，运算返回新对象</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">BigDecimal</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.1&quot;</span>);</span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.2&quot;</span>);</span><br><span class="line"></span><br><span class="line">a.add(b);        <span class="comment">//加</span></span><br><span class="line">a.subtract(b);   <span class="comment">//减</span></span><br><span class="line">a.multiply(b);   <span class="comment">//乘</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 除法极易报错！除不尽会抛出 ArithmeticException</span></span><br><span class="line">a.divide(b);</span><br></pre></td></tr></table></figure><ol><li>除法 &amp; 保留小数、舍入模式（核心）</li></ol><p>除法必须指定<strong>保留位数 + 舍入模式</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">BigDecimal</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;10&quot;</span>);</span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;3&quot;</span>);</span><br><span class="line"><span class="comment">//保留2位小数，四舍五入</span></span><br><span class="line">a.divide(b,<span class="number">2</span>,BigDecimal.ROUND_HALF_UP);</span><br></pre></td></tr></table></figure><p>常用舍入模式：</p><ol><li><code>ROUND_HALF_UP</code>：四舍五入（日常最常用）</li><li><code>ROUND_UP</code>：向上取整</li><li><code>ROUND_DOWN</code>：直接截断，舍弃后面小数</li><li><code>ROUND_HALF_EVEN</code>：银行家舍入（JDK 默认）</li></ol><ol><li>比较大小</li></ol><p>禁止使用 <code>==</code>（比较地址）</p><p>尽量不要用 equals ()！equals 会比较<strong>小数位数</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">BigDecimal</span> <span class="variable">d1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;2.0&quot;</span>);</span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">d2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;2.00&quot;</span>);</span><br><span class="line">System.out.println(d1.equals(d2)); <span class="comment">//false！位数不同</span></span><br></pre></td></tr></table></figure><p>正确方式：<code>compareTo()</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 返回值：</span></span><br><span class="line"><span class="comment">// 0 相等  &gt;0 当前数大  &lt;0 当前数小</span></span><br><span class="line"><span class="keyword">if</span>(d1.compareTo(d2) == <span class="number">0</span>)&#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;数值相等&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>小数点操作</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">bd.setScale(<span class="number">2</span>,BigDecimal.ROUND_HALF_UP); <span class="comment">//保留2位小数</span></span><br></pre></td></tr></table></figure><p>四、BigInteger vs BigDecimal 对比</p><div class="table-container"><table><thead><tr><th style="text-align:center">类</th><th style="text-align:center">适用场景</th><th style="text-align:center">存储内容</th></tr></thead><tbody><tr><td style="text-align:center">BigInteger</td><td style="text-align:center">超大整数运算</td><td style="text-align:center">整数，无小数</td></tr><tr><td style="text-align:center">BigDecimal</td><td style="text-align:center">金融金额、高精度小数</td><td style="text-align:center">支持小数，解决浮点精度丢失</td></tr></tbody></table></div><p>共性：</p><ol><li><strong>不可变类</strong>，每次计算产生新对象</li><li>不支持运算符重载，必须调用方法</li><li>在<code>java.math</code>包</li></ol><p>五、高频易错知识点</p><p>double 直接传入 BigDecimal 构造器会精度丢失，优先字符串构造</p><ol><li>BigDecimal 除法如果无法整除，<strong>必须指定精度和舍入模式，否则抛异常</strong></li><li>equals 会比较小数位数；数值比较用<code>compareTo</code></li><li>对象不可变，运算结果需要接收返回值</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//错误写法</span></span><br><span class="line">bd.add(xxx); <span class="comment">//结果丢失，原对象不变</span></span><br><span class="line"><span class="comment">//正确</span></span><br><span class="line">bd = bd.add(xxx);</span><br></pre></td></tr></table></figure><ol><li>double/float 不要用于金额计算，强制使用 BigDecimal</li></ol><p>六、开发规范（金融项目）</p><ol><li>数据库金额：decimal 类型</li><li>Java 代码：使用 BigDecimal 存储金额</li><li>运算统一指定保留小数位数与四舍五入</li></ol><h3 id="日期时间类"><a href="#日期时间类" class="headerlink" title="日期时间类"></a>日期时间类</h3><p>分为两大体系：</p><ol><li><p><strong>旧版（JDK1.0 ~ JDK7）</strong>：<code>Date</code>、<code>SimpleDateFormat</code>、<code>Calendar</code></p><p>缺点：线程不安全、API 设计差、可变性</p></li><li><p><strong>新版（JDK8+ 推荐）java.time 包</strong>：<code>LocalDate、LocalTime、LocalDateTime、DateTimeFormatter、Instant、Duration、Period</code></p><p>优点：不可变、线程安全、设计优雅，项目优先使用</p></li></ol><p>一、旧 API（了解，尽量不推荐新项目使用）</p><ol><li>java.util.Date</li></ol><p>代表<strong>瞬时时间点（毫秒时间戳）</strong></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 当前系统时间</span></span><br><span class="line"><span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>();</span><br><span class="line"><span class="comment">// 指定毫秒时间戳构造</span></span><br><span class="line"><span class="type">Date</span> <span class="variable">date2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>(<span class="number">1750000000000L</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取时间戳毫秒</span></span><br><span class="line"><span class="type">long</span> <span class="variable">time</span> <span class="operator">=</span> date.getTime();</span><br><span class="line"><span class="comment">// 设置时间戳</span></span><br><span class="line">date.setTime(<span class="number">1750000000000L</span>);</span><br></pre></td></tr></table></figure><p>缺点：</p><ol><li><code>Date</code>对象<strong>可变</strong>；setTime 修改原有对象</li><li><p>无法直接获取年、月、日；月份从 0 开始（0=1 月，大坑）</p></li><li><p>SimpleDateFormat 日期格式化</p></li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">SimpleDateFormat</span> <span class="variable">sdf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy-MM-dd HH:mm:ss&quot;</span>);</span><br><span class="line"><span class="comment">// Date → 字符串</span></span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> sdf.format(<span class="keyword">new</span> <span class="title class_">Date</span>());</span><br><span class="line"><span class="comment">// 字符串 → Date</span></span><br><span class="line"><span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> sdf.parse(<span class="string">&quot;2026-07-28 12:30:00&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>致命坑：SimpleDateFormat 线程不安全！多线程共用会异常。</strong></p><p>常用格式符号：</p><ul><li><code>yyyy</code> 4 位年份</li><li><code>MM</code> 月份 (01~12)</li><li><code>dd</code> 日期</li><li><code>HH</code> 24 小时制；<code>hh</code>12 小时制</li><li><code>mm</code>分钟；<code>ss</code>秒；<code>SSS</code>毫秒</li></ul><ol><li>Calendar 日历类（JDK1.1 替代 Date）</li></ol><p>抽象类，常用实现 <code>GregorianCalendar</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Calendar</span> <span class="variable">cal</span> <span class="operator">=</span> Calendar.getInstance();</span><br><span class="line"><span class="type">int</span> <span class="variable">year</span> <span class="operator">=</span> cal.get(Calendar.YEAR);</span><br><span class="line"><span class="type">int</span> <span class="variable">month</span> <span class="operator">=</span> cal.get(Calendar.MONTH); <span class="comment">// 依旧 0代表一月！</span></span><br><span class="line"><span class="type">int</span> <span class="variable">day</span> <span class="operator">=</span> cal.get(Calendar.DAY_OF_MONTH);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置日期</span></span><br><span class="line">cal.set(<span class="number">2026</span>,<span class="number">6</span>,<span class="number">28</span>);</span><br><span class="line"><span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> cal.getTime(); <span class="comment">//Calendar转Date</span></span><br></pre></td></tr></table></figure><p>缺点：依旧可变，API 别扭。</p><hr><p>二、JDK8 全新时间包 java.time</p><p>所有对象<strong>不可变</strong>，修改操作返回新对象；线程安全！</p><ol><li>核心类区分</li></ol><div class="table-container"><table><thead><tr><th style="text-align:center">类</th><th style="text-align:center">作用</th></tr></thead><tbody><tr><td style="text-align:center"><code>Instant</code></td><td style="text-align:center">时间戳（UTC 标准时间，毫秒 / 纳秒），对应旧 Date</td></tr><tr><td style="text-align:center"><code>LocalDate</code></td><td style="text-align:center">只有年月日 2026-07-28（无时分秒、无时区）</td></tr><tr><td style="text-align:center"><code>LocalTime</code></td><td style="text-align:center">只有时分秒 14:30:20</td></tr><tr><td style="text-align:center"><code>LocalDateTime</code></td><td style="text-align:center">年月日 + 时分秒，<strong>无时区</strong></td></tr><tr><td style="text-align:center"><code>ZonedDateTime</code></td><td style="text-align:center">带时区的完整时间（需要时区运算使用）</td></tr><tr><td style="text-align:center"><code>DateTimeFormatter</code></td><td style="text-align:center">格式化类（替代 SimpleDateFormat，线程安全）</td></tr><tr><td style="text-align:center"><code>Duration</code></td><td style="text-align:center">时间间隔：时分秒（秒、毫秒级别，两个时间点）</td></tr><tr><td style="text-align:center"><code>Period</code></td><td style="text-align:center">日期间隔：年、月、日（两个 LocalDate）</td></tr></tbody></table></div><ol><li>创建对象</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 获取当前时间</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">nowDate</span> <span class="operator">=</span> LocalDate.now();</span><br><span class="line"><span class="type">LocalTime</span> <span class="variable">nowTime</span> <span class="operator">=</span> LocalTime.now();</span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">nowDT</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 指定时间创建</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">date</span> <span class="operator">=</span> LocalDate.of(<span class="number">2026</span>,<span class="number">7</span>,<span class="number">28</span>);</span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">dt</span> <span class="operator">=</span> LocalDateTime.of(<span class="number">2026</span>,<span class="number">7</span>,<span class="number">28</span>,<span class="number">14</span>,<span class="number">30</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Instant 时间戳</span></span><br><span class="line"><span class="type">Instant</span> <span class="variable">instant</span> <span class="operator">=</span> Instant.now();</span><br><span class="line"><span class="comment">// 时间戳转Instant</span></span><br><span class="line"><span class="type">Instant</span> <span class="variable">instant2</span> <span class="operator">=</span> Instant.ofEpochMilli(<span class="number">1750000000000L</span>);</span><br></pre></td></tr></table></figure><ol><li>常用运算（不可变！操作返回新对象）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">LocalDateTime</span> <span class="variable">time</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line"></span><br><span class="line">time.plusDays(<span class="number">3</span>);    <span class="comment">//加3天</span></span><br><span class="line">time.minusHours(<span class="number">2</span>);  <span class="comment">//减2小时</span></span><br><span class="line">time.plusMonths(<span class="number">1</span>);  <span class="comment">//加1月</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="variable">month</span> <span class="operator">=</span> time.getMonthValue(); <span class="comment">//月份1~12（正常！不再从0开始）</span></span><br><span class="line"><span class="type">int</span> <span class="variable">day</span> <span class="operator">=</span> time.getDayOfMonth();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改指定字段</span></span><br><span class="line">time.withYear(<span class="number">2027</span>);</span><br></pre></td></tr></table></figure><ol><li>格式化与解析 DateTimeFormatter 线程安全</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">DateTimeFormatter</span> <span class="variable">formatter</span> <span class="operator">=</span> DateTimeFormatter.ofPattern(<span class="string">&quot;yyyy-MM-dd HH:mm:ss&quot;</span>);</span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">now</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line"></span><br><span class="line"><span class="comment">//时间 → 字符串</span></span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> now.format(formatter);</span><br><span class="line"><span class="comment">//字符串 → 时间</span></span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">dt</span> <span class="operator">=</span> LocalDateTime.parse(<span class="string">&quot;2026-07-28 15:20:00&quot;</span>,formatter);</span><br></pre></td></tr></table></figure><ol><li>时间间隔计算 Duration / Period</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 时分秒间隔（LocalDateTime / Instant）</span></span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">t1</span> <span class="operator">=</span> LocalDateTime.of(<span class="number">2026</span>,<span class="number">7</span>,<span class="number">28</span>,<span class="number">10</span>,<span class="number">0</span>);</span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">t2</span> <span class="operator">=</span> LocalDateTime.of(<span class="number">2026</span>,<span class="number">7</span>,<span class="number">28</span>,<span class="number">12</span>,<span class="number">30</span>);</span><br><span class="line"><span class="type">Duration</span> <span class="variable">duration</span> <span class="operator">=</span> Duration.between(t1,t2);</span><br><span class="line">System.out.println(duration.toHours()); <span class="comment">//相差小时</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 年月日间隔（仅LocalDate）</span></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">d1</span> <span class="operator">=</span> LocalDate.of(<span class="number">2026</span>,<span class="number">1</span>,<span class="number">1</span>);</span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">d2</span> <span class="operator">=</span> LocalDate.of(<span class="number">2026</span>,<span class="number">7</span>,<span class="number">28</span>);</span><br><span class="line"><span class="type">Period</span> <span class="variable">period</span> <span class="operator">=</span> Period.between(d1,d2);</span><br><span class="line">period.getMonths();</span><br></pre></td></tr></table></figure><ol><li>新旧 API 互相转换（项目兼容必备）</li></ol><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// LocalDateTime ↔ Date</span></span><br><span class="line"><span class="comment">//1. LocalDateTime -&gt; Date</span></span><br><span class="line"><span class="type">Instant</span> <span class="variable">instant</span> <span class="operator">=</span> LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();</span><br><span class="line"><span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> Date.from(instant);</span><br><span class="line"></span><br><span class="line"><span class="comment">//2. Date -&gt; LocalDateTime</span></span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">ldt</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();</span><br></pre></td></tr></table></figure><ol><li>时区简单说明</li></ol><p><code>LocalDateTime</code><strong>不带时区</strong>，跨时区业务（海外系统）必须使用 <code>ZonedDateTime</code>。</p><p>服务器一般统一使用 UTC 时间存储。</p><p>三、新旧 API 核心对比</p><div class="table-container"><table><thead><tr><th style="text-align:center">特性</th><th style="text-align:center">Date/SimpleDateFormat</th><th style="text-align:center">java.time(JDK8)</th></tr></thead><tbody><tr><td style="text-align:center">可变性</td><td style="text-align:center">可变</td><td style="text-align:center">不可变</td></tr><tr><td style="text-align:center">线程安全</td><td style="text-align:center">SimpleDateFormat 不安全</td><td style="text-align:center">全部安全</td></tr><tr><td style="text-align:center">月份</td><td style="text-align:center">0 开始</td><td style="text-align:center">1 开始，符合直觉</td></tr><tr><td style="text-align:center">API</td><td style="text-align:center">难用，功能弱</td><td style="text-align:center">丰富清晰</td></tr></tbody></table></div><p>四、高频易错知识点</p><ol><li>旧 Date、Calendar 月份从 0 开始；LocalDateTime 月份 1~12</li><li>SimpleDateFormat 禁止多线程共享；新项目一律用 DateTimeFormatter</li><li>LocalDateTime 无时区；不要直接用于跨时区业务</li><li>Duration 处理时分秒；Period 只处理年月日，不能混用</li><li>所有 JDK8 时间对象修改后<strong>返回新实例，原对象不变</strong></li></ol>]]></content>
    
    
    <summary type="html">Java基础知识点</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=a038fb8b-1fa2-a38b-026c-991fa1df58af" type="image"/>
    
    
    <category term="Java学习笔记" scheme="https://blog.itrf.cn/tags/Java%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>OpenClaw-QQBot 插件安装失败（npm install failed）完整解决方案</title>
    <link href="https://blog.itrf.cn/posts/29b5/"/>
    <id>https://blog.itrf.cn/posts/29b5/</id>
    <published>2026-03-15T14:28:26.000Z</published>
    <updated>2026-09-03T07:47:05.173Z</updated>
    
    <content type="html"><![CDATA[<p>今天安装openclaw对接qq机器人遇到了一个问题，直接运行</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">openclaw plugins install @tencent-connect/openclaw-qqbot@latest</span><br></pre></td></tr></table></figure><p>这个脚本，会出现下面这个提示</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install failed:</span><br></pre></td></tr></table></figure><p>npm 安装依赖失败，尝试了很多次都会提示这个(之前运行这个脚本就没遇到这个情况，很玄学) 。</p><p>经过了一天的折腾，找到了问题所在和解决方案</p><h2 id="问题核心原因"><a href="#问题核心原因" class="headerlink" title="问题核心原因"></a>问题核心原因</h2><h4 id="1-OpenClaw-内置安装机制的「过度限制」"><a href="#1-OpenClaw-内置安装机制的「过度限制」" class="headerlink" title="1. OpenClaw 内置安装机制的「过度限制」"></a>1. OpenClaw 内置安装机制的「过度限制」</h4><p>OpenClaw 的 <code>plugins install</code> 命令不是简单调用 <code>npm install</code>，而是加了多层校验：</p><ul><li>检测插件代码中的 <code>child_process</code>（Shell 执行）、环境变量读取等行为，即使是插件正常功能，也会触发「安全拦截」；</li><li>内置的 npm 执行环境被限制了权限 / 超时时间，一旦依赖下载稍慢或编译耗时，就会直接判定「npm install failed」；</li><li>临时目录（<code>/tmp/openclaw-plugin-*</code>）的权限 / 生命周期管控严格，手动操作稍慢就会被自动清理。</li></ul><h4 id="2-服务器基础环境不匹配"><a href="#2-服务器基础环境不匹配" class="headerlink" title="2. 服务器基础环境不匹配"></a>2. 服务器基础环境不匹配</h4><ul><li>Node.js 版本：插件要求 <code>node &gt;=24</code>，openclaw安装脚本安装的默认版本是 v22.22.1，部分依赖编译会出现问题；</li><li>编译依赖缺失：插件中的音频转换、系统调用类依赖需要 <code>gcc/g++/python</code>，即使安装了基础编译环境，低版本系统仍可能编译失败；</li><li>资源不足：服务器内存 / CPU 不足时，<code>npm install</code> 会因资源抢占卡住，表现为「无报错但一直卡」。</li></ul><h4 id="3-网络层的隐性问题"><a href="#3-网络层的隐性问题" class="headerlink" title="3. 网络层的隐性问题"></a>3. 网络层的隐性问题</h4><ul><li>npm 源的「安全审计接口」在国内镜像（如淘宝源）未实现，触发 404 警告，OpenClaw 会误判为安装失败；</li><li>依赖包分片下载时，服务器网络波动导致超时，npm 无重试机制，直接终止安装；</li><li>部分依赖包（如二进制预编译包）的 CDN 节点在境外，下载速度极慢甚至超时。</li></ul><h2 id="分步解决方案"><a href="#分步解决方案" class="headerlink" title="分步解决方案"></a>分步解决方案</h2><h4 id="1-升级node版本"><a href="#1-升级node版本" class="headerlink" title="1.升级node版本"></a>1.升级node版本</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 全局安装 n 工具</span></span><br><span class="line">npm install -g n</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果提示权限不足（Permission denied），加 sudo 执行</span></span><br><span class="line"><span class="built_in">sudo</span> npm install -g n</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装并切换到 v24 最新稳定版</span></span><br><span class="line"><span class="built_in">sudo</span> n 24</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看 Node.js 版本（输出 v24.x.x 即为成功）</span></span><br><span class="line">node -v</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看 npm 版本（同步升级，无需单独操作）</span></span><br><span class="line">npm -v</span><br></pre></td></tr></table></figure><h4 id="2-将npm源改成官方源"><a href="#2-将npm源改成官方源" class="headerlink" title="2.将npm源改成官方源"></a>2.将npm源改成官方源</h4><p>国内镜像会出现更新不同步的问题</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm config <span class="built_in">set</span> registry https://registry.npmjs.org/</span><br></pre></td></tr></table></figure><h4 id="3-直接通过npm安装插件"><a href="#3-直接通过npm安装插件" class="headerlink" title="3.直接通过npm安装插件"></a>3.直接通过npm安装插件</h4><p> 绕过OpenClaw 插件安装机制的限制</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install -g @tencent-connect/openclaw-qqbot@latest</span><br></pre></td></tr></table></figure><p>等待安装好之后把 npm 安装好的包迁移到 OpenClaw 插件目录</p><h4 id="4-定位插件安装路径"><a href="#4-定位插件安装路径" class="headerlink" title="4.定位插件安装路径"></a>4.定位插件安装路径</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 自动定位 npm 全局安装的插件路径（兼容不同系统）</span></span><br><span class="line">PLUGIN_PATH=$(npm root -g)/@tencent-connect/openclaw-qqbot</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 验证路径是否存在（不存在则提示错误）</span></span><br><span class="line"><span class="keyword">if</span> [ ! -d <span class="string">&quot;<span class="variable">$PLUGIN_PATH</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;❌ 插件安装失败，请检查 npm install 命令是否执行成功&quot;</span></span><br><span class="line">  <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;✅ 插件安装路径：<span class="variable">$PLUGIN_PATH</span>&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><h4 id="5-迁移到-OpenClaw-插件目录"><a href="#5-迁移到-OpenClaw-插件目录" class="headerlink" title="5.迁移到 OpenClaw 插件目录"></a>5.迁移到 OpenClaw 插件目录</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 删除 OpenClaw 旧的未完成目录</span></span><br><span class="line"><span class="built_in">rm</span> -rf /root/.openclaw/extensions/openclaw-qqbot</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 将 npm 安装好的插件完整复制过去</span></span><br><span class="line"><span class="built_in">cp</span> -r <span class="variable">$PLUGIN_PATH</span> /root/.openclaw/extensions/openclaw-qqbot</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 赋予正确权限</span></span><br><span class="line"><span class="built_in">chown</span> -R <span class="variable">$USER</span>:<span class="variable">$USER</span> /root/.openclaw/extensions/openclaw-qqbot</span><br><span class="line"><span class="built_in">chmod</span> -R 755 /root/.openclaw/extensions/openclaw-qqbot</span><br></pre></td></tr></table></figure><h4 id="6-配置信任-amp-验证插件"><a href="#6-配置信任-amp-验证插件" class="headerlink" title="6.配置信任 &amp; 验证插件"></a>6.配置信任 &amp; 验证插件</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 配置 plugins.allow 消除安全提示</span></span><br><span class="line"><span class="built_in">cat</span> &gt; /root/.openclaw/config.json &lt;&lt; <span class="string">EOF</span></span><br><span class="line"><span class="string">&#123;</span></span><br><span class="line"><span class="string">  &quot;plugins&quot;: &#123;</span></span><br><span class="line"><span class="string">    &quot;allow&quot;: [&quot;@tencent-connect/openclaw-qqbot&quot;, &quot;wecom-app&quot;]</span></span><br><span class="line"><span class="string">  &#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2.重启OpenClaw服务</span></span><br><span class="line">openclaw gateway restart</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 最终验证（看到插件名称即为成功）</span></span><br><span class="line">openclaw plugins list | grep openclaw-qqbot</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="7-配置-QQ-机器人"><a href="#7-配置-QQ-机器人" class="headerlink" title="7.配置 QQ 机器人"></a>7.配置 QQ 机器人</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1.配置绑定当前QQ机器人</span></span><br><span class="line">openclaw channels add --channel qqbot --token <span class="string">&quot;190xxxxx:iT9xxxxxx&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2.重启OpenClaw服务</span></span><br><span class="line">openclaw gateway restart</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li>核心解决思路：<strong>绕过 OpenClaw 内置安装机制</strong>，手动用 npm 安装插件后迁移到指定目录；</li><li>关键前置：升级 Node.js 到 v24+、安装编译依赖、切换 npm 官方源，解决底层环境问题；</li><li>验证要点：插件迁移后需配置信任列表，重启服务并通过 <code>openclaw plugins list</code> 确认加载成功。</li></ol>]]></content>
    
    
    <summary type="html">本文针对 openclaw plugins install @tencent-connect/openclaw-qqbot 执行时出现的 npm install failed 报错，提供更清晰、更健壮、更易操作的解决方案，覆盖问题定位、环境修复、手动安装、权限配置全流程。</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=ee803194-276a-2597-69e3-a6a1a7c31fa3" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>一文读懂比特币工作原理</title>
    <link href="https://blog.itrf.cn/posts/34af/"/>
    <id>https://blog.itrf.cn/posts/34af/</id>
    <published>2026-02-28T18:55:17.000Z</published>
    <updated>2026-09-07T07:04:30.624Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、比特币的诞生"><a href="#一、比特币的诞生" class="headerlink" title="一、比特币的诞生"></a>一、比特币的诞生</h2><p>2008 年，一位化名中本聪的神秘人物（或组织）发表了比特币白皮书，提出一种去中心化的数字货币构想，旨在实现无需传统金融机构的交易体系。次年，即 2009 年，中本聪基于白皮书中的理念，开发出比特币钱包软件，并公开供用户下载使用，比特币网络由此正式启动。</p><h2 id="二、比特币网络搭建"><a href="#二、比特币网络搭建" class="headerlink" title="二、比特币网络搭建"></a>二、比特币网络搭建</h2><p>当新用户下载并运行比特币钱包软件时，软件会自动连接内置的种子节点。这些种子节点如同网络的 “联络站”，会向新用户节点分享其已连接的其他节点信息。新用户节点借此与众多对等节点建立起 P2P（点对点）连接。在这个过程中，各个节点按照既定规则相互交换信息，逐渐构建起一个没有中心控制的比特币网络。网络中的所有节点地位平等，共同维护着网络的正常运转。这种 P2P 网络结构确保了比特币系统的去中心化特性，没有任何一个节点能够单独掌控整个网络。</p><h2 id="三、钱包地址生成"><a href="#三、钱包地址生成" class="headerlink" title="三、钱包地址生成"></a>三、钱包地址生成</h2><p>每个用户都可以通过本地的比特币钱包软件生成独一无二的钱包地址。钱包地址类似于现实生活中的银行账号，是用户在比特币网络中接收和存储比特币的标识。它由钱包软件依据特定算法生成，整个生成过程无需依赖第三方机构，极大地保障了用户的自主性和隐私性。这意味着用户可以完全掌控自己的钱包地址，无需担心第三方机构的干预或泄露风险。</p><h2 id="四、内存池功能"><a href="#四、内存池功能" class="headerlink" title="四、内存池功能"></a>四、内存池功能</h2><p>钱包软件会在用户电脑里开辟一块默认大小为 300M 的内存空间，这就是内存池。它就像一个 “交易等待区”，主要任务是临时存放转账记录。当用户发起交易时，相关的转账信息会先被暂存到内存池中，等待被打包进区块，成为区块链账本的一部分。内存池中的交易处于待确认状态，它们在这里排队等待被矿工打包处理。</p><h2 id="五、创世区块诞生"><a href="#五、创世区块诞生" class="headerlink" title="五、创世区块诞生"></a>五、创世区块诞生</h2><ol><li><strong>铸币交易</strong>：比特币网络的首个钱包用户开始创建区块 0 。在这个过程中，该用户通过一种特殊的 coinbase 铸币交易，凭空给自己的钱包地址转入 50 枚比特币。这 50 枚比特币是比特币网络给予打包区块者的初始奖励，此后每四年奖励减半。这种奖励机制旨在激励更多节点参与到比特币网络的维护和新区块的创建中来，为比特币网络的发展提供动力。</li><li><strong>提取记录</strong>：完成铸币交易后，由于此时网络刚刚起步，内存池中仅有这笔铸币交易，并无其他交易，用户便从内存池中提取该转账记录，随后进入打包区块头环节。</li></ol><h2 id="六、打包区块头流程"><a href="#六、打包区块头流程" class="headerlink" title="六、打包区块头流程"></a>六、打包区块头流程</h2><ol><li><p><strong>确定交易记录</strong>：将从内存池提取的铸币交易记录确定为该区块要记录的交易内容，这是构成区块的核心数据。</p></li><li><p><strong>交易哈希运算</strong>：对确定好的铸币交易进行哈希运算。哈希算法具有三个重要特性：</p><ul><li><strong>唯一性</strong>：无论何时何地，对相同的数据进行哈希运算，得到的结果始终固定且唯一。例如，无论谁对字符 “a” 进行 SHA256 哈希运算，都会得到相同的哈希值。这保证了每个交易在经过哈希运算后都有独一无二的标识。</li><li><strong>不可逆性</strong>：无法通过哈希值反向推导出原始数据内容。比如将一篇文章进行哈希运算后，他人无法从哈希值还原出文章原文。这为交易信息提供了保密性。</li><li><strong>雪崩效应</strong>：原始数据哪怕只有极其微小的改动，都会导致哈希运算结果截然不同。通过对交易记录进行哈希运算得到的数值，会被填入区块头。这使得任何对交易的篡改都会导致哈希值的明显变化，从而易于被发现。</li></ul></li></ol><ol><li><p><strong>添加相关信息</strong>：</p><ul><li><strong>上一区块哈希值</strong>：因为这是首个区块，不存在上一个区块，所以在区块头此位置填 0 。后续区块则会在此处填入上一区块的哈希值，通过这种方式将各个区块依次连接起来，形成区块链结构。这种链式结构保证了区块链数据的连贯性和可追溯性，每个区块都依赖于前一个区块的哈希值，一旦前面的区块被篡改，后续区块的哈希值就会不匹配。</li><li><strong>时间戳</strong>：记录区块创建的时间，为区块链提供时间维度信息，用于确定交易顺序和区块先后关系，帮助节点验证数据的真实性和顺序性。时间戳使得区块链上的交易和区块按照时间顺序排列，便于追踪和验证。</li><li><strong>规定难度</strong>：区块头设定当前难度值，如 0X1D00FFFF ，该值决定挖矿难度。难度值会根据全网算力的变化而动态调整，以确保比特币网络平均每 10 分钟产生一个新区块。难度值的调整是比特币网络自我调节的重要机制，保证了出块速度的稳定性。</li><li><strong>随机数</strong>：初始值设为 0 ，在挖矿过程中，通过不断改变随机数重新计算哈希值，以寻找符合难度要求的结果。随机数是挖矿过程中不断尝试的变量，通过调整它来改变哈希运算结果，是挖矿找到有效区块的关键因素之一。</li></ul></li></ol><h2 id="七、挖矿过程解析"><a href="#七、挖矿过程解析" class="headerlink" title="七、挖矿过程解析"></a>七、挖矿过程解析</h2><ol><li><strong>哈希计算与比较</strong>：对打包好的区块头进行哈希计算，会得到一个很大的数值。该数值需与当前设定的难度值（bits）进行比较。若计算值小于 bits ，则挖矿成功；若大于 bits ，需改变随机数重新计算哈希值，反复尝试直至找到小于 bits 的数值。这个过程就像是在海量数字中碰运气，每秒计算哈希值的次数越多（即算力越高），挖到矿的概率就越大。可以想象成在一个巨大的数字迷宫中寻找特定的出口，算力就是寻找出口的速度。</li><li><strong>难度调整与时间控制</strong>：比特币网络设定大约每 10 分钟产生一个区块。为实现这一目标，会根据全网算力的变化动态调整难度值。每挖出 2016 个区块（大约两周时间），系统会检查挖矿平均速度。若平均出块时间快于 10 分钟，表明全网算力提升，将增加挖矿难度（减小 bits 值）；若慢于 10 分钟，说明全网算力降低，将降低挖矿难度（增大 bits 值）。通过这种自动调整机制，确保比特币网络维持相对稳定的出块节奏，使得比特币的发行和交易能够有序进行。</li><li><strong>新区块传播与验证</strong>：当某节点成功挖到矿，会立即向其邻居节点发送新区块哈希值。邻居节点发现自身无此区块，便向该节点索要完整区块数据。收到后，邻居节点对区块头进行哈希计算，验证哈希值与接收到的一致且小于 bits 规定难度，确认区块有效后，将其保存至自身电脑的区块链数据库，并继续向其他节点传播。首个挖出的区块即创世区块，创世区块诞生后，比特币网络随即开启第二个区块的挖掘。新区块的传播和验证过程确保了整个比特币网络数据的一致性和准确性。</li></ol><h2 id="八、后续区块挖掘与交易处理"><a href="#八、后续区块挖掘与交易处理" class="headerlink" title="八、后续区块挖掘与交易处理"></a>八、后续区块挖掘与交易处理</h2><ol><li><strong>第二个区块挖掘</strong>：第二个区块挖掘流程与首个类似。节点先创建铸币交易，奖励自己 50 枚比特币，接着从内存池提取交易记录（此时内存池可能已有其他交易，如转账交易）放入区块。随后打包区块头，填入所有交易哈希值及上一区块（创世区块）的哈希值，添加时间戳、目标难度、随机数等信息，通过改变随机数计算哈希值，竞争挖掘新区块。不同节点同时竞争，谁先找到符合难度要求的哈希值，谁就成功挖到第二个区块。在这个过程中，各个节点都在努力尝试找到合适的随机数，以满足难度要求，就像一场紧张的竞赛。</li><li><strong>交易处理</strong>：例如，某用户（假设为 A）要向另一用户（假设为 B）转账比特币。A 先确认自己拥有足够且未花费的比特币，如在创世区块获得的 50 枚。A 创建交易，给 B 转 9 枚比特币，同时给自己转回剩余比特币（假设转回 40 枚，遵循比特币需花完之前交易金额的规则），剩余 1 枚作为矿工手续费。A 将交易信息广播给邻居节点，其他节点验证 A 是否有足够比特币。通过查之前区块确认后，接受交易并暂存于内存池。此时 B 的钱包显示收到 9 枚比特币，但处于未确认状态。当某节点成功挖到包含该交易的新区块并广播，其他节点验证有效后加入区块链数据库，同时从内存池删除该交易，B 才真正收到比特币。随着新区块不断生成，区块链不断增长，交易记录被永久记录，确保交易不可篡改与可追溯。这个过程保证了比特币交易的安全性和可靠性，每一笔交易都被准确记录在区块链上。</li></ol><h2 id="九、挖矿意义"><a href="#九、挖矿意义" class="headerlink" title="九、挖矿意义"></a>九、挖矿意义</h2><p>挖矿在比特币网络中起着核心作用。由于每个节点都能构建区块，为确定使用哪个区块添加到区块链，设计了挖矿机制。挖矿基于 PoW（工作量证明），是一种竞争性记账机制。只有率先找到符合条件哈希值（小于规定难度值）的节点，其构建的区块才能被添加到区块链，并获得挖矿奖励。每个区块头包含上一区块哈希值，形成链式结构。若有人篡改某区块内容，该区块哈希值改变，后续区块哈希值对应不上，链会断开。比特币网络只认可最长区块链，篡改无效。理论上，若个体或组织拥有超全网 51% 的算力，可从创世区块重新挖矿改写历史，即 “51% 算力攻击”。但现实中，要超越比特币全网目前 900EH/s 的算力几乎不可能，保障了比特币网络的安全与稳定。挖矿不仅是产生新比特币的方式，更是维护比特币网络安全和一致性的重要手段。</p>]]></content>
    
    
    <summary type="html">Bitcoin is a decentralized digital currency that can be used without a centralized authority or bank. It uses peer-to-peer technology to allow users to send and receive money without the need for a financial institution.</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=f5d006eb-d805-6e79-9857-8b42873d9d89" type="image"/>
    
    
    <category term="虚拟货币" scheme="https://blog.itrf.cn/tags/%E8%99%9A%E6%8B%9F%E8%B4%A7%E5%B8%81/"/>
    
    <category term="区块链" scheme="https://blog.itrf.cn/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    
  </entry>
  
  <entry>
    <title>HTTPS页面请求HTTP接口失败？一文讲透Mixed Content</title>
    <link href="https://blog.itrf.cn/posts/ceca/"/>
    <id>https://blog.itrf.cn/posts/ceca/</id>
    <published>2026-02-13T15:15:57.000Z</published>
    <updated>2026-02-13T15:34:34.956Z</updated>
    
    <content type="html"><![CDATA[<h1 id="HTTPS页面请求HTTP接口失败？一文讲透Mixed-Content"><a href="#HTTPS页面请求HTTP接口失败？一文讲透Mixed-Content" class="headerlink" title="HTTPS页面请求HTTP接口失败？一文讲透Mixed Content"></a>HTTPS页面请求HTTP接口失败？一文讲透Mixed Content</h1><blockquote><p>前端开发中，有一个高频踩坑场景：前端绑定域名后，HTTP 协议下请求后端 IP 接口一切正常，可一旦开启 SSL 切换为 HTTPS，接口就直接请求失败，控制台报出红色错误 —— 这不是代码 Bug，而是浏览器的安全限制，核心原因就是「Mixed Content（混合内容）」。</p></blockquote><h2 id="一、先明确核心概念：Mixed-Content（混合内容）"><a href="#一、先明确核心概念：Mixed-Content（混合内容）" class="headerlink" title="一、先明确核心概念：Mixed Content（混合内容）"></a>一、先明确核心概念：Mixed Content（混合内容）</h2><p>首先厘清两个基础认知，避免混淆：</p><ol><li><p>HTTPS 的本质</p><p>HTTPS = HTTP + TLS（Transport Layer Security，传输层安全协议），相当于给 HTTP 传输的数据加了一层加密外套，防止数据被窃听、篡改，保障访问安全；而 HTTP 是明文传输，无任何加密保护。我们常说的「开启 SSL」，本质就是启用 HTTPS 加密。</p></li><li><p>Mixed Content（混合内容）的定义</p><p>当 HTTPS 加密页面 中，请求了 HTTP 明文资源 时，就属于 Mixed Content（混合内容）。</p><p>浏览器的安全逻辑：HTTPS 页面代表用户处于安全的加密环境，若此时请求 HTTP 明文接口，传输的数据会暴露在风险中，可能被抓包、篡改。因此，浏览器会强制拦截此类请求，这是无法通过后端配置绕过的硬规则。</p></li></ol><p>浏览器控制台的标准报错：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title class_">Mixed</span> <span class="title class_">Content</span>: <span class="title class_">The</span> page at <span class="string">&#x27;https://yourdomain.com&#x27;</span> was loaded over <span class="variable constant_">HTTPS</span>, but requested an insecure <span class="title class_">XMLHttpRequest</span> endpoint <span class="string">&#x27;http://your-server-ip:port/api&#x27;</span>. <span class="title class_">This</span> request has been blocked; the content must be served over <span class="variable constant_">HTTPS</span>.</span><br></pre></td></tr></table></figure><h2 id="二、重点解答：HTTPS-页面能否请求-HTTP-接口？"><a href="#二、重点解答：HTTPS-页面能否请求-HTTP-接口？" class="headerlink" title="二、重点解答：HTTPS 页面能否请求 HTTP 接口？"></a>二、重点解答：HTTPS 页面能否请求 HTTP 接口？</h2><p>结论：对于接口请求（fetch/axios/XHR），现代浏览器 100% 拦截，无生产可用的例外场景。</p><p>浏览器将 Mixed Content 分为两类，仅被动内容有极特殊情况：</p><ul><li>被动内容（Passive Mixed Content）：如图片、CSS、字体，部分浏览器可能放行，但会标红警告，无实际生产意义；</li><li>主动内容（Active Mixed Content）：如接口请求、JS 脚本、XHR/fetch，所有现代浏览器（Chrome/Edge/Firefox 等）一律强制拦截，这也是我们踩坑的核心场景。</li></ul><p>补充：手动关闭浏览器混合内容拦截（仅测试用），无法推广给普通用户，生产环境完全不可用。</p><h2 id="三、最简单、一劳永逸的解决方案（Nginx-代理）"><a href="#三、最简单、一劳永逸的解决方案（Nginx-代理）" class="headerlink" title="三、最简单、一劳永逸的解决方案（Nginx 代理）"></a>三、最简单、一劳永逸的解决方案（Nginx 代理）</h2><p>核心思路：让前端和接口「同域名、同 HTTPS」，彻底规避 Mixed Content 和跨域，无需修改后端代码。</p><h3 id="最终实现效果"><a href="#最终实现效果" class="headerlink" title="最终实现效果"></a>最终实现效果</h3><p>前端：<a href="https://yourdomain.com">https://yourdomain.com</a> （HTTPS 加密，原域名）；</p><p>接口：<a href="https://yourdomain.com/api">https://yourdomain.com/api</a> （同域名、同 HTTPS，通过 Nginx 代理到后端）。</p><h3 id="Nginx-核心配置（复制即用，替换实际信息）"><a href="#Nginx-核心配置（复制即用，替换实际信息）" class="headerlink" title="Nginx 核心配置（复制即用，替换实际信息）"></a>Nginx 核心配置（复制即用，替换实际信息）</h3><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="section">server</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="attribute">listen</span> <span class="number">443</span> ssl;</span><br><span class="line"></span><br><span class="line"><span class="attribute">server_name</span> [yourdomain.com](https://yourdomain.com);  <span class="comment"># 你的前端域名</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># SSL 证书配置（复用前端证书，无需额外申请）</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">ssl_certificate</span> /your-ssl-path/yourdomain.pem;</span><br><span class="line"></span><br><span class="line"><span class="attribute">ssl_certificate_key</span> /your-ssl-path/yourdomain.key;</span><br><span class="line">    </span><br><span class="line"><span class="comment"># 前端静态资源配置（无需部署前端可删除）</span></span><br><span class="line"></span><br><span class="line"><span class="section">location</span> / &#123;</span><br><span class="line"></span><br><span class="line"><span class="attribute">root</span> /your-frontend-path;</span><br><span class="line"></span><br><span class="line"><span class="attribute">index</span> index.html;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line"><span class="comment">#核心：接口代理，转发 /api 请求到后端 HTTP 服务</span></span><br><span class="line">    </span><br><span class="line"><span class="section">location</span> /api &#123;</span><br><span class="line"></span><br><span class="line"><span class="attribute">proxy_pass</span> http://your-server-ip:your-server-port;  <span class="comment"># 后端 IP + 端口</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">proxy_set_header</span> Host <span class="variable">$host</span>;</span><br><span class="line"></span><br><span class="line"><span class="attribute">proxy_set_header</span> X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line"></span><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="comment"># 可选：80 端口自动跳转 HTTPS，优化用户体验</span></span><br><span class="line"></span><br><span class="line"><span class="section">server</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line"></span><br><span class="line"><span class="attribute">server_name</span> [yourdomain.com](https://yourdomain.com);</span><br><span class="line"></span><br><span class="line"><span class="attribute">return</span> <span class="number">301</span> https://hostrequest_uri;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="前端代码修改（1-行搞定）"><a href="#前端代码修改（1-行搞定）" class="headerlink" title="前端代码修改（1 行搞定）"></a>前端代码修改（1 行搞定）</h3><p>原写法（硬编码 HTTP 协议和 IP 地址，触发 Mixed Content 拦截）：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">fetch</span>(<span class="string">&#x27;http://your-server-ip:your-server-port/api/xxx&#x27;</span>)</span><br></pre></td></tr></table></figure><p>修改后（使用相对路径，自动继承页面的 HTTPS 协议和域名，规避混合内容）：</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">fetch</span>(<span class="string">&#x27;/api/xxx&#x27;</span>)</span><br></pre></td></tr></table></figure><h2 id="四、终极避坑口诀（记牢不踩坑）"><a href="#四、终极避坑口诀（记牢不踩坑）" class="headerlink" title="四、终极避坑口诀（记牢不踩坑）"></a>四、终极避坑口诀（记牢不踩坑）</h2><ol><li>HTTPS 页面只能请求 HTTPS 接口，否则触发 Mixed Content 拦截；</li><li>HTTP 页面可请求 HTTP/HTTPS 接口，无 Mixed Content 限制；</li><li>跨域可通过后端 CORS 解决，Mixed Content 只能统一 HTTPS 协议；</li><li>接口类主动内容，HTTPS 页面请求 HTTP 接口必失败，无例外。</li></ol>]]></content>
    
    
    <summary type="html">前端开发中，有一个高频踩坑场景：前端绑定域名后，HTTP 协议下请求后端 IP 接口一切正常，可一旦开启 SSL 切换为 HTTPS，接口就直接请求失败，控制台报出红色错误 —— 这不是代码 Bug，而是浏览器的安全限制，核心原因就是「Mixed Content（混合内容）」。</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=92edf4fe-5f0f-8cf5-9c7a-8b57c5a576b9" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>阿里云大模型工程师ACA认证学习笔记</title>
    <link href="https://blog.itrf.cn/posts/5f08/"/>
    <id>https://blog.itrf.cn/posts/5f08/</id>
    <published>2026-01-27T09:14:17.000Z</published>
    <updated>2026-09-03T07:46:54.521Z</updated>
    
    <content type="html"><![CDATA[<h1 id="阿里云大模型工程师ACA认证学习笔记"><a href="#阿里云大模型工程师ACA认证学习笔记" class="headerlink" title="阿里云大模型工程师ACA认证学习笔记"></a>阿里云大模型工程师ACA认证学习笔记</h1><h2 id="背景知识"><a href="#背景知识" class="headerlink" title="背景知识"></a>背景知识</h2><p>从2022年底ChatGPT的一鸣惊人，再到持续进行的”百模大战”，”大模型”已经逐渐成为了技术和公众领域的热点。</p><p>大模型是人工智能领域的一个重要里程碑，它推动了人工智能技术的发展，并为人类的未来带来新的可能性。有人曾经类比，大模型的发明相当于人类文明的哪个节点？一个浪漫的答案可能是：人类学会使用火的时刻。</p><h2 id="人工智能"><a href="#人工智能" class="headerlink" title="人工智能"></a>人工智能</h2><p>定义：人工智能（AI）是一门使机器模拟人类<strong>智能过程</strong>的学科，其中具体包括学习、推理、自我修正、感知和处理语言等功能。人工智能涉及计算机科学、数据分析、统计学、机器工程、语言学、神经科学、哲学和心理学等多个学科的领域，旨在研究、设计、构建具备智能、学习、推理和行动能力的计算机和机器。</p><h3 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h3><ul><li><p>机器学习(ML)：研究计算机如何在没有明确编程的情况下，通过对数据的分析、学习，自动改进其行为或做出预测的学科。旨在使计算机系统具备从经验中学习的能力，以适应新情况、解决问题或完成特定任务。</p><p>根据工作模式分类：</p><ul><li>监督学习：学习为人提供的数据，对具备某种特征的数据进行人为标记。</li><li>无监督学习：根据数据特征进行相似归类。</li><li>强化学习：奖励机制，猜对了奖励，猜错了不给奖励。</li></ul></li><li><p>深度学习(DL)：是机器学习的一个分支，主要使用<strong>神经网络模型</strong>对数据进行学习和表示。深度学习算法试图模拟人类大脑的工作方式，通过对大量数据的学习，自动提取出数据的高层次特征和模式，从而实现图像识别、语音识别、自然语言处理等任务。</p><p>神经网络的分类：</p><ul><li>卷积神经网络 CNNs</li><li>循环神经网络 RNNs</li><li>Transformer网络</li></ul></li><li><p>生成式人工智能(GenerativeAI)：深度学习中快速增长的子集，使用大模型提供支持，在大量原始、未标记的数据基础上对深度学习模型进行预训练，使得机器能够“理解”语言甚至图像，并能够根据需要自动生成内容。</p></li></ul><h2 id="大模型"><a href="#大模型" class="headerlink" title="大模型"></a>大模型</h2><p>提出：2021年，斯坦福大学的研究员团队发表了一篇论文，提出了<a href="https://arxiv.org/abs/2108.07258">Foundational Models</a>（基础模型，即大模型）的概念。</p><p>定义：它是一类具有大量参数（通常在十亿以上），能在极为广泛的数据上进行训练，并适用于多种任务和应用的预训练深度学习模型。</p><h3 id="大模型的训练的三个阶段"><a href="#大模型的训练的三个阶段" class="headerlink" title="大模型的训练的三个阶段"></a>大模型的训练的三个阶段</h3><ul><li><p>预训练(Pre-training)：在这个阶段大模型会从海量的互联网文本中学习，掌握语言的统计规律、事实性知识以及上下文关联能力。但它本质上只是学会了“预测下一个词”或“补全句子”，并没有真正理解人类提问的意图。它虽然“知道很多”，却还不懂得“如何回应人的请求”。因此，我们需要通过监督微调（SFT）来教会它遵循指令。</p></li><li><p>监督微调(SFT)：在这个阶段大模型会学习大量由人类精心构造的“指令-回答”样本，涵盖日常对话、逻辑推理、专业问答等多种场景。通过这些示范，模型学会将用户的输入理解为明确的指令，并给出有针对性的回应。经过SFT，模型已经具备了基本的指令遵循能力和任务完成能力。但它仍可能输出不恰当的内容——比如包含偏见、虚假信息、不安全言论等，因为它只学会了“怎么答”，还没学会“什么该答、什么不该答”。</p></li><li><p>对齐偏好(Preference Alignment)：在这个阶段大模型会针对同一个问题生成多个不同的回答，由人类评估者根据有用性、安全性、礼貌性、事实准确性等标准进行比较和选择。模型通过学习这些“人类更喜欢哪个回答”的信号，逐渐调整自己的输出方式，使得回答不仅正确，而且更符合人类的价值观和交流习惯。  </p><p>实现对齐的方式：</p><ul><li>基于人类反馈的强化学习（RLHF，Reinforcement Learning from Human Feedback），先用人类标注的数据训练一个“评分老师”（奖励模型），再让这个“老师”指导大模型改进回答。</li><li>DPO（Direct Preference Optimization），跳过训练“老师”的中间步骤，直接根据人类偏好数据优化模型本身。这个方法简化了训练流程，效果也很好</li></ul></li></ul><h3 id="大模型的特点"><a href="#大模型的特点" class="headerlink" title="大模型的特点"></a>大模型的特点</h3><ul><li>规模和参数量大</li><li>适应性和灵活性强</li><li>广泛数据集的预训练</li><li>计算资源需求大</li></ul><h3 id="大模型分类"><a href="#大模型分类" class="headerlink" title="大模型分类"></a>大模型分类</h3><ul><li><p>大语言模型(LLM)：这类大模型专注于自然语言处理（NLP），旨在处理语言、文章、对话等自然语言文本。它们通常基于深度学习架构（如Transformer模型），经过大规模文本数据集训练而成，能够捕捉语言的复杂性，包括语法、语义、语境以及蕴含的文化和社会知识。语言大模型典型应用包括文本生成、问答系统、文本分类、机器翻译、对话系统等。</p></li><li><p>多模态模型：多模态大模型能够同时处理和理解来自不同感知通道（如文本、图像、音频、视频等）的数据，并在这些模态之间建立关联和交互。它们能够整合不同类型的输入信息，进行跨模态推理、生成和理解任务。多模态大模型的应用涵盖视觉问答、图像描述生成、跨模态检索、多媒体内容理解等领域。</p><p>模态组合：</p><ul><li>视觉+文本：包括VQA视觉问答，图像字幕，图文检索、文生图等应用。</li><li>音频+文本：包括语音生成、语音摘要、语音识别等应用。</li><li>音频+视觉：包括音生图、演讲人脸生成等应用。</li></ul></li></ul><h3 id="大模型工作原理"><a href="#大模型工作原理" class="headerlink" title="大模型工作原理"></a>大模型工作原理</h3><p>用户可以使用自然语言与大模型交流，用户的文本就是“提示词”。提示词越清晰，模型的回答就越符合预期。</p><p>大模型处理提示词的工作流程可以分为两部分，第一部分是分词化与词表映射，第二部分为生成文本。</p><h4 id="分词化"><a href="#分词化" class="headerlink" title="分词化"></a>分词化</h4><p>分词化（Tokenization）是自然语言处理（NLP）中的重要概念，它是将段落和句子分割成更小的分词（token）的过程。</p><p>分词化的粒度分类：</p><ul><li>词粒度（Word-Level Tokenization）分词化，适用于大多数西方语言，如英语。</li><li>字符粒度（Character-Level）分词化，是中文最直接的分词方法，以单个汉字为单位进行分词化。</li><li>子词粒度（Subword-Level）分词化，它将单词分解成更小的单位，比如词根、词缀等。这种方法对于处理新词（比如专有名词、网络用语等）特别有效，因为即使是新词，它的组成部分（子词）很可能已经存在于词表中了。</li></ul><h4 id="词表映射"><a href="#词表映射" class="headerlink" title="词表映射"></a>词表映射</h4><p>分出的词就是token，每一个token都会通过预先设置好的词表，映射为一个 token id，这是token 的“身份证”，一句话最终会被表示为一个元素为token id的列表，供计算机进行下一步处理。</p><h4 id="生成文本"><a href="#生成文本" class="headerlink" title="生成文本"></a>生成文本</h4><p>大语言模型的工作概括来说是根据给定的文本预测下一个token。对我们来说，看似像在对大模型提问，但实际上是给了大模型一串提示文本，让它可以对后续的文本进行推理。</p><p>大模型的推理过程不是一步到位的，当大模型进行推理时，它会基于现有的token，根据概率最大原则预测出下一个最有可能的token，然后将该预测的token加入到输入序列中，并将更新后的输入序列继续输入大模型预测下一个token，这个过程叫做<strong>自回归</strong>。直到输出特殊token（如<EOS>，end of sentence，专门用来控制推理何时结束）或输出长度达到阈值。</p><h3 id="大模型的经典应用场景"><a href="#大模型的经典应用场景" class="headerlink" title="大模型的经典应用场景"></a>大模型的经典应用场景</h3><ul><li>文本生成</li><li>文本编辑</li><li>推理分析</li><li>总结概要</li></ul><h2 id="提示词"><a href="#提示词" class="headerlink" title="提示词"></a>提示词</h2><p><strong>提示词（Prompt）是用户发送给大语言模型的问题、指令或请求，</strong>来明确地告诉模型用户想要解决的问题或完成的任务，是大语言模型理解用户需求并据此生成相关、准确回答或内容的基础。对于大语言模型来说，提示词就是用户输入给大语言模型的文本信息。</p><p>提示词Prompt—-&gt;大语言模型LLM—-&gt;返回结果Completion</p><h3 id="提示词工程"><a href="#提示词工程" class="headerlink" title="提示词工程"></a>提示词工程</h3><p><strong>提示词工程（Prompt Engineering）</strong>就是研究如何构建和调整提示词，从而让大语言模型实现各种符合用户预期的任务的过程。</p><p>提示词工程包括以下关键步骤：</p><ol><li>理解任务需求</li><li>构建有效提示词</li><li>评估提示词效果</li><li>持续迭代改进</li></ol><h3 id="提示词技巧"><a href="#提示词技巧" class="headerlink" title="提示词技巧"></a>提示词技巧</h3><h4 id="直接提问"><a href="#直接提问" class="headerlink" title="直接提问"></a>直接提问</h4><p>直接提问，也称为<strong>零样本提示（Zero-Shot Prompting），</strong>即不给大语言模型提供案例，完全依靠 LLM 理解和处理能力完成任务。适用于<strong>目标明确、问题简单、答案确定且唯一</strong>等场景。</p><p>直接提问时，可遵循以下原则：</p><ul><li><strong>简洁</strong>：尽量用最简短的方式表达问题。过于冗长的问题可能包含多余的信息，导致模型理解错误或答非所问。</li><li><strong>具体</strong>：避免抽象的问题，确保问题是具体的，不含糊。</li><li><strong>详细上下文</strong>：如果问题涉及特定上下文或背景信息，要提供足够的详情以帮助模型理解，即使是直接提问也不例外。</li><li><strong>避免歧义</strong>：如果一个词或短语可能有多重含义，要么明确其含义，要么重新表述以消除歧义。</li><li><strong>逻辑清晰</strong>：问题应逻辑连贯，避免出现逻辑上的混淆或矛盾，这样才能促使模型提供有意义的回答。</li></ul><h4 id="增加示例"><a href="#增加示例" class="headerlink" title="增加示例"></a>增加示例</h4><p>在提示词中提供少量（通常几个或几十个）示例，也称为<strong>少样本提示（Few-Shot Prompting）</strong>，以帮助模型更好地理解任务要求和期望输出。比如：</p><ul><li>让 LLM 跟随我们所要求的规范、格式、概念、文法、语气进行输出。</li><li>提供进一步推理的参考，比如让大模型学会数学运算或按照示例方式进行逻辑推理。</li></ul><p>增加示例时可参考以下技巧：</p><ul><li><strong>精选代表性样本</strong>：选择具有代表性的示例，覆盖任务的各种情况，同时避免使用可能引起模型混淆的极端或边缘案例，确保模型能从有限的数据中学习到任务的核心特征。</li><li><strong>保证示例的多样性</strong>：尽可能覆盖任务的各种角度和情境，确保模型能从有限的数据中学习到任务的核心特征。</li><li><strong>使用相似的格式和结构</strong>：在所有示例中使用相似的提示格式和结构，使模型能够清晰识别输入与输出的关系。</li><li><strong>让大语言模型生成示例</strong>：实践时，我们还可以先让 LLM 按照提示生成一些示例，再进行筛选或人工调整，以提高示例质量和针对性。</li></ul><h4 id="分配角色"><a href="#分配角色" class="headerlink" title="分配角色"></a>分配角色</h4><p>赋予模型一个具体的角色或身份，来引导模型在特定角色视角下生成回答。</p><p>分配角色适用以下场景：</p><ul><li>需要专业知识或特定视角的问题解答。例如，模拟老师、医生、律师等回答相关领域的问题。</li><li>模拟特定人物或角色的语言风格。例如，模仿某个著名人物（如历史人物或文学角色）语言风格的文本时。</li><li>进行角色扮演游戏或创作。在创意写作或角色扮演游戏中扮演指定的角色，与使用者进行互动。</li><li>在特定行业内进行决策模拟。例如，模拟一个管理咨询师来分析商业案例或提供商业建议。</li></ul><p>分配角色引导模型生成符合特定情境和对话逻辑的内容，可遵循以下技巧：</p><ul><li><strong>明确角色身份与特性</strong><ul><li>确定角色的基本属性，如年龄、性别、职业、性格、技能、价值观等。</li><li>赋予角色相关领域的专业知识或特殊背景，如专家、学者、历史人物、虚构角色等。</li></ul></li><li><strong>设定角色目标与动机</strong><ul><li>为角色设定对话的目标，如寻求信息、说服他人、解决问题、分享观点等。</li><li>揭示角色的内在动机，如个人利益、道德信念、情感需求等，有助于塑造角色的真实性和深度。</li></ul></li><li><strong>设定角色语言风格</strong>：<ul><li>根据角色性格、教育水平、文化背景等设定其语言习惯、用词选择、句式结构、口头禅等。</li><li>规定角色在对话中的情绪状态，如冷静理智、激动愤怒、悲伤失落、幽默风趣等，影响其表达方式。</li></ul></li><li><strong>设定角色规则约束</strong>：规定角色在对话中的行为约束，如不得人身攻击、保持礼貌尊重、遵守讨论主题等。</li><li><strong>动态调整角色设定</strong>：<ul><li>随着对话深入，适时调整角色设定以适应新的情境和话题，如角色态度转变、关系演变、目标更新等。</li><li>向模型反馈角色表现，如偏离设定、缺乏个性、对话僵化等，及时修正角色设定并引导模型调整。</li></ul></li></ul><h4 id="限定输出风格-格式"><a href="#限定输出风格-格式" class="headerlink" title="限定输出风格/格式"></a>限定输出风格/格式</h4><p>为了更好的限定“<strong>风格</strong>”，准确引导模型写出符合需求的内容，下面我们介绍一些推荐的技巧：</p><ol><li><strong>明确指出所需的内容类型</strong>，如“论文/散文/诗歌/新闻报道/剧本/日记”等。</li><li><strong>用形容词限定风格</strong>，如“严谨客观”、“感性抒情”、“幽默诙谐”、“庄重典雅”等。</li><li><strong>列举风格（代表人物/作品）示例</strong>，如“仿照鲁迅先生的笔触描述社会现象”或“以J.K.罗琳的叙述风格撰写一段奇幻冒险故事”。</li><li><strong>设定语境与情感色彩</strong>：为模型设定故事背景、情感基调或角色视角，影响其语言表达和修辞选择，从而形成特定风格。如“以一名二战老兵的视角，深情回忆战场经历”。</li><li><strong>规定语言与句式特点</strong>：要求使用特定词汇、短语、成语、俚语、古语等，或强调长句、短句、排比、反问、比喻等修辞手法的运用，以契合特定风格。</li></ol><p>还可以进一步对<strong>内容的输出格式</strong>进行限定：</p><ol><li><strong>明确输出长度</strong>：如“撰写一篇关于全球气候变化的新闻报道，标题需简洁明快，概括主题，不超过300字”。</li><li><strong>段落结构</strong>：规定正文的段落数量、每段的大致内容与逻辑关系，如“文章分为引言、主体（分三点论述）和结论三部分”。</li><li><strong>列表与编号</strong>：要求使用项目符号、数字编号等形式列出要点或步骤，如“通过数字编号列出五种有效的时间管理方法，并简要解释”。</li><li><strong>引用与注释</strong>：指示何时使用引号、脚注、尾注、参考文献等格式引用他人观点或资料，如“在论述中适当引用至少两篇相关学术论文，并按照APA格式添加引用和参考文献”。</li><li><strong>格式标记</strong>：如对齐方式（左对齐、居中、右对齐）、字体样式（加粗、斜体、下划线）、缩进、行距、页眉页脚等进行详细说明，甚至可以要求大模型按照JSON格式输出。</li><li><strong>特殊要求</strong>：针对特定场景，如邮件、信函、通知、海报、简历等，规定相应的格式标准，如“按照商务电子邮件的标准格式撰写邀请函，包括收件人、抄送人、主题、问候语、正文、结束语、签名档等部分”。</li></ol><h4 id="拆解复杂任务"><a href="#拆解复杂任务" class="headerlink" title="拆解复杂任务"></a>拆解复杂任务</h4><p>把一个复杂的任务，拆解成多个稍微简单的任务，让大语言模型<strong>分步</strong>来思考问题，称为<strong>思维链（Chain-of-Thought Prompting, CoT）</strong>提示，这种方式可让大语言模型像人类一样逐步解释或进行推理，从而极大地提升 LLM 的能力。</p><p>思维链分类：</p><ul><li><p>零样本思维链（Zero-shot CoT）：没有给 LLM 提供问题解析示例。</p></li><li><p>少样本思维链（Few-shot Cot）：给 LLM 提供问题解析示例。</p><p><strong>有时 LLM 通过</strong>零样本思维链可能会得到错误的答案，可以通过增加示例的方式，即少样本思维链，帮助 LLM 理解相关任务并正确执行。</p></li></ul><h3 id="提示词框架"><a href="#提示词框架" class="headerlink" title="提示词框架"></a>提示词框架</h3><p>提示词可以包含的要素：</p><ul><li><p><strong>指令 Instruction</strong>：需要模型去做什么，如回答某个问题、撰写某种类型的文章或按照特定格式进行总结。指令应该简洁、明确，确保 LLM 能够理解任务的目标和要求。</p></li><li><p><strong>背景信息 Context</strong>：背景信息可以包括任务的背景、目的相关的各类信息，还可以为 LLM 设置角色背景、观点立场等信息，LLM 将在此背景下进行回应或生成文本。</p></li><li><p><strong>参考样本 Examples</strong>：与解决用户问题相关的示例，比如通过少样本提示的方式帮助 LLM 更好理解如何处理指令。</p></li><li><p><strong>输入数据 Input Data</strong>：用户输入指令和要求，比如用什么语气，生成多少字的内容。</p></li><li><p><strong>输出指示 Output Indicator</strong>：指定输出的类型或格式，我们可以给出限定关键词、限制条件或要求的输出格式/方式（如表格），也可以避免无关或不期望的信息出现。</p></li></ul><p>结合上述要素，我们可以根据任务的复杂度来设计提示词，具体有以下几种情况：</p><ol><li><strong>纯指令型</strong>：最直接的互动方式，仅通过简明指令向模型提出需求，适合于寻求快速、基本答案的场景。</li><li><strong>背景</strong>+<strong>指令</strong>：在指令基础上融入背景信息，为模型创造一个理解和响应任务的框架，尤其适用于需要考虑特定情境或角色定位的任务。</li><li><strong>指令</strong>/<strong>背景</strong>+<strong>输出指示</strong>：在基础指令或背景信息之上，加入输出指示，精确指导模型如何组织和呈现答案，以满足特定的格式或风格要求。</li><li><strong>综合型提示</strong>：结合指令、背景信息、输入数据与输出指示，形成一个全方位的引导体系。这种复合型提示尤为强大，能够在复杂任务中提高模型输出的针对性与质量，尤其是在模型需要从示例中学习并模仿特定风格或结构时。</li></ol><p>常见提示词框架及场景</p><div class="table-container"><table><thead><tr><th><strong>框架</strong></th><th><strong>具体应用领域（举例子）</strong></th><th><strong>内容含义</strong></th></tr></thead><tbody><tr><td><strong>CRISPE</strong></td><td>项目管理、团队协作、客户服务</td><td>能力 (Capabilities)角色 (Roles)洞察 (Insights)陈述 (Statement)个性 (Personality)实验 (Experiment)</td></tr><tr><td><strong>ROSES</strong></td><td>软件开发、产品设计、市场营销策略规划</td><td>角色 (Role)目标 (Objective)场景 (Setting)预期解决方案 (Expected Solution)步骤 (Steps)</td></tr><tr><td><strong>TRACE</strong></td><td>市场研究、业务分析、教学设计</td><td>任务 (Task)请求 (Request)操作 (Action)上下文 (Context)示例 (Examples)</td></tr></tbody></table></div><h2 id="推理模型"><a href="#推理模型" class="headerlink" title="推理模型"></a>推理模型</h2><p>推理模型通常指专门优化用于逻辑推理、问题解决、多步推断等任务的模型，它们通常在数学解题、代码生成、逻辑分析等方面有更好的表现。与通用模型相比，推理模型拥有更强的逻辑能力，因为它经过了大量的专项训练，能够更好地理解复杂的问题，并且在解答时更有条理。但并不是说就一定比通用模型更好，两种模型都有各自的应用场景，下表从一些典型维度对这两类模型进行了对比：</p><div class="table-container"><table><thead><tr><th><strong>维度</strong></th><th><strong>推理模型</strong></th><th><strong>通用模型</strong></th></tr></thead><tbody><tr><td>设计目标</td><td>专注于<strong>逻辑推理</strong>、<strong>多步问题求解</strong>、<strong>数学计算</strong>等需要深度分析的任务</td><td>面向<strong>通用对话、知识问答、文本生成</strong>等广泛场景</td></tr><tr><td>训练数据侧重</td><td>大量<strong>数学题解</strong>、<strong>代码逻辑</strong>、<strong>科学推理</strong>数据集增强推理能力</td><td>覆盖<strong>百科、文学、对话</strong>等多领域海量数据</td></tr><tr><td>典型输出特征</td><td>输出包含<strong>完整推导</strong>步骤，注重逻辑链条的完整性</td><td>输出<strong>简洁直接</strong>，侧重结果的自然语言表达</td></tr><tr><td>响应速度</td><td>复杂推理任务<strong>响应较慢</strong>（需多步计算）</td><td>常规任务<strong>响应更快</strong>（单步生成为主）</td></tr></tbody></table></div><p>推理模型还是通用模型？如何选择？以下是一些推荐：</p><ul><li><strong>明确的通用任务</strong>：对于明确定义的问题，<strong>通用模型</strong>一般能够很好地处理。</li><li><strong>复杂任务</strong>：对于非常复杂的任务，且需要给出相对<strong>更精确和可靠</strong>的答案，推荐使用<strong>推理模型</strong>。这些任务可能有：<ul><li>模糊的任务：任务相关信息很少，你无法提供模型相对明确的指引。</li><li>大海捞针：传递大量非结构化数据，提取最相关的信息或寻找关联/差别。</li><li>调试和改进代码：需要审查并进一步调试、改进大量代码。</li></ul></li><li><strong>速度和成本</strong>：一般来说推理模型的推理时间较长，如果你对于时间和成本敏感，且任务复杂度不高，<strong>通用模型</strong>可能是更好的选择。</li></ul><p>当然你还可以在你的应用中结合使用两种模型：使用推理模型完成Agent的规划和决策，使用通用模型完成任务执行。</p><p>推理模型在面对相对模糊的任务也能给出详尽且格式良好的响应。因此，使用一些提示词技巧（如指示模型“逐步思考”）可能反而限制了模型的推理。下面列举了一些适用于推理模型的提示技巧：</p><ul><li><strong>直接提问</strong>：保持提示<strong>简洁、清晰</strong>，且明确任务限制。</li><li><strong>避免思维链提示</strong>：你无需提示推理模型“逐步思考”或“解释你的推理”，它们本身会进行深入的思考。</li><li><strong>根据模型响应调整提示词</strong>：直接提问推理模型通常能够产生良好的响应，但如果你有更复杂精细的要求，可以在提示词中明确，比如有<strong>明确的输入信息和输出要求</strong>时，你可以通过<strong>增加示例</strong>明确这些信息，还可以通过<strong>分隔符</strong>帮助推理模型区分不同的信息模块。这个过程可以是重复多次的，不断尝试调整提示，让模型不断推理迭代，直到符合你的要求。</li></ul><h2 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h2><p>定义：大模型插件是一种软件组件，它们设计用于增强和扩展基础大模型的功能。如网络搜索、视觉生成、语音合成等。</p><p>典型的插件包括：</p><ul><li>基础工具，如计算器、时钟、天气信息、股票行情</li><li>可以让用户获得实时性消息的插件，比如体育赛事报道、实时、热点新闻</li><li>可以为模型拓展更多能力的插件，比如图片生成、语音合成、代码解释器</li></ul><h3 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h3><p>首先接收输入，判断是否需要使用工具及选择合适的工具，然后使用工具并获得返回结果进行后续推理生成。</p><p>用户提问-&gt; 程序根据用户问题调用大模型-&gt;大模型判断是否需要调用工具-&gt;大模型输出工具名称解析工具入参-&gt;程序根据工具名称、入参调用工具-&gt;工具输出运行结果-&gt;程序根据工具结果、用户提问重新调用大模型-&gt;大模型给出回复-&gt;程序展示结果给用户</p><h2 id="RAG"><a href="#RAG" class="headerlink" title="RAG"></a>RAG</h2><p>RAG（Retrieval-Augmented Generation，检索增强生成） 是一种结合了信息检索技术与语言生成模型的人工智能技术。该技术通过从外部知识库中检索相关信息，并将其作为提示（Prompt）输入给大型语言模型（LLMs），以增强模型处理知识密集型任务的能力，如问答、文本摘要、内容生成等。</p><ul><li>内置知识库</li><li>用户定制知识库</li></ul><h3 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h3><p><strong>检索增强生成</strong>包括三个步骤，建立<strong>索引</strong>、<strong>检索</strong>、<strong>生成</strong>。</p><p><strong>建立索引：</strong>首先要清洗和提取原始数据，将 PDF、Docx等不同格式的文件解析为<strong>纯文本</strong>数据；然后将文本数据分割成更小的片段（chunk）；最后将这些片段经过嵌入模型转换成向量数据（此过程叫做embedding），并将原始语料块和嵌入向量以键值对形式存储到向量数据库中，以便进行后续快速且频繁的搜索。这就是建立索引的过程。</p><p><strong>检索生成</strong>：系统会获取到用户输入，随后计算出用户的问题与向量数据库中的文档块之间的相似度，选择相似度最高的K个文档块（K值可以自己设置）作为回答当前问题的知识。知识与问题会合并到提示词模板中提交给大模型，大模型给出回复。这就是检索生成的过程。</p><blockquote><p>提示词模板类似于：请阅读：{知识文档块}，请问：{用户的问题}</p></blockquote><h3 id="RAG-存在的问题"><a href="#RAG-存在的问题" class="headerlink" title="RAG 存在的问题"></a>RAG 存在的问题</h3><ul><li>问题比较抽象或者概念比较模糊，导致大模型没有准确理解使用者的问题。</li><li>知识库没有检索到问题的答案</li><li>缺少对答案做兜底验证的机制</li></ul><h3 id="RAG评测标准"><a href="#RAG评测标准" class="headerlink" title="RAG评测标准"></a>RAG评测标准</h3><ul><li>考察<strong>检索</strong>模块的准确性，如准确率、召回率、F1值等等；</li><li>考察<strong>生成</strong>模块生成答案的价值，如相关性、真实性等等。</li></ul><p>参考<a href="https://docs.ragas.io/en/stable/concepts/metrics/index.html#"><strong>Ragas</strong></a>提及的评测矩阵指南，可以建立一些自己的评测指标。</p><h3 id="改造方案"><a href="#改造方案" class="headerlink" title="改造方案"></a>改造方案</h3><h4 id="提升索引准确度"><a href="#提升索引准确度" class="headerlink" title="提升索引准确度"></a>提升索引准确度</h4><ul><li><p>优化文本解析过程，正确的从文档中提取有效语料。</p></li><li><p>优化chunk切分模式</p><ul><li>利用领域专有知识进行精准切分</li><li>基于固定大小切分</li><li>上下文感知，切分时考虑前后文关系避免信息断裂</li></ul></li></ul><ul><li><p>句子滑动窗口检索，通过设置window_size（窗口大小）来调整提取句子的数量，当用户的问题匹配到一个chunk语料块时，通过窗函数提取目标语料块的上下文，而不仅仅是语料块本身，这样来获得更完整的语料上下文信息，提升RAG生成质量。</p></li><li><p>自动合并检索，将文档分块，建成一棵语料块的树，比如1024切分、512切分、128切分，并构造出一棵文档结构树。当应用作搜索时，如果同一个父节点的多个叶子节点被选中，则返回整个父节点对应的语料块。</p></li><li><p>选择更适合业务的Embedding模型</p><p>把chunk语料块由原来的文本内容转换为机器可以用于比对计算的一组数字，即变为Embedding向量。</p></li><li><p>选择更合适业务的ReRank模型，好的ReRank模型会让更贴近用户问题的chunks的排名更靠前。</p></li><li><p>Raptor用聚类为文档块建立索引，这就像通过文档的内容为文档自动建立目录的过程。</p></li></ul><h4 id="让问题更好理解"><a href="#让问题更好理解" class="headerlink" title="让问题更好理解"></a>让问题更好理解</h4><ul><li><p>Enrich完善用户问题</p><ul><li><p>多轮对话补全需求</p></li><li><p>大模型转述用户问题</p></li><li><p>让用户补全信息</p></li></ul></li></ul><ul><li><p>Multi-Query 多路召回，先一次性改写出多种用户问题，让大模型根据用户提出的问题，从多种不同角度去生成有一定提问角度或提问内容上存在差异的问题。让这些存在差异的问题作为大模型对用户真实需求的猜测，然后再把每个问题分别生成答案，并总结出最终答案。</p><p>提示词模板</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">You are an AI language model assistant. Your task is to generate five </span><br><span class="line">different versions of the given user question to retrieve relevant documents from a vector database. By generating multiple perspectives on the user question, your goal is to help the user overcome some of the limitations of the distance-based similarity search. </span><br><span class="line">Provide these alternative questions separated by newlines. Original question: &#123;question&#125;</span><br></pre></td></tr></table></figure></li><li><p>RAG-Fusion过滤融合，在经过多路召回获取了各种语料块之后，并不是将所有检索到的语料块都交给大模型，而是先进行一轮筛选，给检索到的语料块进行去重操作，然后按照与原问题的相关性进行排序，再将语料块打包喂给大模型来生成答案。</p></li><li><p>Step Back 问题摘要，让大模型先对问题进行一轮抽象，从大体上去把握用户的问题，获得一层高级思考下的语料块。</p></li><li><p>Decomposition问题分解，用户的问题拆成一个一个小问题来理解，或者可以说是RAG中的CoT。</p><p>提示词</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">You are a helpful assistant that generates multiple sub-questions related to an input question. \n</span><br><span class="line">The goal is to break down the input into a set of sub-problems / sub-questions that can be answers in isolation. \n</span><br><span class="line">Generate multiple search queries related to: &#123;question&#125; \n</span><br><span class="line">Output (3 queries):</span><br></pre></td></tr></table></figure><ul><li>串行执行</li><li>并行执行</li></ul></li><li><p>HyDE假设答案，让大模型先来根据用户的问题生成一段假设答案，然后用这段假设的答案作为新的问题去文档库里匹配新的文档块，再进行总结，生成最终答案。</p><p>用户提问-&gt;生成答案-&gt;信息抽离-&gt;生成答案</p></li></ul><h4 id="改造信息抽取途径"><a href="#改造信息抽取途径" class="headerlink" title="改造信息抽取途径"></a>改造信息抽取途径</h4><p>   <strong>Corrective Retrieval Augmented Generation (CRAG)</strong>是一种改善提取信息质量的策略：如果通过知识库检索得到的信息与用户问题相关性太低，我们就主动搜索互联网，将网络搜索到的信息与知识库搜索到的信息合并，再让大模型进行整理给出最终答案。</p><p>   两种实现方式</p><ul><li><strong>向量相似度</strong>，我们用检索信息得到的向量相似度分来判断。判断每个语料块与用户问题的相似度评分，是否高过某个阈值，如果搜索到的语料块与用户问题的相似度都比较低，就代表知识库中的信息与用户问题不太相关；</li></ul><ul><li><strong>直接问大模型</strong>，我们可以先将知识库检索到的信息交给大模型，让大模型自主判断，这些资料是否能回答用户的问题。</li></ul><h4 id="回答前反复思考"><a href="#回答前反复思考" class="headerlink" title="回答前反复思考"></a>回答前反复思考</h4><p>Self-RAG，也称为self-reflection，是一种通过在应用中设计<strong>反馈路径</strong>实现<strong>自我反思</strong>的策略。<br>   基于这个思想，我们可以让应用问自己三个问题：</p><ul><li><p><strong>相关性</strong>：我获取的这些材料和问题相关吗？ 在生成答案前判断，不相关就改写问题。</p></li><li><p><strong>无幻觉</strong>：我的答案是不是按照材料写的来讲，还是我自己编造的？在生成答案后判断，出现幻觉就重新生成答案。</p></li><li><p><strong>已解答</strong>：我的答案是不是解答了问题？在输出答案前判断，没有解答问题就改写问题。</p></li></ul><h4 id="从多个数据源中获取资料"><a href="#从多个数据源中获取资料" class="headerlink" title="从多个数据源中获取资料"></a>从多个数据源中获取资料</h4><ul><li>从数据库中获取统计指标，大模型可以将用户问题转化为SQL语句去数据库中检索相关信息，这个能力就是NL2SQL（Natural Language to SQL）。</li><li>从知识图谱中获取数据，Neo4j是一款图数据库引擎，可以为我们提供知识图谱构建和计算服务。在知识图谱上，我们调用各种图分析算法，如标签传播、关键节点发现等等，可以快速检索多度关联关系，挖掘隐藏关系。</li></ul><h2 id="微调"><a href="#微调" class="headerlink" title="微调"></a>微调</h2><p>核心思想：在预训练的基础上，使用特定领域的数据对模型进行进一步的训练，从而让模型更擅长处理你想要解决的问题，也就是说，<strong>让大模型更懂你</strong>。</p><h3 id="微调的特点"><a href="#微调的特点" class="headerlink" title="微调的特点"></a>微调的特点</h3><ol><li>数据规模小</li><li>训练时间短</li></ol><h3 id="微调可以实现"><a href="#微调可以实现" class="headerlink" title="微调可以实现"></a>微调可以实现</h3><ul><li>风格化，角色扮演</li><li>格式化，系统对接</li></ul><h3 id="微调的作用"><a href="#微调的作用" class="headerlink" title="微调的作用"></a>微调的作用</h3><p>提高效率降低成本</p><h3 id="微调的关键"><a href="#微调的关键" class="headerlink" title="微调的关键"></a>微调的关键</h3><ul><li>需要特定领域的高质量数据</li><li>需要配置合适的参数才能达到想要的微调效果</li></ul><h3 id="微调的使用场景"><a href="#微调的使用场景" class="headerlink" title="微调的使用场景"></a>微调的使用场景</h3><p>如果你的部署环境里无法提供知识检索服务，或者你单纯希望大模型记住这些重要的信息。你可以考虑使用微调，为大模型注入专有领域的知识库，确保大模型的回答会更准确、更高效。</p><h3 id="业务决策"><a href="#业务决策" class="headerlink" title="业务决策"></a>业务决策</h3><ul><li><p>业务需求匹配度</p><p>首先明确业务的具体需求，明确要通过大模型微调解决的具体业务问题和应用场景。可以先问自己以下问题：</p><ul><li>你的任务是否需要复杂的语言理解或生成能力？例如，复杂的自然语言生成、意图识别等任务可能受益于大模型微调。</li><li>你的任务是否需要高度特定于某个领域或任务的语言能力？例如，法律文书分析、医学文本理解等领域任务。</li><li>当前的模型是否已能满足大部分需求？如果能满足，则可能不需要微调。</li><li>是否有具体的业务指标来衡量微调前后效果对比？比如微调后的大模型推送给客户的信息更加准确，从而降低投诉率。</li></ul></li><li><p>数据可用性与质量</p><p>微调需要足够的高质量领域特定数据。需要评估当前业务系统中是否能够提取出足够的标注数据用于训练，以及数据的质量、代表性是否满足要求。</p></li><li><p>合规与隐私</p><p>业务工作者需要确保使用的数据符合法律法规要求，处理个人数据时遵循隐私保护原则，尤其是GDPR等国际和地区隐私法规。此外，还要关注模型偏见、过拟合、泛化能力不足等潜在风险，以及这些风险对业务可能造成的影响。</p></li><li><p>资源和技术可行性</p><p>微调过程需要计算资源和时间成本，包括GPU资源、存储空间以及可能的专家人力成本。需要评估项目预算和资源是否允许进行有效微调。</p><p>团队是否具备微调大模型所需的技术能力和经验，或者是否有合适的合作伙伴提供技术支持。</p></li></ul><h3 id="微调流程"><a href="#微调流程" class="headerlink" title="微调流程"></a>微调流程</h3><ol><li>准备数据<ul><li>数据收集：收集用于微调模型的数据，例如之前的交互记录、常见问题及回答等。</li><li>数据清洗：清洗这些数据，去除敏感信息，保证数据的质量。</li></ul></li><li>模型选择<ul><li>选择适合的预训练大模型。准备一个或多个有使用权限的通用大模型。</li></ul></li><li>模型微调<ul><li>使用你收集的数据来微调所选模型。</li><li>标注数据：如果需要，对数据进行标注以支持监督学习。</li><li>微调模型：运行微调程序，调整模型的参数使其更适应你的业务数据。</li></ul></li><li>模型评估<ul><li>你可以在准备训练集的同时，准备一份与训练集格式一致的评测集，该评测集用来评测微调后模型的效果。如果评测集数据条目较少，你可以直接去观察微调后模型的输出，并与评测集的<strong>output</strong>进行比较。</li></ul></li><li>模型集成<ul><li>编写代码将模型调用集成到现有的业务流程中去。</li><li>设定合理的调用逻辑和流程，如何处理模型的输出，如何处理API调用错误等。</li><li>确保API调用遵守数据保护法规。</li><li>实施适当的身份验证和授权控制。</li></ul></li><li>测试与优化<ul><li>在开发和测试环境中测试API调用和模型集成。</li><li>根据测试结果优化模型性能。</li></ul></li><li>模型部署<ul><li>在生产环境中部署微调后的模型。</li></ul></li><li>监控与维护<ul><li>监控模型的性能和API的健康状况。</li><li>定期重新评估和优化模型以适应新数据和业务变化。</li></ul></li><li>持续迭代<ul><li>根据用户反馈和业务需求的变化，继续改进模型和API。</li></ul></li></ol><p>在进行这一切工作时，保持良好的文档习惯也至关重要，这样团队成员可以轻松跟踪项目的进展，新加入的成员也能够快速上手。此外，确保所有与微调和部署模型相关的关键决策都得到适当记录和备份。</p><h3 id="微调的方式"><a href="#微调的方式" class="headerlink" title="微调的方式"></a>微调的方式</h3><p>大模型由多个子模块组成</p><p><strong>全参微调（Full Fine Tuning）</strong>是在预训练模型的基础上进行<strong>全量参数</strong>微调的模型优化方法，只要有参数，就会被调整。该方法避免消耗重新开始训练模型所有参数所需的大量计算资源，又能避免部分参数未被微调导致模型性能下降。但是，大模型训练成本高昂，需要庞大的计算资源和大量的数据，即使是全参数微调，往往也需要较高的训练成本。</p><p><strong>高效微调 PEFT</strong>（Parameter-Efficient Fine-Tuning）旨在降低微调参数的数量和计算复杂度的同时提高预训练模型在新任务上的性能，缓解微调大型预训练模型的训练成本，只需要调整某几层网络的参数即可。当前比较主流的 PEFT 方法包括 Adapter Tuning、Prompt Tuning、LoRA 等等。</p><ul><li>LoRA 的全称是 “Low-Rank Adaptation”（低秩适应）。它不对原模型做微调，而是在原始模型旁边增加一个旁路，通过学习小参数的低秩矩阵来近似模型权重矩阵的参数更新，训练时只优化低秩矩阵参数，通过降维度再升维度的操作来大量压缩需要训练的参数。</li><li>Adapter Tuning，该方法会在原有的模型架构上，在某些位置之间插入Adapter层，微调训练时只训练这些Adapter层，而原先的参数不会参与训练。该方法可以在只额外增加3.6%参数的情况下达到与全参数微调相似的效果。</li><li>Prefix Tuning，该方法不改变原有模型的结构，而是给文本数据的token之前构造一段固定长度任务相关的虚拟token作为前缀Prefix。训练的时候只更新这个虚拟token部分的参数，而其他部分参数固定。这些前缀向量可以视为对模型进行引导的“指令”，帮助模型理解即将处理的任务。由于只需学习远少于模型全部参数数量的前缀向量，这种方法极大地减少了内存占用和计算成本。</li><li>Prompt Tuning，一种轻量高效的微调方法，其原理与Prefix Tuning类似，但仅在输入层加入可训练的prompt tokens向量（而非像Prefix Tuning那样作用于每层Transformer）来引导模型输出。这些提示词向量并不一定放在问题的前边，也可以针对不同的任务设计不同的提示词向量。微调训练只对这些前缀提示词向量进行更新，从而<strong>不修改大模型本身的参数</strong>，大幅减少训练参数。</li></ul><h3 id="RAG-v-s-微调"><a href="#RAG-v-s-微调" class="headerlink" title="RAG v.s. 微调"></a>RAG v.s. 微调</h3><div class="table-container"><table><thead><tr><th><strong>对比项</strong></th><th><strong>RAG</strong></th><th><strong>微调</strong></th></tr></thead><tbody><tr><td>应用场景</td><td>解决垂直领域知识问答的“幻觉”问题</td><td>解决风格化生成和垂域系统指令生成问题</td></tr><tr><td>技术能力要求</td><td>需要了解如何使用向量数据库</td><td>需要有较强的数据预处理能力需要熟悉微调的参数体系和调整策略</td></tr><tr><td>成本比较</td><td>部署和运维线上推理服务与向量数据库</td><td>微调训练成本，部署运维成本，需要专业技术人员操作</td></tr><tr><td>数据集准备</td><td>准备一份知识库文档即可</td><td>需要在知识库文档里提取高质量问答对</td></tr><tr><td>最终输入大模型的token个数</td><td>需要将知识与问题拼接起来，输入大模型的tokens个数较多</td><td>只需输入问题即可，输入大模型的tokens个数很少</td></tr><tr><td>推理速度</td><td>经过检索、生成两个步骤，且向大模型输入较多token，推理速度相对较慢</td><td>直接推理，速度较快</td></tr><tr><td>知识更新的及时性</td><td>可以做到准实时，只需要及时地更新知识库中的文档</td><td>比RAG慢，微调训练需要花费较多时间</td></tr></tbody></table></div><h2 id="Agent"><a href="#Agent" class="headerlink" title="Agent"></a>Agent</h2><p>定义：从软件工程的角度来看，大模型Agent是指基于大语言模型的，能使用工具与外部世界进行交互的计算机程序。</p><h3 id="Agent的工作流程"><a href="#Agent的工作流程" class="headerlink" title="Agent的工作流程"></a>Agent的工作流程</h3><ol><li>感知</li><li>思考</li><li>行动</li></ol><h3 id="Agent的能力"><a href="#Agent的能力" class="headerlink" title="Agent的能力"></a>Agent的能力</h3><ul><li><p>记忆能力</p><ul><li>短期记忆，对应着用户与大模型Agent之间的历史对话、提示词、搜索工具反馈的语料块等等，但这些信息仅与当前用户与大模型的对话内容有关。一旦用户关闭应用或离开聊天环境，这类信息就消失了。</li><li>长期记忆，对应着系统持久化的信息，如业务历史记录、知识库等，通常存储在外部向量数据库和文档库中，Agent会利用长期记忆来回答用户私有知识或专业领域相关的问题。</li></ul></li><li><p>规划能力</p><ul><li><p>任务分解</p><ul><li>思维链</li><li>思维树</li><li>思维图</li></ul></li><li><p>推理与反思，让大模型先审视自己的结果，然后再输出结果。</p><ul><li><p>ReAct，现原理是把思考、推理、观察整合到提示词模版中，让大模型在具体工作中既可以基于环境反馈选择使用何种工具来完成工作，又可以基于环境反馈推理是否可以给出任务的答案。</p><p>大模型在ReAct提示词的引导下完成推理、行动、再推理、再行动，直至生成最终答案。</p><ul><li>当大模型接收到用户请求的时候，大模型先判断能不能解答用户的问题。</li><li>当大模型不能解答用户的问题时，大模型会根据可用工具列表的介绍，选择一个合适的工具。</li><li>大模型根据工具的使用说明生成调用指令，并在指令中插入需要的参数。</li><li>Agent主体程序会根据大模型生成的指令来调用对应的工具服务</li><li>Agent主体程序等待工具执行完成并收集执行结果。</li><li>带着用户问题和执行结果，大模型会再次考虑是否已经解决了问题。如果没有解决，就继续选择一个工具来执行。如果解决了，大模型会生成最终答案，并结束任务。</li></ul><p>提示词模板</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">input_variables=[&#x27;agent_scratchpad&#x27;, &#x27;input&#x27;, &#x27;tool_names&#x27;, &#x27;tools&#x27;] </span><br><span class="line">template=&#x27;Answer the following questions as best you can. You have access to the following tools:</span><br><span class="line"></span><br><span class="line">&#123;tools&#125;</span><br><span class="line"></span><br><span class="line">Use the following format:</span><br><span class="line"></span><br><span class="line">Question: the input question you must answer</span><br><span class="line">Thought: you should always think about what to do</span><br><span class="line">Action: the action to take, should be one of [&#123;tool_names&#125;]</span><br><span class="line">Action Input: the input to the action</span><br><span class="line">Observation: the result of the action</span><br><span class="line">... (this Thought/Action/Action Input/Observation can repeat N times)</span><br><span class="line">Thought: I now know the final answer</span><br><span class="line">Final Answer: the final answer to the original input question</span><br><span class="line"></span><br><span class="line">Begin!</span><br><span class="line"></span><br><span class="line">Question: &#123;input&#125;</span><br><span class="line">Thought:&#123;agent_scratchpad&#125;&#x27;</span><br></pre></td></tr></table></figure></li></ul></li></ul></li></ul><h3 id="Agent与大模型的区别"><a href="#Agent与大模型的区别" class="headerlink" title="Agent与大模型的区别"></a>Agent与大模型的区别</h3><ol><li>它不局限于输出回答，还能通过插件（工具）<strong>与外部世界交互</strong>，论上只要是计算机程序能做的事情，它都能做到。</li><li>它不再是被动式地接受多轮提问，而是能<strong>自主地推理</strong>（拆解任务、选择最优路径）、<strong>主动纠错、自主完成任务</strong>。你可以让它每完成一个或多个步骤就给你同步进展，和你确认下一步的动作，也可以授权它自主地完成所有步骤。</li><li>它不仅可以完成简单的事情，还能<strong>完成复杂的任务</strong>。</li><li>它可以自我迭代，<strong>吸取历史经验</strong>，不断成长，因为它不仅能记住这次会话里你对它的指导，还能记住以前的会话里你给它提过的要求。</li><li>它不仅能完成通用的任务，还能<strong>完成特定领域的任务</strong>，因为它可以接入特定领域的外部知识库和工具。</li></ol><h3 id="Multi-Agent"><a href="#Multi-Agent" class="headerlink" title="Multi-Agent"></a>Multi-Agent</h3><p>通过给每个Agent制定明确且专业的角色名称和职责描述，不仅可以提升它们的专业性，还能帮助它们更好地理解和配合各自的工作。</p><p>Agent之间可以互相指派任务，也可以是一个主Agent带着一系列从Agent协同工作。</p><h2 id="多模态大模型"><a href="#多模态大模型" class="headerlink" title="多模态大模型"></a>多模态大模型</h2><p>每一种信息的来源或者形式，都可以称为一种模态（Modality）。</p><p>在大模型领域，模态指的是数据或信息的类型或表达形式，文本、图像、视频、音频等都是不同的模态。多模态，顾名思义，就是指结合两种及以上的模态，进行更综合的数据处理和分析。</p><h3 id="混合专家模型（MoE）"><a href="#混合专家模型（MoE）" class="headerlink" title="混合专家模型（MoE）"></a>混合专家模型（MoE）</h3><p>混合专家模型的核心思想就是术有专攻，类似“专家会诊”，由多个不同领域的模型（即“专家”）组合成一个模型，分别去解决不同领域的问题。</p><ul><li>Experts（专家网络）：每个专家网络是用来处理某一特定类型问题或数据子集的独立模型，一般都在他们各自的专长上受过训练，推理时只有部分专家网络参与计算。</li><li>GateNet（门控网络）：类似于“交通指挥官”，负责评估输入数据，并决定由哪些专家参与处理当前的问题，尽可能地利用每个专家的专业知识来提供最准确的预测或决策输出。</li></ul><p>局限性：</p><ul><li><strong>计算资源需求高</strong>：MoE模型中的每个专家都是一个独立的模型，当专家数量增加时，模型的总参数量也相应增加，导致部署模型可能需要更多的GPU显存资源。</li><li><strong>过拟合风险</strong>：由于参数量大和复杂性，可能比简单模型更容易过拟合训练数据，特别是当数据量不足时。</li></ul><h3 id="大小模型云端协同"><a href="#大小模型云端协同" class="headerlink" title="大小模型云端协同"></a>大小模型云端协同</h3><p><strong>将大模型部署在云端，向边、端的小模型输出模型能力，小模型负责实时数据处理和初步推理，并向大模型反馈算法与执行成效，</strong>这样既能在云端充分发挥大模型的推理训练能力，又能调动边、端的小模型的敏捷性，可实现合理分配计算资源、提高响应速度。</p><h3 id="Agent与多模态"><a href="#Agent与多模态" class="headerlink" title="Agent与多模态"></a>Agent与多模态</h3><ul><li><strong>Agent</strong>概念通常指的是可以通过组件或工具与外界环境进行交互的大模型应用。它能够扩展模型的功能，比如增加特定领域的知识、优化交互流程或实现定制化逻辑，使模型更加灵活高效地应用于实际场景。</li><li><strong>多模态</strong>指的是模型能够处理和整合多种类型的数据，如文本、图像、声音等。在大模型领域，多模态技术的融合旨在让模型具备跨媒体理解和生成能力，这对于提升模型在复杂应用场景中的表现至关重要，如图文并茂的问答系统、视频内容分析等。</li></ul>]]></content>
    
    
    <summary type="html">阿里云大模型工程师ACA认证学习笔记</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=a6fa29ec-e0d8-ccda-e1fd-2c6a697d35fc" type="image"/>
    
    
    <category term="学习笔记" scheme="https://blog.itrf.cn/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="大模型" scheme="https://blog.itrf.cn/tags/%E5%A4%A7%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>学习NAT类型与实战各类型打洞可行性</title>
    <link href="https://blog.itrf.cn/posts/f77d/"/>
    <id>https://blog.itrf.cn/posts/f77d/</id>
    <published>2026-01-02T07:03:23.000Z</published>
    <updated>2026-09-03T07:47:01.180Z</updated>
    
    <content type="html"><![CDATA[<p>参考：<a href="https://tailscale.com/blog/how-nat-traversal-works">https://tailscale.com/blog/how-nat-traversal-works</a></p><h2 id="一、相关工具（含使用场景）"><a href="#一、相关工具（含使用场景）" class="headerlink" title="一、相关工具（含使用场景）"></a>一、相关工具（含使用场景）</h2><ul><li>在线检测NAT类型：<a href="https://natchecker.com">NAT Checker</a>（核心功能：检测映射/过滤行为，精准判定NAT1-9类型，建议打洞前必测）</li><li>通用打洞工具：<a href="https://tailscale.com/">Tailscale</a>（适配EasyNAT全场景，HardNAT需依赖UPnP，操作简单，适合新手）</li><li>复杂NAT适配工具：<a href="https://easytier.cn/">Easytier</a>（支持端口扫描/预测，可尝试HardNAT组合，适合进阶用户）</li><li>辅助排查工具：Wireshark（抓包分析流量走向，定位打洞失败原因，如端口过滤、映射端口变化）</li></ul><h2 id="二、必备知识（打洞原理基础）"><a href="#二、必备知识（打洞原理基础）" class="headerlink" title="二、必备知识（打洞原理基础）"></a>二、必备知识（打洞原理基础）</h2><p>路由器通过两张核心表实现NAT管控，这是打洞能否成功的关键：</p><ol><li><strong>NAT映射表</strong>：记录内部主机（内网IP:端口）与公网（公网IP:端口）的映射关系，仅当内部主机向外发起请求时生成/更新；</li><li><strong>状态表</strong>：记录内部主机的对外请求状态（如请求目标IP:端口、连接状态），仅当外部流量与状态表匹配时，才允许穿透NAT进入内网。</li></ol><p>打洞的核心逻辑：通过中转服务器交换双方公网映射信息，再主动发起请求触发对方状态表更新，让后续流量能被放行。</p><h3 id="2-1-NAT映射行为（决定公网端口是否固定）"><a href="#2-1-NAT映射行为（决定公网端口是否固定）" class="headerlink" title="2.1 NAT映射行为（决定公网端口是否固定）"></a>2.1 NAT映射行为（决定公网端口是否固定）</h3><ul><li><strong>地址和端口相关映射</strong>（HardNAT特征）：仅当请求的「目标IP+目标端口」与历史请求完全一致时，才复用原公网映射端口；只要有一项不同，就生成新的公网端口（如内网主机先访问中转服务器A:8080，再访问主机B:80，会生成两个不同公网端口）。</li><li><strong>地址相关映射</strong>（HardNAT特征）：只要请求的「目标IP」与历史一致，即使目标端口不同，也复用原公网端口；若目标IP不同，则生成新端口（如内网主机访问A:8080和A:80，共用一个公网端口；访问A:8080和B:80，生成两个端口）。</li><li><strong>端点无关映射</strong>（EasyNAT特征）：无论请求的目标IP、目标端口如何变化，始终复用同一个公网映射端口（如内网主机访问A:8080、B:80、C:443，均使用同一个公网端口）。</li></ul><h3 id="2-2-NAT过滤行为（决定外部流量能否放行）"><a href="#2-2-NAT过滤行为（决定外部流量能否放行）" class="headerlink" title="2.2 NAT过滤行为（决定外部流量能否放行）"></a>2.2 NAT过滤行为（决定外部流量能否放行）</h3><ul><li><strong>地址和端口相关过滤</strong>：仅当外部响应的「源IP+源端口」与内部主机之前的请求目标完全一致时，才放行流量；否则直接过滤。</li><li><strong>地址相关过滤</strong>：只要外部响应的「源IP」与内部请求的目标IP一致，即使源端口不同，也放行流量；源IP不同则过滤。</li><li><strong>端点无关过滤</strong>：无论外部响应的源IP、源端口是什么，均直接放行（最宽松的过滤规则）。</li></ul><h3 id="2-3-NAT类型对照表（含打洞难度标注）"><a href="#2-3-NAT类型对照表（含打洞难度标注）" class="headerlink" title="2.3 NAT类型对照表（含打洞难度标注）"></a>2.3 NAT类型对照表（含打洞难度标注）</h3><div class="table-container"><table><thead><tr><th style="text-align:left">NAT类型</th><th style="text-align:left">RFC 5780映射方式</th><th style="text-align:left">RFC 5780过滤方式</th><th style="text-align:left">RFC 3489对应类型</th><th style="text-align:left">分类</th><th style="text-align:left">打洞难度</th></tr></thead><tbody><tr><td style="text-align:left">NAT Type 1</td><td style="text-align:left">端点无关映射</td><td style="text-align:left">端点无关过滤</td><td style="text-align:left">全锥型（Full Cone）</td><td style="text-align:left">EasyNAT</td><td style="text-align:left">⭐ 极低</td></tr><tr><td style="text-align:left">NAT Type 2</td><td style="text-align:left">端点无关映射</td><td style="text-align:left">地址相关过滤</td><td style="text-align:left">受限锥型（Restricted Cone）</td><td style="text-align:left">EasyNAT</td><td style="text-align:left">⭐ 低</td></tr><tr><td style="text-align:left">NAT Type 3</td><td style="text-align:left">端点无关映射</td><td style="text-align:left">地址和端口相关过滤</td><td style="text-align:left">端口受限锥型（Port Restricted Cone）</td><td style="text-align:left">EasyNAT</td><td style="text-align:left">⭐⭐ 中低</td></tr><tr><td style="text-align:left">NAT Type 4</td><td style="text-align:left">地址相关映射</td><td style="text-align:left">端点无关过滤</td><td style="text-align:left">-</td><td style="text-align:left">HardNAT</td><td style="text-align:left">⭐⭐⭐⭐ 高</td></tr><tr><td style="text-align:left">NAT Type 5</td><td style="text-align:left">地址相关映射</td><td style="text-align:left">地址相关过滤</td><td style="text-align:left">-</td><td style="text-align:left">HardNAT</td><td style="text-align:left">⭐⭐⭐⭐ 高</td></tr><tr><td style="text-align:left">NAT Type 6</td><td style="text-align:left">地址相关映射</td><td style="text-align:left">地址和端口相关过滤</td><td style="text-align:left">-</td><td style="text-align:left">HardNAT</td><td style="text-align:left">⭐⭐⭐⭐⭐ 极高</td></tr><tr><td style="text-align:left">NAT Type 7</td><td style="text-align:left">地址和端口相关映射</td><td style="text-align:left">端点无关过滤</td><td style="text-align:left">-</td><td style="text-align:left">HardNAT</td><td style="text-align:left">⭐⭐⭐⭐ 高</td></tr><tr><td style="text-align:left">NAT Type 8</td><td style="text-align:left">地址和端口相关映射</td><td style="text-align:left">地址相关过滤</td><td style="text-align:left">-</td><td style="text-align:left">HardNAT</td><td style="text-align:left">⭐⭐⭐⭐⭐ 极高</td></tr><tr><td style="text-align:left">NAT Type 9</td><td style="text-align:left">地址和端口相关映射</td><td style="text-align:left">地址和端口相关过滤</td><td style="text-align:left">对称型（Symmetric）</td><td style="text-align:left">HardNAT</td><td style="text-align:left">⭐⭐⭐⭐⭐ 极高</td></tr></tbody></table></div><h2 id="三、各NAT类型打洞可行性（详细拆解）"><a href="#三、各NAT类型打洞可行性（详细拆解）" class="headerlink" title="三、各NAT类型打洞可行性（详细拆解）"></a>三、各NAT类型打洞可行性（详细拆解）</h2><p>前置操作：双方均通过NAT Checker确认自身NAT类型，安装Tailscale（基础场景）或Easytier（复杂场景），确保双方网络能访问中转服务器（Tailscale/Easytier官方服务器）。</p><h3 id="3-1-NAT1（全锥型）-↔-NAT1（全锥型）"><a href="#3-1-NAT1（全锥型）-↔-NAT1（全锥型）" class="headerlink" title="3.1 NAT1（全锥型） ↔ NAT1（全锥型）"></a>3.1 NAT1（全锥型） ↔ NAT1（全锥型）</h3><h4 id="核心原理"><a href="#核心原理" class="headerlink" title="核心原理"></a>核心原理</h4><p>双方均为端点无关映射（公网端口固定）+ 端点无关过滤（任意外部流量放行），无需触发状态表更新，交换公网信息后可直接通信。</p><h4 id="实战步骤"><a href="#实战步骤" class="headerlink" title="实战步骤"></a>实战步骤</h4><ol><li>双方启动Tailscale，自动连接中转服务器，完成身份认证；</li><li>中转服务器同步双方的公网IP和映射端口（因端点无关映射，此端口固定不变）；</li><li>双方获取对方公网信息后，直接向对方的「公网IP:映射端口」发起TCP/UDP连接；</li><li>由于双方过滤规则宽松，流量直接放行，打洞成功，后续通信脱离中转服务器。</li></ol><h4 id="可行性结论"><a href="#可行性结论" class="headerlink" title="可行性结论"></a>可行性结论</h4><p>✅ 100%稳定可行，无特殊条件限制，是打洞最理想的场景。</p><h4 id="问题排查"><a href="#问题排查" class="headerlink" title="问题排查"></a>问题排查</h4><p>若失败，优先排查：① 防火墙是否拦截对应端口（如Tailscale默认使用41641端口，需放行）；② 公网IP是否为运营商内网IP（极少数情况，可通过IP138查询公网IP是否一致）。</p><h3 id="3-2-NAT1（全锥型）-↔-NAT3（端口受限锥型）"><a href="#3-2-NAT1（全锥型）-↔-NAT3（端口受限锥型）" class="headerlink" title="3.2 NAT1（全锥型） ↔ NAT3（端口受限锥型）"></a>3.2 NAT1（全锥型） ↔ NAT3（端口受限锥型）</h3><h4 id="核心原理-1"><a href="#核心原理-1" class="headerlink" title="核心原理"></a>核心原理</h4><p>NAT1公网端口固定+过滤宽松，NAT3公网端口固定但过滤严格（仅放行「之前请求过的IP+端口」的响应）。关键是让NAT3先向NAT1发起请求，更新NAT3的状态表，从而放行NAT1的后续请求。</p><h4 id="实战步骤-1"><a href="#实战步骤-1" class="headerlink" title="实战步骤"></a>实战步骤</h4><ol><li>双方启动Tailscale，连接中转服务器，同步对方公网IP:端口（记为NAT1：A:PortA；NAT3：B:PortB）；</li><li>关键操作：让NAT3主机主动向NAT1的A:PortA发起请求（Tailscale会自动触发此步骤，无需手动操作）；</li><li>NAT3的状态表更新：记录「请求目标A:PortA」，此时允许A:PortA的流量进入内网；</li><li>NAT1主机向NAT3的B:PortB发起请求，由于NAT3状态表已记录A:PortA，此请求被放行；</li><li>双方建立双向连接，打洞成功。</li></ol><h4 id="可行性结论-1"><a href="#可行性结论-1" class="headerlink" title="可行性结论"></a>可行性结论</h4><p>✅ 可行性95%以上，核心依赖「NAT3先发起请求」的顺序，Tailscale等工具会自动协调此顺序，无需手动干预。</p><h4 id="问题排查-1"><a href="#问题排查-1" class="headerlink" title="问题排查"></a>问题排查</h4><p>若失败，排查：① NAT3主机是否被路由器限制“主动对外发起连接”；② 双方是否同时启动工具（若NAT1先发起请求，会被NAT3过滤，可重启双方Tailscale重试）。</p><h3 id="3-3-NAT3（端口受限锥型）-↔-NAT3（端口受限锥型）"><a href="#3-3-NAT3（端口受限锥型）-↔-NAT3（端口受限锥型）" class="headerlink" title="3.3 NAT3（端口受限锥型） ↔ NAT3（端口受限锥型）"></a>3.3 NAT3（端口受限锥型） ↔ NAT3（端口受限锥型）</h3><h4 id="核心原理-2"><a href="#核心原理-2" class="headerlink" title="核心原理"></a>核心原理</h4><p>双方均为“端口固定+地址和端口相关过滤”，仅放行「自身之前请求过的IP:端口」的流量。需让双方在极短时间内同时向对方发起请求，互相更新对方的状态表，实现流量双向放行。</p><h4 id="实战步骤-2"><a href="#实战步骤-2" class="headerlink" title="实战步骤"></a>实战步骤</h4><ol><li>双方启动Tailscale，连接中转服务器，同步对方公网IP:端口（NAT3-1：C:PortC；NAT3-2：D:PortD）；</li><li>工具协调：Tailscale会通过中转服务器向双方发送“同步发起请求”的指令，确保双方在1秒内同时行动；</li><li>双方同时向对方公网地址发起请求：NAT3-1→D:PortD；NAT3-2→C:PortC；</li><li>双方状态表同时更新：NAT3-1记录D:PortD，允许其流量进入；NAT3-2记录C:PortC，允许其流量进入；</li><li>双方请求均被对方放行，建立双向连接，打洞成功。</li></ol><h4 id="可行性结论-2"><a href="#可行性结论-2" class="headerlink" title="可行性结论"></a>可行性结论</h4><p>✅ 可行性90%以上，核心依赖「双方同时发起请求」的时间同步。若网络延迟过高（如跨运营商、跨地域），同步难度增加，成功率会略降。</p><h4 id="问题排查-2"><a href="#问题排查-2" class="headerlink" title="问题排查"></a>问题排查</h4><p>若失败，排查：① 双方网络延迟是否过高（可通过ping中转服务器判断，延迟&gt;200ms建议换同运营商网络）；② 工具是否未正常协调同步请求（重启工具或切换网络重试）。</p><h3 id="3-4-NAT1（全锥型）-↔-NAT4（地址相关映射-端点无关过滤）"><a href="#3-4-NAT1（全锥型）-↔-NAT4（地址相关映射-端点无关过滤）" class="headerlink" title="3.4 NAT1（全锥型） ↔ NAT4（地址相关映射+端点无关过滤）"></a>3.4 NAT1（全锥型） ↔ NAT4（地址相关映射+端点无关过滤）</h3><h4 id="核心原理-3"><a href="#核心原理-3" class="headerlink" title="核心原理"></a>核心原理</h4><p>NAT1公网端口固定，NAT4为HardNAT（映射端口随目标IP变化）：NAT4向中转服务器发起请求时，生成映射端口Port4-1；当NAT4向NAT1发起请求时，因目标IP变化，会生成新的映射端口Port4-2（与Port4-1不同）。打洞关键是让NAT1识别到NAT4的新端口Port4-2，重新发起请求。</p><h4 id="实战步骤-3"><a href="#实战步骤-3" class="headerlink" title="实战步骤"></a>实战步骤</h4><ol><li>双方启动Tailscale，连接中转服务器，同步的NAT4公网信息为「E:Port4-1」（NAT4与中转服务器通信的端口）；</li><li>NAT1先向E:Port4-1发起请求，因Port4-1仅对应“NAT4→中转服务器”的状态，此请求被NAT4的映射表匹配失败，直接过滤；</li><li>中转服务器触发NAT4向NAT1的A:PortA发起请求，此时NAT4因目标IP变为A，生成新映射端口Port4-2；</li><li>NAT1接收到NAT4的请求（源地址E:Port4-2），自动更新NAT4的公网信息为「E:Port4-2」；</li><li>NAT1向E:Port4-2重新发起请求，此时NAT4的状态表已记录「请求目标A:PortA」，且Port4-2为当前映射端口，请求被放行；</li><li>双方建立连接，打洞成功。</li></ol><h4 id="可行性结论-3"><a href="#可行性结论-3" class="headerlink" title="可行性结论"></a>可行性结论</h4><p>⚠️ 可行性70%左右，需依赖工具支持“动态端口识别与二次请求”（Tailscale、Easytier均支持）。若NAT4的映射端口变化频繁（如部分运营商每30秒刷新一次），可能导致连接不稳定。</p><h4 id="问题排查-3"><a href="#问题排查-3" class="headerlink" title="问题排查"></a>问题排查</h4><p>若失败，排查：① 工具是否支持动态端口更新（老旧版本可能不支持，需升级）；② NAT4的映射端口刷新周期是否过短（可通过Wireshark抓包观察端口变化频率）。</p><h3 id="3-5-NAT3（端口受限锥型）-↔-NAT4（地址相关映射-端点无关过滤）"><a href="#3-5-NAT3（端口受限锥型）-↔-NAT4（地址相关映射-端点无关过滤）" class="headerlink" title="3.5 NAT3（端口受限锥型） ↔ NAT4（地址相关映射+端点无关过滤）"></a>3.5 NAT3（端口受限锥型） ↔ NAT4（地址相关映射+端点无关过滤）</h3><h4 id="核心原理-4"><a href="#核心原理-4" class="headerlink" title="核心原理"></a>核心原理</h4><p>双方均有严格限制：NAT3仅放行「自身请求过的IP:端口」，NAT4映射端口随目标IP变化。常规情况下，NAT3请求NAT4的“中转端口”会被过滤，NAT4请求NAT3时，因NAT3未记录其新端口，也会被过滤。需通过辅助方案突破限制。</p><h4 id="实战步骤（分两种辅助方案）"><a href="#实战步骤（分两种辅助方案）" class="headerlink" title="实战步骤（分两种辅助方案）"></a>实战步骤（分两种辅助方案）</h4><h5 id="方案1：开启UPnP-NAT-PNP（推荐，成功率高）"><a href="#方案1：开启UPnP-NAT-PNP（推荐，成功率高）" class="headerlink" title="方案1：开启UPnP/NAT-PNP（推荐，成功率高）"></a>方案1：开启UPnP/NAT-PNP（推荐，成功率高）</h5><ol><li>登录NAT4所在的路由器管理后台，找到「UPnP」或「NAT-PNP」功能（通常在“高级设置-端口映射”中），开启该功能；</li><li>双方启动Tailscale，工具会通过UPnP向路由器申请“固定映射端口”（强制NAT4使用固定端口，而非动态变化）；</li><li>后续步骤与「NAT3↔NAT3」一致：双方通过中转服务器同步固定端口，同时发起请求，互相更新状态表；</li><li>流量双向放行，打洞成功。</li></ol><h5 id="方案2：使用Easytier（无UPnP时备选，成功率低）"><a href="#方案2：使用Easytier（无UPnP时备选，成功率低）" class="headerlink" title="方案2：使用Easytier（无UPnP时备选，成功率低）"></a>方案2：使用Easytier（无UPnP时备选，成功率低）</h5><ol><li>双方启动Easytier，工具自动检测到NAT类型组合，触发“端口扫描+预测”模式；</li><li>NAT4主机向NAT3的C:PortC连续发送100-500条请求（目标端口固定，源端口动态生成，形成多个映射端口：Port4-3、Port4-4…）；</li><li>NAT3主机同时向NAT4的E:Port4-3~Port4-500依次发起扫描请求；</li><li>当NAT3扫描到NAT4正在向自己发起请求的端口（如Port4-10）时，双方状态表同时记录对方信息，请求被放行，建立连接。</li></ol><h4 id="可行性结论-4"><a href="#可行性结论-4" class="headerlink" title="可行性结论"></a>可行性结论</h4><p>❌ 常规不可行；✅ 开启UPnP后可行性85%；⚠️ 用Easytier可行性30%-50%（依赖端口扫描命中率，且易被运营商判定为网络攻击，不推荐公网使用）。</p><h4 id="问题排查-4"><a href="#问题排查-4" class="headerlink" title="问题排查"></a>问题排查</h4><p>若UPnP方案失败，排查：① 路由器是否支持UPnP（部分老旧路由器无此功能）；② 工具是否有权限调用UPnP（需以管理员身份运行）；若Easytier方案失败，排查：① 网络是否限制高频端口扫描（如企业内网可能拦截）；② 双方网络延迟是否过高（延迟越高，扫描命中率越低）。</p><h3 id="3-6-NAT4（地址相关映射）-↔-NAT4（地址相关映射）"><a href="#3-6-NAT4（地址相关映射）-↔-NAT4（地址相关映射）" class="headerlink" title="3.6 NAT4（地址相关映射） ↔ NAT4（地址相关映射）"></a>3.6 NAT4（地址相关映射） ↔ NAT4（地址相关映射）</h3><h4 id="核心原理-5"><a href="#核心原理-5" class="headerlink" title="核心原理"></a>核心原理</h4><p>双方均为HardNAT，映射端口随目标IP动态变化，且过滤规则严格。打洞需满足“双方同时扫描到对方当前的动态端口”，而每个动态端口的有效期通常仅几十秒，命中率极低；同时高频扫描易触发运营商的DDoS防护机制，导致IP被临时封禁。</p><h4 id="实战尝试（不推荐）"><a href="#实战尝试（不推荐）" class="headerlink" title="实战尝试（不推荐）"></a>实战尝试（不推荐）</h4><ol><li>双方启动Easytier，开启“深度端口扫描”模式（需手动设置扫描范围，如1000-65535）；</li><li>双方同时向对方公网IP的全端口范围发起扫描请求，试图匹配对方的动态映射端口；</li><li>若运气极佳，双方扫描到对方当前的动态端口，可建立连接；否则扫描持续数分钟后失败。</li></ol><h4 id="可行性结论-5"><a href="#可行性结论-5" class="headerlink" title="可行性结论"></a>可行性结论</h4><p>❌ 强烈不建议尝试，可行性低于10%，且存在IP被封禁的风险。推荐替代方案：① 让一方切换到EasyNAT网络（如连接手机热点）；② 使用中转服务器转发流量（Tailscale默认支持中转，稳定性高但延迟略高）；③ 升级IPv6网络。</p><h2 id="四、打洞可行性总结表（详细版）"><a href="#四、打洞可行性总结表（详细版）" class="headerlink" title="四、打洞可行性总结表（详细版）"></a>四、打洞可行性总结表（详细版）</h2><div class="table-container"><table><thead><tr><th style="text-align:left">通信组合</th><th style="text-align:left">打洞结果</th><th style="text-align:left">核心条件</th><th style="text-align:left">推荐工具</th><th style="text-align:left">风险/注意事项</th></tr></thead><tbody><tr><td style="text-align:left">NAT1↔NAT1</td><td style="text-align:left">✅ 100%稳定</td><td style="text-align:left">无需特殊条件，交换公网信息即可</td><td style="text-align:left">Tailscale、Easytier</td><td style="text-align:left">低风险，仅需放行工具端口</td></tr><tr><td style="text-align:left">NAT1↔NAT3</td><td style="text-align:left">✅ 95%可行</td><td style="text-align:left">NAT3先向NAT1发起请求</td><td style="text-align:left">Tailscale（自动协调顺序）</td><td style="text-align:left">低风险，跨地域延迟高时成功率略降</td></tr><tr><td style="text-align:left">NAT3↔NAT3</td><td style="text-align:left">✅ 90%可行</td><td style="text-align:left">双方同时发起请求，时间同步</td><td style="text-align:left">Tailscale（自动同步）</td><td style="text-align:left">中低风险，延迟&gt;200ms需重试</td></tr><tr><td style="text-align:left">NAT1↔NAT4</td><td style="text-align:left">⚠️ 70%可行</td><td style="text-align:left">工具支持动态端口识别与二次请求</td><td style="text-align:left">Tailscale、Easytier</td><td style="text-align:left">中风险，NAT4端口刷新频繁可能导致连接不稳定</td></tr><tr><td style="text-align:left">NAT3↔NAT4</td><td style="text-align:left">❌ 常规不可行；✅ UPnP后85%可行；⚠️ 扫描后30%-50%可行</td><td style="text-align:left">开启UPnP（推荐）或工具端口扫描（备选）</td><td style="text-align:left">Tailscale（UPnP）、Easytier（扫描）</td><td style="text-align:left">高风险，扫描模式易被判定为攻击</td></tr><tr><td style="text-align:left">NAT4↔NAT4</td><td style="text-align:left">❌ 可行性&lt;10%</td><td style="text-align:left">双方同时扫描到对方动态端口（极难）</td><td style="text-align:left">Easytier（不推荐）</td><td style="text-align:left">极高风险，易导致IP被封禁</td></tr></tbody></table></div><h2 id="五、补充：IPv6替代方案（最优解）"><a href="#五、补充：IPv6替代方案（最优解）" class="headerlink" title="五、补充：IPv6替代方案（最优解）"></a>五、补充：IPv6替代方案（最优解）</h2><p>若双方网络支持IPv6（可通过<a href="https://test-ipv6.com/">test-ipv6.com</a>检测），优先放弃NAT打洞，直接使用IPv6通信：</p><ol><li><strong>核心优势</strong>：IPv6为每个设备分配独立公网地址，无需NAT转换，直接通过IPv6地址建立连接，稳定性100%，延迟低于NAT打洞；</li><li><strong>实战步骤</strong>：        <ol><li>双方开启设备IPv6功能（电脑：设置-网络-属性，勾选IPv6；路由器：开启IPv6拨号，获取前缀）；</li><li>通过命令行获取自身IPv6地址（Windows：ipconfig；Linux：ifconfig）；</li><li>使用支持IPv6的工具（如Tailscale、SSH、FTP），直接输入对方IPv6地址建立连接，无需中转和打洞。</li></ol></li><li><strong>注意事项</strong>：部分运营商IPv6地址为动态分配（重启路由器会变化），可在路由器中设置“IPv6地址绑定”，固定设备的IPv6后缀。</li></ol><h2 id="六、打洞核心规律与建议"><a href="#六、打洞核心规律与建议" class="headerlink" title="六、打洞核心规律与建议"></a>六、打洞核心规律与建议</h2><ol><li><strong>EasyNAT之间（Type1-3）</strong>：打洞成功率高，优先使用Tailscale，操作简单且稳定，适合大多数个人/家庭场景；</li><li><strong>EasyNAT↔HardNAT</strong>：优先尝试开启UPnP，其次使用Easytier，避免手动操作，减少风险；</li><li><strong>HardNAT之间（Type4+）</strong>：放弃打洞，选择中转服务器或IPv6，既保证稳定性，又避免IP被封禁；</li><li><strong>实战优先级</strong>：检测NAT类型 → 尝试EasyNAT打洞 → 开启UPnP重试 → 切换IPv6 → 使用中转服务器。</li></ol>]]></content>
    
    
    <summary type="html">本文介绍了各NAT类型打洞可行性，以及实际操作步骤。</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=8b325dd1-ae3b-41dd-ea39-94ee9381a49d" type="image"/>
    
    
    <category term="学习笔记" scheme="https://blog.itrf.cn/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>EdgeOne加速Vercel配置及踩坑</title>
    <link href="https://blog.itrf.cn/posts/7eac/"/>
    <id>https://blog.itrf.cn/posts/7eac/</id>
    <published>2025-12-06T08:39:55.000Z</published>
    <updated>2026-09-03T07:47:20.864Z</updated>
    
    <content type="html"><![CDATA[<p>用过 Vercel 的开发者大概都对它的便捷性印象深刻 —— 将 Node 项目推送到 GitHub 后，几分钟内就能自动构建部署并提供访问服务，免费计划也足够日常折腾。但随着 Vercel 域名 DNS 污染越来越频繁，不挂梯子几乎无法访问，好好的免费资源就这么闲置了实在可惜。于是我尝试用腾讯云 EO（EdgeOne）对其进行加速，过程中踩了个典型的坑，最终摸索出解决方案，特此记录分享给有同样需求的朋友。</p><h2 id="一、踩坑现场：自信配置却遭遇-522-死循环"><a href="#一、踩坑现场：自信配置却遭遇-522-死循环" class="headerlink" title="一、踩坑现场：自信配置却遭遇 522 死循环"></a>一、踩坑现场：自信配置却遭遇 522 死循环</h2><p>核心诉求很明确：利用腾讯云 EO 的免费计划，解决 Vercel 域名访问受阻的问题。我想当然地认为加速配置的关键就是 “回源 Host 填写 Vercel 提供的默认域名（如 xxx-project.vercel.app）”。</p><p>按照这个思路配置完成后，满心期待地等待部署结束，结果访问时直接弹出 522 错误 —— 服务器无响应。反复检查配置参数、重新部署项目，甚至更换网络环境测试，522 错误始终如影随形，完全摸不着头脑。</p><h2 id="二、问题本质：多层-CDN-嵌套导致回源失效"><a href="#二、问题本质：多层-CDN-嵌套导致回源失效" class="headerlink" title="二、问题本质：多层 CDN 嵌套导致回源失效"></a>二、问题本质：多层 CDN 嵌套导致回源失效</h2><p>我翻了一圈 CDN 加速的核心原理文档，再结合 Google 到的案例，终于理清了 522 错误的根源，本质是<strong>多层 CDN 嵌套引发的回源链路异常</strong>：</p><ol><li><strong>Vercel 默认域名的本质</strong>：xxx-project.vercel.app 并不是直接指向项目服务器的 “源站地址”，而是 Vercel 自家的 CDN 边缘节点域名。当我把这个域名填作 EO 的回源 Host 时，EO 实际是向 Vercel 的 CDN 节点发起回源请求，而非项目的真实源站。</li><li><strong>522 错误的触发逻辑</strong>：Vercel 的 CDN 节点会校验请求的 Host 头是否匹配其配置的域名，而 EO 向 Vercel CDN 发起的请求，其 Host 头与 Vercel 配置不兼容，再加上跨地域链路的延迟，最终导致回源超时，返回 522 错误。</li><li><strong>我的认知误区</strong>：之前误以为 EO 加速是简单的 “反代”，只要填个目标域名就行，忽略了源站是否本身已接入 CDN 这一关键前提。</li></ol><h2 id="三、正确配置：两步搞定-Vercel-加速"><a href="#三、正确配置：两步搞定-Vercel-加速" class="headerlink" title="三、正确配置：两步搞定 Vercel 加速"></a>三、正确配置：两步搞定 Vercel 加速</h2><p>找到问题根源后，解决方案就清晰了 —— 跳过 Vercel 的 CDN 层，让 EO 直接指向项目的真实源站。具体配置步骤如下，同样适用于加速其他自带 CDN 的海外平台（如 Netlify、GitHub Pages）：</p><h3 id="1-获取-Vercel-真实回源-CNAME"><a href="#1-获取-Vercel-真实回源-CNAME" class="headerlink" title="1. 获取 Vercel 真实回源 CNAME"></a>1. 获取 Vercel 真实回源 CNAME</h3><p>首先要拿到 Vercel 项目的 “原始源站 CNAME”（非 CDN 域名），方法很简单：</p><ul><li>登录 Vercel 控制台，添加你自己的域名，会给你一个Vercel的CNAME值；</li><li>这个CNAME值是 Vercel 源站得 CNAME，我们不要解析这个CNAME值;</li></ul><h3 id="2-腾讯云-EO-核心配置"><a href="#2-腾讯云-EO-核心配置" class="headerlink" title="2. 腾讯云 EO 核心配置"></a>2. 腾讯云 EO 核心配置</h3><p>进入腾讯云 EO 控制台，添加与在Vercel添加的相同的自己的域名，重点修改以下两项：</p><div class="table-container"><table><thead><tr><th>配置项</th><th>正确取值</th><th>配置目的</th></tr></thead><tbody><tr><td>回源域名</td><td>第一步获取的 Vercel 原始 CNAME</td><td>跳过 Vercel CDN 层，直接指向真实源站</td></tr><tr><td>Host 头</td><td>你在Vercel添加的自己的域名</td><td>让 Vercel 源站正确识别请求来源，通过校验</td></tr></tbody></table></div><h3 id="3-域名控制台添加-CNAME-解析"><a href="#3-域名控制台添加-CNAME-解析" class="headerlink" title="3. 域名控制台添加 CNAME 解析"></a>3. 域名控制台添加 CNAME 解析</h3><p>到域名控制台，添加 CNAME 解析，注意填的是EO平台给你的CNAME值，而不是Vercel给你的CNAME值。</p><h2 id="四、拓展：海外-CDN-加速通用逻辑"><a href="#四、拓展：海外-CDN-加速通用逻辑" class="headerlink" title="四、拓展：海外 CDN 加速通用逻辑"></a>四、拓展：海外 CDN 加速通用逻辑</h2><p>这次踩坑让我总结出一个通用规律：<strong>当需要用 CDN 加速海外平台时，若目标平台自带 CDN，绝对不能直接用其公开的 CDN 域名作为回源地址</strong>。正确的逻辑是：</p><ol><li>在平台添加自己的域名，获取平台的CNAME值，不要做解析；</li><li>在CDN控制台，回源地址填平台的CNAME值，Host 头填平台添加的域名；</li><li>在域名控制台，添加CNAME解析，值填CDN给你的CNAME值;</li></ol><p>希望这篇踩坑记能帮到同样被 Vercel 访问问题困扰的朋友，少走弯路，充分利用好免费的技术资源～</p>]]></content>
    
    
    <summary type="html">用过 Vercel 的开发者大概都对它的便捷性印象深刻 —— 将 Node 项目推送到 GitHub 后，几分钟内就能自动构建部署并提供访问服务，免费计划也足够日常折腾。但随着 Vercel 域名 DNS 污染越来越频繁，不挂梯子几乎无法访问，好好的免费资源就这么闲置了实在可惜。于是我尝试用腾讯云 EO（EdgeOne）对其进行加速，过程中踩了个典型的坑，最终摸索出解决方案，特此记录分享给有同样需求的朋友。</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=10511ab5-ffac-3fc0-4812-9f59203e6094" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>数据库分页 vs 程序分页：核心决策指南与实践方案</title>
    <link href="https://blog.itrf.cn/posts/516b/"/>
    <id>https://blog.itrf.cn/posts/516b/</id>
    <published>2025-11-29T05:00:00.000Z</published>
    <updated>2026-09-03T07:47:23.697Z</updated>
    
    <content type="html"><![CDATA[<p>在数据查询场景中，“如何高效呈现大量数据” 是开发者绕不开的问题。分页作为解决 “数据量过大导致的内存溢出、响应缓慢” 的核心手段，主要分为<strong>数据库分页</strong>（数据库端过滤 + 限制返回条数）和<strong>程序分页</strong>（全量查询后内存中分片）两种实现方式。</p><p>很多开发者在选型时会陷入纠结：到底该让数据库 “多干活”，还是让应用程序 “扛压力”？其实两者没有绝对优劣，核心取决于数据量、业务复杂度、实时性要求等关键因素。本文将从原理、场景、优缺点、实操建议四个维度，帮你彻底理清选型逻辑，避免踩坑。</p><h2 id="一、先搞懂：两种分页的核心原理"><a href="#一、先搞懂：两种分页的核心原理" class="headerlink" title="一、先搞懂：两种分页的核心原理"></a>一、先搞懂：两种分页的核心原理</h2><p>在深入选型前，我们需要先明确两种分页的本质区别 ——<strong>数据过滤和分片的 “执行位置” 不同</strong>。</p><h3 id="1-数据库分页：数据库端-“按需取数”"><a href="#1-数据库分页：数据库端-“按需取数”" class="headerlink" title="1. 数据库分页：数据库端 “按需取数”"></a>1. 数据库分页：数据库端 “按需取数”</h3><p>数据库分页的核心是<strong>让数据库只返回当前页需要的数据</strong>，通过 SQL 语法（如 <code>LIMIT/OFFSET</code>、<code>ROW_NUMBER()</code>）或条件过滤，在数据查询阶段就完成 “筛选 + 分片”，最终只将单页数据（如 10 条、20 条）返回给应用程序。</p><p><strong>典型实现示例</strong>：</p><ul><li>MySQL：<code>SELECT * FROM orders WHERE status = 1 ORDER BY id DESC LIMIT 10 OFFSET 20;</code>（查询第 3 页，每页 10 条待发货订单）</li><li>PostgreSQL：<code>SELECT * FROM orders WHERE status = 1 ORDER BY id DESC LIMIT 10 OFFSET 20;</code></li><li>Oracle：<code>SELECT * FROM (SELECT t.*, ROW_NUMBER() OVER(ORDER BY id DESC) rn FROM orders t WHERE status = 1) WHERE rn BETWEEN 21 AND 30;</code></li></ul><p>其核心逻辑是：数据库利用索引快速过滤条件数据，再通过分页语法截取目标片段，减少数据传输和应用内存占用。</p><h3 id="2-程序分页：应用端-“全量处理”"><a href="#2-程序分页：应用端-“全量处理”" class="headerlink" title="2. 程序分页：应用端 “全量处理”"></a>2. 程序分页：应用端 “全量处理”</h3><p>程序分页的核心是<strong>先从数据库查询符合条件的全量数据</strong>，加载到应用程序内存中，再通过代码（如 Java 的 <code>List.subList()</code>、Python 的切片 <code>[start:end]</code>）实现分片，返回当前页数据。</p><p><strong>典型实现示例</strong>（Java）：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1. 查全量数据（数据库返回所有待发货订单）</span></span><br><span class="line">List&lt;Order&gt; allOrders = orderMapper.selectByStatus(<span class="number">1</span>);</span><br><span class="line"><span class="comment">// 2. 程序分页：第3页（页码从1开始），每页10条</span></span><br><span class="line"><span class="type">int</span> <span class="variable">pageNum</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">pageSize</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">start</span> <span class="operator">=</span> (pageNum - <span class="number">1</span>) * pageSize;</span><br><span class="line"><span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> Math.min(start + pageSize, allOrders.size());</span><br><span class="line">List&lt;Order&gt; pageOrders = allOrders.subList(start, end);</span><br></pre></td></tr></table></figure><p>其核心逻辑是：数据库仅负责 “全量查询”，分页的计算、分片均在应用内存中完成，依赖应用的计算能力而非数据库。</p><h2 id="二、核心选型：3-个维度定答案"><a href="#二、核心选型：3-个维度定答案" class="headerlink" title="二、核心选型：3 个维度定答案"></a>二、核心选型：3 个维度定答案</h2><p>选型的核心逻辑的是：<strong>让 “擅长的角色做擅长的事”</strong>—— 数据库擅长高效过滤、排序和海量数据存储，应用程序擅长复杂逻辑处理和灵活计算。具体可通过以下 3 个维度快速决策：</p><h3 id="维度-1：数据量大小（最关键因素）"><a href="#维度-1：数据量大小（最关键因素）" class="headerlink" title="维度 1：数据量大小（最关键因素）"></a>维度 1：数据量大小（最关键因素）</h3><p>数据量直接决定了两种方案的可行性和性能上限：</p><ul><li><p>小数据量（万级以内）：优先选程序分页。<br>万级数据占用内存通常在几十 MB 以内（如每条数据 1KB，1 万条仅 10MB），应用程序完全可以承载。此时全量查询的 IO 开销远低于 “多次数据库分页查询”，且程序分页无需频繁与数据库交互，响应更连贯。</p></li><li><p>大数据量（10 万级以上）：必须选数据库分页。<br>百万级 / 千万级数据全量查询会导致两个致命问题：① 数据库查询 + 数据传输耗时极长（可能秒级甚至分钟级）；② 应用内存溢出（OOM）—— 比如 100 万条数据约占 1GB 内存，远超普通应用的内存阈值。而数据库分页仅返回单页数据（几十条），内存占用可忽略，且依赖索引能快速响应。</p></li></ul><h3 id="维度-2：业务复杂度（分类-筛选逻辑）"><a href="#维度-2：业务复杂度（分类-筛选逻辑）" class="headerlink" title="维度 2：业务复杂度（分类 / 筛选逻辑）"></a>维度 2：业务复杂度（分类 / 筛选逻辑）</h3><p>业务逻辑的复杂程度决定了 “谁更适合处理分页前的过滤”：</p><ul><li><p>简单逻辑（单一条件 / 多条件且可 SQL 表达）：优先选数据库分页。<br>比如 “按订单状态筛选 + 按创建时间排序分页”“按地区 + 金额区间过滤分页”，这些逻辑可通过 SQL 的 <code>WHERE</code>子句 + 索引快速实现，数据库处理效率远高于应用程序全量加载后过滤。</p></li><li><p>复杂逻辑（多维度交叉 / 非 SQL 可表达）：优先选程序分页。<br>比如 “按「金额区间（高 / 中 / 低）+ 配送区域 + 下单设备」三维交叉分类，且每个分类需独立分页”“按模糊匹配（如备注含特定关键词）+ 自定义规则（如 VIP 用户优先）筛选”，这些逻辑用 SQL 实现会非常繁琐（多层 <code>CASE WHEN</code>+<code>GROUP BY</code>），甚至无法实现，而应用程序可通过哈希表分组、流处理等方式灵活实现。</p></li></ul><h3 id="维度-3：业务场景（实时性-复用性要求）"><a href="#维度-3：业务场景（实时性-复用性要求）" class="headerlink" title="维度 3：业务场景（实时性 / 复用性要求）"></a>维度 3：业务场景（实时性 / 复用性要求）</h3><ul><li><p>实时性要求高（如订单列表、实时数据监控）：选数据库分页。<br>数据库分页每次查询都是实时从数据库取数，能保证数据最新状态（如用户刚创建的订单立即显示）。而程序分页若不重新查询，会存在数据滞后问题。</p></li><li><p>需频繁复用数据（如多维度筛选、前端切换分类）：选程序分页。<br>比如前端页面需要同时展示 “待付款”“待发货”“已完成” 三个分类的分页列表，程序分页可全量查询后在内存中缓存所有分类数据，用户切换分类时直接从内存取数，无需再次查询数据库；而数据库分页需要执行 3 次独立查询，增加数据库压力。</p></li></ul><h2 id="三、两种方案的优缺点深度对比"><a href="#三、两种方案的优缺点深度对比" class="headerlink" title="三、两种方案的优缺点深度对比"></a>三、两种方案的优缺点深度对比</h2><div class="table-container"><table><thead><tr><th>对比维度</th><th>数据库分页</th><th>程序分页</th></tr></thead><tbody><tr><td>内存占用</td><td>极低（仅单页数据）</td><td>较高（全量数据加载到内存）</td></tr><tr><td>响应速度（首次）</td><td>快（数据量小，传输 + 处理耗时短）</td><td>慢（全量数据查询 + 传输耗时）</td></tr><tr><td>响应速度（后续）</td><td>每次分页需查询数据库，存在 IO 延迟</td><td>后续分页 / 切换分类无 IO 延迟，响应连贯</td></tr><tr><td>数据库压力</td><td>多次查询（如多分类分页），压力中等</td><td>一次查询，压力小</td></tr><tr><td>灵活性</td><td>低（依赖 SQL 逻辑，复杂场景难以扩展）</td><td>高（支持任意自定义逻辑，扩展方便）</td></tr><tr><td>实时性</td><td>高（每次查询实时取数）</td><td>低（全量查询后数据静态，需重新查询更新）</td></tr><tr><td>适用数据量</td><td>10 万级以上（海量数据）</td><td>万级以内（小数据量）</td></tr><tr><td>开发成本</td><td>中等（需编写分页 SQL，处理深分页优化）</td><td>低（全量查询后简单分片，逻辑直观）</td></tr></tbody></table></div><h2 id="四、实操建议：避坑指南与优化技巧"><a href="#四、实操建议：避坑指南与优化技巧" class="headerlink" title="四、实操建议：避坑指南与优化技巧"></a>四、实操建议：避坑指南与优化技巧</h2><p>选型后如何落地才能最大化性能？以下是两种方案的实操优化建议，避免踩常见坑：</p><h3 id="数据库分页：3-个优化技巧，避免性能瓶颈"><a href="#数据库分页：3-个优化技巧，避免性能瓶颈" class="headerlink" title="数据库分页：3 个优化技巧，避免性能瓶颈"></a>数据库分页：3 个优化技巧，避免性能瓶颈</h3><ol><li><p><strong>优先用 “游标分页” 替代<code>OFFSET</code>深分页</strong>传统的 <code>LIMIT OFFSET</code> 分页在深分页场景（如 <code>LIMIT 100000, 10</code>）会出现性能问题 —— 数据库需要扫描前 100010 条数据才能返回目标 10 条。优化方案是 “游标分页”：按唯一有序字段（如 ID、创建时间戳）排序，通过 <code>WHERE</code> 条件替代 <code>OFFSET</code>：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 上一页最大ID为10000，查询下一页（每页10条）</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="number">1</span> <span class="keyword">AND</span> id <span class="operator">&gt;</span> <span class="number">10000</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> id <span class="keyword">ASC</span> LIMIT <span class="number">10</span>;</span><br></pre></td></tr></table></figure><p>优点：利用索引直接定位起始位置，无需扫描前置数据，深分页性能无衰减。</p></li><li><p><strong>给过滤 / 排序字段加索引</strong>分页查询的 <code>WHERE</code> 条件字段（如 <code>status</code>、<code>region</code>）和 <code>ORDER BY</code> 字段（如 <code>create_time</code>、<code>id</code>）必须加索引，否则数据库会全表扫描，分页性能骤降。示例索引设计：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 复合索引：适配“按状态筛选+按创建时间排序”的分页场景</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_orders_status_create_time <span class="keyword">ON</span> orders(status, create_time);</span><br></pre></td></tr></table></figure></li><li><p><strong>合并统计查询，减少数据库交互</strong>若需要同时展示 “总条数”“总页数” 和 “当前页数据”，避免执行两次查询（一次查总数 + 一次查分页数据），可通过 <code>SQL_CALC_FOUND_ROWS</code>（MySQL）或子查询合并：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- MySQL：查询分页数据的同时获取总条数</span></span><br><span class="line"><span class="keyword">SELECT</span> SQL_CALC_FOUND_ROWS <span class="operator">*</span> <span class="keyword">FROM</span> orders <span class="keyword">WHERE</span> status <span class="operator">=</span> <span class="number">1</span> <span class="keyword">ORDER</span> <span class="keyword">BY</span> id <span class="keyword">DESC</span> LIMIT <span class="number">10</span> <span class="keyword">OFFSET</span> <span class="number">20</span>;</span><br><span class="line"><span class="keyword">SELECT</span> FOUND_ROWS(); <span class="comment">-- 获取总条数（无需再次扫描表）</span></span><br></pre></td></tr></table></figure></li></ol><h3 id="程序分页：3-个避坑要点，保证稳定性"><a href="#程序分页：3-个避坑要点，保证稳定性" class="headerlink" title="程序分页：3 个避坑要点，保证稳定性"></a>程序分页：3 个避坑要点，保证稳定性</h3><ol><li><p><strong>加数据量限制，防止意外 OOM</strong>即使是小数据量场景，也需限制全量查询的最大条数，避免因业务异常导致数据量暴增（如用户查询 “所有历史订单” 时，意外返回 10 万条）。示例代码（Java）：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 限制最大查询条数为2万条</span></span><br><span class="line">List&lt;Order&gt; allOrders = orderMapper.selectByCondition(condition, <span class="number">20000</span>);</span><br></pre></td></tr></table></figure></li><li><p><strong>用高效数据结构，优化分页性能</strong>全量数据加载后，优先用 <code>ArrayList</code>（随机访问效率高）存储，分页时通过 <code>subList()</code> 分片（时间复杂度 O (1)），避免使用链表（随机访问效率低）。若需多分类分页，可提前用 <code>HashMap</code> 按分类键分组，后续分页直接从分组结果中取数：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 多分类分组+分页优化</span></span><br><span class="line">Map&lt;String, List&lt;Order&gt;&gt; categoryMap = allOrders.stream()</span><br><span class="line">        .collect(Collectors.groupingBy(order -&gt; getCategoryKey(order))); <span class="comment">// 自定义分类键</span></span><br><span class="line"><span class="comment">// 某分类分页</span></span><br><span class="line">List&lt;Order&gt; categoryOrders = categoryMap.get(<span class="string">&quot;高金额-华东地区&quot;</span>);</span><br><span class="line">List&lt;Order&gt; pageOrders = categoryOrders.subList(start, end);</span><br></pre></td></tr></table></figure></li><li><p><strong>缓存结果，减少重复查询</strong>若数据不频繁变化（如统计数据、历史订单），可将全量查询 + 分组后的结果缓存到 Redis（设置合理过期时间），下次分页直接从缓存取数，避免重复查询数据库。</p></li></ol><h2 id="五、进阶场景：混合分页方案（取长补短）"><a href="#五、进阶场景：混合分页方案（取长补短）" class="headerlink" title="五、进阶场景：混合分页方案（取长补短）"></a>五、进阶场景：混合分页方案（取长补短）</h2><p>当遇到 “大数据量 + 复杂逻辑” 的场景（如 “千万级订单，需按三维交叉分类分页，且每个分类需实时展示最新数据”），单一方案无法满足需求，此时可采用<strong>混合分页方案</strong>：</p><h3 id="核心思路：统计数预计算，详情页实时分页"><a href="#核心思路：统计数预计算，详情页实时分页" class="headerlink" title="核心思路：统计数预计算，详情页实时分页"></a>核心思路：统计数预计算，详情页实时分页</h3><ol><li><strong>预计算分类统计</strong>：用定时任务（如 Quartz）或流处理（如 Flink）将复杂分类的统计结果（如 “华东地区 - 高金额订单数”“华北地区 - APP 下单订单数”）存储到 Redis 或宽表，避免实时全量计算。</li><li><strong>实时分页查详情</strong>：用户点击某个分类时，通过 “分类条件 + 游标分页” 查询该分类下的实时数据 —— 比如 “华东地区 - 高金额订单” 的分页，可通过 SQL <code>WHERE 金额 &gt; 1000 AND 地区 = &#39;华东&#39; AND id &gt; 上一页最大ID LIMIT 10</code> 实现，兼顾实时性和性能。</li></ol><h3 id="适用场景："><a href="#适用场景：" class="headerlink" title="适用场景："></a>适用场景：</h3><ul><li>海量数据 + 复杂分类分页（如电商平台的订单多维度筛选分页）；</li><li>需兼顾实时性（详情数据）和性能（统计数据）的场景。</li></ul><h2 id="六、总结：选型决策表（直接套用）"><a href="#六、总结：选型决策表（直接套用）" class="headerlink" title="六、总结：选型决策表（直接套用）"></a>六、总结：选型决策表（直接套用）</h2><div class="table-container"><table><thead><tr><th>场景描述</th><th>推荐方案</th><th>核心理由</th></tr></thead><tbody><tr><td>小数据量（万级内）+ 复杂分类 / 筛选逻辑</td><td>程序分页</td><td>内存承载无压力，逻辑灵活，响应连贯</td></tr><tr><td>小数据量（万级内）+ 简单筛选逻辑</td><td>程序分页 / 数据库分页均可</td><td>程序分页开发效率高，数据库分页更省内存</td></tr><tr><td>大数据量（10 万级以上）+ 任意筛选逻辑</td><td>数据库分页</td><td>避免 OOM，依赖索引高效处理海量数据</td></tr><tr><td>实时性要求高（如订单列表）+ 简单筛选</td><td>数据库分页</td><td>实时取数，响应快</td></tr><tr><td>需频繁切换分类 / 复用数据（如多维度筛选页）</td><td>程序分页 + 缓存</td><td>减少数据库交互，切换分类无延迟</td></tr><tr><td>大数据量 + 复杂分类分页（进阶场景）</td><td>混合分页方案</td><td>预计算统计数 + 实时分页详情，兼顾性能和灵活</td></tr></tbody></table></div><h2 id="最终结论"><a href="#最终结论" class="headerlink" title="最终结论"></a>最终结论</h2><p>数据库分页和程序分页不是 “非此即彼” 的选择，而是 “按需适配” 的方案：</p><ul><li>小数据量、复杂逻辑场景，让应用程序 “扛下” 分页，享受灵活和高效；</li><li>大数据量、简单逻辑场景，让数据库 “搞定” 分页，保证稳定和性能；</li><li>复杂场景下，用混合方案取长补短，兼顾业务需求和技术上限。</li></ul><p>核心原则是：<strong>避免 “用数据库分页解决复杂逻辑”（逻辑繁琐、性能差），或 “用程序分页处理海量数据”（内存崩溃、响应慢）</strong>，让技术方案适配业务场景，而非反过来让业务妥协于技术。</p>]]></content>
    
    
    <summary type="html">在数据查询场景中，“如何高效呈现大量数据” 是开发者绕不开的问题。分页作为解决 “数据量过大导致的内存溢出、响应缓慢” 的核心手段，主要分为数据库分页（数据库端过滤 + 限制返回条数）和程序分页（全量查询后内存中分片）两种实现方式。很多开发者在选型时会陷入纠结：到底该让数据库 “多干活”，还是让应用程序 “扛压力”？其实两者没有绝对优劣，核心取决于数据量、业务复杂度、实时性要求等关键因素。本文将从原理、场景、优缺点、实操建议四个维度，帮你彻底理清选型逻辑，避免踩坑。</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=8c804fae-baa5-a9aa-6aad-6ed3301bd90e" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>Flask项目无法获取favicon.ico原因以及解决方案</title>
    <link href="https://blog.itrf.cn/posts/c93f/"/>
    <id>https://blog.itrf.cn/posts/c93f/</id>
    <published>2025-11-29T04:59:16.000Z</published>
    <updated>2026-09-03T07:46:16.055Z</updated>
    
    <content type="html"><![CDATA[<p>部署后浏览器从「根目录（<code>/favicon.ico</code>）」获取图标，而非你设置的 <code>static/favicon.ico</code>，核心原因是 <strong>浏览器的默认行为 + 配置 / 引用缺失</strong>，具体拆解和解决步骤如下：</p><h3 id="一、核心原因：浏览器的「默认-favicon-ico-请求」"><a href="#一、核心原因：浏览器的「默认-favicon-ico-请求」" class="headerlink" title="一、核心原因：浏览器的「默认 /favicon.ico 请求」"></a>一、核心原因：浏览器的「默认 /favicon.ico 请求」</h3><p>这是最根本的原因 ——<strong>浏览器会自动向网站根目录发送 <code>/favicon.ico</code> 请求</strong>，无论你是否在页面中引用：</p><ul><li>即使你在 <code>&lt;head&gt;</code> 中写了 <code>&lt;link rel=&quot;icon&quot; href=&quot;/static/favicon.ico&quot;&gt;</code>，部分浏览器（如 Chrome、Edge）仍会先尝试请求 <code>/favicon.ico</code>（根目录），失败后才会使用你指定的路径；</li><li>部署环境中，若根目录没有 <code>favicon.ico</code>，且你的页面引用有问题（如路径错误、缓存），就会出现「图标不显示，Network 面板看到 <code>/favicon.ico 404</code>」的现象。</li></ul><h3 id="二、其他辅助原因（部署环境常见）"><a href="#二、其他辅助原因（部署环境常见）" class="headerlink" title="二、其他辅助原因（部署环境常见）"></a>二、其他辅助原因（部署环境常见）</h3><ol><li><p><strong>页面引用路径错误</strong>：</p><ul><li>本地开发时 <code>url_for(&#39;static&#39;, filename=&#39;favicon.ico&#39;)</code> 生成 <code>/static/favicon.ico</code>，但部署后若应用有「子路径前缀」（如 <code>https://your-domain.com/app/</code>），会生成 <code>/app/static/favicon.ico</code>，而浏览器仍请求 <code>/favicon.ico</code>；</li><li>引用时用了相对路径（如 <code>../static/favicon.ico</code>），部署后路径层级变化导致失效。</li></ul></li><li><p><strong>Nginx/Apache 反向代理配置覆盖</strong>：生产环境用反向代理时，可能配置了「根目录 <code>favicon.ico</code> 优先返回」，忽略 Flask 的静态文件路由：</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 错误示例：Nginx 配置了根目录/favicon.ico，但实际文件不存在</span></span><br><span class="line"><span class="section">location</span> /favicon.ico &#123;</span><br><span class="line">    <span class="attribute">root</span> /var/www/html/;  <span class="comment"># 若该目录没有favicon.ico，直接404</span></span><br><span class="line">    <span class="attribute">expires</span> <span class="number">7d</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p><strong>浏览器缓存</strong>：之前访问过你的网站（或测试环境），浏览器缓存了「根目录 <code>/favicon.ico</code> 不存在」的结果，部署后即使修复了引用，仍会优先使用缓存。</p></li></ol><h3 id="三、3-种解决方法（从简单到彻底）"><a href="#三、3-种解决方法（从简单到彻底）" class="headerlink" title="三、3 种解决方法（从简单到彻底）"></a>三、3 种解决方法（从简单到彻底）</h3><h4 id="方法-1：确保页面中正确引用（最基础）"><a href="#方法-1：确保页面中正确引用（最基础）" class="headerlink" title="方法 1：确保页面中正确引用（最基础）"></a>方法 1：确保页面中正确引用（最基础）</h4><p>首先保证 <code>&lt;head&gt;</code> 中明确指定图标路径，覆盖浏览器默认行为：</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- templates/index.html（SPA入口页，全局生效） --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- 核心：同时指定 rel=&quot;icon&quot; 和 rel=&quot;shortcut icon&quot;，兼容所有浏览器 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;icon&quot;</span> <span class="attr">href</span>=<span class="string">&quot;&#123;&#123; url_for(&#x27;static&#x27;, filename=&#x27;favicon.ico&#x27;) &#125;&#125;&quot;</span> <span class="attr">type</span>=<span class="string">&quot;image/x-icon&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;shortcut icon&quot;</span> <span class="attr">href</span>=<span class="string">&quot;&#123;&#123; url_for(&#x27;static&#x27;, filename=&#x27;favicon.ico&#x27;) &#125;&#125;&quot;</span> <span class="attr">type</span>=<span class="string">&quot;image/x-icon&quot;</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- 可选：添加不同尺寸/格式，适配更多场景（如PWA） --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;apple-touch-icon&quot;</span> <span class="attr">sizes</span>=<span class="string">&quot;180x180&quot;</span> <span class="attr">href</span>=<span class="string">&quot;&#123;&#123; url_for(&#x27;static&#x27;, filename=&#x27;apple-touch-icon.png&#x27;) &#125;&#125;&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>关键：用 <code>url_for(&#39;static&#39;, filename=&#39;favicon.ico&#39;)</code> 动态生成路径，避免硬编码（适配部署后的子路径前缀）；</li><li>验证：部署后查看页面源码，确认 <code>href</code> 是正确的 <code>https://your-domain.com/static/favicon.ico</code>（而非 <code>/favicon.ico</code>）。</li></ul><h4 id="方法-2：路由重定向（让根目录请求指向-static）"><a href="#方法-2：路由重定向（让根目录请求指向-static）" class="headerlink" title="方法 2：路由重定向（让根目录请求指向 static）"></a>方法 2：路由重定向（让根目录请求指向 static）</h4><p>即使浏览器请求 <code>/favicon.ico</code>，也通过 Flask 路由重定向到 <code>static</code> 目录的文件，彻底解决默认请求问题：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># app.py（在create_app()中添加路由）</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_app</span>(<span class="params">config_name=<span class="literal">None</span></span>):</span><br><span class="line">    app = Flask(__name__)</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 新增：根目录favicon.ico请求 → 重定向到static目录</span></span><br><span class="line"><span class="meta">    @app.route(<span class="params"><span class="string">&#x27;/favicon.ico&#x27;</span></span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">favicon_redirect</span>():</span><br><span class="line">        <span class="keyword">from</span> flask <span class="keyword">import</span> redirect, url_for</span><br><span class="line">        <span class="keyword">return</span> redirect(url_for(<span class="string">&#x27;static&#x27;</span>, filename=<span class="string">&#x27;favicon.ico&#x27;</span>))</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 其他配置（蓝图、错误处理等）...</span></span><br><span class="line">    <span class="keyword">return</span> app</span><br></pre></td></tr></table></figure><ul><li>效果：访问 <code>/favicon.ico</code> 会自动跳转到 <code>/static/favicon.ico</code>，浏览器能正确获取图标；</li><li>优势：无需修改 Nginx 配置，纯 Flask 代码解决，适配所有部署环境。</li></ul><h4 id="方法-3：Nginx-配置（生产环境最优，性能最高）"><a href="#方法-3：Nginx-配置（生产环境最优，性能最高）" class="headerlink" title="方法 3：Nginx 配置（生产环境最优，性能最高）"></a>方法 3：Nginx 配置（生产环境最优，性能最高）</h4><p>生产环境用 Nginx 反向代理时，直接让 Nginx 处理 <code>favicon.ico</code> 请求（不经过 Flask），提升性能：</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line"><span class="attribute">server_name</span> your-domain.com;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 核心配置：/favicon.ico 直接指向 static 目录的文件</span></span><br><span class="line">    <span class="section">location</span> /favicon.ico &#123;</span><br><span class="line">        <span class="comment"># 注意：root 是项目根目录（static的上级目录），不是static目录本身</span></span><br><span class="line">        <span class="attribute">root</span> /path/to/your_project/;  <span class="comment"># 示例：/var/www/music_project/</span></span><br><span class="line">        <span class="comment"># 实际文件路径 = root + static/favicon.ico → /var/www/music_project/static/favicon.ico</span></span><br><span class="line">        <span class="attribute">try_files</span> /static/favicon.ico =<span class="number">404</span>;  <span class="comment"># 尝试读取文件，不存在返回404</span></span><br><span class="line">        <span class="attribute">expires</span> <span class="number">7d</span>;  <span class="comment"># 缓存7天，减少重复请求</span></span><br><span class="line">        <span class="attribute">add_header</span> Cache-Control <span class="string">&quot;public&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="section">location</span> /static/ &#123;</span><br><span class="line">        <span class="attribute">root</span> /path/to/your_project/;</span><br><span class="line">        <span class="attribute">expires</span> <span class="number">7d</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 动态请求转发给Flask</span></span><br><span class="line">    <span class="section">location</span> / &#123;</span><br><span class="line">        <span class="attribute">proxy_pass</span> http://127.0.0.1:5000;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> Host <span class="variable">$host</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>关键：<code>root</code> 指向项目根目录（如 <code>/var/www/music_project/</code>），<code>try_files /static/favicon.ico</code> 表示「在 root 目录下查找 <code>static/favicon.ico</code> 文件」；</li><li>优势：Nginx 直接返回静态文件，比 Flask 路由重定向性能更高，适合高并发场景。</li></ul><h3 id="四、部署后验证步骤（确保生效）"><a href="#四、部署后验证步骤（确保生效）" class="headerlink" title="四、部署后验证步骤（确保生效）"></a>四、部署后验证步骤（确保生效）</h3><ol><li><p><strong>清除浏览器缓存</strong>：按 <code>Ctrl+Shift+R</code>（Chrome）强制刷新，或打开「开发者工具 → Network → 勾选 Disable cache」后刷新；</p></li><li><p>查看 Network 面板：</p><ul><li>搜索 <code>favicon</code>，查看请求路径：应为 <code>/static/favicon.ico</code> 或 <code>/favicon.ico</code>（状态码 200）；</li><li>若仍为 404，检查路径是否正确（如 <code>static</code> 目录是否上传到服务器、文件权限是否为可读）；</li></ul></li><li><p>直接访问验证：</p><ul><li>访问 <code>https://your-domain.com/static/favicon.ico</code>，能看到图标说明文件存在且路径正确；</li><li>访问 <code>https://your-domain.com/favicon.ico</code>，能看到图标说明重定向 / Nginx 配置生效。</li></ul></li></ol><h3 id="五、常见坑点排查"><a href="#五、常见坑点排查" class="headerlink" title="五、常见坑点排查"></a>五、常见坑点排查</h3><ol><li><p><strong>文件路径错误</strong>：</p><ul><li>服务器上的 <code>static</code> 目录是否和本地一致？（如本地 <code>static/favicon.ico</code>，服务器是否误放在 <code>static/icons/favicon.ico</code>）；</li><li>文件名是否大小写敏感？（Linux 服务器区分 <code>favicon.ico</code> 和 <code>Favicon.ico</code>，Windows 不区分）。</li></ul></li><li><p><strong>文件权限问题</strong>：</p><ul><li>服务器上的 <code>favicon.ico</code> 文件是否有「读权限」？（执行 <code>chmod 644 /path/to/your_project/static/favicon.ico</code> 赋予权限）；</li><li><code>static</code> 目录是否有「执行权限」？（执行 <code>chmod 755 /path/to/your_project/static</code>，确保浏览器能进入目录）。</li></ul></li><li><p><strong>子路径部署场景</strong>：若应用部署在子路径（如 <code>https://your-domain.com/music/</code>），需注意：</p><ul><li><p>页面引用用 <code>url_for(&#39;static&#39;, filename=&#39;favicon.ico&#39;)</code> 会自动生成 <code>/music/static/favicon.ico</code>；</p></li><li><p>Nginx 配置需调整 <code>root</code> 或 <code>alias</code><br>例如：</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="section">location</span> /music/favicon.ico &#123;</span><br><span class="line">    <span class="attribute">alias</span> /path/to/your_project/static/favicon.ico;  <span class="comment"># 直接指向文件</span></span><br><span class="line">    <span class="attribute">expires</span> <span class="number">7d</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li></ol><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>部署后浏览器从根目录获取 <code>favicon.ico</code> 是「浏览器默认行为」导致的，解决核心是：</p><ol><li>页面中明确引用 <code>static/favicon.ico</code>（用 <code>url_for</code> 动态生成路径）；</li><li>用「Flask 路由重定向」或「Nginx 配置」让 <code>/favicon.ico</code> 指向正确文件；</li><li>清除缓存并验证文件路径和权限。</li></ol><p>按方法 1 + 方法 2（或方法 1 + 方法 3）配置后，所有浏览器都能正确获取图标，不会再出现「根目录请求 404」的问题。</p>]]></content>
    
    
    <summary type="html">Flask项目无法获取favicon.ico原因以及解决方案</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=8404bd04-a221-3dfb-57d6-35c518581c37" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>EdgeOne 获取用户真实 IP 完整解决方案（Nginx 适配版）</title>
    <link href="https://blog.itrf.cn/posts/8ab6/"/>
    <id>https://blog.itrf.cn/posts/8ab6/</id>
    <published>2025-11-29T04:58:50.000Z</published>
    <updated>2026-09-07T07:21:44.920Z</updated>
    
    <content type="html"><![CDATA[<p>在使用腾讯云 EdgeOne 海外节点加速海外业务时，很多开发者会遇到一个核心问题：Nginx 日志中记录的是 EdgeOne 节点 IP 而非用户真实 IP，导致无法进行用户行为分析、地域统计和异常访问拦截。本文将从「问题原理」「手动配置」「自动化更新」「故障排查」四个维度，提供一套适配 Nginx + 宝塔面板的完整解决方案，新手也能快速上手。</p><h2 id="一、核心原理：为什么需要特殊配置？"><a href="#一、核心原理：为什么需要特殊配置？" class="headerlink" title="一、核心原理：为什么需要特殊配置？"></a>一、核心原理：为什么需要特殊配置？</h2><p>当用户访问经过 EdgeOne 加速的网站时，请求会先经过 EdgeOne 海外节点（反向代理），再由节点转发到源站 Nginx。此时 Nginx 会默认将「直接连接的客户端 IP」（即 EdgeOne 节点 IP）识别为用户 IP，而非真实的用户公网 IP。</p><p>解决思路的核心是「信任代理」：</p><ol><li>EdgeOne 节点会在转发请求时，通过 <code>X-Forwarded-For</code> 头携带用户真实 IP（格式：<code>用户真实IP, 节点IP1, 节点IP2</code>）；</li><li>配置 Nginx 信任所有 EdgeOne 节点 IP，让 Nginx 从 <code>X-Forwarded-For</code> 头中自动跳过节点 IP，提取最原始的用户真实 IP。</li></ol><blockquote><p>关键提示：本文以「海外业务场景」为例，适配 CentOS 系统 + Nginx + 宝塔面板，其他系统（如 Ubuntu）或 Web 服务器（如 Apache）可参考原理调整。</p></blockquote><h2 id="二、前置准备：确认-EdgeOne-基础配置"><a href="#二、前置准备：确认-EdgeOne-基础配置" class="headerlink" title="二、前置准备：确认 EdgeOne 基础配置"></a>二、前置准备：确认 EdgeOne 基础配置</h2><p>在配置 Nginx 前，需先确保 EdgeOne 已正确配置「回源透传」，否则节点不会携带用户真实 IP 信息：</p><ol><li>登录 <a href="https://console.cloud.tencent.com/edgeone">EdgeOne 控制台</a>，进入目标站点的「回源配置」；</li><li>开启「HTTP 头配置」，确认已添加「透传 X-Forwarded-For 头」（默认开启，若未开启需手动添加）；</li><li>关闭「源站保护」功能（重要！开启后无法获取完整节点 IP 段，配置完成后可重新开启）。</li></ol><h2 id="三、手动配置：快速实现真实-IP-解析"><a href="#三、手动配置：快速实现真实-IP-解析" class="headerlink" title="三、手动配置：快速实现真实 IP 解析"></a>三、手动配置：快速实现真实 IP 解析</h2><p>若业务规模较小，可先通过手动配置验证效果，步骤如下：</p><h3 id="3-1-获取-EdgeOne-海外节点-IP-段"><a href="#3-1-获取-EdgeOne-海外节点-IP-段" class="headerlink" title="3.1 获取 EdgeOne 海外节点 IP 段"></a>3.1 获取 EdgeOne 海外节点 IP 段</h3><p>EdgeOne 提供官方接口用于获取最新节点 IP 段，海外 IPv4 段专属获取方式：</p><ol><li>本地浏览器访问接口：<code>https://api.edgeone.ai/ips?version=v4&amp;area=overseas</code>（仅返回海外节点 IP 段，精准适配海外业务）；</li><li>复制页面中所有 IP 段（纯文本格式，如 <code>43.174.0.0/15</code>），保存到本地文档。</li></ol><blockquote><p>注意事项：接口返回的 IP 段会不定期更新，建议每 1-2 个月重新获取一次，避免因 IP 段过期导致解析失效。</p></blockquote><h3 id="3-2-配置-Nginx-信任节点-IP-段"><a href="#3-2-配置-Nginx-信任节点-IP-段" class="headerlink" title="3.2 配置 Nginx 信任节点 IP 段"></a>3.2 配置 Nginx 信任节点 IP 段</h3><p>通过宝塔面板修改 Nginx 配置（手动操作更直观，适合新手）：</p><ol><li>登录宝塔面板，进入「网站」→ 目标站点（如 <code>music.686909.xyz</code>）→ 「配置文件」；</li><li>在 <code>server</code> 块顶部添加「节点 IP 段信任配置」，完整配置示例如下（重点关注标记内内容）：</li></ol><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="section">server</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">443</span> ssl;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">443</span> quic;</span><br><span class="line">    <span class="attribute">http2</span> <span class="literal">on</span>;</span><br><span class="line">    <span class="attribute">server_name</span> music.<span class="number">686909</span>.xyz;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># ====== EDGEONE_IP_START 标记（后续自动化需用到）======</span></span><br><span class="line">    <span class="comment"># EdgeOne 海外节点 IP 段（手动复制接口返回内容）</span></span><br><span class="line">    <span class="attribute">set_real_ip_from</span> <span class="number">43.174.0.0</span>/<span class="number">15</span>;</span><br><span class="line">    <span class="attribute">set_real_ip_from</span> <span class="number">45.12.0.0</span>/<span class="number">16</span>;</span><br><span class="line">    <span class="attribute">set_real_ip_from</span> <span class="number">45.65.0.0</span>/<span class="number">16</span>;</span><br><span class="line">    <span class="attribute">set_real_ip_from</span> <span class="number">45.113.0.0</span>/<span class="number">16</span>;</span><br><span class="line">    <span class="attribute">set_real_ip_from</span> <span class="number">52.58.0.0</span>/<span class="number">15</span>;</span><br><span class="line">    <span class="comment"># ... 此处省略其他从接口获取的 IP 段，需完整粘贴 ...</span></span><br><span class="line">    <span class="attribute">set_real_ip_from</span> <span class="number">217.160.0.0</span>/<span class="number">16</span>;</span><br><span class="line">    <span class="comment"># ====== EDGEONE_IP_END 标记（后续自动化需用到）======</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 真实 IP 解析核心规则</span></span><br><span class="line">    <span class="attribute">real_ip_header</span> X-Forwarded-For;        <span class="comment"># 从该头提取真实 IP</span></span><br><span class="line">    <span class="attribute">real_ip_recursive</span> <span class="literal">on</span>;                  <span class="comment"># 递归跳过信任的节点 IP</span></span><br><span class="line">    <span class="attribute">set_real_ip_from</span> <span class="number">127.0.0.1</span>;            <span class="comment"># 额外信任本地代理（避免干扰）</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 以下为原有配置（SSL、反向代理等，无需修改）</span></span><br><span class="line">    <span class="attribute">index</span> index.html index.htm default.htm default.html;</span><br><span class="line">    <span class="attribute">root</span> /www/wwwroot/music;</span><br><span class="line">    <span class="attribute">ssl_certificate</span>    /www/server/panel/vhost/cert/music/fullchain.pem;</span><br><span class="line">    <span class="attribute">ssl_certificate_key</span>    /www/server/panel/vhost/cert/music/privkey.pem;</span><br><span class="line">    <span class="comment"># ... 其他配置省略 ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>点击「保存」，然后在宝塔面板「服务」中重启 Nginx，配置生效。</li></ol><h3 id="3-3-验证配置效果"><a href="#3-3-验证配置效果" class="headerlink" title="3.3 验证配置效果"></a>3.3 验证配置效果</h3><p>配置完成后，通过以下步骤确认真实 IP 是否解析成功：</p><ol><li>用海外网络访问目标网站（如通过海外 VPN 或在线代理工具）；</li><li>在宝塔面板进入「网站」→ 目标站点 → 「日志」，查看访问日志（如 <code>music.log</code>）；</li><li>日志中「IP」字段若显示为非 EdgeOne 网段的公网 IP（可通过 <a href="https://www.ip138.com/">IP138</a> 验证归属地），说明解析成功。</li></ol><h2 id="四、自动化方案：IP-段自动更新（运维必备）"><a href="#四、自动化方案：IP-段自动更新（运维必备）" class="headerlink" title="四、自动化方案：IP 段自动更新（运维必备）"></a>四、自动化方案：IP 段自动更新（运维必备）</h2><p>手动更新 IP 段效率低且易遗漏，通过「Shell 脚本 + 定时任务」可实现全自动更新，核心逻辑：定期从官方接口获取最新 IP 段 → 自动替换 Nginx 配置 → 验证并重启 Nginx。</p><h3 id="4-1-编写自动更新脚本"><a href="#4-1-编写自动更新脚本" class="headerlink" title="4.1 编写自动更新脚本"></a>4.1 编写自动更新脚本</h3><p>创建脚本 <code>update_edgeone_ips.sh</code>，上传到服务器 <code>/root/</code> 目录，脚本已适配海外节点和宝塔面板，关键部分已加注释（需替换脚本中 <code>NGINX_CONF</code> 为你的站点配置路径）：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># ==============================================</span></span><br><span class="line"><span class="comment"># EdgeOne 海外节点 IP 段自动更新脚本（Nginx 适配版）</span></span><br><span class="line"><span class="comment"># 功能：接口获取IP段 → 替换配置 → 验证重启 → 备份回滚</span></span><br><span class="line"><span class="comment"># 适配：CentOS + 宝塔面板 + 海外业务</span></span><br><span class="line"><span class="comment"># ==============================================</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------- 核心参数（需根据实际调整）----------------------</span></span><br><span class="line">NGINX_CONF=<span class="string">&quot;/www/server/panel/vhost/nginx/python_music.conf&quot;</span>  <span class="comment"># 你的站点Nginx配置路径</span></span><br><span class="line">IP_API=<span class="string">&quot;https://api.edgeone.ai/ips?version=v4&amp;area=overseas&quot;</span>  <span class="comment"># 海外IPv4专属接口</span></span><br><span class="line">TIMEOUT=30  <span class="comment"># 接口超时时间（秒）</span></span><br><span class="line">RETRY=3     <span class="comment"># 接口失败重试次数</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 临时文件（规避Sed长字符串语法问题）</span></span><br><span class="line">TMP_IP_FILE=$(<span class="built_in">mktemp</span>)</span><br><span class="line">TMP_NGINX_CONF=$(<span class="built_in">mktemp</span>)</span><br><span class="line">BACKUP_CONF=<span class="string">&quot;<span class="variable">$&#123;NGINX_CONF&#125;</span>.bak.<span class="subst">$(date +%Y%m%d)</span>&quot;</span>  <span class="comment"># 带日期的配置备份文件</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 1. 接口获取IP段（适配海外网络，带重试+证书兼容）</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] 从官方接口获取EdgeOne海外IP段（最多重试<span class="variable">$RETRY</span>次）...&quot;</span></span><br><span class="line"><span class="keyword">for</span> ((i=<span class="number">1</span>; i&lt;=<span class="variable">$RETRY</span>; i++)); <span class="keyword">do</span></span><br><span class="line">    <span class="comment"># curl参数说明：跳过证书校验、设置超时、失败重试</span></span><br><span class="line">    curl -s --connect-timeout 10 --max-time <span class="variable">$TIMEOUT</span> \</span><br><span class="line">         --retry <span class="variable">$RETRY</span> --retry-delay 3 \</span><br><span class="line">         --insecure \</span><br><span class="line">         <span class="string">&quot;<span class="variable">$IP_API</span>&quot;</span> &gt; <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 验证获取结果：非空且包含标准IPv4网段格式</span></span><br><span class="line">    <span class="keyword">if</span> [ -s <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span> ] &amp;&amp; grep -qE <span class="string">&#x27;^([0-9]&#123;1,3&#125;\.)&#123;3&#125;[0-9]&#123;1,3&#125;/[0-9]&#123;1,2&#125;$&#x27;</span> <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span>; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] ✅ 第<span class="variable">$i</span>次尝试成功，获取到有效IP段&quot;</span></span><br><span class="line">        <span class="built_in">break</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] ❌ 第<span class="variable">$i</span>次尝试失败，正在重试...&quot;</span></span><br><span class="line">        <span class="keyword">if</span> [ <span class="variable">$i</span> -eq <span class="variable">$RETRY</span> ]; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] ❌ 所有重试失败，无法获取IP段，退出脚本&quot;</span></span><br><span class="line">            <span class="built_in">rm</span> -f <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span> <span class="string">&quot;<span class="variable">$TMP_NGINX_CONF</span>&quot;</span></span><br><span class="line">            <span class="built_in">exit</span> 1</span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 过滤无效IP段并生成Nginx格式配置</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] 过滤无效IP段并生成配置...&quot;</span></span><br><span class="line"><span class="comment"># 仅保留标准IPv4网段，剔除异常内容</span></span><br><span class="line">grep -E <span class="string">&#x27;^([0-9]&#123;1,3&#125;\.)&#123;3&#125;[0-9]&#123;1,3&#125;/[0-9]&#123;1,2&#125;$&#x27;</span> <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span> &gt; <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>.tmp&quot;</span></span><br><span class="line"><span class="built_in">mv</span> <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>.tmp&quot;</span> <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查过滤后是否有有效IP段</span></span><br><span class="line"><span class="keyword">if</span> [ ! -s <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] ❌ 过滤后无有效IP段，退出脚本&quot;</span></span><br><span class="line">    <span class="built_in">rm</span> -f <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span> <span class="string">&quot;<span class="variable">$TMP_NGINX_CONF</span>&quot;</span></span><br><span class="line">    <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 生成带标记的完整IP配置（写入临时文件，避免Sed拼接过长）</span></span><br><span class="line">IP_CONFIG_FILE=$(<span class="built_in">mktemp</span>)</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;# ====== EDGEONE_IP_START 自动更新标记（请勿删除）======&quot;</span> &gt; <span class="string">&quot;<span class="variable">$IP_CONFIG_FILE</span>&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;# 自动更新时间：<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>&quot;</span> &gt;&gt; <span class="string">&quot;<span class="variable">$IP_CONFIG_FILE</span>&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;# EdgeOne 海外节点IP段（从官方接口获取）&quot;</span> &gt;&gt; <span class="string">&quot;<span class="variable">$IP_CONFIG_FILE</span>&quot;</span></span><br><span class="line"><span class="comment"># 将IP段转换为Nginx的set_real_ip_from格式</span></span><br><span class="line">awk <span class="string">&#x27;&#123;print &quot;    set_real_ip_from &quot; $0 &quot;;&quot;&#125;&#x27;</span> <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span> &gt;&gt; <span class="string">&quot;<span class="variable">$IP_CONFIG_FILE</span>&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;# ====== EDGEONE_IP_END 自动更新标记（请勿删除）======&quot;</span> &gt;&gt; <span class="string">&quot;<span class="variable">$IP_CONFIG_FILE</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 备份原Nginx配置（避免替换失败导致服务异常）</span></span><br><span class="line"><span class="built_in">cp</span> <span class="string">&quot;<span class="variable">$NGINX_CONF</span>&quot;</span> <span class="string">&quot;<span class="variable">$BACKUP_CONF</span>&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] ✅ 原配置已备份至：<span class="variable">$BACKUP_CONF</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 替换配置文件中的IP段（文件分段拼接，兼容长内容）</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] 开始替换Nginx配置中的IP段...&quot;</span></span><br><span class="line"><span class="comment"># 步骤1：提取标记前的内容</span></span><br><span class="line">sed -n <span class="string">&#x27;/# ====== EDGEONE_IP_START 自动更新标记（请勿删除）======/q;p&#x27;</span> <span class="string">&quot;<span class="variable">$NGINX_CONF</span>&quot;</span> &gt; <span class="string">&quot;<span class="variable">$TMP_NGINX_CONF</span>&quot;</span></span><br><span class="line"><span class="comment"># 步骤2：插入新生成的IP配置</span></span><br><span class="line"><span class="built_in">cat</span> <span class="string">&quot;<span class="variable">$IP_CONFIG_FILE</span>&quot;</span> &gt;&gt; <span class="string">&quot;<span class="variable">$TMP_NGINX_CONF</span>&quot;</span></span><br><span class="line"><span class="comment"># 步骤3：提取标记后的内容（排除结束标记本身，避免重复）</span></span><br><span class="line">sed -n <span class="string">&#x27;/# ====== EDGEONE_IP_END 自动更新标记（请勿删除）======/,$!d;/# ====== EDGEONE_IP_END 自动更新标记（请勿删除）======/!p&#x27;</span> <span class="string">&quot;<span class="variable">$NGINX_CONF</span>&quot;</span> &gt;&gt; <span class="string">&quot;<span class="variable">$TMP_NGINX_CONF</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 覆盖原配置并保持文件权限（宝塔默认Nginx配置权限为644）</span></span><br><span class="line"><span class="built_in">cp</span> <span class="string">&quot;<span class="variable">$TMP_NGINX_CONF</span>&quot;</span> <span class="string">&quot;<span class="variable">$NGINX_CONF</span>&quot;</span></span><br><span class="line"><span class="built_in">chmod</span> 644 <span class="string">&quot;<span class="variable">$NGINX_CONF</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 验证配置替换结果</span></span><br><span class="line"><span class="keyword">if</span> grep -q <span class="string">&quot;set_real_ip_from 43.174.0.0/15;&quot;</span> <span class="string">&quot;<span class="variable">$NGINX_CONF</span>&quot;</span>; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] ✅ IP段成功写入Nginx配置&quot;</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] ❌ IP段写入失败，正在恢复原配置...&quot;</span></span><br><span class="line">    <span class="built_in">cp</span> <span class="string">&quot;<span class="variable">$BACKUP_CONF</span>&quot;</span> <span class="string">&quot;<span class="variable">$NGINX_CONF</span>&quot;</span></span><br><span class="line">    <span class="built_in">rm</span> -f <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span> <span class="string">&quot;<span class="variable">$TMP_NGINX_CONF</span>&quot;</span> <span class="string">&quot;<span class="variable">$IP_CONFIG_FILE</span>&quot;</span></span><br><span class="line">    <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 6. 测试Nginx配置并重启服务</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] 测试Nginx配置语法有效性...&quot;</span></span><br><span class="line"><span class="keyword">if</span> /www/server/nginx/sbin/nginx -t; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] ✅ 配置语法正确，重启Nginx服务...&quot;</span></span><br><span class="line">    /www/server/nginx/sbin/nginx -s reload</span><br><span class="line">    IP_COUNT=$(<span class="built_in">wc</span> -l &lt; <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span>)</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] 🎉 全部操作完成！本次共更新<span class="variable">$IP_COUNT</span>个海外IP段&quot;</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;[<span class="subst">$(date +&#x27;%Y-%m-%d %H:%M:%S&#x27;)</span>] ❌ 配置语法错误，正在恢复原配置...&quot;</span></span><br><span class="line">    <span class="built_in">cp</span> <span class="string">&quot;<span class="variable">$BACKUP_CONF</span>&quot;</span> <span class="string">&quot;<span class="variable">$NGINX_CONF</span>&quot;</span></span><br><span class="line">    /www/server/nginx/sbin/nginx -s reload</span><br><span class="line">    <span class="built_in">rm</span> -f <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span> <span class="string">&quot;<span class="variable">$TMP_NGINX_CONF</span>&quot;</span> <span class="string">&quot;<span class="variable">$IP_CONFIG_FILE</span>&quot;</span></span><br><span class="line">    <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 清理临时文件</span></span><br><span class="line"><span class="built_in">rm</span> -f <span class="string">&quot;<span class="variable">$TMP_IP_FILE</span>&quot;</span> <span class="string">&quot;<span class="variable">$TMP_NGINX_CONF</span>&quot;</span> <span class="string">&quot;<span class="variable">$IP_CONFIG_FILE</span>&quot;</span></span><br></pre></td></tr></table></figure><h3 id="4-2-脚本授权与测试运行"><a href="#4-2-脚本授权与测试运行" class="headerlink" title="4.2 脚本授权与测试运行"></a>4.2 脚本授权与测试运行</h3><p>通过 SSH 工具登录服务器（如 Xshell、FinalShell），执行以下命令完成脚本授权和手动测试，确保脚本可正常运行：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 1. 给脚本添加可执行权限（仅需执行一次）</span></span><br><span class="line"><span class="built_in">chmod</span> +x /root/update_edgeone_ips.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 手动执行脚本，验证流程是否正常</span></span><br><span class="line">/root/update_edgeone_ips.sh</span><br></pre></td></tr></table></figure><h4 id="成功运行的输出示例："><a href="#成功运行的输出示例：" class="headerlink" title="成功运行的输出示例："></a>成功运行的输出示例：</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[2025-12-01 09:30:00] 从官方接口获取EdgeOne海外IP段（最多重试3次）...</span><br><span class="line">[2025-12-01 09:30:02] ✅ 第1次尝试成功，获取到有效IP段</span><br><span class="line">[2025-12-01 09:30:02] 过滤无效IP段并生成配置...</span><br><span class="line">[2025-12-01 09:30:02] ✅ 原配置已备份至：/www/server/panel/vhost/nginx/python_music.conf.bak.20251201</span><br><span class="line">[2025-12-01 09:30:03] 开始替换Nginx配置中的IP段...</span><br><span class="line">[2025-12-01 09:30:03] ✅ IP段成功写入Nginx配置</span><br><span class="line">[2025-12-01 09:30:04] 测试Nginx配置语法有效性...</span><br><span class="line">nginx: the configuration file /www/server/nginx/conf/nginx.conf syntax is ok</span><br><span class="line">nginx: configuration file /www/server/nginx/conf/nginx.conf test is successful</span><br><span class="line">[2025-12-01 09:30:04] ✅ 配置语法正确，重启Nginx服务...</span><br><span class="line">[2025-12-01 09:30:05] 🎉 全部操作完成！本次共更新36个海外IP段</span><br></pre></td></tr></table></figure><h4 id="常见测试失败处理："><a href="#常见测试失败处理：" class="headerlink" title="常见测试失败处理："></a>常见测试失败处理：</h4><p>若提示 “接口访问失败”，可先执行 <code>curl -v --insecure https://api.edgeone.ai/ips?version=v4&amp;area=overseas</code> 查看具体错误（如防火墙拦截、DNS 解析异常），针对性解决后再重新测试。</p><h3 id="4-3-配置定时任务（实现每月自动更新）"><a href="#4-3-配置定时任务（实现每月自动更新）" class="headerlink" title="4.3 配置定时任务（实现每月自动更新）"></a>4.3 配置定时任务（实现每月自动更新）</h3><p>手动执行脚本可验证效果，但为避免 IP 段过期，需配置定时任务每月自动更新，通过宝塔面板操作更直观：</p><ol><li>登录宝塔面板，进入「计划任务」模块，点击「添加任务」；</li><li>任务类型：选择「Shell 脚本」（核心类型，执行服务器命令）；</li><li>执行周期：选择「每月」，日期设为「1」，时间选凌晨空闲时段（如 03:00，避免影响业务）；</li><li>脚本路径：填写 <code>/root/update_edgeone_ips.sh</code>（脚本实际存放路径）；</li><li>日志设置：勾选「保存日志」，日志路径默认即可（便于后续排查执行情况）；</li><li>点击「添加」，系统会自动生成定时任务，每月 1 号凌晨自动执行 IP 段更新。</li></ol><blockquote><p>⚠️ 注意事项：添加定时任务后，建议手动执行一次「测试」按钮，确认任务可正常触发脚本，避免因路径错误、权限问题导致定时执行失败。</p></blockquote><h2 id="五、常见问题与故障排查"><a href="#五、常见问题与故障排查" class="headerlink" title="五、常见问题与故障排查"></a>五、常见问题与故障排查</h2><p>配置过程中可能遇到接口访问失败、真实 IP 解析无效等问题，以下是高频问题的解决方案，按排查优先级排序：</p><h3 id="5-1-接口无法获取-IP-段（脚本第一步失败）"><a href="#5-1-接口无法获取-IP-段（脚本第一步失败）" class="headerlink" title="5.1 接口无法获取 IP 段（脚本第一步失败）"></a>5.1 接口无法获取 IP 段（脚本第一步失败）</h3><div class="table-container"><table><thead><tr><th>错误现象</th><th>核心原因</th><th>解决方案</th></tr></thead><tbody><tr><td>curl: (7) Connection timed out</td><td>服务器防火墙 / 安全组禁止 443 出站</td><td>1. 宝塔面板「安全」→「防火墙」→ 放行「443 出站」；2. 云厂商控制台（如腾讯云）安全组添加规则：允许 TCP:443 出站</td></tr><tr><td>curl: (6) Could not resolve host</td><td>DNS 解析异常，无法识别 api.edgeone.ai</td><td>修改服务器 DNS 配置：编辑 <code>/etc/resolv.conf</code>，添加 <code>nameserver 8.8.8.8</code>（谷歌 DNS）或 <code>nameserver 1.1.1.1</code>（Cloudflare DNS），保存后执行 <code>systemctl restart network</code></td></tr><tr><td>返回内容为空或非 IP 段</td><td>接口参数错误，未指定海外区域</td><td>确认脚本中 IP_API 为 <code>https://api.edgeone.ai/ips?version=v4&amp;area=overseas</code>，而非默认的全球接口</td></tr></tbody></table></div><h3 id="5-2-真实-IP-解析不生效（日志仍为节点-IP）"><a href="#5-2-真实-IP-解析不生效（日志仍为节点-IP）" class="headerlink" title="5.2 真实 IP 解析不生效（日志仍为节点 IP）"></a>5.2 真实 IP 解析不生效（日志仍为节点 IP）</h3><p>这是最常见的问题，按以下步骤逐步排查，可覆盖 90% 以上场景：</p><ol><li><strong>检查 EdgeOne 透传配置</strong>：登录 EdgeOne 控制台，确认「回源配置」→「HTTP 头配置」中已开启「透传 X-Forwarded-For 头」，且未开启「源站保护」（开启后会隐藏节点 IP）；</li><li><strong>验证 Nginx 核心规则</strong>：确保 <code>real_ip_header X-Forwarded-For;</code> 和 <code>real_ip_recursive on;</code> 已配置，且位于所有 <code>set_real_ip_from</code> 规则之后（顺序错误会导致解析失效）；</li><li><strong>确认 IP 段完整性</strong>：在访问日志中复制记录的 “节点 IP”，通过 EdgeOne 控制台「工具」→「IP 归属查询」确认是否为 EdgeOne 节点 IP，若不在配置的 IP 段中，说明 IP 段未更新，需重新执行脚本；</li><li><strong>重启 Nginx 服务</strong>：配置修改后未重启服务，执行 <code>www/server/nginx/sbin/nginx -s reload</code> 确保配置生效。</li></ol><h3 id="5-3-脚本替换配置后-Nginx-启动失败"><a href="#5-3-脚本替换配置后-Nginx-启动失败" class="headerlink" title="5.3 脚本替换配置后 Nginx 启动失败"></a>5.3 脚本替换配置后 Nginx 启动失败</h3><p>主要原因是配置语法错误，脚本已内置回滚机制，会自动恢复原配置，可通过以下方式定位问题：</p><ol><li>查看 Nginx 错误日志：执行 <code>cat /www/server/nginx/logs/error.log</code>，重点关注 “syntax error” 相关内容；</li><li>检查 IP 段格式：手动打开 Nginx 配置文件，确认标记内的 <code>set_real_ip_from</code> 规则是否均为 <code>set_real_ip_from x.x.x.x/x;</code> 格式，无多余空格、逗号；</li><li>恢复原配置：若脚本未自动回滚，执行 <code>cp 备份文件路径 原配置路径</code>（如 <code>cp /www/server/panel/vhost/nginx/python_music.conf.bak.20251201 /www/server/panel/vhost/nginx/python_music.conf</code>），重启 Nginx 即可恢复服务。</li></ol><h2 id="六、总结与扩展"><a href="#六、总结与扩展" class="headerlink" title="六、总结与扩展"></a>六、总结与扩展</h2><p>本文提供的 EdgeOne 海外节点真实 IP 解析方案，核心是「信任代理节点 IP + 提取 X-Forwarded-For 头真实 IP」，通过 “手动配置验证 + 自动化脚本更新” 的组合，既保证了新手可快速上手，又解决了运维过程中 IP 段过期的痛点。</p><h3 id="方案扩展场景"><a href="#方案扩展场景" class="headerlink" title="方案扩展场景"></a>方案扩展场景</h3><ol><li><strong>多站点适配</strong>：若服务器部署多个站点，可复制脚本并修改 <code>NGINX_CONF</code> 路径，或在脚本中添加多路径数组循环处理；</li><li><strong>IPv6 场景</strong>：将脚本中 <code>IP_API</code> 改为 <code>https://api.edgeone.ai/ips?version=v6&amp;area=overseas</code>，同时在 Nginx 配置中添加 <code>real_ip_header X-Forwarded-For;</code>（IPv6 解析规则与 IPv4 一致）；</li><li><strong>非宝塔环境</strong>：修改脚本中 Nginx 启动命令（如 <code>systemctl restart nginx</code>）和配置路径（如 <code>/etc/nginx/conf.d/your_site.conf</code>）即可适配。</li></ol><p>通过本文方案配置后，Nginx 日志将准确记录用户真实 IP，可正常开展地域访问统计、异常 IP 拦截等运维工作，且每月自动更新 IP 段，实现 “一次配置，长期稳定” 的效果。</p>]]></content>
    
    
    <summary type="html">在使用腾讯云 EdgeOne 海外节点加速海外业务时，很多开发者会遇到一个核心问题：Nginx 日志中记录的是 EdgeOne 节点 IP 而非用户真实 IP，导致无法进行用户行为分析、地域统计和异常访问拦截。本文将从「问题原理」「手动配置」「自动化更新」「故障排查」四个维度，提供一套适配 Nginx + 宝塔面板的完整解决方案，新手也能快速上手。</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=6c57936a-9fd8-6773-a97e-d2058ce28f37" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>Pymusic一款Flask框架开发的音乐网站</title>
    <link href="https://blog.itrf.cn/posts/3a52/"/>
    <id>https://blog.itrf.cn/posts/3a52/</id>
    <published>2025-11-14T04:49:40.000Z</published>
    <updated>2026-09-03T07:47:09.997Z</updated>
    
    <content type="html"><![CDATA[<p>体验网址：<a href="https://music.686909.xyz">音悦台</a><br>开源仓库：<a href="https://github.com/itrfcn/Pymusic">Pymusic</a></p><p>🌐 项目简介</p><p>这是一个基于Flask框架开发的个人音乐播放器项目，旨在模拟网易云音乐的核心功能并提供简洁的用户体验。该项目集成了网易云音乐API，支持音乐搜索、歌单管理、用户认证等功能，为音乐爱好者提供一个轻量级的音乐播放和管理平台。</p><p>🌟 核心亮点</p><ul><li>完全免费：基于网易云音乐资源爬虫，无付费门槛</li><li>安全稳定：依托网易云底层资源，播放与数据传输更可靠</li><li>全端适配：自适应各类分辨率设备，手机、电脑端体验一致</li><li>开放接口：提供公开 API 接口，支持二次开发（详见「关于我们」页面）</li><li>轻量高效：无冗余功能，聚焦核心需求，加载速度快</li></ul><p>🚀 功能特性</p><ul><li>精准音乐搜索：支持按歌曲名、歌手检索网易云资源，分页展示结果，快速定位目标音乐</li><li>灵活歌单管理：创建、编辑、删除个人歌单，自由添加 / 移除歌曲，支持自定义歌单封面</li><li>安全用户系统：完整的注册、登录与身份验证机制，保障个人歌单与播放数据安全</li><li>历史记录管理：自动记录播放轨迹，支持查看最近播放，内置数据清理工具优化性能</li><li>热门音乐推荐：同步网易云热门歌单，一键发现优质音乐，拓宽听歌边界</li><li>基础播放功能：支持歌曲播放 / 暂停、实时歌词显示、封面查看，满足日常听歌需求</li><li><p>🛠 技术栈选型</p></li><li><p>后端框架：Flask 2.x（Python）</p></li><li>数据库：MySQL 5.7+（自定义工具类操作）</li><li>前端技术：HTML5 + CSS3 + JavaScript</li><li>API 集成：网易云音乐非官方 API（爬虫获取）</li><li>认证机制：Session-based 身份认证</li><li>配置管理：环境变量 + 配置文件双模式</li><li>工具依赖：自定义数据库、文件及 JS 操作工具类</li></ul><p>💡 项目心得</p><p>Flask 框架在 API 开发和小型网站场景中优势明显，开发效率高、配置灵活。但当项目业务复杂度提升或页面交互增多时，其灵活性会带来代码耦合度高的问题。<br>此外，不建议用原生 CSS/JS 开发 SPA 类型网站，后续维护成本高、功能扩展难度大，推荐引入前端框架拆分组件。<br>这是我的第一个 Flask 项目，可能存在潜在 bug，欢迎大家提交 Issues 反馈问题，或提出优化建议，共同完善项目～</p>]]></content>
    
    
    <summary type="html">这是一个基于Flask框架开发的个人音乐播放器项目，旨在模拟网易云音乐的核心功能并提供简洁的用户体验。该项目集成了网易云音乐API，支持音乐搜索、歌单管理、用户认证等功能，为音乐爱好者提供一个轻量级的音乐播放和管理平台。</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=bfbfee73-eb27-a2c5-e402-9f3665a78bdb" type="image"/>
    
    
    <category term="原创项目" scheme="https://blog.itrf.cn/tags/%E5%8E%9F%E5%88%9B%E9%A1%B9%E7%9B%AE/"/>
    
  </entry>
  
  <entry>
    <title>href属性中/的不同作用</title>
    <link href="https://blog.itrf.cn/posts/b272/"/>
    <id>https://blog.itrf.cn/posts/b272/</id>
    <published>2025-11-09T04:58:36.000Z</published>
    <updated>2026-01-27T16:35:31.837Z</updated>
    
    <content type="html"><![CDATA[<h1 id="首部-的作用"><a href="#首部-的作用" class="headerlink" title="首部/的作用"></a>首部/的作用</h1><p>加/ 是根路径相对定位，不加/ 是当前路径相对定位，最终指向的 URL 会完全不同</p><h2 id="1-加-：从网站根目录开始找"><a href="#1-加-：从网站根目录开始找" class="headerlink" title="1. 加 /：从网站根目录开始找"></a>1. 加 /：从网站根目录开始找</h2><p>href=”/xxx” 中的 / 代表网站的根目录（比如 <a href="https://www.example.com/）">https://www.example.com/）</a> ，不管当前页面在哪个层级，都会从根目录出发拼接路径。示例：</p><ul><li>网站根目录：<a href="https://www.example.com/">https://www.example.com/</a></li><li>当前页面 URL：<a href="https://www.example.com/a/b/c.html">https://www.example.com/a/b/c.html</a></li><li>href=”/about” → 最终指向：<a href="https://www.example.com/about">https://www.example.com/about</a></li><li>href=”/static/css/style.css” → 最终指向：<a href="https://www.example.com/static/css/style.css">https://www.example.com/static/css/style.css</a></li></ul><h2 id="2-不加-：从当前页面所在目录开始找"><a href="#2-不加-：从当前页面所在目录开始找" class="headerlink" title="2. 不加 /：从当前页面所在目录开始找"></a>2. 不加 /：从当前页面所在目录开始找</h2><p>href=”xxx” 是相对于当前页面的 URL 路径拼接，相当于 “在当前文件夹里找目标资源”。示例（和上面同个网站、同个当前页面）：</p><ul><li>当前页面 URL：<a href="https://www.example.com/a/b/c.html">https://www.example.com/a/b/c.html</a> （当前目录是 /a/b/）</li><li>href=”about” → 最终指向：<a href="https://www.example.com/a/b/about">https://www.example.com/a/b/about</a></li><li>href=”static/css/style.css” → 最终指向：<a href="https://www.example.com/a/b/static/css/style.css">https://www.example.com/a/b/static/css/style.css</a></li></ul><h1 id="尾部-的作用"><a href="#尾部-的作用" class="headerlink" title="尾部/的作用"></a>尾部/的作用</h1><p>核心区别是是否默认指向 “目录” ，会影响服务器解析和最终跳转的 URL</p><h2 id="1-末尾加-：表示-“这是一个目录”"><a href="#1-末尾加-：表示-“这是一个目录”" class="headerlink" title="1. 末尾加 /：表示 “这是一个目录”"></a>1. 末尾加 /：表示 “这是一个目录”</h2><p>当 URL 末尾带 / 时，浏览器和服务器会默认把它当作目录路径，优先查找目录下的默认索引文件（如 index.html、index.php 等）。示例：</p><ul><li>href=”<a href="https://www.example.com/about/">https://www.example.com/about/</a>“ → 服务器会去 about 目录下找默认索引文件（如 about/index.html）</li><li>若当前页面是 <a href="https://www.example.com/a/">https://www.example.com/a/</a> ， href=”b/“ → 指向 <a href="https://www.example.com/a/b/">https://www.example.com/a/b/</a> （目录）</li></ul><h2 id="2-末尾不加-：表示-“可能是文件，也可能是目录”"><a href="#2-末尾不加-：表示-“可能是文件，也可能是目录”" class="headerlink" title="2. 末尾不加 /：表示 “可能是文件，也可能是目录”"></a>2. 末尾不加 /：表示 “可能是文件，也可能是目录”</h2><p>末尾不带 / 时，服务器会先尝试当作文件路径查找；若找不到对应文件，会自动做 “重定向”—— 在末尾加上 /，再按目录查找默认索引文件。示例：</p><ul><li>href=”<a href="https://www.example.com/about">https://www.example.com/about</a>“ → 服务器先找 about 文件，找不到就重定向到 <a href="https://www.example.com/about/">https://www.example.com/about/</a></li><li>若当前页面是 <a href="https://www.example.com/a/">https://www.example.com/a/</a> ， href=”b” → 先找 a/b 文件，找不到就重定向到 <a href="https://www.example.com/a/b/">https://www.example.com/a/b/</a></li></ul>]]></content>
    
    
    <summary type="html">href属性中/的不同作用</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=b9f9675b-949b-d5c8-8572-6fb080db1c39" type="image"/>
    
    
    <category term="零碎知识" scheme="https://blog.itrf.cn/tags/%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>Linux 常用命令大全</title>
    <link href="https://blog.itrf.cn/posts/bf2c/"/>
    <id>https://blog.itrf.cn/posts/bf2c/</id>
    <published>2025-10-29T05:06:38.000Z</published>
    <updated>2026-01-27T16:35:29.563Z</updated>
    
    <content type="html"><![CDATA[<p>本文整理了 Linux 系统中<strong>文件操作、权限管理、系统监控、网络操作</strong>等高频命令，覆盖新手入门到日常运维场景，格式清晰可直接查阅。</p><h2 id="一、文件与目录操作命令（最基础高频）"><a href="#一、文件与目录操作命令（最基础高频）" class="headerlink" title="一、文件与目录操作命令（最基础高频）"></a>一、文件与目录操作命令（最基础高频）</h2><h3 id="1-ls-列出目录内容"><a href="#1-ls-列出目录内容" class="headerlink" title="1. ls - 列出目录内容"></a>1. ls - 列出目录内容</h3><p><strong>功能</strong>：列出指定目录下的文件 / 目录，默认列出当前目录。<strong>核心参数</strong>：</p><ul><li><code>-l</code>（长格式）：显示文件权限、所有者、大小、修改时间等详细信息。</li><li><code>-a</code>（all）：显示所有文件（含隐藏文件，以<code>.</code>开头）。</li><li><code>-h</code>（human-readable）：以人类可读格式显示文件大小（如 KB、MB）。</li><li><code>-r</code>（reverse）：反向排序（默认按字母升序）。</li><li><code>-t</code>（time）：按修改时间排序（最新在前）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">ls</span> -lha  <span class="comment"># 长格式显示当前目录所有文件，含隐藏文件，大小人性化显示</span></span><br><span class="line"><span class="built_in">ls</span> /home/user  <span class="comment"># 列出指定目录/home/user下的内容</span></span><br></pre></td></tr></table></figure></p><h3 id="2-cd-切换工作目录"><a href="#2-cd-切换工作目录" class="headerlink" title="2. cd - 切换工作目录"></a>2. cd - 切换工作目录</h3><p><strong>功能</strong>：切换当前终端的工作目录。<strong>核心用法</strong>：</p><ul><li><code>cd 目录路径</code>：切换到指定目录（绝对路径 / 相对路径）。</li><li><code>cd ~</code> 或 <code>cd</code>：切换到当前用户的家目录（如<code>/home/ubuntu</code>）。</li><li><code>cd ..</code>：切换到上级目录。</li><li><code>cd -</code>：切换到上一次所在的目录。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> /etc/nginx  <span class="comment"># 绝对路径切换到nginx配置目录</span></span><br><span class="line"><span class="built_in">cd</span> ../logs  <span class="comment"># 相对路径切换到上级目录的logs子目录</span></span><br><span class="line"><span class="built_in">cd</span> ~/Documents  <span class="comment"># 切换到家目录的Documents文件夹</span></span><br></pre></td></tr></table></figure></p><h3 id="3-pwd-显示当前工作目录"><a href="#3-pwd-显示当前工作目录" class="headerlink" title="3. pwd - 显示当前工作目录"></a>3. pwd - 显示当前工作目录</h3><p><strong>功能</strong>：打印当前终端所在的绝对路径。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">pwd</span>  <span class="comment"># 输出如：/home/user/Documents</span></span><br></pre></td></tr></table></figure></p><h3 id="4-mkdir-创建目录"><a href="#4-mkdir-创建目录" class="headerlink" title="4. mkdir - 创建目录"></a>4. mkdir - 创建目录</h3><p><strong>功能</strong>：创建新目录。<strong>核心参数</strong>：</p><ul><li><code>-p</code>（parents）：递归创建多级目录（父目录不存在时自动创建）。</li><li><code>-m</code>（mode）：创建时指定目录权限（如 755）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> <span class="built_in">test</span>  <span class="comment"># 创建单个目录test</span></span><br><span class="line"><span class="built_in">mkdir</span> -p data/logs  <span class="comment"># 递归创建data目录及子目录logs</span></span><br><span class="line"><span class="built_in">mkdir</span> -m 700 private  <span class="comment"># 创建权限为700（仅所有者可读写执行）的目录</span></span><br></pre></td></tr></table></figure></p><h3 id="5-rm-删除文件-目录"><a href="#5-rm-删除文件-目录" class="headerlink" title="5. rm - 删除文件 / 目录"></a>5. rm - 删除文件 / 目录</h3><p><strong>功能</strong>：删除文件或目录，慎用（无回收站，删除后难恢复）。<strong>核心参数</strong>：</p><ul><li><code>-f</code>（force）：强制删除（忽略不存在的文件，不提示）。</li><li><code>-r</code>（recursive）：递归删除目录及目录内所有内容（删除目录必须用）。</li><li><code>-i</code>（interactive）：删除前提示确认（默认部分系统启用）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">rm</span> file.txt  <span class="comment"># 删除单个文件file.txt</span></span><br><span class="line"><span class="built_in">rm</span> -rf <span class="built_in">dir</span>/  <span class="comment"># 强制递归删除dir目录及所有内容（高危命令，谨慎使用）</span></span><br><span class="line"><span class="built_in">rm</span> -i *.<span class="built_in">log</span>  <span class="comment"># 删除所有.log文件，删除前逐一提示</span></span><br></pre></td></tr></table></figure></p><h3 id="6-cp-复制文件-目录"><a href="#6-cp-复制文件-目录" class="headerlink" title="6. cp - 复制文件 / 目录"></a>6. cp - 复制文件 / 目录</h3><p><strong>功能</strong>：复制文件或目录到指定位置。<strong>核心参数</strong>：</p><ul><li><code>-r</code>（recursive）：递归复制目录（复制目录必须用）。</li><li><code>-p</code>（preserve）：保留文件的权限、所有者、修改时间等属性。</li><li><code>-f</code>（force）：强制覆盖目标文件（无需提示）。</li><li><code>-a</code>（archive）：等价于<code>-dR --preserve=all</code>，适用于备份（保留所有属性 + 递归复制）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cp</span> file.txt /home/user/  <span class="comment"># 复制file.txt到/home/user目录下</span></span><br><span class="line"><span class="built_in">cp</span> -r <span class="built_in">dir</span>/ /backup/  <span class="comment"># 递归复制dir目录到/backup目录下</span></span><br><span class="line"><span class="built_in">cp</span> -a docs/ docs_backup/  <span class="comment"># 备份docs目录，保留所有属性</span></span><br></pre></td></tr></table></figure></p><h3 id="7-mv-移动-重命名文件-目录"><a href="#7-mv-移动-重命名文件-目录" class="headerlink" title="7. mv - 移动 / 重命名文件 / 目录"></a>7. mv - 移动 / 重命名文件 / 目录</h3><p><strong>功能</strong>：移动文件 / 目录到指定位置，或重命名文件 / 目录。<strong>核心参数</strong>：</p><ul><li><code>-f</code>（force）：强制覆盖目标文件（无提示）。</li><li><code>-i</code>（interactive）：覆盖前提示确认。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mv</span> file.txt newfile.txt  <span class="comment"># 重命名file.txt为newfile.txt</span></span><br><span class="line"><span class="built_in">mv</span> data/ /home/user/  <span class="comment"># 移动data目录到/home/user目录下</span></span><br><span class="line"><span class="built_in">mv</span> -f old.log /tmp/  <span class="comment"># 强制移动old.log到/tmp目录，覆盖已存在文件</span></span><br></pre></td></tr></table></figure></p><h3 id="8-touch-创建空文件-修改文件时间戳"><a href="#8-touch-创建空文件-修改文件时间戳" class="headerlink" title="8. touch - 创建空文件 / 修改文件时间戳"></a>8. touch - 创建空文件 / 修改文件时间戳</h3><p><strong>功能</strong>：创建空白文件，或更新文件的访问时间、修改时间（默认两者都更新）。<strong>核心参数</strong>：</p><ul><li><code>-d</code>（date）：指定时间戳（如<code>2024-01-01 10:00</code>）。</li><li><code>-m</code>（modify）：仅更新修改时间。</li><li><code>-a</code>（access）：仅更新访问时间。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">touch</span> test.txt  <span class="comment"># 创建空文件test.txt</span></span><br><span class="line"><span class="built_in">touch</span> -d <span class="string">&quot;2024-05-20 14:30&quot;</span> old.txt  <span class="comment"># 修改old.txt的时间戳为指定时间</span></span><br><span class="line"><span class="built_in">touch</span> -a readme.md  <span class="comment"># 仅更新readme.md的访问时间</span></span><br></pre></td></tr></table></figure></p><h3 id="9-cat-查看文件内容"><a href="#9-cat-查看文件内容" class="headerlink" title="9. cat - 查看文件内容"></a>9. cat - 查看文件内容</h3><p><strong>功能</strong>：连接并打印文件内容（适合查看小文件）。<strong>核心参数</strong>：</p><ul><li><code>-n</code>（number）：显示行号。</li><li><code>-b</code>（number-nonblank）：仅对非空行显示行号。</li><li><code>-T</code>（show-tabs）：显示制表符（Tab）为<code>^I</code>。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cat</span> /etc/hosts  <span class="comment"># 查看hosts文件内容</span></span><br><span class="line"><span class="built_in">cat</span> -n test.txt  <span class="comment"># 显示test.txt内容并标注行号</span></span><br><span class="line"><span class="built_in">cat</span> file1.txt file2.txt &gt; file3.txt  <span class="comment"># 合并file1和file2的内容到file3（覆盖）</span></span><br></pre></td></tr></table></figure></p><h3 id="10-more-less-分页查看大文件"><a href="#10-more-less-分页查看大文件" class="headerlink" title="10. more/less - 分页查看大文件"></a>10. more/less - 分页查看大文件</h3><p><strong>功能</strong>：分页显示文件内容（适合大文件，避免内容刷屏）。<strong>核心区别</strong>：</p><ul><li><code>more</code>：仅支持向下滚动（空格翻页，Enter 换行，q 退出）。</li><li><code>less</code>：支持上下滚动、搜索（更灵活，<code>/关键词</code>搜索，<code>n</code>下一个，<code>q</code>退出）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">more /var/log/syslog  <span class="comment"># 分页查看系统日志（仅向下翻）</span></span><br><span class="line">less /etc/passwd  <span class="comment"># 分页查看用户配置文件（支持上下滚动和搜索）</span></span><br></pre></td></tr></table></figure></p><h3 id="11-head-tail-查看文件首尾内容"><a href="#11-head-tail-查看文件首尾内容" class="headerlink" title="11. head/tail - 查看文件首尾内容"></a>11. head/tail - 查看文件首尾内容</h3><p><strong>功能</strong>：<code>head</code>查看文件开头部分，<code>tail</code>查看文件结尾部分。<strong>核心参数</strong>：</p><ul><li><code>-n N</code>：显示前 N 行（head）或后 N 行（tail），默认 10 行。</li><li><code>-f</code>（follow）：实时跟踪文件更新（tail 专属，适合查看日志）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">head</span> -5 /etc/passwd  <span class="comment"># 查看passwd文件前5行</span></span><br><span class="line"><span class="built_in">tail</span> -10 /var/log/nginx/access.log  <span class="comment"># 查看nginx访问日志最后10行</span></span><br><span class="line"><span class="built_in">tail</span> -f /var/log/syslog  <span class="comment"># 实时跟踪系统日志更新（按Ctrl+C退出）</span></span><br></pre></td></tr></table></figure></p><h3 id="12-find-查找文件-目录"><a href="#12-find-查找文件-目录" class="headerlink" title="12. find - 查找文件 / 目录"></a>12. find - 查找文件 / 目录</h3><p><strong>功能</strong>：按路径、名称、大小、时间等条件查找文件 / 目录。<strong>核心参数</strong>：</p><ul><li><code>-name &quot;文件名&quot;</code>：按名称查找（支持通配符<code>*</code>、<code>?</code>）。</li><li><code>-path &quot;路径&quot;</code>：按路径查找。</li><li><code>-size [+/-]大小</code>：按大小查找（如<code>+100M</code>大于 100MB，<code>-10K</code>小于 10KB）。</li><li><code>-mtime [+/-]天数</code>：按修改时间查找（如<code>+7</code>修改时间超过 7 天，<code>-1</code>24 小时内修改）。</li><li><code>-type f/d</code>：指定查找类型（f 文件，d 目录）。</li><li><code>-exec 命令 {} \;</code>：对查找结果执行指定命令（<code>{}</code>代指查找结果）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">find /home -name <span class="string">&quot;*.txt&quot;</span>  <span class="comment"># 在/home目录下查找所有.txt文件</span></span><br><span class="line">find /etc -<span class="built_in">type</span> d -name <span class="string">&quot;nginx&quot;</span>  <span class="comment"># 在/etc目录下查找名为nginx的目录</span></span><br><span class="line">find /var/log -size +50M  <span class="comment"># 在/var/log目录下查找大于50MB的文件</span></span><br><span class="line">find /tmp -mtime +3 -<span class="built_in">exec</span> <span class="built_in">rm</span> -rf &#123;&#125; \;  <span class="comment"># 删除/tmp目录下3天前修改的文件/目录</span></span><br></pre></td></tr></table></figure></p><h3 id="13-grep-文本搜索工具"><a href="#13-grep-文本搜索工具" class="headerlink" title="13. grep - 文本搜索工具"></a>13. grep - 文本搜索工具</h3><p><strong>功能</strong>：在文件或命令输出中搜索匹配的字符串 / 正则表达式。<strong>核心参数</strong>：</p><ul><li><code>-i</code>（ignore-case）：忽略大小写。</li><li><code>-n</code>（line-number）：显示匹配行的行号。</li><li><code>-v</code>（invert-match）：反向匹配（显示不包含关键词的行）。</li><li><code>-r</code>（recursive）：递归搜索目录下所有文件。</li><li><code>-E</code>（extended-regexp）：支持扩展正则表达式（无需转义<code>+</code>、<code>|</code>等）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">grep <span class="string">&quot;error&quot;</span> /var/log/syslog  <span class="comment"># 在系统日志中搜索&quot;error&quot;字符串</span></span><br><span class="line">grep -<span class="keyword">in</span> <span class="string">&quot;warning&quot;</span> /etc/nginx/nginx.conf  <span class="comment"># 忽略大小写搜索nginx配置文件中的&quot;warning&quot;，显示行号</span></span><br><span class="line">grep -r <span class="string">&quot;test&quot;</span> /home/user  <span class="comment"># 递归搜索/home/user目录下所有文件中的&quot;test&quot;</span></span><br><span class="line">ps aux | grep <span class="string">&quot;nginx&quot;</span>  <span class="comment"># 结合管道，搜索nginx进程</span></span><br></pre></td></tr></table></figure></p><h2 id="二、文件权限与用户管理命令"><a href="#二、文件权限与用户管理命令" class="headerlink" title="二、文件权限与用户管理命令"></a>二、文件权限与用户管理命令</h2><h3 id="1-chmod-修改文件-目录权限"><a href="#1-chmod-修改文件-目录权限" class="headerlink" title="1. chmod - 修改文件 / 目录权限"></a>1. chmod - 修改文件 / 目录权限</h3><p><strong>功能</strong>：修改文件或目录的访问权限（所有者、组用户、其他用户的读 / 写 / 执行权限）。<strong>权限表示</strong>：</p><ul><li><strong>数字法</strong>（常用）：<code>r=4</code>、<code>w=2</code>、<code>x=1</code>，权限组合为三者之和（如<code>755</code>= 所有者 rwx，组用户 rx，其他用户 rx）。</li><li><strong>符号法</strong>：<code>u</code>（所有者）、<code>g</code>（组用户）、<code>o</code>（其他用户）、<code>a</code>（所有用户）；<code>+</code>（添加权限）、<code>-</code>（移除权限）、<code>=</code>（设置权限）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">chmod</span> 755 script.sh  <span class="comment"># 设为所有者可读写执行，其他用户只读执行</span></span><br><span class="line"><span class="built_in">chmod</span> 644 file.txt  <span class="comment"># 设为所有者读写，其他用户只读（文件常用权限）</span></span><br><span class="line"><span class="built_in">chmod</span> u+x test.sh  <span class="comment"># 给所有者添加执行权限</span></span><br><span class="line"><span class="built_in">chmod</span> g-w <span class="built_in">dir</span>/  <span class="comment"># 给组用户移除写权限</span></span><br></pre></td></tr></table></figure></p><h3 id="2-chown-修改文件-目录所有者"><a href="#2-chown-修改文件-目录所有者" class="headerlink" title="2. chown - 修改文件 / 目录所有者"></a>2. chown - 修改文件 / 目录所有者</h3><p><strong>功能</strong>：修改文件或目录的所有者（需 root 权限或文件所有者）。<strong>核心用法</strong>：<code>chown [所有者][:组] 文件/目录</code></p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">chown</span> user:user file.txt  <span class="comment"># 将file.txt的所有者和组改为user</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chown</span> root /home/user/test.sh  <span class="comment"># 将test.sh的所有者改为root</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chown</span> -R www-data:www-data /var/www  <span class="comment"># 递归修改/var/www目录的所有者为www-data（web服务常用）</span></span><br></pre></td></tr></table></figure></p><h3 id="3-chgrp-修改文件-目录所属组"><a href="#3-chgrp-修改文件-目录所属组" class="headerlink" title="3. chgrp - 修改文件 / 目录所属组"></a>3. chgrp - 修改文件 / 目录所属组</h3><p><strong>功能</strong>：单独修改文件或目录的所属组（<code>chown</code>可替代，但该命令更专注）。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">chgrp</span> <span class="built_in">users</span> file.txt  <span class="comment"># 将file.txt的所属组改为users</span></span><br><span class="line"><span class="built_in">chgrp</span> -R staff /home/shared  <span class="comment"># 递归修改/shared目录的所属组为staff</span></span><br></pre></td></tr></table></figure></p><h3 id="4-useradd-创建新用户"><a href="#4-useradd-创建新用户" class="headerlink" title="4. useradd - 创建新用户"></a>4. useradd - 创建新用户</h3><p><strong>功能</strong>：创建 Linux 系统用户（需 root 权限）。<strong>核心参数</strong>：</p><ul><li><code>-m</code>（create-home）：自动创建用户家目录（如<code>/home/用户名</code>）。</li><li><code>-s shell路径</code>：指定用户登录 shell（如<code>/bin/bash</code>，默认<code>/bin/sh</code>）。</li><li><code>-g 组名</code>：指定用户初始组。</li><li><code>-G 组名1,组名2</code>：指定用户附加组。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> useradd -m -s /bin/bash newuser  <span class="comment"># 创建新用户newuser，自动创建家目录，登录shell为bash</span></span><br><span class="line"><span class="built_in">sudo</span> useradd -g staff -G <span class="built_in">sudo</span> newuser  <span class="comment"># 创建用户newuser，初始组为staff，附加组为sudo（获取管理员权限）</span></span><br></pre></td></tr></table></figure></p><h3 id="5-passwd-修改用户密码"><a href="#5-passwd-修改用户密码" class="headerlink" title="5. passwd - 修改用户密码"></a>5. passwd - 修改用户密码</h3><p><strong>功能</strong>：修改当前用户或其他用户的密码（修改其他用户需 root 权限）。<strong>核心参数</strong>：</p><ul><li><code>-l</code>（lock）：锁定用户（禁止登录）。</li><li><code>-u</code>（unlock）：解锁用户。</li><li><code>-d</code>（delete）：删除用户密码（登录无需密码）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">passwd  <span class="comment"># 修改当前用户密码（输入时密码不显示）</span></span><br><span class="line"><span class="built_in">sudo</span> passwd newuser  <span class="comment"># 管理员修改newuser的密码</span></span><br><span class="line"><span class="built_in">sudo</span> passwd -l newuser  <span class="comment"># 锁定newuser用户</span></span><br></pre></td></tr></table></figure></p><h3 id="6-userdel-删除用户"><a href="#6-userdel-删除用户" class="headerlink" title="6. userdel - 删除用户"></a>6. userdel - 删除用户</h3><p><strong>功能</strong>：删除系统用户（需 root 权限）。<strong>核心参数</strong>：</p><ul><li><code>-r</code>（remove）：递归删除用户家目录和邮件目录（默认仅删除用户账号）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> userdel -r newuser  <span class="comment"># 彻底删除newuser用户及家目录</span></span><br></pre></td></tr></table></figure></p><h3 id="7-groupadd-groupdel-组管理"><a href="#7-groupadd-groupdel-组管理" class="headerlink" title="7. groupadd/groupdel - 组管理"></a>7. groupadd/groupdel - 组管理</h3><p><strong>功能</strong>：<code>groupadd</code>创建用户组，<code>groupdel</code>删除用户组（需 root 权限）。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> groupadd dev  <span class="comment"># 创建名为dev的用户组</span></span><br><span class="line"><span class="built_in">sudo</span> groupdel dev  <span class="comment"># 删除dev用户组</span></span><br></pre></td></tr></table></figure></p><h3 id="8-id-whoami-w-查看用户信息"><a href="#8-id-whoami-w-查看用户信息" class="headerlink" title="8. id/whoami/w - 查看用户信息"></a>8. id/whoami/w - 查看用户信息</h3><ul><li><code>id</code>：显示当前用户的 UID、GID 及所属组。</li><li><code>whoami</code>：显示当前登录用户名。</li><li><code>w</code>：显示当前系统所有登录用户及他们的操作。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">id</span>  <span class="comment"># 输出如：uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom)...</span></span><br><span class="line"><span class="built_in">whoami</span>  <span class="comment"># 输出当前用户名，如user</span></span><br><span class="line">w  <span class="comment"># 显示所有登录用户及进程信息</span></span><br></pre></td></tr></table></figure></p><h2 id="三、系统管理与进程命令"><a href="#三、系统管理与进程命令" class="headerlink" title="三、系统管理与进程命令"></a>三、系统管理与进程命令</h2><h3 id="1-ps-查看进程状态"><a href="#1-ps-查看进程状态" class="headerlink" title="1. ps - 查看进程状态"></a>1. ps - 查看进程状态</h3><p><strong>功能</strong>：列出当前系统的进程信息。<strong>核心参数</strong>：</p><ul><li><code>aux</code>：显示所有用户的所有进程（最常用组合）。</li><li><code>-ef</code>：显示进程的父进程 ID（PPID）、进程 ID（PID）等详细信息。</li><li><code>-u 用户名</code>：仅显示指定用户的进程。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ps aux  <span class="comment"># 查看所有进程（USER用户名、PID进程ID、%CPU CPU使用率、%MEM内存使用率、COMMAND命令）</span></span><br><span class="line">ps aux | grep <span class="string">&quot;nginx&quot;</span>  <span class="comment"># 查看nginx相关进程</span></span><br><span class="line">ps -ef | grep <span class="string">&quot;python&quot;</span>  <span class="comment"># 查看python进程及父进程ID</span></span><br></pre></td></tr></table></figure></p><h3 id="2-top-实时监控系统资源与进程"><a href="#2-top-实时监控系统资源与进程" class="headerlink" title="2. top - 实时监控系统资源与进程"></a>2. top - 实时监控系统资源与进程</h3><p><strong>功能</strong>：实时动态显示系统 CPU、内存、进程等资源使用情况（默认每 3 秒刷新）。<strong>交互快捷键</strong>：</p><ul><li><code>P</code>：按 CPU 使用率排序（默认）。</li><li><code>M</code>：按内存使用率排序。</li><li><code>N</code>：按进程 ID（PID）排序。</li><li><code>k</code>：终止指定 PID 的进程。</li><li><code>q</code>：退出 top。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">top  <span class="comment"># 启动实时监控</span></span><br><span class="line">top -u user  <span class="comment"># 仅监控用户user的进程</span></span><br></pre></td></tr></table></figure></p><h3 id="3-kill-终止进程"><a href="#3-kill-终止进程" class="headerlink" title="3. kill - 终止进程"></a>3. kill - 终止进程</h3><p><strong>功能</strong>：向进程发送信号，实现终止、重启等操作（需知道进程 PID，root 可终止任意进程）。<strong>核心信号</strong>：</p><ul><li><code>-15</code>（SIGTERM，默认）：优雅终止进程（允许进程清理资源）。</li><li><code>-9</code>（SIGKILL）：强制终止进程（立即杀死，无清理，慎用）。</li><li><code>-1</code>（SIGHUP）：重启进程（部分服务如 nginx、apache 支持）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">kill</span> 1234  <span class="comment"># 向PID为1234的进程发送SIGTERM信号（优雅终止）</span></span><br><span class="line"><span class="built_in">kill</span> -9 1234  <span class="comment"># 强制终止PID为1234的进程</span></span><br><span class="line"><span class="built_in">kill</span> -1 8080  <span class="comment"># 重启PID为8080的服务进程</span></span><br><span class="line">killall nginx  <span class="comment"># 终止所有nginx进程（按进程名终止，需安装psmisc包）</span></span><br></pre></td></tr></table></figure></p><h3 id="4-systemctl-系统服务管理（Systemd-系统）"><a href="#4-systemctl-系统服务管理（Systemd-系统）" class="headerlink" title="4. systemctl - 系统服务管理（Systemd 系统）"></a>4. systemctl - 系统服务管理（Systemd 系统）</h3><p><strong>功能</strong>：管理系统服务（启动、停止、重启、设置开机自启，适用于 Ubuntu 16.04+、CentOS 7+）。<strong>核心用法</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">systemctl start 服务名    <span class="comment"># 启动服务</span></span><br><span class="line">systemctl stop 服务名     <span class="comment"># 停止服务</span></span><br><span class="line">systemctl restart 服务名  <span class="comment"># 重启服务</span></span><br><span class="line">systemctl status 服务名   <span class="comment"># 查看服务状态</span></span><br><span class="line">systemctl <span class="built_in">enable</span> 服务名   <span class="comment"># 设置开机自启</span></span><br><span class="line">systemctl <span class="built_in">disable</span> 服务名  <span class="comment"># 取消开机自启</span></span><br><span class="line">systemctl list-unit-files --<span class="built_in">type</span>=service  <span class="comment"># 列出所有服务及开机自启状态</span></span><br></pre></td></tr></table></figure></p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl start nginx  <span class="comment"># 启动nginx服务</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl stop mysql  <span class="comment"># 停止mysql服务</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl restart sshd  <span class="comment"># 重启ssh服务</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl status docker  <span class="comment"># 查看docker服务状态</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> nginx  <span class="comment"># 设置nginx开机自启</span></span><br></pre></td></tr></table></figure></p><h3 id="5-df-查看磁盘空间使用情况"><a href="#5-df-查看磁盘空间使用情况" class="headerlink" title="5. df - 查看磁盘空间使用情况"></a>5. df - 查看磁盘空间使用情况</h3><p><strong>功能</strong>：显示文件系统的磁盘空间使用情况（总容量、已用、可用、挂载点）。<strong>核心参数</strong>：</p><ul><li><code>-h</code>（human-readable）：人性化显示大小（KB/MB/GB）。</li><li><code>-T</code>（type）：显示文件系统类型（如 ext4、xfs）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">df</span> -h  <span class="comment"># 查看所有挂载的文件系统磁盘使用情况</span></span><br><span class="line"><span class="built_in">df</span> -h /home  <span class="comment"># 查看/home目录所在文件系统的磁盘使用情况</span></span><br><span class="line"><span class="built_in">df</span> -T  <span class="comment"># 显示文件系统类型</span></span><br></pre></td></tr></table></figure></p><h3 id="6-du-查看目录-文件磁盘占用"><a href="#6-du-查看目录-文件磁盘占用" class="headerlink" title="6. du - 查看目录 / 文件磁盘占用"></a>6. du - 查看目录 / 文件磁盘占用</h3><p><strong>功能</strong>：计算文件或目录占用的磁盘空间（与<code>df</code>区别：<code>df</code>看文件系统整体，<code>du</code>看具体文件 / 目录）。<strong>核心参数</strong>：</p><ul><li><code>-h</code>：人性化显示大小。</li><li><code>-s</code>（summarize）：仅显示总大小（不显示子目录详情）。</li><li><code>-c</code>（total）：显示总计大小。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">du</span> -sh /home/user  <span class="comment"># 查看/home/user目录总占用空间</span></span><br><span class="line"><span class="built_in">du</span> -h /var/log/  <span class="comment"># 查看/var/log目录及子目录的占用详情</span></span><br><span class="line"><span class="built_in">du</span> -hc *.txt  <span class="comment"># 查看所有.txt文件的占用大小，并显示总计</span></span><br></pre></td></tr></table></figure></p><h3 id="7-free-查看内存使用情况"><a href="#7-free-查看内存使用情况" class="headerlink" title="7. free - 查看内存使用情况"></a>7. free - 查看内存使用情况</h3><p><strong>功能</strong>：显示系统内存（物理内存、交换分区）的使用情况。<strong>核心参数</strong>：</p><ul><li><code>-h</code>：人性化显示大小。</li><li><code>-m</code>：以 MB 为单位显示（默认 KB）。</li><li><code>-g</code>：以 GB 为单位显示。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">free -h  <span class="comment"># 人性化显示内存使用情况</span></span><br><span class="line">free -m  <span class="comment"># 以MB为单位显示</span></span><br></pre></td></tr></table></figure></p><h3 id="8-uname-查看系统信息"><a href="#8-uname-查看系统信息" class="headerlink" title="8. uname - 查看系统信息"></a>8. uname - 查看系统信息</h3><p><strong>功能</strong>：显示系统内核版本、主机名、硬件架构等信息。<strong>核心参数</strong>：</p><ul><li><code>-a</code>（all）：显示所有系统信息（最常用）。</li><li><code>-r</code>：仅显示内核版本。</li><li><code>-n</code>：仅显示主机名。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">uname</span> -a  <span class="comment"># 输出如：Linux ubuntu 5.4.0-150-generic #167-Ubuntu SMP Wed May 24 15:51:08 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux</span></span><br><span class="line"><span class="built_in">uname</span> -r  <span class="comment"># 输出内核版本：5.4.0-150-generic</span></span><br></pre></td></tr></table></figure></p><h3 id="9-hostname-查看-修改主机名"><a href="#9-hostname-查看-修改主机名" class="headerlink" title="9. hostname - 查看 / 修改主机名"></a>9. hostname - 查看 / 修改主机名</h3><p><strong>功能</strong>：显示或临时修改系统主机名（永久修改需编辑配置文件）。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hostname  <span class="comment"># 显示当前主机名</span></span><br><span class="line"><span class="built_in">sudo</span> hostname new-hostname  <span class="comment"># 临时修改主机名为new-hostname（重启失效）</span></span><br><span class="line"><span class="built_in">sudo</span> hostnamectl set-hostname new-hostname  <span class="comment"># 永久修改主机名（Systemd系统，无需重启）</span></span><br></pre></td></tr></table></figure></p><h3 id="10-date-查看-修改系统时间"><a href="#10-date-查看-修改系统时间" class="headerlink" title="10. date - 查看 / 修改系统时间"></a>10. date - 查看 / 修改系统时间</h3><p><strong>功能</strong>：显示或修改系统日期和时间。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">date</span>  <span class="comment"># 显示当前系统时间（如：Wed Oct 18 15:30:22 CST 2023）</span></span><br><span class="line"><span class="built_in">date</span> <span class="string">&quot;+%Y-%m-%d %H:%M:%S&quot;</span>  <span class="comment"># 自定义格式显示（年-月-日 时:分:秒）</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">date</span> -s <span class="string">&quot;2024-01-01 10:00:00&quot;</span>  <span class="comment"># 修改系统时间为指定时间</span></span><br></pre></td></tr></table></figure></p><h2 id="四、网络操作命令"><a href="#四、网络操作命令" class="headerlink" title="四、网络操作命令"></a>四、网络操作命令</h2><h3 id="1-ifconfig-ip-网络接口配置"><a href="#1-ifconfig-ip-网络接口配置" class="headerlink" title="1. ifconfig/ip - 网络接口配置"></a>1. ifconfig/ip - 网络接口配置</h3><p><strong>功能</strong>：查看或配置网络接口（IP 地址、子网掩码、MAC 地址等）。</p><ul><li><code>ifconfig</code>：传统命令（部分系统需安装 net-tools 包）。</li><li><code>ip</code>：新一代命令（功能更全，推荐使用）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ifconfig  <span class="comment"># 查看所有网络接口信息</span></span><br><span class="line">ip addr  <span class="comment"># 查看IP地址（等价于ifconfig）</span></span><br><span class="line">ip <span class="built_in">link</span> <span class="built_in">set</span> eth0 up/down  <span class="comment"># 启用/禁用eth0网卡</span></span><br><span class="line"><span class="built_in">sudo</span> ifconfig eth0 192.168.1.100 netmask 255.255.255.0  <span class="comment"># 临时设置eth0的IP地址</span></span><br></pre></td></tr></table></figure></p><h3 id="2-ping-测试网络连通性"><a href="#2-ping-测试网络连通性" class="headerlink" title="2. ping - 测试网络连通性"></a>2. ping - 测试网络连通性</h3><p><strong>功能</strong>：向目标主机发送 ICMP 请求包，测试网络是否可达。<strong>核心参数</strong>：</p><ul><li><code>-c 次数</code>：指定发送包的次数（默认无限发送，Ctrl+C 终止）。</li><li><code>-i 间隔</code>：指定发送间隔（默认 1 秒）。</li><li><code>-s 大小</code>：指定数据包大小。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ping www.baidu.com  <span class="comment"># 测试与百度的连通性（无限发送）</span></span><br><span class="line">ping -c 4 192.168.1.1  <span class="comment"># 向网关发送4个数据包测试连通性</span></span><br></pre></td></tr></table></figure></p><h3 id="3-netstat-ss-查看网络连接状态"><a href="#3-netstat-ss-查看网络连接状态" class="headerlink" title="3. netstat/ss - 查看网络连接状态"></a>3. netstat/ss - 查看网络连接状态</h3><p><strong>功能</strong>：显示系统的网络连接、端口监听、进程关联等信息。</p><ul><li><p><code>netstat</code>：传统命令（需安装 net-tools 包）。</p></li><li><p><code>ss</code>：新一代命令（更快、功能更全）。<br>核心参数：</p></li><li><p><code>-t</code>（tcp）：仅显示 TCP 连接。</p></li><li><p><code>-u</code>（udp）：仅显示 UDP 连接。</p></li><li><p><code>-l</code>（listen）：仅显示监听状态的端口。</p></li><li><p><code>-n</code>（numeric）：显示 IP 和端口号（不解析域名）。</p></li><li><p><code>-p</code>（program）：显示关联的进程 PID 和名称（需 root 权限）。</p></li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">netstat -tlnp  <span class="comment"># 查看所有监听的TCP端口及关联进程</span></span><br><span class="line">ss -ulnp  <span class="comment"># 查看所有监听的UDP端口及关联进程</span></span><br><span class="line">netstat -an | grep 80  <span class="comment"># 查看80端口的连接状态</span></span><br></pre></td></tr></table></figure></p><h3 id="4-curl-wget-网络下载工具"><a href="#4-curl-wget-网络下载工具" class="headerlink" title="4. curl/wget - 网络下载工具"></a>4. curl/wget - 网络下载工具</h3><ul><li><strong>curl</strong>：多功能网络工具，支持 HTTP/HTTPS/FTP 等协议，可下载文件、发送请求。</li><li><strong>wget</strong>：专注于文件下载（支持断点续传、后台下载）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># curl用法</span></span><br><span class="line">curl https://www.baidu.com  <span class="comment"># 访问百度并输出HTML内容</span></span><br><span class="line">curl -O https://example.com/file.zip  <span class="comment"># 下载文件并保存为原文件名</span></span><br><span class="line">curl -L https://example.com/redirect  <span class="comment"># 跟随重定向下载</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># wget用法</span></span><br><span class="line">wget https://example.com/file.zip  <span class="comment"># 下载文件</span></span><br><span class="line">wget -c https://example.com/large.zip  <span class="comment"># 断点续传下载大文件</span></span><br><span class="line">wget -b https://example.com/bigfile.iso  <span class="comment"># 后台下载（日志保存到wget-log）</span></span><br></pre></td></tr></table></figure></p><h3 id="5-scp-远程文件复制"><a href="#5-scp-远程文件复制" class="headerlink" title="5. scp - 远程文件复制"></a>5. scp - 远程文件复制</h3><p><strong>功能</strong>：通过 SSH 协议在本地和远程主机之间复制文件 / 目录（加密传输）。<strong>核心参数</strong>：</p><ul><li><code>-r</code>：递归复制目录。</li><li><code>-P 端口</code>：指定远程主机 SSH 端口（默认 22）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 本地文件复制到远程主机</span></span><br><span class="line">scp localfile.txt user@remote-ip:/home/user/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 远程文件复制到本地</span></span><br><span class="line">scp user@remote-ip:/home/user/remotefile.txt ./</span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制目录（加-r）</span></span><br><span class="line">scp -r localdir/ user@remote-ip:/home/user/</span><br></pre></td></tr></table></figure></p><h3 id="6-ssh-远程登录"><a href="#6-ssh-远程登录" class="headerlink" title="6. ssh - 远程登录"></a>6. ssh - 远程登录</h3><p><strong>功能</strong>：通过 SSH 协议远程登录 Linux 主机（加密传输，安全）。<strong>核心参数</strong>：</p><ul><li><code>-p 端口</code>：指定远程主机 SSH 端口（默认 22）。</li><li><code>-X</code>：启用 X11 转发（远程图形界面）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ssh user@remote-ip  <span class="comment"># 登录远程主机（默认端口22）</span></span><br><span class="line">ssh -p 2222 user@remote-ip  <span class="comment"># 登录指定端口2222的远程主机</span></span><br><span class="line">ssh -X user@remote-ip  <span class="comment"># 登录并启用图形界面转发</span></span><br></pre></td></tr></table></figure></p><h2 id="五、压缩与解压命令"><a href="#五、压缩与解压命令" class="headerlink" title="五、压缩与解压命令"></a>五、压缩与解压命令</h2><h3 id="1-tar-归档-压缩工具（最常用）"><a href="#1-tar-归档-压缩工具（最常用）" class="headerlink" title="1. tar - 归档 / 压缩工具（最常用）"></a>1. tar - 归档 / 压缩工具（最常用）</h3><p><strong>功能</strong>：将多个文件 / 目录归档为一个 tar 包，可结合 gzip/bzip2/xz 压缩。<strong>核心参数</strong>（记忆口诀：打包压缩<code>czf</code>，解压<code>xzf</code>）：</p><ul><li><code>-c</code>（create）：创建归档包。</li><li><code>-x</code>（extract）：解压归档包。</li><li><code>-z</code>（gzip）：用 gzip 压缩 / 解压（后缀<code>.tar.gz</code>或<code>.tgz</code>）。</li><li><code>-j</code>（bzip2）：用 bzip2 压缩 / 解压（后缀<code>.tar.bz2</code>）。</li><li><code>-J</code>（xz）：用 xz 压缩 / 解压（后缀<code>.tar.xz</code>）。</li><li><code>-f</code>（file）：指定归档文件名（必须放在参数最后）。</li><li><code>-v</code>（verbose）：显示压缩 / 解压过程。</li><li><code>-C</code>（directory）：指定解压目录（默认当前目录）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 压缩（tar.gz格式）</span></span><br><span class="line">tar -czvf file.tar.gz dir1/ file1.txt  <span class="comment"># 将dir1和file1.txt压缩为file.tar.gz</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 解压（tar.gz格式）</span></span><br><span class="line">tar -xzvf file.tar.gz  <span class="comment"># 解压到当前目录</span></span><br><span class="line">tar -xzvf file.tar.gz -C /home/user/  <span class="comment"># 解压到/home/user目录</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 压缩（tar.bz2格式）</span></span><br><span class="line">tar -cjvf file.tar.bz2 dir1/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 解压（tar.xz格式）</span></span><br><span class="line">tar -xJvf file.tar.xz</span><br></pre></td></tr></table></figure></p><h3 id="2-zip-unzip-ZIP-格式压缩-解压"><a href="#2-zip-unzip-ZIP-格式压缩-解压" class="headerlink" title="2. zip/unzip - ZIP 格式压缩 / 解压"></a>2. zip/unzip - ZIP 格式压缩 / 解压</h3><p><strong>功能</strong>：处理 ZIP 格式的压缩文件（跨平台兼容，Windows/Linux 通用）。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 压缩（需安装zip包）</span></span><br><span class="line">zip -r file.zip dir1/ file1.txt  <span class="comment"># 递归压缩dir1和file1.txt为file.zip</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 解压（需安装unzip包）</span></span><br><span class="line">unzip file.zip  <span class="comment"># 解压到当前目录</span></span><br><span class="line">unzip file.zip -d /home/user/  <span class="comment"># 解压到指定目录</span></span><br><span class="line">unzip -l file.zip  <span class="comment"># 查看压缩包内的文件列表</span></span><br></pre></td></tr></table></figure></p><h3 id="3-gzip-gunzip-GZ-格式压缩-解压"><a href="#3-gzip-gunzip-GZ-格式压缩-解压" class="headerlink" title="3. gzip/gunzip - GZ 格式压缩 / 解压"></a>3. gzip/gunzip - GZ 格式压缩 / 解压</h3><p><strong>功能</strong>：压缩单个文件（不能压缩目录），压缩后原文件删除（后缀<code>.gz</code>）。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">gzip file.txt  <span class="comment"># 压缩file.txt为file.txt.gz（原文件删除）</span></span><br><span class="line">gzip -k file.txt  <span class="comment"># 压缩并保留原文件</span></span><br><span class="line">gunzip file.txt.gz  <span class="comment"># 解压为file.txt（原.gz文件删除）</span></span><br></pre></td></tr></table></figure></p><h2 id="六、其他实用命令"><a href="#六、其他实用命令" class="headerlink" title="六、其他实用命令"></a>六、其他实用命令</h2><h3 id="1-history-查看命令历史"><a href="#1-history-查看命令历史" class="headerlink" title="1. history - 查看命令历史"></a>1. history - 查看命令历史</h3><p><strong>功能</strong>：显示当前用户执行过的命令历史（默认保存 1000 条，可通过<code>HISTSIZE</code>调整）。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">history</span>  <span class="comment"># 查看所有命令历史</span></span><br><span class="line"><span class="built_in">history</span> 10  <span class="comment"># 查看最近10条命令</span></span><br><span class="line">!123  <span class="comment"># 执行历史中第123条命令</span></span><br><span class="line">!<span class="built_in">ls</span>  <span class="comment"># 执行上一条以ls开头的命令</span></span><br><span class="line">Ctrl+R  <span class="comment"># 搜索命令历史（输入关键词匹配）</span></span><br></pre></td></tr></table></figure></p><h3 id="2-alias-设置命令别名"><a href="#2-alias-设置命令别名" class="headerlink" title="2. alias - 设置命令别名"></a>2. alias - 设置命令别名</h3><p><strong>功能</strong>：为常用命令设置简化别名（临时生效，永久生效需写入配置文件）。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">alias</span> ll=<span class="string">&#x27;ls -lha&#x27;</span>  <span class="comment"># 临时设置ll为ls -lha的别名</span></span><br><span class="line"><span class="built_in">alias</span> <span class="built_in">rm</span>=<span class="string">&#x27;rm -i&#x27;</span>  <span class="comment"># 临时设置rm为带提示的删除（安全）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 永久生效（写入用户配置文件）</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;alias ll=&#x27;ls -lha&#x27;&quot;</span> &gt;&gt; ~/.bashrc  <span class="comment"># 对当前用户生效</span></span><br><span class="line"><span class="built_in">source</span> ~/.bashrc  <span class="comment"># 立即生效（无需重启终端）</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">unalias</span> ll  <span class="comment"># 取消别名</span></span><br></pre></td></tr></table></figure></p><h3 id="3-sudo-临时获取-root-权限"><a href="#3-sudo-临时获取-root-权限" class="headerlink" title="3. sudo - 临时获取 root 权限"></a>3. sudo - 临时获取 root 权限</h3><p><strong>功能</strong>：允许普通用户执行需 root 权限的命令（需在<code>/etc/sudoers</code>中配置权限）。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update  <span class="comment"># 以root权限更新软件源（Debian/Ubuntu）</span></span><br><span class="line"><span class="built_in">sudo</span> yum install nginx  <span class="comment"># 以root权限安装nginx（CentOS/RHEL）</span></span><br><span class="line"><span class="built_in">sudo</span> -i  <span class="comment"># 切换到root用户（需输入当前用户密码）</span></span><br></pre></td></tr></table></figure></p><h3 id="4-apt-yum-dnf-包管理命令"><a href="#4-apt-yum-dnf-包管理命令" class="headerlink" title="4. apt/yum/dnf - 包管理命令"></a>4. apt/yum/dnf - 包管理命令</h3><p><strong>功能</strong>：Linux 系统软件包管理工具（自动解决依赖关系）。</p><ul><li><code>apt</code>：Debian/Ubuntu 系列系统（<code>apt-get</code>的简化版）。</li><li><code>yum</code>：CentOS 6/RHEL 6 系列（已逐步被 dnf 替代）。</li><li><code>dnf</code>：CentOS 7+/RHEL 7 + 系列（yum 的升级版）。</li></ul><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Debian/Ubuntu（apt）</span></span><br><span class="line"><span class="built_in">sudo</span> apt update  <span class="comment"># 更新软件源索引</span></span><br><span class="line"><span class="built_in">sudo</span> apt install nginx  <span class="comment"># 安装nginx</span></span><br><span class="line"><span class="built_in">sudo</span> apt remove nginx  <span class="comment"># 卸载nginx（保留配置文件）</span></span><br><span class="line"><span class="built_in">sudo</span> apt purge nginx  <span class="comment"># 彻底卸载nginx（删除配置文件）</span></span><br><span class="line"><span class="built_in">sudo</span> apt upgrade  <span class="comment"># 升级所有已安装软件</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># CentOS/RHEL（yum）</span></span><br><span class="line"><span class="built_in">sudo</span> yum update  <span class="comment"># 更新软件源</span></span><br><span class="line"><span class="built_in">sudo</span> yum install nginx  <span class="comment"># 安装nginx</span></span><br><span class="line"><span class="built_in">sudo</span> yum remove nginx  <span class="comment"># 卸载nginx</span></span><br><span class="line"><span class="built_in">sudo</span> yum list installed  <span class="comment"># 列出已安装软件</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># CentOS 8+/RHEL 8+（dnf）</span></span><br><span class="line"><span class="built_in">sudo</span> dnf install nginx</span><br><span class="line"><span class="built_in">sudo</span> dnf remove nginx</span><br></pre></td></tr></table></figure></p><h3 id="5-echo-输出字符串-变量"><a href="#5-echo-输出字符串-变量" class="headerlink" title="5. echo - 输出字符串 / 变量"></a>5. echo - 输出字符串 / 变量</h3><p><strong>功能</strong>：在终端输出指定字符串或环境变量的值。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Hello Linux&quot;</span>  <span class="comment"># 输出字符串</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$PATH</span>  <span class="comment"># 输出环境变量PATH的值（系统命令搜索路径）</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;当前用户：<span class="variable">$USER</span>&quot;</span>  <span class="comment"># 输出当前用户名（变量需加$）</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;abc&quot;</span> &gt; file.txt  <span class="comment"># 将字符串写入文件（覆盖原有内容）</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;def&quot;</span> &gt;&gt; file.txt  <span class="comment"># 将字符串追加到文件末尾</span></span><br></pre></td></tr></table></figure></p><h3 id="6-wc-统计文本行数-字数-字节数"><a href="#6-wc-统计文本行数-字数-字节数" class="headerlink" title="6. wc - 统计文本行数 / 字数 / 字节数"></a>6. wc - 统计文本行数 / 字数 / 字节数</h3><p><strong>功能</strong>：统计文件或命令输出的行数（<code>-l</code>）、单词数（<code>-w</code>）、字节数（<code>-c</code>）。</p><p><strong>示例</strong>：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">wc</span> -l file.txt  <span class="comment"># 统计file.txt的行数</span></span><br><span class="line"><span class="built_in">wc</span> -w file.txt  <span class="comment"># 统计单词数</span></span><br><span class="line"><span class="built_in">wc</span> -c file.txt  <span class="comment"># 统计字节数</span></span><br><span class="line"><span class="built_in">cat</span> file.txt | <span class="built_in">wc</span> -l  <span class="comment"># 统计命令输出的行数</span></span><br></pre></td></tr></table></figure></p><h2 id="七、命令行实用技巧"><a href="#七、命令行实用技巧" class="headerlink" title="七、命令行实用技巧"></a>七、命令行实用技巧</h2><ol><li>管道符 <code>|</code>：将前一个命令的输出作为后一个命令的输入<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ps aux | grep nginx  <span class="comment"># 筛选nginx进程</span></span><br></pre></td></tr></table></figure></li><li><p>重定向</p><ul><li><p>覆盖写入文件</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">ls</span> &gt; list.txt</span><br></pre></td></tr></table></figure></li><li><p>追加写入文件</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;test&quot;</span> &gt;&gt; list.txt</span><br></pre></td></tr></table></figure></li><li><p>从文件读取输入</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">wc</span> -l &lt; file.txt</span><br></pre></td></tr></table></figure></li></ul></li></ol><ol><li><p>通配符</p><ul><li><code>*</code>：匹配任意多个字符（如<code>*.txt</code>所有.txt 文件）</li><li><code>?</code>：匹配单个字符（如<code>file?.txt</code>匹配 file1.txt、file2.txt）</li><li><code>[]</code>：匹配括号内的任意一个字符（如<code>file[1-3].txt</code>匹配 file1-3.txt）</li></ul></li><li><p>快捷键</p><ul><li><code>Tab</code>：自动补全命令 / 文件名 / 目录名（高效必备）</li><li><code>Ctrl+C</code>：终止当前运行的命令</li><li><code>Ctrl+D</code>：退出当前终端 / 登录会话（等价于 exit）</li><li><code>Ctrl+Z</code>：暂停当前命令（用<code>fg</code>恢复到前台，<code>bg</code>放到后台）</li></ul></li></ol>]]></content>
    
    
    <summary type="html">Linux 常用命令大全</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=903e14d8-9137-b647-9f3d-7026b1b0278f" type="image"/>
    
    
    <category term="学习笔记" scheme="https://blog.itrf.cn/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="Linux" scheme="https://blog.itrf.cn/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>SQL Server学习笔记</title>
    <link href="https://blog.itrf.cn/posts/d3d/"/>
    <id>https://blog.itrf.cn/posts/d3d/</id>
    <published>2025-07-24T04:46:41.000Z</published>
    <updated>2026-09-03T07:47:16.820Z</updated>
    
    <content type="html"><![CDATA[<h2 id="数据库管理技术的基本概念"><a href="#数据库管理技术的基本概念" class="headerlink" title="数据库管理技术的基本概念"></a>数据库管理技术的基本概念</h2><ol><li>数据</li><li>信息</li><li>数据处理</li></ol><h2 id="数据库管理技术的发展阶段"><a href="#数据库管理技术的发展阶段" class="headerlink" title="数据库管理技术的发展阶段"></a>数据库管理技术的发展阶段</h2><ol><li>人工管理</li><li>文件系统</li><li>数据库系统</li><li>大数据</li></ol><h2 id="数据库系统的组成"><a href="#数据库系统的组成" class="headerlink" title="数据库系统的组成"></a>数据库系统的组成</h2><ol><li>硬件系统</li><li>操作系统</li><li>.数据库</li><li>数据库管理系统</li><li>数据库应用系统的开发工具及相关接口软件</li><li>数据库应用系统</li><li>用户<ol><li>数据库管理员</li><li>数据库分析师</li><li>应用程序员</li><li>终端用户</li></ol></li></ol><h2 id="数据库体系结构"><a href="#数据库体系结构" class="headerlink" title="数据库体系结构"></a>数据库体系结构</h2><ol><li>集中式系统</li><li>分布式系统</li><li>个人计算机系统</li><li>客户/服务器系统</li><li>浏览器/服务器系统    </li></ol><h2 id="数据库的三级模式"><a href="#数据库的三级模式" class="headerlink" title="数据库的三级模式"></a>数据库的三级模式</h2><ol><li>内模式</li><li>模式</li><li>外模式    </li></ol><h2 id="数据库的二级映像"><a href="#数据库的二级映像" class="headerlink" title="数据库的二级映像"></a>数据库的二级映像</h2><ol><li>外模式/模式</li><li>模式/内模式</li></ol><h2 id="数据库管理系统的功能"><a href="#数据库管理系统的功能" class="headerlink" title="数据库管理系统的功能"></a>数据库管理系统的功能</h2><ol><li>数据定义</li><li>数据操控</li><li>数据库运行控制</li><li>数据字典</li></ol><h2 id="登录方式"><a href="#登录方式" class="headerlink" title="登录方式"></a>登录方式</h2><ol><li>windows登录，本地登录</li><li>sqlserver登录，远程登录</li></ol><h2 id="数据库备份"><a href="#数据库备份" class="headerlink" title="数据库备份"></a>数据库备份</h2><p>mdf数据文件，ldf日志文件</p><ol><li>分离-&gt;附加  分离后数据库会不可用</li><li>备份-&gt;还原  保存为.bak文件   任务——&gt;备份</li><li>数据库脚本</li></ol><h2 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h2><p>单行注释 —<br>多行注释 /<em>   </em>/</p><h2 id="数据库相关"><a href="#数据库相关" class="headerlink" title="数据库相关"></a>数据库相关</h2><h3 id="创建数据库"><a href="#创建数据库" class="headerlink" title="创建数据库"></a>创建数据库</h3><p>完整语法</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">create</span> database DBTEST</span><br><span class="line"><span class="keyword">on</span> <span class="comment">--数据文件</span></span><br><span class="line">(</span><br><span class="line">  name <span class="operator">=</span> <span class="string">&#x27;DBTEST&#x27;</span>, <span class="comment">--逻辑名称</span></span><br><span class="line">  filename <span class="operator">=</span> <span class="string">&#x27;&#x27;</span>,  <span class="comment">--物理路径和名称 以.mdf结尾</span></span><br><span class="line">  size <span class="operator">=</span> <span class="number">5</span>MB,<span class="comment">--文件的初始大小</span></span><br><span class="line">  maxsize <span class="operator">=</span> <span class="number">40</span>MB，  <span class="comment">--文件的最大大小</span></span><br><span class="line">  filegrowth<span class="operator">=</span><span class="number">2</span>MB,  <span class="comment">--文件增长方式 可以是大小，也可以是百分比</span></span><br><span class="line">)</span><br><span class="line">log <span class="keyword">on</span> <span class="comment">--日志文件</span></span><br><span class="line">(</span><br><span class="line">  name <span class="operator">=</span> <span class="string">&#x27;DBTEST_log&#x27;</span>, <span class="comment">--逻辑名称</span></span><br><span class="line">  filename <span class="operator">=</span> <span class="string">&#x27;&#x27;</span>,  <span class="comment">--物理路径和名称 以.ldf结尾</span></span><br><span class="line">  size <span class="operator">=</span> <span class="number">5</span>MB,<span class="comment">--文件的初始大小</span></span><br><span class="line">  filegrowth<span class="operator">=</span><span class="number">2</span>MB,  <span class="comment">--文件增长方式 可以是大小，也可以是百分比</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>简单语法</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">create</span> database DBTEST  <span class="comment">--采用默认值创建 </span></span><br></pre></td></tr></table></figure><h3 id="修改数据库"><a href="#修改数据库" class="headerlink" title="修改数据库"></a>修改数据库</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">alter</span> database DNTEST</span><br></pre></td></tr></table></figure><h3 id="删除数据库"><a href="#删除数据库" class="headerlink" title="删除数据库"></a>删除数据库</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">drop</span> database 数据库名称</span><br></pre></td></tr></table></figure><h3 id="切换数据库"><a href="#切换数据库" class="headerlink" title="切换数据库"></a>切换数据库</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">use DBTEST <span class="comment">--use 数据库名称</span></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><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">create table</span> 表名</span><br><span class="line">(</span><br><span class="line"> 字段<span class="number">1</span> 数据类型，</span><br><span class="line"> 字段<span class="number">2</span> 数据类型，</span><br><span class="line"> 字段<span class="number">3</span> 数据类型，</span><br><span class="line">) </span><br></pre></td></tr></table></figure><h4 id="约束"><a href="#约束" class="headerlink" title="约束"></a>约束</h4><ul><li>primary key:主建 </li><li>references 表名(字段名)：外键</li><li>identify(1,1):自动增长，初始值1，增长步长1</li><li>not null：不为空</li><li>check():检查约束</li><li>default()：默认值</li><li>unique：唯一约束</li></ul><h4 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h4><ul><li>int:整型</li><li>float:浮点型</li><li>decimal(n,m):n表示总长度，m表示小数位数</li><li>date:年月日  —getdate() 获取当前时间</li><li>datetime：年月日时分秒</li><li>smalldatetime：范围更小</li><li>char():定长字符串</li><li>varchar():变长字符串</li><li>text:长文本</li><li>nchar,nvarchar,ntext:unicode字符,对中文友好</li></ul><h3 id="删除表"><a href="#删除表" class="headerlink" title="删除表"></a>删除表</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">drop</span> <span class="keyword">table</span> 表名</span><br></pre></td></tr></table></figure><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="code"><pre><span class="line"><span class="keyword">alter table</span> 表名 <span class="keyword">add</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="code"><pre><span class="line"><span class="keyword">alter table</span> 表名 <span class="keyword">drop</span> <span class="keyword">column</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="code"><pre><span class="line"><span class="keyword">alter table</span> 表名 <span class="keyword">alter</span> <span class="keyword">column</span> 列名 数据类型</span><br></pre></td></tr></table></figure><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="code"><pre><span class="line"><span class="keyword">alter table</span> 表名 <span class="keyword">drop</span> <span class="keyword">constraint</span> 约束名</span><br></pre></td></tr></table></figure><h4 id="2-添加约束"><a href="#2-添加约束" class="headerlink" title="2.添加约束"></a>2.添加约束</h4><h5 id="check约束"><a href="#check约束" class="headerlink" title="check约束"></a>check约束</h5><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">alter table</span> 表名 <span class="keyword">add constraint</span> 约束名 <span class="keyword">check</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="code"><pre><span class="line"><span class="keyword">alter table</span> 表名 <span class="keyword">add constraint</span> 约束名 <span class="keyword">primary key</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="code"><pre><span class="line"><span class="keyword">alter table</span> 表名 <span class="keyword">add constraint</span> 约束名 <span class="keyword">unique</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="code"><pre><span class="line"><span class="keyword">alter table</span> 表名 <span class="keyword">add constraint</span> 约束名 <span class="keyword">default</span> 默认值 <span class="keyword">for</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="code"><pre><span class="line"><span class="keyword">alter table</span> 表名 <span class="keyword">add constraint</span> 约束名 <span class="keyword">foreign key</span>(列名)<span class="keyword">references</span> 关联表名(列名)</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><figure class="highlight sql"><table><tr><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="number">3</span>)<span class="keyword">values</span>(内容<span class="number">1</span>,内容<span class="number">2</span>,内容<span class="number">3</span>)</span><br></pre></td></tr></table></figure><p>简写 —不建议</p><figure class="highlight sql"><table><tr><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 class="number">3</span>)</span><br></pre></td></tr></table></figure><p>一次性插入多行数据</p><figure class="highlight sql"><table><tr><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="number">3</span>)</span><br><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">union</span></span><br><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">union</span></span><br><span class="line"><span class="keyword">select</span> 内容<span class="number">1</span>,内容<span class="number">2</span>,内容<span class="number">3</span></span><br></pre></td></tr></table></figure><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="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><h4 id="2-删除数据"><a href="#2-删除数据" class="headerlink" title="2.删除数据"></a>2.删除数据</h4><figure class="highlight sql"><table><tr><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><h4 id="关于删除"><a href="#关于删除" class="headerlink" title="关于删除"></a>关于删除</h4><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">drop</span> <span class="keyword">table</span> 表名  <span class="comment">--删除表对象</span></span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">truncate</span> <span class="keyword">table</span> 表名  <span class="comment">--删除数据(清空数据)，表对象即表结构依然存在，不能有条件，自动编号存在</span></span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">delete</span> <span class="keyword">from</span> 表名  <span class="comment">--删除数据(清空数据)，表对象即表结构依然存在，可以有条件，自动编号清除</span></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><h4 id="1-查询所有列所有行"><a href="#1-查询所有列所有行" class="headerlink" title="1.查询所有列所有行"></a>1.查询所有列所有行</h4><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</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="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></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="code"><pre><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="number">3</span> 别名<span class="number">3</span> <span class="keyword">from</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="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><h3 id="条件查询"><a href="#条件查询" class="headerlink" title="条件查询"></a>条件查询</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 表名 <span class="keyword">where</span> 条件</span><br><span class="line"><span class="keyword">select</span> top <span class="number">10</span> <span class="operator">*</span> <span class="keyword">from</span> 表名 <span class="keyword">where</span> 条件  <span class="comment">--前十位</span></span><br><span class="line"><span class="keyword">select</span> top <span class="number">10</span> <span class="keyword">percent</span> <span class="operator">*</span> <span class="keyword">from</span> 表名 <span class="keyword">where</span> 条件  <span class="comment">--前百分之十</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 表名 <span class="keyword">where</span> <span class="keyword">order</span> <span class="keyword">by</span> 字段 <span class="keyword">desc</span><span class="operator">/</span><span class="keyword">asc</span></span><br></pre></td></tr></table></figure><h3 id="模糊查询"><a href="#模糊查询" class="headerlink" title="模糊查询"></a>模糊查询</h3><ul><li>like</li><li>%：代表匹配0个字符，一个字符或多个字符</li><li>_:代表匹配有且只有一个字符</li><li>[]:代表匹配范围内</li><li>！[]:代表匹配不在范围内</li><li>substring(n,m,t) —n为字符串，m是起始位，t是跨度</li></ul><h4 id="聚合函数"><a href="#聚合函数" class="headerlink" title="聚合函数"></a>聚合函数</h4><ul><li>count:求数量</li><li>max:求最大值</li><li>min:求最小值</li><li>sum: 求和</li><li>avg:求平均值</li><li>round:保留小数位 round(2.556,2) ——&gt;2.55</li><li>datediff:求时间差 datediff(year,时间1,时间2)</li></ul><h3 id="分组查询"><a href="#分组查询" class="headerlink" title="分组查询"></a>分组查询</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 表名 <span class="keyword">where</span> 条件 <span class="keyword">group</span> <span class="keyword">by</span> 列名 <span class="keyword">having</span> 条件</span><br></pre></td></tr></table></figure><p> —普通条件在where后，聚合条件在 having后<br>注：分组查询只允许查询分组条件和聚合函数</p><h3 id="多表查询"><a href="#多表查询" class="headerlink" title="多表查询"></a>多表查询</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 表名<span class="number">1</span>,表名<span class="number">2</span> <span class="keyword">where</span> 条件</span><br></pre></td></tr></table></figure><h4 id="1-内连接"><a href="#1-内连接" class="headerlink" title="1.内连接"></a>1.内连接</h4><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 表名<span class="number">1</span> <span class="keyword">inner</span> <span class="keyword">join</span> 表名<span class="number">2</span> <span class="keyword">on</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="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 表名<span class="number">1</span> <span class="keyword">left</span> <span class="keyword">join</span> 表名<span class="number">2</span> <span class="keyword">on</span> 条件  <span class="comment">--左外</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 表名<span class="number">1</span> <span class="keyword">right</span> <span class="keyword">join</span> 表名<span class="number">2</span> <span class="keyword">on</span> 条件  <span class="comment">--右外</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 表名<span class="number">1</span> <span class="keyword">full</span> <span class="keyword">join</span> 表名<span class="number">2</span> <span class="keyword">on</span> 条件  <span class="comment">--全外</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="code"><pre><span class="line"><span class="keyword">select</span> 别名<span class="number">1.</span>字段,别名<span class="number">2.</span>字段 <span class="keyword">from</span> 表名 别名<span class="number">1</span> <span class="keyword">inner</span> <span class="keyword">join</span> 表名 别名<span class="number">2</span> <span class="keyword">on</span> 条件</span><br></pre></td></tr></table></figure><h2 id="数据库设计"><a href="#数据库设计" class="headerlink" title="数据库设计"></a>数据库设计</h2><h3 id="1-三范式"><a href="#1-三范式" class="headerlink" title="1.三范式"></a>1.三范式</h3><ul><li>第一范式：原子性  不可再分</li><li>第二范式：实体唯一性</li><li>第三范式：不能有冗余</li></ul><h3 id="2-表关系"><a href="#2-表关系" class="headerlink" title="2.表关系"></a>2.表关系</h3><ul><li>一对一</li><li>一对多</li><li>多对多</li></ul><h2 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h2><h3 id="—信息打印："><a href="#—信息打印：" class="headerlink" title="—信息打印："></a>—信息打印：</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">print<span class="string">&#x27;hello,sql&#x27;</span>      <span class="comment">--消息窗口显示</span></span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span><span class="string">&#x27;hello,sql&#x27;</span>     <span class="comment">--信息窗口显示</span></span><br></pre></td></tr></table></figure><h3 id="—变量："><a href="#—变量：" class="headerlink" title="—变量："></a>—变量：</h3><ol><li><p>局部变量：以@开头，先声明，在赋值</p> <figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="variable">@str</span> <span class="type">varchar</span>(<span class="number">20</span>)</span><br><span class="line"><span class="keyword">set</span> <span class="variable">@str</span><span class="operator">=</span><span class="string">&#x27;i like sql&#x27;</span>        <span class="comment">--赋值变量指定的值</span></span><br><span class="line"><span class="keyword">select</span> <span class="variable">@str</span> <span class="operator">=</span><span class="string">&#x27;i like sql&#x27;</span> <span class="comment">--一般用于表中的查询出的数据赋值给变量，如果查询结果有多条，取最后一条赋值</span></span><br></pre></td></tr></table></figure></li><li><p>全局变量：以@@开头，由系统就行定义和维护</p></li></ol><h3 id="—go语句"><a href="#—go语句" class="headerlink" title="—go语句:"></a>—go语句:</h3><ol><li>等待go语句之前的代码执行完之后才能执行后面的代码</li><li>批处理结束的一个标志</li></ol><h2 id="运算符"><a href="#运算符" class="headerlink" title="运算符"></a>运算符</h2><h3 id="强制类型转化"><a href="#强制类型转化" class="headerlink" title="强制类型转化"></a>强制类型转化</h3><ol><li>Convert(数据类型，变量名)  例：Convert(varchar(10)，@zc)</li><li>cast(变量名 as 数据类型)   例：cast(@zc as varchar(10))</li></ol><h3 id="流程控制语句"><a href="#流程控制语句" class="headerlink" title="流程控制语句"></a>流程控制语句</h3><h4 id="1-选择分支"><a href="#1-选择分支" class="headerlink" title="(1)选择分支"></a>(1)选择分支</h4><p>case选择：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">case</span> </span><br><span class="line"><span class="keyword">when</span> 条件 <span class="keyword">then</span> 执行<span class="number">1</span></span><br><span class="line"><span class="keyword">when</span> 条件 <span class="keyword">then</span> 执行<span class="number">2</span></span><br><span class="line"><span class="keyword">else</span> 最终执行</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>if判断语句：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">if 条件判断</span><br><span class="line">   <span class="keyword">begin</span></span><br><span class="line"> 语句</span><br><span class="line">   <span class="keyword">end</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">   <span class="keyword">begin</span></span><br><span class="line">     语句</span><br><span class="line">   <span class="keyword">end</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="code"><pre><span class="line">while 条件</span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">  语句</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><h2 id="子查询"><a href="#子查询" class="headerlink" title="子查询"></a>子查询</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">   <span class="keyword">where</span>条件</span><br><span class="line">临时表</span><br><span class="line">exist</span><br></pre></td></tr></table></figure><h2 id="分页"><a href="#分页" class="headerlink" title="分页"></a>分页</h2><h3 id="1-top方案"><a href="#1-top方案" class="headerlink" title="(1)top方案"></a>(1)top方案</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="variable">@PageSize</span> <span class="type">int</span> <span class="operator">=</span> <span class="number">5</span></span><br><span class="line"><span class="keyword">declare</span> <span class="variable">@PageIndex</span> <span class="type">int</span> <span class="operator">=</span> <span class="number">3</span></span><br><span class="line"><span class="keyword">select</span> top(<span class="variable">@PageSize</span>)<span class="operator">*</span><span class="keyword">from</span> Student</span><br><span class="line"><span class="keyword">where</span> StuId <span class="keyword">not</span> <span class="keyword">in</span> (<span class="keyword">select</span> top(<span class="variable">@PageSize</span><span class="operator">*</span>(PageIndex<span class="number">-1</span>))StuId <span class="keyword">from</span> Student)</span><br></pre></td></tr></table></figure><h3 id="2-使用row-number分页"><a href="#2-使用row-number分页" class="headerlink" title="(2)使用row_number分页"></a>(2)使用row_number分页</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="variable">@PageSize</span> <span class="type">int</span> <span class="operator">=</span> <span class="number">5</span></span><br><span class="line"><span class="keyword">declare</span> <span class="variable">@PageIndex</span> <span class="type">int</span> <span class="operator">=</span> <span class="number">3</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span><span class="keyword">from</span></span><br><span class="line">(<span class="keyword">select</span> <span class="built_in">ROW_NUMBER</span>() <span class="keyword">over</span>(<span class="keyword">order</span> <span class="keyword">by</span> StuId) RowId,<span class="operator">*</span><span class="keyword">from</span> Student) Temp</span><br><span class="line"><span class="keyword">where</span> RowId <span class="keyword">between</span> (<span class="variable">@PageIndex</span><span class="number">-1</span>)<span class="operator">*</span><span class="variable">@PageSize</span><span class="operator">+</span><span class="number">1</span> <span class="keyword">and</span> <span class="variable">@PageIndex</span><span class="operator">*</span><span class="variable">@PageSize</span></span><br></pre></td></tr></table></figure><h2 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h2><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">begin</span> transaction   <span class="comment">--开始语句</span></span><br><span class="line">语句</span><br><span class="line">@<span class="variable">@ERROR</span>             <span class="comment">--统计错误 如果出现错误 @@ERROR的值不为零</span></span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">commit</span> transaction     <span class="comment">--提交事务</span></span><br><span class="line"><span class="keyword">rollback</span> transaction   <span class="comment">--回滚事务</span></span><br></pre></td></tr></table></figure><h2 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h2><ol><li>聚集索引     —类似于按照拼音查找     只能有一个</li><li>非聚集索引    —类似于按照部首查询     可以有多个</li></ol><h3 id="添加索引"><a href="#添加索引" class="headerlink" title="添加索引"></a>添加索引</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">create</span> [<span class="keyword">unique</span>][clustered<span class="operator">|</span>nonclustered] index <span class="operator">&lt;</span>index name<span class="operator">&gt;</span> <span class="keyword">on</span> <span class="operator">&lt;</span><span class="keyword">table</span> <span class="keyword">or</span> <span class="keyword">view</span> name<span class="operator">&gt;</span>(<span class="operator">&lt;</span>colum name<span class="operator">&gt;</span>[<span class="keyword">ASC</span><span class="operator">|</span><span class="keyword">DESC</span>][,...n])</span><br></pre></td></tr></table></figure><h3 id="查看索引-（sys-indexes）"><a href="#查看索引-（sys-indexes）" class="headerlink" title="查看索引 （sys.indexes）"></a>查看索引 （sys.indexes）</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> sys.indexes <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;索引名&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="删除索引"><a href="#删除索引" class="headerlink" title="删除索引"></a>删除索引</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">drop</span> index 索引名 <span class="keyword">on</span> 表名</span><br></pre></td></tr></table></figure><h3 id="使用索引"><a href="#使用索引" class="headerlink" title="使用索引"></a>使用索引</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 表名 <span class="keyword">with</span> (index<span class="operator">=</span>索引名) <span class="keyword">where</span> 条件</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><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">view</span> 视图名 <span class="keyword">as</span> <span class="keyword">select</span>语句 go</span><br></pre></td></tr></table></figure><h3 id="查询视图："><a href="#查询视图：" class="headerlink" title="查询视图："></a>查询视图：</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 视图名</span><br></pre></td></tr></table></figure><h3 id="删除视图："><a href="#删除视图：" class="headerlink" title="删除视图："></a>删除视图：</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">drop</span> <span class="keyword">view</span> 视图名</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>静态游标</li><li>动态游标</li><li>键集驱动游标</li></ul><h3 id="创建游标-scroll：滚动游标，没有scroll，只进"><a href="#创建游标-scroll：滚动游标，没有scroll，只进" class="headerlink" title="创建游标(scroll：滚动游标，没有scroll，只进)"></a>创建游标(scroll：滚动游标，没有scroll，只进)</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">declare</span> 游标名 <span class="keyword">cursor</span> <span class="keyword">scroll</span> <span class="keyword">for</span> <span class="keyword">select</span> 字段名 <span class="keyword">from</span> 表名</span><br></pre></td></tr></table></figure><h3 id="打开游标"><a href="#打开游标" class="headerlink" title="打开游标"></a>打开游标</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">open</span> 游标名</span><br></pre></td></tr></table></figure><h3 id="关闭游标"><a href="#关闭游标" class="headerlink" title="关闭游标"></a>关闭游标</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">close</span> 游标名</span><br></pre></td></tr></table></figure><h3 id="删除游标"><a href="#删除游标" class="headerlink" title="删除游标"></a>删除游标</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">deallocate</span> 游标名</span><br></pre></td></tr></table></figure><h3 id="提取数据"><a href="#提取数据" class="headerlink" title="提取数据"></a>提取数据</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">fetch</span> <span class="keyword">first</span> <span class="keyword">from</span> 游标名         <span class="comment">--提取第一行</span></span><br><span class="line"><span class="keyword">fetch</span> <span class="keyword">last</span> <span class="keyword">from</span> 游标名   <span class="comment">--提取最后一行</span></span><br><span class="line"><span class="keyword">fetch</span> absolute <span class="number">2</span> <span class="keyword">from</span> 游标名    <span class="comment">--提取第二行</span></span><br><span class="line"><span class="keyword">fetch</span> relative <span class="number">2</span> <span class="keyword">from</span> 游标名    <span class="comment">--当前行下移两行</span></span><br><span class="line"><span class="keyword">fetch</span> next <span class="keyword">from</span> 游标名     <span class="comment">--下移一行</span></span><br><span class="line"><span class="keyword">fetch</span> prior <span class="keyword">from</span> 游标名    <span class="comment">--上移一行</span></span><br></pre></td></tr></table></figure><p>—提取游标数据存放入变量，进行查询所有列信息</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="variable">@acc</span> <span class="type">varchar</span>(<span class="number">20</span>)</span><br><span class="line"><span class="keyword">fetch</span> absolute <span class="number">2</span> <span class="keyword">from</span> mycur <span class="keyword">into</span> <span class="variable">@acc</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> Menber <span class="keyword">where</span> MenberAccount<span class="operator">=</span><span class="variable">@acc</span></span><br></pre></td></tr></table></figure><p>—遍历游标</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">  @<span class="variable">@fetch_status</span>  ：<span class="number">0</span>提取成功，<span class="number">-1</span>提取失败，<span class="number">-2</span>不存在</span><br><span class="line"><span class="keyword">declare</span> <span class="variable">@acc</span> <span class="type">varchar</span>(<span class="number">20</span>)</span><br><span class="line"><span class="keyword">fetch</span> absolute <span class="number">2</span> <span class="keyword">from</span> mycur <span class="keyword">into</span> <span class="variable">@acc</span></span><br><span class="line">while @<span class="variable">@fetch_status</span><span class="operator">=</span><span class="number">0</span></span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line">print<span class="string">&#x27;提取成功：&#x27;</span><span class="operator">+</span><span class="variable">@acc</span></span><br><span class="line"><span class="keyword">fetch</span> next <span class="keyword">from</span> mycur <span class="keyword">into</span> <span class="variable">@acc</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>—利用游标进行数据的修改和删除</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">fetch</span> absolute <span class="number">2</span> <span class="keyword">from</span> mycur</span><br><span class="line"><span class="keyword">update</span> <span class="keyword">Member</span> <span class="keyword">set</span> MemberPwd <span class="operator">=</span> <span class="string">&#x27;654321&#x27;</span> <span class="keyword">where</span> <span class="keyword">current</span> <span class="keyword">of</span> mycur</span><br><span class="line"></span><br><span class="line"><span class="keyword">fetch</span> absolute <span class="number">2</span> <span class="keyword">from</span> mycur</span><br><span class="line"><span class="keyword">delete</span> <span class="keyword">from</span> <span class="keyword">Member</span> <span class="keyword">where</span> <span class="keyword">current</span> <span class="keyword">of</span> mycur</span><br></pre></td></tr></table></figure><p>—一行多列游标</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">declare</span> 游标名 <span class="keyword">cursor</span> <span class="keyword">scroll</span> <span class="keyword">for</span> <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></pre></td></tr></table></figure><h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><ol><li>系统函数</li><li>自定义函数<ol><li>标量值函数（返回单个值）</li><li>表值函数（返回查询结果）</li></ol></li></ol><h3 id="创建函数"><a href="#创建函数" class="headerlink" title="创建函数"></a>创建函数</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">function</span> 函数名(@传入值 传入值类型) <span class="keyword">returns</span> 返回值类型</span><br><span class="line"><span class="keyword">as</span> </span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line"><span class="keyword">declare</span> @返回值 返回值类型</span><br><span class="line"><span class="keyword">select</span> @返回值 <span class="operator">=</span> </span><br><span class="line"><span class="keyword">return</span> @返回值</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><h4 id="返回值类型的情况"><a href="#返回值类型的情况" class="headerlink" title="返回值类型的情况"></a>返回值类型的情况</h4><ol><li><p>可以有其他逻辑代码</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">function</span> 函数名(@传入值 传入值类型) <span class="keyword">returns</span> @返回值 <span class="keyword">table</span></span><br><span class="line">(</span><br><span class="line">字段<span class="number">1</span> 数据类型，</span><br><span class="line">字段<span class="number">2</span> 数据类型，</span><br><span class="line">字段<span class="number">3</span> 数据类型，</span><br><span class="line">字段<span class="number">4</span> 数据类型</span><br><span class="line">)</span><br><span class="line"><span class="keyword">as</span></span><br><span class="line"><span class="keyword">begin</span></span><br><span class="line"><span class="keyword">insert into</span> @返回值</span><br><span class="line">查询语句</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure></li><li><p>函数体只能有return+sql查询结果</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">function</span> 函数名(@传入值 传入值类型) <span class="keyword">returns</span> <span class="keyword">table</span></span><br><span class="line"><span class="keyword">as</span></span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">查询语句</span><br><span class="line">go</span><br></pre></td></tr></table></figure><p>函数调用</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">select</span> dbo.函数名()        <span class="comment">--返回值为一个值</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> 函数名()     <span class="comment">--返回值为一个表</span></span><br></pre></td></tr></table></figure></li></ol><h3 id="函数删除"><a href="#函数删除" class="headerlink" title="函数删除"></a>函数删除</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">drop</span> <span class="keyword">function</span> 函数名</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><ol><li>instead of  事前触发器</li><li>after  事后触发器</li></ol><h3 id="创建触发器"><a href="#创建触发器" class="headerlink" title="创建触发器"></a>创建触发器</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">trigger</span> 触发器名称 <span class="keyword">on</span> 表名 [after<span class="operator">|</span>instead <span class="keyword">of</span>] [<span class="keyword">insert</span><span class="operator">|</span><span class="keyword">delete</span><span class="operator">|</span><span class="keyword">update</span>]</span><br><span class="line"><span class="keyword">as</span> </span><br><span class="line">语句</span><br><span class="line">go</span><br><span class="line"></span><br><span class="line">   inserted插入的临时表</span><br><span class="line">   deleted删除的临时表</span><br></pre></td></tr></table></figure><p>   数据更新实际上是先删除再插入</p><h2 id="存储过程"><a href="#存储过程" class="headerlink" title="存储过程"></a>存储过程</h2><p>存储过程与函数的主要区别：存储过程可以被其他程序调用，函数一般用于select语句中</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="code"><pre><span class="line"><span class="keyword">create</span> proc 存储过程名 @变量<span class="number">1</span>名 变量类型，@变量<span class="number">2</span>名 变量类型，@变量<span class="number">3</span>名 变量类型 output，@变量<span class="number">4</span>名 变量类型 output</span><br><span class="line"><span class="keyword">as</span></span><br><span class="line">语句</span><br><span class="line">go</span><br></pre></td></tr></table></figure><h4 id="存储过程的调用"><a href="#存储过程的调用" class="headerlink" title="存储过程的调用"></a>存储过程的调用</h4><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">exec</span> 存储过程名 变量值<span class="number">1</span>，变量值<span class="number">2</span>，变量名 output，变量名 output   <span class="comment">--前两个用于输入，后两个用于接收，如果变量名(第三四个)已经赋值了则具有输入输出功能</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="code"><pre><span class="line"><span class="keyword">drop</span> proc 存储过程名</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">SQL Server学习笔记</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=d2ea7dd5-1c44-4210-36f9-61524c90fb06" type="image"/>
    
    
    <category term="学习笔记" scheme="https://blog.itrf.cn/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>Git 推送失败问题解决指南</title>
    <link href="https://blog.itrf.cn/posts/9c16/"/>
    <id>https://blog.itrf.cn/posts/9c16/</id>
    <published>2025-07-20T04:41:27.000Z</published>
    <updated>2026-09-03T07:46:28.920Z</updated>
    
    <content type="html"><![CDATA[<p>当执行 <code>git push -u origin master</code> 时遇到类似以下错误：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">error: failed to push some refs to &#x27;http://xxxx.cn/vip/xxx.git&#x27;</span><br><span class="line">To http://xxxx.cn/vip/xxx.git</span><br><span class="line">!refs/heads/master:refs/heads/master[rejected] (fetch first)</span><br><span class="line">Done</span><br><span class="line">hint: Updates were rejected because the remote contains work that you do not</span><br><span class="line">hint: have locally. This is usually caused by another repository pushing to</span><br><span class="line">hint: the same ref. If you want to integrate the remote changes, use</span><br><span class="line">hint: &#x27;git pull&#x27; before pushing again.</span><br></pre></td></tr></table></figure><p>或简化版错误：<br><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">[rejected]   master -&gt; master (fetch first)    </span><br><span class="line">error: failed to push some refs</span><br></pre></td></tr></table></figure></p><p>这是 Git 常见的版本冲突问题，原因是远程仓库存在本地没有的更新，为防止覆盖远程变更而拒绝推送。</p><h2 id="推荐解决方案"><a href="#推荐解决方案" class="headerlink" title="推荐解决方案"></a>推荐解决方案</h2><p>最简洁高效的处理方式是执行以下命令：<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 拉取远程master分支并以rebase方式整合本地更改</span></span><br><span class="line">git pull --rebase origin master</span><br><span class="line"></span><br><span class="line"><span class="comment"># 完成整合后再次推送（-u参数会建立本地与远程分支的关联）</span></span><br><span class="line">git push -u origin master</span><br></pre></td></tr></table></figure><br>这种方式会：</p><ol><li>将远程最新更改下载到本地</li><li>以线性方式重新应用你的本地提交</li><li>建立本地与远程分支的关联关系</li></ol><h2 id="备选解决方案（适用于复杂冲突）"><a href="#备选解决方案（适用于复杂冲突）" class="headerlink" title="备选解决方案（适用于复杂冲突）"></a>备选解决方案（适用于复杂冲突）</h2><p>如果遇到复杂的代码冲突，可使用标准合并流程：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 拉取远程更新并合并到本地</span></span><br><span class="line">git pull origin master</span><br><span class="line"></span><br><span class="line"><span class="comment"># 若出现冲突，编辑冲突文件后标记为已解决</span></span><br><span class="line">git add &lt;冲突的文件路径&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 提交合并结果</span></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"># 推送本地更改</span></span><br><span class="line">git push origin master</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul><li><code>git pull --rebase</code> 会改写提交历史，保持提交记录更整洁</li><li>多人协作时，推送前先拉取是良好习惯</li><li>解决冲突时需仔细检查代码，确保逻辑正确</li><li>首次推送分支时，<code>-u</code> 参数只需使用一次，后续可直接用 <code>git push</code><br>以上方法可解决绝大多数推送失败问题，推荐优先使用 <code>git pull --rebase</code> 方式，它能更优雅地处理分支同步问题。</li></ul>]]></content>
    
    
    <summary type="html">Git 推送失败问题解决指南</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=151eedde-a979-a0e0-b516-2642c27b9ab8" type="image"/>
    
    
    <category term="学习笔记" scheme="https://blog.itrf.cn/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>正则表达式学习笔记</title>
    <link href="https://blog.itrf.cn/posts/3a56/"/>
    <id>https://blog.itrf.cn/posts/3a56/</id>
    <published>2025-07-20T04:27:11.000Z</published>
    <updated>2026-09-03T07:47:13.738Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>参考<a href="https://www.runoob.com/regexp/regexp-tutorial.html">正则表达式 – 教程 | 菜鸟教程</a></p></blockquote><h2 id="1-什么是正则表达式？"><a href="#1-什么是正则表达式？" class="headerlink" title="1.什么是正则表达式？"></a>1.什么是正则表达式？</h2><p><em>正则表达式（Regular Expression，简称 Regex 或 RegExp）是一种用来匹配字符串中字符组合的模式。</em></p><p>简单来说就是一种匹配内容的模板。</p><h2 id="2-正则表达式的作用"><a href="#2-正则表达式的作用" class="headerlink" title="2.正则表达式的作用"></a>2.正则表达式的作用</h2><ul><li><strong>查找：</strong> 在文本中找到特定模式的内容</li><li><strong>替换：</strong> 将符合某种模式的文本替换为其他内容</li><li><strong>验证：</strong> 检查输入的数据是否符合预期格式</li><li><strong>提取：</strong> 从复杂文本中提取需要的信息</li></ul><h2 id="3-正则表达式的模式"><a href="#3-正则表达式的模式" class="headerlink" title="3.正则表达式的模式"></a>3.正则表达式的模式</h2><ul><li>字面值字符：例如字母、数字、空格等，可以直接匹配它们自身。</li><li>特殊字符：例如点号 <code>.</code>、星号 <code>*</code>、加号 <code>+</code>、问号 <code>?</code> 等，它们具有特殊的含义和功能。</li><li>字符类：用方括号 <code>[ ]</code> 包围的字符集合，用于匹配方括号内的任意一个字符。</li><li>元字符：例如 <code>\d</code>、<code>\w</code>、<code>\s</code> 等，用于匹配特定类型的字符，如数字、字母、空白字符等。</li><li>量词：例如 <code>{n}</code>、<code>{n,}</code>、<code>{n,m}</code> 等，用于指定匹配的次数或范围。</li><li>边界符号：例如 <code>^</code>、<code>$</code>、<code>\b</code>、<code>\B</code> 等，用于匹配字符串的开头、结尾或单词边界位置。</li></ul><h2 id="4-正则表达式元字符和特性"><a href="#4-正则表达式元字符和特性" class="headerlink" title="4.正则表达式元字符和特性"></a>4.正则表达式元字符和特性</h2><h4 id="字符匹配"><a href="#字符匹配" class="headerlink" title="字符匹配"></a>字符匹配</h4><ul><li>普通字符：普通字符按照字面意义进行匹配，例如匹配字母 “a” 将匹配到文本中的 “a” 字符。</li><li>元字符：元字符具有特殊的含义，例如 <code>\d</code> 匹配任意数字字符，<code>\w</code> 匹配任意字母数字字符，<code>.</code> 匹配任意字符（除了换行符）等。</li></ul><h4 id="基本元字符"><a href="#基本元字符" class="headerlink" title="基本元字符"></a>基本元字符</h4><p><code>.</code> (点号)</p><ul><li><p>匹配除换行符(<code>\n</code>)外的任意单个字符</p><p>示例：<code>a.b</code> 匹配 “aab”, “a1b”, “a b” 等</p></li></ul><p><code>^</code> (脱字符)</p><ul><li>匹配字符串的开始位置</li><li>示例：<code>^abc</code> 匹配以 “abc” 开头的字符串</li></ul><p><code>$</code> (美元符)</p><ul><li>匹配字符串的结束位置</li><li>示例：<code>xyz$</code> 匹配以 “xyz” 结尾的字符串</li></ul><p><code>\</code> (反斜杠)</p><ul><li>转义字符，使后面的字符失去特殊含义</li><li>示例：<code>\.</code> 匹配实际的点号而不是任意字符</li></ul><h4 id="量词"><a href="#量词" class="headerlink" title="量词"></a>量词</h4><ul><li><code>*</code>：匹配前面的模式零次或多次。</li><li><code>+</code>：匹配前面的模式一次或多次。</li><li><code>?</code>：匹配前面的模式零次或一次。</li><li><code>{n}</code>：匹配前面的模式恰好 n 次。</li><li><code>{n,}</code>：匹配前面的模式至少 n 次。</li><li><code>{n,m}</code>：匹配前面的模式至少 n 次且不超过 m 次。</li></ul><h4 id="字符类"><a href="#字符类" class="headerlink" title="字符类"></a>字符类</h4><ul><li><code>[ ]</code>：匹配括号内的任意一个字符。例如，<code>[abc]</code> 匹配字符 “a”、”b” 或 “c”。</li><li><code>[^ ]</code>：匹配除了括号内的字符以外的任意一个字符。例如，<code>[^abc]</code> 匹配除了字符 “a”、”b” 或 “c” 以外的任意字符。</li><li><code>[A-Z]</code>：表示一个区间，匹配所有大写字母，[a-z] 表示所有小写字母。</li></ul><h4 id="边界匹配"><a href="#边界匹配" class="headerlink" title="边界匹配"></a>边界匹配</h4><ul><li><code>^</code>：匹配字符串的开头。</li><li><code>$</code>：匹配字符串的结尾。</li><li><code>\b</code>：匹配单词边界。</li><li><code>\B</code>：匹配非单词边界。</li></ul><h4 id="分组和选择元字符"><a href="#分组和选择元字符" class="headerlink" title="分组和选择元字符"></a>分组和选择元字符</h4><p><code>()</code> (圆括号)</p><ul><li>定义子表达式或捕获组</li><li>示例：<code>(ab)+</code> 匹配 “ab”, “abab” 等</li></ul><p><code>|</code> (竖线)</p><ul><li>表示”或”关系</li><li>示例：<code>cat|dog</code> 匹配 “cat” 或 “dog”</li></ul><h4 id="特殊字符"><a href="#特殊字符" class="headerlink" title="特殊字符"></a>特殊字符</h4><p><code>\d</code></p><ul><li>匹配任意数字，等价于 <code>[0-9]</code></li></ul><p><code>\D</code></p><ul><li>匹配任意非数字，等价于 <code>[^0-9]</code></li></ul><p><code>\w</code></p><ul><li>匹配任意单词字符(字母、数字、下划线)，等价于 <code>[a-zA-Z0-9_]</code></li></ul><p><code>\W</code></p><ul><li>匹配任意非单词字符，等价于 <code>[^a-zA-Z0-9_]</code></li></ul><p><code>\s</code></p><ul><li>匹配任意空白字符(空格、制表符、换行符等)</li></ul><p><code>\S</code></p><ul><li>匹配任意非空白字符</li></ul><h4 id="非打印符"><a href="#非打印符" class="headerlink" title="非打印符"></a>非打印符</h4><div class="table-container"><table><thead><tr><th style="text-align:left">字符</th><th style="text-align:left">描述</th></tr></thead><tbody><tr><td style="text-align:left">\cx</td><td style="text-align:left">匹配由x指明的控制字符。例如， \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则，将 c 视为一个原义的 ‘c’ 字符。</td></tr><tr><td style="text-align:left">\f</td><td style="text-align:left">匹配一个换页符。等价于 \x0c 和 \cL。</td></tr><tr><td style="text-align:left">\n</td><td style="text-align:left">匹配一个换行符。等价于 \x0a 和 \cJ。</td></tr><tr><td style="text-align:left">\r</td><td style="text-align:left">匹配一个回车符。等价于 \x0d 和 \cM。</td></tr><tr><td style="text-align:left">\s</td><td style="text-align:left">匹配任何空白字符，包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。注意 Unicode 正则表达式会匹配全角空格符。</td></tr><tr><td style="text-align:left">\S</td><td style="text-align:left">匹配任何非空白字符。等价于 [ ^ \f\n\r\t\v]。</td></tr><tr><td style="text-align:left">\t</td><td style="text-align:left">匹配一个制表符。等价于 \x09 和 \cI。</td></tr><tr><td style="text-align:left">\v</td><td style="text-align:left">匹配一个垂直制表符。等价于 \x0b 和 \cK。</td></tr></tbody></table></div><h2 id="5-常用修饰符"><a href="#5-常用修饰符" class="headerlink" title="5.常用修饰符"></a>5.常用修饰符</h2><h4 id="1-i-ignore-case-忽略大小写"><a href="#1-i-ignore-case-忽略大小写" class="headerlink" title="1. i (ignore case) - 忽略大小写"></a>1. <code>i</code> (ignore case) - 忽略大小写</h4><ul><li>使匹配不区分大小写</li><li>示例：<code>/abc/i</code> 可以匹配 “abc”, “Abc”, “ABC” 等</li><li>支持语言：几乎所有正则表达式实现（JavaScript、PHP、Python等）</li></ul><h4 id="2-g-global-全局匹配"><a href="#2-g-global-全局匹配" class="headerlink" title="2. g (global) - 全局匹配"></a>2. <code>g</code> (global) - 全局匹配</h4><ul><li>查找所有匹配项，而不是在第一个匹配后停止</li><li>示例：在字符串 “ababab” 中，<code>/ab/g</code> 会匹配所有三个 “ab”</li><li>支持语言：JavaScript、PHP等</li></ul><h4 id="3-m-multiline-多行模式"><a href="#3-m-multiline-多行模式" class="headerlink" title="3. m (multiline) - 多行模式"></a>3. <code>m</code> (multiline) - 多行模式</h4><ul><li>改变 <code>^</code> 和 <code>$</code> 的行为，使其匹配每行的开头和结尾，而不仅是整个字符串的开头和结尾</li><li>示例：在多行字符串中，<code>/^abc/m</code> 会匹配每行开头的 “abc”</li><li>支持语言：JavaScript、PHP、Python、Perl等</li></ul><h4 id="4-s-single-line-dotall-单行模式"><a href="#4-s-single-line-dotall-单行模式" class="headerlink" title="4. s (single line/dotall) - 单行模式"></a>4. <code>s</code> (single line/dotall) - 单行模式</h4><ul><li>使点号 <code>.</code> 匹配包括换行符在内的所有字符</li><li>在JavaScript中称为”dotall”模式，使用 <code>/s</code> 修饰符</li><li>示例：<code>/a.b/s</code> 可以匹配 “a\nb”</li><li>支持语言：PHP、Perl、Python(作为<code>re.DOTALL</code>)、JavaScript(ES2018+)</li></ul><h4 id="5-u-unicode-Unicode模式"><a href="#5-u-unicode-Unicode模式" class="headerlink" title="5. u (unicode) - Unicode模式"></a>5. <code>u</code> (unicode) - Unicode模式</h4><ul><li>启用完整的Unicode支持</li><li>正确处理UTF-16代理对和Unicode字符属性</li><li>示例：<code>/\p{Script=Greek}/u</code> 可以匹配希腊字母</li><li>支持语言：JavaScript、PHP等</li></ul><h4 id="6-y-sticky-粘性匹配"><a href="#6-y-sticky-粘性匹配" class="headerlink" title="6. y (sticky) - 粘性匹配"></a>6. <code>y</code> (sticky) - 粘性匹配</h4><ul><li>从目标字符串的当前位置开始匹配（使用<code>lastIndex</code>属性）</li><li>类似于<code>^</code>锚点，但针对的是匹配的起始位置</li><li>示例：在JavaScript中，<code>/a/y</code> 会从<code>lastIndex</code>开始匹配 “a”</li><li>支持语言：JavaScript</li></ul><h4 id="7-x-extended-扩展模式"><a href="#7-x-extended-扩展模式" class="headerlink" title="7. x (extended) - 扩展模式"></a>7. <code>x</code> (extended) - 扩展模式</h4><ul><li>忽略模式中的空白和注释，使正则表达式更易读</li><li>示例：在PHP中，<code>/a b c/x</code> 等同于 <code>/abc/</code></li><li>支持语言：PHP、Perl、Python(作为<code>re.VERBOSE</code>)</li></ul><h2 id="6-贪婪与非贪婪量词"><a href="#6-贪婪与非贪婪量词" class="headerlink" title="6.贪婪与非贪婪量词"></a>6.贪婪与非贪婪量词</h2><p>默认情况下，量词(<code>*</code>, <code>+</code>, <code>?</code>, <code>{}</code>)是贪婪的，会尽可能多地匹配字符。在量词后加<code>?</code>可使其变为非贪婪(懒惰)模式：</p><ul><li><code>*?</code>：零次或多次，但尽可能少</li><li><code>+?</code>：一次或多次，但尽可能少</li><li><code>??</code>：零次或一次，但尽可能少</li><li><code>{n,m}?</code>：n到m次，但尽可能少</li></ul><p>示例：<code>&lt;.*?&gt;</code> 匹配HTML标签时不会跨标签匹配</p><h2 id="7-断言"><a href="#7-断言" class="headerlink" title="7.断言"></a>7.断言</h2><p>断言（Assertion）是正则表达式中用于指定匹配位置的元字符，它们不匹配任何实际字符，而是匹配字符之间的位置。</p><p>简单来说，断言就是”条件”，它要求目标字符串必须满足某些条件，但不会消耗字符。</p><h4 id="断言的特点"><a href="#断言的特点" class="headerlink" title="断言的特点"></a>断言的特点</h4><ol><li><strong>零宽度</strong>：不占用匹配字符的位置</li><li><strong>条件检查</strong>：只检查是否满足特定条件</li><li><strong>不影响匹配结果</strong>：仅作为匹配的约束条件</li></ol><h4 id="断言类型"><a href="#断言类型" class="headerlink" title="断言类型"></a>断言类型</h4><p><code>(?=...)</code> (正向肯定预查)</p><ul><li>匹配后面跟着特定模式的位置</li><li>示例：<code>Windows(?=95|98)</code> 匹配后面跟着95或98的”Windows”</li></ul><p><code>(?!...)</code> (正向否定预查)</p><ul><li>匹配后面不跟着特定模式的位置</li><li>示例：<code>Windows(?!95|98)</code> 匹配后面不跟着95或98的”Windows”</li></ul><p><code>(?&lt;=...)</code> (反向肯定预查)</p><ul><li>匹配前面是特定模式的位置</li><li>示例：<code>(?&lt;=95|98)Windows</code> 匹配前面是95或98的”Windows”</li></ul><p><code>(?&lt;!...)</code> (反向否定预查)</p><ul><li>匹配前面不是特定模式的位置</li><li>示例：<code>(?&lt;!95|98)Windows</code> 匹配前面不是95或98的”Windows”</li></ul><h2 id="8-运算符优先级"><a href="#8-运算符优先级" class="headerlink" title="8.运算符优先级"></a>8.运算符优先级</h2><p>以下是一些常见正则表达式运算符按照优先级从高到低的顺序：</p><ul><li><p><strong>转义符号：</strong> <code>\</code> 是用于转义其他特殊字符的转义符号。它具有最高的优先级。</p><p>示例：<code>\d</code>、<code>\.</code> 等，其中 <code>\d</code> 匹配数字，<code>\.</code> 匹配点号。</p></li><li><p><strong>括号：</strong> 圆括号 <code>()</code> 用于创建子表达式，具有高于其他运算符的优先级。</p><p>示例：<code>(abc)+</code> 匹配 “abc” 一次或多次。</p></li><li><p><strong>量词：</strong> 量词指定前面的元素可以重复的次数。</p><p>示例：<code>a*</code> 匹配零个或多个 “a”。</p></li><li><p><strong>字符类：</strong> 字符类使用方括号 <code>[]</code> 表示，用于匹配括号内的任意字符。</p><p>示例：<code>[aeiou]</code> 匹配任何一个元音字母。</p></li><li><p><strong>断言：</strong> 断言是用于检查字符串中特定位置的条件的元素。</p><p>示例：<code>^</code> 表示行的开头，<code>$</code> 表示行的结尾。</p></li><li><p><strong>连接：</strong> 连接在没有其他运算符的情况下表示字符之间的简单连接。</p><p>示例：<code>abc</code> 匹配 “abc”。</p></li><li><p><strong>管道：</strong> 管道符号 <code>|</code> 表示”或”关系，用于在多个模式之间选择一个。</p><p>示例：<code>cat|dog</code> 匹配 “cat” 或 “dog”。</p></li></ul><h2 id="9-分组和引用"><a href="#9-分组和引用" class="headerlink" title="9.分组和引用"></a>9.分组和引用</h2><div class="table-container"><table><thead><tr><th style="text-align:left">概念</th><th style="text-align:left">语法</th><th style="text-align:left">用途</th></tr></thead><tbody><tr><td style="text-align:left">捕获分组</td><td style="text-align:left"><code>(pattern)</code></td><td style="text-align:left">捕获匹配内容并分配编号</td></tr><tr><td style="text-align:left">非捕获分组</td><td style="text-align:left"><code>(?:pattern)</code></td><td style="text-align:left">分组但不捕获</td></tr><tr><td style="text-align:left">命名分组</td><td style="text-align:left"><code>(?P&lt;name&gt;pattern)</code> (Python)</td><td style="text-align:left">为分组指定名称</td></tr><tr><td style="text-align:left">反向引用</td><td style="text-align:left"><code>\1</code>, <code>\2</code> 等</td><td style="text-align:left">引用前面匹配的分组</td></tr><tr><td style="text-align:left">命名反向引用</td><td style="text-align:left"><code>(?P=name)</code> (Python)</td><td style="text-align:left">按名称引用分组</td></tr><tr><td style="text-align:left">替换引用</td><td style="text-align:left"><code>$1</code>, <code>$2</code> 或 <code>\1</code>, <code>\2</code></td><td style="text-align:left">在替换字符串中引用分组</td></tr></tbody></table></div>]]></content>
    
    
    <summary type="html">正则表达式学习笔记</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=30c8d9f0-8047-86e8-00a1-073b063695ce" type="image"/>
    
    
    <category term="学习笔记" scheme="https://blog.itrf.cn/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>秘密花园</title>
    <link href="https://blog.itrf.cn/posts/1a1/"/>
    <id>https://blog.itrf.cn/posts/1a1/</id>
    <published>2019-12-31T16:00:00.000Z</published>
    <updated>2026-09-07T07:42:24.228Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="抱歉，这个密码看着不太对，请再试试。" data-whm="抱歉，这个文章不能被纠正，不过您还是能看看解密后的内容">  <script id="hbeData" type="hbeData" data-hmacdigest="9fb906de58b6ee0478b80ef04fe6dba0b0ddb199efcee3bfcf2f03f8b8eae62f">301ba6d5cdadf7fe86fdc59c24feda41ad1ca31c0c9c974c9614596a47f93d5ebcc66ab52f04387f3d9534b0d7aa61b1</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-default">      <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-default">您好，这里需要密码。</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">这是一个秘密花园</summary>
    
    
    <content src="https://t.alcy.cc/ycy?_r_=47f634ff-1d8d-5f44-e666-0ee1aaabeb0a" type="image"/>
    
    
    <category term="加密" scheme="https://blog.itrf.cn/tags/%E5%8A%A0%E5%AF%86/"/>
    
  </entry>
  
</feed>