<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Alan</title>
  <subtitle>代码,日常,人生 | alan.org.cn</subtitle>
  <icon>https://alan.org.cn/assets/images/avatar.jpeg</icon>
  <link href="https://alan.org.cn/atom.xml" rel="self"/>
  <link href="https://alan.org.cn/"/>
  <updated>2026-03-14T12:00:00.000Z</updated>
  <id>https://alan.org.cn/</id>
  
  <author>
    <name>Alan</name>
    <email>fignet@163.com</email>
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>yolov5/yolov3训练自己的数据集</title>
    <link href="https://alan.org.cn/posts/yolov5-yolov3%E8%AE%AD%E7%BB%83%E8%87%AA%E5%B7%B1%E7%9A%84%E6%95%B0%E6%8D%AE%E9%9B%86.html"/>
    <id>https://alan.org.cn/posts/yolov5-yolov3%E8%AE%AD%E7%BB%83%E8%87%AA%E5%B7%B1%E7%9A%84%E6%95%B0%E6%8D%AE%E9%9B%86.html</id>
    <published>2026-03-14T12:00:00.000Z</published>
    <updated>2026-03-14T12:00:00.000Z</updated>
    
    
    
    
      
      
    <summary type="html">&lt;p&gt;YOLO系列是非常优秀的物体检测框架，目前总共有V1-V5五个版本。本博客的目的在于教大家如何使用YOLOv5训练自己的数据，重点在于应用。有关原理我会在后面的博客中详细介绍。使用YOLOv5训练自己的数据往往要经过以下几个步骤：&lt;/p&gt;
&lt;p&gt;一、环境配置&lt;/p&gt;
&lt;p&gt;</summary>
      
    
    
    <content type="html"><![CDATA[<p>YOLO系列是非常优秀的物体检测框架，目前总共有V1-V5五个版本。本博客的目的在于教大家如何使用YOLOv5训练自己的数据，重点在于应用。有关原理我会在后面的博客中详细介绍。使用YOLOv5训练自己的数据往往要经过以下几个步骤：</p><p>一、环境配置</p><p>在环境配置方面，我安装的是tensorflow-gpu2.3.0、cuda10.1、cudnn7.6.4、torch1.9.0、anaconda我选择的是python3.8版本。有关详细的安装过程大家可以参考我的另一篇博客《Tensorflow-gpu2.1.0+cuda+cudnn+torch安装教程（超详细）》博客地址：</p><p>CSDN</p><p>二、克隆源代码</p><p>目前GitHub上有非常多的YOLOv5代码，其中YOLOv5论文的源代码地址为：</p><p>GitHub - ultralytics&#x2F;yolov5: YOLOv5 🚀 in PyTorch &gt; ONNX &gt; CoreML &gt; TFLite，建议大家尽量去下载“官方代码”。具体如下图所示:</p><p> 然后点击Code会弹出下载选项，具体如下图所示：</p><p> 当下载完YOLOv5工程压缩文件后，保存在你想要保存的位置然后进行解压，介意大家最好新建一个文件夹专门存放YOLOv5工程代码。</p><p> 解压后的文件如下图所示：</p><p> 至此，我们就完成了源代码的下载。</p><p>三、调试源代码</p><p>当下载完源代码以后，我们就可以开始调试源代码。确保源代码可以正常运行后，再开始训练我们的数据。下载完成后打开requirements.txt文件，安装工程需要的python包。</p><p> 大家可以根据自己的情况选择性的安装。这里并不介意大家直接安装官方指导进行安装。当安装完所需要的库时接下来就可以进行代码的调试。</p><p>使用notebook打开我们的工程文件中的tutorial.ipynb文件，首先打开notebook打开的过程中应该注意在jupyter notebook后面添加你的工程路径，这样你才能进入此工程目录下。如下图所示:</p><p> 打开notebook后点击tutorial.ipynb文件。进入后的界面如下图所示：</p><p>  然后按照官网的要求进行代码调试。如果大家事先安装好了环境，并且按照上面的步骤打开tutorial.ipynb文件，可以注释掉前三行代码，然后运行后面的代码即可。具体如下图所示：</p><p>当大家运行第二行代码时，可能发现代码运行错误，具体的错误如上图所示。报错的原因在于没有在系统文件中找到yolov5s.pt。大家可以尝试去找一下，发现确实工程中不存在此文件。 </p><p> 接下来需要解决此错误。首先打开GitHub中的工程文件，然后拉到页面最下面，点击yolov5s。具体如下图所示：</p><p> 点击进去后的界面如下图所示：</p><p> 然后将此界面拉到最低下，就可以看到yolov5s.pt的下载界面，具体如下图所示：</p><p> 然后点击yolov5s.pt进行下载。</p><p> 这样我们就完成了yolov5s.pt的下载，如果后续开发中还需要其他的工具，也可以直接在原工程中进行下载。接下来我们需要在工程目录中新建“weights”文件夹。具体如下图所示：</p><p> 然后再将刚刚下载的yolov5s.pt文件复制在此目录下，具体如下图所示：</p><p>接下来我们就可以进行模型的检测程序了。然后修改原运行代码，在yolov5s.pt前面添加.&#x2F;weights&#x2F;,这样就可以进行代码的正常测试了。也即</p><p>!python detect.py –weights .&#x2F;weights&#x2F;yolov5s.pt –img 640 –conf 0.25 –source data&#x2F;images&#x2F;</p><p> 代码运行结果如下：</p><p> 如果看到上特的检测结果，那就表明我们的程序可以进行正常运行了。当然，我们也可以调用摄像头进行物体的实时检测，具体的命令是：</p><p>!python detect.py –weights .&#x2F;weights&#x2F;yolov5s.pt –source 0  运行的结果如下图所示:</p><p> 至此，我们就完成了代码的调试。</p><p>四、准备自己的数据集并标注</p><p>在完成了代码调试后就可以进行训练自己的数据了。首先准备自己的数据集，然后进行标注。首先将所有要打标签的图片存放在data下面的images文件夹中，（这点比较重要，千万注意路径images文件夹一定要放在data下面，如果放在外面那需要修改split.py文件中的路径。并且，要标注的图片直接放在images文件夹下）具体如下图所示：</p><p> 然后在工程中的data文件夹下新建两个文件夹，分别为dataset、labels。其中，datasets主要用来存放打好标签后的数据集，labels主要用来存放数据标签。具体如下图所示：</p><p> 创建好文件夹后，进行过数据的标注。标注的工具选用labelimg。首先安装labelimg然后启动:</p><p>（我事先已经安装好了，所以会有这样的提示）</p><p> 启动后的界面如下图所示：</p><p> 接下来，我们首先需要对labelimg进行一些设置：首先选择自动保存模型，然后再选择改变保存路径，最后打开图片文件进行数据的标注。具体操作如下面几张图所示：</p><p> 然后开始给图片打标签 </p><p> 对所有的图片都进行打标签，最后可以在dataset文件夹中，看到我们打好标签的xml文件。具体如下图所示：</p><p> 这样我们就完成了数据的标注。</p><p>五、数据预处理与源代码修改</p><p>接下来需要对标注好的数据进行预处理操作，首先在工程目录文件下新建split.py程序，具体如下所示：</p><p> split.py的具体代码如下所示：</p><p>import os<br>import random<br>import argparse<br>parser &#x3D; argparse.ArgumentParser()<br>parser.add_argument(‘–xml_path’, default&#x3D;’data&#x2F;dataset’, type&#x3D;str, help&#x3D;’input xml label path’)<br>parser.add_argument(‘–txt_path’, default&#x3D;’data&#x2F;labels’, type&#x3D;str, help&#x3D;’output txt label path’)<br>opt &#x3D; parser.parse_args()<br>trainval_percent &#x3D; 1.0<br>train_percent &#x3D; 0.8<br>xmlfilepath &#x3D; opt.xml_path<br>txtsavepath &#x3D; opt.txt_path<br>total_xml &#x3D; os.listdir(xmlfilepath)<br>if not os.path.exists(txtsavepath):<br>    os.makedirs(txtsavepath)<br>num &#x3D; len(total_xml)<br>list_index &#x3D; range(num)<br>tv &#x3D; int(num * trainval_percent)<br>tr &#x3D; int(tv * train_percent)<br>trainval &#x3D; random.sample(list_index, tv)<br>train &#x3D; random.sample(trainval, tr)<br>file_trainval &#x3D; open(txtsavepath + ‘&#x2F;trainval.txt’, ‘w’)<br>file_test &#x3D; open(txtsavepath + ‘&#x2F;test.txt’, ‘w’)<br>file_train &#x3D; open(txtsavepath + ‘&#x2F;train.txt’, ‘w’)<br>file_val &#x3D; open(txtsavepath + ‘&#x2F;val.txt’, ‘w’)<br>for i in list_index:<br>    name &#x3D; total_xml[i][:-4] + ‘\n’<br>    if i in trainval:<br>        file_trainval.write(name)<br>        if i in train:<br>            file_train.write(name)<br>        else:<br>            file_val.write(name)<br>    else:<br>        file_test.write(name)<br>file_trainval.close()<br>file_train.close()<br>file_val.close()<br>file_test.close()<br>AI写代码<br>python<br>运行</p><p>然后运行此代码，运行完成后的结果如下图所示：</p><p> 然后以同样的方式再创建xml_to_txt.py文件并运行，具体如下图所示：</p><p> xml_to_txt.py代码如下：</p><p>import xml.etree.ElementTree as ET<br>from tqdm import tqdm<br>import os<br>from os import getcwd</p><p>sets &#x3D; [‘train’, ‘val’, ‘test’]<br>classes &#x3D; [‘1’,’5’]   # 这里改为你要训练的标签，否则会报错。比如你要识别“hand”，那这里就改为hand</p><p>def convert(size, box):<br>    dw &#x3D; 1. &#x2F; (size[0])<br>    dh &#x3D; 1. &#x2F; (size[1])<br>    x &#x3D; (box[0] + box[1]) &#x2F; 2.0 - 1<br>    y &#x3D; (box[2] + box[3]) &#x2F; 2.0 - 1<br>    w &#x3D; box[1] - box[0]<br>    h &#x3D; box[3] - box[2]<br>    x &#x3D; x * dw<br>    w &#x3D; w * dw<br>    y &#x3D; y * dh<br>    h &#x3D; h * dh<br>    return x, y, w, h</p><p>def convert_annotation(image_id):<br>    # try:<br>        in_file &#x3D; open(‘data&#x2F;dataset&#x2F;%s.xml’ % (image_id), encoding&#x3D;’utf-8’)<br>        out_file &#x3D; open(‘data&#x2F;labels&#x2F;%s.txt’ % (image_id), ‘w’, encoding&#x3D;’utf-8’)<br>        tree &#x3D; ET.parse(in_file)<br>        root &#x3D; tree.getroot()<br>        size &#x3D; root.find(‘size’)<br>        w &#x3D; int(size.find(‘width’).text)<br>        h &#x3D; int(size.find(‘height’).text)<br>        for obj in root.iter(‘object’):<br>            difficult &#x3D; obj.find(‘difficult’).text<br>            cls &#x3D; obj.find(‘name’).text<br>            if cls not in classes or int(difficult) &#x3D;&#x3D; 1:<br>                continue<br>            cls_id &#x3D; classes.index(cls)<br>            xmlbox &#x3D; obj.find(‘bndbox’)<br>            b &#x3D; (float(xmlbox.find(‘xmin’).text), float(xmlbox.find(‘xmax’).text), float(xmlbox.find(‘ymin’).text),<br>                 float(xmlbox.find(‘ymax’).text))<br>            b1, b2, b3, b4 &#x3D; b<br>            # 标注越界修正<br>            if b2 &gt; w:<br>                b2 &#x3D; w<br>            if b4 &gt; h:<br>                b4 &#x3D; h<br>            b &#x3D; (b1, b2, b3, b4)<br>            bb &#x3D; convert((w, h), b)<br>            out_file.write(str(cls_id) + “ “ +<br>                           “ “.join([str(a) for a in bb]) + ‘\n’)<br>    # except Exception as e:<br>    #     print(e, image_id)</p><p>wd &#x3D; getcwd()<br>for image_set in sets:<br>    if not os.path.exists(‘data&#x2F;labels&#x2F;‘):<br>        os.makedirs(‘data&#x2F;labels&#x2F;‘)<br>    image_ids &#x3D; open(‘data&#x2F;labels&#x2F;%s.txt’ %<br>                     (image_set)).read().strip().split()<br>    list_file &#x3D; open(‘data&#x2F;%s.txt’ % (image_set), ‘w’)<br>    for image_id in tqdm(image_ids):<br>        list_file.write(‘data&#x2F;images&#x2F;%s.jpg\n’ % (image_id))<br>        convert_annotation(image_id)<br>    list_file.close()<br>AI写代码<br>python<br>运行</p><p>以上代码中classes &#x3D; [“你要训练的标签名称”]，在本代码中我要训练的数据是识别1和5所以我这里改成了“1”和“5”。大家根据自己的情况进行修改。运行此代码，运行后的结果如下图所示：</p><p> 接下来在data文件夹中新建myvoc.yaml文件，具体操作以及代码如下图所示：</p><p> 这里一定要注意一点在train,val,nc,names冒号后面一定要空一格，要判断正确不正确，只要看这几个关键字有没有变颜色，要是变颜色那就说明是正确的。最后再修改models下的yolov5s.yaml文件，具体修改方式如下图所示:</p><p> 至此，我们就完成了数据预处理以及代码的修改。</p><p> 六、训练自己的数据</p><p>接下来，我们就可以训练自己的数据了。首先打开终端并进入到yolov5的工程目录中：</p><p> 然后输入命令：</p><p>python train.py –epoch 300 –batch 4 –data .&#x2F;data&#x2F;myvoc.yaml –cfg .&#x2F;models&#x2F;yolov5s.yaml –weight .&#x2F;weights&#x2F;yolov5s.pt –workers 0   进行训练，如果出现内存不足的错误，将batch后面的4进一步改小，应该就不会出问题。</p><p> 训练结束后，我们就可以看到训练好模型存储的位置，具体如下图所示：</p><p> 找到训练好的模型last.py将其复制在weights文件下，具体如下图所示：</p><p>至此我们就完成了模型的训练。 如果训练提前终止，不能按照自己设定的epoch进行训练，可以使用以下指令来训练：</p><p>python train.py –epoch 500 –patience 0 –batch 4 –data .&#x2F;data&#x2F;myvoc.yaml –cfg .&#x2F;models&#x2F;yolov5s.yaml –weight .&#x2F;weights&#x2F;yolov5s.pt –workers 0</p><p>七、模型测试</p><p>训练完成后，我们可以进行模型的测试，测试一下刚刚训练的结果。打开命令行:</p><p>输入指令:</p><p>python detect.py –weight .&#x2F;weights&#x2F;last.pt –source 0</p><p>至此，我们就完成了在yolov5上训练自己的数据集，由于我的数据比较少，所以准确度不是很好，大家可以自行尝试训练其他的数据。Yolov3训练自己的数据也可以按照此方法进行，经过测试也可以进行训练。在过程中遇到问题时，大家也可以随时留言。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>Windows 环境下部署 OpenClaw 完整指南</title>
    <link href="https://alan.org.cn/posts/openclaw-deployment-guide.html"/>
    <id>https://alan.org.cn/posts/openclaw-deployment-guide.html</id>
    <published>2026-03-13T11:53:00.000Z</published>
    <updated>2026-06-04T04:10:49.248Z</updated>
    
    
    <category term="技术教程" scheme="https://alan.org.cn/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
    <category term="OpenClaw" scheme="https://alan.org.cn/tags/OpenClaw/"/>
    
    <category term="部署教程" scheme="https://alan.org.cn/tags/%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B/"/>
    
    <category term="AI 助手" scheme="https://alan.org.cn/tags/AI-%E5%8A%A9%E6%89%8B/"/>
    
    <category term="Windows" scheme="https://alan.org.cn/tags/Windows/"/>
    
    
    <summary type="html">本教程详细介绍在 Windows 环境下安装和配置 OpenClaw 的全过程，包括环境准备、安装、配置以及故障排查。</summary>
    
    
    <content type="html"><![CDATA[<h1 id="Windows-环境下部署-OpenClaw-完整指南"><a href="#Windows-环境下部署-OpenClaw-完整指南" class="headerlink" title="Windows 环境下部署 OpenClaw 完整指南"></a>Windows 环境下部署 OpenClaw 完整指南</h1><p>OpenClaw 是一个强大的 AI 助手框架，本文档将详细指导你如何在 Windows 系统上完成安装和配置。</p><h2 id="📋-目录"><a href="#📋-目录" class="headerlink" title="📋 目录"></a>📋 目录</h2><ul><li><a href="#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C">准备工作</a></li><li><a href="#%E7%8E%AF%E5%A2%83%E5%AE%89%E8%A3%85">环境安装</a></li><li><a href="#openclaw-%E5%AE%89%E8%A3%85">OpenClaw 安装</a></li><li><a href="#%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE">基础配置</a></li><li><a href="#%E9%AB%98%E7%BA%A7%E9%85%8D%E7%BD%AE">高级配置</a></li><li><a href="#%E6%95%85%E9%9A%9C%E6%8E%92%E6%9F%A5">故障排查</a></li></ul><hr><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><h3 id="1-下载必要软件"><a href="#1-下载必要软件" class="headerlink" title="1. 下载必要软件"></a>1. 下载必要软件</h3><h4 id="Node-js-下载地址"><a href="#Node-js-下载地址" class="headerlink" title="Node.js 下载地址"></a>Node.js 下载地址</h4><p><a href="https://nodejs.org/zh-cn/download">https://nodejs.org/zh-cn/download</a></p><p>建议选择 LTS 版本（长期支持版），更适合生产环境使用。</p><h4 id="Git-下载地址"><a href="#Git-下载地址" class="headerlink" title="Git 下载地址"></a>Git 下载地址</h4><p><a href="https://registry.npmmirror.com/binary.html?path=git-for-windows">https://registry.npmmirror.com/binary.html?path=git-for-windows</a></p><p>国内用户推荐使用 NPM Mirror 提供的镜像源，下载速度更快。</p><h3 id="2-设置-PowerShell-执行策略"><a href="#2-设置-PowerShell-执行策略" class="headerlink" title="2. 设置 PowerShell 执行策略"></a>2. 设置 PowerShell 执行策略</h3><p>Windows 默认会阻止脚本执行，需要先修改执行策略：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell"><span class="hljs-built_in">Set-ExecutionPolicy</span> <span class="hljs-literal">-ExecutionPolicy</span> RemoteSigned <span class="hljs-literal">-Scope</span> CurrentUser <span class="hljs-literal">-Force</span><br></code></pre></td></tr></table></figure><p><strong>说明</strong>: <code>RemoteSigned</code> 允许本地执行的脚本，对于远程下载的脚本需要签名。这是平衡安全性和可用性的最佳选择。</p><hr><h2 id="环境安装"><a href="#环境安装" class="headerlink" title="环境安装"></a>环境安装</h2><h3 id="1-安装-Node-js"><a href="#1-安装-Node-js" class="headerlink" title="1. 安装 Node.js"></a>1. 安装 Node.js</h3><p>运行下载好的 Node.js 安装包，一路点击 “Next” 即可完成安装。</p><p>安装完成后，验证安装是否成功：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs powershell">node <span class="hljs-literal">--version</span><br>npm <span class="hljs-literal">--version</span><br></code></pre></td></tr></table></figure><p>应该能输出类似：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">v20.x.x<br>10.x.x<br></code></pre></td></tr></table></figure><h3 id="2-配置国内镜像源"><a href="#2-配置国内镜像源" class="headerlink" title="2. 配置国内镜像源"></a>2. 配置国内镜像源</h3><p>为了加速 npm 包的安装，建议切换到国内镜像源：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">npm config <span class="hljs-built_in">set</span> registry https://registry.npmmirror.com/<br></code></pre></td></tr></table></figure><p>验证配置：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">npm config get registry<br></code></pre></td></tr></table></figure><p>应该输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://registry.npmmirror.com/<br></code></pre></td></tr></table></figure><h3 id="3-安装-Git"><a href="#3-安装-Git" class="headerlink" title="3. 安装 Git"></a>3. 安装 Git</h3><p>运行 Git 安装包，使用默认配置即可。安装完成后验证：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">git <span class="hljs-literal">--version</span><br></code></pre></td></tr></table></figure><hr><h2 id="OpenClaw-安装"><a href="#OpenClaw-安装" class="headerlink" title="OpenClaw 安装"></a>OpenClaw 安装</h2><h3 id="标准安装方式"><a href="#标准安装方式" class="headerlink" title="标准安装方式"></a>标准安装方式</h3><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">npm install <span class="hljs-literal">-g</span> openclaw@<span class="hljs-number">2026.3</span>.<span class="hljs-number">2</span><br></code></pre></td></tr></table></figure><h3 id="跳过编译安装（推荐）"><a href="#跳过编译安装（推荐）" class="headerlink" title="跳过编译安装（推荐）"></a>跳过编译安装（推荐）</h3><p>如果遇到编译错误，可以使用以下命令跳过编译步骤：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">npm install <span class="hljs-literal">-g</span> openclaw@<span class="hljs-number">2026.3</span>.<span class="hljs-number">2</span> <span class="hljs-literal">--ignore-scripts</span> <span class="hljs-literal">--no-optional</span> <span class="hljs-literal">--force</span><br></code></pre></td></tr></table></figure><p><strong>参数说明</strong>:</p><ul><li><code>--ignore-scripts</code>: 跳过安装脚本</li><li><code>--no-optional</code>: 不安装可选依赖</li><li><code>--force</code>: 强制安装</li></ul><h3 id="验证安装"><a href="#验证安装" class="headerlink" title="验证安装"></a>验证安装</h3><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">openclaw <span class="hljs-literal">--version</span><br></code></pre></td></tr></table></figure><hr><h2 id="基础配置"><a href="#基础配置" class="headerlink" title="基础配置"></a>基础配置</h2><h3 id="1-生成-SSH-密钥对"><a href="#1-生成-SSH-密钥对" class="headerlink" title="1. 生成 SSH 密钥对"></a>1. 生成 SSH 密钥对</h3><p>首先需要生成 SSH 密钥，用于与服务器通信：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">ssh<span class="hljs-literal">-keygen</span> <span class="hljs-literal">-t</span> rsa <span class="hljs-literal">-b</span> <span class="hljs-number">4096</span> <span class="hljs-literal">-C</span> <span class="hljs-string">&quot;你的邮箱@example.com&quot;</span><br></code></pre></td></tr></table></figure><p><strong>注意</strong>: 将 <code>&quot;你的邮箱@example.com&quot;</code> 替换为你自己的邮箱地址。</p><p>按提示保存密钥（直接回车使用默认位置），并设置 passphrase（可选）。</p><p>生成的密钥文件位于：</p><ul><li>私钥：<code>~/.ssh/id_rsa</code></li><li>公钥：<code>~/.ssh/id_rsa.pub</code></li></ul><h3 id="2-初始化-OpenClaw"><a href="#2-初始化-OpenClaw" class="headerlink" title="2. 初始化 OpenClaw"></a>2. 初始化 OpenClaw</h3><p>运行以下命令进行初始配置：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">openclaw onboard <span class="hljs-literal">--install-daemon</span><br></code></pre></td></tr></table></figure><h3 id="3-查看访问地址和-Token"><a href="#3-查看访问地址和-Token" class="headerlink" title="3. 查看访问地址和 Token"></a>3. 查看访问地址和 Token</h3><p>安装完成后，获取访问 Dashboard 的地址和 Token：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">openclaw dashboard <span class="hljs-literal">--no-open</span><br></code></pre></td></tr></table></figure><p>这个命令会输出包含 Token 的访问地址，请妥善保管。</p><hr><h2 id="高级配置"><a href="#高级配置" class="headerlink" title="高级配置"></a>高级配置</h2><h3 id="1-启动服务"><a href="#1-启动服务" class="headerlink" title="1. 启动服务"></a>1. 启动服务</h3><h4 id="重启服务"><a href="#重启服务" class="headerlink" title="重启服务"></a>重启服务</h4><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">openclaw gateway restart<br></code></pre></td></tr></table></figure><h4 id="以调试模式启动"><a href="#以调试模式启动" class="headerlink" title="以调试模式启动"></a>以调试模式启动</h4><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">openclaw gateway <span class="hljs-literal">--port</span> <span class="hljs-number">18789</span> <span class="hljs-literal">--verbose</span><br></code></pre></td></tr></table></figure><p><strong>参数说明</strong>:</p><ul><li><code>--port</code>: 指定服务端口（默认为 18789）</li><li><code>--verbose</code>: 启用详细日志输出</li></ul><h3 id="2-配置文件位置"><a href="#2-配置文件位置" class="headerlink" title="2. 配置文件位置"></a>2. 配置文件位置</h3><p>编辑 OpenClaw 配置文件：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">openclaw config<br></code></pre></td></tr></table></figure><p>或者手动编辑配置文件（通常位于 <code>~/.openclaw/config.json</code>）。</p><h3 id="3-配置工具权限"><a href="#3-配置工具权限" class="headerlink" title="3. 配置工具权限"></a>3. 配置工具权限</h3><p>在配置文件中添加以下权限设置， granting 所有工具的完全访问权限：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;tools&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;profile&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;full&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;allow&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>      <span class="hljs-string">&quot;group:all&quot;</span><br>    <span class="hljs-punctuation">]</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><h3 id="4-配置-Feishu-飞书-集成"><a href="#4-配置-Feishu-飞书-集成" class="headerlink" title="4. 配置 Feishu (飞书) 集成"></a>4. 配置 Feishu (飞书) 集成</h3><p>如果需要集成 Feishu 作为消息通道，需要在 OpenClaw 配置中添加以下权限：</p><p><strong>必需权限</strong>:</p><ul><li><code>im:message</code> - 发送和接收消息</li><li><code>im:chat</code> - 管理聊天会话</li></ul><p>在 Feishu 开发者平台申请应用时，确保勾选这些权限。</p><hr><h2 id="系统维护"><a href="#系统维护" class="headerlink" title="系统维护"></a>系统维护</h2><h3 id="检查与修复"><a href="#检查与修复" class="headerlink" title="检查与修复"></a>检查与修复</h3><p>如果遇到问题，可以使用诊断工具：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">openclaw doctor <span class="hljs-literal">--fix</span><br></code></pre></td></tr></table></figure><p>这个命令会自动检测常见问题并提供修复建议。</p><h3 id="查看状态"><a href="#查看状态" class="headerlink" title="查看状态"></a>查看状态</h3><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">openclaw status<br></code></pre></td></tr></table></figure><hr><h2 id="故障排查"><a href="#故障排查" class="headerlink" title="故障排查"></a>故障排查</h2><h3 id="问题-1-全局安装目录问题"><a href="#问题-1-全局安装目录问题" class="headerlink" title="问题 1: 全局安装目录问题"></a>问题 1: 全局安装目录问题</h3><p>如果遇到安装错误或权限问题，可以尝试切换全局安装目录：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs powershell"><span class="hljs-comment"># 创建新的全局模块目录</span><br>mkdir C:\node<span class="hljs-literal">-modules</span><br><br><span class="hljs-comment"># 设置全局模块路径</span><br>npm config <span class="hljs-built_in">set</span> prefix C:\node<span class="hljs-literal">-modules</span><br><br><span class="hljs-comment"># 设置全局缓存路径</span><br>npm config <span class="hljs-built_in">set</span> cache C:\node<span class="hljs-literal">-cache</span><br><br><span class="hljs-comment"># 添加到环境变量</span><br><span class="hljs-variable">$env:Path</span> += <span class="hljs-string">&quot;;C:\node-modules\node_modules\bin&quot;</span><br></code></pre></td></tr></table></figure><p><strong>注意</strong>: 修改后需要重新打开 PowerShell 窗口使环境变量生效。</p><h3 id="问题-2-编译错误"><a href="#问题-2-编译错误" class="headerlink" title="问题 2: 编译错误"></a>问题 2: 编译错误</h3><p>如果在安装过程中遇到编译相关的错误（如缺少 C++ 编译器等），需要：</p><ol><li><p><strong>安装 Visual Studio</strong>: </p><ul><li>下载 Visual Studio Community（免费）</li><li>安装时勾选 “C++桌面开发” 工作负载</li></ul></li><li><p><strong>安装 Windows Build Tools</strong>:</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">npm install <span class="hljs-literal">-g</span> windows<span class="hljs-literal">-build-tools</span><br></code></pre></td></tr></table></figure></li></ol><h3 id="问题-3-端口被占用"><a href="#问题-3-端口被占用" class="headerlink" title="问题 3: 端口被占用"></a>问题 3: 端口被占用</h3><p>如果 18789 端口被占用，可以更换其他端口：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">openclaw gateway <span class="hljs-literal">--port</span> <span class="hljs-number">18790</span> <span class="hljs-literal">--verbose</span><br></code></pre></td></tr></table></figure><h3 id="问题-4-服务无法启动"><a href="#问题-4-服务无法启动" class="headerlink" title="问题 4: 服务无法启动"></a>问题 4: 服务无法启动</h3><p>尝试以下步骤：</p><ol><li>检查防火墙设置，确保相应端口已开放</li><li>查看日志文件获取详细错误信息</li><li>使用 <code>openclaw doctor --fix</code> 自动修复</li><li>重启服务：<code>openclaw gateway restart</code></li></ol><hr><h2 id="常见问题-FAQ"><a href="#常见问题-FAQ" class="headerlink" title="常见问题 FAQ"></a>常见问题 FAQ</h2><h3 id="Q-为什么安装这么慢？"><a href="#Q-为什么安装这么慢？" class="headerlink" title="Q: 为什么安装这么慢？"></a>Q: 为什么安装这么慢？</h3><p>A: 建议配置国内镜像源，或使用 <code>--ignore-scripts</code> 选项跳过编译。</p><h3 id="Q-Dashboard-打不开？"><a href="#Q-Dashboard-打不开？" class="headerlink" title="Q: Dashboard 打不开？"></a>Q: Dashboard 打不开？</h3><p>A: 检查服务是否在运行，确认端口正确，查看防火墙设置。</p><h3 id="Q-如何卸载-OpenClaw？"><a href="#Q-如何卸载-OpenClaw？" class="headerlink" title="Q: 如何卸载 OpenClaw？"></a>Q: 如何卸载 OpenClaw？</h3><p>A: 运行 <code>npm uninstall -g openclaw</code> 即可卸载。</p><h3 id="Q-如何升级-OpenClaw？"><a href="#Q-如何升级-OpenClaw？" class="headerlink" title="Q: 如何升级 OpenClaw？"></a>Q: 如何升级 OpenClaw？</h3><p>A: 运行 <code>npm install -g openclaw@latest</code> 升级到最新版本。</p><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>恭喜！你已经完成了 OpenClaw 的安装和基础配置。接下来可以：</p><ol><li>访问 Dashboard 开始使用</li><li>配置更多的集成和插件</li><li>探索 OpenClaw 的高级功能</li></ol><p>如有问题，请使用 <code>openclaw doctor --help</code> 获取更多帮助。</p><hr><p><strong>最后更新</strong>: 2026-03-13<br><strong>适用版本</strong>: OpenClaw 2026.3.2<br><strong>操作系统</strong>: Windows 10&#x2F;11</p>]]></content>
    
  </entry>
  
  <entry>
    <title>纳瓦尔宝典读书笔记</title>
    <link href="https://alan.org.cn/posts/%E7%BA%B3%E7%93%A6%E5%B0%94%E5%AE%9D%E5%85%B8%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/%E7%BA%B3%E7%93%A6%E5%B0%94%E5%AE%9D%E5%85%B8%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.html</id>
    <published>2026-03-08T18:31:00.000Z</published>
    <updated>2026-03-08T18:31:00.000Z</updated>
    
    
    <category term="读书笔记" scheme="https://alan.org.cn/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="读书" scheme="https://alan.org.cn/tags/%E8%AF%BB%E4%B9%A6/"/>
    
    <category term="纳瓦尔" scheme="https://alan.org.cn/tags/%E7%BA%B3%E7%93%A6%E5%B0%94/"/>
    
    <category term="财富" scheme="https://alan.org.cn/tags/%E8%B4%A2%E5%AF%8C/"/>
    
    <category term="幸福" scheme="https://alan.org.cn/tags/%E5%B9%B8%E7%A6%8F/"/>
    
    
    <summary type="html">纳瓦尔宝典核心思想整理——关于财富、幸福和人生的智慧</summary>
    
    
    <content type="html"><![CDATA[<p>最近读了《纳瓦尔宝典》（The Almanack of Naval Ravikant），这本书整理了硅谷知名天使投资人纳瓦尔·拉维坎特（Naval Ravikant）多年来关于财富、幸福和人生的智慧。</p><p>纳瓦尔是 AngelList 的创始人，投资过 Twitter、Uber 等知名公司。他的很多观点在社交媒体上广泛传播，这本书则是他思想的系统整理。</p><p>整本书可以分成两条主线：<strong>如何不靠运气获得财富</strong> 和 <strong>如何获得幸福</strong>。</p><hr><h2 id="第一部分：财富篇"><a href="#第一部分：财富篇" class="headerlink" title="第一部分：财富篇"></a>第一部分：财富篇</h2><h3 id="核心思想"><a href="#核心思想" class="headerlink" title="核心思想"></a>核心思想</h3><blockquote><p><strong>致富不是靠运气，而是一项可以学习的技能。</strong></p></blockquote><p>纳瓦尔认为，财富创造是有规律可循的，关键在于理解以下几个概念：</p><h3 id="1-财富-vs-金钱-vs-地位"><a href="#1-财富-vs-金钱-vs-地位" class="headerlink" title="1. 财富 vs 金钱 vs 地位"></a>1. 财富 vs 金钱 vs 地位</h3><ul><li><strong>财富</strong>：在你睡觉时仍能为你赚钱的资产（公司股权、代码、媒体内容等）</li><li><strong>金钱</strong>：我们转移财富的方式</li><li><strong>地位</strong>：你在社会等级中的位置（这是零和游戏）</li></ul><p><strong>重点</strong>：追求财富，而不是地位。地位游戏是零和的，财富游戏可以正和。</p><h3 id="2-三种杠杆"><a href="#2-三种杠杆" class="headerlink" title="2. 三种杠杆"></a>2. 三种杠杆</h3><p>纳瓦尔提出了三种杠杆，它们能放大你的努力：</p><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>代码和媒体</td><td>软件、播客、视频、书籍</td></tr></tbody></table><p><strong>重点</strong>：前两种杠杆需要他人许可，第三种杠杆（代码和媒体）是普通人最容易获得的——你只需要一台电脑。</p><h3 id="3-专长（Specific-Knowledge）"><a href="#3-专长（Specific-Knowledge）" class="headerlink" title="3. 专长（Specific Knowledge）"></a>3. 专长（Specific Knowledge）</h3><blockquote><p><strong>专长是无法被培训的知识。如果社会可以培训你，它也可以培训别人来取代你。</strong></p></blockquote><p>专长的特点：</p><ul><li>通常是你天生擅长且热爱的事情</li><li>需要通过实践而非课堂学习获得</li><li>具有创造性，难以自动化或外包</li></ul><p><strong>如何找到专长</strong>：回顾你小时候喜欢做什么，什么让你觉得像玩但别人觉得是工作。</p><h3 id="4-责任（Accountability）"><a href="#4-责任（Accountability）" class="headerlink" title="4. 责任（Accountability）"></a>4. 责任（Accountability）</h3><p>以自己的名义承担风险，社会会以责任、股权和杠杆作为回报。</p><ul><li>把名字放在工作上，承担风险</li><li>建立个人品牌</li><li>成为某个领域的”第一人”</li></ul><h3 id="5-判断力（Judgment）"><a href="#5-判断力（Judgment）" class="headerlink" title="5. 判断力（Judgment）"></a>5. 判断力（Judgment）</h3><p>在杠杆时代，判断力比勤奋更重要。</p><blockquote><p><strong>一个正确的决策，胜过一千个勤奋的努力。</strong></p></blockquote><p>培养判断力的方法：</p><ul><li>在重要决策上花更多时间</li><li>向已经成功的人学习</li><li>避免情绪化决策</li><li>保持清晰的思考</li></ul><h3 id="6-财富公式"><a href="#6-财富公式" class="headerlink" title="6. 财富公式"></a>6. 财富公式</h3><blockquote><p><strong>财富 &#x3D; 专长 + 责任 + 杠杆 + 判断力</strong></p></blockquote><p>纳瓦尔的建议：</p><ol><li>找到你的专长</li><li>承担责任（以自己的名义做事）</li><li>应用杠杆（代码、媒体、资本、人力）</li><li>用判断力做出正确决策</li></ol><hr><h2 id="第二部分：幸福篇"><a href="#第二部分：幸福篇" class="headerlink" title="第二部分：幸福篇"></a>第二部分：幸福篇</h2><h3 id="核心思想-1"><a href="#核心思想-1" class="headerlink" title="核心思想"></a>核心思想</h3><blockquote><p><strong>幸福是一种技能，可以练习和习得。</strong></p></blockquote><p>纳瓦尔认为，幸福不是外在条件的结果，而是一种内在状态，可以通过训练获得。</p><h3 id="1-幸福的定义"><a href="#1-幸福的定义" class="headerlink" title="1. 幸福的定义"></a>1. 幸福的定义</h3><ul><li>幸福 &#x3D; 现实 - 期望</li><li>幸福是默认状态，焦虑和欲望是后天习得的</li><li>幸福不是兴奋（兴奋必然伴随低谷），而是平静</li></ul><h3 id="2-欲望是痛苦的根源"><a href="#2-欲望是痛苦的根源" class="headerlink" title="2. 欲望是痛苦的根源"></a>2. 欲望是痛苦的根源</h3><blockquote><p><strong>欲望是你与自己签订的合同：在得到你想要的东西之前，你一直是不快乐的。</strong></p></blockquote><p>减少欲望的方法：</p><ul><li>一次只保留一个主要欲望</li><li>意识到欲望是选择，不是必须</li><li>练习满足于当下</li></ul><h3 id="3-习惯与练习"><a href="#3-习惯与练习" class="headerlink" title="3. 习惯与练习"></a>3. 习惯与练习</h3><p>幸福需要像健身一样练习：</p><ul><li><strong>冥想</strong>：每天花时间观察自己的思绪</li><li><strong>阅读</strong>：读经典，而非新闻</li><li><strong>锻炼</strong>：身体健康影响心理健康</li><li><strong>断舍离</strong>：减少物质和关系的负担</li></ul><h3 id="4-当下"><a href="#4-当下" class="headerlink" title="4. 当下"></a>4. 当下</h3><blockquote><p><strong>所有真正的智慧都来自当下。</strong></p></blockquote><ul><li>焦虑来自对未来的担忧</li><li>抑郁来自对过去的执念</li><li>平静只存在于当下</li></ul><p>练习方法：</p><ul><li>深呼吸，感受此刻</li><li>专注于正在做的事情</li><li>减少多任务处理</li></ul><hr><h2 id="第三部分：决策与人生"><a href="#第三部分：决策与人生" class="headerlink" title="第三部分：决策与人生"></a>第三部分：决策与人生</h2><h3 id="1-决策原则"><a href="#1-决策原则" class="headerlink" title="1. 决策原则"></a>1. 决策原则</h3><ul><li><strong>如果答案是不确定的，那答案就是否定的</strong></li><li><strong>如果两个选择同样困难，选择短期更痛苦的那个</strong>（长期通常更好）</li><li><strong>和比你聪明的人在一起</strong>，他们会提升你</li></ul><h3 id="2-时间管理"><a href="#2-时间管理" class="headerlink" title="2. 时间管理"></a>2. 时间管理</h3><blockquote><p><strong>你的时间价值多少？按这个标准来生活。</strong></p></blockquote><ul><li>设定一个高时薪（比如 $500&#x2F;小时）</li><li>任何低于这个价值的事情，尽量外包或不做</li><li>保护你的时间，它是你最重要的资产</li></ul><h3 id="3-健康优先"><a href="#3-健康优先" class="headerlink" title="3. 健康优先"></a>3. 健康优先</h3><p>健康 &gt; 财富 &gt; 幸福</p><p>没有健康，财富和幸福都没有意义。纳瓦尔的建议：</p><ul><li>规律锻炼</li><li>健康饮食</li><li>充足睡眠</li><li>管理压力</li></ul><h3 id="4-阅读建议"><a href="#4-阅读建议" class="headerlink" title="4. 阅读建议"></a>4. 阅读建议</h3><ul><li>读你喜欢的，直到你喜欢上阅读</li><li>不要害怕放弃一本不好的书</li><li>重读经典，而非追逐新书</li><li>基础学科：数学、逻辑、进化论、心理学等</li></ul><hr><h2 id="金句摘录"><a href="#金句摘录" class="headerlink" title="金句摘录"></a>金句摘录</h2><blockquote><p>“追求财富，而不是地位。”</p></blockquote><blockquote><p>“专长是无法被培训的知识。”</p></blockquote><blockquote><p>“给自己设定一个高时薪，然后按这个标准生活。”</p></blockquote><blockquote><p>“如果答案是不确定的，那答案就是否定的。”</p></blockquote><blockquote><p>“幸福是一种技能，可以练习。”</p></blockquote><blockquote><p>“欲望是你与自己签订的合同。”</p></blockquote><blockquote><p>“所有真正的智慧都来自当下。”</p></blockquote><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>《纳瓦尔宝典》不是一本传统的成功学书籍，它更像是一本关于如何过好这一生的指南。</p><p><strong>关于财富</strong>：找到你的专长，承担责任，应用杠杆，用判断力做决策。</p><p><strong>关于幸福</strong>：减少欲望，练习冥想，活在当下，保持健康。</p><p><strong>关于人生</strong>：和聪明人在一起，保护你的时间，持续学习，做长期正确的事。</p><p>这本书值得反复阅读，每次读都会有新的收获。推荐给每一个想要更好理解财富和幸福的人。</p><hr><p><em>2026 年 3 月 9 日 记</em></p>]]></content>
    
  </entry>
  
  <entry>
    <title>铃木俊隆</title>
    <link href="https://alan.org.cn/posts/%E9%93%83%E6%9C%A8%E4%BF%8A%E9%9A%86.html"/>
    <id>https://alan.org.cn/posts/%E9%93%83%E6%9C%A8%E4%BF%8A%E9%9A%86.html</id>
    <published>2026-03-01T10:00:14.000Z</published>
    <updated>2026-03-01T10:00:14.000Z</updated>
    
    
    
    
      
      
    <summary type="html">&lt;p&gt;禅并不是什么令人兴奋的东西，&lt;/p&gt;
&lt;p&gt;而只是对日常生活的专注。&lt;/p&gt;
&lt;p&gt;“Zen is not some kind of excitement,&lt;/p&gt;
&lt;p&gt;but concentration on &lt;/p&gt;
&lt;p&gt;our usual everyday rou</summary>
      
    
    
    <content type="html"><![CDATA[<p>禅并不是什么令人兴奋的东西，</p><p>而只是对日常生活的专注。</p><p>“Zen is not some kind of excitement,</p><p>but concentration on </p><p>our usual everyday routine.”</p><p>——（Shunryu Suzuki）</p><p>铃木俊隆禅师，是将佛教与禅修带入西方世界的先驱之一。藏传佛教大师邱阳·创巴仁波切尊敬地称铃木俊隆为自己“精神上的父亲”。</p><p>他亲身经历两次世界大战等世间诸种悲惨情景，亦经历过二度丧妻之痛——第一任妻子，因肺结核而必须离开他（在20世纪40年代，那等同于绝症）；第二任妻子，惨遭疯狂寺僧的砍杀而身亡（他的小女儿，因母亲的意外而精神失常）。</p><p>对他而言，<strong>“</strong>万事总是变迁流转，所以没有你可以拥有的事物。”人生寿命的长短，或如逾千年的日面佛，抑或一日夜的月面佛，那都已经不是问题。他只是踏实地活在当下、此处，做他自己的主人，与万物为一。</p><p><img src="http://bucket.alan.org.cn/blog/2026/03/01/18-01-11-931900d674a6dee3f1b77b48a3aba14b-58a6f9.webp" alt="640.webp"></p><p>铃木俊隆禅师</p><p>《禅者的初心》，是铃木俊隆的美国弟子整理他坐禅时的演讲辑录。这本书告诉人们：<strong>“</strong>禅修的心，应该始终是一颗初心，必须归复自己无边的初学者的心，不受各种习性的羁绊，只有这样，才能忠于自己，同情众生，并且切实修行。”这本书正是乔布斯学禅的入门书籍，从学禅中得来的生活与工作的启示，影响了乔布斯一生。</p><p>今天和大家再次分享铃木俊隆禅师自己的12条生活禅修准则，愿将静心带入带入生活中的每个当下。</p><p><strong>12</strong>条生活禅修准则</p><p>铃木俊隆（日）</p><p><img src="http://bucket.alan.org.cn/blog/2026/03/01/18-01-30-ee6c3c9c1409dd94932612b85641570e-82c34a.webp" alt="640.webp"></p><p><strong>01.</strong> 一次只专注于一件事情</p><p><strong>Do one thing at a time</strong></p><p>这一规则（还有其他一些后面的规则）为长期阅读禅僧习惯的读者所熟知。这也是我哲学的一部分，也是一个禅僧生活的一部分：单任务而不一次进行多项任务。当你在倒水的时候就只倒水就可以了。而当你吃饭的时候就只吃饭好了。当你洗澡的时候，就专注于洗澡。不要试着停下一些工作去吃饭或洗澡。</p><p>禅僧格言：当你行走的时候，专心行走。当你吃饭的时候，专心吃饭。</p><p><strong>02.</strong> 慢慢地并谨慎地做事</p><p><strong>Do it slowly and deliberately</strong> </p><p>你可以一次完成一个任务，但也可能会急匆匆的完成它。相反，你不能急，要慢慢来。谨慎地注意你的行动，不要太急忙和随意了。这需要练习，但他有助于你专注于你的任务。</p><p><img src="http://bucket.alan.org.cn/blog/2026/03/01/18-01-58-63cd3a8fcd56a4faead5a72b67a7450d-3ad5b9.webp" alt="640.webp"></p><p><strong>03.</strong> 彻底地完成它</p><p><strong>Do it completely</strong></p><p>全心全意关注你的任务。不完成目前的任务不要进行下一个。如果因为某些原因你不得不去做别的事，至少尽量把没有完成的放好并整理一下自己。如果你在做三明治，在吃掉它之前你要先把用来做三明治的东西清理好，清扫一下柜台，洗干净做三明治碟子。这样你就完成了做三明治的工作，你就可以完成专注于下一个任务了。</p><p><strong>04.</strong> 少做</p><p><strong>Do less</strong></p><p>一个禅僧不会过一种懒惰的生活：他很早就醒来了而且每天都塞满了工作。然而，他不会有一个无休止的任务清单——今天他要完成特定的事情，不会太多。如果你少做，你就能够更专注地慢慢地、更彻底地做这些事情，如果你把一天用不同的任务填满，你就会急忙地从一件事情到另一件事情，而不会停下来去想你究竟做了些什么。</p><p><strong>05.</strong> 在事情之间留些空间</p><p><strong>Put space between things</strong></p><p>跟“少做”原则相关，但这是管理你的日程表保证你总是有时间来完成每项任务的一种方法。不要把事情安排的过于紧密——相反在事情之间留些空间出来。这会让你的日程表更轻松，留出空间也可以防止万一一项任务花费的时间超过了你的计划。</p><p><img src="http://bucket.alan.org.cn/blog/2026/03/01/18-02-23-4783c56f3cd1d4f1efede707ed4eb9ae-367400.webp" alt="640.webp"></p><p>**06.**培养一种仪式</p><p><strong>Develop rituals</strong></p><p>禅僧们对于他们做的很多事情都有仪式，从吃饭到清洁到冥想。仪式给人一种重要的感觉<strong>——</strong>如果事情重要到需要一种仪式，就需要给予你完成的注意，并且慢慢地正确地来完成。你不必学习禅僧的仪式——你可以建立你自己的仪式，为食物的准备、为吃饭、为清洁、为你开始做的事情、为你醒来后做的事情和你上床之前做的事情、为你在锻炼之前做的事情。是的，为任何你想做的事情建立一种仪式。</p><p><strong>07.</strong> 为特定的事情分配好时间</p><p><strong>Designate time for certain things</strong></p><p>在一天中的特定时间内禅僧是用来完成特定的活动的。洗澡的时间、工作的时间、清洁的时间、吃饭的时间。这可以保证这些事情有规律地完成。你可以为你自己的活动分配时间，不管是工作或是清洁或是锻炼或是宁静的沉思。如果它很重要需要有规律地来完成，那就考虑为它分配好时间吧。</p><p><img src="http://bucket.alan.org.cn/blog/2026/03/01/18-02-33-92c9765f104145fc51129b8297e7e114-ccdfda.webp" alt="640.webp"></p><p><strong>08.</strong> 花些时间在打坐上</p><p><strong>Devote time to sitting</strong></p><p>在禅僧的一生中，禅宗打坐是他每天最重要的部分之一。每天，有专门分配用来打坐的时间。这种沉思确实是学习活在当下的练习。你可以花些时间来静坐冥想，或做我所做的：我以跑步做为活在当下的一种锻炼。同样地，你可以采用任何活动，只要你经常地去做并练习活在当下。</p><p>**09.**微笑和服务他人</p><p><strong>Smile and serve others</strong></p><p>禅僧把他们每天中的部分时间用来服务他人，不管是寺院里的其他禅僧还是外面世界里的人们。这教会了他们谦逊，确保他们的生活不单单是自私的，也是为了奉献他人的。</p><p>如果你是一个家长，可能你已经至少在家庭中为服务别人花了些时间，而不是家长的人们也许也做了这些。类似地，微笑并与人为善是改善你周围人生活的一种很好的方法。也可以考虑志愿参加慈善工作。</p><p><img src="http://bucket.alan.org.cn/blog/2026/03/01/18-02-42-13654a83668d62b604eddf92e6a58745-aa67c9.webp" alt="640.webp"></p><p><strong>10.</strong> 把清洁和烹饪变成冥想</p><p><strong>Make cleaning and cooking</strong></p><p><strong>become meditation</strong></p><p>除了上面提到的打坐以外，清洁和烹饪也是禅僧每天最荣耀的部分。他们都是练习专注力的好方式，可以是每天进行的很好的仪式。如果烹饪和清洁对你来说似乎烦琐的杂务，试着用冥想的方式来完成它们。全心全意做这些工作，集中注意、慢慢地、彻底地做这些事情。这会改变你的一天（同时也给你一间干净的房间）。</p><p><strong>11.</strong> 思考什么是必须的</p><p><strong>Think about what is necessary</strong></p><p>在禅僧的生活中基本上都是必须的要做的。他没有充满鞋子的衣柜或最新的时尚衣服。他没有冰箱和橱柜来存放垃圾食物。他没有最新的小玩意、汽车、电视或苹果音乐播放器。他有保暖的衣物、有避雨的房屋、必须的器具、不可少的工具和充饥的食物（他们吃的很简单，素餐通常有大米、味噌汤、蔬菜和泡菜组成）。</p><p>现在，我的意思不是说你应该完成像禅僧一样生活——我也没有这样做。但这确实提醒我们在我们的生活中有很多事情是没有必要的。思考我们真正需要的是什么，考虑拥那些不必要的东西是否对我们真的重要，这对我们是有用的。</p><p><img src="http://bucket.alan.org.cn/blog/2026/03/01/18-02-49-95cd6b41fc48f46e9e78587a4d6c4970-fd02ba.webp" alt="640.webp"></p><p><strong>12.</strong> 简单生活</p><p><strong>Live simply</strong></p><p>每11条原则——如果某些事情不是必须的——的推论，那么没有他们你也可以生活。所以简单生活就是尽可能地处理掉那些没有必要的和不重要的东西，为重要的东西留存空间。而什么是重要的是因人而异的。</p><p>对我来说，家庭、写作、跑步和阅读是重要的。对其他人来说，也许护理和志愿工作、做礼拜和收集连环画是重要的。没有什么定律来规定对你来说什么应该重要的——但你应该考虑什么是你生命中最为重要的，通过去除不是那么重要的事情为你生命中重要的事情留出空间来</p>]]></content>
    
  </entry>
  
  <entry>
    <title>小狗钱钱读书笔记</title>
    <link href="https://alan.org.cn/posts/%E5%B0%8F%E7%8B%97%E9%92%B1%E9%92%B1%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/%E5%B0%8F%E7%8B%97%E9%92%B1%E9%92%B1%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.html</id>
    <published>2026-03-01T09:45:56.000Z</published>
    <updated>2026-03-01T09:45:56.000Z</updated>
    
    
    
    
      
      
    <summary type="html">&lt;p&gt;今天想分享一本对我影响很大的书——&lt;a href=&quot;https://zhida.zhihu.com/search?content_id=270591844&amp;content_type=Article&amp;match_order=1&amp;q=%E3%80%8A%E5%B0%8F%E7%</summary>
      
    
    
    <content type="html"><![CDATA[<p>今天想分享一本对我影响很大的书——<a href="https://zhida.zhihu.com/search?content_id=270591844&content_type=Article&match_order=1&q=%E3%80%8A%E5%B0%8F%E7%8B%97%E9%92%B1%E9%92%B1%E3%80%8B&zhida_source=entity">《小狗钱钱》</a>。</p><p>它讲了一个很简单的故事：小女孩吉娅在一只叫“钱钱”的小狗指导下，学会了怎么和钱打交道，最后靠自己的努力实现了当初写下的梦想。</p><p>别被“童书”的外表骗了。这本书里的方法，我一个成年人读下来，照样被戳中。</p><p><strong>整本书可以沿着三条线索往下走：为什么需要钱？怎么赚钱？怎么让钱生钱？</strong></p><h2 id="1、为什么需要钱？先搞清楚你的“为什么”"><a href="#1、为什么需要钱？先搞清楚你的“为什么”" class="headerlink" title="1、为什么需要钱？先搞清楚你的“为什么”"></a>1、为什么需要钱？先搞清楚你的“为什么”</h2><p>书里一开始就抛出一个问题：你为什么想要变有钱？</p><p>很多人第一反应或许是：这还用问？因为钱有用啊。</p><p>但钱钱让吉娅做的第一件事，是写下来——</p><p><strong>①先想出10个你想变富有的原因。</strong></p><p><strong>②然后圈出其中最重要的3个。</strong>这能够帮助你确切地知道自己心里渴望的是什么。</p><p><strong>③每天看这3个愿望。</strong>它会不断地提醒你自己想得到什么，那么你就会密切关注一切可以帮助你实现这些愿望的机遇。</p><p><strong>④为这3个梦想分别建立“<a href="https://zhida.zhihu.com/search?content_id=270591844&content_type=Article&match_order=1&q=%E6%A2%A6%E6%83%B3%E5%82%A8%E8%93%84%E7%BD%90&zhida_source=entity">梦想储蓄罐</a>”。</strong></p><p>这个储蓄罐里要放两样东西：一样是钱（为这个目标攒下的每一分）；一样是画面（相关的照片、想象完成后的状态）。</p><p>为什么要放照片？因为大脑更容易相信“看得见”的东西。你每天看着那所大学的照片、那台相机的图片，潜意识就会默认：这是真的，我在去那里的路上。</p><p>目标很重要，因为只有知道自己为什么而做，才不至于在路上迷路。</p><p><img src="https://pic4.zhimg.com/v2-53a52dc87972be9a256c09d77f00662d_1440w.jpg" alt="img"></p><h2 id="2、怎么赚钱？0到1是最难的一步"><a href="#2、怎么赚钱？0到1是最难的一步" class="headerlink" title="2、怎么赚钱？0到1是最难的一步"></a>2、怎么赚钱？0到1是最难的一步</h2><p>赚第一桶金是最难的，因为你要完成从0到1的跨越。</p><p>书里给了两个建议，简单但直击本质：</p><p><strong>①为别人解决一个难题</strong></p><p>钱是你提供价值的回报。你能解决多大的问题，就能赚到多少钱。</p><p><strong>②把精力集中在你会的、你有的、你喜欢的上面</strong></p><p>不是追风口，是问自己：我喜欢做什么？我擅长做什么？然后，再考虑怎么用它赚钱。</p><p>这个思路让我开始重新审视自己——原来那些“只是喜欢”的事，也可以变成价值。</p><h2 id="3、怎么让钱生钱？学会养一只“鹅”"><a href="#3、怎么让钱生钱？学会养一只“鹅”" class="headerlink" title="3、怎么让钱生钱？学会养一只“鹅”"></a>3、怎么让钱生钱？学会养一只“鹅”</h2><p>书里有个特别经典的比喻：养一只会下金蛋的鹅。</p><p>“鹅”就是你的本金，“金蛋”就是它产生的利息。很多人想要金蛋，却把鹅杀了。正确的做法是：</p><p>把收入的50%变成“鹅”（储蓄），40%放进梦想储蓄罐，10%用来日常开销。</p><p>至于怎么让鹅长大，书里推荐了一个普通人也能操作的方法：<strong>基金定投</strong>。不是一夜暴富，而是用时间让钱慢慢长大。</p><h2 id="4、害怕的时候，写“成功日记”"><a href="#4、害怕的时候，写“成功日记”" class="headerlink" title="4、害怕的时候，写“成功日记”"></a>4、害怕的时候，写“<a href="https://zhida.zhihu.com/search?content_id=270591844&content_type=Article&match_order=1&q=%E6%88%90%E5%8A%9F%E6%97%A5%E8%AE%B0&zhida_source=entity">成功日记</a>”</h2><p>在实现目标的路上，最大的敌人往往不是别人，而是自己脑子里那个声音：</p><p>“你不行吧”“万一失败了呢”“别人会怎么看你”。</p><p>作者分享了一个对抗恐惧的方法：写成功日记。</p><p>准备一个本子，每天写至少5条个人成果，任何小事都可以：</p><blockquote><p>今天早起了10分钟<br>没点外卖，自己做了一顿饭<br>主动跟同事打了个招呼</p></blockquote><p>这不是自恋，是给自己建一座自信银行。当恐惧来袭的时候，翻开本子看看——原来我已经做成了这么多事。</p><p>那些怀疑你的声音，不只是来自别人，也来自自己。成功日记，就是让你能听到自己的声音。</p><h2 id="5、72小时法则：想到就去做"><a href="#5、72小时法则：想到就去做" class="headerlink" title="5、72小时法则：想到就去做"></a>5、<a href="https://zhida.zhihu.com/search?content_id=270591844&content_type=Article&match_order=1&q=72%E5%B0%8F%E6%97%B6%E6%B3%95%E5%88%99&zhida_source=entity">72小时法则</a>：想到就去做</h2><p>书里有个“72小时法则”：即如果你决定做一件事，72小时内没行动，那这件事大概率就永远不会做了。</p><p>不是因为你懒，是因为热情会消退，借口会上线。</p><p>所以想到什么，就马上埋一个“行动钩子”——写在待办清单最上面、把需要的资料先打开、哪怕只是打个头也行。</p><p>成功日记和梦想储蓄罐是一直需要坚持的举动，这意味着不管是顺境还是逆境，你都需要从一而终的执行。在成功的道路上，行动力和坚持缺一不可。</p><h2 id="6、负债怎么办？四个忠告别忘掉"><a href="#6、负债怎么办？四个忠告别忘掉" class="headerlink" title="6、负债怎么办？四个忠告别忘掉"></a>6、负债怎么办？四个忠告别忘掉</h2><p>如果你和我一样，读这本书的时候已经深陷债务危机，也别慌。书里给的四个建议，我亲测有效：</p><blockquote><p>毁掉所有信用卡——尽量不给自己超前消费的机会<br>按最低额度还分期——别把现金流勒太死，留点余地<br>生活费之外的结余，一半存起来，一半还贷——最好别再申请消费贷<br>每次花钱前问一句：这真的有必要吗？</p></blockquote><p><img src="https://picx.zhimg.com/v2-6eebf71695b77904b2b007f8e58eb547_1440w.jpg" alt="img"></p><p>最后，我想说，《小狗钱钱》是一本可操作性很强的书。它把复杂的金钱问题，简化成了几个核心本质：</p><blockquote><p><strong>先搞清楚“为什么”</strong><br><strong>再用擅长的事去赚钱</strong><br><strong>然后学会让钱生钱</strong><br><strong>过程中用成功日记对抗恐惧</strong><br><strong>用72小时法则推动行动</strong><br><strong>用四个忠告管住负债</strong></p></blockquote><p>很多人读完之后觉得“懂了”，然后什么都没做。而有些人，读完之后真的去做了，真心的希望你是后者。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>Java多线程笔记</title>
    <link href="https://alan.org.cn/posts/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AC%94%E8%AE%B0.html</id>
    <published>2026-01-18T09:40:32.000Z</published>
    <updated>2026-01-18T09:40:32.000Z</updated>
    
    
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;线程池&quot;&gt;&lt;a href=&quot;#线程池&quot; class=&quot;headerlink&quot; title=&quot;线程池&quot;&gt;&lt;/a&gt;线程池&lt;/h2&gt;&lt;p&gt;核心原理：提交任务时，池子会创建心的线程对象，任务执行完毕，线程归还给池子，下回提交任务，不需要创建线程，直接复用&lt;/p&gt;
&lt;p&gt;代</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h2><p>核心原理：提交任务时，池子会创建心的线程对象，任务执行完毕，线程归还给池子，下回提交任务，不需要创建线程，直接复用</p><p>代码实现步骤：</p><ol><li>创建线程池</li><li>提交任务</li><li>所有任务执行完毕关闭线程池 （一般不会关闭）</li></ol><p>Executors：线程池的工具类通过调用方法返回不同类型的线程池对象</p><table><thead><tr><th>方法名</th><th>说明</th></tr></thead><tbody><tr><td>public static ExecutorService newCachedThreadPool()</td><td>创建一个没有上线的线程池</td></tr><tr><td>public static ExecutorService newFixedThreadPool(int nThreads)</td><td>创建一有上线的线程池</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ExecutorDemo</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        <span class="hljs-type">ExecutorService</span> <span class="hljs-variable">pool</span> <span class="hljs-operator">=</span> Executors.newCachedThreadPool();<br>        pool.submit(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Runnable</span>() &#123;<br>            <span class="hljs-meta">@Override</span><br>            <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>                System.out.println(<span class="hljs-string">&quot;aaa&quot;</span>);<br>            &#125;<br>        &#125;);<br>        Thread.sleep(<span class="hljs-number">1000</span>);<br>        pool.submit(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Runnable</span>() &#123;<br>            <span class="hljs-meta">@Override</span><br>            <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>                System.out.println(<span class="hljs-string">&quot;aaa&quot;</span>);<br>            &#125;<br>        &#125;);<br>        pool.shutdown();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
  </entry>
  
  <entry>
    <title>grpc笔记</title>
    <link href="https://alan.org.cn/posts/grpc%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/grpc%E7%AC%94%E8%AE%B0.html</id>
    <published>2026-01-08T15:05:53.000Z</published>
    <updated>2026-01-08T15:05:53.000Z</updated>
    
    
    <category term="微服务" scheme="https://alan.org.cn/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
    
    <category term="微服务" scheme="https://alan.org.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;如何编写proto&quot;&gt;&lt;a href=&quot;#如何编写proto&quot; class=&quot;headerlink&quot; title=&quot;如何编写proto&quot;&gt;&lt;/a&gt;如何编写proto&lt;/h2&gt;&lt;figure class=&quot;highlight protobuf&quot;&gt;&lt;table&gt;&lt;tr</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="如何编写proto"><a href="#如何编写proto" class="headerlink" title="如何编写proto"></a>如何编写proto</h2><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs protobuf">syntax = <span class="hljs-string">&quot;proto3&quot;</span>;<br><br><span class="hljs-keyword">option</span> java_multiple_files = <span class="hljs-literal">true</span>;<br><span class="hljs-comment">// 生成位置</span><br><span class="hljs-keyword">option</span> java_package = <span class="hljs-string">&quot;com.lizq.jrpc.api&quot;</span>;<br><span class="hljs-keyword">option</span> java_outer_classname = <span class="hljs-string">&quot;UserService&quot;</span>;<br><br><span class="hljs-keyword">package</span> user;<br><br><span class="hljs-keyword">service </span><span class="hljs-title class_">User</span> &#123;<br>    <span class="hljs-function"><span class="hljs-keyword">rpc</span> SayHello (UserRequest) <span class="hljs-keyword">returns</span> (UserResponse) </span>&#123;&#125;<br>&#125;<br><br><span class="hljs-keyword">message </span><span class="hljs-title class_">UserRequest</span> &#123;<br>    <span class="hljs-type">string</span> name = <span class="hljs-number">1</span>;<br>    <span class="hljs-type">int32</span> age = <span class="hljs-number">2</span>;<br>    <span class="hljs-type">string</span> addr = <span class="hljs-number">3</span>;<br>&#125;<br><br><span class="hljs-keyword">message </span><span class="hljs-title class_">UserResponse</span> &#123;<br>    <span class="hljs-type">string</span> name = <span class="hljs-number">1</span>;<br>    <span class="hljs-type">int32</span> age = <span class="hljs-number">2</span>;<br>    <span class="hljs-type">string</span> addr = <span class="hljs-number">3</span>;<br>    OtherMsg otherMsg = <span class="hljs-number">4</span>;<br>    map&lt;<span class="hljs-type">string</span>, <span class="hljs-type">string</span>&gt; otherMap = <span class="hljs-number">5</span>;<br><br>    <span class="hljs-comment">// 嵌套对象</span><br>    <span class="hljs-keyword">message </span><span class="hljs-title class_">OtherMsg</span> &#123;<br>        <span class="hljs-type">string</span> ext1 = <span class="hljs-number">1</span>;<br>        <span class="hljs-type">string</span> ext2 = <span class="hljs-number">2</span>;<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><h2 id="如何编写proto-1"><a href="#如何编写proto-1" class="headerlink" title="如何编写proto"></a>如何编写proto</h2><ul><li>**syntax &#x3D; “proto3”;**：指定使用的protobuf版本；</li><li>**option java_multiple_files &#x3D; true;**：如果为 false，则只会.java为此文件生成一个.proto文件，以及所有 Java 类&#x2F;枚举&#x2F;等。为顶级消息、服务和枚举生成的将嵌套在外部类中。如果为 true，.java将为每个 Java 类&#x2F;枚举&#x2F;等生成单独的文件。为顶级消息、服务和枚举生成，并且为此.proto文件生成的包装 Java 类将不包含任何嵌套类&#x2F;枚举&#x2F;等。 如果不生成 Java 代码，则此选项无效。</li><li>**package user;**：定义本服务的包名，避免不同服务相同消息类型产生冲突；</li><li>**option java_package &#x3D; “com.lizq.jrpc.api”;**：生成java文件包名；</li><li>**option java_outer_classname &#x3D; “UserService”;**：生成java文件类名称。如果文件中没有明确 java_outer_classname指定，.proto则将通过将.proto文件名转换为驼峰式来构造类名（因此 foo_bar.proto变为FooBar.java）</li><li><strong>message UserResponse</strong>：定义服务的接口名称；</li><li>**rpc SayHello (UserRequest) returns (UserResponse) {}**：远程调用方法名，参数及响应类型；</li><li><strong>message XXXXX{}：</strong>定义数据类型；</li></ul><h2 id="数据类型及对应关系"><a href="#数据类型及对应关系" class="headerlink" title="数据类型及对应关系"></a>数据类型及对应关系</h2><table><thead><tr><th>.proto类型</th><th>Notes</th><th>C++ Type</th><th>Java&#x2F;Kotlin</th><th>Python</th></tr></thead><tbody><tr><td>double</td><td></td><td>double</td><td>double</td><td>float</td></tr><tr><td>float</td><td></td><td>float</td><td>float</td><td>float</td></tr><tr><td>int32</td><td>使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值，请改用 sint32。</td><td>int32</td><td>int</td><td>int</td></tr><tr><td>int64</td><td>使用可变长度编码。对负数进行编码效率低下——如果您的字段可能有负值，请改用 sint64。</td><td>int64</td><td>long</td><td>int&#x2F;long</td></tr><tr><td>uint32</td><td>使用可变长度编码。</td><td>uint32</td><td>int</td><td>int&#x2F;long</td></tr><tr><td>uint64</td><td>使用可变长度编码。</td><td>uint64</td><td>long</td><td>int&#x2F;long</td></tr><tr><td>sint32</td><td>使用可变长度编码。带符号的 int 值。这些比常规 int32 更有效地编码负数。</td><td>int32</td><td>int</td><td>int</td></tr><tr><td>sint64</td><td>使用可变长度编码。带符号的 int 值。这些比常规 int64 更有效地编码负数。</td><td>int64</td><td>long</td><td>int&#x2F;long</td></tr><tr><td>fixed32</td><td>总是四个字节。如果值通常大于 228，则比 uint32 更有效</td><td>uint32</td><td>int</td><td>int&#x2F;long</td></tr><tr><td>fixed64</td><td>总是八个字节。如果值通常大于 256，则比 uint64 更有效。</td><td>uint64</td><td>long</td><td>int&#x2F;long</td></tr><tr><td>sfixed32</td><td>总是四个字节。</td><td>int32</td><td>int</td><td>int</td></tr><tr><td>sfixed64</td><td>总是八个字节。</td><td>int64</td><td>long</td><td>int&#x2F;long</td></tr><tr><td>bool</td><td></td><td>bool</td><td>boolean</td><td>bool</td></tr><tr><td>string</td><td>字符串必须始终包含 UTF-8 编码或 7 位 ASCII 文本，并且不能超过 232。</td><td>string</td><td>String</td><td>str&#x2F;unicode</td></tr><tr><td>bytes</td><td>可能包含不超过 2 32的任意字节序列。</td><td>string</td><td>ByteString</td><td>str (Python 2)、bytes (Python 3)</td></tr></tbody></table><h2 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">enum Sex &#123;<br>  NONE = 0;<br>  MAN = 1;<br>  WOMAN = 2;<br>&#125;<br><br>message UserRequest &#123;<br>  string name = 1;<br>  int32 age = 2;<br>  string addr = 3;<br>  Sex sex = 4;<br>&#125;<br>**注意：**第一个枚举的值必须为0，因为0 是默认值，0 必须是第一个，保持和proto2 兼容<br></code></pre></td></tr></table></figure><h2 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h2><p>使用 repeated 关键字来定义数组。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">message UserRequest &#123;<br>  string name = 1;<br>  int32 age = 2;<br>  string addr = 3;<br>  Sex sex = 4;<br>  // 定义一个数组<br>  repeated string cellphones = 5;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="map类型"><a href="#map类型" class="headerlink" title="map类型"></a>map类型</h2><p>在开发的过程中经常需要使用关联字段，很自然的想到使用map，protobuf也提供了map的类型。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">message UserResponse &#123;<br>  string name = 1;<br>  map&lt;string, string&gt; otherMap = 2;<br>&#125;<br></code></pre></td></tr></table></figure><p>注意： map 字段前面不能是repeated</p><h2 id="嵌套对象"><a href="#嵌套对象" class="headerlink" title="嵌套对象"></a>嵌套对象</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java">message UserResponse &#123;<br>  <span class="hljs-type">string</span> <span class="hljs-variable">name</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br>  <span class="hljs-type">int32</span> <span class="hljs-variable">age</span> <span class="hljs-operator">=</span> <span class="hljs-number">2</span>;<br>  <span class="hljs-type">string</span> <span class="hljs-variable">addr</span> <span class="hljs-operator">=</span> <span class="hljs-number">3</span>;<br>  <span class="hljs-type">OtherMsg</span> <span class="hljs-variable">otherMsg</span> <span class="hljs-operator">=</span> <span class="hljs-number">4</span>;<br>  map&lt;string, string&gt; otherMap = <span class="hljs-number">5</span>;<br><br>  <span class="hljs-comment">// 嵌套对象</span><br>  message OtherMsg &#123;<br>    <span class="hljs-type">string</span> <span class="hljs-variable">ext1</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br>    <span class="hljs-type">string</span> <span class="hljs-variable">ext2</span> <span class="hljs-operator">=</span> <span class="hljs-number">2</span>;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="SpringBoot使用grpc"><a href="#SpringBoot使用grpc" class="headerlink" title="SpringBoot使用grpc"></a>SpringBoot使用grpc</h2><h3 id="Service端"><a href="#Service端" class="headerlink" title="Service端"></a>Service端</h3><h4 id="POM"><a href="#POM" class="headerlink" title="POM"></a>POM</h4><p>注意：</p><ol><li><p>grpc-spring-boot-<a href="https://so.csdn.net/so/search?q=starter&spm=1001.2101.3001.7020">starter</a></p></li><li><p>os-maven-plugin</p></li><li><p>protobuf-maven-plugin</p></li></ol><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.5.6<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span> <span class="hljs-comment">&lt;!-- lookup parent from repository --&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.leadtrans<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>report<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.6.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>report<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>Demo project for Spring Boot<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">spring-cloud.version</span>&gt;</span>2020.0.4<span class="hljs-tag">&lt;/<span class="hljs-name">spring-cloud.version</span>&gt;</span><br>        <span class="hljs-comment">&lt;!--    GRPC    --&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">grpc-spring-boot-starter.version</span>&gt;</span>2.3.2<span class="hljs-tag">&lt;/<span class="hljs-name">grpc-spring-boot-starter.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">os-maven-plugin.version</span>&gt;</span>1.6.0<span class="hljs-tag">&lt;/<span class="hljs-name">os-maven-plugin.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">protobuf-maven-plugin.version</span>&gt;</span>0.5.1<span class="hljs-tag">&lt;/<span class="hljs-name">protobuf-maven-plugin.version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br> <br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br> <br>        <span class="hljs-comment">&lt;!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>fastjson<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.2.78<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br> <br>        <span class="hljs-comment">&lt;!--    GRPC    --&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.lognet<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>grpc-spring-boot-starter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;grpc-spring-boot-starter.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br> <br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br> <br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;spring-cloud.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">extensions</span>&gt;</span><br>            <span class="hljs-comment">&lt;!-- os-maven-plugin --&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">extension</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>kr.motd.maven<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>os-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;os-maven-plugin.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">extension</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">extensions</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">executions</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">execution</span>&gt;</span><br>                        <span class="hljs-tag">&lt;<span class="hljs-name">goals</span>&gt;</span><br>                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>repackage<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span><br>                        <span class="hljs-tag">&lt;/<span class="hljs-name">goals</span>&gt;</span><br>                    <span class="hljs-tag">&lt;/<span class="hljs-name">execution</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">executions</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>maven-surefire-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">skip</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">skip</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>            <span class="hljs-comment">&lt;!-- protobuf-maven-plugin --&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.xolstice.maven.plugins<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>protobuf-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;protobuf-maven-plugin.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">protocArtifact</span>&gt;</span>com.google.protobuf:protoc:3.5.1-1:exe:$&#123;os.detected.classifier&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">protocArtifact</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">pluginId</span>&gt;</span>grpc-java<span class="hljs-tag">&lt;/<span class="hljs-name">pluginId</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">pluginArtifact</span>&gt;</span>io.grpc:protoc-gen-grpc-java:1.11.0:exe:$&#123;os.detected.classifier&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">pluginArtifact</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">outputDirectory</span>&gt;</span>$&#123;project.build.sourceDirectory&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">outputDirectory</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">clearOutputDirectory</span>&gt;</span>false<span class="hljs-tag">&lt;/<span class="hljs-name">clearOutputDirectory</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">executions</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">execution</span>&gt;</span><br>                        <span class="hljs-tag">&lt;<span class="hljs-name">goals</span>&gt;</span><br>                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>compile<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span><br>                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>compile-custom<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span><br>                        <span class="hljs-tag">&lt;/<span class="hljs-name">goals</span>&gt;</span><br>                    <span class="hljs-tag">&lt;/<span class="hljs-name">execution</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">executions</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><h4 id="创建Proto"><a href="#创建Proto" class="headerlink" title="创建Proto"></a>创建Proto</h4><blockquote><p>PS：Proto文件夹，必须和JAVA同级！</p></blockquote><blockquote><p>PS:请使用 option java_package&#x3D;”xxxx”，</p><p>而不是 package xxxx</p><p>package xxxx 会导致你引用其他包时，会报错</p></blockquote><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs protobuf">syntax = <span class="hljs-string">&quot;proto3&quot;</span>;<br> <br><span class="hljs-keyword">option</span> java_multiple_files = <span class="hljs-literal">true</span>;<br><span class="hljs-keyword">option</span> java_package=<span class="hljs-string">&quot;com.example.test.helloworld&quot;</span>;<br> <br><span class="hljs-comment">//请求</span><br><span class="hljs-keyword">message </span><span class="hljs-title class_">Request</span> &#123;<br>  <span class="hljs-type">double</span> num1 = <span class="hljs-number">1</span>;<br>  <span class="hljs-type">double</span> num2 = <span class="hljs-number">2</span>;<br>  OperateType opType = <span class="hljs-number">3</span>;<br>&#125;<br> <br><span class="hljs-comment">//操作类型</span><br><span class="hljs-keyword">enum </span><span class="hljs-title class_">OperateType</span> &#123;<br>  Addition = <span class="hljs-number">0</span>;<br>  Division = <span class="hljs-number">1</span>;<br>  Multiplication = <span class="hljs-number">2</span>;<br>  Subtraction = <span class="hljs-number">3</span>;<br>&#125;<br><span class="hljs-keyword">message </span><span class="hljs-title class_">Response</span> &#123;<br>  <span class="hljs-type">double</span> result = <span class="hljs-number">1</span>;<br>&#125;<br> <br><span class="hljs-comment">//定义服务</span><br><span class="hljs-keyword">service </span><span class="hljs-title class_">Operate</span> &#123;<br>  <span class="hljs-function"><span class="hljs-keyword">rpc</span> Calculate (Request) <span class="hljs-keyword">returns</span> (Response)</span>;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="执行命令，生成文件"><a href="#执行命令，生成文件" class="headerlink" title="执行命令，生成文件"></a>执行命令，生成文件</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">mvn clean install <br>或者点击<br>protobuf:compile 和 protobuf:compile-custom<br></code></pre></td></tr></table></figure><h4 id="实现接口"><a href="#实现接口" class="headerlink" title="实现接口"></a>实现接口</h4><p><strong>为了简单演示，这里就直接设置Response.Result&#x3D;2</strong></p><p><strong>OperateImpl</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@GRpcService</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OperateImpl</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">OperateGrpc</span>.OperateImplBase &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">calculate</span><span class="hljs-params">(Request request,</span><br><span class="hljs-params">                          StreamObserver&lt;Response&gt; responseObserver)</span> &#123;<br>        Response response=Response.newBuilder().setResult(<span class="hljs-number">2</span>).build();<br>        System.out.println(response);<br>        responseObserver.onNext(response);<br>        responseObserver.onCompleted();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="Client端"><a href="#Client端" class="headerlink" title="Client端"></a>Client端</h3><h4 id="POM（同Service端）"><a href="#POM（同Service端）" class="headerlink" title="POM（同Service端）"></a>POM（同Service端）</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span>?&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0&quot;</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span><br><span class="hljs-tag">         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.5.6<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span> <span class="hljs-comment">&lt;!-- lookup parent from repository --&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>test1<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>test1<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>Demo project for Spring Boot<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">spring-cloud.version</span>&gt;</span>2021.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">spring-cloud.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">grpc-spring-boot-starter.version</span>&gt;</span>2.3.2<span class="hljs-tag">&lt;/<span class="hljs-name">grpc-spring-boot-starter.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">os-maven-plugin.version</span>&gt;</span>1.6.0<span class="hljs-tag">&lt;/<span class="hljs-name">os-maven-plugin.version</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">protobuf-maven-plugin.version</span>&gt;</span>0.5.1<span class="hljs-tag">&lt;/<span class="hljs-name">protobuf-maven-plugin.version</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br> <br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>        <span class="hljs-comment">&lt;!-- grpc --&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.lognet<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>grpc-spring-boot-starter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;grpc-spring-boot-starter.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br> <br>    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">extensions</span>&gt;</span><br>            <span class="hljs-comment">&lt;!-- os-maven-plugin --&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">extension</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>kr.motd.maven<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>os-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;os-maven-plugin.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">extension</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">extensions</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span><br>            <span class="hljs-comment">&lt;!-- spring-boot-maven-plugin --&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>            <span class="hljs-comment">&lt;!-- protobuf-maven-plugin --&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.xolstice.maven.plugins<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>protobuf-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>$&#123;protobuf-maven-plugin.version&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">protocArtifact</span>&gt;</span>com.google.protobuf:protoc:3.5.1-1:exe:$&#123;os.detected.classifier&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">protocArtifact</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">pluginId</span>&gt;</span>grpc-java<span class="hljs-tag">&lt;/<span class="hljs-name">pluginId</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">pluginArtifact</span>&gt;</span>io.grpc:protoc-gen-grpc-java:1.11.0:exe:$&#123;os.detected.classifier&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">pluginArtifact</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">outputDirectory</span>&gt;</span>$&#123;project.build.sourceDirectory&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">outputDirectory</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">clearOutputDirectory</span>&gt;</span>false<span class="hljs-tag">&lt;/<span class="hljs-name">clearOutputDirectory</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span><br>                <span class="hljs-tag">&lt;<span class="hljs-name">executions</span>&gt;</span><br>                    <span class="hljs-tag">&lt;<span class="hljs-name">execution</span>&gt;</span><br>                        <span class="hljs-tag">&lt;<span class="hljs-name">goals</span>&gt;</span><br>                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>compile<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span><br>                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>compile-custom<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span><br>                        <span class="hljs-tag">&lt;/<span class="hljs-name">goals</span>&gt;</span><br>                    <span class="hljs-tag">&lt;/<span class="hljs-name">execution</span>&gt;</span><br>                <span class="hljs-tag">&lt;/<span class="hljs-name">executions</span>&gt;</span><br>            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span><br> <br><span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span><br></code></pre></td></tr></table></figure><h4 id="拷贝Proto"><a href="#拷贝Proto" class="headerlink" title="拷贝Proto"></a>拷贝Proto</h4><p>和java同级目录</p><h4 id="执行命令"><a href="#执行命令" class="headerlink" title="执行命令"></a>执行命令</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">mvn clean install <br>或者点击<br>protobuf:compile 和 protobuf:compile-custom<br></code></pre></td></tr></table></figure><p><strong>4，调用方法</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">CalculateService<br></code></pre></td></tr></table></figure><p>其中 main方法，是测试方法。</p><p>GRPC启动的默认端口是6565，在main中设置了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CalculateService</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> ManagedChannel channel;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> OperateGrpc.OperateBlockingStub blockingStub;<br> <br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">CalculateService</span><span class="hljs-params">(ManagedChannel channel)</span> &#123;<br>        <span class="hljs-built_in">this</span>.channel = channel;<br>        blockingStub = OperateGrpc.newBlockingStub(channel);<br>    &#125;<br> <br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">CalculateService</span><span class="hljs-params">(String host, <span class="hljs-type">int</span> port)</span> &#123;<br>        <span class="hljs-built_in">this</span>(ManagedChannelBuilder.forAddress(host, port)<br>                .usePlaintext()<br>                .build());<br>    &#125;<br> <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">shutdown</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        channel.shutdown().awaitTermination(<span class="hljs-number">5</span>, TimeUnit.SECONDS);<br>    &#125;<br> <br>    <span class="hljs-keyword">public</span> <span class="hljs-type">float</span> <span class="hljs-title function_">operate</span><span class="hljs-params">(<span class="hljs-type">float</span> num1, <span class="hljs-type">float</span> num2, OperateType operateType)</span> &#123;<br>        <span class="hljs-type">Request</span> <span class="hljs-variable">request</span> <span class="hljs-operator">=</span> Request.newBuilder().setNum1(num1).setNum2(num2).setOpType(operateType).build();<br>        <span class="hljs-type">Response</span> <span class="hljs-variable">response</span> <span class="hljs-operator">=</span> blockingStub.calculate(request);<br>        <span class="hljs-keyword">return</span> (<span class="hljs-type">float</span>) response.getResult();<br>    &#125;<br> <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-type">CalculateService</span> <span class="hljs-variable">service</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CalculateService</span>(<span class="hljs-string">&quot;localhost&quot;</span>, <span class="hljs-number">6565</span>);<br>            System.out.println(service.operate(<span class="hljs-number">100</span>, <span class="hljs-number">0</span>, OperateType.Division));<br>            service.shutdown();<br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            System.out.println(e);<br>        &#125;<br>    &#125;<br> <br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
  </entry>
  
  <entry>
    <title>Netty笔记</title>
    <link href="https://alan.org.cn/posts/Netty%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/Netty%E7%AC%94%E8%AE%B0.html</id>
    <published>2025-12-23T15:09:59.000Z</published>
    <updated>2025-12-23T15:09:59.000Z</updated>
    
    
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Netty笔记&quot;&gt;&lt;a href=&quot;#Netty笔记&quot; class=&quot;headerlink&quot; title=&quot;Netty笔记&quot;&gt;&lt;/a&gt;Netty笔记&lt;/h1&gt;&lt;h2 id=&quot;Netty是什么&quot;&gt;&lt;a href=&quot;#Netty是什么&quot; class=&quot;headerli</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="Netty笔记"><a href="#Netty笔记" class="headerlink" title="Netty笔记"></a>Netty笔记</h1><h2 id="Netty是什么"><a href="#Netty是什么" class="headerlink" title="Netty是什么"></a>Netty是什么</h2><p>netty是一个异步的、基于事件驱动的网络应用框架，用以快速开发高性能、高可靠性的网络IO程序</p><p>主要针对在TCP协议下，面向Clients端端高并发应用，或者Peer to Peer场景下的大量数据持续传输的应用。</p><p>本质是一个NIO框架，适用于服务器通讯相关的多种应用场景</p><p>TCP&#x2F;IP&lt;-JDK原生&lt;-NIO&lt;-Netty</p><h2 id="Netty应用场景"><a href="#Netty应用场景" class="headerlink" title="Netty应用场景"></a>Netty应用场景</h2><ul><li>RPC框架</li><li>游戏行业</li><li>大数据领域</li></ul><h2 id="IO模型"><a href="#IO模型" class="headerlink" title="IO模型"></a>IO模型</h2><p>IO模型就是用什么样的通道进行数据的发送和接收，很大程度上决定了程序通信性能</p><p>Java支持3种网络编程模型 &#x2F;IO模型:BIO、NIO、AIO</p><ul><li><p>BIO 同步并阻塞（传统阻塞型）</p><p>一个连接一个线程，容易造成不必要的线程开销</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-7a75fe8167007b15fbd6300d7ec68000-6086a1.png" alt="截屏2025-12-24 下午9.53.50"></p></li><li><p>NIO 同步非阻塞</p><p>一个线程处理多个请求（连接），客户端发送的请求都会注册到多路复用器上，多路复用器轮询到连接有I&#x2F;O的请求就进行处理</p></li></ul><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-3e05326e77fda6f1bd764b20bf237ed1-f68911.png" alt="截屏2025-12-24 下午9.54.07"></p><ul><li>AIO 异步非阻塞，AIO引入了异步通道的概念，采用了Proactor模型，简化了程序编写，有效的请求才启动线程，它的特点是先由操作系统完成后才通知服务端程序启动线程去处理，一般适用于连接数较多且连接时间较长的应用。</li></ul><h4 id="BIO、NIO、AIO适用场景"><a href="#BIO、NIO、AIO适用场景" class="headerlink" title="BIO、NIO、AIO适用场景"></a>BIO、NIO、AIO适用场景</h4><ul><li>BIO 方式适用于<strong>连接数目比较小且固定</strong>的架构，这种方式对服务器资源要求比较高,并发局限于应用中，JDK1.4以前的唯一选择，但程序简单易理解。</li><li>NIO 方式适用于<strong>连接数目多且连接比较短</strong>(轻操作)的架构，比如聊天服务器，弹幕系统，服务器间通讯等。编程比较复杂，JDK1.4开始支持:</li><li>AIO 方式使用于<strong>连接数目多且连接比较长</strong>(重操作)的架构，比如相册服务器，充分调用OS参与并发操作，编程比较复杂，JDK7开始支持。</li></ul><h2 id="BIO介绍"><a href="#BIO介绍" class="headerlink" title="BIO介绍"></a>BIO介绍</h2><ul><li>Java BIO 就是传统的javaIO 编程，其相关的类和接口在 java.io</li><li>BI0(blocking I&#x2F;O): <strong>同步阻塞</strong>，服务器实现模式为一个连接一个线程，即客户端有连接请求时服务器端就需要启动一个线程进行处理，如果这个连接不做任何事情会造成不必要的线程开销，可以通过<strong>线程池机制改善</strong>。</li><li>BIO方式适用于连接数目比较小且固定的架构，这种方式对服务器资源要求比较高3)并发局限于应用中，JDK1.4以前的唯一选择，程序简单易理解</li></ul><h2 id="BIO实例及分析"><a href="#BIO实例及分析" class="headerlink" title="BIO实例及分析"></a>BIO实例及分析</h2><p>Coming soon…</p><h2 id="BIO内容梳理小结"><a href="#BIO内容梳理小结" class="headerlink" title="BIO内容梳理小结"></a>BIO内容梳理小结</h2><p>Coming soon…</p><h2 id="NIO介绍"><a href="#NIO介绍" class="headerlink" title="NIO介绍"></a>NIO介绍</h2><ul><li>Java NIO 全称 java non-blocking lO，是指 JDK提供的新API。从 JDK1.4 开始，Java 提供了一系列改进的输入&#x2F;输出的新特性，被统称为 NIO(即 New lO)，是<strong>同步非阻塞</strong>的</li><li>NIO 相关类都被放在 java.nio 包及子包下，并且对原 java.io包中的很多类进行改写。</li><li>NIO 有三大核心部分:<strong>Channel(通道)，Buffer(缓冲区),Selector(选择器)</strong></li><li>NIO是 面向<strong>缓冲区，或者面向块</strong>编程的。数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区中前后移动，这就增加了处理过程中的灵活性，使用它可以提供非阻塞式的高伸缩性网络</li><li>Java NIO的非阻塞模式，使一个线程从某通道发送请求或者读取数据，但是它仅能得到目前可用的数据，如果目前没有数据可用时，就什么都不会获取，而<strong>不是保持线程阻塞</strong>，所以直至数据变的可以读取之前，该线程可以继续做其他的事情。非阻塞写也是如此，一个线程请求写入一些数据到某通道，但不需要等待它完全写入，这个线程同时可以去做别的事情。</li><li>通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来根据实际情况，可以分配50或者100个线程来处理。不像之前的阻塞10那样，非得分配10000个。</li><li>HTTP2.0使用了多路复用的技术，做到同一个连接并发处理多个请求，而且并发请求的数量比HTTP1.1大了好几个数量级。</li></ul><h2 id="NIO的Buffer基本使用"><a href="#NIO的Buffer基本使用" class="headerlink" title="NIO的Buffer基本使用"></a>NIO的Buffer基本使用</h2><p>基本方法</p><ul><li>allocate() 设置大小，含义为可以方多少个</li><li>capacity() 容量有多大</li><li><strong>flip() 读写切换</strong></li><li>hasRemaining()有过有剩余就返回true</li><li>get() 没get一次索引就会往后移动一次</li></ul><p><strong>NIO和BIO的比较</strong></p><ul><li>BIO以流的方式处理数据,而 NIO以块的方式处理数据,块 I&#x2F;O的效率比流 I&#x2F;O高很多</li><li>BIO是阻塞的，NIO 则是非阻塞的</li><li>BIO基于字节流和字符流进行操作，而 NIO基于 Channel(通道)和 Buffer(缓冲区)进行操作，数据总是从通道读取到缓冲区中，或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求，数据到达等)，因此使用<strong>单个线程就可以监听多个客户端通道</strong></li></ul><h2 id="NIO三大核心组件的关系"><a href="#NIO三大核心组件的关系" class="headerlink" title="NIO三大核心组件的关系"></a>NIO三大核心组件的关系</h2><p>关系说明</p><ul><li>每个channel都会对应一个Bufer</li><li>Selector对应一个线程，一个线程对应多个channel(连接)</li><li>程序切换到那个channel是由事件决定的，Event就是一个重要的概念</li><li>Selector会根据不同的事件，在各个通道上切换</li><li>Buffer就是一个内存块，底层是有一个数组</li><li>数据的读取写入是通过Buffer，BIO中要么是输入流，要么是输出流，不能是双向的，但是NIO的Buffer是可以读也可以写，需要flip方法读写切换</li><li>channel是双向的，可以返回底层操作系统的情况</li></ul><h2 id="Buffer的机制及子类"><a href="#Buffer的机制及子类" class="headerlink" title="Buffer的机制及子类"></a>Buffer的机制及子类</h2><p>缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块，可以理解成是一个<strong>容器对象(含数组)<strong>，该对象提供了</strong>一组方法</strong>，可以更轻松地使用内存块，，缓冲区对象内置了一些机制，能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道，但是读取或写入的数据都必须经由Buffer</p><h3 id="NIO中Buffer下面的子类"><a href="#NIO中Buffer下面的子类" class="headerlink" title="NIO中Buffer下面的子类"></a><strong>NIO中Buffer下面的子类</strong></h3><ul><li>ByteBuffer，存储字节数据到缓冲区</li><li>ShortBuffer，存储字符串数据到缓冲区</li><li>CharBuffer，存储字符数据到缓冲区</li><li>IntBuffer，存储整数数据到缓冲区</li><li>LongBuffer，存储长整型数据到缓冲区</li><li>DoubleBuffer，存储小数到缓冲区</li><li>FloatBuffer，存储小数到缓冲区</li></ul><h3 id="Buffer类型定义了缓冲区都具有的四个属性"><a href="#Buffer类型定义了缓冲区都具有的四个属性" class="headerlink" title="Buffer类型定义了缓冲区都具有的四个属性"></a>Buffer类型定义了缓冲区都具有的四个属性</h3><table><thead><tr><th>属性</th><th>描述</th></tr></thead><tbody><tr><td>Capacity</td><td>容量，即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变</td></tr><tr><td>Limit</td><td>表示缓冲区的当前终点，不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的</td></tr><tr><td>Position</td><td>位置，下一个要被读或写的元素的索引，每次读写缓冲区数据时都会改变改值，为下次读写作准备</td></tr><tr><td>Mark</td><td>标记</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> Buffer <span class="hljs-title function_">flip</span><span class="hljs-params">()</span>&#123;<br>  limit = position;<br>  position = <span class="hljs-number">0</span>;<br>  mark = -<span class="hljs-number">1</span>;<br>  <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="Buffer的相关方法"><a href="#Buffer的相关方法" class="headerlink" title="Buffer的相关方法"></a>Buffer的相关方法</h3><p>&#x2F;&#x2F;JDK1.4时引入的api</p><p><strong>public final int capacity()&#x2F;&#x2F;返回此缓冲区的容量</strong></p><p><strong>public finalint position()&#x2F;&#x2F;返回此缓冲区的位置</strong></p><p><strong>public final Buffer position (int newPositio)&#x2F;&#x2F;设置此缓冲区的位置</strong></p><p><strong>public final int limit()&#x2F;&#x2F;返回此缓冲区的限制</strong></p><p><strong>public final Buffer limit (int newLimit)&#x2F;&#x2F;设置此缓冲区的限制</strong></p><p>public final Buffer mark()&#x2F;&#x2F;在此缓冲区的位置设置标记,</p><p>public final Buffer reset()&#x2F;&#x2F;将此缓冲区的位置重置为以前标记的位置</p><p><strong>public final Buffer clear()&#x2F;&#x2F;清除此缓冲区,即将各个标记恢复到初始状态，但是数据并没有真正擦除</strong></p><p><strong>public final Buffer flip()&#x2F;&#x2F;反转此缓冲区</strong><br>public final Bufferrewind()&#x2F;&#x2F;重绕此缓冲X<br>public finalint remaining()&#x2F;&#x2F;返回当前位置与限制之间的元素数</p><p><strong>public final boolean hasRemaining()&#x2F;&#x2F;告知在当前位置和限制之间是否有元素</strong></p><p><strong>public abstract boolean isReadOnly();&#x2F;&#x2F;告知此缓冲区是否为只读缓冲区</strong><br>&#x2F;&#x2F;JDK1.6时引入的api<br><strong>public abstract boolean hasArray();&#x2F;&#x2F;告知此缓冲区是否具有可访问的底层实现数组</strong></p><p><strong>public abstract Object array();&#x2F;&#x2F;返回此缓冲区的底层实现数组</strong></p><p>public abstractint arrayOffset0);&#x2F;&#x2F;返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量public abstract boolean isDirect();&#x2F;&#x2F;告知此缓冲区是否为直接缓冲区</p><h3 id="ByteBuffer"><a href="#ByteBuffer" class="headerlink" title="ByteBuffer"></a>ByteBuffer</h3><p>&#x2F;&#x2F;缓冲区创建相关api</p><p><strong>public static ByteBuffer allocateDirect(int capacity)&#x2F;&#x2F;创建直接缓神X</strong></p><p><strong>public static ByteBuffer allocate(int capacity)&#x2F;&#x2F;设置缓冲区的初始容量</strong></p><p>public static ByteBuffer wrap(bytell array)&#x2F;&#x2F;把一个数组放到缓冲区中使用&#x2F;&#x2F;构造初始化位置offset和上界length的缓冲区</p><p>public static ByteBuffer wrap(bytel] array,int offset, int length)&#x2F;&#x2F;缓存区存取相关API<br><strong>public abstract byte get();&#x2F;&#x2F;从当前位置position上get，get之后，position会自动+1</strong></p><p><strong>public abstract byte get (int index);&#x2F;&#x2F;从绝对位置get</strong></p><p><strong>public abstract ByteBuffer put (byte b);&#x2F;&#x2F;从当前位置上添加，put之后，position会自动+1</strong></p><p><strong>public abstract ByteBuffer put (int index, byte b);&#x2F;&#x2F;从绝对位置-put</strong></p><h2 id="Channel的基本介绍"><a href="#Channel的基本介绍" class="headerlink" title="Channel的基本介绍"></a>Channel的基本介绍</h2><h3 id="NIO的通道类似于流，但有些区别如下"><a href="#NIO的通道类似于流，但有些区别如下" class="headerlink" title="NIO的通道类似于流，但有些区别如下:"></a>NIO的通道类似于流，但有些区别如下:</h3><ul><li><p>通道可以同时进行读写，而流只能读或者只能写</p></li><li><p>通道可以实现异步读写数据</p></li><li><p>通道可以从缓冲读数据，也可以写数据到缓冲:</p></li></ul><h3 id="Channel的介绍"><a href="#Channel的介绍" class="headerlink" title="Channel的介绍"></a>Channel的介绍</h3><ol><li><p>BIO中的 stream 是单向的，例如 FilelnputStream 对象只能进行读取数据的操作，而 NIO 中的通道(Channel)是双向的，可以读操作，也可以写操作。</p></li><li><p>Channel在NIO中是一个接口<strong>public interface Channel extends Closeable{}</strong></p></li><li><p>常用的 Channel 类有:<strong>FileChannel、DatagramChannel、ServerSocketChannel 和SocketChannel.</strong></p></li><li><p>FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannel和SocketChannel用于TCP的数据读写。</p></li></ol><h3 id="Filechannel类"><a href="#Filechannel类" class="headerlink" title="Filechannel类"></a>Filechannel类</h3><p>FileChannel主要用来对本地文件进行I&#x2F;O 操作，常见的方法有</p><ul><li>public int read(ByteBuffer dst)，从通道读取数据并放到缓冲区中</li><li>public int write(ByteBuffer src)，把缓冲区的数据写到通道中</li><li>public long transferFrom(ReadableByteChannel src,long position, long count)，从目标通道中复制数据到当前通道</li><li>public long transferTo(long position, long count, WritableByteChannel target)，把数据从当前通道复制给目标通道</li></ul><h2 id="Channel应用案例1"><a href="#Channel应用案例1" class="headerlink" title="Channel应用案例1"></a>Channel应用案例1</h2><p>本地文件写</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">String</span> <span class="hljs-variable">str</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;test&quot;</span>;<br><span class="hljs-type">FileOutputStream</span> <span class="hljs-variable">fis</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FileOutputStream</span>(<span class="hljs-string">&quot;./test.txt&quot;</span>);<br><span class="hljs-type">FileChannel</span> <span class="hljs-variable">fc</span> <span class="hljs-operator">=</span> fis.getChannel();<br><span class="hljs-type">ByteBuffer</span> <span class="hljs-variable">buf</span> <span class="hljs-operator">=</span> ByteBuffer.allocate(<span class="hljs-number">1024</span>);<br>buf.put(str.getBytes());<br>buf.flip();<br>fc.write(buf);<br>fis.close;<br></code></pre></td></tr></table></figure><h2 id="Channel应用案例2"><a href="#Channel应用案例2" class="headerlink" title="Channel应用案例2"></a>Channel应用案例2</h2><p>本地文件读</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">File</span> <span class="hljs-variable">file</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">File</span>(<span class="hljs-string">&quot;./test.txt&quot;</span>)<br><span class="hljs-type">FileInputStream</span> <span class="hljs-variable">fis</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FileInputStream</span>(file);<br><span class="hljs-type">FileChannel</span> <span class="hljs-variable">fc</span> <span class="hljs-operator">=</span> fis.getChannel();<br><span class="hljs-type">ByteBuffer</span> <span class="hljs-variable">buf</span> <span class="hljs-operator">=</span> ByteBuffer.allocate((<span class="hljs-type">int</span>)file.lenght);<br>fc.read(buf);<br>System.out.println(<span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(buf.array()))<br>fis.close;<br></code></pre></td></tr></table></figure><h2 id="Channel应用案例3"><a href="#Channel应用案例3" class="headerlink" title="Channel应用案例3"></a>Channel应用案例3</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">FileInputStream</span> <span class="hljs-variable">fis1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FileInputStream</span>(<span class="hljs-string">&quot;./test.txt&quot;</span>);<br><span class="hljs-type">FileChannel</span> <span class="hljs-variable">fc1</span> <span class="hljs-operator">=</span> fis1.getChannel();<br><span class="hljs-type">FileOutputStream</span> <span class="hljs-variable">fis2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FileOutputStream</span>(<span class="hljs-string">&quot;./test.txt&quot;</span>);<br><span class="hljs-type">FileChannel</span> <span class="hljs-variable">fc2</span> <span class="hljs-operator">=</span> fis2.getChannel();<br><span class="hljs-type">ByteBuffer</span> <span class="hljs-variable">buf</span> <span class="hljs-operator">=</span> ByteBuffer.allocate(<span class="hljs-number">512</span>);<br><span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>)&#123;<br>  buf.clear(); <span class="hljs-comment">// 复位很重要 会导致limt和position相同导致死循环</span><br>  <span class="hljs-type">int</span> <span class="hljs-variable">read</span> <span class="hljs-operator">=</span> fc2.read(buf);<br>  <span class="hljs-keyword">if</span>(read == -<span class="hljs-number">1</span>)&#123;<br>    <span class="hljs-keyword">break</span>;<br>  &#125;<br>  buf.flip();<br>  fc2.write(buf);<br>&#125;<br>fis1.close;<br>fis2.close;<br><br></code></pre></td></tr></table></figure><h2 id="Channel拷贝文件"><a href="#Channel拷贝文件" class="headerlink" title="Channel拷贝文件"></a>Channel拷贝文件</h2><p>transferFrom</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">FileInputStream</span> <span class="hljs-variable">fis1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FileInputStream</span>(<span class="hljs-string">&quot;./test.txt&quot;</span>);<br><span class="hljs-type">FileChannel</span> <span class="hljs-variable">fc1</span> <span class="hljs-operator">=</span> fis1.getChannel();<br><span class="hljs-type">FileOutputStream</span> <span class="hljs-variable">fis2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FileOutputStream</span>(<span class="hljs-string">&quot;./test.txt&quot;</span>);<br><span class="hljs-type">FileChannel</span> <span class="hljs-variable">fc2</span> <span class="hljs-operator">=</span> fis2.getChannel();<br>fc2.transferFrom(fc1,<span class="hljs-number">0</span>,fc1.size());<br>fis1.close;<br>fis2.close;<br></code></pre></td></tr></table></figure><h2 id="Buffer的注意细节"><a href="#Buffer的注意细节" class="headerlink" title="Buffer的注意细节"></a>Buffer的注意细节</h2><ul><li><p>ByteBuffer 支持类型化的put 和 get,put 放入的是什么数据类型，get就应该使用相应的数据类型来取出，否则可能有 BufferUnderflowException 异常。</p></li><li><p>可以将一个普通Buffer 转成只读Buffer;buffer.asReadOnlyBuffer(),如果继续写会抛出ReadOnlyBufferException</p></li><li><p>NIO 还提供了 MappedByteBuffer，可以让文件直接在内存(<strong>堆外的内存</strong>)中进行修改，而如何同步到文件由NIO 来完成，操作系统不需要拷贝一次</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">RandomAccessFile</span> <span class="hljs-variable">rfile</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RandomAccessFile</span>(<span class="hljs-string">&quot;./test.txt&quot;</span>,rw);<br><span class="hljs-type">FileChannel</span> <span class="hljs-variable">cl</span> <span class="hljs-operator">=</span> rfile.getChannel();<br><span class="hljs-comment">// 参数1: FileChannel.MapMode.READ_WRITE 使用读写模式</span><br><span class="hljs-comment">// 参数2: 0是可以映射的起始位置</span><br><span class="hljs-comment">// 参数3: 5是可以映射到内存的大小（*不是索引位置），即将test.txt的多少个字节映射到内存</span><br><span class="hljs-comment">// 可以直接修改的范围为0-5不包含5,如果超过会抛出IndexOutOfBoundsException</span><br><span class="hljs-comment">// 实际类型DirectByteBUffer</span><br><span class="hljs-type">MappedByteBuffer</span> <span class="hljs-variable">mapBuf</span> <span class="hljs-operator">=</span> cl.map(FileChannel.MapMode.READ_WRITE,<span class="hljs-number">0</span>,<span class="hljs-number">5</span>);<br>mapBuf.put(<span class="hljs-number">0</span>,(<span class="hljs-type">byte</span>)<span class="hljs-string">&#x27;H&#x27;</span>);<br>mapBuf.put(<span class="hljs-number">3</span>,(<span class="hljs-type">byte</span>)<span class="hljs-string">&#x27;9&#x27;</span>);<br>rfile.close();<br></code></pre></td></tr></table></figure></li><li><p>前面我们讲的读写操作，都是通过一个Buffer 完成的，NIO 还支持 通过多个Buffer(即 Buffer 数组)完成读写操作，即 Scattering(分散) 和 Gatering（聚合）</p><ul><li>Scattering : 将数据写入到buffer时，可以采用buffer数组，依次写入（分散）</li><li>Gatering:从buffer读取数据时，可以采用buffer数组依次读入</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// ServerSocketChannel和SocketChannel网络</span><br><span class="hljs-type">ServerSocketChannel</span> <span class="hljs-variable">ssc</span> <span class="hljs-operator">=</span> ServerSocketChannel.open();<br><span class="hljs-type">InetSocketAddress</span> <span class="hljs-variable">address</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InetSocketAddress</span>(<span class="hljs-number">7000</span>);<br><span class="hljs-comment">// 绑定端口到socket并启动</span><br>ssc.socket().bind(address);<br><span class="hljs-comment">// 创建buffer数组</span><br>ByteBuffer[] byteBufs = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ByteBuffer</span>[<span class="hljs-number">2</span>];<br>byteBufs[<span class="hljs-number">0</span>] = ByteBuffer.allocate(<span class="hljs-number">5</span>);<br>byteBufs[<span class="hljs-number">1</span>] = ByteBuffer.allocate(<span class="hljs-number">3</span>);<br><span class="hljs-comment">// 等待客户端连接(telnet)</span><br><span class="hljs-type">SocketChannel</span> <span class="hljs-variable">sc</span> <span class="hljs-operator">=</span> ssc.accept();<br><span class="hljs-comment">// 循环读取</span><br><span class="hljs-type">int</span> <span class="hljs-variable">messageLength</span> <span class="hljs-operator">=</span> <span class="hljs-number">8</span>; <span class="hljs-comment">// 假定从客户端接收8个字节</span><br><span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>)&#123;<br>  <span class="hljs-type">int</span> <span class="hljs-variable">byteRead</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>  <span class="hljs-keyword">while</span>(byteRead&lt;messageLength)&#123;<br>    <span class="hljs-type">long</span> <span class="hljs-variable">l</span> <span class="hljs-operator">=</span> sc.read(byteBufs);<br>    byteRead =+ l; <span class="hljs-comment">// 累计读取到的字节数 ;</span><br>    <span class="hljs-comment">// 使用流打印，可靠当前的这个buffer的position和limit</span><br>    Arrays.asList(byteBufs).stream().map(buf-&gt;<span class="hljs-string">&quot;position=&quot;</span>+buf.position()+<span class="hljs-string">&quot;,limit=&quot;</span>+buf.limit()).forEach(System.out::println);<br>  &#125;<br>  <span class="hljs-comment">// 将所有的buffer进行flip</span><br>  Arrays.asList(byteBufs).forEach(buf-&gt;buf.flip());<br>  <span class="hljs-comment">// 将数据读出显示到客户端</span><br>  <span class="hljs-type">long</span> <span class="hljs-variable">byteWrite</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br>  <span class="hljs-keyword">while</span>(byteWrite&lt;messageLength)&#123;<br>    <span class="hljs-type">long</span> <span class="hljs-variable">l</span> <span class="hljs-operator">=</span> sc.write(byteBufs);<br>    byteWrite+=l;<br>  &#125;<br>  <span class="hljs-comment">// 将所有的buffer进行clear</span><br>  Arrays.asList(byteBufs).forEach(buf-&gt;buf.clear());<br>  System.out.println(<span class="hljs-string">&quot;byteRead:=&quot;</span>+byteRead+<span class="hljs-string">&quot; byteWrite:=&quot;</span>+byteWrite+<span class="hljs-string">&quot;,messageLength&quot;</span>+messageLength)<br>&#125;<br></code></pre></td></tr></table></figure></li></ul><h2 id="Selector选择器"><a href="#Selector选择器" class="headerlink" title="Selector选择器"></a>Selector选择器</h2><h3 id="Selector基本介绍"><a href="#Selector基本介绍" class="headerlink" title="Selector基本介绍"></a>Selector基本介绍</h3><ul><li>Java 的 NIO，用非阻塞的 IO方式。可以用一个线程，处理多个的客户端连接，就会使用到<strong>Selector(选择器)</strong></li><li>**Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector)**，如果有事件发生，便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道，也就是管理多个连接和请求。</li><li>只有在连接真正有读写事件发生时，才会进行读写，就大大地减少了系统开销，并且不必为每个连接都创建一个线程，不用去维护多个线程</li><li>避免了多线程之间的上下文切换导致的开销</li></ul><p><strong>特别说明</strong></p><ul><li>Netty 的IO线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器)，可以同时并发处理成百上千个客户端连接。</li><li>当线程从某客户端 Socket 通道进行读写数据时，若没有数据可用时，该线程可以进行其他任务。</li><li>线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作，所以单独的线程可以管理多个输入和输出通道。</li><li>由于读写操作都是非阻塞的，这就可以充分提升IO线程的运行效率，避免由于频繁I&#x2F;O阻塞导致的线程挂起。</li><li>一个I&#x2F;O 线程可以并发处理 N个客户端连接和读写操作，这从根本上解决了传统同步阻塞 I&#x2F;O一连接一线程模型，架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。</li></ul><h3 id="SelectorAPi介绍"><a href="#SelectorAPi介绍" class="headerlink" title="SelectorAPi介绍"></a>SelectorAPi介绍</h3><p>Selector 类是一个抽象类,常用方法和说明如下:<br>public abstract class Selector implements Closeable </p><ul><li><p>public static selector open(); &#x2F;&#x2F; 得到一个选择器对象</p></li><li><p>public int select(long timeout); &#x2F;&#x2F; 监控所有注册的通道，当其中有10 操作可以进行时，将对应的 SelectionKey 加入到内部集合中并返回，参数用来设置超时时间</p></li><li><p>public set<selectionKey> selectedKeys(); &#x2F;&#x2F; 从内部集合中得到所有的 SelectionKey</p></li></ul><p><strong>注意事项</strong></p><ol><li>NIO中的ServerSocketChannel功能类似ServerSocket,SocketChannel功能类似的Socket</li><li>selector相关方法说明<ul><li>selector:select() &#x2F;&#x2F; 阻塞</li><li>selector:select(1000) &#x2F;&#x2F; 阻塞1000毫秒，在1000毫秒后返回</li><li>selector:wakeup() &#x2F;&#x2F; 阻塞 唤醒selector</li><li>selector:selectNow() &#x2F;&#x2F; 不阻塞，立马返回</li></ul></li></ol><h3 id="SelectionKey在NIO体系"><a href="#SelectionKey在NIO体系" class="headerlink" title="SelectionKey在NIO体系"></a>SelectionKey在NIO体系</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-1affbd251b6b92bbbc942ecddfcd791c-38970d.png" alt="截屏2025-12-26 下午8.55.14"></p><ol><li>当客户端连接时，会通过ServerSocketChannel得到SocketChannel</li><li>将socketChannel注册到Selector，register(Selector sel,int ops),一个Selector可以注解多个SocketChannel</li><li>注册后返回一个SelectorKey,会和该Selector关联（集合）</li><li>Selector进行监听select方法，返回有事件发生的通道个数</li><li>进一步的到各个的SelectorKey(有事件发生的)</li><li>在通过SelectionKey反向获取SocketChannel，方法channel()</li><li>可以通过得到的channel,完成业务处理</li></ol><p>**事  件 **</p><ul><li>OP_READ：有读取事件发生</li><li>OP_WRITE：有写事件发生</li><li>OP_CONNECT：连接成立了</li><li>OP_ACCEPT：有一个客户端来连接我们</li></ul><h2 id="NIO快速入门1-服务端"><a href="#NIO快速入门1-服务端" class="headerlink" title="NIO快速入门1(服务端)"></a>NIO快速入门1(服务端)</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.lwl.NIO;<br><br><span class="hljs-keyword">import</span> java.net.InetSocketAddress;<br><span class="hljs-keyword">import</span> java.nio.ByteBuffer;<br><span class="hljs-keyword">import</span> java.nio.channels.*;<br><span class="hljs-keyword">import</span> java.util.Iterator;<br><span class="hljs-keyword">import</span> java.util.Set;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NIOServer</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception&#123;<br>        <span class="hljs-comment">//创建一个服务端channel</span><br>        <span class="hljs-type">ServerSocketChannel</span> <span class="hljs-variable">serverSocketChannel</span> <span class="hljs-operator">=</span> ServerSocketChannel.open();<br>        <span class="hljs-comment">//创建一个selector</span><br>        <span class="hljs-type">Selector</span> <span class="hljs-variable">selector</span> <span class="hljs-operator">=</span> Selector.open();<br>        <span class="hljs-comment">//绑定一个端口，在服务端监听</span><br>        serverSocketChannel.socket().bind(<span class="hljs-keyword">new</span> <span class="hljs-title class_">InetSocketAddress</span>(<span class="hljs-number">6666</span>));<br>        <span class="hljs-comment">//设置为非阻塞</span><br>        serverSocketChannel.configureBlocking(<span class="hljs-literal">false</span>);<br>        <span class="hljs-comment">//serverSocketChannel注册到selector,并设置关心事件</span><br>        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);<br><br><span class="hljs-comment">//        System.out.println(&quot;注册SelectionKey的数量&quot; + selector.keys().size());</span><br><br>        <span class="hljs-comment">//循环等待客户链接</span><br>        <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>)&#123;<span class="hljs-comment">//一次循环解决一个事件集合里的全部事件，下次循环会再继续监听</span><br>            <span class="hljs-keyword">if</span> (selector.select(<span class="hljs-number">1000</span>) == <span class="hljs-number">0</span>)&#123;<span class="hljs-comment">//无事发生,继续循环等待</span><br><span class="hljs-comment">//                selector.selectedKeys().size() // 有事件发生的事件的数量</span><br>                System.out.println(<span class="hljs-string">&quot;服务器等待了一秒，无事发生，当前以注册的selectionKey数量为：&quot;</span> + selector.keys().size());<br>                <span class="hljs-keyword">continue</span>;<br>            &#125;<br>            <span class="hljs-comment">//返回》0，有事发生,获取客户端发生关注事件的集合</span><br>            <span class="hljs-comment">//通过selectionKeys可以反向获取Channel</span><br>            Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();<br>            <span class="hljs-comment">//遍历selectionKeys，获取当前事件集</span><br>            Iterator&lt;SelectionKey&gt; Keyiterator = selectionKeys.iterator();<br>            <span class="hljs-keyword">while</span> (Keyiterator.hasNext())&#123;<br>                <span class="hljs-comment">//获取当前事件</span><br>                <span class="hljs-type">SelectionKey</span> <span class="hljs-variable">selectionKey</span> <span class="hljs-operator">=</span> Keyiterator.next();<br>                <span class="hljs-comment">//查看key发生的事件并作相应的处理</span><br>                <span class="hljs-keyword">if</span>(selectionKey.isAcceptable()) &#123;<span class="hljs-comment">//有新客户端链接</span><br>                    <span class="hljs-comment">//为这个客户端生成一个channel</span><br>                    <span class="hljs-type">SocketChannel</span> <span class="hljs-variable">socketchannel</span> <span class="hljs-operator">=</span> serverSocketChannel.accept();<br>                    socketchannel.configureBlocking(<span class="hljs-literal">false</span>);<br>                    System.out.println(<span class="hljs-string">&quot;客户端连接成功，&quot;</span> + socketchannel.hashCode());<br>                    <span class="hljs-comment">//将当前channel注册到selector,并关心这个事件有没有发生读事件OP_READ，同时给channel关联一个buffer</span><br>                    socketchannel.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(<span class="hljs-number">1024</span>));<br>                &#125;<br>                <span class="hljs-keyword">if</span>(selectionKey.isReadable())&#123;<br>                    <span class="hljs-comment">//发生了读事件,通过key反向得到channel和绑定的buffer</span><br>                    <span class="hljs-type">SocketChannel</span> <span class="hljs-variable">socketchannel</span> <span class="hljs-operator">=</span> (SocketChannel)selectionKey.channel();<br>                    <span class="hljs-comment">//获取与该channel关联的buffer，在和连接时就已经绑定了，40行处</span><br>                    <span class="hljs-type">ByteBuffer</span> <span class="hljs-variable">buffer</span> <span class="hljs-operator">=</span> (ByteBuffer) selectionKey.attachment();<br>                    socketchannel.read(buffer);<span class="hljs-comment">//从客户端读到的数据</span><br>                    System.out.println(<span class="hljs-string">&quot;from 客户端 : &quot;</span> + <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>(buffer.array()));<br>                    buffer.clear();<br>                &#125;<br>            &#125;<br>            <span class="hljs-comment">//及时将当前的SelectionKey,防止操作</span><br>            Keyiterator.remove();<br>        &#125;<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><h2 id="NIO快速入门2-客户端"><a href="#NIO快速入门2-客户端" class="headerlink" title="NIO快速入门2(客户端)"></a>NIO快速入门2(客户端)</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.lwl.NIO;<br><br><span class="hljs-keyword">import</span> java.io.IOException;<br><span class="hljs-keyword">import</span> java.net.InetSocketAddress;<br><span class="hljs-keyword">import</span> java.nio.ByteBuffer;<br><span class="hljs-keyword">import</span> java.nio.channels.Channel;<br><span class="hljs-keyword">import</span> java.nio.channels.SocketChannel;<br><span class="hljs-keyword">import</span> java.util.Scanner;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NIOClient</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> IOException &#123;<br>        <span class="hljs-comment">//得到一个网络通道</span><br>        <span class="hljs-type">SocketChannel</span> <span class="hljs-variable">socketChannel</span> <span class="hljs-operator">=</span> SocketChannel.open();<br>        <span class="hljs-comment">//非阻塞</span><br>        socketChannel.configureBlocking(<span class="hljs-literal">false</span>);<br>        <span class="hljs-comment">//提供服务器的ip和端口</span><br>        <span class="hljs-type">InetSocketAddress</span> <span class="hljs-variable">inetSocketAddress</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InetSocketAddress</span>(<span class="hljs-string">&quot;127.0.0.1&quot;</span>, <span class="hljs-number">6666</span>);<br>        <span class="hljs-comment">//链接服务器</span><br>        <span class="hljs-keyword">if</span>(!socketChannel.connect(inetSocketAddress))&#123;<br>            <span class="hljs-keyword">while</span> (!socketChannel.finishConnect())&#123;<br>                System.out.println(<span class="hljs-string">&quot;链接需要时间，客户端不会阻塞&quot;</span>);<br>            &#125;<br>        &#125;<br><span class="hljs-comment">//        String str = &quot;hello world!&quot;;</span><br>        <span class="hljs-type">Scanner</span> <span class="hljs-variable">scanner</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Scanner</span>(System.in);<br>        String str;<br>        <span class="hljs-type">ByteBuffer</span> <span class="hljs-variable">buffer</span> <span class="hljs-operator">=</span> ByteBuffer.allocate(<span class="hljs-number">1024</span>);<br>        <span class="hljs-keyword">while</span> (scanner.hasNext())&#123;<br>            str = scanner.next();<br>            System.out.println(str);<br>            buffer.put(str.getBytes());<br>            buffer.flip();<br>            socketChannel.write(buffer);<br>            buffer.clear();<br>        &#125;<br>        <span class="hljs-comment">//生成一个和数组一样大的buffer</span><br><span class="hljs-comment">//        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());</span><br><span class="hljs-comment">//        //发送数据，将buffer写入channel</span><br><span class="hljs-comment">//        socketChannel.write(byteBuffer);</span><br><br>        <span class="hljs-comment">//代码会停在这</span><br>        System.in.read();<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><h2 id="SelectionKey-API"><a href="#SelectionKey-API" class="headerlink" title="SelectionKey API"></a>SelectionKey API</h2><ul><li>SelectionKey，表示 Selector 和网络通道的注册关系,共四种:</li><li>int OP ACCEPT:有新的网络连接可以 accept，值为 16</li><li>int OP CONNECT:代表连接已经建立，值为8</li><li>int OP READ:代表读操作，值为1</li><li>int OP WRITE:代表写操作，值为 4</li></ul><p>源码中:</p><ul><li>public static final int OP READ &#x3D; 1 &lt;&lt;0;</li><li>public static final int OP WRITE &#x3D; 1 &lt;&lt;2;</li><li>public static final int OP CONNECT &#x3D;1 &lt;&lt; 3;</li><li>public static final int OP ACCEPT&#x3D; 1 &lt;&lt; 4;</li></ul><p>SelectionKey的相关方法</p><p>public abstract class SelectionKey{</p><ul><li>public abstract Selector selector();&#x2F;&#x2F;得到与之关联的Selector 对象</li><li>public abstract SelectableChannel channel();&#x2F;&#x2F;得到与之关联的通道</li><li>public final object attachment();&#x2F;&#x2F;得到与之关联的共享数<br>据</li><li>public abstract SelectionKey interestOps(int ops);&#x2F;&#x2F;设置或改变监听事件</li><li>public final boolean isAcceptable();&#x2F;&#x2F;是否可以accept</li><li>public final boolean isReadable();&#x2F;&#x2F;是否可以读</li><li>public final boolean isWritable();&#x2F;&#x2F;是否可以写</li></ul><ol><li><p>ServerSocketChannel 在<strong>服务器端监听新的客户端socket 连接</strong></p></li><li><p>相关方法如下</p><ul><li>public abstract class <strong>ServerSocketChannel</strong> extends AbstractSelectableChanne!imp lements NetworkChannel{</li><li>public static ServerSocketChannel open()，得到一个ServerSocketChannel通道</li><li>public final ServerSocketChannel bind(SocketAddress local)，设置服务器端端县</li><li>public final SelectableChannel configureBlocking(boolean block)，设置阻塞或非阻塞模式，取值 false表示采用非阻塞模式</li><li>public SocketChannel accept()，接受一个连接，返回代表这个连接的通道对冢</li><li>public final SelectionKeyregister(Selector sel,intops)，注册一个选择器并设置监听事件</li></ul></li><li><p>SocketChannel，网络IO通道，<strong>具体负责进行读写操作</strong>。NIO把缓冲区的数据写入通<br>道，或者把通道里的数据读到缓冲区。</p></li><li><p>相关方法如下</p><ul><li>public abstract class <strong>SocketChannel</strong> extends AbstractSelectableChannelimplements ByteChannel, ScatteringByteChannel, GatheringByteChannel,NetworkChannelf</li><li>public static SocketChannel open();&#x2F;&#x2F;得到-个 SocketChannel 通道</li><li>public final SelectableChannel configureBlocking(boolean block);&#x2F;&#x2F;设置阻塞或非阻塞模式，取值 false表示采用非阻塞模式</li><li>public boolean connect(SocketAddress remote);&#x2F;&#x2F;连接服务器</li><li>public boolean finishconnect();&#x2F;&#x2F;如果上面的方法连接失败，接下来就要通过该方法完成连接操作</li><li>public int write(ByteBuffersrc);&#x2F;&#x2F;往通道里写数据</li><li>public int read(ByteBuffer dst);&#x2F;&#x2F;从通道里读数据</li><li>public final SelectionKey register(Selector sel, int ops, Object att);&#x2F;&#x2F;注册一个选择器并设置监听事件，最后一个参数可以设置共享数据</li><li>public final void close();&#x2F;&#x2F;关闭通道</li></ul></li></ol><h2 id="Netty概述"><a href="#Netty概述" class="headerlink" title="Netty概述"></a>Netty概述</h2><ul><li>NIO 的类库和 API 繁杂，使用麻烦:需要熟练掌握 Selector、ServerSocketChannel.SocketChannel、ByteBuffer 等:</li><li>需要具备其他的额外技能:要熟悉 Java 多线程编程，因为 NIO 编程涉及到 Reactor模式，你必须对多线程和网络编程非常熟悉，才能编写出高质量的 NIO 程序。</li><li>开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失3)败缓存、网络拥塞和异常流的处理等等。</li><li>JDK NIO 的 Bug:例如臭名昭著的 Epoll Bug，它会导致 Selector 空轮询，最终导致 CPU 100%。直到 JDK 1.7 版本该问题仍旧存在，没有被根本解决。</li></ul><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-c3a3cdcce8affa16a09321bdb7c613f3-63264d.png" alt="截屏2026-01-01 下午8.36.18"></p><ul><li><p>Netty是由 JBOSS 提供的一个 Java开源框架。</p></li><li><p>Netty 提供异步的、基于事件驱动的网络应用程序框架，用以快速开发高性能、高可靠性的网络10程序</p></li><li><p>Netty 可以帮助你快速、简单的开发出一个网络应用，相当于简化和流程化了 NI0 的2)开发过程行业等获得了广泛的应用，知名的 Elasticsearch、Dubbo 框架内部都采用了 Netty.</p></li></ul><h3 id="Netty的优点"><a href="#Netty的优点" class="headerlink" title="Netty的优点"></a>Netty的优点</h3><p>Netty 对 JDK 自带的 NIO 的 API进行了封装，解决了上述问题。</p><ul><li><p>设计优雅:适用于各种传输类型的统- API阻塞和非阻塞 Socket;基于灵活且可扩展的事件模型，可以清晰地分离关注点;高度可定制的线程模型-单线程，一个或多个线程池.</p></li><li><p>使用方便:详细记录的 Javadoc，用户指南和示例;没有其他依赖项，JDK5(Netty3.x)或6(Netty 4.x)就足够了。</p></li><li><p>高性能、吞吐量更高:延迟更低;减少资源消耗;最小化不必要的内存复制3)</p></li><li><p>安全:完整的 SSL&#x2F;TLS 和 StartTls 支持:4)</p></li><li><p>社区活跃、不断更新:社区活跃，版本迭代周期短，发现的Bug可以被及时修复5)2同时，更多的新功能会被加入</p></li></ul><p>现在常用netty4.x版本</p><h2 id="线程模型概述-架构设计"><a href="#线程模型概述-架构设计" class="headerlink" title="线程模型概述-架构设计"></a>线程模型概述-架构设计</h2><ul><li><p>不同的线程模式，对程序的性能有很大影响，为了搞清Netty 线程模式，我们来系统的讲解下 各个线程模式，最后看看Netty 线程模型有什么优越性.</p></li><li><p>目前存在的线程模型有</p><ul><li><strong>传统阻塞IO服务模型</strong></li><li><strong>Reactor 模式</strong>（反应器模式）</li></ul></li><li><p><strong>根据Reactor 的数量和处理资源池线程的数量不同，有3种典型的实现</strong></p><ul><li>单 Reactor 单线程:</li><li>单 Reactor 多线程;</li><li>主从 Reactor 多线程</li></ul></li><li><p>Netty 线程模式(Netty 主要基于<strong>主从 Reactor 多线程模型</strong>做了一定的改进，其中主从Reactor多线程模型有多个 Reactor)</p></li></ul><h3 id="传统阻塞I-O服务模型"><a href="#传统阻塞I-O服务模型" class="headerlink" title="传统阻塞I&#x2F;O服务模型"></a>传统阻塞I&#x2F;O服务模型</h3><p><strong>工作原理图</strong></p><p>黄色的框表示对象，蓝色的框表示线程</p><p>白色的框表示方法（API）</p><p><strong>模型特点</strong></p><ol><li>采用阻塞IO模型来获取输入的数据</li><li>每个连接都需要独立的线程完成数据的输入，业务处理，数据返回</li></ol><p><strong>问题分析</strong></p><ol><li>当并发数很大，就会创建大量的线程，占用很大系数资源</li><li>连接创建后，如果当前线程暂时没有数据可读，该线程会阻塞在read操作，造成线程资源量费</li></ol><p>总结：传统阻塞I&#x2F;O服务模型，无法适应大并发的场景</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-2ffccec2c3fea6e06076dbd56b302699-fd3ae6.png" alt="截屏2026-01-01 下午9.12.22"></p><h3 id="Reactor模型"><a href="#Reactor模型" class="headerlink" title="Reactor模型"></a>Reactor模型</h3><p>针对传统阻塞I&#x2F;0 服务模型的2个缺点，解决方案:</p><ol><li>基于 I&#x2F;O 复用模型:多个连接共用一个阻塞对象，应用程序只需要在一个阻塞对象等待，无需阻塞等待所有连接。当某个连接有新的数据可以处理时，操作系统通知应用程序，线程从阻塞状态返回，开始进行业务处理</li><li>基于线程池复用线程资源:不必再为每个连接创建线程，将连接完成后的业务处理任务分配给线程进行处理，一个线程可以处理多个连接的业务。</li></ol><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-0a5af009a31ebb6ccf4ed165577b6f68-b83fef.png" alt="截屏2026-01-01 下午9.22.05"></p><p>解决了传统IO模型的以下问题</p><ol><li>当并发数很大，就会创建大量的线程，占用很大系数资源</li><li>连接创建后，如果当前线程暂时没有数据可读，该线程会阻塞在read操作，造成线程资源量费</li></ol><p>Reactor又叫1. 反应器模型 2.分发者模型 3.统治者模型 </p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-8fd47448f177400bcb15c54098941e99-e4094f.png" alt="截屏2026-01-01 下午9.24.31"></p><p>I&#x2F;O复用结合线程池，就是Reactor模型基本设计思想</p><p>说明：</p><ol><li>Reactor模型，指通过一个或多个输入请求，同时传递给服务处理器的模式（基于事件驱动）</li><li>服务器程序处理传入的多个请求，并将它们同步分派到相应的处理线程，因此Reactor模式也叫Dispatcher模式</li><li>Reactor模式使用IO复用监听事件后，分发给某个线程（进程），这点就是网络服务高并发处理关键</li></ol><h3 id="Reactor模式中核心组成"><a href="#Reactor模式中核心组成" class="headerlink" title="Reactor模式中核心组成:"></a>Reactor模式中核心组成:</h3><ol><li>Reactor:Reactor在一个单独的线程中运行，负责监听和分发事件，分发给适当的处理程序来对 IO事件做出反应。 它就像公司的电话接线员，它接听来自客户的电话并将线路转移到适当的联系人;</li><li>Handlers:处理程序执行 I&#x2F;0 事件要完成的实际事件，类似于客户想要与之交谈的公司中的实际官员。Reactor 通过调度适当的处理程序来响应 I&#x2F;O 事件，处理程序执行非阻塞操作。</li></ol><h3 id="Reactor模式分类："><a href="#Reactor模式分类：" class="headerlink" title="Reactor模式分类："></a>Reactor模式分类：</h3><p>根据Reactor的梳理和处理资源线程池的数量不同，有3种典型的实现</p><ol><li>单 Reactor 单线程:</li><li>单 Reactor 多线程;</li><li>主从 Reactor 多线程</li></ol><h2 id="单-Reactor-单线程"><a href="#单-Reactor-单线程" class="headerlink" title="单 Reactor 单线程"></a>单 Reactor 单线程</h2><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-ed24028c092d112f289085dc369b0429-ecc985.png" alt="截屏2026-01-02 下午2.05.18"></p><ol><li>Select 是前面 I&#x2F;O 复用模型介绍的标准网络编程 API，可以实现应用程序通过一个阻塞对象监听多路连接请求</li><li>Reactor 对象通过 Select 监控客户端请求事件，收到事件后通过 Dispatch 进行分发</li><li>如果是建立连接请求事件，则由 Acceptor 通过 Accept 处理连接请求，然后创建一个Handler对象处理连接完成后的后续业务处理</li><li>如果不是建立连接事件，则 Reactor 会分发调用连接对应的 Handler 来响应</li><li>Handler 会完成 Read→业务处理→&gt;Send 的完整业务流程</li></ol><p><strong>结合实例</strong>:服务器端用一个线程通过多路复用搞定所有的 10操作(包括连接，读、写等)，编码简单，清晰明了，但是如果客户端连接数量较多，将无法支撑，前面的 NIO案例就属于这种模型。</p><h3 id="缺点和优点"><a href="#缺点和优点" class="headerlink" title="缺点和优点"></a>缺点和优点</h3><p><strong>优点</strong>:模型简单，没有多线程、进程通信、竞争的问题，全部都在一个线程中完成</p><p><strong>缺点</strong>:性能问题，只有一个线程，无法完全发挥多核 CPU的性能。Handler 在处理某个连接上的业务时，整个进程无法处理其他连接事件，很容易导致性能瓶颈</p><p><strong>缺点</strong>:可靠性问题，线程意外终止，或者进入死循环，会导致整个系统通信模块不3)可用，不能接收和处理外部消息，造成节点故障<br><strong>使用场景</strong>:客户端的数量有限，业务处理非常快速，比如Redis在业务处理的时间复4)杂度 0(1) 的情况</p><h2 id="单Reactor多线程"><a href="#单Reactor多线程" class="headerlink" title="单Reactor多线程"></a>单Reactor多线程</h2><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-cb446f2fd2dafc5898f6143ee58ef148-da7c27.png" alt="截屏2026-01-02 下午2.43.46"></p><p>方案说明</p><ol><li>Reactor对象通过select监控客户端请求事件，收到事件后，通过dispatch进行分发</li><li>如果建立连接的请求，则由Acceptor通过accept处理连接请求，然后创建一个handle对象处理完成连接后的各种事件</li><li>如果不是连接请求，则由reactor分发调用连接对应的handle来处理</li><li>handle只负责响应事件，不做具体的业务处理，通过read读取数据后，会分发后面的worker线程池的某个线程处理业务</li><li>worker线程池会分配独立线程完成真正的业务，并将结果返回给handle</li><li>handle收到响应后，通过send将结果返回给client</li></ol><h3 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h3><p><strong>优点</strong>： 可以充分的利用多核cpu的处理能力</p><p><strong>缺点</strong>：多线程数据共享和访问比较复杂,Reactor处理所有的事件的监听和响应，在单线程运行，在高并发场景容易出现性能瓶颈。</p><h2 id="主从Reactor多线程"><a href="#主从Reactor多线程" class="headerlink" title="主从Reactor多线程"></a>主从Reactor多线程</h2><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-af1d53688da29b7370d67690eaa2e9b8-aa93b4.png" alt="截屏2026-01-02 下午3.05.47"></p><p><strong>工作原理示意：</strong></p><p>针对单Reactor多线程模型中，Reactor在单线程中运行，高并发场景下容易成为性能瓶颈，可以让Reactor在多线程中运行</p><p><strong>方案说明：</strong></p><ol><li>Reactor主线程MainReactor对象通过select监听连接事件，收到请求后，通过Acceptor处理连接事件</li><li>当Acceptor处理连接事件后，MainReactor将连接分配给SubReactor</li><li>subreacotr将连接加入到连接队列进行监听，并将创建handler进行处理各种事件处理</li><li>当新事件发生时，subreactor就会调用对应的handle处理</li><li>handler通过read读取数据，分发给后面的worker线程处理</li><li>worker线程池分配独立的worker线程进行业务处理，并返回结果</li><li>handler收到响应的结果后，再通过send将结果返回给client</li><li>Reactor主线程可以对应多个Reactor子线程，即MainReactor可以关联多个SubReactor</li></ol><h2 id="Netty-模型"><a href="#Netty-模型" class="headerlink" title="Netty 模型"></a>Netty 模型</h2><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-50-46-d9f3fdc9d79a361ade03596fcbd009b5-469675.png" alt="截屏2026-01-16 下午11.33.45"></p><ol><li>Netty抽象出两组线程池BossGroup专门接收客户端连接，WorkerGroup专门负责网络的读写</li><li>BossGroup和WorkerGroup类型都是NioEventLoopGroup</li><li>NioEventLoopGroup相当于一个事件循环组，这个组含有多个事件循环，每一个事件循环是NioEventLoop</li><li>NioEventLoop表示一个不断循环执行处理任务多线程，每个NioEventLoop都有一个Selector，用于监听绑定在其上的socket的网络通讯</li><li>NioEventLoopGroup可以有多个线程，即可以含有多个NioEventLoop </li><li>每个BossEventLoop执行的步骤有3步<ol><li>轮询accept事件</li><li>处理accept事件，与client建立连接，生成NioSocketChannel，并将其注册到某个workerNioEventLoop上的selector</li><li>处理任务队列的任务，即runAllTasks</li></ol></li><li>每个WorkerNioEventLoop循环执行步骤<ol><li>轮询read或write事件</li><li>处理I&#x2F;O事件，即read，write事件，在对应NioSocketChannel处理</li><li>处理任务队列的任务，即runAllTasks</li></ol></li><li>每个WorkerNioEventLoop处理业务时，会使用Pipeline 管道,Pipeline 中包含了channel，即通过pipline可以获取到对应的通道，管道中维护了很多的处理器</li></ol><h2 id="Netty入门案例"><a href="#Netty入门案例" class="headerlink" title="Netty入门案例"></a>Netty入门案例</h2><h3 id="服务端"><a href="#服务端" class="headerlink" title="服务端"></a>服务端</h3><p>NettyServer</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> cn.org.alan.netty.simple;<br><br><span class="hljs-keyword">import</span> io.netty.bootstrap.ServerBootstrap;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelFuture;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelInitializer;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelOption;<br><span class="hljs-keyword">import</span> io.netty.channel.EventLoopGroup;<br><span class="hljs-keyword">import</span> io.netty.channel.nio.NioEventLoopGroup;<br><span class="hljs-keyword">import</span> io.netty.channel.socket.SocketChannel;<br><span class="hljs-keyword">import</span> io.netty.channel.socket.nio.NioServerSocketChannel;<br><span class="hljs-keyword">import</span> io.netty.channel.socket.nio.NioSocketChannel;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2026/1/16 下午11:50</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NettyServer</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        <span class="hljs-comment">// 创建BossGroup 和 workerGroup</span><br>        <span class="hljs-comment">// 说明</span><br>        <span class="hljs-comment">// 1. 创建两个线程组BossGroup 和 workerGroup</span><br>        <span class="hljs-comment">// 2. bossGroup只是处理连接请求，真正的客户端业务处理会交给workerGroup完成</span><br>        <span class="hljs-comment">// 3. 两个都是无限循环</span><br>        <span class="hljs-type">EventLoopGroup</span> <span class="hljs-variable">bossGroup</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NioEventLoopGroup</span>();<br>        <span class="hljs-type">EventLoopGroup</span> <span class="hljs-variable">workerGroup</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NioEventLoopGroup</span>();<br><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 创建服务器端的启动对象，配置参数</span><br>            <span class="hljs-type">ServerBootstrap</span> <span class="hljs-variable">bootstrap</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ServerBootstrap</span>();<br><br>            <span class="hljs-comment">// 使用链式编程来进行设置</span><br>            bootstrap.group(bossGroup,workerGroup) <span class="hljs-comment">// 设置两个线程组</span><br>                    .channel(NioServerSocketChannel.class) <span class="hljs-comment">// 使用NioSocketChannel作为服务器通道的实现</span><br>                    .option(ChannelOption.SO_BACKLOG,<span class="hljs-number">128</span>) <span class="hljs-comment">// 设置线程队列得到连接的个数</span><br>                    .childOption(ChannelOption.SO_KEEPALIVE,<span class="hljs-literal">true</span>) <span class="hljs-comment">// 设置保持连接活动连接状态</span><br>                    .childHandler(<span class="hljs-keyword">new</span> <span class="hljs-title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;<span class="hljs-comment">// 创建一个通道初始化对象（匿名对象）</span><br>                        <span class="hljs-comment">// 给pipline设置处理器</span><br>                        <span class="hljs-meta">@Override</span><br>                        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>                            ch.pipeline().addLast(<span class="hljs-keyword">new</span> <span class="hljs-title class_">NettyServerHandler</span>()); <span class="hljs-comment">// 向管道的最后追加一个处理器</span><br>                        &#125;<br>                    &#125;); <span class="hljs-comment">// 给我们的workerGroup的EventLoop对应的管道设置处理器</span><br>            <span class="hljs-comment">// 指定相关端口,并且同步处理 生成了一个ChannelFuture对象</span><br>            <span class="hljs-type">ChannelFuture</span> <span class="hljs-variable">sync</span> <span class="hljs-operator">=</span> bootstrap.bind(<span class="hljs-number">6668</span>).sync();<br><br>            <span class="hljs-comment">// 对关闭通道进行[监听]</span><br>            sync.channel().closeFuture().sync();<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            bossGroup.shutdownGracefully();<br>            workerGroup.shutdownGracefully();<br>        &#125;<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>NettyServerHandle</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> cn.org.alan.netty.simple;<br><br><span class="hljs-keyword">import</span> io.netty.buffer.ByteBuf;<br><span class="hljs-keyword">import</span> io.netty.buffer.Unpooled;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelHandlerContext;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelInboundHandlerAdapter;<br><span class="hljs-keyword">import</span> io.netty.util.CharsetUtil;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> *  我们自定义一个Handler需要继续netty规定好的某个HandlerAdapter(规范)</span><br><span class="hljs-comment"> *  这时我们定自定义一个Handler,才能成为一个handler</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2026/1/17 上午12:04</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NettyServerHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">ChannelInboundHandlerAdapter</span> &#123;<br>    <span class="hljs-comment">// 读取实际数据（这里可以读取客户端发送的消息）</span><br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * ChannelHandlerContext ctx：上下文对象，含有，管道pipline,通道channel,地址</span><br><span class="hljs-comment">     * Object msg：是客户端发送的数据 默认Object</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> ctx</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> msg</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@throws</span> Exception</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        System.out.println(<span class="hljs-string">&quot;server ctx = &quot;</span>+ ctx);<br>        <span class="hljs-comment">// 将msg转成一个ByteBuf</span><br>        <span class="hljs-comment">// ByteBuf是netty提供的，不是NIO的ByteBuffer</span><br>        <span class="hljs-type">ByteBuf</span> <span class="hljs-variable">buf</span> <span class="hljs-operator">=</span> (ByteBuf) msg;<br>        System.out.println(<span class="hljs-string">&quot;客户端发送的消息是：&quot;</span>+ buf.toString(CharsetUtil.UTF_8));<br>        System.out.println(<span class="hljs-string">&quot;客户端地址：&quot;</span>+ ctx.channel().remoteAddress());<br>        <span class="hljs-comment">// super.channelRead(ctx, msg);</span><br>    &#125;<br>    <span class="hljs-comment">// 数据读取完毕</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">channelReadComplete</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        <span class="hljs-comment">// writeAndFlush是write+flush</span><br>        <span class="hljs-comment">// 将数据写入到缓存，并刷新</span><br>        <span class="hljs-comment">// 一般将，我们对发送到数据进行编码</span><br>        ctx.writeAndFlush(Unpooled.copiedBuffer(<span class="hljs-string">&quot;hello&quot;</span>,CharsetUtil.UTF_8));<br>        <span class="hljs-comment">// super.channelReadComplete(ctx);</span><br>    &#125;<br>    <span class="hljs-comment">// 处理异常，一般需要关闭通道</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">exceptionCaught</span><span class="hljs-params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        ctx.close();<br>        <span class="hljs-comment">// super.exceptionCaught(ctx, cause);</span><br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>NettyClient</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> cn.org.alan.netty.simple;<br><br><span class="hljs-keyword">import</span> io.netty.bootstrap.Bootstrap;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelFuture;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelInitializer;<br><span class="hljs-keyword">import</span> io.netty.channel.nio.NioEventLoopGroup;<br><span class="hljs-keyword">import</span> io.netty.channel.socket.SocketChannel;<br><span class="hljs-keyword">import</span> io.netty.channel.socket.nio.NioSocketChannel;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2026/1/17 上午12:16</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NettyClient</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> InterruptedException &#123;<br>        <span class="hljs-comment">// 客户端需要一个事件循环组</span><br>        <span class="hljs-type">NioEventLoopGroup</span> <span class="hljs-variable">eventExecutors</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NioEventLoopGroup</span>();<br>        <span class="hljs-comment">// 创建客户端启动对象</span><br>        <span class="hljs-comment">// 客户端使用的不是serverBootstrap而是Bootstrap</span><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-type">Bootstrap</span> <span class="hljs-variable">bootstrap</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Bootstrap</span>();<br>            <span class="hljs-comment">// 设置相关参数</span><br>            bootstrap.group(eventExecutors) <span class="hljs-comment">// 设置线程组</span><br>                    .channel(NioSocketChannel.class)<span class="hljs-comment">// 设置客户端通道实现类</span><br>                    .handler(<span class="hljs-keyword">new</span> <span class="hljs-title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;<br>                        <span class="hljs-meta">@Override</span><br>                        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>                            ch.pipeline().addLast(<span class="hljs-keyword">new</span> <span class="hljs-title class_">NettyClientHandler</span>()); <span class="hljs-comment">// 加入自己的处理器</span><br>                        &#125;<br>                    &#125;);<br>            System.out.println(<span class="hljs-string">&quot;客户端启动成功&quot;</span>);<br>            <span class="hljs-comment">// 启动客户端去连接服务端</span><br>            <span class="hljs-comment">// 关于ChannelFuture要分析，涉及netty端异步模型</span><br>            <span class="hljs-type">ChannelFuture</span> <span class="hljs-variable">channelFuture</span> <span class="hljs-operator">=</span> bootstrap.connect(<span class="hljs-string">&quot;127.0.0.1&quot;</span>, <span class="hljs-number">6668</span>).sync();<br>            <span class="hljs-comment">// 给关闭通道进行监听</span><br>            channelFuture.channel().closeFuture().sync();<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            eventExecutors.shutdownGracefully();<br>        &#125;<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>NettyClientHandler</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> cn.org.alan.netty.simple;<br><br><span class="hljs-keyword">import</span> io.netty.buffer.ByteBuf;<br><span class="hljs-keyword">import</span> io.netty.buffer.Unpooled;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelHandlerContext;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelInboundHandlerAdapter;<br><span class="hljs-keyword">import</span> io.netty.util.CharsetUtil;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2026/1/17 上午12:22</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NettyClientHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">ChannelInboundHandlerAdapter</span> &#123;<br>    <span class="hljs-comment">// 当通道就绪就会触发方法</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">channelActive</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        System.out.println(<span class="hljs-string">&quot;client &quot;</span>+ ctx);<br>        ctx.writeAndFlush(Unpooled.copiedBuffer(<span class="hljs-string">&quot;hello,server&quot;</span>, CharsetUtil.UTF_8));<br>        <span class="hljs-comment">// super.channelActive(ctx);</span><br>    &#125;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 当通道有读取时间时，会触发</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> ctx</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> msg</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@throws</span> Exception</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">channelRead</span><span class="hljs-params">(ChannelHandlerContext ctx, Object msg)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        <span class="hljs-comment">// 将msg转成一个ByteBuf</span><br>        <span class="hljs-comment">// ByteBuf是netty提供的，不是NIO的ByteBuffer</span><br>        <span class="hljs-type">ByteBuf</span> <span class="hljs-variable">buf</span> <span class="hljs-operator">=</span> (ByteBuf) msg;<br>        System.out.println(<span class="hljs-string">&quot;服务器回复的消息：&quot;</span>+ buf.toString(CharsetUtil.UTF_8));<br>        System.out.println(<span class="hljs-string">&quot;服务器地址：&quot;</span>+ ctx.channel().remoteAddress());<br>        <span class="hljs-comment">// super.channelRead(ctx, msg);</span><br>    &#125;<br>    <span class="hljs-comment">// 处理异常，一般需要关闭通道</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">exceptionCaught</span><span class="hljs-params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        cause.printStackTrace();<br>        ctx.close();<br>        <span class="hljs-comment">// super.exceptionCaught(ctx, cause);</span><br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><h2 id="TaskQueue"><a href="#TaskQueue" class="headerlink" title="TaskQueue"></a>TaskQueue</h2><p>任务队列中的Task的3种典型场景</p><ol><li><p>自定义普通任务：该任务是提交到taskQueue中</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 解决方案1 用户查询自定义普通任务</span><br>        ctx.channel().eventLoop().execute(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Runnable</span>() &#123;<br>            <span class="hljs-meta">@Override</span><br>            <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    Thread.sleep(<span class="hljs-number">10</span>*<span class="hljs-number">1000</span>);<br>                    ctx.writeAndFlush(Unpooled.copiedBuffer(<span class="hljs-string">&quot;testtest&quot;</span>,CharsetUtil.UTF_8));<br>                &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                    System.out.println(<span class="hljs-string">&quot;发送异常&quot;</span>);<br>                &#125;<br>            &#125;<br>        &#125;);<br></code></pre></td></tr></table></figure><p>如果上吗代码复制一份改为20秒，但因为是同一个线程，所以会累加为30秒返回</p></li><li><p>自定义定时任务 : 该任务是提交到scheduleTaskQueue中</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 方法2 ：用户自定义定时任务-&gt;该任务是提交到scheduleTaskQueue中</span><br>      ctx.channel().eventLoop().schedule(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Runnable</span>() &#123;<br>          <span class="hljs-meta">@Override</span><br>          <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>                      <span class="hljs-keyword">try</span> &#123;<br>                          Thread.sleep(<span class="hljs-number">20</span>*<span class="hljs-number">1000</span>);<br>                          ctx.writeAndFlush(Unpooled.copiedBuffer(<span class="hljs-string">&quot;testtest&quot;</span>,CharsetUtil.UTF_8));<br>                      &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                          System.out.println(<span class="hljs-string">&quot;发送异常&quot;</span>);<br>                      &#125;<br>          &#125;<br>      &#125;,<span class="hljs-number">5</span>, TimeUnit.SECONDS);<br>   <br></code></pre></td></tr></table></figure></li><li><p>非当前Reactor线程调用Channel的各种方法</p></li></ol><p>例如推送系统，会根据用户标识，找到对应channel，然后调用write类方法向用户推送消息。其中write会提交到任务队列后被异步消费</p><p>方案说明：</p><ol><li>Netty抽象出两组线程池BossGroup专门接收客户端连接，WorkerGroup专门负责网络的读写</li><li>NioEventLoop表示一个不断循环执行处理任务的线程，每个NioEventLoop都有一个selector,用于监听绑定在其上的socket网络通道</li><li>NioEventLoop内部采用串行化设计，从消息的读取-&gt;解码-&gt;处理-&gt;编码-&gt;发送，始终由IO线程NioEventLoop负责</li></ol><ul><li>NioEventLoopGroup下包含了多个NioEventLoop</li><li>每个NioEventLoop中包含一个Selector,一个taskQueue</li><li>每个NioEventLoop的Selector上可以注册监听多个NioChannel</li><li>每个NioChannel只会绑定在唯一的NioEventLoop上</li><li>每个NioChannel都绑定有一个自己的ChannelPipeline</li></ul><h2 id="Netty心跳检测机制"><a href="#Netty心跳检测机制" class="headerlink" title="Netty心跳检测机制"></a>Netty心跳检测机制</h2><p>案例：</p><ol><li>服务器超过3秒没有读，就提示读空闲</li><li>服务器超过5秒没有写操作，就提示写空闲</li><li>当服务器超过7秒没有读或写操作时，就提示读写空闲</li></ol><p>NettyServer</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> cn.org.alan.netty.heartbeat;<br><br><span class="hljs-keyword">import</span> cn.org.alan.netty.simple.NettyServerHandler;<br><span class="hljs-keyword">import</span> io.netty.bootstrap.ServerBootstrap;<br><span class="hljs-keyword">import</span> io.netty.channel.*;<br><span class="hljs-keyword">import</span> io.netty.channel.nio.NioEventLoopGroup;<br><span class="hljs-keyword">import</span> io.netty.channel.socket.SocketChannel;<br><span class="hljs-keyword">import</span> io.netty.channel.socket.nio.NioServerSocketChannel;<br><span class="hljs-keyword">import</span> io.netty.handler.logging.LogLevel;<br><span class="hljs-keyword">import</span> io.netty.handler.logging.LoggingHandler;<br><span class="hljs-keyword">import</span> io.netty.handler.timeout.IdleStateHandler;<br><br><span class="hljs-keyword">import</span> java.util.concurrent.TimeUnit;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2026/1/17 下午4:10</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyServer</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">EventLoopGroup</span> <span class="hljs-variable">bossGroup</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NioEventLoopGroup</span>();<br>        <span class="hljs-type">EventLoopGroup</span> <span class="hljs-variable">workerGroup</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NioEventLoopGroup</span>();<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-type">ServerBootstrap</span> <span class="hljs-variable">bootstrap</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ServerBootstrap</span>();<br>            bootstrap.group(bossGroup,workerGroup)<br>                    .channel(NioServerSocketChannel.class) <br>                    .handler(<span class="hljs-keyword">new</span> <span class="hljs-title class_">LoggingHandler</span>(LogLevel.INFO))<br>                    .option(ChannelOption.SO_BACKLOG,<span class="hljs-number">128</span>) <br>                    .childOption(ChannelOption.SO_KEEPALIVE,<span class="hljs-literal">true</span>)<br>                    .childHandler(<span class="hljs-keyword">new</span> <span class="hljs-title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;<br>                        <span class="hljs-meta">@Override</span><br>                        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br><br>                            <span class="hljs-type">ChannelPipeline</span> <span class="hljs-variable">pipeline</span> <span class="hljs-operator">=</span> ch.pipeline();<br>                            <span class="hljs-comment">// IdleStateHandler 时Netty提供的处理空闲状态的处理器</span><br>                            <span class="hljs-comment">// long readerIdleTime 表示多长时间没有读，就会发送一个心跳检测是否连接</span><br>                            <span class="hljs-comment">// long writerIdleTime 表示多长时间没有写，就会发送一个心跳检测是否连接</span><br>                            <span class="hljs-comment">// long allIdleTime 表示多长时间即没有读页没有写，就会发送一个心跳检测是否连接</span><br>                            <span class="hljs-comment">// TimeUnit unit 时间单位</span><br>                            <span class="hljs-comment">// 当IdleStateHandler触发后，就会传递给管道下一个handler去处理，通过调用触发下一个handler的userEventTiggered</span><br>                            <span class="hljs-comment">// 在该方法中去处理IdleStateHandler(读空闲，写空闲，读写空闲)</span><br>                            pipeline.addLast(<span class="hljs-keyword">new</span> <span class="hljs-title class_">IdleStateHandler</span>(<span class="hljs-number">3</span>,<span class="hljs-number">5</span>,<span class="hljs-number">7</span>, TimeUnit.SECONDS));<br>                            <span class="hljs-comment">// 加入一个对空闲检测进一步处理的handler（自定义)</span><br>                            pipeline.addLast(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MyserverHandler</span>());<br>                        &#125;<br>                    &#125;); <br><br>            <span class="hljs-type">ChannelFuture</span> <span class="hljs-variable">sync</span> <span class="hljs-operator">=</span> bootstrap.bind(<span class="hljs-number">7000</span>).sync();<br>            sync.channel().closeFuture().sync();<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            bossGroup.shutdownGracefully();<br>            workerGroup.shutdownGracefully();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>NettyHandler</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> cn.org.alan.netty.heartbeat;<br><br><span class="hljs-keyword">import</span> io.netty.channel.ChannelHandlerContext;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelInboundHandlerAdapter;<br><span class="hljs-keyword">import</span> io.netty.handler.timeout.IdleStateEvent;<br><span class="hljs-keyword">import</span> io.netty.handler.timeout.IdleStateHandler;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2026/1/17 下午4:21</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyserverHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">ChannelInboundHandlerAdapter</span> &#123;<br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     *</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> ctx 上下文</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@param</span> evt 事件</span><br><span class="hljs-comment">     * <span class="hljs-doctag">@throws</span> Exception</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">userEventTriggered</span><span class="hljs-params">(ChannelHandlerContext ctx, Object evt)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        <span class="hljs-keyword">if</span>(evt <span class="hljs-keyword">instanceof</span> IdleStateEvent)&#123;<br>            <span class="hljs-comment">// 将evt向下转型 IdleStateHandler</span><br>            <span class="hljs-type">IdleStateEvent</span> <span class="hljs-variable">event</span> <span class="hljs-operator">=</span> (IdleStateEvent) evt;<br>            <span class="hljs-type">String</span>  <span class="hljs-variable">eventType</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>            <span class="hljs-keyword">switch</span> (event.state())&#123;<br>                <span class="hljs-keyword">case</span> READER_IDLE :<br>                    eventType = <span class="hljs-string">&quot;读空闲&quot;</span>;<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-keyword">case</span> WRITER_IDLE:<br>                    eventType = <span class="hljs-string">&quot;写空闲&quot;</span>;<br>                    <span class="hljs-keyword">break</span>;<br>                <span class="hljs-keyword">case</span> ALL_IDLE:<br>                    eventType = <span class="hljs-string">&quot;读写空闲&quot;</span>;<br>                    <span class="hljs-keyword">break</span>;<br>            &#125;<br>            System.out.println(ctx.channel().remoteAddress() + <span class="hljs-string">&quot;--超时事件发生--&quot;</span>+eventType);<br>        &#125;<br>        <span class="hljs-built_in">super</span>.userEventTriggered(ctx, evt);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="WebSocket长连接"><a href="#WebSocket长连接" class="headerlink" title="WebSocket长连接"></a>WebSocket长连接</h2><p>NettyServer</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> cn.org.alan.netty.websocket;<br><br><span class="hljs-keyword">import</span> io.netty.bootstrap.ServerBootstrap;<br><span class="hljs-keyword">import</span> io.netty.channel.*;<br><span class="hljs-keyword">import</span> io.netty.channel.nio.NioEventLoopGroup;<br><span class="hljs-keyword">import</span> io.netty.channel.socket.SocketChannel;<br><span class="hljs-keyword">import</span> io.netty.channel.socket.nio.NioServerSocketChannel;<br><span class="hljs-keyword">import</span> io.netty.handler.codec.http.HttpObjectAggregator;<br><span class="hljs-keyword">import</span> io.netty.handler.codec.http.HttpServerCodec;<br><span class="hljs-keyword">import</span> io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;<br><span class="hljs-keyword">import</span> io.netty.handler.logging.LogLevel;<br><span class="hljs-keyword">import</span> io.netty.handler.logging.LoggingHandler;<br><span class="hljs-keyword">import</span> io.netty.handler.stream.ChunkedWriteHandler;<br><span class="hljs-keyword">import</span> io.netty.handler.timeout.IdleStateHandler;<br><br><span class="hljs-keyword">import</span> java.util.concurrent.TimeUnit;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2026/1/17 下午4:10</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyServer</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">EventLoopGroup</span> <span class="hljs-variable">bossGroup</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NioEventLoopGroup</span>();<br>        <span class="hljs-type">EventLoopGroup</span> <span class="hljs-variable">workerGroup</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">NioEventLoopGroup</span>();<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-type">ServerBootstrap</span> <span class="hljs-variable">bootstrap</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ServerBootstrap</span>();<br>            bootstrap.group(bossGroup, workerGroup)<br>                    .channel(NioServerSocketChannel.class)<br>                    .handler(<span class="hljs-keyword">new</span> <span class="hljs-title class_">LoggingHandler</span>(LogLevel.INFO))<br>                    .option(ChannelOption.SO_BACKLOG, <span class="hljs-number">128</span>)<br>                    .childOption(ChannelOption.SO_KEEPALIVE, <span class="hljs-literal">true</span>)<br>                    .childHandler(<span class="hljs-keyword">new</span> <span class="hljs-title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;<br>                        <span class="hljs-meta">@Override</span><br>                        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">initChannel</span><span class="hljs-params">(SocketChannel ch)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br><br>                            <span class="hljs-type">ChannelPipeline</span> <span class="hljs-variable">pipeline</span> <span class="hljs-operator">=</span> ch.pipeline();<br>                            <span class="hljs-comment">// 因为基于http协议，使用http的编码和解码器</span><br>                            pipeline.addLast(<span class="hljs-keyword">new</span> <span class="hljs-title class_">HttpServerCodec</span>());<br>                            <span class="hljs-comment">// 是以块方式写，添加CHunkedWriteHandler处理器</span><br>                            pipeline.addLast(<span class="hljs-keyword">new</span> <span class="hljs-title class_">ChunkedWriteHandler</span>());<br>                            <span class="hljs-comment">// 因为http数据在传输过程中时分段的，HttpObjectAggregator 就是可以将多个段聚合</span><br>                            <span class="hljs-comment">// 这就是为什么，当浏览器发送大量数据时，就会发出多长http请求</span><br>                            pipeline.addLast(<span class="hljs-keyword">new</span> <span class="hljs-title class_">HttpObjectAggregator</span>(<span class="hljs-number">8192</span>));<br>                            <span class="hljs-comment">// 对应websocket它的数据时以帧(frame)形式传递</span><br>                            <span class="hljs-comment">// 可以看到WebSocketFrame下面有6个子类</span><br>                            <span class="hljs-comment">// 浏览器请求时，ws://localhost:7000/xxx 表示请求到url</span><br>                            pipeline.addLast(<span class="hljs-keyword">new</span> <span class="hljs-title class_">WebSocketServerProtocolHandler</span>(<span class="hljs-string">&quot;/hello&quot;</span>));<br>                            pipeline.addLast(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MyserverHandler</span>());<br>                        &#125;<br>                    &#125;);<br><br>            <span class="hljs-type">ChannelFuture</span> <span class="hljs-variable">sync</span> <span class="hljs-operator">=</span> bootstrap.bind(<span class="hljs-number">7000</span>).sync();<br>            sync.channel().closeFuture().sync();<br>        &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(e);<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            bossGroup.shutdownGracefully();<br>            workerGroup.shutdownGracefully();<br>        &#125;<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>NettyHandler</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> cn.org.alan.netty.websocket;<br><br><span class="hljs-keyword">import</span> io.netty.channel.ChannelHandlerContext;<br><span class="hljs-keyword">import</span> io.netty.channel.ChannelInboundHandlerAdapter;<br><span class="hljs-keyword">import</span> io.netty.channel.SimpleChannelInboundHandler;<br><span class="hljs-keyword">import</span> io.netty.handler.codec.http.websocketx.TextWebSocketFrame;<br><span class="hljs-keyword">import</span> io.netty.handler.codec.http.websocketx.WebSocketFrame;<br><span class="hljs-keyword">import</span> io.netty.handler.timeout.IdleStateEvent;<br><br><span class="hljs-keyword">import</span> java.time.LocalDate;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2026/1/17 下午4:21</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyserverHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">SimpleChannelInboundHandler</span>&lt;TextWebSocketFrame&gt; &#123;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">channelRead0</span><span class="hljs-params">(ChannelHandlerContext ctx, TextWebSocketFrame msg)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        System.out.println(<span class="hljs-string">&quot;服务端收到消息:&quot;</span>+msg.text() );<br>        <span class="hljs-comment">// 回复浏览器</span><br>        ctx.channel().writeAndFlush(<span class="hljs-keyword">new</span> <span class="hljs-title class_">TextWebSocketFrame</span>(<span class="hljs-string">&quot;服务器事件+&quot;</span>+ LocalDate.now()));<br>    &#125;<br>    <span class="hljs-comment">// 当web客户端连接后，触发方法</span><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">handlerAdded</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        System.out.println(<span class="hljs-string">&quot;handlerAdded被调用&quot;</span>+ctx.channel().id().asLongText());<br>        System.out.println(<span class="hljs-string">&quot;handlerAdded被调用&quot;</span>+ctx.channel().id().asShortText());<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">handlerRemoved</span><span class="hljs-params">(ChannelHandlerContext ctx)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        System.out.println(<span class="hljs-string">&quot;handlerRemoved被调用&quot;</span>+ctx.channel().id().asShortText());<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">exceptionCaught</span><span class="hljs-params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        System.out.println(<span class="hljs-string">&quot;异常发生&quot;</span>);<br>        ctx.close();<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure>]]></content>
    
  </entry>
  
  <entry>
    <title>Nacos笔记</title>
    <link href="https://alan.org.cn/posts/Nacos%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/Nacos%E7%AC%94%E8%AE%B0.html</id>
    <published>2025-12-22T14:28:50.000Z</published>
    <updated>2025-12-22T14:28:50.000Z</updated>
    
    
    <category term="微服务" scheme="https://alan.org.cn/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
    
    <category term="微服务" scheme="https://alan.org.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Nacos&quot;&gt;&lt;a href=&quot;#Nacos&quot; class=&quot;headerlink&quot; title=&quot;Nacos&quot;&gt;&lt;/a&gt;Nacos&lt;/h1&gt;&lt;p&gt;一个注册中心+配置中心的组合，Nacos支持AP和CP模型&lt;/p&gt;
&lt;p&gt;登录地址&lt;/p&gt;
&lt;figure clas</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="Nacos"><a href="#Nacos" class="headerlink" title="Nacos"></a>Nacos</h1><p>一个注册中心+配置中心的组合，Nacos支持AP和CP模型</p><p>登录地址</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">http://localhost:8848/nacos<br></code></pre></td></tr></table></figure><p>默认账户 nacos 默认密码 nacos</p><h2 id="Nacos服务提供者注册"><a href="#Nacos服务提供者注册" class="headerlink" title="Nacos服务提供者注册"></a>Nacos服务提供者注册</h2><h3 id="Pom怎么找"><a href="#Pom怎么找" class="headerlink" title="Pom怎么找"></a>Pom怎么找</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://spring.io<br></code></pre></td></tr></table></figure><p>Projects-&gt;springClound-&gt;springCloudAlibaba-&gt;Learn-&gt;Reference Doc中去查找</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-alibaba-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.0.0.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span><br></code></pre></td></tr></table></figure><h3 id="基于Nacos的服务提供者"><a href="#基于Nacos的服务提供者" class="headerlink" title="基于Nacos的服务提供者"></a>基于Nacos的服务提供者</h3><ol><li>建Module</li><li>Pom</li></ol><ul><li>父工程</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-alibaba-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.0.0.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span><br></code></pre></td></tr></table></figure><ul><li>子POM</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure><ol><li>Yml</li></ol><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs properties"><span class="hljs-attr">server.port</span>=<span class="hljs-string">8081 // 服务端口号</span><br><span class="hljs-attr">spring.application.name</span>=<span class="hljs-string">nacos-producer // 服务名称</span><br><span class="hljs-attr">spring.cloud.nacos.discovery.server-addr</span>=<span class="hljs-string">127.0.0.1:8848 // 注入到什么地方</span><br><span class="hljs-attr">management.endpoints.web.exposure.include</span>=<span class="hljs-string">* // 监控的端点全部打开</span><br></code></pre></td></tr></table></figure><ol><li>主启动</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-meta">@EnableDiscoveryClient</span> <span class="hljs-comment">// nacos要添加该注解</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NacosProviderDemoApplication</span> &#123;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        SpringApplication.run(NacosProducerDemoApplication.class, args);<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><ol><li>业务类</li><li>测试</li></ol><p>注入后可以登录nacos访问服务列表查看</p><h3 id="Nacos之服务提供者注册和负载"><a href="#Nacos之服务提供者注册和负载" class="headerlink" title="Nacos之服务提供者注册和负载"></a>Nacos之服务提供者注册和负载</h3><p>为什么nacos支持负载均衡</p><p>是因为nacos-discovery继承了ribbon,天生自带负载均衡</p><ul><li>注意</li></ul><p>如果用RestTemplate去调我们的微服务的时候，如果使用到ribbon要加**@LoadBalanced**注解</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Bean</span><br><span class="hljs-meta">@LoadBalanced</span><br><span class="hljs-keyword">public</span> RestTemplate <span class="hljs-title function_">getRestTemplate</span><span class="hljs-params">()</span>&#123;<br>  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RestTemplate</span>();<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="Nacos服务注册中心对比提升"><a href="#Nacos服务注册中心对比提升" class="headerlink" title="Nacos服务注册中心对比提升"></a>Nacos服务注册中心对比提升</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-51-35-fa73c0ce03211a437202f4b30efbd852-3dd83e.png" alt="截屏2025-12-23 下午10.18.42"></p><ul><li><p>何时选何种模式</p><ul><li>如果不需要存储服务级别的信息且服务实例是通过nacos-client注册，并能够保持心跳上报，那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务，都适用于AP模式，AP模式为了服务的可能性而减弱了一致性，因此AP模式下只支持注册临时实例。</li><li>如果需要在服务级别编辑或者存储配置信息，那么CP 是必须，K8S服务和DNS服务则适用于CP模式,CP模式下则支持注册持久化实例，此时则是以 Raft 协议为集群运行模式，该模式下注册实例之前必须先注册服务，如果服务不存在，则会返回错误。</li></ul></li><li><p>AP和CP模式的切换命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">curl -X PUT <span class="hljs-string">&#x27;$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&amp;value=CP&#x27;</span><br></code></pre></td></tr></table></figure></li></ul><h3 id="Nacos之服务配置中心"><a href="#Nacos之服务配置中心" class="headerlink" title="Nacos之服务配置中心"></a>Nacos之服务配置中心</h3><h4 id="Nacos作为配置中心的基础配置"><a href="#Nacos作为配置中心的基础配置" class="headerlink" title="Nacos作为配置中心的基础配置"></a>Nacos作为配置中心的基础配置</h4><ol><li>POM</li></ol><ul><li>父工程</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-alibaba-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.0.0.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span><br></code></pre></td></tr></table></figure><ul><li>子工程</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-config<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure><ol start="2"><li>配置文件</li></ol><p>Nacos同springcloud-config一样，在项目初始化时，要保证先从配置中心进行配置拉取拉取配置之后，才能保证项目的正常启动。springboot中配置文件的加载是存在优先级顺序的，bootstrap优先级高于application</p><p>全局的重点点放在bootstrap，自己的放在application</p><p>Bootstrap.yml</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-comment"># nacos配置</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">3377</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">nacos-config-client</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span> <span class="hljs-comment">#Nacos服务注册中心地址</span><br>      <span class="hljs-attr">config:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span> <span class="hljs-comment">#Nacos作为配置中心地址</span><br>        <span class="hljs-attr">file-extension:</span> <span class="hljs-string">yaml</span> <span class="hljs-comment">#指定yaml格式的配置</span><br><span class="hljs-comment"># $&#123;spring.application.name&#125;-$&#123;spring.profile.active&#125;.$&#123;spring.cloud.nacos.config.file-extension&#125;</span><br></code></pre></td></tr></table></figure><p>Application.yml</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">profiles:</span><br>    <span class="hljs-attr">active:</span> <span class="hljs-string">dev</span> <span class="hljs-comment"># 表示开发环境</span><br></code></pre></td></tr></table></figure><ol start="3"><li>主启动类</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.atguigu.springcloud.alibaba;<br><br><span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;<br><span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;<br><span class="hljs-keyword">import</span> org.springframework.cloud.client.discovery.EnableDiscoveryClient;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@auther</span> zzyy</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@create</span> 2020-02-10 16:51</span><br><span class="hljs-comment"> */</span><br><span class="hljs-meta">@EnableDiscoveryClient</span><br><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NacosConfigClientMain3377</span><br>&#123;<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>            SpringApplication.run(NacosConfigClientMain3377.class, args);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><ol start="4"><li>业务类：**@RefreshScope**动态刷新注解（热更新）</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.atguigu.springcloud.alibaba.controller;<br><br><span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Value;<br><span class="hljs-keyword">import</span> org.springframework.cloud.context.config.annotation.RefreshScope;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@auther</span> zzyy</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@create</span> 2020-02-10 16:55</span><br><span class="hljs-comment"> */</span><br><span class="hljs-meta">@RestController</span><br><span class="hljs-meta">@RefreshScope</span> <span class="hljs-comment">//在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ConfigClientController</span><br>&#123;<br><span class="hljs-meta">@Value(&quot;$&#123;config.info&#125;&quot;)</span><br><span class="hljs-keyword">private</span> String configInfo;<br><br><span class="hljs-meta">@GetMapping(&quot;/config/info&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">getConfigInfo</span><span class="hljs-params">()</span> &#123;<br><span class="hljs-keyword">return</span> configInfo;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>官网地址</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html<br></code></pre></td></tr></table></figure><p>dataid的格式为</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"># $&#123;spring.application.name&#125;-$&#123;spring.profile.active&#125;.$&#123;spring.cloud.nacos.config.file-extension&#125;<br></code></pre></td></tr></table></figure><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-51-35-acdc7daab260f5dd54f04ef1e871ac7c-787bb1.png" alt="img"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-51-35-c68c832130b1d098a45b8923eed58465-a1c9a1.png" alt="img"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-51-35-a469d6486f42e573040d1e5ebfdc9fd3-2357d0.png" alt="img"></p><h3 id="Nacos之命名空间、分组和DataID三者之间的关系"><a href="#Nacos之命名空间、分组和DataID三者之间的关系" class="headerlink" title="Nacos之命名空间、分组和DataID三者之间的关系"></a>Nacos之命名空间、分组和DataID三者之间的关系</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-51-35-8378ede4ed8e393c6dd6227a06d16446-59deee.png" alt="img"></p><p> 默认情况：</p><p>Namespace&#x3D;public，Group&#x3D;DEFAULT_GROUP, 默认Cluster是DEFAULT</p><p>Nacos默认的命名空间是public，Namespace主要用来实现隔离。</p><p>比方三个环境：开发、测试、生产环境，就可以创建三个Namespace，不同的Namespace之间是隔离的。</p><p>Group默认是DEFAULT_GROUP，Group可以把不同的微服务划分到同一个分组里面去</p><p>Service就是微服务；一个Service可以包含多个Cluster（集群），Nacos默认Cluster是DEFAULT，Cluster是对指定微服务的一个虚拟划分。</p><p>比方说为了容灾，将Service微服务分别部署在了不同机房，</p><p>这时就可以给不通机房的Service微服务起一个集群名称，还可以尽量让同一个机房的微服务互相调用，以提升性能。</p><p>最后是Instance，就是微服务的实例。</p><h3 id="Nacos之DataID配置"><a href="#Nacos之DataID配置" class="headerlink" title="Nacos之DataID配置"></a>Nacos之DataID配置</h3><p>指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置</p><p>默认空间+默认分组+新建dev和test两个DataID</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-51-35-e0a0c00b1fbe2df779566b99d9960979-78dc6c.png" alt="img"></p><h3 id="Nacos之Group分组方案"><a href="#Nacos之Group分组方案" class="headerlink" title="Nacos之Group分组方案"></a>Nacos之Group分组方案</h3><ul><li>在配置congfig下面添加group参数，添加<strong>分组名称</strong></li><li>分组名称是创建配置的时候手动填写的</li></ul><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-51-35-7c4c3175a81d575fc6365504efba9cf5-c52a69.png" alt="img"></p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-comment"># nacos配置</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">3377</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">nacos-config-client</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span> <span class="hljs-comment">#Nacos服务注册中心地址</span><br>      <span class="hljs-attr">config:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span> <span class="hljs-comment">#Nacos作为配置中心地址</span><br>        <span class="hljs-attr">file-extension:</span> <span class="hljs-string">yaml</span> <span class="hljs-comment">#指定yaml格式的配置</span><br>        <span class="hljs-attr">group:</span> <span class="hljs-string">groupName</span> <span class="hljs-comment"># 指定分组名称</span><br><span class="hljs-comment"># $&#123;spring.application.name&#125;-$&#123;spring.profile.active&#125;.$&#123;spring.cloud.nacos.config.file-extension&#125;</span><br></code></pre></td></tr></table></figure><h3 id="Nacos之NameSpace命名空间"><a href="#Nacos之NameSpace命名空间" class="headerlink" title="Nacos之NameSpace命名空间"></a>Nacos之NameSpace命名空间</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-51-35-4a6929593a04bbe094fab466a502b2b4-2b60cf.png" alt="img"></p><ul><li>在配置congfig下面添加namespace参数，添加<strong>命名空间的id</strong></li><li>命名空间在左侧菜单栏点击命名空间创建</li></ul><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-comment"># nacos配置</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">3377</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">nacos-config-client</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">nacos:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span> <span class="hljs-comment">#Nacos服务注册中心地址</span><br>      <span class="hljs-attr">config:</span><br>        <span class="hljs-attr">server-addr:</span> <span class="hljs-string">localhost:8848</span> <span class="hljs-comment">#Nacos作为配置中心地址</span><br>        <span class="hljs-attr">file-extension:</span> <span class="hljs-string">yaml</span> <span class="hljs-comment">#指定yaml格式的配置</span><br>        <span class="hljs-attr">group:</span> <span class="hljs-string">groupName</span> <span class="hljs-comment"># 指定分组名称</span><br>        <span class="hljs-attr">namespace:</span> <span class="hljs-string">nameSpaceID</span> <span class="hljs-comment"># 指定命名空间id</span><br></code></pre></td></tr></table></figure><h2 id="Nacos集群"><a href="#Nacos集群" class="headerlink" title="Nacos集群"></a>Nacos集群</h2><p>Coming soon…</p><h2 id="Nacos持久化"><a href="#Nacos持久化" class="headerlink" title="Nacos持久化"></a>Nacos持久化</h2><p>Coming soon…</p>]]></content>
    
  </entry>
  
  <entry>
    <title>分布式事务</title>
    <link href="https://alan.org.cn/posts/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1.html"/>
    <id>https://alan.org.cn/posts/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1.html</id>
    <published>2025-12-21T10:38:38.000Z</published>
    <updated>2025-12-21T10:38:38.000Z</updated>
    
    
    <category term="分布式" scheme="https://alan.org.cn/categories/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
    
    <category term="分布式" scheme="https://alan.org.cn/tags/%E5%88%86%E5%B8%83%E5%BC%8F/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;分布式事务&quot;&gt;&lt;a href=&quot;#分布式事务&quot; class=&quot;headerlink&quot; title=&quot;分布式事务&quot;&gt;&lt;/a&gt;分布式事务&lt;/h1&gt;&lt;h2 id=&quot;基础概念&quot;&gt;&lt;a href=&quot;#基础概念&quot; class=&quot;headerlink&quot; title=&quot;基础概念&quot;</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="分布式事务"><a href="#分布式事务" class="headerlink" title="分布式事务"></a>分布式事务</h1><h2 id="基础概念"><a href="#基础概念" class="headerlink" title="基础概念"></a>基础概念</h2><h3 id="什么是事务"><a href="#什么是事务" class="headerlink" title="什么是事务"></a>什么是事务</h3><p>一次大的活动，它由不同的小活动组成，这些活动要么全成功，要么全失败</p><h3 id="本地事物"><a href="#本地事物" class="headerlink" title="本地事物"></a>本地事物</h3><p>本地中，大多数是通过关系性数据库来控制事务，这种事利用数据库本身特性实现，因此叫<strong>数据库事务</strong>。</p><p>由于应用主要靠关系数据库来控制事务，而数据库通常和应用在同一个服务器，所以基于<strong>关系型数据库的事务</strong>又称为<strong>本地事物</strong>。</p><p><strong>数据库四大事务特性（ACID）</strong></p><ul><li>原子性：构成事务的所有操作，要么都执行完成，要么全部不执行，不可能出现部分成功部分失败的情况。</li><li>一致性：事务执行前后，数据库的一致性约束没有被破坏。</li><li>隔离性：数据库中的事务一般都是并发的，隔离性是指并发的两个事务的执行互不干扰。一个事务不能看到其他事务运行过程的中间状态。通过配置事务隔离级别可以避脏读，重复读问题。</li><li>持久性：事务完成之后，该事务对数据库的更改会被持久化到数据库，且不会被回滚。</li></ul><p>数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个不可分割的执行单元，该执行单元中的所有操作要么都成功，要么都失败，只要其中任一操作执行失败，都将导致整个事务的回滚</p><h3 id="分布式事务-1"><a href="#分布式事务-1" class="headerlink" title="分布式事务"></a>分布式事务</h3><p>分布式系统将一个系统拆分成可独立部署的多个服务，服务于服务之间需要远程协作才能完成事务操作。</p><p>这种分布式系统下的不同服务之间通过网络远程协作完成的事务称为<strong>分布式事务</strong></p><h3 id="会分布式事务产生的场景"><a href="#会分布式事务产生的场景" class="headerlink" title="会分布式事务产生的场景"></a>会分布式事务产生的场景</h3><ol><li>典型场景就是微服务架构：跨JVM进程产生分布式事务</li><li>单体系统访问多个数据库：跨数据库实例产生分布式事务</li><li>多服务访问同一个数据库实例：跨JVM进程产生分布式事务</li></ol><h2 id="分布式事务基础理论"><a href="#分布式事务基础理论" class="headerlink" title="分布式事务基础理论"></a>分布式事务基础理论</h2><h3 id="CAP理论"><a href="#CAP理论" class="headerlink" title="CAP理论"></a>CAP理论</h3><h4 id="理解CAP"><a href="#理解CAP" class="headerlink" title="理解CAP"></a>理解CAP</h4><p>CAP表示：一致性、可用性、分区容忍性</p><ul><li><p>一致性（C）：写操作后的读操作可以读取到最新的数据状态，当数据分布在多个节点上，从任意节点读取到的数据都是最新的状态</p><ul><li>如何实现一致性<ul><li>写入主数据库后将数据同步到从数据库</li><li>写入主数据库后在向从数据库期间要将从数据库锁定，待同步完成后在释放锁，以免在新数据写入成功后，向从数据库查询到旧的数据。</li></ul></li><li>分布式系统一致性的特点：<ul><li>由于存在数据同步的过程，写操作的响应会有一定的延迟。</li><li>为了保证数据一致性会对资源暂时锁定，待数据同步完成释放锁定资源。</li><li>如果请求数据同步失败的节点则会返回错误信息，<strong>一定不会返回旧信息</strong>。</li></ul></li></ul></li><li><p>可用性（A）：事务操作都可以得到响应结果，且不会出现响应超时或响应错误</p><ul><li>如何实现：<ul><li>从数据库接收到数据查询到请求则立即能够响应数据查询结果</li><li>从数据库不允许出现响应超时或响应错误</li></ul></li><li>如何实现可用性<ul><li>写入主数据库后要将数据同步到从数据库</li><li>由于要保证从数据库的可用性，不可将从数据库中的资源进行锁定</li><li>即使数据还没有同步过来，从数据库也要返回查询的数据，<strong>哪怕上旧数据</strong>，如果连旧数据也没有则可用按照约定返回一个默认信息，但不能返回错误或响应超时。</li></ul></li><li>分布式系统可用性的特点：<ul><li>所有请求都有响应，且不会出现响应超时或响应错误</li></ul></li></ul></li><li><p>分区容忍性（P）：网络分区不可避免网络问题导致通信失败，但此时任何对外提供服务，这叫分区容忍性。</p><ul><li>如何实现<ul><li>主数据库向从数据库同步数据失败不影响读写操作</li><li>其一个节点挂掉不影响另一个节点对外提供服务</li></ul></li><li>如何实现分区容忍性<ul><li>尽量使用异步取代同步问题，例如使用异步方式将数据从主数据库同步到从数据库，这样节点之间能有效的实现松耦合。</li><li>添加从数据库节点，其中一个从节点挂掉其他从节点提供服务</li></ul></li><li>分布式系统分区容忍性的特点：<ul><li>分区容忍性上分布式系统具备的基本功能</li></ul></li></ul></li></ul><h4 id="CAP组合方式"><a href="#CAP组合方式" class="headerlink" title="CAP组合方式"></a>CAP组合方式</h4><p><strong>在所有分布式事务场景中不会同时具备CAP三个特性，因为在具备了P的前提下C和A上不能共存的。</strong></p><p><strong>分区容忍性的含义是：</strong></p><ol><li>主数据库通过网络向从数据库同步数据，可用认为主从数据库部署在不同分区，通过网络进行交互。</li><li>当主数据库和从数据库之间的网络出现问题，不影响主数据库和从数据库对外提供服务</li><li>其一一个节点挂掉不影响另一个节点对外提供服务。</li></ol><p>如果要实现一致性，则为了保证数据的一致会上锁，如果同步失败要返回错误信息和超时信息</p><p>如果要实现可用性，则不管什么时候都可以查询到数据，而且不会响应超时信息或返回错误信息</p><p>通过分析发现在瞒住P的前提下C和A存在矛盾性</p><p><strong>CAP的组合方式：</strong></p><ol><li><p>AP</p><p>放弃一致性，追求可用性和分区容忍性，这是分布式系统<strong>最常用</strong>的选择</p></li><li><p>CP</p><p>放弃可用性，追求一致性和分区容忍性，我们的zookeeper就是追求的强一致性。</p></li><li><p>CA</p><p>放弃分区容忍性，即不进行分区，不考虑由于网络不通或节点挂掉的问题，则可以实现一致性和可用性，那么这个系统不是一个标准的分布式系统，关系性数据库就可以满足CA</p></li></ol><h4 id="BASE理论"><a href="#BASE理论" class="headerlink" title="BASE理论"></a>BASE理论</h4><ol><li><p>理解强一致性和最终一致性</p><p>CAP中的一致性要求在任何时间查询每个节点数据都必须一致，它强调的是强一致性</p><p>最终一致性是允许可以在一段时间内每个节点的数据不一致，但是经过一段时候后数据必须一致。</p></li><li><p>BASE理论介绍</p><p>Base是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。是对CAP中AP的一个扩展，通过系统强一致性来获得可用性，当出现故障允许部分不可用但要保证核心功能可用，允许数据在一段时间内是不一致的，但最终达到一致状态。满足BASE理论的事务，我们称为“柔性事务”</p><ul><li>基本可用：系统故障时，允许损失部分可用功能，保证核心功能可用</li><li>软状态：由于不要求强一致，所以允许系统中存在中间状态（也叫状态），这个状态不影响系统可用性</li><li>最终一致：指经过一段时间，所有节点数据都将达成一致</li></ul></li></ol><h2 id="分布式事务解决方案"><a href="#分布式事务解决方案" class="headerlink" title="分布式事务解决方案"></a>分布式事务解决方案</h2><p>业内最常见的方案有<strong>2PC、TCC、可靠消息最终一致性、最大努力通知</strong>这几种。</p><h3 id="分布式事务解决方案之2PC（两阶段提交）"><a href="#分布式事务解决方案之2PC（两阶段提交）" class="headerlink" title="分布式事务解决方案之2PC（两阶段提交）"></a>分布式事务解决方案之2PC（两阶段提交）</h3><h4 id="什么是2PC"><a href="#什么是2PC" class="headerlink" title="什么是2PC"></a>什么是2PC</h4><p>2PC即两阶段提交协议，是将整个事务流程分为两个阶段，分别是准备阶段（P Prepare phase)和提交阶段(C Commit phase)</p><p>整个事务过程由事务管理器和参与者组成，事务管理器决定整个分布式事务的提交和回滚，事务参与者负责自己本地事物的提交和回滚。</p><p>在关系性数据库Oracle和Mysql支持两阶段提交协议</p><ol><li><p>准备阶段：事务管理器给每个参与者发送准备（Prepare）消息，每个数据库参与者在本地执行事务，并写入本地Undo&#x2F;Redo日志，此时事务没有提交。</p><p>(Undo日志是记录修改前的数据，用于数据库回滚，Redo日志是记录修改后的数据，用于提交事务后写入数据文件)</p></li></ol><ul><li>提交阶段：如果事务管理器收到了参与者的执行失败或者超时消息时，直接给每个参与者发送回滚(Rollback)消息；否则，发送提交(Commit)消息，；参与者根据事务管理器的指令执行提交或回滚操作，并释放事务处理过程中使用的锁资源。注意：必须在最后阶段释放锁资源。</li></ul><h3 id="分布式事务解决方案之TCC"><a href="#分布式事务解决方案之TCC" class="headerlink" title="分布式事务解决方案之TCC"></a>分布式事务解决方案之TCC</h3><h4 id="什么是TCC"><a href="#什么是TCC" class="headerlink" title="什么是TCC"></a>什么是TCC</h4><p>TCC是Try、Confirm、Cancel三个词语的缩写，TCC要求每个分支事务实现三个操作：预处理Try，确认Confirm，撤销Cancel。</p><ul><li>Try操作：做业务检查及资源预留</li><li>Confirm操作：做业务确认操作</li><li>Cancel操作：实现一个与Try相反的操作及回滚操作。</li></ul><p>TM首先发起所有的分布式事务的try操作，任何一个分支事务的try操作执行失败，TM将会发起所有分支事务的Cancel操作，若try操作全部成功，TM将会发起所有分支事务的Confirm操作，其中Confirm&#x2F;Cancel操作若执行失败，TM会进行重试。</p><p><strong>TCC分为是哪个阶段：</strong></p><ol><li><p>Try阶段是做业务检查（一致性）及资源预留（隔离），此阶段仅是一个初步操作，他它和后续的Confirm一起才能真正构成一个完整的业务逻辑。</p></li><li><p>Confirm阶段是做确认提交，Try阶段所有分支事务执行成功后开始执行Confirm。通常情况下，<strong>采用TCC则认为Confirm阶段是不会出错的。</strong>即：只要Try成功，Confirm一定成功。若Confirm阶段真的出错了，需要引入重试机制或人工处理。</p></li><li><p>Cancel阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消，预留资源释放。通常情况下，采用TCC则认为Cancel阶段也是一定成功的。若Cancel阶段真多出错了，，需要引入重试机制或人工处理。</p></li><li><p>TM事务管理器</p><p>TM事务管理器可用实现为独立的服务，也可以让<strong>全局事务发起方</strong>充当TM的角色，TM独立出来是为了成为公用组件，是为了考虑系统解结构和软件复用</p><p>TM在发起全局事务时生成全局事务记录，全局事务ID贯穿整个分布式事务调用链条，用来记录事务上下文，追踪和记录状态，由于Confirm和Cancel失败需要进行重试，因此需要实现为幂等，<strong>幂等性是指同一个操作无论请求多少次，其结果都相同。</strong></p></li></ol><h3 id="分布式事务解决方案之可靠消息最终一致性"><a href="#分布式事务解决方案之可靠消息最终一致性" class="headerlink" title="分布式事务解决方案之可靠消息最终一致性"></a>分布式事务解决方案之可靠消息最终一致性</h3><h4 id="什么是可靠消息最终一致性"><a href="#什么是可靠消息最终一致性" class="headerlink" title="什么是可靠消息最终一致性"></a>什么是可靠消息最终一致性</h4><p>可靠消息最终一致性方案是指当事务发起方执行完成本地事物后并发出一条消息，事务参与方（消息消费者）一定能够接收到消息并处理事务成功，此方案强调的是只要消息发给事务参与方最终事务要达到一致</p><p>事务发起方（消息生产方）将消息发给消息中间件，事务参与方从消息中间件接收消息，事务发起方和消息中间件之间，事务参与方（消息消费方）和中间件之间都是通过网络通信，由于网络通信的不确定性会导致分布式事务问题</p><p>因此可靠消息最终一致性方案要解决一下几个问题：</p><ol><li><p>本地事物与消息发送的原子性问题</p><p>本地事物与消息发送到原子性问题即：事务发起方在本地事物执行成功后消息必须发出去，否则就丢弃消息。即实现本地事物和消息发送到原子性，要么都成功，要么都失败。本地事物与消息发送到原子性问题是实现可靠消息最终一致性方案的关键问题</p></li><li><p>事务参与方接收消息的可靠性</p><p>事务参与方必须能够从消息队列接收到消息，如果接收消息失败可以重复接收消息</p></li><li><p>消息重复消费的问题</p><p>由于网络的存在，若某一个消费节点超时但是消费成功，此时消息中间件会重复投递此消息，就导致了消息的重复消费。</p></li></ol><h3 id="分布式事务解决方案之最大努力通知"><a href="#分布式事务解决方案之最大努力通知" class="headerlink" title="分布式事务解决方案之最大努力通知"></a>分布式事务解决方案之最大努力通知</h3><h4 id="什么是最大努力通知"><a href="#什么是最大努力通知" class="headerlink" title="什么是最大努力通知"></a>什么是最大努力通知</h4><ul><li><p>目标</p><p>发起通知方通过一定的机制最大努力将业务处理结果通知到接收方。</p></li><li><p>具体包括</p><ol><li><p>有一定的消息<strong>重复</strong>通知机制（通知异常的情况）</p><p>因为接收到通知方可能没有接收到通知，此时要有一定的机制对消息重复通知。</p></li><li><p>消息<strong>校对</strong>机制（查询接口，可以主动校验）</p><p>如果尽最大努力也没有通知到接收方，或者接收方消费消息后要再次消费，此时可由接收方主动向通知方查询消息信息未来满足需求</p></li></ol></li><li><p>最大努力通知与可靠消息一致性有什么不同？</p><ul><li>解决方案思想不同<ul><li>可靠消息一致性：发起通知方需要保证将消息发出去，并且将消息发到接收通知方，消息的可靠性关键由发起通知方来保证。</li><li>最大努力通知：发起通知方尽最大的努力将业务处理结果通知为接收通知方，但是可能消息接收不到，此时需要接收通知方主动调用发起通知方的借口查询业务处理结果，通知的可靠性关键在接收通知方。</li></ul></li><li>两者的业务应用场景不同<ul><li>可靠消息一致性关注的是交易过程的事务一致，以异步的方式完成交易。</li><li>最大努力通知关注的是交易后的通知事务，即将交易结果可靠的通知出去。</li></ul></li><li>技术解决方向不同<ul><li>可靠消息一致性要解决消息从发出到接收到一致性，即消息发出并且被接收到。</li><li>最大努力通知无法保证消息从发出到接收到一致性，只提供消息接收的可靠性机制。可靠机制是，最大努力通知的将消息通知给接收方，当消息<strong>无法被接收</strong>方接收时，由接收方<strong>主动查询消息</strong>（业务处理结果）</li></ul></li></ul></li></ul><p>Coming soon…</p>]]></content>
    
  </entry>
  
  <entry>
    <title>销售圣经读书笔记</title>
    <link href="https://alan.org.cn/posts/%E9%94%80%E5%94%AE%E5%9C%A3%E7%BB%8F%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/%E9%94%80%E5%94%AE%E5%9C%A3%E7%BB%8F%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.html</id>
    <published>2025-12-21T07:15:19.000Z</published>
    <updated>2025-12-21T07:15:19.000Z</updated>
    
    
    <category term="读书笔记" scheme="https://alan.org.cn/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="读书笔记" scheme="https://alan.org.cn/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;销售圣经读书笔记&quot;&gt;&lt;a href=&quot;#销售圣经读书笔记&quot; class=&quot;headerlink&quot; title=&quot;销售圣经读书笔记&quot;&gt;&lt;/a&gt;销售圣经读书笔记&lt;/h1&gt;&lt;h2 id=&quot;10-5条戒律&quot;&gt;&lt;a href=&quot;#10-5条戒律&quot; class=&quot;header</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="销售圣经读书笔记"><a href="#销售圣经读书笔记" class="headerlink" title="销售圣经读书笔记"></a>销售圣经读书笔记</h1><h2 id="10-5条戒律"><a href="#10-5条戒律" class="headerlink" title="10.5条戒律"></a>10.5条戒律</h2><ol><li>思考</li><li>信仰</li><li>交流</li><li>发现</li><li>提问</li><li>观察</li><li>勇气</li><li>承担</li><li>赢取</li><li>佐证</li></ol><p>10.5 日积月累</p><h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><p>交易在你的脑袋里。</p><ul><li>总结</li></ul><p>心态决定最终的成败，调整心态做到胸有成竹。友好、微笑、热情、积极、自信、沉着、有亲和力、充分准备以至于都感觉不到紧张。早在最终成交之前，销售计划一早就在你的头脑里形成。</p><ul><li>案例</li></ul><p>Coming soon…</p><ul><li>行动</li></ul><p>在接下来的100次约访中，记录可能发生的好事及期待的结果。约访结束记录可以可以改善的地方。</p><h1 id="信仰"><a href="#信仰" class="headerlink" title="信仰"></a>信仰</h1><p>创建一个四位一体、坚不可摧的信仰体系。</p><ul><li>总结<ul><li>相信你的公司</li><li>相信你的产品</li><li>相信你自己</li><li>相信你的产品会让客户生活更美好</li></ul></li><li>案例</li></ul><p>Coming soon…</p><ul><li>行动</li></ul><p>记下四个为什么：</p><ol><li>为什么我会相信我的公司</li><li>为什么我会相信我的产品</li><li>为什么我会相信我自己</li><li>为什么我认为购买我的产品能改善客户的生活</li></ol><h2 id="交流"><a href="#交流" class="headerlink" title="交流"></a>交流</h2><p>除非已经建立良好的个人关系，否则不要涉及销售话题。</p><ul><li>总结</li></ul><p>运用诱敌深入原则。设法让潜在客户对你的谈话感兴趣，让他们围着你转。可以以提问的方式开始交流，设法让他们开心起来，与他们建立朋友或者更亲密的私人关系，甚至如果可能的话，找到共同点。</p><ul><li>案例</li></ul><p>Coming soon…</p><ul><li>行动</li></ul><p>回顾最近的交易，记录交易如何开始。当时的气氛是否友好？在销售环节你是否感觉不自在？不管是登门拜访还是电话约访，非正式但有意义话题都是合适的开场白。</p><ul><li>熟练掌握</li></ul><p>用一句话记录下每次约访的开场白。在哪些最终达成交易的约访中，销售人员与客户都建立了良好的个人关系，甚至找到了共同点。</p><ul><li>完全掌握</li></ul><p>如果能找到了与客户的共同点，而且每次电话约访或登门拜访时彼此都能开怀大笑，那么这就能说明你已经掌握了建立个人关系的技巧。这样做的目的并不仅是为了拿下当前这笔交易，同时还在于建立个人有一及关系网，而这在未来将转化成更多的交易和推荐机会。</p><ul><li>发现</li></ul><p>客户购物是基于自己的理由，而非你的理由，因此需要首先确定他们的理由。</p><ul><li>总结</li></ul><p>换个说法，即”人们不喜欢被推销，但喜欢购物“。销售成败的细微差别正是在于是否可以找到客户因何购买。”他们为什么买“远比”如何卖“更重要</p><ul><li>建议</li></ul><ol><li>购买动机</li><li>提问有关动机的问题</li><li>过往经历</li><li>专业技能</li><li>智慧</li><li>需求</li><li>欲望</li><li>占有欲</li><li>竞争优势</li><li>想要解决</li><li>补救</li><li>激情</li><li>恐惧</li><li>贪欲</li><li>虚荣心</li><li>表现欲</li><li>平静</li><li>预期结果</li><li>未曾言尽的风险</li></ol><ul><li>行动</li></ul><p>参考上面的，看一下有多少适用于你及你的销售经理。从最实用的开始，从下一个电话约访或登门拜访就开始付诸实施。看看你都能发现什么动机。</p><h2 id="提问"><a href="#提问" class="headerlink" title="提问"></a>提问</h2><p>错误的提问引出错误的答案。</p><ul><li>事实<ul><li>提问是销售的核心</li><li>提问促成了由销售人员推销想客户主动购买的转变</li><li>提问有助于揭露真实的购买过程和动机</li><li>有关提问的多数现有论述都是错误的—不过，我希望你的竞争对手就是在通过他们学习提问。</li></ul></li><li>永远不要直接问：“你看重什么？”，而是要通过提出其他问题迂回寻找这一问题的答案。<ul><li>在向客户或潜在客户提出问题前，可先试着问一下自己。</li><li>如果你不能回答下列问题，那么请先不要开始约访。</li><li>他们的业务受何影响？</li><li>他们去年过得怎么样？或他们现在过得怎么样？</li><li>购买和使用你的产品后，他们能如何提高赢利或产量？</li><li>促使他们购买的可能动机都有哪些？</li><li>跟你对话的人是谁？决策者还是需要事事请示的雇员？</li><li>他们当前的迫切需求是什么？</li><li>他们都有什么经理？</li></ul></li><li>行动</li></ul><p>下次销售约访时</p><p>​带上十个精心准备的问题。</p><p>​带上堆他们的背景调查。</p><p>​带上对他们有帮组的好想法。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>VO、BO、PO、DTO、DO含义整理</title>
    <link href="https://alan.org.cn/posts/VO%E3%80%81BO%E3%80%81PO%E3%80%81DTO%E3%80%81DO%E5%90%AB%E4%B9%89%E6%95%B4%E7%90%86.html"/>
    <id>https://alan.org.cn/posts/VO%E3%80%81BO%E3%80%81PO%E3%80%81DTO%E3%80%81DO%E5%90%AB%E4%B9%89%E6%95%B4%E7%90%86.html</id>
    <published>2025-12-21T02:19:40.000Z</published>
    <updated>2025-12-21T02:19:40.000Z</updated>
    
    
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;VO、BO、PO、DTO、DO含义整理&quot;&gt;&lt;a href=&quot;#VO、BO、PO、DTO、DO含义整理&quot; class=&quot;headerlink&quot; title=&quot;VO、BO、PO、DTO、DO含义整理&quot;&gt;&lt;/a&gt;VO、BO、PO、DTO、DO含义整理&lt;/h1&gt;&lt;h3 i</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="VO、BO、PO、DTO、DO含义整理"><a href="#VO、BO、PO、DTO、DO含义整理" class="headerlink" title="VO、BO、PO、DTO、DO含义整理"></a>VO、BO、PO、DTO、DO含义整理</h1><h3 id="一、先放结论"><a href="#一、先放结论" class="headerlink" title="一、先放结论"></a>一、先放结论</h3><blockquote><p>它们都是“为了隔离变化”而诞生的马甲</p></blockquote><table><thead><tr><th>缩写</th><th>英文全称</th><th>中文直译</th><th>出现位置</th><th>核心目的</th></tr></thead><tbody><tr><td>PO</td><td>Persistent Object</td><td>持久化对象</td><td>数据库 ↔ 代码</td><td>一张表一行记录的直接映射</td></tr><tr><td>DO</td><td>Domain Object</td><td>领域对象</td><td>核心业务逻辑层</td><td>充血模型，封装业务行为</td></tr><tr><td>BO</td><td>Business Object</td><td>业务对象</td><td>应用&#x2F;服务层</td><td>聚合多个DO，面向用例编排</td></tr><tr><td>DTO</td><td>Data Transfer Object</td><td>数据传输对象</td><td>进程&#x2F;服务间</td><td>精简字段，抗网络延迟</td></tr><tr><td>VO</td><td>View Object</td><td>视图对象</td><td>控制层 ↔ 前端</td><td>展示友好，防敏感字段泄露</td></tr></tbody></table><p>一句话总结： <strong>PO 管存储，DO 管业务，BO 管编排，DTO 管网络，VO 管界面。</strong></p><p>下面上代码，咱们边喝奶茶边讲。</p><hr><h3 id="二、业务场景"><a href="#二、业务场景" class="headerlink" title="二、业务场景"></a>二、业务场景</h3><blockquote><p>用户下一单“芋泥波波奶茶”</p></blockquote><p>需求：</p><ol><li>用户选好规格（大杯、少冰、五分糖）。</li><li>点击“提交订单”，前端把数据发过来。</li><li>后端算价格、扣库存、落库，返回“订单创建成功”页面。</li></ol><p>整条链路里，我们到底需要几个对象？</p><hr><h3 id="三、从数据库开始：PO"><a href="#三、从数据库开始：PO" class="headerlink" title="三、从数据库开始：PO"></a>三、从数据库开始：PO</h3><blockquote><p>PO是<code>Persistent Object</code>的简写 PO 就是“一行数据一个对象”，字段名、类型和数据库保持一一对应，不改表就不改它。</p></blockquote><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs kotlin">kotlin<br><br> 体验AI代码助手<br> 代码解读<br>复制代码<span class="hljs-comment">// 表：t_order</span><br><span class="hljs-meta">@Data</span><br><span class="hljs-meta">@TableName(<span class="hljs-string">&quot;t_order&quot;</span>)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderPO</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">Long</span> id;          <span class="hljs-comment">// 主键</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">Long</span> userId;      <span class="hljs-comment">// 用户ID</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">Long</span> productId;   <span class="hljs-comment">// 商品ID</span><br>    <span class="hljs-keyword">private</span> String sku;       <span class="hljs-comment">// 规格JSON</span><br>    <span class="hljs-keyword">private</span> BigDecimal price; <span class="hljs-comment">// 原价</span><br>    <span class="hljs-keyword">private</span> BigDecimal payAmount; <span class="hljs-comment">// 实付</span><br>    <span class="hljs-keyword">private</span> Integer status;   <span class="hljs-comment">// 订单状态</span><br>    <span class="hljs-keyword">private</span> LocalDateTime createTime;<br>    <span class="hljs-keyword">private</span> LocalDateTime updateTime;<br>&#125;<br></code></pre></td></tr></table></figure><p>注意：PO 里<strong>绝不能出现业务方法</strong>，它只是一个“数据库搬运工”。</p><hr><h3 id="四、核心业务：DO"><a href="#四、核心业务：DO" class="headerlink" title="四、核心业务：DO"></a>四、核心业务：DO</h3><blockquote><p>DO 是“有血有肉的对象”，它把业务规则写成方法，让代码自己说话。</p></blockquote><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs csharp">csharp<br><br> 体验AI代码助手<br> 代码解读<br>复制代码<span class="hljs-comment">// 领域对象：订单</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OrderDO</span> &#123;<br><br>    <span class="hljs-keyword">private</span> Long id;<br>    <span class="hljs-keyword">private</span> UserDO user;      <span class="hljs-comment">// 聚合根</span><br>    <span class="hljs-keyword">private</span> MilkTeaDO milkTea; <span class="hljs-comment">// 商品</span><br>    <span class="hljs-keyword">private</span> SpecDO spec;      <span class="hljs-comment">// 规格</span><br>    <span class="hljs-keyword">private</span> Money price;      <span class="hljs-comment">// Money是值对象，防精度丢失</span><br>    <span class="hljs-keyword">private</span> OrderStatus status;<br><br>    <span class="hljs-comment">// 业务方法：计算最终价格</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> Money <span class="hljs-title">calcFinalPrice</span>()</span> &#123;<br>        <span class="hljs-comment">// 会员折扣</span><br>        Money discount = user.getVipDiscount();<br>        <span class="hljs-comment">// 商品促销</span><br>        Money promotion = milkTea.getPromotion(spec);<br>        <span class="hljs-keyword">return</span> price.minus(discount).minus(promotion);<br>    &#125;<br><br>    <span class="hljs-comment">// 业务方法：下单前置校验</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">checkBeforeCreate</span>()</span> &#123;<br>        <span class="hljs-keyword">if</span> (!milkTea.hasStock(spec)) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> BizException(<span class="hljs-string">&quot;库存不足&quot;</span>);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>DO 可以引用别的 DO，形成聚合根。它不关心数据库，也不关心网络。</p><hr><h3 id="五、面向用例：BO"><a href="#五、面向用例：BO" class="headerlink" title="五、面向用例：BO"></a>五、面向用例：BO</h3><blockquote><p>BO 是“场景大管家”，把多个 DO 攒成一个用例，常出现在 Service 层。</p></blockquote><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs scss">scss<br><br> 体验AI代码助手<br> 代码解读<br>复制代码<span class="hljs-keyword">@Service</span><br>public class OrderBO &#123;<br><br>    <span class="hljs-keyword">@Resource</span><br>    private OrderRepository orderRepository; <span class="hljs-comment">// 操作PO</span><br>    <span class="hljs-keyword">@Resource</span><br>    private InventoryService inventoryService; <span class="hljs-comment">// RPC或本地</span><br>    <span class="hljs-keyword">@Resource</span><br>    private PaymentService paymentService;<br><br>    <span class="hljs-comment">// 用例：下单</span><br>    <span class="hljs-keyword">@Transactional</span><br>    public OrderDTO createOrder(CreateOrderDTO cmd) &#123;<br><br>        <span class="hljs-comment">// 1. 构建DO</span><br>        OrderDO <span class="hljs-attribute">order</span> = OrderAssembler<span class="hljs-selector-class">.toDO</span>(cmd);<br><br>        <span class="hljs-comment">// 2. 执行业务校验</span><br>        <span class="hljs-attribute">order</span><span class="hljs-selector-class">.checkBeforeCreate</span>();<br><br>        <span class="hljs-comment">// 3. 聚合逻辑：扣库存、算价格</span><br>        inventoryService<span class="hljs-selector-class">.lock</span>(order.getSpec());<br>        Money payAmount = <span class="hljs-attribute">order</span><span class="hljs-selector-class">.calcFinalPrice</span>();<br><br>        <span class="hljs-comment">// 4. 落库</span><br>        OrderPO po = OrderAssembler<span class="hljs-selector-class">.toPO</span>(order, payAmount);<br>        orderRepository<span class="hljs-selector-class">.save</span>(po);<br><br>        <span class="hljs-comment">// 5. 返回给前端需要的数据</span><br>        return OrderAssembler<span class="hljs-selector-class">.toDTO</span>(po);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>BO 的核心是<strong>编排</strong>，它把 DO、外部服务、PO 串成一个完整的业务动作。</p><hr><h3 id="六、跨进程-服务：DTO"><a href="#六、跨进程-服务：DTO" class="headerlink" title="六、跨进程&#x2F;服务：DTO"></a>六、跨进程&#x2F;服务：DTO</h3><blockquote><p>DTO 是“网络快递员”，字段被压缩成最少，只带对方需要的数据。</p></blockquote><p>1）<strong>入口 DTO</strong>：前端 → 后端</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs kotlin">kotlin<br><br> 体验AI代码助手<br> 代码解读<br>复制代码<span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CreateOrderDTO</span> &#123;<br>    <span class="hljs-meta">@NotNull</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">Long</span> userId;<br>    <span class="hljs-meta">@NotNull</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">Long</span> productId;<br>    <span class="hljs-meta">@Valid</span><br>    <span class="hljs-keyword">private</span> SpecDTO spec; <span class="hljs-comment">// 规格</span><br>&#125;<br></code></pre></td></tr></table></figure><p>2）<strong>出口 DTO</strong>：后端 → 前端</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs kotlin">kotlin<br><br> 体验AI代码助手<br> 代码解读<br>复制代码<span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderDTO</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-built_in">Long</span> orderId;<br>    <span class="hljs-keyword">private</span> String productName;<br>    <span class="hljs-keyword">private</span> BigDecimal payAmount;<br>    <span class="hljs-keyword">private</span> String statusDesc;<br>    <span class="hljs-keyword">private</span> LocalDateTime createTime;<br>&#125;<br></code></pre></td></tr></table></figure><p>DTO 的字段命名常带 UI 友好词汇（如 <code>statusDesc</code>），并且<strong>绝不暴露敏感字段</strong>（如 <code>userId</code> 在返回给前端时可直接省略）。</p><hr><h3 id="七、最后一步：VO"><a href="#七、最后一步：VO" class="headerlink" title="七、最后一步：VO"></a>七、最后一步：VO</h3><blockquote><p>VO 是“前端专属快递”，字段可能二次加工，甚至带 HTML 片段。</p></blockquote><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs typescript">typescript<br><br> 体验<span class="hljs-variable constant_">AI</span>代码助手<br> 代码解读<br>复制代码<span class="hljs-meta">@Data</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderVO</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-title class_">String</span> orderId; <span class="hljs-comment">// 用字符串避免 JS long 精度丢失</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title class_">String</span> productImage; <span class="hljs-comment">// 带 CDN 前缀</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title class_">String</span> priceText; <span class="hljs-comment">// 已格式化为“￥18.00”</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-title class_">String</span> statusTag; <span class="hljs-comment">// 带颜色：green/red</span><br>&#125;<br></code></pre></td></tr></table></figure><p>VO 通常由前端同学自己写 TypeScript&#x2F;Java 类，后端只负责给 DTO，再让前端 BFF 层转 VO。如果你用 Node 中间层或 Serverless，VO 就出现在那儿。</p><hr><h3 id="八、一张图记住流转过程"><a href="#八、一张图记住流转过程" class="headerlink" title="八、一张图记住流转过程"></a>八、一张图记住流转过程</h3><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs scss">scss<br><br> 体验AI代码助手<br> 代码解读<br>复制代码前端页面<br>   │ JSON<br>   ▼<br>CreateOrderVO (前端 TS)<br>   │ 序列化<br>   ▼<br>CreateOrderDTO (后端入口)<br>   │ BO<span class="hljs-selector-class">.createOrder</span>()<br>   ▼<br>OrderDO (充血领域模型)<br>   │ 聚合、计算<br>   ▼<br>OrderPO (落库)<br>   │ MyBatis<br>   ▼<br>数据库<br></code></pre></td></tr></table></figure><p>返回时反向走一遍：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java">java<br><br> 体验AI代码助手<br> 代码解读<br>复制代码数据库<br>   │ SELECT<br>OrderPO<br>   │ 转换<br>OrderDTO<br>   │ JSON<br><span class="hljs-title function_">OrderVO</span> <span class="hljs-params">(前端 TS 渲染)</span><br></code></pre></td></tr></table></figure><hr><h3 id="九、常见疑问答疑"><a href="#九、常见疑问答疑" class="headerlink" title="九、常见疑问答疑"></a>九、常见疑问答疑</h3><ol><li><strong>为什么 DO 和 PO 不合并？</strong> 数据库加索引、加字段不影响业务；业务改规则不改表结构。隔离变化。</li><li><strong>DTO 和 VO 能合并吗？</strong> 小项目可以，但一上微服务或多端（App、小程序、管理后台），立马爆炸。比如后台需要用户手机号，App 不需要，合并后前端会拿到不该看的数据。</li><li><strong>BO 和 Service 有什么区别？</strong> BO 更贴近用例，粒度更粗。Service 可能细分读写、缓存等。命名随意，关键看团队约定。</li></ol><hr><h3 id="十、一句话背下来"><a href="#十、一句话背下来" class="headerlink" title="十、一句话背下来"></a>十、一句话背下来</h3><blockquote><p>数据库里叫 PO，业务里是 DO，编排靠 BO，网络走 DTO，前端看 VO。</p></blockquote>]]></content>
    
  </entry>
  
  <entry>
    <title>IDEA常用快捷键</title>
    <link href="https://alan.org.cn/posts/IDEA%E5%B8%B8%E7%94%A8%E5%BF%AB%E6%8D%B7%E9%94%AE.html"/>
    <id>https://alan.org.cn/posts/IDEA%E5%B8%B8%E7%94%A8%E5%BF%AB%E6%8D%B7%E9%94%AE.html</id>
    <published>2025-12-21T02:18:13.000Z</published>
    <updated>2025-12-21T02:18:13.000Z</updated>
    
    
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;IDEA常用快捷键&quot;&gt;&lt;a href=&quot;#IDEA常用快捷键&quot; class=&quot;headerlink&quot; title=&quot;IDEA常用快捷键&quot;&gt;&lt;/a&gt;IDEA常用快捷键&lt;/h1&gt;&lt;h2 id=&quot;跳转&quot;&gt;&lt;a href=&quot;#跳转&quot; class=&quot;headerlink&quot; </summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="IDEA常用快捷键"><a href="#IDEA常用快捷键" class="headerlink" title="IDEA常用快捷键"></a>IDEA常用快捷键</h1><h2 id="跳转"><a href="#跳转" class="headerlink" title="跳转"></a>跳转</h2><ul><li><strong><code>F2 / Shift + F2</code></strong> 跳转至有错误或警告或提示的代码处</li><li><strong><code>Ctrl + ↑</code></strong> 光标位置不变，工作区窗口在文档处上移一行</li><li><strong><code>Ctrl + ↓</code></strong> 光标位置不变，工作区窗口在文档处下移一行</li><li><strong><code>Ctrl + ←</code></strong> 光标移动至当前单词的左边一个单词起始处</li><li><strong><code>Ctrl + →</code></strong> 光标移动至当前单词的右边一个单词起始处</li><li><strong><code>Ctrl + Tab</code></strong> 切换至某个窗口，包括已打开的文档窗口以及工具窗口</li><li><strong><code>Ctrl + Shift + ↑</code></strong> 上移当前行</li><li><strong><code>Ctrl + Shift + ↓</code></strong> 下移当前行</li><li><strong><code>Ctrl + Shift + Backspace</code></strong> 跳转至自打开项目以来的上一个改动过的地方，适用于多个文件修改过的情况</li><li><strong><code>Alt + ↑</code></strong> 光标跳转至上一个方法名或类名起始处，用于在当前文档中不同方法之间快速跳转</li><li><strong><code>Alt + ↓</code></strong> 光标跳转至下一个方法名或类名起始处</li><li><strong><code>Alt + 6</code></strong> 查找项目中的 <strong>TODO</strong> 标记，不区分大小写</li><li><strong><code>Ctrl + B</code></strong> 打开光标位于的类、接口、方法标识符的声明文件并跳转至声明处</li><li><strong><code>Ctrl + Alt + B</code></strong> 打开光标位于的类、接口、方法标识符的使用处，开发者可以选择打开使用到该标识符的文件</li></ul><h2 id="生成"><a href="#生成" class="headerlink" title="生成"></a>生成</h2><ul><li><strong><code>Alt + Insert</code></strong> 弹出代码生成菜单，包括但不限于 <strong>Constructor</strong>、<strong>Getter</strong>、<strong>Setter</strong>、<strong>toString</strong> 等</li><li><strong><code>Ctrl + Shift + Enter</code></strong> 对 <strong>{</strong> 生成匹配的 **}**，对未加分号的语句补全 <strong>;</strong></li><li><strong><code>Ctrl + J</code></strong> 弹出快捷代码模板</li><li><strong><code>Ctrl + O</code></strong> 弹出父类方法重写、接口方法实现菜单</li><li><strong><code>Shift + Enter</code></strong> 在不破坏当前行结构的前提下创建新行，并移动光标至新行开头。主要用于光标不在语句末尾的情况</li><li><strong><code>Ctrl + D</code></strong> 复制光标所在行</li><li><strong><code>Ctrl + Y</code></strong> 删除光标所在行</li><li><strong><code>Ctrl + Alt + T</code></strong> 将选中的代码或当前行包裹起来，其中包裹语句可选择 <strong>if</strong>、<strong>while</strong>、<strong>for</strong>、<strong>try &#x2F; catch</strong> 等</li></ul><h2 id="修复"><a href="#修复" class="headerlink" title="修复"></a>修复</h2><ul><li><strong><code>Alt + Enter</code></strong> 导入待引入的包，或这弹出快速修菜单(如快速生成尚未创建的方法、类等)</li><li><strong><code>Ctrl + Shift + U</code></strong> 所选择的内容(或光标所在单词)进行大小写转换</li></ul><h2 id="重构"><a href="#重构" class="headerlink" title="重构"></a>重构</h2><ul><li><strong><code>Shift + F6</code></strong> 重命名光标所位于的标识符</li><li><strong><code>Ctrl + Alt + M</code></strong> 将选中的语句提取为新方法，当前位置将会被新方法调用所替代</li><li><strong><code>Ctrl + Shift + J</code></strong> 将选中的多行代码(如果没有选中则使用光标所在行及下一行)合并为一行代码</li><li><strong><code>Ctrl + Alt + O</code></strong> 去除没有使用到的类和包，例如把 <strong>java.util.</strong>* 改为 <strong>java.util.某个具体的被引用到的类</strong>，以减少打包时的体积</li><li><strong><code>Ctrl + Alt + L</code></strong> 格式化代码。不一定生效，因为比较多的软件会设置该快捷键，导致冲突从而失效。这时可以选择菜单上的 “Code” 来手动格式化</li><li><strong><code>Ctrl + Alt + V</code></strong> 剔除非必要的(光标必须位于该变量标识符处)变量声明，用于减少中间变量</li></ul><h2 id="选择"><a href="#选择" class="headerlink" title="选择"></a>选择</h2><ul><li><strong><code>Ctrl + W</code></strong> 按语法选中代码，每按一次范围就扩大一层</li><li><strong><code>Ctrl + Shift + W</code></strong> 单独使用无效。它其实是与 <strong>Ctrl + W</strong> 匹配使用，每按一次范围就缩小一层</li><li><strong><code>Ctrl + Shift + F7</code></strong> 高亮光标所在标记符的全部引用。按 Escape 清除高亮显示</li></ul><h2 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h2><ul><li><strong><code>Shift + Shift</code></strong> 弹出全局搜搜框，用于搜索类、方法、资源、配置项</li><li><strong><code>Alt + F3</code></strong> 在编辑器中实现快速查找</li><li><strong><code>Ctrl + N</code></strong> 快速查找类文件并跳转</li></ul><h2 id="打开"><a href="#打开" class="headerlink" title="打开"></a>打开</h2><ul><li><strong><code>Ctrl + E</code></strong> 打开最近打开过的文件列表</li><li><strong><code>Ctrl + H</code></strong> 打开光标所在类标识符(如果光标所在位置不是类标识符，则使用当前作用域的类标识符)的继承关系列表</li><li><strong><code>Ctrl + Q</code></strong> 打开文档窗口，就是鼠标移至标识符上时会弹出的那个说明小窗口</li><li><strong><code>Ctrl + -</code></strong> 收缩当前代码段</li><li><strong><code>Ctrl + +</code></strong> 展开当前代码段</li><li><strong><code>Ctrl + Shift + -</code></strong> 收缩本文档的所有代码段</li><li><strong><code>Ctrl + Shift + +</code></strong> 展开本文档的所有代码段</li><li><strong><code>Ctrl + F12</code></strong> 打开当前作用域所声明的类或接口的结构列表</li><li><strong><code>Ctrl + P</code></strong> 光标位于方法调用时的方法名或括号中时，弹出当前方法所有可用的参数形式</li></ul>]]></content>
    
  </entry>
  
  <entry>
    <title>程序员职场避坑：这三句话千万别说出口</title>
    <link href="https://alan.org.cn/posts/%E7%A8%8B%E5%BA%8F%E5%91%98%E8%81%8C%E5%9C%BA%E9%81%BF%E5%9D%91%EF%BC%9A%E8%BF%99%E4%B8%89%E5%8F%A5%E8%AF%9D%E5%8D%83%E4%B8%87%E5%88%AB%E8%AF%B4%E5%87%BA%E5%8F%A3.html"/>
    <id>https://alan.org.cn/posts/%E7%A8%8B%E5%BA%8F%E5%91%98%E8%81%8C%E5%9C%BA%E9%81%BF%E5%9D%91%EF%BC%9A%E8%BF%99%E4%B8%89%E5%8F%A5%E8%AF%9D%E5%8D%83%E4%B8%87%E5%88%AB%E8%AF%B4%E5%87%BA%E5%8F%A3.html</id>
    <published>2025-12-20T15:04:19.000Z</published>
    <updated>2025-12-20T15:04:19.000Z</updated>
    
    
    <category term="产品" scheme="https://alan.org.cn/categories/%E4%BA%A7%E5%93%81/"/>
    
    
    <category term="产品" scheme="https://alan.org.cn/tags/%E4%BA%A7%E5%93%81/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;程序员职场避坑：这三句话千万别说出口&quot;&gt;&lt;a href=&quot;#程序员职场避坑：这三句话千万别说出口&quot; class=&quot;headerlink&quot; title=&quot;程序员职场避坑：这三句话千万别说出口&quot;&gt;&lt;/a&gt;&lt;strong&gt;程序员职场避坑：这三句话千万别说出口&lt;/stro</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="程序员职场避坑：这三句话千万别说出口"><a href="#程序员职场避坑：这三句话千万别说出口" class="headerlink" title="程序员职场避坑：这三句话千万别说出口"></a><strong>程序员职场避坑：这三句话千万别说出口</strong></h1><p><strong>1、“这个需求做不了”</strong></p><p>这句话相信在平时的需求会或者评审会上经常会听到。</p><p>当接到一个新需求或任务时，有些同学的第一反应是：“这个需求做不了”。</p><p>当员工说出“这个需求做不了”时，背后的含义有可能是“我不想做”、“我不会做”或“我觉得没必要做”。无论是哪种情况，传递出来的都是拒绝和封闭的态度。</p><p>因为现代职场没有绝对“做不了”的事情，只有尚未找到的解决方案或者不够充分的资源支持而已。</p><p>如果面对平级的产品经理这样说倒还无所谓，如果面对大领导这样表达那就多少有些欠妥了。</p><p>所以遇到这类问题<strong>大可不必当场上头去否定</strong>，别急着说‘做不了’，我们<strong>不妨换一个方式来表达</strong>。</p><p><strong>表达变换 Tips：</strong></p><ul><li>“这个需求很有挑战，我需要先调研看看”</li><li>“这个需求很有挑战，我需要XX资源和XX支持来实现它”</li><li>“目前有几点困难，我需要先评估一下”</li><li>“我需要XX资源和XX条件的支持，能否帮我协调”</li></ul><p><strong>2、“我以前都是这么做的”</strong></p><p>当员工不断强调“以前都是这么做的”，他实际上是在拒绝创新，拒绝适应变化，拒绝接受新思想。</p><p>在 IT 互联网行业，方法和工具迭代速度极快。之前曾经高效的方法，现在可能已经落后了，而对过往的经验和标签的一味固守往往会阻碍人进行第二层、第三层的进一步思考。</p><p>所以面对这种表达情形，我们<strong>不妨也可以换一种表达方式</strong>。</p><p><strong>表达变换 Tips：</strong></p><ul><li>“我过去是这样做的，但不确定是否适合现在的情况，需要先确定一下”</li><li>“过去我们是用XX方法做的，效果是XX。我们也可以探讨一下新方法，看看会不会更好”</li><li>“我能分享一下过去做这类项目的经验教训，供大家参考”</li><li>“我对这个新方法还不太熟，需要一点时间学习一下”</li></ul><p><strong>3、“这个不归我负责”</strong></p><p>我认为这句话可能是这三句中最致命的一句，因为它通常反映的是一种界限感和团队协作意识的缺失。</p><p>“各人自扫门前雪”的心态在某些特殊的场景下确实“杀伤力”极大，尤其如果在上级领导问责面前这么说，那基本真就 gg 了。。</p><p>在团队工作中，职责边界清晰本是好事，但过度强调“不归我管”则是一种危险的信号。</p><p>而且有时候模糊地带的问题往往就是自己能创造价值的机会，而且即便自己不想插手，也完全没必要这样去表达，我们<strong>不妨也换一些表达方式</strong>。</p><p><strong>表达变换 Tips：</strong></p><ul><li>“这个事情谁更熟悉？我可以协助”</li><li>“这个事情的负责人是xxx，我可以了解之后协助解决”</li><li>“我不太熟悉这个领域，但可以了解一下，谁能给我指点”</li><li>“我们需要先明确一下这个问题的负责人，我可以暂时跟进一下”</li></ul><p>所以以上这三点沟通的艺术也是程序员职场生活里切实可能会遇到的问题。当你下次准备说出这三句话中的任何一句时，不妨先停顿三秒，换个表达方式。</p><p>虽说向上管理和沟通艺术是被很多程序员所诟病和瞧不上的，但是有时候因为一句上头的话或者表达而导致了某些通道的关闭那也实在是太可惜了，这很不公平，但这往往就是职场的现实。</p><p>程序员作为一个有个性的创造性群体要专注精进技术这本身没错，但是职场毕竟也是一个充满人情世故的江湖，掌握一些通用的职场规则和沟通艺术也是十分有必要的，所以<strong>埋头赶路的同时也不要忘记看看周围的环境和机会</strong>。</p><p>在这个快速变化的时代，唯一不变的就是需要不停地适应和成长。共勉。</p><p>好了，那以上就是今天的内容分享了，希望能对大家有所启发，我们下篇见。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>Java反射机制</title>
    <link href="https://alan.org.cn/posts/%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6%EF%BC%9AClass%E7%B1%BB%E3%80%81%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E3%80%81%E6%B3%A8%E8%A7%A3%E8%A7%A3%E6%9E%90.html"/>
    <id>https://alan.org.cn/posts/%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6%EF%BC%9AClass%E7%B1%BB%E3%80%81%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E3%80%81%E6%B3%A8%E8%A7%A3%E8%A7%A3%E6%9E%90.html</id>
    <published>2025-05-10T17:16:05.000Z</published>
    <updated>2025-05-10T17:16:05.000Z</updated>
    
    
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Java-反射机制&quot;&gt;&lt;a href=&quot;#Java-反射机制&quot; class=&quot;headerlink&quot; title=&quot;Java 反射机制&quot;&gt;&lt;/a&gt;Java 反射机制&lt;/h1&gt;&lt;h2 id=&quot;一、反射基础：Class-类&quot;&gt;&lt;a href=&quot;#一、反射基础：Clas</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="Java-反射机制"><a href="#Java-反射机制" class="headerlink" title="Java 反射机制"></a>Java 反射机制</h1><h2 id="一、反射基础：Class-类"><a href="#一、反射基础：Class-类" class="headerlink" title="一、反射基础：Class 类"></a>一、反射基础：Class 类</h2><h3 id="1-Class-对象的获取方式"><a href="#1-Class-对象的获取方式" class="headerlink" title="1. Class 对象的获取方式"></a>1. Class 对象的获取方式</h3><table><thead><tr><th>获取方式</th><th>代码示例</th><th>适用场景</th></tr></thead><tbody><tr><td>类名.class</td><td><code>Class&lt;User&gt; clazz = User.class;</code></td><td>编译时已知类名</td></tr><tr><td>对象.getClass()</td><td><code>user.getClass();</code></td><td>已有对象实例</td></tr><tr><td>Class.forName()</td><td><code>Class.forName(&quot;com.example.User&quot;);</code></td><td>动态加载类</td></tr><tr><td>类加载器</td><td><code>ClassLoader.loadClass(&quot;com.example.User&quot;);</code></td><td>自定义类加载</td></tr></tbody></table><h3 id="2-Class-类核心方法"><a href="#2-Class-类核心方法" class="headerlink" title="2. Class 类核心方法"></a>2. Class 类核心方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 获取构造方法</span><br>Constructor&lt;?&gt;[] getConstructors();  <span class="hljs-comment">// 公共构造</span><br>Constructor&lt;T&gt; <span class="hljs-title function_">getConstructor</span><span class="hljs-params">(Class&lt;?&gt;... parameterTypes)</span>;<br><br><span class="hljs-comment">// 获取字段</span><br>Field <span class="hljs-title function_">getField</span><span class="hljs-params">(String name)</span>;         <span class="hljs-comment">// 公共字段（包括继承）</span><br>Field <span class="hljs-title function_">getDeclaredField</span><span class="hljs-params">(String name)</span>; <span class="hljs-comment">// 本类所有字段</span><br><br><span class="hljs-comment">// 获取方法</span><br>Method <span class="hljs-title function_">getMethod</span><span class="hljs-params">(String name, Class&lt;?&gt;... parameterTypes)</span>;<br>Method <span class="hljs-title function_">getDeclaredMethod</span><span class="hljs-params">(String name, Class&lt;?&gt;... parameterTypes)</span>;<br><br><span class="hljs-comment">// 创建实例</span><br>T <span class="hljs-title function_">newInstance</span><span class="hljs-params">()</span>;                     <span class="hljs-comment">// 过时方法（Java 9+）</span><br>Constructor.newInstance(Object... initargs); <span class="hljs-comment">// 推荐方式</span><br></code></pre></td></tr></table></figure><h3 id="3-类型检查方法"><a href="#3-类型检查方法" class="headerlink" title="3. 类型检查方法"></a>3. 类型检查方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java">isInterface()      <span class="hljs-comment">// 是否接口</span><br>isArray()          <span class="hljs-comment">// 是否数组</span><br>isEnum()           <span class="hljs-comment">// 是否枚举</span><br>isPrimitive()      <span class="hljs-comment">// 是否基本类型</span><br>isAnnotationPresent(Class&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Annotation</span>&gt; annotationClass) <span class="hljs-comment">// 是否有注解</span><br></code></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><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ReflectFieldDemo</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>(<span class="hljs-string">&quot;张三&quot;</span>, <span class="hljs-number">25</span>);<br>        Class&lt;?&gt; clazz = user.getClass();<br>        <br>        <span class="hljs-comment">// 获取私有字段并修改值</span><br>        <span class="hljs-type">Field</span> <span class="hljs-variable">ageField</span> <span class="hljs-operator">=</span> clazz.getDeclaredField(<span class="hljs-string">&quot;age&quot;</span>);<br>        ageField.setAccessible(<span class="hljs-literal">true</span>); <span class="hljs-comment">// 突破private限制</span><br>        ageField.setInt(user, <span class="hljs-number">30</span>);<br>        <br>        System.out.println(user); <span class="hljs-comment">// 输出修改后的对象</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-方法调用示例"><a href="#2-方法调用示例" class="headerlink" title="2. 方法调用示例"></a>2. 方法调用示例</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ReflectMethodDemo</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        Class&lt;User&gt; clazz = User.class;<br>        <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> clazz.getConstructor(String.class, <span class="hljs-type">int</span>.class)<br>                       .newInstance(<span class="hljs-string">&quot;李四&quot;</span>, <span class="hljs-number">28</span>);<br>        <br>        <span class="hljs-comment">// 调用私有方法</span><br>        <span class="hljs-type">Method</span> <span class="hljs-variable">method</span> <span class="hljs-operator">=</span> clazz.getDeclaredMethod(<span class="hljs-string">&quot;privateMethod&quot;</span>, String.class);<br>        method.setAccessible(<span class="hljs-literal">true</span>);<br>        <span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> method.invoke(user, <span class="hljs-string">&quot;参数&quot;</span>);<br>        <br>        System.out.println(<span class="hljs-string">&quot;方法返回值: &quot;</span> + result);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-构造方法实例化对比"><a href="#3-构造方法实例化对比" class="headerlink" title="3. 构造方法实例化对比"></a>3. 构造方法实例化对比</h3><table><thead><tr><th>方式</th><th>代码示例</th><th>特点</th></tr></thead><tbody><tr><td>newInstance()</td><td><code>User user = User.class.newInstance();</code></td><td>只能调用无参构造，已废弃</td></tr><tr><td>Constructor</td><td><code>Constructor&lt;User&gt; c = User.class.getConstructor(String.class, int.class);&lt;br&gt;User user = c.newInstance(&quot;王五&quot;, 30);</code></td><td>推荐方式，支持有参构造</td></tr></tbody></table><h2 id="三、动态代理机制"><a href="#三、动态代理机制" class="headerlink" title="三、动态代理机制"></a>三、动态代理机制</h2><h3 id="1-JDK-动态代理"><a href="#1-JDK-动态代理" class="headerlink" title="1. JDK 动态代理"></a>1. JDK 动态代理</h3><p><strong>特点</strong>：</p><ul><li>基于接口实现</li><li><code>java.lang.reflect.Proxy</code> 生成代理类</li><li>需要实现 <code>InvocationHandler</code></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">JdkProxyDemo</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">UserService</span> <span class="hljs-variable">realService</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserServiceImpl</span>();<br>        <br>        <span class="hljs-comment">// 生成代理对象</span><br>        <span class="hljs-type">UserService</span> <span class="hljs-variable">proxy</span> <span class="hljs-operator">=</span> (UserService) Proxy.newProxyInstance(<br>            realService.getClass().getClassLoader(),<br>            realService.getClass().getInterfaces(),<br>            (proxy1, method, args1) -&gt; &#123;<br>                System.out.println(<span class="hljs-string">&quot;前置处理&quot;</span>);<br>                <span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> method.invoke(realService, args1);<br>                System.out.println(<span class="hljs-string">&quot;后置处理&quot;</span>);<br>                <span class="hljs-keyword">return</span> result;<br>            &#125;);<br>        <br>        proxy.addUser(<span class="hljs-string">&quot;test&quot;</span>); <span class="hljs-comment">// 调用代理方法</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-CGLIB-动态代理"><a href="#2-CGLIB-动态代理" class="headerlink" title="2. CGLIB 动态代理"></a>2. CGLIB 动态代理</h3><p><strong>特点</strong>：</p><ul><li>基于类继承</li><li>不需要接口</li><li>Spring AOP 默认使用</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CglibProxyDemo</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Enhancer</span> <span class="hljs-variable">enhancer</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Enhancer</span>();<br>        enhancer.setSuperclass(UserServiceImpl.class);<br>        enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -&gt; &#123;<br>            System.out.println(<span class="hljs-string">&quot;CGLIB前置处理&quot;</span>);<br>            <span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> proxy.invokeSuper(obj, args1);<br>            System.out.println(<span class="hljs-string">&quot;CGLIB后置处理&quot;</span>);<br>            <span class="hljs-keyword">return</span> result;<br>        &#125;);<br>        <br>        <span class="hljs-type">UserService</span> <span class="hljs-variable">proxy</span> <span class="hljs-operator">=</span> (UserService) enhancer.create();<br>        proxy.addUser(<span class="hljs-string">&quot;test&quot;</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-两种代理对比"><a href="#3-两种代理对比" class="headerlink" title="3. 两种代理对比"></a>3. 两种代理对比</h3><table><thead><tr><th>特性</th><th>JDK 动态代理</th><th>CGLIB 代理</th></tr></thead><tbody><tr><td>代理方式</td><td>接口实现</td><td>类继承</td></tr><tr><td>依赖</td><td>无需额外库</td><td>需要cglib库</td></tr><tr><td>性能</td><td>调用快，创建慢</td><td>创建快，调用稍慢</td></tr><tr><td>限制</td><td>必须实现接口</td><td>不能代理final类&#x2F;方法</td></tr><tr><td>适用场景</td><td>接口明确的场景</td><td>无接口或需要高性能</td></tr></tbody></table><h2 id="四、注解解析技术"><a href="#四、注解解析技术" class="headerlink" title="四、注解解析技术"></a>四、注解解析技术</h2><h3 id="1-运行时注解处理"><a href="#1-运行时注解处理" class="headerlink" title="1. 运行时注解处理"></a>1. 运行时注解处理</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><br><span class="hljs-meta">@Target(ElementType.METHOD)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> MyAnnotation &#123;<br>    String <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">&quot;&quot;</span>;<br>    <span class="hljs-type">int</span> <span class="hljs-title function_">priority</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>;<br>&#125;<br><br><span class="hljs-comment">// 注解处理器</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AnnotationProcessor</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">processAnnotations</span><span class="hljs-params">(Object obj)</span> &#123;<br>        Class&lt;?&gt; clazz = obj.getClass();<br>        <span class="hljs-keyword">for</span> (Method method : clazz.getDeclaredMethods()) &#123;<br>            <span class="hljs-keyword">if</span> (method.isAnnotationPresent(MyAnnotation.class)) &#123;<br>                <span class="hljs-type">MyAnnotation</span> <span class="hljs-variable">annotation</span> <span class="hljs-operator">=</span> method.getAnnotation(MyAnnotation.class);<br>                System.out.println(<span class="hljs-string">&quot;发现注解方法: &quot;</span> + method.getName());<br>                System.out.println(<span class="hljs-string">&quot;注解值: &quot;</span> + annotation.value());<br>                System.out.println(<span class="hljs-string">&quot;优先级: &quot;</span> + annotation.priority());<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-Spring-风格注解解析"><a href="#2-Spring-风格注解解析" class="headerlink" title="2. Spring 风格注解解析"></a>2. Spring 风格注解解析</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 自定义注解</span><br><span class="hljs-meta">@Target(ElementType.FIELD)</span><br><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> Autowired &#123;<br>&#125;<br><br><span class="hljs-comment">// 简易DI容器</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DIContainer</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;T&gt; T <span class="hljs-title function_">getInstance</span><span class="hljs-params">(Class&lt;T&gt; clazz)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        <span class="hljs-type">T</span> <span class="hljs-variable">instance</span> <span class="hljs-operator">=</span> clazz.getDeclaredConstructor().newInstance();<br>        <br>        <span class="hljs-comment">// 处理字段注入</span><br>        <span class="hljs-keyword">for</span> (Field field : clazz.getDeclaredFields()) &#123;<br>            <span class="hljs-keyword">if</span> (field.isAnnotationPresent(Autowired.class)) &#123;<br>                <span class="hljs-type">Object</span> <span class="hljs-variable">fieldInstance</span> <span class="hljs-operator">=</span> getInstance(field.getType());<br>                field.setAccessible(<span class="hljs-literal">true</span>);<br>                field.set(instance, fieldInstance);<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> instance;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-编译时注解处理（APT）"><a href="#3-编译时注解处理（APT）" class="headerlink" title="3. 编译时注解处理（APT）"></a>3. 编译时注解处理（APT）</h3><ol><li><p>定义注解处理器：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@SupportedAnnotationTypes(&quot;com.example.MyAnnotation&quot;)</span><br><span class="hljs-meta">@SupportedSourceVersion(SourceVersion.RELEASE_11)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyAnnotationProcessor</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractProcessor</span> &#123;<br>    <br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">process</span><span class="hljs-params">(Set&lt;? extends TypeElement&gt; annotations, </span><br><span class="hljs-params">                         RoundEnvironment roundEnv)</span> &#123;<br>        <span class="hljs-keyword">for</span> (Element element : roundEnv.getElementsWithAnnotation(MyAnnotation.class)) &#123;<br>            <span class="hljs-comment">// 处理被注解的元素</span><br>            <span class="hljs-keyword">if</span> (element.getKind() == ElementKind.METHOD) &#123;<br>                <span class="hljs-type">ExecutableElement</span> <span class="hljs-variable">method</span> <span class="hljs-operator">=</span> (ExecutableElement) element;<br>                <span class="hljs-type">String</span> <span class="hljs-variable">methodName</span> <span class="hljs-operator">=</span> method.getSimpleName().toString();<br>                processingEnv.getMessager().printMessage(<br>                    Diagnostic.Kind.NOTE, <span class="hljs-string">&quot;发现注解方法: &quot;</span> + methodName);<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>注册处理器（META-INF&#x2F;services&#x2F;javax.annotation.processing.Processor）</p></li></ol><h2 id="五、反射性能优化"><a href="#五、反射性能优化" class="headerlink" title="五、反射性能优化"></a>五、反射性能优化</h2><h3 id="1-缓存反射对象"><a href="#1-缓存反射对象" class="headerlink" title="1. 缓存反射对象"></a>1. 缓存反射对象</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ReflectionCache</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Map&lt;Class&lt;?&gt;, Method&gt; methodCache = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span>&lt;&gt;();<br>    <br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title function_">invokeMethod</span><span class="hljs-params">(Object obj, String methodName, Object... args)</span> <br>            <span class="hljs-keyword">throws</span> Exception &#123;<br>        Class&lt;?&gt; clazz = obj.getClass();<br>        <span class="hljs-type">Method</span> <span class="hljs-variable">method</span> <span class="hljs-operator">=</span> methodCache.computeIfAbsent(clazz, <br>            c -&gt; Arrays.stream(c.getDeclaredMethods())<br>                     .filter(m -&gt; m.getName().equals(methodName))<br>                     .findFirst()<br>                     .orElseThrow());<br>        <br>        method.setAccessible(<span class="hljs-literal">true</span>);<br>        <span class="hljs-keyword">return</span> method.invoke(obj, args);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-MethodHandle（Java-7-）"><a href="#2-MethodHandle（Java-7-）" class="headerlink" title="2. MethodHandle（Java 7+）"></a>2. MethodHandle（Java 7+）</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MethodHandleDemo</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Throwable &#123;<br>        MethodHandles.<span class="hljs-type">Lookup</span> <span class="hljs-variable">lookup</span> <span class="hljs-operator">=</span> MethodHandles.lookup();<br>        <span class="hljs-type">MethodType</span> <span class="hljs-variable">type</span> <span class="hljs-operator">=</span> MethodType.methodType(<span class="hljs-keyword">void</span>.class, String.class);<br>        <span class="hljs-type">MethodHandle</span> <span class="hljs-variable">handle</span> <span class="hljs-operator">=</span> lookup.findVirtual(User.class, <span class="hljs-string">&quot;setName&quot;</span>, type);<br>        <br>        <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>();<br>        handle.invokeExact(user, <span class="hljs-string">&quot;张三&quot;</span>);<br>        System.out.println(user.getName());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="3-性能对比（纳秒级操作）"><a href="#3-性能对比（纳秒级操作）" class="headerlink" title="3. 性能对比（纳秒级操作）"></a>3. 性能对比（纳秒级操作）</h3><table><thead><tr><th>操作方式</th><th>首次调用</th><th>缓存后调用</th><th>MethodHandle</th></tr></thead><tbody><tr><td>直接调用</td><td>2-5 ns</td><td>-</td><td>-</td></tr><tr><td>反射调用</td><td>1000-2000 ns</td><td>500-1000 ns</td><td>50-100 ns</td></tr></tbody></table><h2 id="六、安全注意事项"><a href="#六、安全注意事项" class="headerlink" title="六、安全注意事项"></a>六、安全注意事项</h2><ol><li><p><strong>权限控制</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 启用安全检查时（默认）</span><br><span class="hljs-type">SecurityManager</span> <span class="hljs-variable">sm</span> <span class="hljs-operator">=</span> System.getSecurityManager();<br><span class="hljs-keyword">if</span> (sm != <span class="hljs-literal">null</span>) &#123;<br>    sm.checkPermission(<span class="hljs-keyword">new</span> <span class="hljs-title class_">ReflectPermission</span>(<span class="hljs-string">&quot;suppressAccessChecks&quot;</span>));<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p><strong>防御措施</strong>：</p><ul><li>限制反射可访问的包路径</li><li>对敏感方法添加额外权限检查</li><li>使用<code>setAccessible()</code>后及时恢复原状态</li></ul></li><li><p><strong>最佳实践</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">Field</span> <span class="hljs-variable">field</span> <span class="hljs-operator">=</span> clazz.getDeclaredField(<span class="hljs-string">&quot;secret&quot;</span>);<br><span class="hljs-type">boolean</span> <span class="hljs-variable">accessible</span> <span class="hljs-operator">=</span> field.isAccessible();<br><span class="hljs-keyword">try</span> &#123;<br>    field.setAccessible(<span class="hljs-literal">true</span>);<br>    <span class="hljs-comment">// 操作字段...</span><br>&#125; <span class="hljs-keyword">finally</span> &#123;<br>    field.setAccessible(accessible); <span class="hljs-comment">// 恢复原状态</span><br>&#125;<br></code></pre></td></tr></table></figure></li></ol><h2 id="七、典型应用场景"><a href="#七、典型应用场景" class="headerlink" title="七、典型应用场景"></a>七、典型应用场景</h2><ol><li><p><strong>框架开发</strong>：</p><ul><li>Spring IOC 容器</li><li>JPA&#x2F;Hibernate ORM 映射</li><li>JUnit 测试框架</li></ul></li><li><p><strong>动态功能</strong>：</p><ul><li>插件系统</li><li>RPC 调用</li><li>序列化&#x2F;反序列化</li></ul></li><li><p><strong>工具类</strong>：</p><ul><li>BeanUtils 属性拷贝</li><li>通用DAO实现</li><li>动态SQL生成</li></ul></li></ol><p>通过合理使用反射机制，可以实现高度灵活的系统架构，但需要注意性能开销和安全风险。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>自定义注解、元注解</title>
    <link href="https://alan.org.cn/posts/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3%E3%80%81%E5%85%83%E6%B3%A8%E8%A7%A3.html"/>
    <id>https://alan.org.cn/posts/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3%E3%80%81%E5%85%83%E6%B3%A8%E8%A7%A3.html</id>
    <published>2025-05-10T17:07:27.000Z</published>
    <updated>2025-05-10T17:07:27.000Z</updated>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/categories/JAVA/"/>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/tags/JAVA/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Java-注解（Annotations）&quot;&gt;&lt;a href=&quot;#Java-注解（Annotations）&quot; class=&quot;headerlink&quot; title=&quot;Java 注解（Annotations）&quot;&gt;&lt;/a&gt;Java 注解（Annotations）&lt;/h1&gt;&lt;</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="Java-注解（Annotations）"><a href="#Java-注解（Annotations）" class="headerlink" title="Java 注解（Annotations）"></a>Java 注解（Annotations）</h1><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>以<code>@</code>符号开头，如<code>@Override</code></li></ul><h3 id="2-注解的作用"><a href="#2-注解的作用" class="headerlink" title="2. 注解的作用"></a>2. 注解的作用</h3><ul><li><strong>编译检查</strong>：如<code>@Override</code>确保方法正确重写</li><li><strong>代码生成</strong>：如Lombok的<code>@Data</code>自动生成getter&#x2F;setter</li><li><strong>运行时处理</strong>：如Spring的<code>@Autowired</code>依赖注入</li></ul><h2 id="二、元注解（Meta-Annotations）"><a href="#二、元注解（Meta-Annotations）" class="headerlink" title="二、元注解（Meta-Annotations）"></a>二、元注解（Meta-Annotations）</h2><p>元注解是指用来注解其他注解的注解，Java提供了5种标准元注解：</p><h3 id="1-Retention-定义注解的生命周期"><a href="#1-Retention-定义注解的生命周期" class="headerlink" title="1. @Retention - 定义注解的生命周期"></a>1. <code>@Retention</code> - 定义注解的生命周期</h3><table><thead><tr><th>保留策略</th><th>作用范围</th><th>典型应用场景</th></tr></thead><tbody><tr><td><code>RetentionPolicy.SOURCE</code></td><td>仅保留在源码阶段</td><td><code>@Override</code>, <code>@SuppressWarnings</code></td></tr><tr><td><code>RetentionPolicy.CLASS</code></td><td>保留到class文件但JVM不加载</td><td>字节码处理工具使用</td></tr><tr><td><code>RetentionPolicy.RUNTIME</code></td><td>运行时可通过反射获取</td><td>Spring框架注解, JPA注解</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> MyRuntimeAnnotation &#123;&#125;<br></code></pre></td></tr></table></figure><h3 id="2-Target-指定注解可以应用的位置"><a href="#2-Target-指定注解可以应用的位置" class="headerlink" title="2. @Target - 指定注解可以应用的位置"></a>2. <code>@Target</code> - 指定注解可以应用的位置</h3><table><thead><tr><th>目标类型（ElementType）</th><th>可应用位置</th></tr></thead><tbody><tr><td><code>TYPE</code></td><td>类、接口、枚举</td></tr><tr><td><code>FIELD</code></td><td>字段（包括枚举常量）</td></tr><tr><td><code>METHOD</code></td><td>方法</td></tr><tr><td><code>PARAMETER</code></td><td>方法参数</td></tr><tr><td><code>CONSTRUCTOR</code></td><td>构造函数</td></tr><tr><td><code>LOCAL_VARIABLE</code></td><td>局部变量</td></tr><tr><td><code>ANNOTATION_TYPE</code></td><td>注解类型</td></tr><tr><td><code>PACKAGE</code></td><td>包声明</td></tr><tr><td><code>TYPE_PARAMETER</code></td><td>类型参数（Java 8+）</td></tr><tr><td><code>TYPE_USE</code></td><td>类型使用处（Java 8+）</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Target(&#123;ElementType.METHOD, ElementType.TYPE&#125;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> MyMethodAndClassAnnotation &#123;&#125;<br></code></pre></td></tr></table></figure><h3 id="3-Documented-控制是否出现在Javadoc中"><a href="#3-Documented-控制是否出现在Javadoc中" class="headerlink" title="3. @Documented - 控制是否出现在Javadoc中"></a>3. <code>@Documented</code> - 控制是否出现在Javadoc中</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Documented</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> MyDocumentedAnnotation &#123;&#125;<br></code></pre></td></tr></table></figure><h3 id="4-Inherited-允许子类继承父类的注解"><a href="#4-Inherited-允许子类继承父类的注解" class="headerlink" title="4. @Inherited - 允许子类继承父类的注解"></a>4. <code>@Inherited</code> - 允许子类继承父类的注解</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Inherited</span><br><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> MyInheritedAnnotation &#123;&#125;<br></code></pre></td></tr></table></figure><h3 id="5-Repeatable-Java-8-允许重复使用同一注解"><a href="#5-Repeatable-Java-8-允许重复使用同一注解" class="headerlink" title="5. @Repeatable (Java 8+) - 允许重复使用同一注解"></a>5. <code>@Repeatable</code> (Java 8+) - 允许重复使用同一注解</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Repeatable(Authorities.class)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> Authority &#123;<br>    String <span class="hljs-title function_">value</span><span class="hljs-params">()</span>;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> Authorities &#123;<br>    Authority[] value();<br>&#125;<br><br><span class="hljs-comment">// 使用</span><br><span class="hljs-meta">@Authority(&quot;admin&quot;)</span><br><span class="hljs-meta">@Authority(&quot;user&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyService</span> &#123;&#125;<br></code></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><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> 注解名称 &#123;<br>    <span class="hljs-comment">// 注解元素</span><br>    元素类型 元素名称() <span class="hljs-keyword">default</span> 默认值;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-注解元素类型限制"><a href="#2-注解元素类型限制" class="headerlink" title="2. 注解元素类型限制"></a>2. 注解元素类型限制</h3><ul><li>基本数据类型（int, float, boolean等）</li><li>String</li><li>Class</li><li>枚举</li><li>其他注解</li><li>以上类型的数组</li></ul><h3 id="3-完整自定义注解示例"><a href="#3-完整自定义注解示例" class="headerlink" title="3. 完整自定义注解示例"></a>3. 完整自定义注解示例</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 用户权限验证注解</span><br><span class="hljs-comment"> */</span><br><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><br><span class="hljs-meta">@Target(ElementType.METHOD)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> Auth &#123;<br>    <span class="hljs-comment">// 必需的角色</span><br>    String[] roles() <span class="hljs-keyword">default</span> &#123;&#125;;<br>    <br>    <span class="hljs-comment">// 权限等级（默认0）</span><br>    <span class="hljs-type">int</span> <span class="hljs-title function_">level</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>;<br>    <br>    <span class="hljs-comment">// 错误提示信息</span><br>    String <span class="hljs-title function_">message</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">&quot;无访问权限&quot;</span>;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="4-使用自定义注解"><a href="#4-使用自定义注解" class="headerlink" title="4. 使用自定义注解"></a>4. 使用自定义注解</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserService</span> &#123;<br>    <span class="hljs-meta">@Auth(roles = &#123;&quot;admin&quot;, &quot;superuser&quot;&#125;, level = 2)</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">deleteUser</span><span class="hljs-params">(String userId)</span> &#123;<br>        <span class="hljs-comment">// 方法实现</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="四、注解的运行时处理"><a href="#四、注解的运行时处理" class="headerlink" title="四、注解的运行时处理"></a>四、注解的运行时处理</h2><h3 id="1-反射API获取注解"><a href="#1-反射API获取注解" class="headerlink" title="1. 反射API获取注解"></a>1. 反射API获取注解</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">Method</span> <span class="hljs-variable">method</span> <span class="hljs-operator">=</span> UserService.class.getMethod(<span class="hljs-string">&quot;deleteUser&quot;</span>, String.class);<br><span class="hljs-type">Auth</span> <span class="hljs-variable">authAnnotation</span> <span class="hljs-operator">=</span> method.getAnnotation(Auth.class);<br><br><span class="hljs-keyword">if</span> (authAnnotation != <span class="hljs-literal">null</span>) &#123;<br>    System.out.println(<span class="hljs-string">&quot;所需角色: &quot;</span> + Arrays.toString(authAnnotation.roles()));<br>    System.out.println(<span class="hljs-string">&quot;权限等级: &quot;</span> + authAnnotation.level());<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="2-Spring风格的注解处理器示例"><a href="#2-Spring风格的注解处理器示例" class="headerlink" title="2. Spring风格的注解处理器示例"></a>2. Spring风格的注解处理器示例</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AuthInterceptor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">HandlerInterceptor</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">preHandle</span><span class="hljs-params">(HttpServletRequest request, </span><br><span class="hljs-params">                           HttpServletResponse response, </span><br><span class="hljs-params">                           Object handler)</span> <span class="hljs-keyword">throws</span> Exception &#123;<br>        <br>        <span class="hljs-type">HandlerMethod</span> <span class="hljs-variable">handlerMethod</span> <span class="hljs-operator">=</span> (HandlerMethod) handler;<br>        <span class="hljs-type">Auth</span> <span class="hljs-variable">auth</span> <span class="hljs-operator">=</span> handlerMethod.getMethodAnnotation(Auth.class);<br>        <br>        <span class="hljs-keyword">if</span> (auth != <span class="hljs-literal">null</span>) &#123;<br>            <span class="hljs-comment">// 实际权限验证逻辑</span><br>            <span class="hljs-keyword">if</span> (!checkAuth(request, auth.roles(), auth.level())) &#123;<br>                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AuthException</span>(auth.message());<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>    &#125;<br>    <br>    <span class="hljs-keyword">private</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">checkAuth</span><span class="hljs-params">(HttpServletRequest request, </span><br><span class="hljs-params">                             String[] requiredRoles, </span><br><span class="hljs-params">                             <span class="hljs-type">int</span> requiredLevel)</span> &#123;<br>        <span class="hljs-comment">// 实现验证逻辑</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>    &#125;<br>&#125;<br></code></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><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><br><span class="hljs-meta">@Target(ElementType.TYPE_USE)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> NotNull &#123;&#125;<br><br><span class="hljs-comment">// 使用</span><br>List&lt;<span class="hljs-meta">@NotNull</span> String&gt; names = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br></code></pre></td></tr></table></figure><h3 id="2-注解处理器（编译时处理）"><a href="#2-注解处理器（编译时处理）" class="headerlink" title="2. 注解处理器（编译时处理）"></a>2. 注解处理器（编译时处理）</h3><p>通过实现<code>AbstractProcessor</code>创建注解处理器：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@SupportedAnnotationTypes(&quot;com.example.MyAnnotation&quot;)</span><br><span class="hljs-meta">@SupportedSourceVersion(SourceVersion.RELEASE_11)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyAnnotationProcessor</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractProcessor</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">process</span><span class="hljs-params">(Set&lt;? extends TypeElement&gt; annotations, </span><br><span class="hljs-params">                         RoundEnvironment roundEnv)</span> &#123;<br>        <span class="hljs-comment">// 处理注解逻辑</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="六、常见框架注解对比"><a href="#六、常见框架注解对比" class="headerlink" title="六、常见框架注解对比"></a>六、常见框架注解对比</h2><table><thead><tr><th>注解类型</th><th>Java标准</th><th>Spring框架</th><th>JPA&#x2F;Hibernate</th></tr></thead><tbody><tr><td><strong>类级别</strong></td><td><code>@Deprecated</code></td><td><code>@Component</code></td><td><code>@Entity</code></td></tr><tr><td><strong>字段级别</strong></td><td><code>@SuppressWarnings</code></td><td><code>@Autowired</code></td><td><code>@Column</code></td></tr><tr><td><strong>方法级别</strong></td><td><code>@Override</code></td><td><code>@RequestMapping</code></td><td><code>@Transient</code></td></tr><tr><td><strong>参数级别</strong></td><td>-</td><td><code>@RequestParam</code></td><td><code>@Param</code></td></tr></tbody></table><h2 id="七、最佳实践"><a href="#七、最佳实践" class="headerlink" title="七、最佳实践"></a>七、最佳实践</h2><ol><li><p><strong>命名规范</strong>：</p><ul><li>注解名称应明确表达其用途</li><li>使用名词或形容词（如<code>@Nullable</code>, <code>@Test</code>）</li></ul></li><li><p><strong>文档说明</strong>：</p><ul><li>使用JavaDoc说明注解用途</li><li>标注是否线程安全</li></ul></li><li><p><strong>性能考虑</strong>：</p><ul><li>运行时注解会增加反射开销</li><li>频繁调用的代码避免过多注解检查</li></ul></li><li><p><strong>组合注解</strong>：</p><ul><li>Spring风格的元注解组合<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RestController</span><br><span class="hljs-meta">@RequestMapping(&quot;/api/v1&quot;)</span><br><span class="hljs-meta">@ResponseBody</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> ApiV1Controller &#123;&#125;<br></code></pre></td></tr></table></figure></li></ul></li><li><p><strong>验证注解</strong>：</p><ul><li>结合Bean Validation使用<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><br><span class="hljs-meta">@Target(ElementType.FIELD)</span><br><span class="hljs-meta">@Constraint(validatedBy = PhoneValidator.class)</span><br><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> ValidPhone &#123;<br>    String <span class="hljs-title function_">message</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">&quot;Invalid phone number&quot;</span>;<br>    Class&lt;?&gt;[] groups() <span class="hljs-keyword">default</span> &#123;&#125;;<br>    Class&lt;? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Payload</span>&gt;[] payload() <span class="hljs-keyword">default</span> &#123;&#125;;<br>&#125;<br></code></pre></td></tr></table></figure></li></ul></li></ol><p>通过合理使用注解，可以极大提高代码的可读性和可维护性，同时减少样板代码。在Spring等现代框架中，注解已成为核心编程范式之一。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>Lambda表达式与方法引用</title>
    <link href="https://alan.org.cn/posts/Lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F%E4%B8%8E%E6%96%B9%E6%B3%95%E5%BC%95%E7%94%A8.html"/>
    <id>https://alan.org.cn/posts/Lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F%E4%B8%8E%E6%96%B9%E6%B3%95%E5%BC%95%E7%94%A8.html</id>
    <published>2025-05-10T17:06:44.000Z</published>
    <updated>2025-05-10T17:06:44.000Z</updated>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/categories/JAVA/"/>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/tags/JAVA/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Lambda表达式与方法引用-Java-8-核心特性笔记&quot;&gt;&lt;a href=&quot;#Lambda表达式与方法引用-Java-8-核心特性笔记&quot; class=&quot;headerlink&quot; title=&quot;Lambda表达式与方法引用 - Java 8+ 核心特性笔记&quot;&gt;&lt;/a</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="Lambda表达式与方法引用-Java-8-核心特性笔记"><a href="#Lambda表达式与方法引用-Java-8-核心特性笔记" class="headerlink" title="Lambda表达式与方法引用 - Java 8+ 核心特性笔记"></a>Lambda表达式与方法引用 - Java 8+ 核心特性笔记</h1><h2 id="一、Lambda表达式"><a href="#一、Lambda表达式" class="headerlink" title="一、Lambda表达式"></a>一、Lambda表达式</h2><h3 id="1-基本概念"><a href="#1-基本概念" class="headerlink" title="1. 基本概念"></a>1. 基本概念</h3><ul><li><strong>本质</strong>：匿名函数，简化匿名内部类的语法</li><li><strong>特点</strong>：<ul><li>属于函数式接口（只有一个抽象方法的接口）</li><li>类型自动推断</li><li>可捕获外部变量（必须是final或等效final）</li></ul></li></ul><h3 id="2-语法结构"><a href="#2-语法结构" class="headerlink" title="2. 语法结构"></a>2. 语法结构</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">(parameters) -&gt; expression<br>或<br>(parameters) -&gt; &#123; statements; &#125;<br></code></pre></td></tr></table></figure><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><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 旧写法</span><br><span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Runnable</span>() &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;Hello&quot;</span>);<br>    &#125;<br>&#125;).start();<br><br><span class="hljs-comment">// Lambda写法</span><br><span class="hljs-keyword">new</span> <span class="hljs-title class_">Thread</span>(() -&gt; System.out.println(<span class="hljs-string">&quot;Hello&quot;</span>)).start();<br></code></pre></td></tr></table></figure><h4 id="示例2：集合操作"><a href="#示例2：集合操作" class="headerlink" title="示例2：集合操作"></a>示例2：集合操作</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; names = Arrays.asList(<span class="hljs-string">&quot;Alice&quot;</span>, <span class="hljs-string">&quot;Bob&quot;</span>, <span class="hljs-string">&quot;Charlie&quot;</span>);<br><br><span class="hljs-comment">// 遍历</span><br>names.forEach(name -&gt; System.out.println(name));<br><br><span class="hljs-comment">// 过滤</span><br>List&lt;String&gt; result = names.stream()<br>                          .filter(name -&gt; name.startsWith(<span class="hljs-string">&quot;A&quot;</span>))<br>                          .collect(Collectors.toList());<br></code></pre></td></tr></table></figure><h3 id="4-变量捕获规则"><a href="#4-变量捕获规则" class="headerlink" title="4. 变量捕获规则"></a>4. 变量捕获规则</h3><ul><li>可捕获外部final或等效final的局部变量</li><li>不能修改捕获的变量</li><li>可以修改对象内部的属性（如集合内容）</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">limit</span> <span class="hljs-operator">=</span> <span class="hljs-number">3</span>; <span class="hljs-comment">// 等效final</span><br>List&lt;String&gt; filtered = names.stream()<br>                           .filter(name -&gt; name.length() &gt; limit) <span class="hljs-comment">// 捕获外部变量</span><br>                           .collect(Collectors.toList());<br></code></pre></td></tr></table></figure><h2 id="二、方法引用"><a href="#二、方法引用" class="headerlink" title="二、方法引用"></a>二、方法引用</h2><h3 id="1-基本概念-1"><a href="#1-基本概念-1" class="headerlink" title="1. 基本概念"></a>1. 基本概念</h3><ul><li><strong>本质</strong>：Lambda表达式的语法糖</li><li><strong>特点</strong>：<ul><li>更简洁</li><li>可读性更强</li><li>需要方法签名匹配函数式接口</li></ul></li></ul><h3 id="2-四种引用类型"><a href="#2-四种引用类型" class="headerlink" title="2. 四种引用类型"></a>2. 四种引用类型</h3><h4 id="1-静态方法引用"><a href="#1-静态方法引用" class="headerlink" title="(1) 静态方法引用"></a>(1) 静态方法引用</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 类名::静态方法</span><br>Function&lt;String, Integer&gt; parser = Integer::parseInt;<br><span class="hljs-type">Integer</span> <span class="hljs-variable">num</span> <span class="hljs-operator">=</span> parser.apply(<span class="hljs-string">&quot;123&quot;</span>);<br></code></pre></td></tr></table></figure><h4 id="2-实例方法引用（特定对象）"><a href="#2-实例方法引用（特定对象）" class="headerlink" title="(2) 实例方法引用（特定对象）"></a>(2) 实例方法引用（特定对象）</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 对象::实例方法</span><br><span class="hljs-type">String</span> <span class="hljs-variable">prefix</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;Mr. &quot;</span>;<br>Function&lt;String, String&gt; addPrefix = prefix::concat;<br><span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> addPrefix.apply(<span class="hljs-string">&quot;Smith&quot;</span>); <span class="hljs-comment">// &quot;Mr. Smith&quot;</span><br></code></pre></td></tr></table></figure><h4 id="3-实例方法引用（任意对象）"><a href="#3-实例方法引用（任意对象）" class="headerlink" title="(3) 实例方法引用（任意对象）"></a>(3) 实例方法引用（任意对象）</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 类名::实例方法</span><br>Function&lt;String, String&gt; upper = String::toUpperCase;<br><span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> upper.apply(<span class="hljs-string">&quot;hello&quot;</span>); <span class="hljs-comment">// &quot;HELLO&quot;</span><br></code></pre></td></tr></table></figure><h4 id="4-构造方法引用"><a href="#4-构造方法引用" class="headerlink" title="(4) 构造方法引用"></a>(4) 构造方法引用</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 类名::new</span><br>Supplier&lt;List&lt;String&gt;&gt; listSupplier = ArrayList::<span class="hljs-keyword">new</span>;<br>List&lt;String&gt; newList = listSupplier.get();<br></code></pre></td></tr></table></figure><h3 id="3-使用场景对比"><a href="#3-使用场景对比" class="headerlink" title="3. 使用场景对比"></a>3. 使用场景对比</h3><table><thead><tr><th>场景</th><th>Lambda表达式</th><th>方法引用</th></tr></thead><tbody><tr><td>简单操作</td><td><code>x -&gt; x.toUpperCase()</code></td><td><code>String::toUpperCase</code></td></tr><tr><td>静态方法调用</td><td><code>s -&gt; Integer.parseInt(s)</code></td><td><code>Integer::parseInt</code></td></tr><tr><td>对象创建</td><td><code>() -&gt; new ArrayList&lt;&gt;()</code></td><td><code>ArrayList::new</code></td></tr><tr><td>多参数操作</td><td><code>(a, b) -&gt; a.compareTo(b)</code></td><td><code>String::compareTo</code></td></tr></tbody></table><h2 id="三、函数式接口"><a href="#三、函数式接口" class="headerlink" title="三、函数式接口"></a>三、函数式接口</h2><h3 id="1-常用内置接口"><a href="#1-常用内置接口" class="headerlink" title="1. 常用内置接口"></a>1. 常用内置接口</h3><table><thead><tr><th>接口</th><th>方法</th><th>示例</th></tr></thead><tbody><tr><td><code>Function&lt;T,R&gt;</code></td><td><code>R apply(T t)</code></td><td>字符串转整数：<code>Function&lt;String, Integer&gt;</code></td></tr><tr><td><code>Predicate&lt;T&gt;</code></td><td><code>boolean test(T t)</code></td><td>过滤条件：<code>Predicate&lt;String&gt;</code></td></tr><tr><td><code>Consumer&lt;T&gt;</code></td><td><code>void accept(T t)</code></td><td>打印输出：<code>Consumer&lt;String&gt;</code></td></tr><tr><td><code>Supplier&lt;T&gt;</code></td><td><code>T get()</code></td><td>对象工厂：<code>Supplier&lt;LocalDate&gt;</code></td></tr><tr><td><code>BiFunction&lt;T,U,R&gt;</code></td><td><code>R apply(T t, U u)</code></td><td>两数相加：<code>BiFunction&lt;Integer, Integer, Integer&gt;</code></td></tr></tbody></table><h3 id="2-自定义函数式接口"><a href="#2-自定义函数式接口" class="headerlink" title="2. 自定义函数式接口"></a>2. 自定义函数式接口</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">StringProcessor</span> &#123;<br>    String <span class="hljs-title function_">process</span><span class="hljs-params">(String input)</span>;<br>    <br>    <span class="hljs-comment">// 可以有默认方法</span><br>    <span class="hljs-keyword">default</span> StringProcessor <span class="hljs-title function_">andThen</span><span class="hljs-params">(StringProcessor after)</span> &#123;<br>        <span class="hljs-keyword">return</span> input -&gt; after.process(<span class="hljs-built_in">this</span>.process(input));<br>    &#125;<br>&#125;<br><br><span class="hljs-comment">// 使用</span><br><span class="hljs-type">StringProcessor</span> <span class="hljs-variable">upper</span> <span class="hljs-operator">=</span> String::toUpperCase;<br><span class="hljs-type">StringProcessor</span> <span class="hljs-variable">addStars</span> <span class="hljs-operator">=</span> s -&gt; <span class="hljs-string">&quot;***&quot;</span> + s + <span class="hljs-string">&quot;***&quot;</span>;<br><span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> upper.andThen(addStars).process(<span class="hljs-string">&quot;hello&quot;</span>); <span class="hljs-comment">// &quot;***HELLO***&quot;</span><br></code></pre></td></tr></table></figure><h2 id="四、Stream-API结合使用"><a href="#四、Stream-API结合使用" class="headerlink" title="四、Stream API结合使用"></a>四、Stream API结合使用</h2><h3 id="1-Lambda在Stream中的应用"><a href="#1-Lambda在Stream中的应用" class="headerlink" title="1. Lambda在Stream中的应用"></a>1. Lambda在Stream中的应用</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; names = Arrays.asList(<span class="hljs-string">&quot;Alice&quot;</span>, <span class="hljs-string">&quot;Bob&quot;</span>, <span class="hljs-string">&quot;Charlie&quot;</span>, <span class="hljs-string">&quot;David&quot;</span>);<br><br><span class="hljs-comment">// 过滤+转换+收集</span><br>List&lt;String&gt; result = names.stream()<br>                        .filter(name -&gt; name.length() &gt; <span class="hljs-number">4</span>) <span class="hljs-comment">// Predicate</span><br>                        .map(String::toUpperCase)          <span class="hljs-comment">// Function</span><br>                        .collect(Collectors.toList());     <span class="hljs-comment">// Collector</span><br></code></pre></td></tr></table></figure><h3 id="2-方法引用优化"><a href="#2-方法引用优化" class="headerlink" title="2. 方法引用优化"></a>2. 方法引用优化</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 原始Lambda</span><br>names.forEach(name -&gt; System.out.println(name));<br><br><span class="hljs-comment">// 方法引用优化</span><br>names.forEach(System.out::println);<br></code></pre></td></tr></table></figure><h2 id="五、注意事项"><a href="#五、注意事项" class="headerlink" title="五、注意事项"></a>五、注意事项</h2><ol><li><p><strong>变量捕获</strong>：</p><ul><li>Lambda只能捕获final或等效final的局部变量</li><li>可以修改实例变量和静态变量</li></ul></li><li><p><strong>this关键字</strong>：</p><ul><li>Lambda中的this指向外部类，匿名内部类中的this指向自身</li></ul></li><li><p><strong>性能考虑</strong>：</p><ul><li>Lambda不会带来额外性能开销（JVM会优化）</li><li>简单操作用方法引用更高效</li></ul></li><li><p><strong>调试困难</strong>：</p><ul><li>Lambda堆栈跟踪可能较难阅读</li><li>复杂逻辑建议提取为独立方法</li></ul></li></ol><h2 id="六、代码对比示例"><a href="#六、代码对比示例" class="headerlink" title="六、代码对比示例"></a>六、代码对比示例</h2><h3 id="传统写法-vs-Lambda-vs-方法引用"><a href="#传统写法-vs-Lambda-vs-方法引用" class="headerlink" title="传统写法 vs Lambda vs 方法引用"></a>传统写法 vs Lambda vs 方法引用</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 1. 传统匿名类</span><br>Collections.sort(names, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Comparator</span>&lt;String&gt;() &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">compare</span><span class="hljs-params">(String a, String b)</span> &#123;<br>        <span class="hljs-keyword">return</span> a.compareToIgnoreCase(b);<br>    &#125;<br>&#125;);<br><br><span class="hljs-comment">// 2. Lambda表达式</span><br>Collections.sort(names, (a, b) -&gt; a.compareToIgnoreCase(b));<br><br><span class="hljs-comment">// 3. 方法引用</span><br>Collections.sort(names, String::compareToIgnoreCase);<br></code></pre></td></tr></table></figure><h2 id="七、最佳实践"><a href="#七、最佳实践" class="headerlink" title="七、最佳实践"></a>七、最佳实践</h2><ol><li><strong>简单操作</strong>：优先使用方法引用</li><li><strong>复杂逻辑</strong>：使用Lambda表达式</li><li><strong>可读性</strong>：当Lambda超过3行，考虑提取为方法</li><li><strong>重用性</strong>：常用Lambda可存储为静态final变量</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 重用Lambda示例</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Predicate&lt;String&gt; NON_EMPTY = s -&gt; s != <span class="hljs-literal">null</span> &amp;&amp; !s.trim().isEmpty();<br><br>List&lt;String&gt; validNames = names.stream()<br>                            .filter(NON_EMPTY)<br>                            .collect(Collectors.toList());<br></code></pre></td></tr></table></figure>]]></content>
    
  </entry>
  
  <entry>
    <title>Autowired和Resource的区别</title>
    <link href="https://alan.org.cn/posts/Autowired%E5%92%8CResource%E7%9A%84%E5%8C%BA%E5%88%AB.html"/>
    <id>https://alan.org.cn/posts/Autowired%E5%92%8CResource%E7%9A%84%E5%8C%BA%E5%88%AB.html</id>
    <published>2025-05-10T16:59:53.000Z</published>
    <updated>2025-05-10T16:59:53.000Z</updated>
    
    
    <category term="Spring注解" scheme="https://alan.org.cn/categories/Spring%E6%B3%A8%E8%A7%A3/"/>
    
    
    <category term="Spring注解" scheme="https://alan.org.cn/tags/Spring%E6%B3%A8%E8%A7%A3/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Autowired与-Resource&quot;&gt;&lt;a href=&quot;#Autowired与-Resource&quot; class=&quot;headerlink&quot; title=&quot;@Autowired与@Resource&quot;&gt;&lt;/a&gt;@Autowired与@Resource&lt;/h1&gt;&lt;p&gt;</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="Autowired与-Resource"><a href="#Autowired与-Resource" class="headerlink" title="@Autowired与@Resource"></a>@Autowired与@Resource</h1><p><code>@Autowired</code> 和 <code>@Resource</code> 是 Java 中常用的两种依赖注入注解，但它们的来源、行为和使用场景有所不同。以下是它们的核心区别：</p><hr><h3 id="1-来源不同"><a href="#1-来源不同" class="headerlink" title="1. 来源不同"></a><strong>1. 来源不同</strong></h3><ul><li><p><strong><code>@Autowired</code></strong>  </p><ul><li>属于 Spring 框架（<code>org.springframework.beans.factory.annotation</code>）。  </li><li>是 Spring 特有的注解，强耦合于 Spring 容器。</li></ul></li><li><p><strong><code>@Resource</code></strong>  </p><ul><li>属于 Java 标准（<code>javax.annotation.Resource</code>），遵循 JSR-250 规范。  </li><li>是 Java 的通用注解，不依赖 Spring，但 Spring 也支持它。</li></ul></li></ul><hr><h3 id="2-默认注入方式"><a href="#2-默认注入方式" class="headerlink" title="2. 默认注入方式"></a><strong>2. 默认注入方式</strong></h3><ul><li><p><strong><code>@Autowired</code></strong>  </p><ul><li><strong>默认按类型（byType）注入</strong>。  </li><li>如果存在多个相同类型的 Bean，会报 <code>NoUniqueBeanDefinitionException</code>，此时需要通过以下方式解决：  <ul><li>结合 <code>@Qualifier(&quot;beanName&quot;)</code> 指定 Bean 名称。  </li><li>使用 <code>@Primary</code> 标记优先注入的 Bean。</li></ul></li></ul></li><li><p><strong><code>@Resource</code></strong>  </p><ul><li><strong>默认按名称（byName）注入</strong>（如果没有显式指定名称，则使用字段名或方法名作为 Bean 名称）。  </li><li>如果按名称找不到 Bean，<strong>会回退到按类型（byType）注入</strong>。  </li><li>支持通过 <code>name</code> 属性直接指定 Bean 名称（如 <code>@Resource(name = &quot;myBean&quot;)</code>），无需额外注解。</li></ul></li></ul><hr><h3 id="3-适用范围"><a href="#3-适用范围" class="headerlink" title="3. 适用范围"></a><strong>3. 适用范围</strong></h3><ul><li><p><strong><code>@Autowired</code></strong>  </p><ul><li>可以用在字段、构造方法、Setter 方法、普通方法上。  </li><li>支持 Spring 的 <code>@Primary</code> 和 <code>@Qualifier</code> 细化注入。</li></ul></li><li><p><strong><code>@Resource</code></strong>  </p><ul><li>只能用在字段、Setter 方法上（<strong>不能用于构造方法</strong>）。  </li><li>无直接等效于 <code>@Primary</code> 的机制，依赖 <code>name</code> 属性明确指定 Bean。</li></ul></li></ul><hr><h3 id="4-依赖是否必须"><a href="#4-依赖是否必须" class="headerlink" title="4. 依赖是否必须"></a><strong>4. 依赖是否必须</strong></h3><ul><li><p><strong><code>@Autowired</code></strong>  </p><ul><li>默认要求依赖必须存在（<code>required = true</code>），否则抛 <code>NoSuchBeanDefinitionException</code>。  </li><li>可设置为 <code>@Autowired(required = false)</code>，允许注入失败（此时依赖为 <code>null</code>）。</li></ul></li><li><p><strong><code>@Resource</code></strong>  </p><ul><li>默认行为类似 <code>required = true</code>，但无显式的 <code>required</code> 属性配置。</li></ul></li></ul><hr><h3 id="5-使用场景建议"><a href="#5-使用场景建议" class="headerlink" title="5. 使用场景建议"></a><strong>5. 使用场景建议</strong></h3><ul><li><p>**优先用 <code>@Autowired</code>**：  </p><ul><li>项目完全基于 Spring，且需要更灵活的注入控制（如结合 <code>@Qualifier</code> 或 <code>@Primary</code>）。  </li><li>需要在构造方法中注入依赖（<code>@Resource</code> 不支持）。</li></ul></li><li><p>**优先用 <code>@Resource</code>**：  </p><ul><li>需要代码脱离 Spring 耦合（如兼容其他 DI 容器）。  </li><li>需要按名称注入且不希望引入额外注解（如 <code>@Qualifier</code>）。</li></ul></li></ul><hr><h3 id="代码示例对比"><a href="#代码示例对比" class="headerlink" title="代码示例对比"></a><strong>代码示例对比</strong></h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 使用 @Autowired（按类型 + 可选 @Qualifier）</span><br><span class="hljs-meta">@Autowired</span><br><span class="hljs-meta">@Qualifier(&quot;userServiceA&quot;)</span><br><span class="hljs-keyword">private</span> UserService userService;<br><br><span class="hljs-comment">// 使用 @Resource（按名称，无需额外注解）</span><br><span class="hljs-meta">@Resource(name = &quot;userServiceA&quot;)</span><br><span class="hljs-keyword">private</span> UserService userService;<br></code></pre></td></tr></table></figure><hr><h3 id="总结对比表"><a href="#总结对比表" class="headerlink" title="总结对比表"></a><strong>总结对比表</strong></h3><table><thead><tr><th>特性</th><th><code>@Autowired</code> (Spring)</th><th><code>@Resource</code> (JSR-250)</th></tr></thead><tbody><tr><td><strong>来源</strong></td><td>Spring 特有</td><td>Java 标准</td></tr><tr><td><strong>默认注入方式</strong></td><td>按类型（byType）</td><td>按名称（byName） → 回退按类型</td></tr><tr><td><strong>支持构造方法注入</strong></td><td>是</td><td>否</td></tr><tr><td><strong>指定 Bean 名称</strong></td><td>需配合 <code>@Qualifier</code></td><td>通过 <code>name</code> 属性</td></tr><tr><td><strong>是否必须依赖</strong></td><td>可配置 <code>required = false</code></td><td>默认必须</td></tr><tr><td><strong>与 Spring 解耦</strong></td><td>否</td><td>是</td></tr></tbody></table><p>根据项目需求和团队规范选择合适的注解。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>函数/工具类/设计模式｜何时用?</title>
    <link href="https://alan.org.cn/posts/%E5%87%BD%E6%95%B0-%E5%B7%A5%E5%85%B7%E7%B1%BB-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BD%9C%E4%BD%95%E6%97%B6%E7%94%A8.html"/>
    <id>https://alan.org.cn/posts/%E5%87%BD%E6%95%B0-%E5%B7%A5%E5%85%B7%E7%B1%BB-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BD%9C%E4%BD%95%E6%97%B6%E7%94%A8.html</id>
    <published>2025-05-10T16:11:26.000Z</published>
    <updated>2025-05-10T16:11:26.000Z</updated>
    
    
    <category term="软件设计" scheme="https://alan.org.cn/categories/%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1/"/>
    
    
    <category term="软件设计" scheme="https://alan.org.cn/tags/%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1/"/>
    
    
      
      
    <summary type="html">&lt;p&gt;在Java Spring Boot项目中，合理抽取函数、工具类或设计模式是提升代码可维护性、复用性和可读性的关键。以下是具体的判断标准和实践建议：&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;一、何时抽取成函数（方法）？&quot;&gt;&lt;a href=&quot;#一、何时抽取成函数（方法）？&quot; clas</summary>
      
    
    
    <content type="html"><![CDATA[<p>在Java Spring Boot项目中，合理抽取函数、工具类或设计模式是提升代码可维护性、复用性和可读性的关键。以下是具体的判断标准和实践建议：</p><hr><h3 id="一、何时抽取成函数（方法）？"><a href="#一、何时抽取成函数（方法）？" class="headerlink" title="一、何时抽取成函数（方法）？"></a><strong>一、何时抽取成函数（方法）？</strong></h3><h4 id="适用场景："><a href="#适用场景：" class="headerlink" title="适用场景："></a><strong>适用场景</strong>：</h4><ol><li><p><strong>重复代码片段</strong>：相同逻辑在多个地方出现（超过2次）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 重复代码（坏味道）</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">processOrderA</span><span class="hljs-params">(Order order)</span> &#123;<br>    <span class="hljs-keyword">if</span> (order.getStatus() == Status.PAID &amp;&amp; order.getItems().size() &gt; <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-comment">// ...业务逻辑</span><br>    &#125;<br>&#125;<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">processOrderB</span><span class="hljs-params">(Order order)</span> &#123;<br>    <span class="hljs-keyword">if</span> (order.getStatus() == Status.PAID &amp;&amp; order.getItems().size() &gt; <span class="hljs-number">0</span>) &#123;<br>        <span class="hljs-comment">// ...其他逻辑</span><br>    &#125;<br>&#125;<br><br><span class="hljs-comment">// 抽取为函数</span><br><span class="hljs-keyword">private</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isOrderValid</span><span class="hljs-params">(Order order)</span> &#123;<br>    <span class="hljs-keyword">return</span> order.getStatus() == Status.PAID &amp;&amp; order.getItems().size() &gt; <span class="hljs-number">0</span>;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p><strong>复杂逻辑拆分</strong>：一个方法过长（如超过20行）或嵌套过深。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 原始长方法</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">generateReport</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-comment">// 步骤1: 查询数据（可抽取）</span><br>    <span class="hljs-comment">// 步骤2: 处理数据（可抽取）</span><br>    <span class="hljs-comment">// 步骤3: 生成文件（可抽取）</span><br>&#125;<br></code></pre></td></tr></table></figure></li><li><p><strong>明确意图</strong>：通过方法名替代注释，提高可读性。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 模糊逻辑</span><br><span class="hljs-keyword">if</span> (user.getAge() &gt; <span class="hljs-number">18</span> &amp;&amp; user.isVerified()) &#123; ... &#125;<br><br><span class="hljs-comment">// 明确意图</span><br><span class="hljs-keyword">if</span> (isEligibleForPremium(user)) &#123; ... &#125;<br></code></pre></td></tr></table></figure></li></ol><h4 id="原则："><a href="#原则：" class="headerlink" title="原则："></a><strong>原则</strong>：</h4><ul><li><strong>单一职责</strong>：一个方法只做一件事。</li><li><strong>无副作用</strong>：尽量设计纯函数（输入决定输出，不修改外部状态）。</li></ul><hr><h3 id="二、何时抽取成工具类（Utils-Helper）？"><a href="#二、何时抽取成工具类（Utils-Helper）？" class="headerlink" title="二、何时抽取成工具类（Utils&#x2F;Helper）？"></a><strong>二、何时抽取成工具类（Utils&#x2F;Helper）？</strong></h3><h4 id="适用场景：-1"><a href="#适用场景：-1" class="headerlink" title="适用场景："></a><strong>适用场景</strong>：</h4><ol><li><p><strong>跨模块复用</strong>：与业务无关的通用功能（如字符串处理、日期转换、加密等）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 工具类示例</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">StringUtils</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title function_">trimToEmpty</span><span class="hljs-params">(String str)</span> &#123;<br>        <span class="hljs-keyword">return</span> str == <span class="hljs-literal">null</span> ? <span class="hljs-string">&quot;&quot;</span> : str.trim();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p><strong>静态方法为主</strong>：无需维护状态，仅提供工具方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// Spring的Assert工具类</span><br>Assert.notNull(obj, <span class="hljs-string">&quot;Object must not be null&quot;</span>);<br></code></pre></td></tr></table></figure></li><li><p><strong>避免重复造轮子</strong>：优先使用现有工具库（如Apache Commons、Guava、Spring Utils）。</p></li></ol><h4 id="原则：-1"><a href="#原则：-1" class="headerlink" title="原则："></a><strong>原则</strong>：</h4><ul><li><strong>命名清晰</strong>：如<code>DateUtils</code>、<code>HttpClientUtils</code>。</li><li><strong>不可实例化</strong>：工具类应为<code>final</code>+私有构造方法。 <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyUtils</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-title function_">MyUtils</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UnsupportedOperationException</span>();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li></ul><hr><h3 id="三、何时使用设计模式？"><a href="#三、何时使用设计模式？" class="headerlink" title="三、何时使用设计模式？"></a><strong>三、何时使用设计模式？</strong></h3><h4 id="适用场景：-2"><a href="#适用场景：-2" class="headerlink" title="适用场景："></a><strong>适用场景</strong>：</h4><ol><li><p><strong>策略模式（Strategy）</strong>：需要动态切换算法或行为。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 支付方式切换</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">PaymentStrategy</span> &#123;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">pay</span><span class="hljs-params">(BigDecimal amount)</span>;<br>&#125;<br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AlipayStrategy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">PaymentStrategy</span> &#123; ... &#125;<br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WechatPayStrategy</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">PaymentStrategy</span> &#123; ... &#125;<br><br><span class="hljs-comment">// 通过上下文调用</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">PaymentContext</span> &#123;<br>    <span class="hljs-keyword">private</span> PaymentStrategy strategy;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setStrategy</span><span class="hljs-params">(PaymentStrategy strategy)</span> &#123; ... &#125;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">executePayment</span><span class="hljs-params">(BigDecimal amount)</span> &#123;<br>        strategy.pay(amount);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p><strong>工厂模式（Factory）</strong>：复杂对象的创建逻辑封装。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">NotificationFactory</span> &#123;<br>    <span class="hljs-keyword">public</span> Notification <span class="hljs-title function_">create</span><span class="hljs-params">(String type)</span> &#123;<br>        <span class="hljs-keyword">switch</span> (type) &#123;<br>            <span class="hljs-keyword">case</span> <span class="hljs-string">&quot;SMS&quot;</span>: <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">SmsNotification</span>();<br>            <span class="hljs-keyword">case</span> <span class="hljs-string">&quot;EMAIL&quot;</span>: <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">EmailNotification</span>();<br>            <span class="hljs-keyword">default</span>: <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalArgumentException</span>();<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p><strong>装饰器模式（Decorator）</strong>：动态扩展功能。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CoffeeDecorator</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Coffee</span> &#123;<br>    <span class="hljs-keyword">protected</span> Coffee decoratedCoffee;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">CoffeeDecorator</span><span class="hljs-params">(Coffee coffee)</span> &#123;<br>        <span class="hljs-built_in">this</span>.decoratedCoffee = coffee;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></li><li><p><strong>观察者模式（Observer）</strong>：事件驱动场景（如Spring的<code>ApplicationEvent</code>）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@EventListener</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">handleOrderEvent</span><span class="hljs-params">(OrderEvent event)</span> &#123; ... &#125;<br></code></pre></td></tr></table></figure></li></ol><h4 id="原则：-2"><a href="#原则：-2" class="headerlink" title="原则："></a><strong>原则</strong>：</h4><ul><li><strong>避免过度设计</strong>：简单场景直接用if&#x2F;else，不要为了模式而模式。</li><li><strong>Spring整合</strong>：优先利用Spring的依赖注入、事件机制等原生能力。</li></ul><hr><h3 id="四、决策流程图"><a href="#四、决策流程图" class="headerlink" title="四、决策流程图"></a><strong>四、决策流程图</strong></h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs mermaid">graph TD<br>    A[发现代码问题] --&gt; B&#123;是否重复?&#125;<br>    B --&gt;|是| C&#123;是否业务无关?&#125;<br>    C --&gt;|是| D[抽取工具类]<br>    C --&gt;|否| E[抽取私有方法]<br>    B --&gt;|否| F&#123;方法是否过长/复杂?&#125;<br>    F --&gt;|是| G[拆分为多个方法]<br>    F --&gt;|否| H&#123;是否需要动态行为或扩展?&#125;<br>    H --&gt;|是| I[应用设计模式]<br>    H --&gt;|否| J[保持现状]<br></code></pre></td></tr></table></figure><hr><h3 id="五、Spring-Boot中的最佳实践"><a href="#五、Spring-Boot中的最佳实践" class="headerlink" title="五、Spring Boot中的最佳实践"></a><strong>五、Spring Boot中的最佳实践</strong></h3><ol><li><p><strong>工具类</strong>：</p><ul><li>使用<code>org.springframework.util</code>下的<code>Assert</code>、<code>StringUtils</code>等。</li><li>自定义工具类用<code>static</code>方法，避免注入Spring Bean。</li></ul></li><li><p><strong>业务逻辑</strong>：</p><ul><li>将核心业务抽取到<code>@Service</code>中，保持Controller轻薄。</li><li>领域逻辑优先用领域模型（Domain Model）而非Utils。</li></ul></li><li><p><strong>设计模式</strong>：</p><ul><li>利用<code>@Autowired</code>实现策略模式。</li><li>使用<code>@Bean</code>定义工厂方法。</li></ul></li></ol><hr><h3 id="示例对比"><a href="#示例对比" class="headerlink" title="示例对比"></a><strong>示例对比</strong></h3><h4 id="Bad-Practice"><a href="#Bad-Practice" class="headerlink" title="Bad Practice"></a><strong>Bad Practice</strong></h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// Controller中混杂业务逻辑</span><br><span class="hljs-meta">@PostMapping(&quot;/order&quot;)</span><br><span class="hljs-keyword">public</span> ResponseEntity&lt;?&gt; createOrder(<span class="hljs-meta">@RequestBody</span> OrderDto dto) &#123;<br>    <span class="hljs-comment">// 参数校验（应抽取）</span><br>    <span class="hljs-keyword">if</span> (dto.getItems() == <span class="hljs-literal">null</span> || dto.getItems().isEmpty()) &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">IllegalArgumentException</span>(<span class="hljs-string">&quot;订单项不能为空&quot;</span>);<br>    &#125;<br>    <span class="hljs-comment">// 价格计算（应抽取）</span><br>    <span class="hljs-type">BigDecimal</span> <span class="hljs-variable">total</span> <span class="hljs-operator">=</span> BigDecimal.ZERO;<br>    <span class="hljs-keyword">for</span> (Item item : dto.getItems()) &#123;<br>        total = total.add(item.getPrice().multiply(item.getQuantity()));<br>    &#125;<br>    <span class="hljs-comment">// 保存订单（应委托给Service）</span><br>    <span class="hljs-type">Order</span> <span class="hljs-variable">order</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Order</span>();<br>    order.setTotal(total);<br>    orderRepository.save(order);<br>    <span class="hljs-keyword">return</span> ResponseEntity.ok().build();<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="Good-Practice"><a href="#Good-Practice" class="headerlink" title="Good Practice"></a><strong>Good Practice</strong></h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 抽取校验逻辑到工具类</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderValidator</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">validate</span><span class="hljs-params">(OrderDto dto)</span> &#123;<br>        Assert.notEmpty(dto.getItems(), <span class="hljs-string">&quot;订单项不能为空&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-comment">// 抽取业务逻辑到Service</span><br><span class="hljs-meta">@Service</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderService</span> &#123;<br>    <span class="hljs-keyword">public</span> Order <span class="hljs-title function_">createOrder</span><span class="hljs-params">(OrderDto dto)</span> &#123;<br>        OrderValidator.validate(dto);<br>        <span class="hljs-type">BigDecimal</span> <span class="hljs-variable">total</span> <span class="hljs-operator">=</span> calculateTotal(dto.getItems());<br>        <span class="hljs-keyword">return</span> orderRepository.save(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Order</span>(total));<br>    &#125;<br>    <span class="hljs-keyword">private</span> BigDecimal <span class="hljs-title function_">calculateTotal</span><span class="hljs-params">(List&lt;Item&gt; items)</span> &#123;<br>        <span class="hljs-keyword">return</span> items.stream()<br>            .map(item -&gt; item.getPrice().multiply(item.getQuantity()))<br>            .reduce(BigDecimal.ZERO, BigDecimal::add);<br>    &#125;<br>&#125;<br><br><span class="hljs-comment">// Controller保持简洁</span><br><span class="hljs-meta">@PostMapping(&quot;/order&quot;)</span><br><span class="hljs-keyword">public</span> ResponseEntity&lt;Order&gt; <span class="hljs-title function_">createOrder</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> OrderDto dto)</span> &#123;<br>    <span class="hljs-keyword">return</span> ResponseEntity.ok(orderService.createOrder(dto));<br>&#125;<br></code></pre></td></tr></table></figure><hr><p>通过合理拆分函数、工具类和设计模式，可以使代码更符合 <strong>SOLID</strong> 原则，尤其在Spring Boot的上下文中，充分利用IoC和AOP能力，能显著提升工程效率。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>防御性编程</title>
    <link href="https://alan.org.cn/posts/%E9%98%B2%E5%BE%A1%E6%80%A7%E7%BC%96%E7%A8%8B.html"/>
    <id>https://alan.org.cn/posts/%E9%98%B2%E5%BE%A1%E6%80%A7%E7%BC%96%E7%A8%8B.html</id>
    <published>2025-05-10T15:27:48.000Z</published>
    <updated>2025-05-10T15:27:48.000Z</updated>
    
    
    <category term="软件设计" scheme="https://alan.org.cn/categories/%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1/"/>
    
    
    <category term="软件设计" scheme="https://alan.org.cn/tags/%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;防御性编程理论笔记&quot;&gt;&lt;a href=&quot;#防御性编程理论笔记&quot; class=&quot;headerlink&quot; title=&quot;防御性编程理论笔记&quot;&gt;&lt;/a&gt;防御性编程理论笔记&lt;/h1&gt;&lt;h2 id=&quot;1-防御性编程概述&quot;&gt;&lt;a href=&quot;#1-防御性编程概述&quot; class</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="防御性编程理论笔记"><a href="#防御性编程理论笔记" class="headerlink" title="防御性编程理论笔记"></a>防御性编程理论笔记</h1><h2 id="1-防御性编程概述"><a href="#1-防御性编程概述" class="headerlink" title="1. 防御性编程概述"></a>1. 防御性编程概述</h2><h3 id="1-1-定义"><a href="#1-1-定义" class="headerlink" title="1.1 定义"></a>1.1 定义</h3><p>防御性编程是一种软件开发方法，旨在通过预见和处理潜在问题来提高软件的健壮性和可靠性。其核心思想是”不信任”任何外部输入、依赖或环境条件，包括来自用户、其他系统、甚至程序员自身的数据和调用。</p><h3 id="1-2-基本原则"><a href="#1-2-基本原则" class="headerlink" title="1.2 基本原则"></a>1.2 基本原则</h3><ol><li>永远不要假设输入是有效的</li><li>永远不要假设环境是稳定的</li><li>永远不要假设代码不会被误用</li><li>所有错误都应该被明确处理</li><li>代码应该清晰表达其意图和约束</li></ol><h3 id="1-3-主要目标"><a href="#1-3-主要目标" class="headerlink" title="1.3 主要目标"></a>1.3 主要目标</h3><ul><li>防止软件在异常情况下崩溃</li><li>减少漏洞和安全风险</li><li>提高代码的可维护性</li><li>使错误更容易诊断和修复</li></ul><h2 id="2-输入验证"><a href="#2-输入验证" class="headerlink" title="2. 输入验证"></a>2. 输入验证</h2><h3 id="2-1-输入源分类"><a href="#2-1-输入源分类" class="headerlink" title="2.1 输入源分类"></a>2.1 输入源分类</h3><ol><li>用户输入：表单、命令行、GUI等</li><li>系统输入：文件、网络、API响应等</li><li>程序间输入：函数参数、方法调用等</li></ol><h3 id="2-2-验证策略"><a href="#2-2-验证策略" class="headerlink" title="2.2 验证策略"></a>2.2 验证策略</h3><ol><li>白名单验证：只允许已知有效的输入</li><li>黑名单验证：拒绝已知无效的输入（不推荐作为主要方法）</li><li>范围检查：数值、长度等限制</li><li>类型检查：确保输入符合预期类型</li><li>格式验证：正则表达式等模式匹配</li><li>业务逻辑验证：检查输入在业务上下文中的有效性</li></ol><h3 id="2-3-验证时机"><a href="#2-3-验证时机" class="headerlink" title="2.3 验证时机"></a>2.3 验证时机</h3><ol><li>尽早验证：在数据进入系统时立即验证</li><li>多次验证：在不同层次重复验证</li><li>边界验证：特别关注系统边界处的验证</li></ol><h2 id="3-错误处理"><a href="#3-错误处理" class="headerlink" title="3. 错误处理"></a>3. 错误处理</h2><h3 id="3-1-错误处理策略"><a href="#3-1-错误处理策略" class="headerlink" title="3.1 错误处理策略"></a>3.1 错误处理策略</h3><ol><li>错误预防：通过设计避免错误发生</li><li>错误检测：主动检查错误条件</li><li>错误恢复：从错误中恢复并继续执行</li><li>错误报告：向用户或日志提供有意义的信息</li></ol><h3 id="3-2-错误传播"><a href="#3-2-错误传播" class="headerlink" title="3.2 错误传播"></a>3.2 错误传播</h3><ol><li>返回码：使用特定返回值表示错误</li><li>异常处理：通过异常机制传播错误</li><li>回调函数：通过错误回调处理</li><li>全局状态：设置全局错误标志（谨慎使用）</li></ol><h3 id="3-3-错误处理最佳实践"><a href="#3-3-错误处理最佳实践" class="headerlink" title="3.3 错误处理最佳实践"></a>3.3 错误处理最佳实践</h3><ol><li>不要忽略错误</li><li>提供有意义的错误信息</li><li>记录错误上下文</li><li>考虑错误恢复策略</li><li>区分可恢复错误和不可恢复错误</li></ol><h2 id="4-断言与契约"><a href="#4-断言与契约" class="headerlink" title="4. 断言与契约"></a>4. 断言与契约</h2><h3 id="4-1-断言"><a href="#4-1-断言" class="headerlink" title="4.1 断言"></a>4.1 断言</h3><ol><li>前置条件：函数开始执行前必须满足的条件</li><li>后置条件：函数执行后必须满足的条件</li><li>不变式：在特定点必须始终满足的条件</li></ol><h3 id="4-2-设计契约"><a href="#4-2-设计契约" class="headerlink" title="4.2 设计契约"></a>4.2 设计契约</h3><ol><li>明确接口的期望和责任</li><li>定义调用方和被调用方的义务</li><li>使用契约式设计方法</li></ol><h3 id="4-3-断言使用指南"><a href="#4-3-断言使用指南" class="headerlink" title="4.3 断言使用指南"></a>4.3 断言使用指南</h3><ol><li>用于检查不应发生的条件</li><li>生产环境中可以禁用（但需谨慎）</li><li>不应替代输入验证</li><li>断言失败应导致明显失败</li></ol><h2 id="5-资源管理"><a href="#5-资源管理" class="headerlink" title="5. 资源管理"></a>5. 资源管理</h2><h3 id="5-1-资源类型"><a href="#5-1-资源类型" class="headerlink" title="5.1 资源类型"></a>5.1 资源类型</h3><ol><li>内存</li><li>文件句柄</li><li>网络连接</li><li>数据库连接</li><li>锁</li></ol><h3 id="5-2-资源管理原则"><a href="#5-2-资源管理原则" class="headerlink" title="5.2 资源管理原则"></a>5.2 资源管理原则</h3><ol><li>获取即初始化(RAII)</li><li>明确所有权</li><li>及时释放</li><li>限制资源使用量</li></ol><h3 id="5-3-资源管理策略"><a href="#5-3-资源管理策略" class="headerlink" title="5.3 资源管理策略"></a>5.3 资源管理策略</h3><ol><li>使用try-finally或类似结构</li><li>实现自动清理机制</li><li>设置资源使用上限</li><li>监控资源泄漏</li></ol><h2 id="6-并发编程防御"><a href="#6-并发编程防御" class="headerlink" title="6. 并发编程防御"></a>6. 并发编程防御</h2><h3 id="6-1-并发风险"><a href="#6-1-并发风险" class="headerlink" title="6.1 并发风险"></a>6.1 并发风险</h3><ol><li>竞态条件</li><li>死锁</li><li>活锁</li><li>资源争用</li></ol><h3 id="6-2-防御策略"><a href="#6-2-防御策略" class="headerlink" title="6.2 防御策略"></a>6.2 防御策略</h3><ol><li>最小化共享状态</li><li>使用不可变对象</li><li>正确使用同步机制</li><li>避免嵌套锁</li><li>使用线程安全的数据结构</li></ol><h3 id="6-3-并发测试"><a href="#6-3-并发测试" class="headerlink" title="6.3 并发测试"></a>6.3 并发测试</h3><ol><li>压力测试</li><li>随机延迟测试</li><li>静态分析工具</li></ol><h2 id="7-安全考虑"><a href="#7-安全考虑" class="headerlink" title="7. 安全考虑"></a>7. 安全考虑</h2><h3 id="7-1-常见安全威胁"><a href="#7-1-常见安全威胁" class="headerlink" title="7.1 常见安全威胁"></a>7.1 常见安全威胁</h3><ol><li>缓冲区溢出</li><li>SQL注入</li><li>跨站脚本(XSS)</li><li>跨站请求伪造(CSRF)</li><li>权限提升</li></ol><h3 id="7-2-安全编程实践"><a href="#7-2-安全编程实践" class="headerlink" title="7.2 安全编程实践"></a>7.2 安全编程实践</h3><ol><li>最小权限原则</li><li>深度防御</li><li>安全默认值</li><li>输入净化</li><li>输出编码</li></ol><h2 id="8-代码质量与可维护性"><a href="#8-代码质量与可维护性" class="headerlink" title="8. 代码质量与可维护性"></a>8. 代码质量与可维护性</h2><h3 id="8-1-代码清晰性"><a href="#8-1-代码清晰性" class="headerlink" title="8.1 代码清晰性"></a>8.1 代码清晰性</h3><ol><li>有意义的命名</li><li>适当的注释</li><li>一致的风格</li><li>合理的模块化</li></ol><h3 id="8-2-可测试性"><a href="#8-2-可测试性" class="headerlink" title="8.2 可测试性"></a>8.2 可测试性</h3><ol><li>单一职责</li><li>低耦合</li><li>可注入依赖</li><li>明确的接口</li></ol><h3 id="8-3-文档化"><a href="#8-3-文档化" class="headerlink" title="8.3 文档化"></a>8.3 文档化</h3><ol><li>API文档</li><li>设计决策记录</li><li>假设和限制说明</li><li>使用示例</li></ol><h2 id="9-防御性编程模式"><a href="#9-防御性编程模式" class="headerlink" title="9. 防御性编程模式"></a>9. 防御性编程模式</h2><h3 id="9-1-空对象模式"><a href="#9-1-空对象模式" class="headerlink" title="9.1 空对象模式"></a>9.1 空对象模式</h3><p>提供默认行为而非返回null</p><h3 id="9-2-哨兵值"><a href="#9-2-哨兵值" class="headerlink" title="9.2 哨兵值"></a>9.2 哨兵值</h3><p>使用特殊值表示特定状态</p><h3 id="9-3-保护性子句"><a href="#9-3-保护性子句" class="headerlink" title="9.3 保护性子句"></a>9.3 保护性子句</h3><p>提前返回以减少嵌套</p><h3 id="9-4-不变性"><a href="#9-4-不变性" class="headerlink" title="9.4 不变性"></a>9.4 不变性</h3><p>使用不可变对象避免意外修改</p><h3 id="9-5-包装器"><a href="#9-5-包装器" class="headerlink" title="9.5 包装器"></a>9.5 包装器</h3><p>在不信任的接口周围添加安全层</p><h2 id="10-测试与验证"><a href="#10-测试与验证" class="headerlink" title="10. 测试与验证"></a>10. 测试与验证</h2><h3 id="10-1-测试策略"><a href="#10-1-测试策略" class="headerlink" title="10.1 测试策略"></a>10.1 测试策略</h3><ol><li>单元测试</li><li>集成测试</li><li>模糊测试</li><li>边界测试</li><li>负面测试</li></ol><h3 id="10-2-静态分析"><a href="#10-2-静态分析" class="headerlink" title="10.2 静态分析"></a>10.2 静态分析</h3><ol><li>代码审查</li><li>静态分析工具</li><li>类型系统利用</li><li>契约检查</li></ol><h3 id="10-3-运行时监控"><a href="#10-3-运行时监控" class="headerlink" title="10.3 运行时监控"></a>10.3 运行时监控</h3><ol><li>日志记录</li><li>性能监控</li><li>异常跟踪</li><li>健康检查</li></ol><h2 id="11-防御性编程的局限性"><a href="#11-防御性编程的局限性" class="headerlink" title="11. 防御性编程的局限性"></a>11. 防御性编程的局限性</h2><ol><li>可能增加代码复杂性</li><li>可能影响性能</li><li>需要权衡防御程度</li><li>不能替代良好的设计和架构</li><li>过度防御可能导致掩盖真正问题</li></ol><h2 id="12-实施建议"><a href="#12-实施建议" class="headerlink" title="12. 实施建议"></a>12. 实施建议</h2><ol><li>将防御性编程纳入代码审查标准</li><li>建立团队编码规范</li><li>使用静态分析工具</li><li>编写防御性编程检查清单</li><li>定期进行安全培训</li></ol>]]></content>
    
  </entry>
  
  <entry>
    <title>JUC笔记</title>
    <link href="https://alan.org.cn/posts/JUC%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/JUC%E7%AC%94%E8%AE%B0.html</id>
    <published>2025-05-05T08:57:58.000Z</published>
    <updated>2025-05-05T08:57:58.000Z</updated>
    
    
    <category term="JAVA高级" scheme="https://alan.org.cn/categories/JAVA%E9%AB%98%E7%BA%A7/"/>
    
    
    <category term="JAVA高级" scheme="https://alan.org.cn/tags/JAVA%E9%AB%98%E7%BA%A7/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一把锁、2个并、3个程&quot;&gt;&lt;a href=&quot;#一把锁、2个并、3个程&quot; class=&quot;headerlink&quot; title=&quot;一把锁、2个并、3个程&quot;&gt;&lt;/a&gt;一把锁、2个并、3个程&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;一把锁&lt;/p&gt;
&lt;p&gt;s&lt;/p&gt;
&lt;/li&gt;
&lt;l</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="一把锁、2个并、3个程"><a href="#一把锁、2个并、3个程" class="headerlink" title="一把锁、2个并、3个程"></a>一把锁、2个并、3个程</h2><ul><li><p>一把锁</p><p>s</p></li><li><p>2个并</p><ul><li>并发：两个任务，统一处理器交替执行</li><li>并行：两个任务同时处理</li></ul></li><li><p>3个程</p><ul><li>进程：一个应用程序就是一个进程</li><li>线程：一个进程下有多个线程</li><li>管程：Monitor（监视器），也就是常说的锁，JVM中同步是基于进入和退出监视器对象来实现的，每个对象实例都会有一个Monitor对象</li></ul></li></ul><h2 id="用户现场与守护线程"><a href="#用户现场与守护线程" class="headerlink" title="用户现场与守护线程"></a>用户现场与守护线程</h2><p>一般情况下不做特别说明，默认都是用户线程</p><ul><li>用户线程：是系统的工作线程，完成这个程序需要完成的业务操作</li><li>守护线程：特殊的线程，为其他线程服务的，默默的执行系统行的服务，服务线程，用户线程结束，虚拟机会自动退出守护线程</li></ul><p>判断是用户线程还是守护线程</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isDaemon</span><span class="hljs-params">()</span>&#123;<br>  <span class="hljs-keyword">return</span> daemon;<br>&#125;<br></code></pre></td></tr></table></figure><p>true：守护线程</p><p>false：用户线程</p><h2 id="CompletableFuture的引出"><a href="#CompletableFuture的引出" class="headerlink" title="CompletableFuture的引出"></a>CompletableFuture的引出</h2><h3 id="Future为什么出现"><a href="#Future为什么出现" class="headerlink" title="Future为什么出现"></a>Future为什么出现</h3><p>Future(FutureTask实现类)接口定义了一步任务执行的一些方法，获取执行结果，取消任务，判断任务是否被取消，判断任务执行是否完毕</p><p>Future是java5提供的接口，提供了一步并行计算的功能</p><p>主线程执行一个很耗时的计算任务，我们就可以把这个任务放到一步线程中，主线程继续执行其他任务，在通过Future获取计算结果</p><p>Runnable接口没有返回值</p><p>Callable接口有返回值</p><p>Future接口和FutureTask实现类</p><p>需要实现：多线程&#x2F;有返回&#x2F;异步</p><p>FutureTask+线程池的缺点：</p><ol><li><p>get()阻塞： 容易导致阻塞，一般建议放在程序后面，一旦调用必须获得结果才执行下一步</p><p>加入不想等待太长时间，希望过时不侯，自动离开，get可以加入超时时间，过时抛异常，，我们就可以捕获异常做相应的处理</p></li><li><p>isDone()轮训：轮训会损耗无谓的cpu资源</p></li></ol><p>get容易导致阻塞，我们可以判断future的状态，什么时候完成了什么时候调用，就不会容易导致阻塞，实际工作不建议直接用get，非常容易阻塞</p><p>futureTask.isDone()判断任务是否完成</p><p>对于简单的业务场景使用Future完全OK</p><p>回调通知：主动通知</p><p>创建异步任务：Future+线程池配合</p><p>多个任务前后依赖可以组合处理：</p><p>​后一个异步任务，依赖于前一个异步任务的值</p><p>​两个或多个异步任务计算合并成一个异步计算</p><p>对计算速度选最快</p><p>阻塞的方式和异步编程的设计理念相违背，而轮训的方式会耗费无谓的CPU资源。因此</p><h2 id="CompletableFuture"><a href="#CompletableFuture" class="headerlink" title="CompletableFuture"></a>CompletableFuture</h2><p>引入了CompletableFuture</p><p>CompletableFuture提供了一种观察者模式类似的机制，可以让任务执行完成后通知监听的一方</p><p>CompletableFuture实现了Future和CompletionStage</p><p>CompletionStage 代表异步计算过程中的某一个阶段，一个阶段完成以后可能会触发另一个阶段</p><h3 id="核心的四个静态方法"><a href="#核心的四个静态方法" class="headerlink" title="核心的四个静态方法"></a>核心的四个静态方法</h3><ul><li>runAsync无 返回值<ul><li>public static CompletableFuture<Void> runAsync(Runable runable)</li><li>public static CompletableFuture<Void> runAsync(Runable runable,Executor executor)</li></ul></li><li>supplyAsync 有 返回值<ul><li>public static<U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)</li><li>public static<U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)</li></ul></li></ul><p>Executor executor说明：如果没有指定默认使用ForkJoinPool.commonPool()作为它的线程池</p><p>案例：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CompleableFutureBuildDemo</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ExecutionException, InterruptedException &#123;<br>        <span class="hljs-type">ExecutorService</span> <span class="hljs-variable">executorService</span> <span class="hljs-operator">=</span> Executors.newFixedThreadPool(<span class="hljs-number">3</span>);<br>        <span class="hljs-comment">// CompletableFuture&lt;Void&gt; voidCompletableFuture = CompletableFuture.runAsync(() -&gt; &#123;</span><br>        <span class="hljs-comment">//     System.out.println(Thread.currentThread().getName());</span><br>        <span class="hljs-comment">//     try &#123;</span><br>        <span class="hljs-comment">//         TimeUnit.SECONDS.sleep(1);</span><br>        <span class="hljs-comment">//     &#125; catch (InterruptedException e) &#123;</span><br>        <span class="hljs-comment">//         e.printStackTrace();</span><br>        <span class="hljs-comment">//     &#125;</span><br>        <span class="hljs-comment">// &#125;,executorService);</span><br>        <span class="hljs-comment">// System.out.println(voidCompletableFuture.get());</span><br><br>        CompletableFuture&lt;String&gt; objectCompletableFuture = CompletableFuture.supplyAsync(()-&gt;&#123;<br>                System.out.println(Thread.currentThread().getName());<br>                <span class="hljs-keyword">try</span> &#123;<br>                    TimeUnit.SECONDS.sleep(<span class="hljs-number">1</span>);<br>                &#125; <span class="hljs-keyword">catch</span> (InterruptedException e) &#123;<br>                    e.printStackTrace();<br>                &#125;<br>                <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;hello&quot;</span>;<br>        &#125;);<br>        System.out.println(objectCompletableFuture.get());<br>        executorService.shutdown();<br>    &#125;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>通用功能和平常注意事项</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">whenComplete((v,e)-&gt;&#123;&#125;) 处理上一个任务的返回值<br>exceptionally(e-&gt;&#123;&#125;) 处理上个任务的异常<br></code></pre></td></tr></table></figure><p>主线程不要立即结束，否则compleableFuture默认使用的线程池会立即关闭，通常使用自定义线程池避免这个问题</p><p>CompleableFuture的优点：</p><ol><li>异步任务结束时，会自动回调某个对象的方法</li><li>主线程设置好回调后，不再关心异步任务的执行，异步任务之间可以顺序执行</li><li>异步任务出错时，会自动回调某个对象的方法</li></ol><p>函数式接口：</p><ul><li>Runable   无参数，无返回值</li><li>Function(功能型函数式接口)  有一个输入参数，有一个返回参数</li><li>Consume(消费型函数式接口) 有一个输入参数，没有返回值<ul><li>BiConsumer 有两个输入参数，没有返回值</li></ul></li><li>Supplier(供给型函数式接口)没有输入参数，有一个返回值</li></ul><table><thead><tr><th>函数式接口名称</th><th>方法名称</th><th>参数</th><th>返回值</th></tr></thead><tbody><tr><td>Runable</td><td>run</td><td>0</td><td>0</td></tr><tr><td>Function</td><td>apply</td><td>1</td><td>1</td></tr><tr><td>Consume</td><td>accept</td><td>1</td><td>0</td></tr><tr><td>BiConsumer</td><td>accept</td><td>2</td><td>0</td></tr><tr><td>supplier</td><td>get</td><td>0</td><td>1</td></tr></tbody></table><p>链式调用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Accessors(chain=true)</span> <span class="hljs-comment">// 开启链式调用</span><br></code></pre></td></tr></table></figure><p>join和get的区别就是抛不抛异常的问题</p><h3 id="CompleableFuture常用方法"><a href="#CompleableFuture常用方法" class="headerlink" title="CompleableFuture常用方法"></a>CompleableFuture常用方法</h3><ol><li><p>获得结果和触发计算</p><ol><li><p>获得结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 后去结果需要抛异常</span><br><span class="hljs-keyword">public</span> T <span class="hljs-title function_">get</span><span class="hljs-params">()</span><br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> T <span class="hljs-title function_">get</span><span class="hljs-params">(<span class="hljs-type">long</span> timeout,TimeUnit unit)</span><br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 获取结果不用抛异常</span><br><span class="hljs-keyword">public</span> T <span class="hljs-title function_">join</span><span class="hljs-params">()</span><br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 如果计算完则获得结果，否则返回valueIfAbsent</span><br><span class="hljs-keyword">public</span> T <span class="hljs-title function_">getNow</span><span class="hljs-params">(T valueIfAbsent)</span> <br></code></pre></td></tr></table></figure></li><li><p>主动触发计算</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 是否打断并立即获得括号中的值</span><br><span class="hljs-comment">// 计算完成时，返回false，后面用join获取值就是计算后的值</span><br><span class="hljs-comment">// 计算未完成时，返回true，后面获取值时就是括号中设置的值</span><br><span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">complete</span><span class="hljs-params">(T value)</span> <br></code></pre></td></tr></table></figure></li></ol></li><li><p>对计算结果</p><ol><li>thenApply<ol><li>计算结果存在依赖关系，这两个线程串行化</li><li>由于存在依赖管理（当前步骤错，不走下一步），当前步骤有异常的话就叫停</li></ol></li><li>handle<ol><li>计算结果存在依赖关系，这两个线程串行化</li><li>有异常也可以往下一步走，根据带的异常参数可以进行下一步的处理</li></ol></li></ol></li><li><p>对计算结果进行消费</p><ol><li><p>thenAccept:接收任务的处理结果，并消费处理，<strong>无返回结果</strong> </p></li><li><p>代码之前的顺序执行问题</p><ol><li>thenRun<ul><li>thenRun(Runable runable) 任务A执行完执行B，并且B不需要A的结果</li></ul></li><li>thenAccept<ul><li>thenAccept(Consumer action) 任务A执行完执行B，B需要A的结果，但是任务B<strong>无返回值</strong></li></ul></li><li>thenApply<ul><li>thenApply(Function fn) 任务A执行完执行B，B需要A的结果，同时任务B是<strong>有返回值的</strong></li></ul></li></ol></li><li><p>CompleableFuture和线程池说明</p><ul><li>没有传入自定义线程池，都用默认线程池ForkJoinPool</li><li>传入了一个自定义线程池<ul><li>如果是thenRun 就使用前一个的线程池</li><li>如果是thenRunAsync，就不会使用前一个的线程池，会使用默认的ForkJoinPool</li></ul></li><li>有可能处理太快，系统优化切换原则，直接使用main线程处理</li></ul></li><li><p>对计算速度进行选用</p><ol><li><p>谁快用谁</p></li><li><p>applyToEither</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 那个先计算出结果，就返回那个的compleableFuture对象</span><br>cpC = cpA.applyToEither(cpB,f-&gt;&#123;<span class="hljs-keyword">return</span> f+ <span class="hljs-string">&quot;is winer&quot;</span>&#125;)<br></code></pre></td></tr></table></figure></li></ol></li><li><p>对计算结果进行合并</p><ol><li><p>连个CompletionStage任务都完成后，最终能把两个任务的结果一起交给thenCombine来处理</p></li><li><p>先完成的先等着，等待其他分支任务</p></li><li><p>thenCombine</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">cp&lt;T&gt; result = cpA.thenCombine(cpB,(x,y)-&gt;&#123;<span class="hljs-keyword">return</span> x+y&#125;);<br></code></pre></td></tr></table></figure></li></ol></li></ol></li></ol><h2 id="多线程锁之线程锁知识概念"><a href="#多线程锁之线程锁知识概念" class="headerlink" title="多线程锁之线程锁知识概念"></a>多线程锁之线程锁知识概念</h2><p>锁的类别：</p><ol><li>乐观锁和悲观锁</li><li>公平锁和非公平锁</li><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>认为自己在使用数据的时候一定有别的线程来修改数据，因此在获取数据的时候会先加锁，确保数据不会被别的线程修改</p><p>适合写操作多的场景，先加锁可以保证写操作时数据正确</p><p>显式的锁定之后再操作同步资源</p><p>synchronized关键字和Lock的实现类都是悲观锁</p><h4 id="乐观锁"><a href="#乐观锁" class="headerlink" title="乐观锁"></a>乐观锁</h4><p>认为自己在使用数据的时候不会有别的线程修改数据或资源，所以不会添加锁</p><p>在java中时通过使用无锁编程来实现的，知识在更新数据的时候去判断，之前有没有别的线程更新了这个数据</p><p>如果这个数据没有被更新，当前线程将自己修改的数据成功写入</p><p>如果这个数据已经被其他线程更新，则更具不同的实现方式执行不同的操作，比如放弃修改，重试抢锁等等。</p><p>判断规则：</p><ol><li>版本号机制Version</li><li>最常采用的是CAS算法（Compare-and-swap,比较并替换），Java原子类中的递增操作就通过CAS自旋实现的</li></ol><p>适合赌操作多的场景，不加锁的特点能够使其他读操作的性能大幅提升</p><p>乐观锁则直接去操作同步资源，是一种无锁算法，得之我幸不得我命，在努力就是</p><hr><h3 id="8锁案例"><a href="#8锁案例" class="headerlink" title="8锁案例"></a>8锁案例</h3><ol><li>标准访问有ab两个线程，请问先打印邮件还是短信</li><li>sendEmail方法中加入暂停3秒，请问先打印邮件还是短信</li><li>添加一个普通的hello方法，请问先打印邮件还是hello</li><li>有两部手机，请问先打印邮件还是短信</li><li>有两个静态同步方法，有1部手机，请问先打印邮件还是短信</li><li>有两个静态同步方法，有2部手机，请问先打印邮件还是短信</li><li>有一个静态同步方法，有一个普通同步方法，有1部手机，请问先打印邮件还是短信</li><li>有一个静态同步方法，有一个普通同步方法，有2部手机，请问先打印邮件还是短信</li></ol><p>详细说明：</p><ul><li>第一种情况：</li></ul><p>因为syncronized是悲观锁，所以同一时刻只能有一个线程可以进入该资源类，所有先打印邮件，再打印短信</p><p>一个资源类里面，假如有100个syncronized方法，只要其中一个被访问了，我锁的不是那一个方法，锁的是syncronized方法所在的整个资源类</p><ul><li>第二种情况：</li></ul><p>同第一种情况，先打印邮件，后打印短信</p><p><strong>总结第一和第二种情况：</strong></p><p>一个对象里面如果有多个syncronized方法，某一个时刻内，值要一个线程前调用其中的一个syncronized方法了</p><p>其他线程都只能等待，换句话说，某一个时刻内，只能有唯一的一个线程前访问这写syncronized方法</p><p>锁的是当前对象this，被锁后，其他的线程都不能进入到当前对象的其他syncronized方法</p><p><strong>总结第三种和第四种情况：</strong></p><p>加个普通方法后发现和同步锁无关</p><p>缓存两个对象后，不是同一把锁，情况立刻变化</p><p><strong>总结第五种和第六种情况：</strong></p><p> 三种synchronized锁的内容有一些差别：</p><p>对于普通同步方法，锁的是当前实例对象，通常指this，具体的异步手机，所有的普通同步方法用的都是同一把锁-&gt;实例对象本身，</p><p>对于静态同步方法，锁的是当前类的Class对象，如iphone.class唯一的一个模板</p><p>对于同步方法块，锁的是synchronized括号内的对象</p><p><strong>总结第七种和第八种情况：</strong></p><p>当一个线程试图访问同步代码时它首先必须得到锁，正常退出或抛出异常时必须释放锁</p><p>所有的普通同步方法用的都是同一把锁–实例对象本身，这就是new出来的具体实例对象本身，本类this</p><p>也就是说如果一个实例对象的普通同步方法获取锁后，该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁</p><p>所有的静态同步方法用的也是同一把锁—类对象本身，就是我们说过的唯一模板Class</p><p>具体实例对象this和唯一模板Class这两把锁锁两个不同的对象，所以静态同步方法与普通同步方法之间时不会有竞态条件的，但是一旦一个静态同步方法获取锁后，其他的静态同步方法都必须等待该方法释放锁后才能获得锁</p><p>synchronized体现的3个地方</p><ul><li>对象锁：作用与实例方法，当前实例加锁，进入同步代码前要获得当前实例的锁</li><li>作用与代码块，对括号里的配置对象加锁</li><li>类锁：作用与静态方法，当前类加锁，进去同步代码前要获得当前类对象的锁</li></ul><p>文件反编译：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">javap - c *.class<br></code></pre></td></tr></table></figure><ul><li><p>-c对代码进行反编译</p></li><li><p>假如需要更多信息</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">javap -v *.class<br></code></pre></td></tr></table></figure><ul><li>-v -verbose 输出附加信息</li></ul></li></ul><p>synchronized同步代码块</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">monitorenter  <span class="hljs-comment">// synchronized加锁开始</span><br>  <br>monitorexit <span class="hljs-comment">// synchronized正常释放</span><br>monitorexit <span class="hljs-comment">// 如果程序出错也要释放锁</span><br></code></pre></td></tr></table></figure><p>一般情况一个enter两个exit</p><p>synchronized方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">ACC_SYNCHRONIZED <span class="hljs-comment">// 开始加锁</span><br></code></pre></td></tr></table></figure><p>synchronized静态方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">ACC_STATIC,ACC_SYNCHRONIZED<br></code></pre></td></tr></table></figure><p>什么事管程Monitor，就是锁，也成为监视器，是一种程序结构，结构内的多个子程序（对象或模块）形成多个工作线程互斥访问共享资源</p><p>为什么任何一个对象都可以成为一个锁</p><p>因为在底层创建对象时，每个对象天生都带着一个对象监视器</p><p>每一个被锁住的对象都会和Monitor关联起来</p><h3 id="公平锁与非公平锁"><a href="#公平锁与非公平锁" class="headerlink" title="公平锁与非公平锁"></a>公平锁与非公平锁</h3><p>java共同抢夺同一份资源时避免乱序和挤压保证数据的一致性和安全性都要先持有锁各个线程获取锁的几率是不一样的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">ReentrantLock</span> <span class="hljs-variable">log</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ReentrantLock</span>(); <span class="hljs-comment">// 默认非公平锁，ReentrantLock(true)公平锁</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sale</span><span class="hljs-params">()</span>&#123;<br>  lock.lock();<br>  <span class="hljs-keyword">try</span>&#123;<br>    <span class="hljs-keyword">if</span>(number &gt;<span class="hljs-number">0</span>)&#123;<br>      sout(<span class="hljs-string">&quot;test&quot;</span>)<br>    &#125;<span class="hljs-keyword">finally</span>&#123;<br>      lock.unlock();<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><ul><li>公平锁（ReentrantLock(true)）：先来先得 </li><li>非公平锁（ReentrantLock(false)），后来的也可能先获得锁</li></ul><p>面试题：为什么会有公平锁&#x2F;非公平锁的设计？为什么默认非公平锁</p><ol><li><p>恢复挂起的线程到真正锁的获取还是有时间差的，从开发人员来看这个时间微乎其微，但是从cpu的角度来看，这个时间差存在的还是很明显。所以非公平锁能更充分的利用CPU的时间片，尽量减少CPU的空闲状态时间</p></li><li><p>使用多线程很重要的考量点事线程切换的开销，当采用非公平锁时，当一个线程请求锁获得同步状态，然后释放同步状态，所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大，所以就减少了线程大开销</p></li></ol><h3 id="可重入锁"><a href="#可重入锁" class="headerlink" title="可重入锁"></a>可重入锁</h3><p>可重入锁又称为递归锁是指在同一个线程在外层方法获取锁的时候，再进入该线程的内层方法会自动获取锁（前提，锁对象得是同一个），不会因为之前已经获取过还没释放而阻塞</p><p>就是一个synchronized方法，拥有一把锁，方法里面还有synchronized代码块也是同一把锁，那么进入里面锁的时候，不会释放外面的</p><p>直接可以获取自己的内部锁</p><p>一个线程中的多个流程可以获得同一把锁，持有这把同步锁可以再次进入</p><p>可重入锁的种类</p><ul><li>隐式锁：及synchronized关键字使用的锁，默认是可重入锁<ul><li>指可以重复递归调用的锁，在外层使用锁之后，在内层仍然可以使用，并且不会发生死锁，这样的锁就叫可重入锁</li><li>一个synchronized修饰的方法或代码块的内部调用本类的前提synchronized修饰的方式或代码块时，是永远可以得到锁</li></ul></li><li>Synchronized的重入的实现机理<ul><li>为什么任何一个对象都可以成为一个锁<ul><li>每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针<ul><li>当执行monitorenter时，如果目标锁对象的计数器为零，那么说明它没有被其他线程所持有，java虚拟机会将改对象的持有线程设置为当前线程，并且将其计数器加1</li><li>在目标所对象的计数器不为0的情况下，如果锁对象的持有线程，那么java虚拟机可以将其计数器加1，否则需要等待，直至持有线程释放该锁</li><li>当执行monitoreexit时java虚拟机则需要将锁对象的计数器-1，计数器为0代表锁已被释放</li></ul></li></ul></li></ul></li><li>显式锁：及Lock，也有ReentrantLock这样的可重入锁 <ul><li>lock和unlock要一一匹配</li><li>要注意上面的+1和-1</li></ul></li></ul><h3 id="死锁案例和排查命令"><a href="#死锁案例和排查命令" class="headerlink" title="死锁案例和排查命令"></a>死锁案例和排查命令</h3><p>死锁：两个及以上的线程因争抢资源而造成互相等待的现象</p><p>产生死锁的主要原因：</p><ol><li>系统资源不足</li><li>进程运行推进的顺序不合适</li><li>资源分配不当</li></ol><p>写死锁：A锁操作B锁，B锁操作A锁</p><p>如何排查死锁：</p><ul><li>第一种：<ul><li>Jsp -l &#x2F;java p s -ef ：查出进程编号</li><li>jstack 进程编号</li></ul></li><li>第二种：<ul><li>jconsole <ul><li>连接查询</li><li>点击线程</li><li>检查死锁</li></ul></li></ul></li></ul><p>会显示 deadlock</p><h3 id="多线程锁之ObjectMonitor和syncronized小结"><a href="#多线程锁之ObjectMonitor和syncronized小结" class="headerlink" title="多线程锁之ObjectMonitor和syncronized小结"></a>多线程锁之ObjectMonitor和syncronized小结</h3><ul><li>8锁案例<ul><li>对象锁</li><li>类锁</li></ul></li><li>公平锁和非公平锁</li><li>可重入锁（递归锁）</li><li>死锁及排查</li><li>为什么任何一个对象都可以成为一个锁<ul><li>objectMonitor.hpp</li></ul></li></ul><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-a5f253872268739302f7e48f39e3f575-5da626.png" alt="截屏2025-05-09 上午12.11.47"></p><h2 id="LogckSupport与线程中断"><a href="#LogckSupport与线程中断" class="headerlink" title="LogckSupport与线程中断"></a>LogckSupport与线程中断</h2><h3 id="线程中断机制"><a href="#线程中断机制" class="headerlink" title="线程中断机制"></a>线程中断机制</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">void</span> <span class="hljs-title function_">interrupt</span><span class="hljs-params">()</span>; <span class="hljs-comment">// 中断此线程</span><br><span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">interrupted</span><span class="hljs-params">()</span>; <span class="hljs-comment">// 测试当前线程是否已被中断</span><br><span class="hljs-type">boolean</span> <span class="hljs-title function_">isInterrupted</span><span class="hljs-params">()</span>; <span class="hljs-comment">// 测试此线程是否已被中断</span><br></code></pre></td></tr></table></figure><p>问题：</p><ol><li>如何中断一个运行中的线程</li><li>如何停止一个运行中的线程</li></ol><p>介绍：</p><p>一个线程不应该由其他线程来强制中断或停止，而是应该由线程自己自行停止。</p><p>在java中没有办法立即停止一条线程，然后停止线程却显得尤其重要，如取消一个耗时操作</p><p>因此java提供了一种用于停止线程的协商机制–中断，也即中断标识协商机制</p><p>中断只是一种协作协商机制，java没有给中断增加任何语法，中断的过程完全需要程序员自己实现</p><p>若要中断一个线程，你需要手动调用该线程的interrupt方法，该方法也仅仅是将线程对象的中断标识设成true</p><p>接下来需要自己写代码不断的检测当前线程的标识位，如果为true，表示别的线程请求这条线程中断，此时究竟该做什么需要自己写代码实现</p><p>每个线程对象中都有一个中断标识位，用于表示线程是否被中断，该标识位为true表示中断，为false表示未中断</p><p>通过调用线程对象的interrupt方法将线程的标识位设置为true，可以在别的线程中调用，也可以在自己的线程中调用</p><ol><li>interrupt() ：中断状态改为true</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">* 实例方法：just to set the interrupt flag</span><br><span class="hljs-comment">* 实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立即停止线程</span><br><span class="hljs-comment">*/</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">interrupt</span><span class="hljs-params">()</span>;<br></code></pre></td></tr></table></figure><ol start="2"><li>interrupted():返回中断状态，并清除中断状态</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">* 静态方法：Thread.interrupted();</span><br><span class="hljs-comment">* 判断线程是否被中断并清除当前中断的状态</span><br><span class="hljs-comment">* 这个方法做两件事</span><br><span class="hljs-comment">* 1. 返回当前线程的中断状态，测试当前线程是否已被中断</span><br><span class="hljs-comment">* 2. 将当前线程的中断状态清零并重新设为false，清除线程的中断状态</span><br><span class="hljs-comment">*/</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">interrupted</span><span class="hljs-params">()</span>;<br></code></pre></td></tr></table></figure><ol start="3"><li>isInterrupted(): 判断当前线程是否被中断（通过检查中断标志位）</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 实例方法</span><br><span class="hljs-comment">// 含义：判断当前线程是否被中断（通过检查中断标志位）</span><br><span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isInterrupted</span><span class="hljs-params">()</span>;<br></code></pre></td></tr></table></figure><p>如何停止中断运行中的线程？</p><ol><li><p>通过一个volatile变量实现</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-780bc1b6832f33fb82ed9ac3263b4935-3a2389.png" alt="截屏2025-05-11 上午12.05.43"></p></li><li><p>通过AutomicBoolean</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-e2a2778c53425aec728f980b3449d430-326ac1.png" alt="截屏2025-05-11 上午12.12.58"></p></li><li><p>同过Thread类自带的中断Api实例方法实现</p><p>在需要中断的线程中不断监听中断状态</p><p>一旦发生中断，就执行相应的中断处理业务逻辑stop线程</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-915b687e258749dd9ccba4c5443c3117-fea243.png" alt="截屏2025-05-11 上午12.16.43"></p></li></ol><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-9d7b06022a818807b2a594a900bb5307-7f6bb0.png" alt="截屏2025-05-11 上午12.21.47"></p><p>interrupt底层调用的是interrupt0这个方法</p><p>当调用interrupt()时</p><ol><li>如果线程处于正常活动状态，那么会将该线程的中断标志设置为true，被设置中断标志的线程将继续正常运行，不受影响，所以，interrupt()并不能真正的中断线程，需要被调用的线程自己进行配合才行</li><li>如果线程处于被阻塞状态（例如：sleep,wait,join等状态),在别的线程中调用当前对象的interrupt方法那么线程将立即退出被阻塞状态，并抛出一个InterruptedException异常</li></ol><p><strong>当前线程的中断标识为true，是不是线程立刻停止</strong></p><p>不会立即停止，需要自己手动代码实现</p><p>以及执行完的线程（不活动的线程）interrupt不会产生任何影响</p><p>如果线程处于被阻塞状态（例如：sleep,wait,join等状态),在别的线程中调用当前对象的interrupt方法那么线程将立即退出被阻塞状态，并抛出一个InterruptedException异常，此时必须做异常处理，并且再调用一次interrupt()否则会死循环</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-9d7b06022a818807b2a594a900bb5307-911e4e.png" alt="截屏2025-05-11 上午12.21.47"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-74dc1948c4efc8e584715f922be85dfe-2d15f0.png" alt="截屏2025-05-11 上午12.46.23"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-70a6cc2d97a8d9144d87d97a65857e2a-873aa5.png" alt="截屏2025-05-11 上午12.44.04"></p><p><strong>静态方法Thread.interrupted()的理解</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">* 静态方法：Thread.interrupted();</span><br><span class="hljs-comment">* 判断线程是否被中断并清除当前中断的状态</span><br><span class="hljs-comment">* 这个方法做两件事</span><br><span class="hljs-comment">* 1. 返回当前线程的中断状态，测试当前线程是否已被中断</span><br><span class="hljs-comment">* 2. 将当前线程的中断状态清零并重新设为false，清除线程的中断状态</span><br><span class="hljs-comment">*/</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">interrupted</span><span class="hljs-params">()</span>;<br></code></pre></td></tr></table></figure><p>Thread.interrupted()&#x2F;&#x2F; 静态方法</p><p>Thread.currentThread().isInterrupted()&#x2F;&#x2F; 实例方法</p><p>这两者底层调用的是同一个方法，区别就是是否需要重置状态</p><h2 id="LockSupport"><a href="#LockSupport" class="headerlink" title="LockSupport"></a>LockSupport</h2><h3 id="LockSupport是什么"><a href="#LockSupport是什么" class="headerlink" title="LockSupport是什么"></a>LockSupport是什么</h3><p>LockSupport用于创建锁和其他同步类的基本线程阻塞原语</p><p>park()和unpack()的作用分别是阻塞线程和接触阻塞线程</p><p>LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能，每个线程都有一个许可证</p><h3 id="线程等待唤醒机制"><a href="#线程等待唤醒机制" class="headerlink" title="线程等待唤醒机制"></a>线程等待唤醒机制</h3><p>3中让线程等待和唤醒的方法：</p><ol><li><p>Obj中的wait()方法与notify()方法</p><p>wait()方法和notify()方法都应该包含在同步代码块中，否则会抛异常IllegalMonitorStateException</p><p>将notify放在wait方法前，程序无法执行，无法唤醒</p><p>总结：wait和notify方法必须放在同步代码块或者方法里面，且成对出现使用，先wait后notify才ok</p></li><li><p>JUC中Condition的await()方法与signal()方法</p><p>总结：Condition中的线程等待和唤醒方法需要先获取锁，一定先await后signal</p></li><li><p>上述两个总结：</p><ol><li>线程先要获得并持有锁，必须在锁块(synchronized或lock)中</li><li>必须要先等待后唤醒，线程才能够被唤醒</li></ol></li><li><p>LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程</p><ol><li>主要方法<ol><li>阻塞<ol><li>park()&#x2F;park(Object blocker)</li><li>阻塞当前线程&#x2F;阻塞传入的具体线程</li></ol></li><li>唤醒：<ol><li>unpack(Thread thread)</li><li>唤醒处于阻塞状态的指定线程</li></ol></li></ol></li><li>新特点<ol><li>正常+无锁块要求</li><li>之前错误的先唤醒后等待，LockSupport照样支持</li><li>成双成对要牢记</li></ol></li></ol></li></ol><h3 id="LockSupport原理总结"><a href="#LockSupport原理总结" class="headerlink" title="LockSupport原理总结"></a>LockSupport原理总结</h3><p>注意：许可证不会累计，最多只能有一个</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-367658513cae4d5be126ba1fdf3ab0a0-e29a23.png" alt="截屏2025-05-11 上午10.17.27"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-49d176b5895d3317541407a3a434884f-906ac7.png" alt="截屏2025-05-11 上午10.18.41"></p><h2 id="Java内存模型之JMM"><a href="#Java内存模型之JMM" class="headerlink" title="Java内存模型之JMM"></a>Java内存模型之JMM</h2><h3 id="JMM入门介绍"><a href="#JMM入门介绍" class="headerlink" title="JMM入门介绍"></a>JMM入门介绍</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-8a83061f3bd4956ca5abd2bda3742c20-181c31.png" alt="截屏2025-05-11 上午10.21.46"></p><h3 id="JMM学术定义和作用"><a href="#JMM学术定义和作用" class="headerlink" title="JMM学术定义和作用"></a>JMM学术定义和作用</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-dfaa75182ed7357fcff800ae0048623e-ccf64b.png" alt="截屏2025-05-11 上午10.23.57"></p><h3 id="JMM规范下，三大特性"><a href="#JMM规范下，三大特性" class="headerlink" title="JMM规范下，三大特性"></a>JMM规范下，三大特性</h3><ol><li><p>可见性</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-e542912d95fec0267955d92042f5eeed-d15bef.png" alt="截屏2025-05-11 上午10.27.21"><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-260fccdfd95c148355d6d916426a567d-05e204.png" alt="截屏2025-05-11 上午10.29.46"><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-05-1450bfe90d30fcb6498cee8a87301519-1c68e8.png" alt="截屏2025-05-11 上午10.31.29"></p></li><li><p>原子性</p><p>指一个操作是不可打断的，即多线程环境下，操作不能被其他线程干扰</p></li><li><p>有序性</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-057c39324f270099380ddee614c98fea-242e51.png" alt="截屏2025-05-11 上午10.33.09"><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-8603b2a999a0afe5d3475164bd3a745f-118528.png" alt="截屏2025-05-11 上午10.36.05"></p></li></ol><h3 id="JMM之多线程对变量的读写过程"><a href="#JMM之多线程对变量的读写过程" class="headerlink" title="JMM之多线程对变量的读写过程"></a>JMM之多线程对变量的读写过程</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-382979f5f3a5a10bb494db31587d8e94-8c6dc7.png" alt="截屏2025-05-11 上午10.38.27"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-526b4314c8a68e3175dd9528662b1774-5ef7f6.png" alt="截屏2025-05-11 上午10.37.43"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-a602a60b889019c618019220c3213513-f9b4e1.png" alt="截屏2025-05-11 上午10.39.28"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-f546008bcf2e35bbcb68885487720407-a960ca.png" alt="截屏2025-05-11 上午10.39.45"></p><h3 id="JMM规范下，多线程先行发生原则之happens-before"><a href="#JMM规范下，多线程先行发生原则之happens-before" class="headerlink" title="JMM规范下，多线程先行发生原则之happens-before"></a>JMM规范下，多线程先行发生原则之happens-before</h3><p>在JMM中如果一个操作执行的结果需要对另一个操作可见性或者 代码重排序，那么这两个操作之间必须存在happens-before(先行发生)原则。逻辑上的先后关系</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-250a3c5562ce520fa9747a7b277cad15-07ff82.png" alt="截屏2025-05-11 上午10.45.09"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-a486841c22025a6e31dc5642b0b05837-942981.png" alt="截屏2025-05-11 上午10.46.05"><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-3f9d3338c6228e8dbab7558e30a91454-096c91.png" alt="截屏2025-05-11 上午10.47.03"></p><ol><li>次序原则：一个线程内，按照代码顺序，写在前面的操作先行发生于写在后面的操作;</li><li>锁定规则：一个unLock操作先行发生于后面((这里的“后面”是指时间上的先后))对同一个锁的lock操作;</li><li>volatile变量规则: 对一个volatile变量的写操作先行发生于后面对这个变量的读操作，前面的写对后面的读是可见的，这里的“后面”同样是指时间上的先后</li><li>传递规则：如果操作A先行发生于操作B，而操作B又先行发生于操作C，则可以得出操作A先行发生于操作C;</li><li>线程启动规则：(Thread Start Rule) : Thread对象的start()方法先行发生于此线程的每一个动作</li><li>线程中断规则：  <ol><li>对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生</li><li>可以通过Thread.interrupted()检测到是否发生中断也就是说你要先调用</li><li>interrupt()方法设置过中断标志位，我才能检测到中断发送</li></ol></li><li>线程终止规则：线程中的所有操作都先行发生于对此线程的终止检测，我们可以通过isAlive()等手段检测线程是否已经终止执行，</li><li>对象终止规则：一个对象的初始化完成（构造函数执行结束）先行发生于它的finalize()方法的开始</li></ol><h3 id="JMM-hanpens-before小总结"><a href="#JMM-hanpens-before小总结" class="headerlink" title="JMM hanpens-before小总结"></a>JMM hanpens-before小总结</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-a45d5b7e2990d086b6d7b932768f2db0-3bbbd5.png" alt="截屏2025-05-11 上午10.57.47"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-45af17faf3c6a4eae5af95baea9fa892-7bcd67.png" alt="截屏2025-05-11 上午11.04.04"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-46b6a17b43058a55b930b2601d81aa97-bf9f17.png" alt="截屏2025-05-11 上午11.01.18"></p><p> <img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-e7c9751e46274d41a1c8f4d784351122-376e31.png" alt="截屏2025-05-11 上午11.02.14"></p><h2 id="volatile"><a href="#volatile" class="headerlink" title="volatile"></a>volatile</h2><h3 id="volatile两大特性"><a href="#volatile两大特性" class="headerlink" title="volatile两大特性"></a>volatile两大特性</h3><ol><li>可见行</li><li>有序性</li></ol><h3 id="volatile的内存语义"><a href="#volatile的内存语义" class="headerlink" title="volatile的内存语义"></a>volatile的内存语义</h3><ol><li><p>当写一个volatile变量时，JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。</p></li><li><p>当读一个volatile变量时，JMM会把该线程对应的本地内存设置为无效，重新回到主内存中读取最新共享变量</p></li><li><p>所以volatile的写内存语义是直接刷新到主内存中，读的内存语义是直接从主内存中读取。</p></li></ol><p>volatile凭什么可以保证可见行和有序性</p><p>因为使用了内存屏障Memory Barrier</p><h3 id="volatile之四大屏障"><a href="#volatile之四大屏障" class="headerlink" title="volatile之四大屏障"></a>volatile之四大屏障</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-ad8c9f6355328c033b4f97e72bdc5543-c6b508.png" alt="截屏2025-05-11 上午11.15.17"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-c38f185f084c120a79c4a38fc34e97d3-6fe34a.png" alt="截屏2025-05-11 上午11.16.20"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-2e668ce72abcb6a95bf831bab6d65d9d-c7ea5d.png" alt="截屏2025-05-11 上午11.17.04"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-ade0f220f745a940aaf9c52208a7a137-972130.png" alt="截屏2025-05-11 下午2.06.08"></p><p>内存屏障的分类</p><ul><li><p>粗分</p><ul><li>读屏障(Load Barrier)<ul><li>在读指令之前插入读屏障，让工作内存或CPU高速缓存当中的缓存数据失效，重新回到主内存中获取最新数据</li></ul></li><li>写屏障(Store Barrier)<ul><li>在写指令之后插入写屏障，强制把写缓冲区的数据刷回到主内存中</li></ul></li></ul></li><li><p>细分</p><ul><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-57-56-25a7f9ce7d869a653e2b138b449feda5-ef4afc.png" alt="截屏2025-05-11 下午2.10.54"></li></ul></li></ul><p>​</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-7f21226cb3b2e28821004514cfe687f7-e73103.png" alt="截屏2025-05-11 下午2.14.20"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-b620f38d83b86d4d1617f6f473799b27-bafa6a.png" alt="截屏2025-05-11 下午2.16.11"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-4be0db7dabf6a15ca819898e64447cd9-13633a.png" alt="截屏2025-05-11 下午2.25.31"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-8c61c1fc26d03b49f5b0b977d98eac3c-44ba49.png" alt="截屏2025-05-11 下午2.20.36"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-992f69b2bb3c2e7ab1334cbd451ab5aa-229522.png" alt="截屏2025-05-11 下午2.26.03"></p><h3 id="volatile之可见性案例详解"><a href="#volatile之可见性案例详解" class="headerlink" title="volatile之可见性案例详解"></a>volatile之可见性案例详解</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-73839ac25437b0a018cdc8e55ac378e6-4e1be9.png" alt="截屏2025-05-11 下午2.28.11"></p><p>  <img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-2770bc47ea84c974a7de10b643a477ee-35122a.png" alt="截屏2025-05-11 下午2.31.30"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-d0d6d1317cfad1a6d5bfc242d2a5adb6-dec934.png" alt="截屏2025-05-11 下午2.31.02"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-d6584c184b82f88cd9f59c648d0ccf99-627605.png" alt="截屏2025-05-11 下午2.31.47"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-302d1aadc78e736f1dbcf6abe426d9a3-a11404.png" alt="截屏2025-05-11 下午2.33.12"></p><p> <img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-74e907a79a3620c9c8d3c73c2b374845-fbdf94.png" alt="截屏2025-05-11 下午2.37.41"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-7e2ad22695528b750f9e88e39a6e1a8d-5fd3e4.png" alt="截屏2025-05-11 下午2.54.53"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-e661542926de19e28546aa9eb7d0c8b5-fe4e32.png" alt="截屏2025-05-11 下午2.56.02"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-c3ae92bd35919e924f09866c84e920fb-0013ea.png" alt="截屏2025-05-11 下午3.01.19"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-79f5bc88acf12dd9113c6eb10cd1832a-cb0e47.png" alt="截屏2025-05-11 下午3.02.05"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-8711dbfca4564c07991b54d072149210-027aea.png" alt="截屏2025-05-11 下午3.03.54"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-e98a4d32ecf50aaa8d98e453dffc97fe-9769e9.png" alt="截屏2025-05-11 下午3.02.39"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-58-46-e62d7bed1c58c59b31049c591c460784-9a339a.png" alt="截屏2025-05-11 下午3.04.12"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-c997e322bb4838484e59cb3bf7734932-a81e9f.png" alt="截屏2025-05-11 下午3.05.03"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-946d061b88bdf69911fcfc492f05036f-18d5af.png" alt="截屏2025-05-11 下午3.07.46"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-21bd612dc0049efd5958808cfca41af5-0c97e5.png" alt="截屏2025-05-11 下午3.08.03"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-9aaa5aee5d99112caf492fd18de9e3e6-edd6e9.png" alt="截屏2025-05-11 下午3.10.43"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-58b0a6cef73917c3d8c64a8869e7467b-3e844e.png" alt="截屏2025-05-11 下午3.11.01"></p><h3 id="volatile日常使用"><a href="#volatile日常使用" class="headerlink" title="volatile日常使用"></a>volatile日常使用</h3><ol><li><p>单一赋值可以，但是含复合运算赋值不可以(i++之类)</p><ol><li>volatile int a &#x3D; 10</li><li>volatile boolean flag &#x3D; false</li></ol></li><li><p>状态标志，判断业务是否结束</p><ol><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-ab3d8e5b89e648ba40691eefa69a6cf7-08d899.png" alt="截屏2025-05-11 下午3.16.26"></li></ol></li><li><p>开销较低的读，写锁策略</p><ol><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-e1a8ee5cff6bd40a107c06cd5f4c9b54-5842b7.png" alt="截屏2025-05-11 下午3.17.07"></li></ol></li><li><p>DCL双端锁的发布</p><ol><li><p>问题：</p><ol><li><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-77bc531bd984095449334243aedc7387-d793b7.png" alt="截屏2025-05-11 下午3.18.27"></p></li><li><p>单线程看问题代码</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-08253b17d05652ea88ef2f9960fd2cc9-958751.png" alt="截屏2025-05-11 下午3.21.02"></p></li><li><p>由于存在指令重排</p></li><li><p>多线程看问题代码</p><ol><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-9e7392e3ed3c11587ad3636ff12b6bf8-1446b6.png" alt="截屏2025-05-11 下午3.22.02"><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-7ff00371edcd1b6e324b392c4d0908d9-a86a3f.png" alt="截屏2025-05-11 下午3.22.27"></li></ol></li></ol></li><li><p>解决：加volatile修饰</p></li></ol></li></ol><h3 id="volatile-总结"><a href="#volatile-总结" class="headerlink" title="volatile 总结"></a>volatile 总结</h3><ul><li>volatile可见行<ul><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-f5a84ef3469a03f4eb1b5a36aa16170b-81eece.png" alt="截屏2025-05-11 下午3.25.23"></li></ul></li><li>volatile没有原子性</li><li>volatile禁重排<ul><li>写指令<ul><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-676b78934616507a39bbaa6ea911e793-d99b28.png" alt="截屏2025-05-11 下午3.26.27"></li></ul></li><li>读指令：全加在后面<ul><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-9e33e81700087713b0122a743cbcde20-223f52.png" alt="截屏2025-05-11 下午3.27.23"></li></ul></li></ul></li><li>凭什么我们java写一个volatile关键字，系统底层加入内存屏障？两者关系如何勾搭上的？<ul><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-1687ed6b3e052c1ab3353209376df1cb-00765e.png" alt="截屏2025-05-11 下午3.27.59"></li></ul></li><li>内存屏障是什么<ul><li>内存屏障 : 是一种 屏障指令，它使得 CPU 或编译器 对 屏障指令的前 和 后所发出的内存操作 执行一个排序的约束。也叫内存栅栏 或 栅栏指令</li></ul></li><li>内存屏障能干嘛<ul><li>阻止屏障两遍的指令重排序</li><li>写数据时加入屏障，强制将线程私有工作内存的数据刷回主物理内存</li><li>读数据时加入屏障，线程私有工作内存的数据是小，重新到主物理内存中获取最新数据</li></ul></li><li>内存屏障四大指令<ul><li>在每一个volatile写操作前面插入一个StoreStore屏障</li><li>在每一个volatile写操作后面插入一个StoreLoad屏障</li><li>在每一个volatile读操作后面插入一个LoadLoad屏障</li><li>在每一个volatile读操作后面插入一个LoadStore屏障</li></ul></li><li>3句话总结<ul><li>volatile 写之前的操作，都禁止重排序到 </li><li>volatile 之后volatile 读之后的操作，都禁止重排序到 volatile 之前</li><li>volatile 写之后 volatile 读，禁止重排序</li></ul></li></ul><h2 id="CAS"><a href="#CAS" class="headerlink" title="CAS"></a>CAS</h2><p><strong>原子类</strong>：java.util.concurrent.atomic下的所有类</p><p><strong>没有CAS之前</strong>：多线程环境不使用原子类保证线程安全i++（基本数据类型)</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-47328e5ea6b48efcfb99e2db801516ab-ea15bc.png" alt="截屏2025-05-11 下午10.32.14"></p><p><strong>使用CAS之后</strong>：多线程环境，使用原子类保证线程安全i++(基本数据类型)，类似乐观锁</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-5049746cd1f2ed5add9586d740cd03cc-fd0c9a.png" alt="截屏2025-05-11 下午10.34.41"></p><p><strong>是什么</strong>：</p><p>CAS<br>compare and swap的缩写，中文翻译成比较并交换,实现并发算法时常用到的一种技术。<br>它包含三个操作数–内存位置、预期原值及更新值。<br>执行CAS操作的时候，将内存位置的值与预期原值比较:</p><p>如果相匹配，那么处理器会自动将该位置值更新为新值</p><p>如果不匹配，处理器不做任何操作，多个线程同时执行CAS操作只有一个会成功。</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-00-25-4a91ada08f42d25060872e80f32bad5f-e20256.png" alt="截屏2025-05-11 下午10.37.37"></p><p>CAS是JDK提供的非阻塞原子性操作，它通过硬件保证了比较-更新的原子性。<br>它是非阻塞的且自身具有原子性，也就是说这玩意效率更高且通过硬件保证，说明这玩意更可靠。<br>会造成所谓的数据不一致问题，Unsafe提供的</p><p>CAS是一条CPU的原子指令(cmpxchg指令)，不CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg.</p><p>执行cmpxchg指令的时候，会判断当前系统是否为多核系统，如果是就给总线加锁，只有一个线程会对总线加锁成功，加锁成功之后会执行cas操作，也就是说CAS的原子性实际上是CPU实现独占的，比起用synchronized重量级锁，这里的排他时间要短很多，所以在多线程情况下性能会比较好。</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-7a3b7fae565a088aaf7ed251adbc4b79-8838d6.png" alt="截屏2025-05-11 下午10.54.21"></p><p>引出的新问题：Unsafe类是什么？</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-0da30c27deb4b2fd62f0e30fd21148a3-d22b5a.png" alt="截屏2025-05-11 下午10.56.55"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-9560589fbc94129aae721dd2ea4a6ec7-af0b5b.png" alt="截屏2025-05-11 下午10.59.50"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-6c15aa2b90c65f96fc06c316e0326352-bf266d.png" alt="截屏2025-05-11 下午11.08.17"></p><h3 id="CAS之Unsafe类底层汇编源码分析"><a href="#CAS之Unsafe类底层汇编源码分析" class="headerlink" title="CAS之Unsafe类底层汇编源码分析"></a>CAS之Unsafe类底层汇编源码分析</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-75d9b0f5c09962d964fae2977e781c36-54c101.png" alt="截屏2025-05-11 下午11.18.24"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-6c5a9fb8af7539b694ac4a1cb1edce35-542279.png" alt="截屏2025-05-11 下午11.21.05"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-d34faf8f2f825c7b27b30755d340944d-9ac174.png" alt="截屏2025-05-11 下午11.22.10"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-ceda909f862b73e98295659b7083aafd-83214e.png" alt="截屏2025-05-11 下午11.26.19"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-902936709e1cf00fb8cecde147f7fc82-c17e9d.png" alt="截屏2025-05-11 下午11.26.44"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-400d18f7799c60004dc01f027041f6cb-2f73c9.png" alt="截屏2025-05-11 下午11.26.52"></p><p>你只需要记住:CAS是靠硬件实现的从而在硬件层面提升效率，最底层还是交给硬件来保证原子性和可见性实现方式是基于硬件平台的汇编指令，在intel的CPU中(X86机器上)，使用的是汇编指令cmpxchg指令。<br>核心思想就是:比较要更新变量的值V和预期值E(compare)，相等才会将V的值设为新值N(swap)如果不相等自旋再来</p><h3 id="CAS之原子引用AtomicReference"><a href="#CAS之原子引用AtomicReference" class="headerlink" title="CAS之原子引用AtomicReference"></a>CAS之原子引用AtomicReference</h3><p>用于自定义原子类型</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-da3bef7eaa66f63cc136d3ba1985134d-1ea6f0.png" alt="截屏2025-05-11 下午11.32.58"></p><h2 id="CAS之手写自旋锁"><a href="#CAS之手写自旋锁" class="headerlink" title="CAS之手写自旋锁"></a>CAS之手写自旋锁</h2><p>CAS与自旋锁，借鉴CAS思想</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-224a76d9bce1c88e8cf2169c5c30051a-866c27.png" alt="截屏2025-05-11 下午11.35.33"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-9db7daf20b6539364f949c855dc0dee9-3a20cb.png" alt="截屏2025-05-11 下午11.43.20"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-09d63812a745d808cfb0dedf4f250043-692b92.png" alt="截屏2025-05-11 下午11.43.33"></p><h3 id="CAS的缺点"><a href="#CAS的缺点" class="headerlink" title="CAS的缺点"></a>CAS的缺点</h3><ul><li>循环时间长开销很大<ul><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-1861518c628a4a776072fca931292ac5-1c1929.png" alt="截屏2025-05-11 下午11.44.29"></li></ul></li><li>引出ABA问题<ul><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-f50ca97909fff39b8ef5466adf0987cf-9ecff6.png" alt="截屏2025-05-11 下午11.45.01"></li></ul></li></ul><h3 id="CAS之AtomicStampedReference入门"><a href="#CAS之AtomicStampedReference入门" class="headerlink" title="CAS之AtomicStampedReference入门"></a>CAS之AtomicStampedReference入门</h3><p>版本号，戳记流水</p><p>AtomicStampedReference带有戳记流水的类</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-f8de83b34fb5ce97d104ddc84f72f775-d5e191.png" alt="截屏2025-05-11 下午11.50.12"></p><h3 id="CAS之ABA问题编码实战"><a href="#CAS之ABA问题编码实战" class="headerlink" title="CAS之ABA问题编码实战"></a>CAS之ABA问题编码实战</h3><p>问题代码：</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-aa5c5faa995d8ffb75af7dca283c49a9-69d603.png" alt="截屏2025-05-11 下午11.51.29"></p><p>解决问题代码：</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-11d2238e714b4f96c0c879ecdecc8e3c-a442b3.png" alt="截屏2025-05-11 下午11.52.57"></p><h2 id="原子类"><a href="#原子类" class="headerlink" title="原子类"></a>原子类</h2><h3 id="原子类入门介绍和分类说明"><a href="#原子类入门介绍和分类说明" class="headerlink" title="原子类入门介绍和分类说明"></a>原子类入门介绍和分类说明</h3><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-586f0aff5de4ccb9c5553f29c7a7e359-f3a469.png" alt="截屏2025-05-11 下午11.54.40"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-2434f6bc088d24797aebf7a57d515346-5470bd.png" alt="截屏2025-05-11 下午11.54.58"></p><p>分类：</p><ul><li>基本类型原子类</li><li>数组类型原子类</li><li>引用类型原子类</li><li>对象的属性修改原子类</li><li>原子操作增强类原理深度解析</li></ul><h3 id="基本类型原子类"><a href="#基本类型原子类" class="headerlink" title="基本类型原子类"></a>基本类型原子类</h3><ul><li>AtomicInterger</li><li>AtomicBoolean</li><li>AtomicLong</li></ul><p>常用API<img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-674f937997be7594607be39d397cf3cd-3c78d8.png" alt="截屏2025-05-11 下午11.57.41"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-70c2e783c33351d66b7e0ec2e254b469-7d4597.png" alt="截屏2025-05-11 下午11.58.02"></p><p>问题：</p><ul><li>获取值时，可能计算还没计算完，获取的不是最后计算完的值<ul><li>使用tsleep-&gt;countDownLatch<ul><li><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-2b687e05c216cdb4fc22b1ef0f3ef722-1520f2.png" alt="截屏2025-05-12 上午12.05.11"></li></ul></li></ul></li></ul><h3 id="数组类型的原子类"><a href="#数组类型的原子类" class="headerlink" title="数组类型的原子类"></a>数组类型的原子类</h3><ul><li>AtomicIntegerArray</li><li>AtomicLongArray</li><li>AtomicReferenceArray</li></ul><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-e5e2951892d0d95265bede4f6945db51-9246dc.png" alt="截屏2025-05-12 上午12.07.42"></p><h3 id="引用类型的原子类"><a href="#引用类型的原子类" class="headerlink" title="引用类型的原子类"></a>引用类型的原子类</h3><ul><li>AtomicReference </li><li>AtomicStampedReference 带戳记流水的</li><li>AtomicMarkableReference 带标记的</li></ul><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/13-01-11-1dddae7dcc60ea855ab7ff984940fba2-c53d54.png" alt="截屏2025-05-12 上午12.11.04"></p>]]></content>
    
  </entry>
  
  <entry>
    <title>StreamAPI</title>
    <link href="https://alan.org.cn/posts/StreamAPI.html"/>
    <id>https://alan.org.cn/posts/StreamAPI.html</id>
    <published>2025-05-05T04:09:29.000Z</published>
    <updated>2025-05-05T04:09:29.000Z</updated>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/categories/JAVA/"/>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/tags/JAVA/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Java-8-Stream-API&quot;&gt;&lt;a href=&quot;#Java-8-Stream-API&quot; class=&quot;headerlink&quot; title=&quot;Java 8 Stream API&quot;&gt;&lt;/a&gt;Java 8 Stream API&lt;/h1&gt;&lt;h2 id=&quot;一、Str</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="Java-8-Stream-API"><a href="#Java-8-Stream-API" class="headerlink" title="Java 8 Stream API"></a>Java 8 Stream API</h1><h2 id="一、Stream-概述"><a href="#一、Stream-概述" class="headerlink" title="一、Stream 概述"></a>一、Stream 概述</h2><p>Stream 是 Java 8 中处理集合的关键抽象概念，它可以对集合进行非常复杂的查找、过滤和映射数据等操作。Stream API 提供了一种高效且易于使用的处理数据的方式。</p><h3 id="Stream-的特点："><a href="#Stream-的特点：" class="headerlink" title="Stream 的特点："></a>Stream 的特点：</h3><ol><li><strong>不是数据结构</strong>：不存储数据，只是从数据源（集合、数组等）获取数据</li><li><strong>不修改源数据</strong>：对Stream的操作会产生新Stream，不会修改原始数据源</li><li><strong>惰性执行</strong>：中间操作是惰性的，只有终端操作才会触发实际计算</li><li><strong>可消费性</strong>：Stream只能被消费一次，终端操作后就不能再次使用</li></ol><h2 id="二、Stream-操作分类"><a href="#二、Stream-操作分类" class="headerlink" title="二、Stream 操作分类"></a>二、Stream 操作分类</h2><p>Stream 操作分为两类：</p><ol><li>**中间操作(Intermediate Operations)**：返回一个新的Stream，可以链式调用</li><li>**终端操作(Terminal Operations)**：返回具体结果或产生副作用，执行后Stream就不能再使用</li></ol><h2 id="三、创建-Stream"><a href="#三、创建-Stream" class="headerlink" title="三、创建 Stream"></a>三、创建 Stream</h2><h3 id="1-从集合创建"><a href="#1-从集合创建" class="headerlink" title="1. 从集合创建"></a>1. 从集合创建</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list = Arrays.asList(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>);<br>Stream&lt;String&gt; stream = list.stream();          <span class="hljs-comment">// 创建顺序流</span><br>Stream&lt;String&gt; parallelStream = list.parallelStream(); <span class="hljs-comment">// 创建并行流</span><br></code></pre></td></tr></table></figure><h3 id="2-从数组创建"><a href="#2-从数组创建" class="headerlink" title="2. 从数组创建"></a>2. 从数组创建</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">String[] array = &#123;<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>&#125;;<br>Stream&lt;String&gt; stream = Arrays.stream(array);<br></code></pre></td></tr></table></figure><h3 id="3-使用Stream-of-静态方法"><a href="#3-使用Stream-of-静态方法" class="headerlink" title="3. 使用Stream.of()静态方法"></a>3. 使用Stream.of()静态方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Stream&lt;String&gt; stream = Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>);<br></code></pre></td></tr></table></figure><h3 id="4-使用Stream-generate-创建无限流"><a href="#4-使用Stream-generate-创建无限流" class="headerlink" title="4. 使用Stream.generate()创建无限流"></a>4. 使用Stream.generate()创建无限流</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Stream&lt;Double&gt; randomStream = Stream.generate(Math::random).limit(<span class="hljs-number">5</span>);<br></code></pre></td></tr></table></figure><h3 id="5-使用Stream-iterate-创建无限流"><a href="#5-使用Stream-iterate-创建无限流" class="headerlink" title="5. 使用Stream.iterate()创建无限流"></a>5. 使用Stream.iterate()创建无限流</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Stream&lt;Integer&gt; iterateStream = Stream.iterate(<span class="hljs-number">0</span>, n -&gt; n + <span class="hljs-number">2</span>).limit(<span class="hljs-number">5</span>);<br><span class="hljs-comment">// 0, 2, 4, 6, 8</span><br></code></pre></td></tr></table></figure><h3 id="6-其他创建方式"><a href="#6-其他创建方式" class="headerlink" title="6. 其他创建方式"></a>6. 其他创建方式</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 从文件创建</span><br>Stream&lt;String&gt; lines = Files.lines(Paths.get(<span class="hljs-string">&quot;data.txt&quot;</span>));<br><br><span class="hljs-comment">// 从字符串创建</span><br><span class="hljs-type">IntStream</span> <span class="hljs-variable">charsStream</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;abcde&quot;</span>.chars();<br></code></pre></td></tr></table></figure><h2 id="四、中间操作详解"><a href="#四、中间操作详解" class="headerlink" title="四、中间操作详解"></a>四、中间操作详解</h2><p>中间操作会返回一个新的Stream，可以链式调用多个中间操作。</p><h3 id="1-filter-Predicate-过滤"><a href="#1-filter-Predicate-过滤" class="headerlink" title="1. filter(Predicate) - 过滤"></a>1. filter(Predicate<T>) - 过滤</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list = Arrays.asList(<span class="hljs-string">&quot;apple&quot;</span>, <span class="hljs-string">&quot;banana&quot;</span>, <span class="hljs-string">&quot;orange&quot;</span>, <span class="hljs-string">&quot;grape&quot;</span>);<br>list.stream()<br>    .filter(s -&gt; s.length() &gt; <span class="hljs-number">5</span>)  <span class="hljs-comment">// 过滤长度大于5的字符串</span><br>    .forEach(System.out::println); <span class="hljs-comment">// banana, orange</span><br></code></pre></td></tr></table></figure><h3 id="2-map-Function-映射"><a href="#2-map-Function-映射" class="headerlink" title="2. map(Function&lt;T,R&gt;) - 映射"></a>2. map(Function&lt;T,R&gt;) - 映射</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list = Arrays.asList(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>);<br>list.stream()<br>    .map(String::toUpperCase)  <span class="hljs-comment">// 转换为大写</span><br>    .forEach(System.out::println); <span class="hljs-comment">// A, B, C</span><br><br><span class="hljs-comment">// 提取对象属性</span><br>List&lt;Person&gt; persons = ...;<br>persons.stream()<br>       .map(Person::getName)  <span class="hljs-comment">// 提取name属性</span><br>       .forEach(System.out::println);<br></code></pre></td></tr></table></figure><h3 id="3-flatMap-Function-扁平化映射"><a href="#3-flatMap-Function-扁平化映射" class="headerlink" title="3. flatMap(Function&lt;T,Stream&gt;) - 扁平化映射"></a>3. flatMap(Function&lt;T,Stream<R>&gt;) - 扁平化映射</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;List&lt;String&gt;&gt; listOfLists = Arrays.asList(<br>    Arrays.asList(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>),<br>    Arrays.asList(<span class="hljs-string">&quot;c&quot;</span>, <span class="hljs-string">&quot;d&quot;</span>)<br>);<br><br>List&lt;String&gt; flatList = listOfLists.stream()<br>    .flatMap(List::stream)  <span class="hljs-comment">// 将多个流合并为一个流</span><br>    .collect(Collectors.toList()); <span class="hljs-comment">// [a, b, c, d]</span><br></code></pre></td></tr></table></figure><h3 id="4-distinct-去重"><a href="#4-distinct-去重" class="headerlink" title="4. distinct() - 去重"></a>4. distinct() - 去重</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;Integer&gt; numbers = Arrays.asList(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>);<br>numbers.stream()<br>       .distinct()  <span class="hljs-comment">// 去重</span><br>       .forEach(System.out::println); <span class="hljs-comment">// 1, 2, 3, 4</span><br></code></pre></td></tr></table></figure><h3 id="5-sorted-sorted-Comparator-排序"><a href="#5-sorted-sorted-Comparator-排序" class="headerlink" title="5. sorted() &#x2F; sorted(Comparator) - 排序"></a>5. sorted() &#x2F; sorted(Comparator<T>) - 排序</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; words = Arrays.asList(<span class="hljs-string">&quot;banana&quot;</span>, <span class="hljs-string">&quot;apple&quot;</span>, <span class="hljs-string">&quot;pear&quot;</span>, <span class="hljs-string">&quot;orange&quot;</span>);<br><br><span class="hljs-comment">// 自然排序</span><br>words.stream()<br>     .sorted()<br>     .forEach(System.out::println); <span class="hljs-comment">// apple, banana, orange, pear</span><br><br><span class="hljs-comment">// 自定义排序</span><br>words.stream()<br>     .sorted((s1, s2) -&gt; s1.length() - s2.length())<br>     .forEach(System.out::println); <span class="hljs-comment">// pear, apple, banana, orange</span><br></code></pre></td></tr></table></figure><h3 id="6-peek-Consumer-查看元素"><a href="#6-peek-Consumer-查看元素" class="headerlink" title="6. peek(Consumer) - 查看元素"></a>6. peek(Consumer<T>) - 查看元素</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java">Stream.of(<span class="hljs-string">&quot;one&quot;</span>, <span class="hljs-string">&quot;two&quot;</span>, <span class="hljs-string">&quot;three&quot;</span>)<br>    .filter(e -&gt; e.length() &gt; <span class="hljs-number">3</span>)<br>    .peek(e -&gt; System.out.println(<span class="hljs-string">&quot;Filtered value: &quot;</span> + e))<br>    .map(String::toUpperCase)<br>    .peek(e -&gt; System.out.println(<span class="hljs-string">&quot;Mapped value: &quot;</span> + e))<br>    .collect(Collectors.toList());<br></code></pre></td></tr></table></figure><h3 id="7-limit-long-限制数量"><a href="#7-limit-long-限制数量" class="headerlink" title="7. limit(long) - 限制数量"></a>7. limit(long) - 限制数量</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">Stream.iterate(<span class="hljs-number">1</span>, n -&gt; n + <span class="hljs-number">1</span>)<br>      .limit(<span class="hljs-number">5</span>)<br>      .forEach(System.out::println); <span class="hljs-comment">// 1, 2, 3, 4, 5</span><br></code></pre></td></tr></table></figure><h3 id="8-skip-long-跳过元素"><a href="#8-skip-long-跳过元素" class="headerlink" title="8. skip(long) - 跳过元素"></a>8. skip(long) - 跳过元素</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>)<br>      .skip(<span class="hljs-number">2</span>)<br>      .forEach(System.out::println); <span class="hljs-comment">// 3, 4, 5</span><br></code></pre></td></tr></table></figure><h2 id="五、终端操作详解"><a href="#五、终端操作详解" class="headerlink" title="五、终端操作详解"></a>五、终端操作详解</h2><p>终端操作会触发实际计算，执行后Stream就不能再使用。</p><h3 id="1-forEach-Consumer-遍历"><a href="#1-forEach-Consumer-遍历" class="headerlink" title="1. forEach(Consumer) - 遍历"></a>1. forEach(Consumer<T>) - 遍历</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list = Arrays.asList(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>);<br>list.stream().forEach(System.out::println); <span class="hljs-comment">// a, b, c</span><br></code></pre></td></tr></table></figure><h3 id="2-toArray-转换为数组"><a href="#2-toArray-转换为数组" class="headerlink" title="2. toArray() - 转换为数组"></a>2. toArray() - 转换为数组</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">String[] array = Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>).toArray(String[]::<span class="hljs-keyword">new</span>);<br></code></pre></td></tr></table></figure><h3 id="3-collect-Collector-收集为集合"><a href="#3-collect-Collector-收集为集合" class="headerlink" title="3. collect(Collector&lt;T,A,R&gt;) - 收集为集合"></a>3. collect(Collector&lt;T,A,R&gt;) - 收集为集合</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 收集为List</span><br>List&lt;String&gt; list = Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>).collect(Collectors.toList());<br><br><span class="hljs-comment">// 收集为Set</span><br>Set&lt;String&gt; set = Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;a&quot;</span>).collect(Collectors.toSet());<br><br><span class="hljs-comment">// 收集为Map</span><br>Map&lt;String, Integer&gt; map = Stream.of(<span class="hljs-string">&quot;apple&quot;</span>, <span class="hljs-string">&quot;banana&quot;</span>, <span class="hljs-string">&quot;orange&quot;</span>)<br>    .collect(Collectors.toMap(Function.identity(), String::length));<br><br><span class="hljs-comment">// 连接字符串</span><br><span class="hljs-type">String</span> <span class="hljs-variable">joined</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>).collect(Collectors.joining(<span class="hljs-string">&quot;, &quot;</span>));<br><span class="hljs-comment">// &quot;a, b, c&quot;</span><br></code></pre></td></tr></table></figure><h3 id="4-reduce-BinaryOperator-归约"><a href="#4-reduce-BinaryOperator-归约" class="headerlink" title="4. reduce(BinaryOperator) - 归约"></a>4. reduce(BinaryOperator<T>) - 归约</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 求和</span><br>Optional&lt;Integer&gt; sum = Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>).reduce(Integer::sum);<br>System.out.println(sum.get()); <span class="hljs-comment">// 10</span><br><br><span class="hljs-comment">// 求最大值</span><br>Optional&lt;Integer&gt; max = Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>).reduce(Integer::max);<br>System.out.println(max.get()); <span class="hljs-comment">// 4</span><br><br><span class="hljs-comment">// 带初始值的reduce</span><br><span class="hljs-type">Integer</span> <span class="hljs-variable">total</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>).reduce(<span class="hljs-number">10</span>, Integer::sum);<br>System.out.println(total); <span class="hljs-comment">// 20 (10+1+2+3+4)</span><br></code></pre></td></tr></table></figure><h3 id="5-min-max-最小-最大值"><a href="#5-min-max-最小-最大值" class="headerlink" title="5. min()&#x2F;max() - 最小&#x2F;最大值"></a>5. min()&#x2F;max() - 最小&#x2F;最大值</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java">Optional&lt;String&gt; min = Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;bb&quot;</span>, <span class="hljs-string">&quot;ccc&quot;</span>).min(Comparator.comparing(String::length));<br>System.out.println(min.get()); <span class="hljs-comment">// &quot;a&quot;</span><br><br>Optional&lt;String&gt; max = Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;bb&quot;</span>, <span class="hljs-string">&quot;ccc&quot;</span>).max(Comparator.comparing(String::length));<br>System.out.println(max.get()); <span class="hljs-comment">// &quot;ccc&quot;</span><br></code></pre></td></tr></table></figure><h3 id="6-count-计数"><a href="#6-count-计数" class="headerlink" title="6. count() - 计数"></a>6. count() - 计数</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">long</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>).count();<br>System.out.println(count); <span class="hljs-comment">// 3</span><br></code></pre></td></tr></table></figure><h3 id="7-anyMatch-allMatch-noneMatch-匹配"><a href="#7-anyMatch-allMatch-noneMatch-匹配" class="headerlink" title="7. anyMatch()&#x2F;allMatch()&#x2F;noneMatch() - 匹配"></a>7. anyMatch()&#x2F;allMatch()&#x2F;noneMatch() - 匹配</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">boolean</span> <span class="hljs-variable">anyStartsWithA</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-string">&quot;apple&quot;</span>, <span class="hljs-string">&quot;banana&quot;</span>, <span class="hljs-string">&quot;orange&quot;</span>)<br>    .anyMatch(s -&gt; s.startsWith(<span class="hljs-string">&quot;a&quot;</span>)); <span class="hljs-comment">// true</span><br><br><span class="hljs-type">boolean</span> <span class="hljs-variable">allLongerThan3</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-string">&quot;apple&quot;</span>, <span class="hljs-string">&quot;banana&quot;</span>, <span class="hljs-string">&quot;orange&quot;</span>)<br>    .allMatch(s -&gt; s.length() &gt; <span class="hljs-number">3</span>); <span class="hljs-comment">// true</span><br><br><span class="hljs-type">boolean</span> <span class="hljs-variable">noneStartsWithZ</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-string">&quot;apple&quot;</span>, <span class="hljs-string">&quot;banana&quot;</span>, <span class="hljs-string">&quot;orange&quot;</span>)<br>    .noneMatch(s -&gt; s.startsWith(<span class="hljs-string">&quot;z&quot;</span>)); <span class="hljs-comment">// true</span><br></code></pre></td></tr></table></figure><h3 id="8-findFirst-findAny-查找元素"><a href="#8-findFirst-findAny-查找元素" class="headerlink" title="8. findFirst()&#x2F;findAny() - 查找元素"></a>8. findFirst()&#x2F;findAny() - 查找元素</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java">Optional&lt;String&gt; first = Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>).findFirst();<br>System.out.println(first.get()); <span class="hljs-comment">// &quot;a&quot;</span><br><br>Optional&lt;String&gt; any = Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>).findAny();<br>System.out.println(any.get()); <span class="hljs-comment">// 可能是&quot;a&quot;, &quot;b&quot;或&quot;c&quot;（并行流中不确定）</span><br></code></pre></td></tr></table></figure><h2 id="六、数值流"><a href="#六、数值流" class="headerlink" title="六、数值流"></a>六、数值流</h2><p>Stream API 提供了专门的数值流来处理原始类型，避免装箱拆箱开销。</p><h3 id="1-IntStream"><a href="#1-IntStream" class="headerlink" title="1. IntStream"></a>1. IntStream</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java">IntStream.range(<span class="hljs-number">1</span>, <span class="hljs-number">5</span>).forEach(System.out::println); <span class="hljs-comment">// 1, 2, 3, 4</span><br>IntStream.rangeClosed(<span class="hljs-number">1</span>, <span class="hljs-number">5</span>).forEach(System.out::println); <span class="hljs-comment">// 1, 2, 3, 4, 5</span><br><br><span class="hljs-comment">// 统计信息</span><br><span class="hljs-type">IntSummaryStatistics</span> <span class="hljs-variable">stats</span> <span class="hljs-operator">=</span> IntStream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>).summaryStatistics();<br>System.out.println(stats.getMax());     <span class="hljs-comment">// 3</span><br>System.out.println(stats.getMin());     <span class="hljs-comment">// 1</span><br>System.out.println(stats.getAverage()); <span class="hljs-comment">// 2.0</span><br></code></pre></td></tr></table></figure><h3 id="2-LongStream"><a href="#2-LongStream" class="headerlink" title="2. LongStream"></a>2. LongStream</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">LongStream.range(<span class="hljs-number">1</span>, <span class="hljs-number">5</span>).forEach(System.out::println);<br></code></pre></td></tr></table></figure><h3 id="3-DoubleStream"><a href="#3-DoubleStream" class="headerlink" title="3. DoubleStream"></a>3. DoubleStream</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">DoubleStream.of(<span class="hljs-number">1.1</span>, <span class="hljs-number">2.2</span>, <span class="hljs-number">3.3</span>).forEach(System.out::println);<br></code></pre></td></tr></table></figure><h3 id="4-对象流与数值流转换"><a href="#4-对象流与数值流转换" class="headerlink" title="4. 对象流与数值流转换"></a>4. 对象流与数值流转换</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 对象流转数值流</span><br>List&lt;Integer&gt; numbers = Arrays.asList(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>);<br><span class="hljs-type">IntStream</span> <span class="hljs-variable">intStream</span> <span class="hljs-operator">=</span> numbers.stream().mapToInt(Integer::intValue);<br><br><span class="hljs-comment">// 数值流转对象流</span><br>Stream&lt;Integer&gt; boxedStream = intStream.boxed();<br></code></pre></td></tr></table></figure><h2 id="七、并行流"><a href="#七、并行流" class="headerlink" title="七、并行流"></a>七、并行流</h2><p>Stream API 可以很容易地实现并行操作。</p><h3 id="1-创建并行流"><a href="#1-创建并行流" class="headerlink" title="1. 创建并行流"></a>1. 创建并行流</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 从集合创建并行流</span><br>List&lt;String&gt; list = Arrays.asList(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>);<br>Stream&lt;String&gt; parallelStream = list.parallelStream();<br><br><span class="hljs-comment">// 将顺序流转为并行流</span><br>Stream&lt;String&gt; parallelStream2 = Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>).parallel();<br></code></pre></td></tr></table></figure><h3 id="2-并行流注意事项"><a href="#2-并行流注意事项" class="headerlink" title="2. 并行流注意事项"></a>2. 并行流注意事项</h3><ul><li>确保操作是无状态的</li><li>确保操作不会对共享变量进行修改</li><li>确保操作是关联的（不影响结果顺序）</li><li>数据量较大时使用并行流才有优势</li><li>某些操作（如limit、findFirst）在并行流中性能可能更差</li></ul><h3 id="3-示例"><a href="#3-示例" class="headerlink" title="3. 示例"></a>3. 示例</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">long</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> IntStream.range(<span class="hljs-number">1</span>, <span class="hljs-number">1_000_000</span>)<br>                     .parallel()  <span class="hljs-comment">// 转为并行流</span><br>                     .filter(n -&gt; n % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>)<br>                     .count();<br>System.out.println(count);<br></code></pre></td></tr></table></figure><h2 id="八、收集器-Collectors-详解"><a href="#八、收集器-Collectors-详解" class="headerlink" title="八、收集器(Collectors)详解"></a>八、收集器(Collectors)详解</h2><p>Collectors 类提供了许多静态工厂方法，用于创建常见的收集器。</p><h3 id="1-转换为集合"><a href="#1-转换为集合" class="headerlink" title="1. 转换为集合"></a>1. 转换为集合</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;String&gt; list = stream.collect(Collectors.toList());<br>Set&lt;String&gt; set = stream.collect(Collectors.toSet());<br></code></pre></td></tr></table></figure><h3 id="2-转换为特定集合"><a href="#2-转换为特定集合" class="headerlink" title="2. 转换为特定集合"></a>2. 转换为特定集合</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Collection&lt;String&gt; treeSet = stream.collect(Collectors.toCollection(TreeSet::<span class="hljs-keyword">new</span>));<br></code></pre></td></tr></table></figure><h3 id="3-连接字符串"><a href="#3-连接字符串" class="headerlink" title="3. 连接字符串"></a>3. 连接字符串</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">String</span> <span class="hljs-variable">joined</span> <span class="hljs-operator">=</span> stream.collect(Collectors.joining()); <span class="hljs-comment">// 直接连接</span><br><span class="hljs-type">String</span> <span class="hljs-variable">joinedWithDelimiter</span> <span class="hljs-operator">=</span> stream.collect(Collectors.joining(<span class="hljs-string">&quot;, &quot;</span>)); <span class="hljs-comment">// 带分隔符</span><br><span class="hljs-type">String</span> <span class="hljs-variable">joinedWithPrefixSuffix</span> <span class="hljs-operator">=</span> stream.collect(Collectors.joining(<span class="hljs-string">&quot;, &quot;</span>, <span class="hljs-string">&quot;[&quot;</span>, <span class="hljs-string">&quot;]&quot;</span>)); <span class="hljs-comment">// 带前后缀</span><br></code></pre></td></tr></table></figure><h3 id="4-分组"><a href="#4-分组" class="headerlink" title="4. 分组"></a>4. 分组</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 简单分组</span><br>Map&lt;Integer, List&lt;String&gt;&gt; groupByLength = <br>    Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;bb&quot;</span>, <span class="hljs-string">&quot;cc&quot;</span>, <span class="hljs-string">&quot;d&quot;</span>)<br>          .collect(Collectors.groupingBy(String::length));<br><span class="hljs-comment">// &#123;1=[&quot;a&quot;, &quot;d&quot;], 2=[&quot;bb&quot;, &quot;cc&quot;]&#125;</span><br><br><span class="hljs-comment">// 多级分组</span><br>Map&lt;Integer, Map&lt;String, List&lt;String&gt;&gt;&gt; multiGroup = <br>    Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;bb&quot;</span>, <span class="hljs-string">&quot;cc&quot;</span>, <span class="hljs-string">&quot;d&quot;</span>)<br>          .collect(Collectors.groupingBy(String::length, <br>                   Collectors.groupingBy(s -&gt; s.startsWith(<span class="hljs-string">&quot;c&quot;</span>) ? <span class="hljs-string">&quot;starts with c&quot;</span> : <span class="hljs-string">&quot;others&quot;</span>)));<br></code></pre></td></tr></table></figure><h3 id="5-分区"><a href="#5-分区" class="headerlink" title="5. 分区"></a>5. 分区</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">Map&lt;Boolean, List&lt;String&gt;&gt; partition = <br>    Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;bb&quot;</span>, <span class="hljs-string">&quot;cc&quot;</span>, <span class="hljs-string">&quot;d&quot;</span>)<br>          .collect(Collectors.partitioningBy(s -&gt; s.length() &gt; <span class="hljs-number">1</span>));<br><span class="hljs-comment">// &#123;false=[&quot;a&quot;, &quot;d&quot;], true=[&quot;bb&quot;, &quot;cc&quot;]&#125;</span><br></code></pre></td></tr></table></figure><h3 id="6-统计"><a href="#6-统计" class="headerlink" title="6. 统计"></a>6. 统计</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 求和</span><br><span class="hljs-type">int</span> <span class="hljs-variable">totalLength</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;bb&quot;</span>, <span class="hljs-string">&quot;ccc&quot;</span>)<br>    .collect(Collectors.summingInt(String::length)); <span class="hljs-comment">// 6</span><br><br><span class="hljs-comment">// 平均值</span><br><span class="hljs-type">Double</span> <span class="hljs-variable">averageLength</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;bb&quot;</span>, <span class="hljs-string">&quot;ccc&quot;</span>)<br>    .collect(Collectors.averagingInt(String::length)); <span class="hljs-comment">// 2.0</span><br><br><span class="hljs-comment">// 统计汇总</span><br><span class="hljs-type">IntSummaryStatistics</span> <span class="hljs-variable">stats</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;bb&quot;</span>, <span class="hljs-string">&quot;ccc&quot;</span>)<br>    .collect(Collectors.summarizingInt(String::length));<br><span class="hljs-comment">// IntSummaryStatistics&#123;count=3, sum=6, min=1, average=2.000000, max=3&#125;</span><br></code></pre></td></tr></table></figure><h3 id="7-自定义收集器"><a href="#7-自定义收集器" class="headerlink" title="7. 自定义收集器"></a>7. 自定义收集器</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java">Collector&lt;String, StringBuilder, String&gt; customCollector = <br>    Collector.of(<br>        StringBuilder::<span class="hljs-keyword">new</span>,           <span class="hljs-comment">// supplier</span><br>        StringBuilder::append,        <span class="hljs-comment">// accumulator</span><br>        (sb1, sb2) -&gt; sb1.append(sb2), <span class="hljs-comment">// combiner</span><br>        StringBuilder::toString       <span class="hljs-comment">// finisher</span><br>    );<br><br><span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> Stream.of(<span class="hljs-string">&quot;a&quot;</span>, <span class="hljs-string">&quot;b&quot;</span>, <span class="hljs-string">&quot;c&quot;</span>).collect(customCollector);<br>System.out.println(result); <span class="hljs-comment">// &quot;abc&quot;</span><br></code></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><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Person</span> &#123;<br>    <span class="hljs-keyword">private</span> String name;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> age;<br>    <span class="hljs-keyword">private</span> String city;<br>    <span class="hljs-comment">// 构造方法、getter/setter省略</span><br>&#125;<br><br>List&lt;Person&gt; people = Arrays.asList(<br>    <span class="hljs-keyword">new</span> <span class="hljs-title class_">Person</span>(<span class="hljs-string">&quot;Alice&quot;</span>, <span class="hljs-number">25</span>, <span class="hljs-string">&quot;New York&quot;</span>),<br>    <span class="hljs-keyword">new</span> <span class="hljs-title class_">Person</span>(<span class="hljs-string">&quot;Bob&quot;</span>, <span class="hljs-number">30</span>, <span class="hljs-string">&quot;London&quot;</span>),<br>    <span class="hljs-keyword">new</span> <span class="hljs-title class_">Person</span>(<span class="hljs-string">&quot;Charlie&quot;</span>, <span class="hljs-number">20</span>, <span class="hljs-string">&quot;New York&quot;</span>),<br>    <span class="hljs-keyword">new</span> <span class="hljs-title class_">Person</span>(<span class="hljs-string">&quot;David&quot;</span>, <span class="hljs-number">35</span>, <span class="hljs-string">&quot;London&quot;</span>)<br>);<br><br><span class="hljs-comment">// 1. 筛选年龄大于25的人</span><br>List&lt;Person&gt; olderThan25 = people.stream()<br>    .filter(p -&gt; p.getAge() &gt; <span class="hljs-number">25</span>)<br>    .collect(Collectors.toList());<br><br><span class="hljs-comment">// 2. 按城市分组</span><br>Map&lt;String, List&lt;Person&gt;&gt; peopleByCity = people.stream()<br>    .collect(Collectors.groupingBy(Person::getCity));<br><br><span class="hljs-comment">// 3. 计算每个城市的平均年龄</span><br>Map&lt;String, Double&gt; avgAgeByCity = people.stream()<br>    .collect(Collectors.groupingBy(Person::getCity, <br>             Collectors.averagingInt(Person::getAge)));<br><br><span class="hljs-comment">// 4. 获取所有城市的名称（去重）</span><br>Set&lt;String&gt; cities = people.stream()<br>    .map(Person::getCity)<br>    .collect(Collectors.toSet());<br><br><span class="hljs-comment">// 5. 获取年龄最大的人</span><br>Optional&lt;Person&gt; oldest = people.stream()<br>    .max(Comparator.comparingInt(Person::getAge));<br></code></pre></td></tr></table></figure><h3 id="示例2：文件处理"><a href="#示例2：文件处理" class="headerlink" title="示例2：文件处理"></a>示例2：文件处理</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 读取文件并统计单词出现次数</span><br><span class="hljs-keyword">try</span> (Stream&lt;String&gt; lines = Files.lines(Paths.get(<span class="hljs-string">&quot;data.txt&quot;</span>))) &#123;<br>    Map&lt;String, Long&gt; wordCount = lines<br>        .flatMap(line -&gt; Arrays.stream(line.split(<span class="hljs-string">&quot;\\s+&quot;</span>)))<br>        .collect(Collectors.groupingBy(String::toLowerCase, Collectors.counting()));<br>    <br>    wordCount.forEach((word, count) -&gt; System.out.println(word + <span class="hljs-string">&quot;: &quot;</span> + count));<br>&#125; <span class="hljs-keyword">catch</span> (IOException e) &#123;<br>    e.printStackTrace();<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="十、性能考虑与最佳实践"><a href="#十、性能考虑与最佳实践" class="headerlink" title="十、性能考虑与最佳实践"></a>十、性能考虑与最佳实践</h2><ol><li><strong>避免在流中修改外部状态</strong>：保持操作无状态</li><li><strong>优先使用方法引用</strong>：使代码更简洁</li><li><strong>避免嵌套流</strong>：考虑使用flatMap替代</li><li><strong>注意自动装箱</strong>：对原始类型使用专门的流(IntStream等)</li><li><strong>谨慎使用并行流</strong>：<ul><li>数据量大时考虑使用</li><li>确保操作可以并行化</li><li>避免共享可变状态</li></ul></li><li><strong>重用流</strong>：流一旦被消费就不能再次使用</li><li><strong>考虑短路操作</strong>：findFirst、limit等可以提前终止流处理</li></ol><p>Stream API 提供了一种声明式处理数据的方式，使代码更简洁、更易读，同时能够充分利用多核处理器的优势。掌握Stream API可以显著提高Java编程效率和代码质量。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>Optional类</title>
    <link href="https://alan.org.cn/posts/Optional%E7%B1%BB.html"/>
    <id>https://alan.org.cn/posts/Optional%E7%B1%BB.html</id>
    <published>2025-05-05T01:47:53.000Z</published>
    <updated>2025-05-05T01:47:53.000Z</updated>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/categories/JAVA/"/>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/tags/JAVA/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Java-Optional-类详解与常用用法&quot;&gt;&lt;a href=&quot;#Java-Optional-类详解与常用用法&quot; class=&quot;headerlink&quot; title=&quot;Java Optional 类详解与常用用法&quot;&gt;&lt;/a&gt;Java &lt;code&gt;Optional&lt;</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="Java-Optional-类详解与常用用法"><a href="#Java-Optional-类详解与常用用法" class="headerlink" title="Java Optional 类详解与常用用法"></a>Java <code>Optional</code> 类详解与常用用法</h1><p><code>Optional</code> 是 Java 8 引入的一个容器类，用于更优雅地处理可能为 <code>null</code> 的值。它可以明确表示”值可能不存在”的情况，避免 <code>NullPointerException</code>。</p><h2 id="1-创建-Optional-对象"><a href="#1-创建-Optional-对象" class="headerlink" title="1. 创建 Optional 对象"></a>1. 创建 Optional 对象</h2><h3 id="创建包含值的-Optional"><a href="#创建包含值的-Optional" class="headerlink" title="创建包含值的 Optional"></a>创建包含值的 Optional</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Optional&lt;String&gt; optional = Optional.of(<span class="hljs-string">&quot;value&quot;</span>); <span class="hljs-comment">// 值不能为null，否则抛NullPointerException</span><br></code></pre></td></tr></table></figure><h3 id="创建可能为空的-Optional"><a href="#创建可能为空的-Optional" class="headerlink" title="创建可能为空的 Optional"></a>创建可能为空的 Optional</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Optional&lt;String&gt; optional = Optional.ofNullable(getStringThatMightBeNull());<br></code></pre></td></tr></table></figure><h3 id="创建空-Optional"><a href="#创建空-Optional" class="headerlink" title="创建空 Optional"></a>创建空 Optional</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Optional&lt;String&gt; emptyOptional = Optional.empty();<br></code></pre></td></tr></table></figure><h2 id="2-检查值是否存在"><a href="#2-检查值是否存在" class="headerlink" title="2. 检查值是否存在"></a>2. 检查值是否存在</h2><h3 id="检查是否有值"><a href="#检查是否有值" class="headerlink" title="检查是否有值"></a>检查是否有值</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">if</span> (optional.isPresent()) &#123;<br>    <span class="hljs-comment">// 值存在时的处理</span><br>&#125;<br></code></pre></td></tr></table></figure><h3 id="Java-11-简洁写法"><a href="#Java-11-简洁写法" class="headerlink" title="Java 11 简洁写法"></a>Java 11 简洁写法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">optional.ifPresent(value -&gt; System.out.println(<span class="hljs-string">&quot;Found value: &quot;</span> + value));<br></code></pre></td></tr></table></figure><h2 id="3-获取值"><a href="#3-获取值" class="headerlink" title="3. 获取值"></a>3. 获取值</h2><h3 id="不安全获取（可能抛异常）"><a href="#不安全获取（可能抛异常）" class="headerlink" title="不安全获取（可能抛异常）"></a>不安全获取（可能抛异常）</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">String</span> <span class="hljs-variable">value</span> <span class="hljs-operator">=</span> optional.get(); <span class="hljs-comment">// 如果值为空会抛NoSuchElementException</span><br></code></pre></td></tr></table></figure><h3 id="安全获取-提供默认值"><a href="#安全获取-提供默认值" class="headerlink" title="安全获取 - 提供默认值"></a>安全获取 - 提供默认值</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">String</span> <span class="hljs-variable">value</span> <span class="hljs-operator">=</span> optional.orElse(<span class="hljs-string">&quot;default&quot;</span>);<br><span class="hljs-type">String</span> <span class="hljs-variable">value</span> <span class="hljs-operator">=</span> optional.orElseGet(() -&gt; getDefaultValue()); <span class="hljs-comment">// 延迟计算默认值</span><br></code></pre></td></tr></table></figure><h3 id="安全获取-抛自定义异常"><a href="#安全获取-抛自定义异常" class="headerlink" title="安全获取 - 抛自定义异常"></a>安全获取 - 抛自定义异常</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">String</span> <span class="hljs-variable">value</span> <span class="hljs-operator">=</span> optional.orElseThrow(() -&gt; <span class="hljs-keyword">new</span> <span class="hljs-title class_">CustomException</span>(<span class="hljs-string">&quot;Value not found&quot;</span>));<br></code></pre></td></tr></table></figure><h2 id="4-链式操作"><a href="#4-链式操作" class="headerlink" title="4. 链式操作"></a>4. 链式操作</h2><h3 id="值转换-map"><a href="#值转换-map" class="headerlink" title="值转换 (map)"></a>值转换 (map)</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Optional&lt;String&gt; upperCase = optional.map(String::toUpperCase);<br></code></pre></td></tr></table></figure><h3 id="扁平化转换-flatMap"><a href="#扁平化转换-flatMap" class="headerlink" title="扁平化转换 (flatMap)"></a>扁平化转换 (flatMap)</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Optional&lt;Double&gt; result = optional.flatMap(<span class="hljs-built_in">this</span>::parseToDouble);<br></code></pre></td></tr></table></figure><h3 id="过滤-filter"><a href="#过滤-filter" class="headerlink" title="过滤 (filter)"></a>过滤 (filter)</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">Optional&lt;String&gt; filtered = optional.filter(s -&gt; s.length() &gt; <span class="hljs-number">3</span>);<br></code></pre></td></tr></table></figure><h2 id="5-组合使用示例"><a href="#5-组合使用示例" class="headerlink" title="5. 组合使用示例"></a>5. 组合使用示例</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java">Optional.ofNullable(user)<br>    .map(User::getAddress)<br>    .map(Address::getStreet)<br>    .ifPresent(street -&gt; System.out.println(<span class="hljs-string">&quot;Street: &quot;</span> + street));<br></code></pre></td></tr></table></figure><h2 id="6-实际应用场景"><a href="#6-实际应用场景" class="headerlink" title="6. 实际应用场景"></a>6. 实际应用场景</h2><h3 id="替代-null-检查"><a href="#替代-null-检查" class="headerlink" title="替代 null 检查"></a>替代 null 检查</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 传统方式</span><br><span class="hljs-keyword">if</span> (user != <span class="hljs-literal">null</span> &amp;&amp; user.getAddress() != <span class="hljs-literal">null</span>) &#123;<br>    <span class="hljs-comment">// 处理address</span><br>&#125;<br><br><span class="hljs-comment">// Optional方式</span><br>Optional.ofNullable(user)<br>    .map(User::getAddress)<br>    .ifPresent(address -&gt; &#123; <span class="hljs-comment">/* 处理address */</span> &#125;);<br></code></pre></td></tr></table></figure><h3 id="从集合中查找元素"><a href="#从集合中查找元素" class="headerlink" title="从集合中查找元素"></a>从集合中查找元素</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java">List&lt;User&gt; users = ...;<br>Optional&lt;User&gt; user = users.stream()<br>    .filter(u -&gt; u.getId().equals(id))<br>    .findFirst();<br><br>user.ifPresentOrElse(<br>    u -&gt; System.out.println(<span class="hljs-string">&quot;Found: &quot;</span> + u),<br>    () -&gt; System.out.println(<span class="hljs-string">&quot;User not found&quot;</span>)<br>);<br></code></pre></td></tr></table></figure><h2 id="7-注意事项"><a href="#7-注意事项" class="headerlink" title="7. 注意事项"></a>7. 注意事项</h2><ol><li><p><strong>不要滥用 Optional</strong>：</p><ul><li>不要作为方法参数（会使API复杂化）</li><li>不要用于类字段（设计问题）</li><li>集合应返回空集合而非 <code>Optional&lt;List&gt;</code></li></ul></li><li><p><strong>性能考虑</strong>：</p><ul><li>创建 Optional 对象有轻微开销</li><li>在性能关键代码中谨慎使用</li></ul></li><li><p><strong>不要这样用</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">if</span> (optional.isPresent()) &#123;<br>    <span class="hljs-keyword">return</span> optional.get();<br>&#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>应该用 <code>optional.orElse(null)</code></p></li></ol><p><code>Optional</code> 的正确使用可以使代码更清晰、更安全，减少 <code>NullPointerException</code> 的风险。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>Kafka学习笔记</title>
    <link href="https://alan.org.cn/posts/Kafka%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/Kafka%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.html</id>
    <published>2025-05-04T14:55:15.000Z</published>
    <updated>2025-05-04T14:55:15.000Z</updated>
    
    
    <category term="中间件" scheme="https://alan.org.cn/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    
    <category term="中间件" scheme="https://alan.org.cn/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;Kafka学习笔记&quot;&gt;&lt;a href=&quot;#Kafka学习笔记&quot; class=&quot;headerlink&quot; title=&quot;Kafka学习笔记&quot;&gt;&lt;/a&gt;Kafka学习笔记&lt;/h3&gt;&lt;h4 id=&quot;第一章：基础知识&quot;&gt;&lt;a href=&quot;#第一章：基础知识&quot; class=&quot;</summary>
      
    
    
    <content type="html"><![CDATA[<h3 id="Kafka学习笔记"><a href="#Kafka学习笔记" class="headerlink" title="Kafka学习笔记"></a>Kafka学习笔记</h3><h4 id="第一章：基础知识"><a href="#第一章：基础知识" class="headerlink" title="第一章：基础知识"></a>第一章：基础知识</h4><h5 id="Producer："><a href="#Producer：" class="headerlink" title="Producer："></a>Producer：</h5><p>消息生产者，就是向Kafka broker 发消息的客户端。</p><h5 id="Consumer："><a href="#Consumer：" class="headerlink" title="Consumer："></a>Consumer：</h5><p>消息消费者，向Kafka broker 取消息的客户端。</p><h5 id="Consumer-Group"><a href="#Consumer-Group" class="headerlink" title="Consumer Group:"></a>Consumer Group:</h5><p>消费者组，由多个consumer组成。消费者组内每个消费者负责消费不同分区的数据，一个分区只能由一个组内消费者消费；消费者组之间互不影响。所有的消费者都属于某个消费者组，即消费者组是逻辑上的一个订阅者。</p><h5 id="Broker："><a href="#Broker：" class="headerlink" title="Broker："></a>Broker：</h5><p>一台Kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。</p><h5 id="Topic："><a href="#Topic：" class="headerlink" title="Topic："></a>Topic：</h5><p> 可以理解为一个队列，生产者和消费者面向的都是一个topic。</p><h5 id="Partition："><a href="#Partition：" class="headerlink" title="Partition："></a>Partition：</h5><p>为了实现扩展性，一个非常大的topic可以分布到多个broker（即服务器）上，一个topic可以分为多个partition，每个partition是一个有序的队列。</p><h5 id="Replica："><a href="#Replica：" class="headerlink" title="Replica："></a>Replica：</h5><p>副本。一个topic的每个分区都有若干个副本，一个Leader和若干个Follower。</p><h5 id="Leader："><a href="#Leader：" class="headerlink" title="Leader："></a>Leader：</h5><p>每个分区多个副本的 “主”，生产者发送数据的对象，以及消费者消费数据的对象都是Leader。</p><h5 id="Follower："><a href="#Follower：" class="headerlink" title="Follower："></a>Follower：</h5><p>每个分区多个副本中的 “从”，实时从 Leader 中同步数据，保持和 Leader 数据的同步。Leader 发生故障时，某个Follower会成为新的 Leader。</p><h4 id="第二章：主题创建"><a href="#第二章：主题创建" class="headerlink" title="第二章：主题创建"></a>第二章：主题创建</h4><p>主题相关 kafka-topics.sh</p><ol><li>创建topic</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">kafka-topics.sh --create --topic <span class="hljs-built_in">test</span> --bootstrap-server localhost:9092<br></code></pre></td></tr></table></figure><p>参数含义：</p><ul><li>–create创建的意思</li><li>–topic 指定topic的意思后面加上topic名称</li><li>–bootstrap-server 指定连接那个kafka，<strong>必须传参</strong></li></ul><p>注意：</p><ul><li>控制台有很多日志是jdk版本问题，需要11以上的版本不会出现日志问题</li></ul><ol start="2"><li>查看topic</li></ol><p>查看topic列表</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">kafka-topics.sh --bootstrap-server localhost:9092 --list<br></code></pre></td></tr></table></figure><p>查看topic详情</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">kafka-topics.sh --bootstrap-server localhost:9092 --topic <span class="hljs-built_in">test</span>  --describe<br></code></pre></td></tr></table></figure><ol start="3"><li>修改topic</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">kafka-topics.sh --bootstrap-server localhost:9092 --topic <span class="hljs-built_in">test</span> --alter --partitions 2<br></code></pre></td></tr></table></figure><p>参数含义：</p><ul><li>–alter 修改的意思</li></ul><ol start="4"><li>删除topic</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">kafka-topics.sh --bootstrap-server localhost:9092 --topic <span class="hljs-built_in">test</span> --delete<br></code></pre></td></tr></table></figure><p>注意：</p><ul><li>删除topic时，如果是windows安装的kafka，会异常中断，linux不会</li></ul><h4 id="第三章：生产者"><a href="#第三章：生产者" class="headerlink" title="第三章：生产者"></a>第三章：生产者</h4><table><thead><tr><th>参数名</th><th>参数作用</th><th>类型</th><th>默认值</th><th>推荐值</th></tr></thead><tbody><tr><td>bootstrap.servers</td><td>集群地址，格式为：brokerIP1:端口号,brokerIP2:端口号</td><td>必须</td><td></td><td></td></tr><tr><td>key.serializer</td><td>对生产数据Key进行序列化的类完整名称</td><td>必须</td><td></td><td>Kafka提供的字符串序列化类：StringSerializer</td></tr><tr><td>value.serializer</td><td>对生产数据Value进行序列化的类完整名称</td><td>必须</td><td></td><td>Kafka提供的字符串序列化类：StringSerializer</td></tr><tr><td>interceptor.classes</td><td>拦截器类名，多个用逗号隔开</td><td>可选</td><td></td><td></td></tr><tr><td>batch.size</td><td>数据批次字节大小。此大小会和数据最大估计值进行比较，取大值。估值&#x3D;61+21+（keySize+1+valueSize+1+1）</td><td>可选</td><td>16K</td><td></td></tr><tr><td>retries</td><td>重试次数</td><td>可选</td><td>整型最大值</td><td>0或整型最大值</td></tr><tr><td>request.timeout.ms</td><td>请求超时时间</td><td>可选</td><td>30s</td><td></td></tr><tr><td>linger.ms</td><td>数据批次在缓冲区中停留时间</td><td>可选</td><td></td><td></td></tr><tr><td>acks</td><td>请求应答类型：all(-1), 0, 1</td><td>可选</td><td>all(-1)</td><td>根据数据场景进行设置</td></tr><tr><td>retry.backoff.ms</td><td>两次重试之间的时间间隔</td><td>可选</td><td>100ms</td><td></td></tr><tr><td>buffer.memory</td><td>数据收集器缓冲区内存大小</td><td>可选</td><td>32M</td><td>64M</td></tr><tr><td>max.in.flight.requests.per.connection</td><td>每个节点连接的最大同时处理请求的数量</td><td>可选</td><td>5</td><td>小于等于5</td></tr><tr><td>enable.idempotence</td><td>幂等性，</td><td>可选</td><td>true</td><td>根据数据场景进行设置</td></tr><tr><td>partitioner.ignore.keys</td><td>是否放弃使用数据key选择分区</td><td>可选</td><td>false</td><td></td></tr><tr><td>partitioner.class</td><td>分区器类名</td><td>可选</td><td>null</td><td></td></tr></tbody></table><h5 id="生产者流程"><a href="#生产者流程" class="headerlink" title="生产者流程"></a>生产者流程</h5><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-52-06-3e1eed9190cfd6393c1e4909f16d01ba-9302cd.png" alt="截屏2025-05-05 上午10.39.19"></p><h5 id="拦截器和序列化"><a href="#拦截器和序列化" class="headerlink" title="拦截器和序列化"></a>拦截器和序列化</h5><h6 id="增加拦截器类"><a href="#增加拦截器类" class="headerlink" title="增加拦截器类"></a>增加拦截器类</h6><ol><li>实现生产者拦截器接口ProducerInterceptor</li></ol><ul><li>实现接口中的方法，根据业务功能重写具体的方法</li></ul><table><thead><tr><th><em><strong>*方法名*</strong></em></th><th><em><strong>*作用*</strong></em></th></tr></thead><tbody><tr><td>onSend</td><td>数据发送前，会执行此方法，进行数据发送前的预处理</td></tr><tr><td>onAcknowledgement</td><td>数据发送后，获取应答时，会执行此方法</td></tr><tr><td>close</td><td>生产者关闭时，会执行此方法，完成一些资源回收和释放的操作</td></tr><tr><td>configure</td><td>创建生产者对象的时候，会执行此方法，可以根据场景对生产者对象的配置进行统一修改或转换。</td></tr></tbody></table><ul><li>配置自定义的拦截器</li></ul><p>ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, KafkaInterceptorMock.class.getName()</p><h5 id="回调方法"><a href="#回调方法" class="headerlink" title="回调方法"></a>回调方法</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// TODO 发送数据</span><br>producer.send(record, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Callback</span>() &#123;<br>   <span class="hljs-comment">// TODO 回调对象</span><br>   <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onCompletion</span><span class="hljs-params">(RecordMetadata recordMetadata, Exception e)</span> &#123;<br>      <span class="hljs-comment">// TODO 当数据发送成功后，会回调此方法</span><br>      System.out.println(<span class="hljs-string">&quot;数据发送成功：&quot;</span> + recordMetadata.timestamp());<br>  &#125;<br>&#125;);<br></code></pre></td></tr></table></figure><p>默认是异步发送</p><p>同步发送：代码实现上，采用的是JDK1.5增加的JUC并发编程的Future接口的get方法实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// TODO 发送数据</span><br>producer.send(record, <span class="hljs-keyword">new</span> <span class="hljs-title class_">Callback</span>() &#123;<br>   <span class="hljs-comment">// TODO 回调对象</span><br>   <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">onCompletion</span><span class="hljs-params">(RecordMetadata recordMetadata, Exception e)</span> &#123;<br>      <span class="hljs-comment">// TODO 当数据发送成功后，会回调此方法</span><br>      System.out.println(<span class="hljs-string">&quot;数据发送成功：&quot;</span> + recordMetadata.timestamp());<br>  &#125;<br>&#125;).get();<br></code></pre></td></tr></table></figure><h5 id="消息分区"><a href="#消息分区" class="headerlink" title="消息分区"></a>消息分区</h5><p>增加分区器</p><ul><li><p>实现Kafka提供的分区类接口Partitioner</p></li><li><p>我们只关注partition方法即可，因为此方法的返回结果就是需要的分区编号</p></li><li><p>配置分区器 ProducerConfig.PARTITIONER_CLASS_CONFIG, KafkaPartitionerMock.class.getName()</p></li></ul><h5 id="消息可靠性"><a href="#消息可靠性" class="headerlink" title="消息可靠性"></a>消息可靠性</h5><p>ACK应答</p><p>0：代表producer往集群发送数据不需要等到集群的返回，不确保消息发送成功。安全性最低但是效 率最高。<br>1：代表producer往集群发送数据只要leader应答就可以发送下一条，只确保leader发送成功。<br>all：代表producer往集群发送数据需要所有的follower都完成从leader的同步才会发送下一条，确保 leader发送成功和所有的副本都完成备份。安全性最⾼高，但是效率最低。</p><h5 id="重复与乱序问题"><a href="#重复与乱序问题" class="headerlink" title="重复与乱序问题"></a>重复与乱序问题</h5><p>kafka因为数据重试引发了重复与乱序问题，解决这个办法kafka引入了幂等性操作</p><h5 id="幂等性"><a href="#幂等性" class="headerlink" title="幂等性"></a>幂等性</h5><p>默认幂等性是不起作用的，所以如果想要使用幂等性操作，只需要在生产者对象的配置中开启幂等性配置即可</p><p>ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true</p><table><thead><tr><th>配置项</th><th>配置值</th><th>说明</th></tr></thead><tbody><tr><td>enable.idempotence</td><td>true</td><td>开启幂等性</td></tr><tr><td>max.in.flight.requests.per.connection</td><td>小于等于5</td><td>每个连接的在途请求数，不能大于5，取值范围为[1,5]</td></tr><tr><td>acks</td><td>all(-1)</td><td>确认应答，固定值，不能修改</td></tr><tr><td>retries</td><td>&gt;0</td><td>重试次数，推荐使用Int最大值</td></tr></tbody></table><h5 id="数据事务"><a href="#数据事务" class="headerlink" title="数据事务"></a>数据事务</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"> <span class="hljs-comment">// TODO 启动事务</span><br><br> producer.beginTransaction();<br><br><span class="hljs-comment">// TODO 终止事务</span><br><br>producer.abortTransaction();<br></code></pre></td></tr></table></figure><h4 id="第四章：数据存储"><a href="#第四章：数据存储" class="headerlink" title="第四章：数据存储"></a>第四章：数据存储</h4><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-52-06-1ab15d51d85fd681df31adc75412d146-229b89.png" alt="截屏2025-05-05 下午1.19.41"></p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-52-06-71f59dd7c2c7fe82b5e18ef3b5c0944b-eb68bb.png" alt="截屏2025-05-05 下午1.16.15"></p><h5 id="存储组件"><a href="#存储组件" class="headerlink" title="存储组件"></a>存储组件</h5><p><strong>Ø</strong> <em><strong>*KafkaApis*</strong></em> : Kafka应用接口组件，当Kafka Producer向Kafka Broker发送数据请求后，Kafka Broker接收请求，会使用Apis组件进行请求类型的判断，然后选择相应的方法进行处理。</p><p><strong>Ø</strong> <em><strong>*ReplicaManager*</strong></em> : 副本管理器组件，用于提供主题副本的相关功能，在数据的存储前进行ACK校验和事务检查，并提供数据请求的响应处理</p><p><strong>Ø</strong> <em><strong>*Partition*</strong></em> : 分区对象，主要包含分区状态变换的监控，分区上下线的处理等功能，在数据存储是主要用于对分区副本数量的相关校验，并提供追加数据的功能</p><p><strong>Ø</strong> <em><strong>*UnifiedLog*</strong></em> : 同一日志管理组件，用于管理数据日志文件的新增，删除等功能，并提供数据日志文件偏移量的相关处理。</p><p><strong>Ø</strong> <em><strong>*LocalLog*</strong></em> : 本地日志组件，管理整个分区副本的数据日志文件。假设当前主题分区中有3个日志文件，那么3个文件都会在组件中进行管理和操作。</p><p><strong>Ø</strong> <em><strong>*LogSegment*</strong></em> : 文件段组件，对应具体的某一个数据日志文件，假设当前主题分区中有3个日志文件，那么3个文件每一个都会对应一个LogSegment组件，并打开文件的数据管道FileChannel。数据存储时，就是采用组件中的FileChannel实现日志数据的追加</p><p><strong>Ø</strong> <em><strong>*LogConfig*</strong></em>: 日志配置对象，常用的数据存储配置</p><p>日志配置参数</p><table><thead><tr><th><em><strong>*参数名*</strong></em></th><th><em><strong>*参数作用*</strong></em></th><th><em><strong>*类型*</strong></em></th><th><em><strong>*默认值*</strong></em></th><th><em><strong>*推荐值*</strong></em></th></tr></thead><tbody><tr><td>min.insync.replicas</td><td>最小同步副本数量</td><td>推荐</td><td>1</td><td>2</td></tr><tr><td>log.segment.bytes</td><td>文件段字节数据大小限制</td><td>可选</td><td>1G &#x3D; 1024<em>1024</em>1024 byte</td><td></td></tr><tr><td>log.roll.hours</td><td>文件段强制滚动时间阈值</td><td>可选</td><td>7天 &#x3D;24 * 7 * 60 * 60 * 1000L ms</td><td></td></tr><tr><td>log.flush.interval.messages</td><td>满足刷写日志文件的数据条数</td><td>可选</td><td>Long.MaxValue</td><td>不推荐</td></tr><tr><td>log.flush.interval.ms</td><td>满足刷写日志文件的时间周期</td><td>可选</td><td>Long.MaxValue</td><td>不推荐</td></tr><tr><td>log.index.interval.bytes</td><td>刷写索引文件的字节数</td><td>可选</td><td>4 * 1024</td><td></td></tr><tr><td>replica.lag.time.max.ms</td><td>副本延迟同步时间</td><td>可选</td><td>30s</td><td></td></tr></tbody></table><h5 id="数据存储"><a href="#数据存储" class="headerlink" title="数据存储"></a>数据存储</h5><h6 id="ACKS校验"><a href="#ACKS校验" class="headerlink" title="ACKS校验"></a>ACKS校验</h6><p>暂未记录</p><h5 id="文件存储格式"><a href="#文件存储格式" class="headerlink" title="文件存储格式"></a>文件存储格式</h5><h6 id="数据日志文件"><a href="#数据日志文件" class="headerlink" title="数据日志文件"></a>数据日志文件</h6><p>暂未记录</p><h6 id="数据索引文件"><a href="#数据索引文件" class="headerlink" title="数据索引文件"></a>数据索引文件</h6><p>暂未记录</p><h6 id="数据时间索引文件"><a href="#数据时间索引文件" class="headerlink" title="数据时间索引文件"></a>数据时间索引文件</h6><p>某些场景中，我们不想根据顺序（偏移量）获取Kafka的数据，而是想根据时间来获取的数据。这个时候，可没有对应的偏移量来定位数据，那么查找的效率就非常低了，因为kafka还提供了时间索引文件，咱们来看看它的内容是什么</p><p>通过时间戳就可以找到偏移量，再通过偏移量找到定位信息，再通过定位信息找到数据不就非常方便了吗。</p><h6 id="查看文件内容"><a href="#查看文件内容" class="headerlink" title="查看文件内容"></a>查看文件内容</h6><p>如果我们想要查看文件的内容，直接看是看不了，需要采用特殊的之类</p><p><strong>执行查看文件的指令</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">kafka-run-class.bat kafka.tools.DumpLogSegments --files ../../data/kafka/test-0/00000000000000000000.<span class="hljs-built_in">log</span> --print-data-log<br></code></pre></td></tr></table></figure><h5 id="数据刷写"><a href="#数据刷写" class="headerlink" title="数据刷写"></a>数据刷写</h5><p>在Linux系统中，当我们把数据写入文件系统之后，其实数据在操作系统的PageCache（页缓冲）里面，并没有刷到磁盘上。如果操作系统挂了，数据就丢失了。一方面，应用程序可以调用fsync这个系统调用来强制刷盘，另一方面，操作系统有后台线程，定时刷盘。频繁调用fsync会影响性能，需要在性能和可靠性之间进行权衡。实际上，Kafka提供了参数进行数据的刷写</p><p>Ø log.flush.interval.messages ：达到消息数量时，会将数据flush到日志文件中。</p><p>Ø log.flush.interval.ms ：间隔多少时间(ms)，执行一次强制的flush操作。</p><p>Ø flush.scheduler.interval.ms：所有日志刷新到磁盘的频率</p><p>log.flush.interval.messages和log.flush.interval.ms无论哪个达到，都会flush。官方不建议通过上述的三个参数来强制写盘，数据的可靠性应该通过replica来保证，而强制flush数据到磁盘会对整体性能产生影响。</p><h5 id="副本同步"><a href="#副本同步" class="headerlink" title="副本同步"></a>副本同步</h5><p>Kafka中，分区的某个副本会被指定为 Leader，负责响应客户端的读写请求。分区中的其他副本自动成为 Follower，主动拉取（同步）Leader 副本中的数据，写入自己本地日志，确保所有副本上的数据是一致的。</p><h6 id="启动数据同步线程"><a href="#启动数据同步线程" class="headerlink" title="启动数据同步线程"></a>启动数据同步线程</h6><p>Kafka创建主题时，会根据副本分配策略向指定的Broker节点发出请求，将不同的副本节点设定为Leader或Follower。一旦某一个Broker节点设定为Follower节点，那么Follower节点会启动数据同步线程ReplicaFetcherThread，从Leader副本节点同步数据。</p><p>线程运行后，会不断重复两个操作：截断（truncate）和抓取（fetch）。</p><p>Ø 截断：为了保证分区副本的数据一致性，当分区存在Leader Epoch值时，会将副本的本地日志截断到Leader Epoch对应的最新位移处.如果分区不存在对应的 Leader Epoch 记录，那么依然使用原来的高水位机制，将日志调整到高水位值处。</p><p>Ø 抓取：向Leader同步最新的数据。</p><h6 id="生成数据同步请求"><a href="#生成数据同步请求" class="headerlink" title="生成数据同步请求"></a>生成数据同步请求</h6><p>启动线程后，需要周期地向Leader节点发送FETCH请求，用于从Leader获取数据。</p><p>等待Leader节点的响应的过程中，会阻塞当前同步数据线程。</p><h6 id="处理响应数据"><a href="#处理响应数据" class="headerlink" title="处理响应数据"></a>处理响应数据</h6><p>当Leader副本返回响应数据时，其中会包含多个分区数据，当前副本会遍历每一个分区，将分区数据写入数据文件中。</p><h5 id="更新数据偏移量"><a href="#更新数据偏移量" class="headerlink" title="更新数据偏移量"></a>更新数据偏移量</h5><p>跟新数据偏移量</p><h6 id="Offset"><a href="#Offset" class="headerlink" title="Offset"></a>Offset</h6><p>Kafka的每个分区的数据都是有序的，所谓的数据偏移量，指的就是Kafka在保存数据时，用于快速定位数据的标识，类似于Java中数组的索引，从0开始。</p><h6 id="LSO"><a href="#LSO" class="headerlink" title="LSO"></a>LSO</h6><p>起始偏移量（Log Start Offset），每个分区副本都有起始偏移量，用于表示副本数据的起始偏移位置，初始值为0。</p><p>LSO一般情况下是无需更新的，但是如果数据过期，或用户手动删除数据时，Leader的Log Start Offset可能发生变化，Follower副本的日志需要和Leader保持严格的一致，因此，如果Leader的该值发生变化，Follower自然也要发生变化保持一致。</p><h6 id="LEO"><a href="#LEO" class="headerlink" title="LEO"></a>LEO</h6><p>日志末端位移（Log End Offset），表示下一条待写入消息的offset，每个分区副本都会记录自己的LEO。对于Follower副本而言，它能读取到Leader副本 LEO 值以下的所有消息。</p><h6 id="HW"><a href="#HW" class="headerlink" title="HW"></a>HW</h6><p>高水位值（High Watermark），定义了消息可见性，标识了一个特定的消息偏移量（offset），消费者只能拉取到这个水位offset之前的消息，同时这个偏移量还可以帮助Kafka完成副本数据同步操作。</p><h5 id="数据一致性"><a href="#数据一致性" class="headerlink" title="数据一致性"></a>数据一致性</h5><p>为了提升数据的一致性，Kafka引入了高水位（HW ：High Watermark）机制，Kafka在不同的副本之间维护了一个水位线的机制（其实也是一个偏移量的概念），消费者只能读取到水位线以下的的数据。<strong>这就是所谓的木桶理论：木桶中容纳水的高度，只能是水桶中最短的那块木板的高度</strong>。这里将整个分区看成一个木桶，其中的数据看成水，而每一个副本就是木桶上的一块木板，那么这个分区（木桶）可以被消费者消费的数据（容纳的水）其实就是数据最少的那个副本的最后数据位置（木板高度）。</p><p>也就是说，消费者一开始在消费Leader的时候，虽然Leader副本中已经有a、b、c、d 4条数据，但是由于高水位线的限制，所以也只能消费到a、b这两条数据。</p><h6 id="ISR伸缩"><a href="#ISR伸缩" class="headerlink" title="ISR伸缩"></a>ISR伸缩</h6><p>ISR（In-Sync Replicas）</p><p>在Kafka中，一个Topic（主题）包含多个Partition（分区），Topic是逻辑概念，而Partition是物理分组。一个Partition包含多个Replica（副本），副本有两种类型Leader Replica&#x2F;Follower Replica，Replica之间是一个Leader副本对应多个Follower副本。注意：分区数可以大于节点数，但副本数不能大于节点数。因为副本需要分布在不同的节点上，才能达到备份的目的。</p><p>Kafka的分区副本中只有Leader副本具有数据写入的功能，而Follower副本需要不断向Leader发出申请，进行数据的同步。这里所有同步的副本会形成一个列表，我们称之为同步副本列表（<strong>In-Sync Replicas</strong>），也可以简称<em><strong>*ISR*<em><strong>，除了</strong></em>*ISR*<em><strong>以外，还有已分配的副本列表（</strong></em>*Assigned Replicas*<em><strong>），简称</strong></em>*AR。*<em><strong>这里的AR其实不仅仅包含ISR，还包含了没有同步的副本列表（</strong></em>*Out-of-Sync Replicas*<em><strong>），简称</strong></em>*OSR*</strong></em></p><p>生产者Producer生产数据时，ACKS应答机制如果设置为all（-1），那此时就需要保证同步副本列表ISR中的所有副本全部接收完毕后，Kafka才会进行确认应答。</p><p>数据存储时，只有ISR中的所有副本LEO数据都更新了，才有可能推高HW偏移量的值。这就可以看出，ISR在Kafka集群的管理中是非常重要的。</p><h4 id="第五章：-消费数据"><a href="#第五章：-消费数据" class="headerlink" title="第五章： 消费数据"></a>第五章： 消费数据</h4><p>消费流程</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-52-06-b0a65f18745ab4b7c3772308828fd08a-b19d36.png" alt="截屏2025-05-05 下午1.18.14"></p><p>基本步骤:</p><ol><li>创建配置对象<ul><li>服务器地址</li><li>key序列化</li><li>value序列化</li><li>消费者组ID</li></ul></li><li>创建消费者对象 KafkaConsumer</li><li>订阅主题 subscribe</li><li>从Kafka的主题中获取数据</li><li>关闭消费者对象</li></ol><h5 id="基本配置"><a href="#基本配置" class="headerlink" title="基本配置"></a>基本配置</h5><table><thead><tr><th>参数名</th><th>参数作用</th><th>类型</th><th>默认值</th><th>推荐值</th></tr></thead><tbody><tr><td>bootstrap.servers</td><td>集群地址，格式为：brokerIP1:端口号,brokerIP2:端口号</td><td>必须</td><td></td><td></td></tr><tr><td>key.deserializer</td><td>对数据Key进行反序列化的类完整名称</td><td>必须</td><td></td><td>Kafka提供的字符串反序列化类：StringSerializer</td></tr><tr><td>value.deserializer</td><td>对数据Value进行反序列化的类完整名称</td><td>必须</td><td></td><td>Kafka提供的字符串反序列化类：ValueSerializer</td></tr><tr><td>group.id</td><td>消费者组ID，用于标识完整的消费场景，一个组中可以包含多个不同的消费者对象。</td><td>必须</td><td></td><td></td></tr><tr><td>auto.offset.reset</td><td></td><td></td><td></td><td></td></tr><tr><td>group.instance.id</td><td>消费者实例ID，如果指定，那么在消费者组中使用此ID作为memberId前缀</td><td>可选</td><td></td><td></td></tr><tr><td>partition.assignment.strategy</td><td>分区分配策略</td><td>可选</td><td></td><td></td></tr><tr><td>enable.auto.commit</td><td>启用偏移量自动提交</td><td>可选</td><td>true</td><td></td></tr><tr><td>auto.commit.interval.ms</td><td>自动提交周期</td><td>可选</td><td>5000ms</td><td></td></tr><tr><td>fetch.max.bytes</td><td>消费者获取服务器端一批消息最大的字节数。如果服务器端一批次的数据大于该值（50m）仍然可以拉取回来这批数据，因此，这不是一个绝对最大值。一批次的大小受message.max.bytes （broker config）or max.message.bytes （topic config）影响</td><td>可选</td><td>52428800（50 m）</td><td></td></tr><tr><td>offsets.topic.num.partitions</td><td>偏移量消费主题分区数</td><td>可选</td><td>50</td><td></td></tr></tbody></table><p>获取数据：</p><p>Kafka会根据消费者发送的参数，返回数据对象ConsumerRecord。返回的数据对象中包括指定的数据。</p><table><thead><tr><th>数据项</th><th>数据含义</th></tr></thead><tbody><tr><td>topic</td><td>主题名称</td></tr><tr><td>partition</td><td>分区号</td></tr><tr><td>offset</td><td>偏移量</td></tr><tr><td>timestamp</td><td>数据时间戳</td></tr><tr><td>key</td><td>数据key</td></tr><tr><td>value</td><td>数据value</td></tr></tbody></table><h5 id="偏移量offset"><a href="#偏移量offset" class="headerlink" title="偏移量offset"></a>偏移量offset</h5><h6 id="起始偏移量"><a href="#起始偏移量" class="headerlink" title="起始偏移量"></a>起始偏移量</h6><p>kafka默认设置为LEO,需要修改服务启动后开始从什么地方读取数据需要配置：auto.offset.reset</p><ol><li>earliest：从最早的数据开始读</li><li>latest：从最晚的数据开始读</li><li>none：</li></ol><h6 id="指定偏移量消费"><a href="#指定偏移量消费" class="headerlink" title="指定偏移量消费"></a>指定偏移量消费</h6><p>如果要自定义偏移量区域，则需要先获取集群信息然后代码去实现</p><p><img src="http://bucket.alan.org.cn/blog/2026/01/17/12-52-06-09a8fc82d457b4ea5c03fe91920eebb4-c6cb6d.png" alt="截屏2025-05-05 下午2.36.08"></p><p>kafka默认会每个5秒把消费者id消费的当前偏移量进行保存，避免机器宕机导致重启不知道该从什么位置开始读取数据</p><p>如果要修改需要改：auto.commit.interval.ms 不过这个方案并不好，其实kafka还提供了手动提交方案</p><h6 id="偏移量提交"><a href="#偏移量提交" class="headerlink" title="偏移量提交"></a>偏移量提交</h6><ol><li>自动提交</li></ol><p>kafka默认就是自动提交，默认5秒提交一次</p><ol start="2"><li>手动提交</li></ol><p>首先配置ENABLE_AUTO_COMMIT_CONFIG:false 先关闭自动提交</p><ul><li>消费者对象.commitAsync() 异步提交</li><li>消费者对象.commitSync()同步提交</li></ul><p>更多情况选用异步提交</p><p> 上面的自动提交或手动提交是为了避免重复消费数据或漏消费数据，不过这两种方法依然不能很好解决这两个问题。</p><ul><li>自动提交的问题：</li></ul><p>数据重复：在消费数据时还未来得及提交偏移量，消费者就重启了，此时就会从上一个偏移量开始消费就出现了重复</p><ul><li>手动提交问题：</li></ul><p>漏消费：我们把偏移量提交了，但我们数据还没来得及处理，消费者重启了，重启的消费者就会从最新数据开始处理，就漏消费了</p><p>如果要解决重复消费与漏消费，就必须将拉取数据后的业务处理和偏移量绑定在一起，但kafka并不提供这种事务处理，所以就需要第三方，这个就没有过多介绍</p><h5 id="消费者组基本介绍"><a href="#消费者组基本介绍" class="headerlink" title="消费者组基本介绍"></a>消费者组基本介绍</h5><p>一个topic不能被多个消费者消费</p><p>一个用户可以消费多个topic</p><p>如果有两个topic，三个用户，那么最后一个用户则不能获取到数据，只起到备用作用</p><p>当有用户宕机，切换到备用用户时，备用用户如何知道之前到用户消费到哪里了？</p><p>在kafka的处理是将偏移量存放到zookeeper，但是有一个问题就是会导致消费者频繁的去更新zookeeper节点数据</p><p>所以0.90版本之前，这个信息是记录在zookeeper内的，在0.90之后的版本，offset保存在**__consumer_offsets**这个内部topic内</p><h5 id="消费者分配策略介绍"><a href="#消费者分配策略介绍" class="headerlink" title="消费者分配策略介绍"></a>消费者分配策略介绍</h5><p>消费者中的每个消费者到底消费哪一个主题分区，这个分配策略其实是由消费者的Leader决定的，这个Leader我们称之为群主。群主是多个消费者中，第一个加入组中的消费者，其他消费者我们称之为Follower，称呼上有点类似与分区的Leader和Follower。</p><p>消费者加入群组的时候，会发送一个JoinGroup请求。群主负责给每一个消费者分配分区。</p><p>每个消费者只知道自己的分配信息，只有群主知道群组内所有消费者的分配信息。</p><h5 id="消费者分配策略"><a href="#消费者分配策略" class="headerlink" title="消费者分配策略"></a>消费者分配策略</h5><ol><li><p>RoundRobinAssignor：轮询分配策略</p></li><li><p>RangeAssignor：范围分配策略</p></li><li><p>StickyAssignor：粘性分区</p></li><li><p>CooperativeStickyAssignor</p></li></ol><p>Kafka消费者默认的分区分配就是RangeAssignor，CooperativeStickyAssignor</p><h4 id="SpringBoot集成"><a href="#SpringBoot集成" class="headerlink" title="SpringBoot集成"></a>SpringBoot集成</h4><h5 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h5><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">kafka:</span><br>    <span class="hljs-attr">bootstrap-servers:</span> <span class="hljs-string">localhost:9092</span><br>    <span class="hljs-attr">producer:</span><br>      <span class="hljs-attr">acks:</span> <span class="hljs-string">all</span><br>      <span class="hljs-attr">batch-size:</span> <span class="hljs-number">16384</span><br>      <span class="hljs-attr">buffer-memory:</span> <span class="hljs-number">33554432</span><br>      <span class="hljs-attr">key-serializer:</span> <span class="hljs-string">org.apache.kafka.common.serialization.StringSerializer</span><br>      <span class="hljs-attr">value-serializer:</span> <span class="hljs-string">org.apache.kafka.common.serialization.StringSerializer</span><br>      <span class="hljs-attr">retries:</span> <span class="hljs-number">0</span><br>    <span class="hljs-attr">consumer:</span><br>      <span class="hljs-attr">group-id:</span> <span class="hljs-string">test#消费者组</span><br>      <span class="hljs-comment">#消费方式: 在有提交记录的时候，earliest与latest是一样的，从提交记录的下一条开始消费</span><br>      <span class="hljs-comment"># earliest：无提交记录，从头开始消费</span><br>      <span class="hljs-comment">#latest：无提交记录，从最新的消息的下一条开始消费</span><br>      <span class="hljs-attr">auto-offset-reset:</span> <span class="hljs-string">earliest</span><br>      <span class="hljs-attr">enable-auto-commit:</span> <span class="hljs-literal">true</span> <span class="hljs-comment">#是否自动提交偏移量offset</span><br>      <span class="hljs-attr">auto-commit-interval:</span> <span class="hljs-string">1s</span> <span class="hljs-comment">#前提是 enable-auto-commit=true。自动提交的频率</span><br>      <span class="hljs-attr">key-deserializer:</span> <span class="hljs-string">org.apache.kafka.common.serialization.StringDeserializer</span><br>      <span class="hljs-attr">value-deserializer:</span> <span class="hljs-string">org.apache.kafka.common.serialization.StringDeserializer</span><br>      <span class="hljs-attr">max-poll-records:</span> <span class="hljs-number">2</span><br>      <span class="hljs-attr">properties:</span><br>        <span class="hljs-comment">#如果在这个时间内没有收到心跳，该消费者会被踢出组并触发&#123;组再平衡 rebalance&#125;</span><br>        <span class="hljs-attr">session.timeout.ms:</span> <span class="hljs-number">120000</span><br>        <span class="hljs-comment">#最大消费时间。此决定了获取消息后提交偏移量的最大时间，超过设定的时间（默认5分钟），服务端也会认为该消费者失效。踢出并再平衡</span><br>        <span class="hljs-attr">max.poll.interval.ms:</span> <span class="hljs-number">300000</span><br>        <span class="hljs-comment">#配置控制客户端等待请求响应的最长时间。</span><br>        <span class="hljs-comment">#如果在超时之前没有收到响应，客户端将在必要时重新发送请求，</span><br>        <span class="hljs-comment">#或者如果重试次数用尽，则请求失败。</span><br>        <span class="hljs-attr">request.timeout.ms:</span> <span class="hljs-number">60000</span><br>        <span class="hljs-comment">#订阅或分配主题时，允许自动创建主题。0.11之前，必须设置false</span><br>        <span class="hljs-attr">allow.auto.create.topics:</span> <span class="hljs-literal">true</span><br>        <span class="hljs-comment">#poll方法向协调器发送心跳的频率，为session.timeout.ms的三分之一</span><br>        <span class="hljs-attr">heartbeat.interval.ms:</span> <span class="hljs-number">40000</span><br>        <span class="hljs-comment">#每个分区里返回的记录最多不超max.partitions.fetch.bytes 指定的字节</span><br>        <span class="hljs-comment">#0.10.1版本后 如果 fetch 的第一个非空分区中的第一条消息大于这个限制</span><br>        <span class="hljs-comment">#仍然会返回该消息，以确保消费者可以进行</span><br>        <span class="hljs-comment">#max.partition.fetch.bytes=1048576  #1M</span><br>    <span class="hljs-attr">listener:</span><br>      <span class="hljs-comment">#当enable.auto.commit的值设置为false时，该值会生效；为true时不会生效</span><br>      <span class="hljs-comment">#manual_immediate:需要手动调用Acknowledgment.acknowledge()后立即提交</span><br>      <span class="hljs-comment">#ack-mode: manual_immediate</span><br>      <span class="hljs-attr">missing-topics-fatal:</span> <span class="hljs-literal">true</span> <span class="hljs-comment">#如果至少有一个topic不存在，true启动失败。false忽略</span><br>      <span class="hljs-comment">#type: single #单条消费？批量消费？ #批量消费需要配合 consumer.max-poll-records</span><br>      <span class="hljs-attr">type:</span> <span class="hljs-string">batch</span><br>      <span class="hljs-attr">concurrency:</span> <span class="hljs-number">2</span> <span class="hljs-comment">#配置多少，就为为每个消费者实例创建多少个线程。多出分区的线程空闲</span><br>    <span class="hljs-attr">template:</span><br>      <span class="hljs-attr">default-topic:</span> <span class="hljs-string">&quot;test&quot;</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">9999</span><br></code></pre></td></tr></table></figure><h5 id="配置文件-1"><a href="#配置文件-1" class="headerlink" title="配置文件"></a>配置文件</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.atguigu.spring.config;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SpringBootKafkaConfig</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">TOPIC_TEST</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;test&quot;</span>;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> <span class="hljs-variable">GROUP_ID</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;test&quot;</span>;<br>&#125;<br></code></pre></td></tr></table></figure><h5 id="发送数据"><a href="#发送数据" class="headerlink" title="发送数据"></a>发送数据</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.atguigu.spring.controller;<br><br><span class="hljs-keyword">import</span> com.atguigu.springkafka.config.SpringBootKafkaConfig;<br><span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;<br><span class="hljs-keyword">import</span> cn.hutool.json.JSONUtil;<br><span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;<br><span class="hljs-keyword">import</span> org.springframework.kafka.core.KafkaTemplate;<br><span class="hljs-keyword">import</span> org.springframework.kafka.support.SendResult;<br><span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.*;<br><br><span class="hljs-keyword">import</span> org.springframework.util.concurrent.ListenableFuture;<br><span class="hljs-keyword">import</span> org.springframework.util.concurrent.ListenableFutureCallback;<br><br><span class="hljs-meta">@RestController</span><br><span class="hljs-meta">@RequestMapping(&quot;/kafka&quot;)</span><br><span class="hljs-meta">@Slf4j</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">KafkaProducerController</span> &#123;<br><br><br>    <span class="hljs-meta">@Autowired</span><br>    <span class="hljs-keyword">private</span> KafkaTemplate&lt;String, String&gt; kafkaTemplate;<br><br>    <span class="hljs-meta">@ResponseBody</span><br>    <span class="hljs-meta">@PostMapping(value = &quot;/produce&quot;, produces = &quot;application/json&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">produce</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> Object obj)</span> &#123;<br><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">obj2String</span> <span class="hljs-operator">=</span> JSONUtil.toJsonStr(obj);<br>            kafkaTemplate.send(SpringBootKafkaConfig.TOPIC_TEST, obj2String);<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;success&quot;</span>;<br>        &#125; <span class="hljs-keyword">catch</span> (Exception e) &#123;<br>            e.getMessage();<br>        &#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;success&quot;</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h5 id="接收数据"><a href="#接收数据" class="headerlink" title="接收数据"></a>接收数据</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.atguigu.spring.component;<br><br><span class="hljs-keyword">import</span> cn.hutool.json.JSONObject;<br><span class="hljs-keyword">import</span> cn.hutool.json.JSONUtil;<br><span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;<br><span class="hljs-keyword">import</span> com.atguigu.springkafka.config.SpringBootKafkaConfig;<br><span class="hljs-keyword">import</span> org.springframework.kafka.annotation.KafkaListener;<br><span class="hljs-keyword">import</span> org.springframework.kafka.support.KafkaHeaders;<br><span class="hljs-keyword">import</span> org.springframework.messaging.handler.annotation.Header;<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Component;<br><br><span class="hljs-keyword">import</span> java.util.List;<br><span class="hljs-keyword">import</span> java.util.Optional;<br><br><br><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Slf4j</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">KafkaDataConsumer</span> &#123;<br>    <span class="hljs-meta">@KafkaListener(topics = SpringBootKafkaConfig.TOPIC_TEST, groupId = SpringBootKafkaConfig.GROUP_ID)</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">topic_test</span><span class="hljs-params">(List&lt;String&gt; messages, <span class="hljs-meta">@Header(KafkaHeaders.RECEIVED_TOPIC)</span> String topic)</span> &#123;<br>        <span class="hljs-keyword">for</span> (String message : messages) &#123;<br>            <span class="hljs-keyword">final</span> <span class="hljs-type">JSONObject</span> <span class="hljs-variable">entries</span> <span class="hljs-operator">=</span> JSONUtil.parseObj(message);<br>            System.out.println(SpringBootKafkaConfig.GROUP_ID + <span class="hljs-string">&quot; 消费了： Topic:&quot;</span> + topic + <span class="hljs-string">&quot;,Message:&quot;</span> + entries.getStr(<span class="hljs-string">&quot;data&quot;</span>));<br>            <span class="hljs-comment">//ack.acknowledge();</span><br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
  </entry>
  
  <entry>
    <title>Docker学习笔记</title>
    <link href="https://alan.org.cn/posts/Docker%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/Docker%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.html</id>
    <published>2025-05-04T10:08:31.000Z</published>
    <updated>2025-05-04T10:08:31.000Z</updated>
    
    
    <category term="中间件" scheme="https://alan.org.cn/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    
    <category term="中间件" scheme="https://alan.org.cn/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Docker-学习笔记&quot;&gt;&lt;a href=&quot;#Docker-学习笔记&quot; class=&quot;headerlink&quot; title=&quot;Docker 学习笔记&quot;&gt;&lt;/a&gt;Docker 学习笔记&lt;/h1&gt;&lt;h2 id=&quot;第一章：镜像管理&quot;&gt;&lt;a href=&quot;#第一章：镜像管理&quot;</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="Docker-学习笔记"><a href="#Docker-学习笔记" class="headerlink" title="Docker 学习笔记"></a>Docker 学习笔记</h1><h2 id="第一章：镜像管理"><a href="#第一章：镜像管理" class="headerlink" title="第一章：镜像管理"></a>第一章：镜像管理</h2><table><thead><tr><th>命令</th><th>描述</th><th>常用参数</th></tr></thead><tbody><tr><td><code>docker search</code></td><td>搜索Docker Hub上的镜像</td><td><code>--limit</code> 限制搜索结果数量</td></tr><tr><td><code>docker pull</code></td><td>下载镜像到本地</td><td><code>-a</code> 下载所有标签版本</td></tr><tr><td><code>docker images</code></td><td>列出本地镜像</td><td><code>-q</code> 只显示镜像ID</td></tr><tr><td><code>docker rmi</code></td><td>删除本地镜像</td><td><code>-f</code> 强制删除</td></tr></tbody></table><p><strong>补充细节</strong>：</p><ul><li>删除镜像前需确保没有容器在使用该镜像</li><li>使用<code>docker image prune</code>可清理悬空镜像</li><li>镜像命名规范：<code>[仓库地址]/[命名空间]/[镜像名]:[标签]</code></li></ul><h2 id="第二章：容器操作"><a href="#第二章：容器操作" class="headerlink" title="第二章：容器操作"></a>第二章：容器操作</h2><table><thead><tr><th>命令</th><th>描述</th></tr></thead><tbody><tr><td><code>docker run</code></td><td>创建并启动容器</td></tr><tr><td><code>docker ps</code></td><td>查看容器列表</td></tr><tr><td><code>docker stop</code></td><td>停止运行中的容器</td></tr><tr><td><code>docker start</code></td><td>启动已停止的容器</td></tr><tr><td><code>docker restart</code></td><td>重启容器</td></tr><tr><td><code>docker stats</code></td><td>查看容器资源使用情况</td></tr><tr><td><code>docker logs</code></td><td>查看容器日志</td></tr><tr><td><code>docker exec</code></td><td>进入运行中的容器</td></tr><tr><td><code>docker rm</code></td><td>删除容器</td></tr></tbody></table><h3 id="docker-run-详细参数"><a href="#docker-run-详细参数" class="headerlink" title="docker run 详细参数"></a><code>docker run</code> 详细参数</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker run [OPTIONS] IMAGE [COMMAND] [ARG...]<br></code></pre></td></tr></table></figure><p>常用参数：</p><ul><li><code>-d</code>：后台运行（守护模式）</li><li><code>--name</code>：指定容器名称</li><li><code>-p</code>：端口映射（格式：<code>主机端口:容器端口</code>）</li><li><code>-e</code>：设置环境变量</li><li><code>--restart</code>：重启策略（<code>always|on-failure|unless-stopped</code>）</li><li><code>--network</code>：指定网络</li><li><code>-v</code>：数据卷&#x2F;目录挂载</li></ul><h3 id="其他命令细节"><a href="#其他命令细节" class="headerlink" title="其他命令细节"></a>其他命令细节</h3><p><strong>进入容器</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker <span class="hljs-built_in">exec</span> -it 容器名 /bin/bash<br><span class="hljs-comment"># 或使用sh</span><br>docker <span class="hljs-built_in">exec</span> -it 容器名 /bin/sh<br></code></pre></td></tr></table></figure><p><strong>查看容器</strong>：</p><ul><li><code>docker ps -a</code>：查看所有容器（包括已停止的）</li><li><code>docker ps -aq</code>：只显示容器ID</li></ul><p><strong>批量操作</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 停止所有容器</span><br>docker stop $(docker ps -aq)<br><br><span class="hljs-comment"># 删除所有容器</span><br>docker <span class="hljs-built_in">rm</span> -f $(docker ps -aq)<br></code></pre></td></tr></table></figure><h2 id="第三章：镜像保存与加载"><a href="#第三章：镜像保存与加载" class="headerlink" title="第三章：镜像保存与加载"></a>第三章：镜像保存与加载</h2><table><thead><tr><th>命令</th><th>描述</th></tr></thead><tbody><tr><td><code>docker commit</code></td><td>将容器保存为新镜像</td></tr><tr><td><code>docker save</code></td><td>将镜像保存为tar文件</td></tr><tr><td><code>docker load</code></td><td>从tar文件加载镜像</td></tr></tbody></table><p><strong><code>docker commit</code>示例</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker commit -m <span class="hljs-string">&quot;描述信息&quot;</span> 容器名 新镜像名:标签<br></code></pre></td></tr></table></figure><p><strong>镜像导出导入</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 导出镜像</span><br>docker save -o image.tar 镜像名:标签<br><br><span class="hljs-comment"># 导入镜像</span><br>docker load -i image.tar<br></code></pre></td></tr></table></figure><h2 id="第四章：镜像分享"><a href="#第四章：镜像分享" class="headerlink" title="第四章：镜像分享"></a>第四章：镜像分享</h2><table><thead><tr><th>命令</th><th>描述</th></tr></thead><tbody><tr><td><code>docker login</code></td><td>登录Docker Registry</td></tr><tr><td><code>docker tag</code></td><td>给镜像打标签</td></tr><tr><td><code>docker push</code></td><td>推送镜像到Registry</td></tr></tbody></table><p><strong>操作流程</strong>：</p><ol><li>登录仓库：<code>docker login registry.example.com</code></li><li>重命名镜像：<code>docker tag 原镜像 新镜像名:标签</code></li><li>推送镜像：<code>docker push 新镜像名:标签</code></li></ol><h2 id="第五章：数据存储"><a href="#第五章：数据存储" class="headerlink" title="第五章：数据存储"></a>第五章：数据存储</h2><h3 id="挂载方式"><a href="#挂载方式" class="headerlink" title="挂载方式"></a>挂载方式</h3><ol><li><strong>目录挂载</strong>：<code>-v /host/path:/container/path</code></li><li><strong>卷挂载</strong>：<code>-v volume_name:/container/path</code></li></ol><h3 id="卷管理"><a href="#卷管理" class="headerlink" title="卷管理"></a>卷管理</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 查看所有卷</span><br>docker volume <span class="hljs-built_in">ls</span><br><br><span class="hljs-comment"># 创建卷</span><br>docker volume create my_volume<br><br><span class="hljs-comment"># 查看卷详情</span><br>docker volume inspect my_volume<br></code></pre></td></tr></table></figure><p><strong>注意</strong>：</p><ul><li>卷默认存储在<code>/var/lib/docker/volumes/</code></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 bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 创建网络</span><br>docker network create my_network<br><br><span class="hljs-comment"># 查看网络</span><br>docker network <span class="hljs-built_in">ls</span><br><br><span class="hljs-comment"># 查看网络详情</span><br>docker network inspect my_network<br><br><span class="hljs-comment"># 删除网络</span><br>docker network <span class="hljs-built_in">rm</span> my_network<br></code></pre></td></tr></table></figure><h3 id="容器连接网络"><a href="#容器连接网络" class="headerlink" title="容器连接网络"></a>容器连接网络</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker run --network=my_network ...<br></code></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>自定义网络支持DNS解析（容器名即主机名）</li><li>默认bridge网络不支持容器名解析</li></ul><h2 id="第七章：Docker-Compose"><a href="#第七章：Docker-Compose" class="headerlink" title="第七章：Docker Compose"></a>第七章：Docker Compose</h2><h3 id="基本命令"><a href="#基本命令" class="headerlink" title="基本命令"></a>基本命令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 启动服务</span><br>docker compose up -d<br><br><span class="hljs-comment"># 停止服务</span><br>docker compose down<br><br><span class="hljs-comment"># 启停特定服务</span><br>docker compose start/stop service_name<br><br><span class="hljs-comment"># 扩容服务</span><br>docker compose scale service=num<br></code></pre></td></tr></table></figure><h3 id="YAML文件结构"><a href="#YAML文件结构" class="headerlink" title="YAML文件结构"></a>YAML文件结构</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">应用名称</span><br><span class="hljs-attr">services:</span><br>  <span class="hljs-string">服务名:</span><br>    <span class="hljs-attr">container_name:</span> <span class="hljs-string">容器名</span><br>    <span class="hljs-attr">image:</span> <span class="hljs-string">镜像:标签</span><br>    <span class="hljs-attr">ports:</span><br>      <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;主机端口:容器端口&quot;</span><br>    <span class="hljs-attr">environment:</span><br>      <span class="hljs-bullet">-</span> <span class="hljs-string">KEY=VALUE</span><br>    <span class="hljs-attr">volumes:</span><br>      <span class="hljs-bullet">-</span> <span class="hljs-string">卷名:容器路径</span><br>      <span class="hljs-bullet">-</span> <span class="hljs-string">主机路径:容器路径</span><br>    <span class="hljs-attr">networks:</span><br>      <span class="hljs-bullet">-</span> <span class="hljs-string">网络名</span><br>    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span><br><br><span class="hljs-attr">networks:</span><br>  <span class="hljs-string">网络名:</span><br><br><span class="hljs-attr">volumes:</span><br>  <span class="hljs-string">卷名:</span><br></code></pre></td></tr></table></figure><p><strong>注意</strong>：</p><ul><li>资源名称会添加<code>应用名_</code>前缀</li><li><code>down</code>命令可选参数：<ul><li><code>--rmi all</code>：删除相关镜像</li><li><code>-v</code>：删除关联卷</li></ul></li></ul><h2 id="第八章：Dockerfile"><a href="#第八章：Dockerfile" class="headerlink" title="第八章：Dockerfile"></a>第八章：Dockerfile</h2><h3 id="常用指令"><a href="#常用指令" class="headerlink" title="常用指令"></a>常用指令</h3><table><thead><tr><th>指令</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td><code>FROM</code></td><td>基础镜像</td><td><code>FROM ubuntu:20.04</code></td></tr><tr><td><code>RUN</code></td><td>执行命令</td><td><code>RUN apt-get update</code></td></tr><tr><td><code>CMD</code></td><td>容器启动命令</td><td><code>CMD [&quot;python&quot;, &quot;app.py&quot;]</code></td></tr><tr><td><code>EXPOSE</code></td><td>声明暴露端口</td><td><code>EXPOSE 8080</code></td></tr><tr><td><code>ENV</code></td><td>设置环境变量</td><td><code>ENV APP_HOME=/app</code></td></tr><tr><td><code>COPY</code></td><td>复制文件</td><td><code>COPY . /app</code></td></tr><tr><td><code>ADD</code></td><td>复制+解压文件</td><td><code>ADD app.tar.gz /app</code></td></tr><tr><td><code>WORKDIR</code></td><td>设置工作目录</td><td><code>WORKDIR /app</code></td></tr><tr><td><code>ENTRYPOINT</code></td><td>容器入口点</td><td><code>ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;]</code></td></tr><tr><td><code>VOLUME</code></td><td>创建挂载点</td><td><code>VOLUME /data</code></td></tr><tr><td><code>USER</code></td><td>指定运行用户</td><td><code>USER appuser</code></td></tr><tr><td><code>ARG</code></td><td>构建参数</td><td><code>ARG APP_VERSION=1.0</code></td></tr></tbody></table><p><strong>案例</strong>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">FROM openjdk:17<br><br>LABEL author=alan<br><br>COPY app.jar /app.jar<br><br>EXPOSE 8080<br><br>ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]<br></code></pre></td></tr></table></figure><h3 id="构建镜像"><a href="#构建镜像" class="headerlink" title="构建镜像"></a>构建镜像</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker build -t 镜像名:标签 -f Dockerfile路径 上下文路径<br><span class="hljs-comment"># 示例</span><br>docker build -t myapp:1.0 -f ./Dockerfile .<br></code></pre></td></tr></table></figure>]]></content>
    
  </entry>
  
  <entry>
    <title>Docker使用过的命令</title>
    <link href="https://alan.org.cn/posts/Docker%E4%BD%BF%E7%94%A8%E8%BF%87%E7%9A%84%E5%91%BD%E4%BB%A4.html"/>
    <id>https://alan.org.cn/posts/Docker%E4%BD%BF%E7%94%A8%E8%BF%87%E7%9A%84%E5%91%BD%E4%BB%A4.html</id>
    <published>2025-03-30T12:20:23.000Z</published>
    <updated>2025-03-30T12:20:23.000Z</updated>
    
    
    <category term="中间件" scheme="https://alan.org.cn/categories/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    
    <category term="中间件" scheme="https://alan.org.cn/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Docker使用过的命令记录&quot;&gt;&lt;a href=&quot;#Docker使用过的命令记录&quot; class=&quot;headerlink&quot; title=&quot;Docker使用过的命令记录&quot;&gt;&lt;/a&gt;Docker使用过的命令记录&lt;/h2&gt;&lt;h2 id=&quot;Zookeeper&quot;&gt;&lt;a hre</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="Docker使用过的命令记录"><a href="#Docker使用过的命令记录" class="headerlink" title="Docker使用过的命令记录"></a>Docker使用过的命令记录</h2><h2 id="Zookeeper"><a href="#Zookeeper" class="headerlink" title="Zookeeper"></a>Zookeeper</h2><ol><li>拉取镜像</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker pull zookeeper<br></code></pre></td></tr></table></figure><ol start="2"><li>创建挂载容器中的数据目录</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">mkdir</span> zookeeper &amp;&amp; <span class="hljs-built_in">cd</span> zookeeper<br><span class="hljs-built_in">mkdir</span> data<br></code></pre></td></tr></table></figure><ol start="3"><li>启动命令</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker run \<br>-d \<br>--name zookeeper \<br>--privileged=<span class="hljs-literal">true</span> \<br>-p 2181:2181 \<br>-v <span class="hljs-variable">$PWD</span>/data:/data \<br>-v <span class="hljs-variable">$PWD</span>/conf:/conf \<br>-v <span class="hljs-variable">$PWD</span>/logs:/datalog \<br>zookeeper<br><br></code></pre></td></tr></table></figure><p>注：-d 后台运行 -p 端口映射 -e 设置时区 –name设置容器名 –restart always自启动 -v将本地目录挂载到容器</p><ol start="4"><li>配置文件</li></ol><p>&#x2F;zookeeper&#x2F;conf下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs cfg">dataDir=/data  # 保存zookeeper中的数据<br>clientPort=2181 # 客户端连接端口，通常不做修改<br>dataLogDir=/datalog<br>tickTime=2000  # 通信心跳时间<br>initLimit=5    # LF(leader - follower)初始通信时限<br>syncLimit=2    # LF 同步通信时限<br>autopurge.snapRetainCount=3<br>autopurge.purgeInterval=0<br>maxClientCnxns=60<br>standaloneEnabled=true<br>admin.enableServer=true<br>server.1=localhost:2888:3888;2181<br></code></pre></td></tr></table></figure><h2 id="Consul"><a href="#Consul" class="headerlink" title="Consul"></a>Consul</h2><ol><li>拉取镜像</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker pull consul:1.6.1<br></code></pre></td></tr></table></figure><ol start="2"><li>启动命令</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker run \<br>-d \<br>-p 8500:8500 \<br>--name=consul \<br>consul:1.6.1 \<br>consul agent -dev \<br>-ui -node=n1 -bootstrap-expect=1 -client=0.0.0.0<br></code></pre></td></tr></table></figure><h2 id="Kafka"><a href="#Kafka" class="headerlink" title="Kafka"></a>Kafka</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker run \<br>-d \<br>--name kafka \<br>-p 9092:9092 \<br>--<span class="hljs-built_in">link</span> zookeeper:zookeeper \<br>--<span class="hljs-built_in">env</span> KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 \<br>--<span class="hljs-built_in">env</span> KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \<br>--<span class="hljs-built_in">env</span> KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \<br>--<span class="hljs-built_in">env</span> KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 wurstmeister/kafka<br><br></code></pre></td></tr></table></figure><h2 id="Rabbitmq"><a href="#Rabbitmq" class="headerlink" title="Rabbitmq"></a>Rabbitmq</h2><ol><li>拉取镜像</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker pull rabbitmq<br></code></pre></td></tr></table></figure><ol start="2"><li>启动命令</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker run -d -p 15672:15672 -p 5672:5672 \<br>        --restart=always \<br>        -e RABBITMQ_DEFAULT_USER=admin \<br>        -e RABBITMQ_DEFAULT_PASS=Aa112211 \<br>        --hostname myRabbit \<br>        --name rabbitmq \<br>        rabbitmq<br></code></pre></td></tr></table></figure><ol start="3"><li>开启Web端</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment">#进入rabbitmq容器</span><br>docker <span class="hljs-built_in">exec</span> -it 容器名/容器<span class="hljs-built_in">id</span> /bin/bash<br><span class="hljs-comment">#开启web客户端</span><br>rabbitmq-plugins <span class="hljs-built_in">enable</span> rabbitmq_management<br></code></pre></td></tr></table></figure>]]></content>
    
  </entry>
  
  <entry>
    <title>微服务学习笔记</title>
    <link href="https://alan.org.cn/posts/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.html"/>
    <id>https://alan.org.cn/posts/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.html</id>
    <published>2025-03-30T12:20:10.000Z</published>
    <updated>2025-03-30T12:20:10.000Z</updated>
    
    
    <category term="微服务" scheme="https://alan.org.cn/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
    
    <category term="微服务" scheme="https://alan.org.cn/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;微服务笔记&quot;&gt;&lt;a href=&quot;#微服务笔记&quot; class=&quot;headerlink&quot; title=&quot;微服务笔记&quot;&gt;&lt;/a&gt;微服务笔记&lt;/h2&gt;&lt;h3 id=&quot;注册中心&quot;&gt;&lt;a href=&quot;#注册中心&quot; class=&quot;headerlink&quot; title=&quot;注册中心&quot;</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="微服务笔记"><a href="#微服务笔记" class="headerlink" title="微服务笔记"></a>微服务笔记</h2><h3 id="注册中心"><a href="#注册中心" class="headerlink" title="注册中心"></a>注册中心</h3><p>基础知识:</p><ol><li>访问注册中心到服务时将以前到地址变成服务名</li><li>Eureka现在已停止更新</li><li>Eureka 支持AP，Zookeeper与Consul支持CP</li><li>在启动类可以开启**@EnableDiscoveryClient**服务发现</li><li>在RestTemplate配置类上要加@LoadBalanced来实现负载均衡(默认轮询)</li></ol><h4 id="Eureka"><a href="#Eureka" class="headerlink" title="Eureka"></a>Eureka</h4><p>一、<strong>客户端</strong>:主启动类上配置**@EnableEurekaClient**开启服务</p><ol><li>依赖</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><ol start="2"><li>配置文件</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">80</span><br><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">cloud-order-service</span><br><span class="hljs-attr">eureka:</span><br>  <span class="hljs-attr">client:</span><br>    <span class="hljs-comment">#表示是否将自己注册进EurekaServer默认为true。</span><br>    <span class="hljs-attr">register-with-eureka:</span> <span class="hljs-literal">true</span><br>    <span class="hljs-comment">#是否从EurekaServer抓取已有的注册信息，默认为true。单节点无所谓，集群必须设置为true才能配合ribbon使用负载均衡</span><br>    <span class="hljs-attr">fetchRegistry:</span> <span class="hljs-literal">true</span><br>    <span class="hljs-attr">service-url:</span><br>      <span class="hljs-comment"># defaultZone: http://localhost:7001/eureka</span><br>      <span class="hljs-attr">defaultZone:</span> <span class="hljs-string">http://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/</span> <span class="hljs-comment"># 集群版</span><br></code></pre></td></tr></table></figure><p>二、<strong>服务端</strong>: 主启动类上配置**@EnableEurekaServer**开启服务</p><ol><li>依赖</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;!--eureka-server--&gt;<br>&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-server&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><ol start="2"><li>配置文件</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">8001</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">cloud-payment-service</span><br><span class="hljs-attr">eureka:</span><br>  <span class="hljs-attr">client:</span><br>    <span class="hljs-comment">#表示是否将自己注册进EurekaServer默认为true。</span><br>    <span class="hljs-attr">register-with-eureka:</span> <span class="hljs-literal">true</span><br>    <span class="hljs-comment">#是否从EurekaServer抓取已有的注册信息，默认为true。单节点无所谓，集群必须设置为true才能配合ribbon使用负载均衡</span><br>    <span class="hljs-attr">fetchRegistry:</span> <span class="hljs-literal">true</span><br>    <span class="hljs-attr">service-url:</span><br>      <span class="hljs-comment"># 单机版</span><br>      <span class="hljs-comment"># defaultZone: http://localhost:7001/eureka</span><br>      <span class="hljs-comment"># 集群版</span><br>      <span class="hljs-attr">defaultZone:</span> <span class="hljs-string">http://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/</span> <br>  <span class="hljs-attr">instance:</span><br>    <span class="hljs-comment"># 设置服务名称</span><br>    <span class="hljs-attr">instance-id:</span> <span class="hljs-string">payment8001</span><br>    <span class="hljs-comment"># 是否显示服务ip</span><br>    <span class="hljs-attr">prefer-ip-address:</span> <span class="hljs-literal">true</span><br>    <span class="hljs-comment">#Eureka客户端向服务端发送心跳的时间间隔，单位为秒(默认是30秒)</span><br>    <span class="hljs-attr">lease-renewal-interval-in-seconds:</span> <span class="hljs-number">1</span><br>    <span class="hljs-comment">#Eureka服务端在收到最后一次心跳后等待时间上限，单位为秒(默认是90秒)，超时将剔除服务</span><br>    <span class="hljs-attr">lease-expiration-duration-in-seconds:</span> <span class="hljs-number">2</span><br></code></pre></td></tr></table></figure><h4 id="Zookeeper"><a href="#Zookeeper" class="headerlink" title="Zookeeper"></a>Zookeeper</h4><p>zookeeper需要安装到linux当中，没有ui界面，java语言开发</p><ol><li>依赖</li></ol><p>客户端</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-cloud-starter-zookeeper-discovery&lt;/artifactId&gt;<br>  &lt;!--先排除自带的zookeeper--&gt;<br>  &lt;exclusions&gt;<br>    &lt;exclusion&gt;<br>      &lt;groupId&gt;org.apache.zookeeper&lt;/groupId&gt;<br>      &lt;artifactId&gt;zookeeper&lt;/artifactId&gt;<br>    &lt;/exclusion&gt;<br>  &lt;/exclusions&gt;<br>  &lt;/dependency&gt;<br>&lt;!--添加zookeeper3.4.9版本--&gt;<br>&lt;dependency&gt;<br>  &lt;groupId&gt;org.apache.zookeeper&lt;/groupId&gt;<br>  &lt;artifactId&gt;zookeeper&lt;/artifactId&gt;<br>  &lt;version&gt;3.4.9&lt;/version&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><p>服务端</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;!-- SpringBoot整合zookeeper客户端 --&gt;<br>&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-cloud-starter-zookeeper-discovery&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><ol start="2"><li>配置文件</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment">#8004表示注册到zookeeper服务器的支付服务提供者端口号</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">8004</span><br><span class="hljs-comment">#服务别名----注册zookeeper到注册中心名称</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">cloud-provider-payment</span><br><span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">zookeeper:</span><br>      <span class="hljs-attr">connect-string:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">:2181</span> <span class="hljs-comment"># zookeeper服务器地址</span><br></code></pre></td></tr></table></figure><h4 id="Consul"><a href="#Consul" class="headerlink" title="Consul"></a>Consul</h4><p>Consulr需要安装到linux当中,有ui界面，go语言开发</p><ol><li>依赖</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;dependency&gt;<br>&lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>&lt;artifactId&gt;spring-cloud-starter-consul-discovery&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><ol start="2"><li>配置文件</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment"># consul服务端口号</span><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">80</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">cloud-consumer-order</span><br><span class="hljs-comment"># consul注册中心地址</span><br><span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">consul:</span><br>      <span class="hljs-attr">host:</span> <span class="hljs-string">localhost</span><br>      <span class="hljs-attr">port:</span> <span class="hljs-number">8500</span><br>    <span class="hljs-attr">discovery:</span><br>      <span class="hljs-comment">#hostname: 127.0.0.1</span><br>      <span class="hljs-attr">service-name:</span> <span class="hljs-string">$&#123;spring.application.name&#125;</span><br></code></pre></td></tr></table></figure><h3 id="负载均衡-LB"><a href="#负载均衡-LB" class="headerlink" title="负载均衡(LB)"></a>负载均衡(LB)</h3><p>负载均衡优点：高可用</p><h4 id="Ribbon"><a href="#Ribbon" class="headerlink" title="Ribbon"></a>Ribbon</h4><p>客户端的负载均衡工具，提供负载均衡算法和服务调用</p><p>Ribbon本地负载均衡与Nginx服务端负载均衡区别</p><p>Nginx是集中式的，客户端的所有请求都会请求nginx，由nginx转发，负载均衡由服务端实现，可以理解为挡在最前面的</p><p>Ribbon是进程内的，在调用微服务接口时候，会在注册中心上获取注册信息服务列表之后缓存到JVM本地，从而在本地实现RPC远程服务调用技术。</p><ol><li>依赖 注意：新版的Eureka自带了Ribbon可以不用引入</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-cloud-starter-netflix-ribbon&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><h5 id="一、RestTemplate的使用"><a href="#一、RestTemplate的使用" class="headerlink" title="一、RestTemplate的使用"></a>一、RestTemplate的使用</h5><ol><li><p>getForObject方法&#x2F;getForEntity方法</p></li><li><p>postForObject&#x2F;postForEntity</p></li></ol><p>Object 返回对象为响应体中数据转化成的对象，基本上可以理解为Json</p><p>Entity 返回对象为ResponseEntity对象，包含了响应中的一些重要信息，比如响应头、响应状态码、响应体等</p><h5 id="二、Ribbon默认自带的负载规则"><a href="#二、Ribbon默认自带的负载规则" class="headerlink" title="二、Ribbon默认自带的负载规则"></a>二、Ribbon默认自带的负载规则</h5><p>IRule：根据特定算法中从服务列表中选取一个要访问的服务</p><ol><li>轮询</li><li>随机</li><li>重试：先轮询失败就重试</li><li>重试扩展：先轮询失败就重试，响应速度越快权重越大，越容易被选择</li></ol><p><img src="/assets/images/post/springcloud/image-20250330000111861.png" alt="image-20250330000111861"></p><h5 id="三、负载规则替换"><a href="#三、负载规则替换" class="headerlink" title="三、负载规则替换"></a>三、负载规则替换</h5><p>注意：这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下</p><p>所以在启动类包之外可以再建一个规则包，放配置类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyselfRule</span> &#123;<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> IRule <span class="hljs-title function_">myRule</span><span class="hljs-params">()</span>&#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RandomRule</span>();<span class="hljs-comment">//定义为随机</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>主启动类添加**@RibbonClient**(name &#x3D; “CLOUD-PAYMENT-SERVICE”,configuration &#x3D; MyselfRule.class)</p><p>参数说明：</p><ol><li>name 要去访问那个服务</li><li>configuration配置的负载规则</li></ol><h5 id="四、Ribbon轮询算法原理"><a href="#四、Ribbon轮询算法原理" class="headerlink" title="四、Ribbon轮询算法原理"></a>四、Ribbon轮询算法原理</h5><p>负载均衡算法：rest接口第几次请求数 % 服务器集群总数量 &#x3D; 实际调用服务器位置下标 ，每次服务重启动后rest接口计数从1开始。</p><p>List instances &#x3D; discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”);</p><p>如：  List [0] instances &#x3D; 127.0.0.1:8002</p><p>List [1] instances &#x3D; 127.0.0.1:8001</p><p>8001+ 8002 组合成为集群，它们共计2台机器，集群总数为2， 按照轮询算法原理：</p><p>当总请求数为1时： 1 % 2 &#x3D;1 对应下标位置为1 ，则获得服务地址为127.0.0.1:8001</p><p>当总请求数位2时： 2 % 2 &#x3D;0 对应下标位置为0 ，则获得服务地址为127.0.0.1:8002</p><p>当总请求数位3时： 3 % 2 &#x3D;1 对应下标位置为1 ，则获得服务地址为127.0.0.1:8001</p><p>当总请求数位4时： 4 % 2 &#x3D;0 对应下标位置为0 ，则获得服务地址为127.0.0.1:8002</p><p>如此类推……</p><h5 id="五、源码分析"><a href="#五、源码分析" class="headerlink" title="五、源码分析"></a>五、源码分析</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//</span><br><span class="hljs-comment">// Source code recreated from a .class file by IntelliJ IDEA</span><br><span class="hljs-comment">// (powered by FernFlower decompiler)</span><br><span class="hljs-comment">//</span><br><br><span class="hljs-keyword">package</span> com.netflix.loadbalancer;<br><br><span class="hljs-keyword">import</span> com.netflix.client.config.IClientConfig;<br><span class="hljs-keyword">import</span> java.util.List;<br><span class="hljs-keyword">import</span> java.util.concurrent.atomic.AtomicInteger;<br><span class="hljs-keyword">import</span> org.slf4j.Logger;<br><span class="hljs-keyword">import</span> org.slf4j.LoggerFactory;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RoundRobinRule</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractLoadBalancerRule</span> &#123;<br>    <span class="hljs-keyword">private</span> AtomicInteger nextServerCyclicCounter;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">boolean</span> <span class="hljs-variable">AVAILABLE_ONLY_SERVERS</span> <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">boolean</span> <span class="hljs-variable">ALL_SERVERS</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">log</span> <span class="hljs-operator">=</span> LoggerFactory.getLogger(RoundRobinRule.class);<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">RoundRobinRule</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">this</span>.nextServerCyclicCounter = <span class="hljs-keyword">new</span> <span class="hljs-title class_">AtomicInteger</span>(<span class="hljs-number">0</span>);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">RoundRobinRule</span><span class="hljs-params">(ILoadBalancer lb)</span> &#123;<br>        <span class="hljs-built_in">this</span>();<br>        <span class="hljs-built_in">this</span>.setLoadBalancer(lb);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> Server <span class="hljs-title function_">choose</span><span class="hljs-params">(ILoadBalancer lb, Object key)</span> &#123;<br>        <span class="hljs-keyword">if</span> (lb == <span class="hljs-literal">null</span>) &#123;<br>            log.warn(<span class="hljs-string">&quot;no load balancer&quot;</span>);<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-type">Server</span> <span class="hljs-variable">server</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">count</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;<br><br>            <span class="hljs-keyword">while</span>(<span class="hljs-literal">true</span>) &#123;<br>                <span class="hljs-keyword">if</span> (server == <span class="hljs-literal">null</span> &amp;&amp; count++ &lt; <span class="hljs-number">10</span>) &#123;<br>                    List&lt;Server&gt; reachableServers = lb.getReachableServers();<br>                    List&lt;Server&gt; allServers = lb.getAllServers();<br>                    <span class="hljs-type">int</span> <span class="hljs-variable">upCount</span> <span class="hljs-operator">=</span> reachableServers.size();<br>                    <span class="hljs-type">int</span> <span class="hljs-variable">serverCount</span> <span class="hljs-operator">=</span> allServers.size();<br>                    <span class="hljs-keyword">if</span> (upCount != <span class="hljs-number">0</span> &amp;&amp; serverCount != <span class="hljs-number">0</span>) &#123;<br>                        <span class="hljs-type">int</span> <span class="hljs-variable">nextServerIndex</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.incrementAndGetModulo(serverCount);<br>                        server = (Server)allServers.get(nextServerIndex);<br>                        <span class="hljs-keyword">if</span> (server == <span class="hljs-literal">null</span>) &#123;<br>                            Thread.<span class="hljs-keyword">yield</span>();<br>                        &#125; <span class="hljs-keyword">else</span> &#123;<br>                            <span class="hljs-keyword">if</span> (server.isAlive() &amp;&amp; server.isReadyToServe()) &#123;<br>                                <span class="hljs-keyword">return</span> server;<br>                            &#125;<br><br>                            server = <span class="hljs-literal">null</span>;<br>                        &#125;<br>                        <span class="hljs-keyword">continue</span>;<br>                    &#125;<br><br>                    log.warn(<span class="hljs-string">&quot;No up servers available from load balancer: &quot;</span> + lb);<br>                    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>                &#125;<br><br>                <span class="hljs-keyword">if</span> (count &gt;= <span class="hljs-number">10</span>) &#123;<br>                    log.warn(<span class="hljs-string">&quot;No available alive servers after 10 tries from load balancer: &quot;</span> + lb);<br>                &#125;<br><br>                <span class="hljs-keyword">return</span> server;<br>            &#125;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">int</span> <span class="hljs-title function_">incrementAndGetModulo</span><span class="hljs-params">(<span class="hljs-type">int</span> modulo)</span> &#123;<br>        <span class="hljs-type">int</span> current;<br>        <span class="hljs-type">int</span> next;<br>        <span class="hljs-keyword">do</span> &#123;<br>            current = <span class="hljs-built_in">this</span>.nextServerCyclicCounter.get();<br>            next = (current + <span class="hljs-number">1</span>) % modulo;<br>        &#125; <span class="hljs-keyword">while</span>(!<span class="hljs-built_in">this</span>.nextServerCyclicCounter.compareAndSet(current, next));<br><br>        <span class="hljs-keyword">return</span> next;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> Server <span class="hljs-title function_">choose</span><span class="hljs-params">(Object key)</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.choose(<span class="hljs-built_in">this</span>.getLoadBalancer(), key);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">initWithNiwsConfig</span><span class="hljs-params">(IClientConfig clientConfig)</span> &#123;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h5 id="六、手写轮训"><a href="#六、手写轮训" class="headerlink" title="六、手写轮训"></a>六、手写轮训</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><code class="hljs java"><br><span class="hljs-keyword">package</span> cn.org.alan.cloud.lb;<br><br><span class="hljs-keyword">import</span> org.springframework.cloud.client.ServiceInstance;<br><br><span class="hljs-keyword">import</span> java.util.List;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> * 自定义负载均衡</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2025/3/30 10:45 AM</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">LoadBalancer</span> &#123;<br>    ServiceInstance <span class="hljs-title function_">instances</span><span class="hljs-params">(List&lt;ServiceInstance&gt; serviceInstances)</span>;<br>&#125;<br><span class="hljs-keyword">package</span> cn.org.alan.cloud.lb;<br><br><span class="hljs-keyword">import</span> org.springframework.cloud.client.ServiceInstance;<br><span class="hljs-keyword">import</span> org.springframework.stereotype.Component;<br><br><span class="hljs-keyword">import</span> java.util.List;<br><span class="hljs-keyword">import</span> java.util.concurrent.atomic.AtomicInteger;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 说明：</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Author</span> Alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Version</span> 1.0</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@Date</span> 2025/3/30 10:47 AM</span><br><span class="hljs-comment"> */</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyLB</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">LoadBalancer</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">AtomicInteger</span> <span class="hljs-variable">atomicInteger</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AtomicInteger</span>(<span class="hljs-number">0</span>);<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getAndIncrement</span><span class="hljs-params">()</span>&#123;<br>        <span class="hljs-type">int</span> current;<br>        <span class="hljs-type">int</span> next;<br>        <span class="hljs-keyword">do</span>&#123;<br>            current = <span class="hljs-built_in">this</span>.atomicInteger.get();<br>            next =current &gt;= <span class="hljs-number">2147483647</span> ? <span class="hljs-number">0</span> : current + <span class="hljs-number">1</span>;<br><br>        &#125;<span class="hljs-keyword">while</span> (!<span class="hljs-built_in">this</span>.atomicInteger.compareAndSet(current,next));<br>        System.out.println(<span class="hljs-string">&quot;next:&quot;</span>+next);<br>        <span class="hljs-keyword">return</span> next;<br>    &#125;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> ServiceInstance <span class="hljs-title function_">instances</span><span class="hljs-params">(List&lt;ServiceInstance&gt; serviceInstances)</span> &#123;<br><br>        <span class="hljs-type">int</span> <span class="hljs-variable">index</span> <span class="hljs-operator">=</span> getAndIncrement() % serviceInstances.size();<br>        <span class="hljs-keyword">return</span> serviceInstances.get(index);<br>    &#125;<br>&#125;<br> <span class="hljs-meta">@GetMapping(&quot;/consumer/payment/lb&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getPaymentLB</span><span class="hljs-params">()</span>&#123;<br>        List&lt;ServiceInstance&gt; instances = discoveryClient.getInstances(<span class="hljs-string">&quot;CLOUD-PAYMENT-SERVICE&quot;</span>);<br>        <span class="hljs-keyword">if</span>(instances==<span class="hljs-literal">null</span>||instances.size()&lt;=<span class="hljs-number">0</span>)&#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>        &#125;<br>        <span class="hljs-type">ServiceInstance</span> <span class="hljs-variable">serviceInstance</span> <span class="hljs-operator">=</span> loadBalancer.instances(instances);<br>        <span class="hljs-type">URI</span> <span class="hljs-variable">uri</span> <span class="hljs-operator">=</span> serviceInstance.getUri();<br>        <span class="hljs-keyword">return</span> restTemplate.getForObject(uri+<span class="hljs-string">&quot;/payment/lb&quot;</span>,String.class);<br>    &#125;<br></code></pre></td></tr></table></figure><h3 id="服务调用"><a href="#服务调用" class="headerlink" title="服务调用"></a>服务调用</h3><h4 id="OpenFeign"><a href="#OpenFeign" class="headerlink" title="OpenFeign"></a>OpenFeign</h4><h5 id="一、使用"><a href="#一、使用" class="headerlink" title="一、使用"></a>一、使用</h5><ol><li>依赖</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;!--openfeign--&gt;<br>&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><ol start="2"><li>开启OpenFeign</li></ol><p>在主启动类上添加**@EnableFeignClients**注解</p><p>接口+注解:微服务调用接口+@FeignClient(“要访问的服务名称”)</p><p>举例：</p><p>Service</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@FeignClient(&quot;CLOUD-PAYMENT-SERVICE&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">PaymentFeignService</span> &#123;<br><br>    <span class="hljs-meta">@GetMapping(value = &quot;/payment/get/&#123;id&#125;&quot;)</span><br>    <span class="hljs-keyword">public</span> CommonResult <span class="hljs-title function_">getPaymentById</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable(&quot;id&quot;)</span> Long id)</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>controller</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RestController</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderFeignController</span> &#123;<br>    <span class="hljs-meta">@Resource</span><br>    <span class="hljs-keyword">private</span> PaymentFeignService paymentFeignService;<br><br>    <span class="hljs-meta">@GetMapping(value = &quot;/consumer/payment/get/&#123;id&#125;&quot;)</span><br>    <span class="hljs-keyword">public</span> CommonResult <span class="hljs-title function_">getPaymentById</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> &#123;<br>        <span class="hljs-keyword">return</span> paymentFeignService.getPaymentById(id);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h5 id="二、超时控制"><a href="#二、超时控制" class="headerlink" title="二、超时控制"></a>二、超时控制</h5><p><strong>OpenFeign默认等待1秒钟，超过后报错</strong> </p><p>配置超时</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment">#设置feign客户端超时时间(OpenFeign默认支持ribbon)</span><br><span class="hljs-attr">ribbon:</span><br>  <span class="hljs-comment">#指的是建立连接所用的时间，适用于网络状况正常的情况下,两端连接所用的时间</span><br>  <span class="hljs-attr">ReadTimeout:</span> <span class="hljs-number">5000</span> <span class="hljs-comment"># 5秒</span><br>  <span class="hljs-comment">#指的是建立连接后从服务器读取到可用资源所用的时间</span><br>  <span class="hljs-attr">ConnectTimeout:</span> <span class="hljs-number">5000</span> <span class="hljs-comment"># 5秒</span><br></code></pre></td></tr></table></figure><h5 id="三、日志打印功能"><a href="#三、日志打印功能" class="headerlink" title="三、日志打印功能"></a>三、日志打印功能</h5><p>Feign 提供了日志打印功能，我们可以通过配置来调整日志级别，从而了解 Feign 中 Http 请求的细节。</p><ol><li>日志级别</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">NONE：默认的，不显示任何日志；<br>BASIC：仅记录请求方法、URL、响应状态码及执行时间；<br>HEADERS：除了 BASIC 中定义的信息之外，还有请求和响应的头信息；<br>FULL：除了 HEADERS 中定义的信息之外，还有请求和响应的正文及元数据。<br></code></pre></td></tr></table></figure><p>配置步骤</p><ol><li>配置文件开启日志</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">logging:</span><br>  <span class="hljs-attr">level:</span><br>    <span class="hljs-comment"># feign日志以什么级别监控哪个接口</span><br>    <span class="hljs-attr">cn.org.alan.cloud.service.PaymentFeignService:</span> <span class="hljs-string">debug</span><br></code></pre></td></tr></table></figure><ol start="2"><li>配置Bean</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">FeignConfig</span> &#123;<br>    <span class="hljs-meta">@Bean</span><br>    Logger.Level <span class="hljs-title function_">feignLoggerLevel</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> Logger.Level.FULL;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="服务降级"><a href="#服务降级" class="headerlink" title="服务降级"></a>服务降级</h3><h4 id="Hystrix"><a href="#Hystrix" class="headerlink" title="Hystrix"></a>Hystrix</h4><p>一、<strong>重要概念</strong></p><ol><li><p>服务降级（fallback）</p><p>服务器忙，请稍后再试，不让客户端等待并立刻返回一个友好提示，fallback</p><p>会发生降级的情况</p><p>​a. 程序运行异常</p><p>​b. 超时</p><p>​c. 服务熔断触发服务降级</p><p>​d. 线程池&#x2F;信号量打满也会导致服务降级</p></li><li><p>服务熔断（break）</p><p>类比保险丝达到最大服务访问后，直接拒绝访问，拉闸限电，然后调用服务降级的方法并返回友好提示</p><p>服务的降级-&gt;进而熔断-&gt;恢复调用链路</p></li><li><p>服务限流（flowlimit）</p><p>秒杀高并发等操作，严禁一窝蜂的过来拥挤，大家排队，一秒钟N个，有序进行</p></li></ol><p>二、<strong>使用</strong></p><ol><li>依赖</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;!--hystrix--&gt;<br>&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-cloud-starter-netflix-hystrix&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><ol start="2"><li><p>服务端，降级配置 <strong>@EnableCircuitBreaker</strong>与**@HystrixCommand** </p><p>2.1 先在主启动类上加 <strong>@EnableCircuitBreaker</strong>注解激活hystrix</p><p>2.2 然后在方法上加@HystrixCommand设置降级方法与规则</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// fallbackMethod 出问题调用那个方法</span><br><span class="hljs-comment">// @HystrixProperty 规则</span><br><span class="hljs-meta">@HystrixCommand(fallbackMethod = &quot;paymentInfo_TimeOutHandler&quot;,commandProperties = &#123;</span><br><span class="hljs-meta">  @HystrixProperty(name=&quot;execution.isolation.thread.timeoutInMilliseconds&quot;,value=&quot;3000&quot;)</span><br><span class="hljs-meta">&#125;)</span><br></code></pre></td></tr></table></figure></li><li><p>配置消费端**@EnableHystrix**和 <strong>@HystrixCommand</strong></p><p>3.1 添加配置文件</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">feign:</span><br>  <span class="hljs-attr">hystrix:</span><br>    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span><br></code></pre></td></tr></table></figure><p>3.2 启动类添加注解**@EnableHystrix**</p><p>3.3 服务添加**@HystrixCommand**</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RestController</span><br><span class="hljs-meta">@Slf4j</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OrderHystirxController</span> &#123;<br>    <span class="hljs-meta">@Resource</span><br>    <span class="hljs-keyword">private</span> PaymentHystrixService paymentHystrixService;<br><br>    <span class="hljs-meta">@GetMapping(&quot;/consumer/payment/hystrix/ok/&#123;id&#125;&quot;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">paymentInfo_OK</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable(&quot;id&quot;)</span> Integer id)</span> &#123;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> paymentHystrixService.paymentInfo_OK(id);<br>        <span class="hljs-keyword">return</span> result;<br>    &#125;<br><br>    <span class="hljs-meta">@GetMapping(&quot;/consumer/payment/hystrix/timeout/&#123;id&#125;&quot;)</span><br>    <span class="hljs-meta">@HystrixCommand(fallbackMethod = &quot;paymentTimeOutFallbackMethod&quot;, commandProperties = &#123;</span><br><span class="hljs-meta">            @HystrixProperty(name = &quot;execution.isolation.thread.timeoutInMilliseconds&quot;, value = &quot;1500&quot;)</span><br><span class="hljs-meta">    &#125;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">paymentInfo_TimeOut</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable(&quot;id&quot;)</span> Integer id)</span> &#123;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> paymentHystrixService.paymentInfo_TimeOut(id);<br>        <span class="hljs-keyword">return</span> result;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">paymentTimeOutFallbackMethod</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable(&quot;id&quot;)</span> Integer id)</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o&quot;</span>;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure></li></ol><ul><li>解决上面@HystrixCommand膨胀度问题</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// fallbackMethod 出问题调用那个方法</span><br><span class="hljs-comment">// @HystrixProperty 规则</span><br><span class="hljs-meta">@HystrixCommand(fallbackMethod = &quot;paymentInfo_TimeOutHandler&quot;,commandProperties = &#123;</span><br><span class="hljs-meta">  @HystrixProperty(name=&quot;execution.isolation.thread.timeoutInMilliseconds&quot;,value=&quot;3000&quot;)</span><br><span class="hljs-meta">&#125;)</span><br></code></pre></td></tr></table></figure><p>引入新标签**@DefaultProperties(defaultFallback &#x3D; “”)**:添加默认defaultFallback，defaultFallback是上面HystrixCommand中fallbackMethod方法</p><p>当我引入DefaultProperties后，我们在方法上就可以标注@HystrixCommand不用写后面的一堆内容，这是通用方法，如果需要单独配置的就使用上面的@HystrixCommand(fallbackMethod&#x3D;””)</p><ul><li>解决代码混乱问题</li></ul><p>降级处理在客户端80实现完成</p><p>根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口，重新新建一个类(PaymentFallbackService)实现该接口，统一为接口里面的方法进行异常处理</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FeignClient(value = &quot;CLOUD-PROVIDER-HYSTRIX-PAYMENT&quot;,fallback = PaymentFallbackService.class)</span><br></code></pre></td></tr></table></figure><p>总结：就是将之前的service接口去写一个类去实现，然后在openfeign注解中设置这个类为fallback，当方法出问题时就会去调用各自的实现方法</p><p><strong>服务熔断</strong></p><p>参数可以在HystrixCommandProperties接口中找到</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@HystrixCommand(fallbackMethod = &quot;paymentCircuitBreaker_fallback&quot;, commandProperties = &#123;</span><br><span class="hljs-meta">            @HystrixProperty(name = &quot;circuitBreaker.enabled&quot;, value = &quot;true&quot;), // 是否开启断路器</span><br><span class="hljs-meta">            @HystrixProperty(name = &quot;circuitBreaker.requestVolumeThreshold&quot;, value = &quot;10&quot;), // 请求次数</span><br><span class="hljs-meta">            @HystrixProperty(name = &quot;circuitBreaker.sleepWindowInMilliseconds&quot;, value = &quot;10000&quot;), // 时间窗口期</span><br><span class="hljs-meta">            @HystrixProperty(name = &quot;circuitBreaker.errorThresholdPercentage&quot;, value = &quot;60&quot;),// 失败率达到多少后跳闸</span><br><span class="hljs-meta">    &#125;)</span><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">paymentCircuitBreaker</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable(&quot;id&quot;)</span> Integer id)</span> &#123;<br>        <span class="hljs-keyword">if</span> (id &lt; <span class="hljs-number">0</span>)<br>        &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(<span class="hljs-string">&quot;******id 不能负数&quot;</span>);<br>        &#125;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">serialNumber</span> <span class="hljs-operator">=</span> IdUtil.simpleUUID();<br><br>        <span class="hljs-keyword">return</span> Thread.currentThread().getName() + <span class="hljs-string">&quot;\t&quot;</span> + <span class="hljs-string">&quot;调用成功，流水号: &quot;</span> + serialNumber;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">paymentCircuitBreaker_fallback</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable(&quot;id&quot;)</span> Integer id)</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;id 不能负数，请稍后再试，/(ㄒoㄒ)/~~   id: &quot;</span> + id;<br>    &#125;<br></code></pre></td></tr></table></figure><ul><li>全部配置</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//========================All</span><br><span class="hljs-meta">@HystrixCommand(fallbackMethod = &quot;str_fallbackMethod&quot;,</span><br><span class="hljs-meta">        groupKey = &quot;strGroupCommand&quot;,</span><br><span class="hljs-meta">        commandKey = &quot;strCommand&quot;,</span><br><span class="hljs-meta">        threadPoolKey = &quot;strThreadPool&quot;,</span><br><span class="hljs-meta"></span><br><span class="hljs-meta">        commandProperties = &#123;</span><br><span class="hljs-meta">                // 设置隔离策略，THREAD 表示线程池 SEMAPHORE：信号池隔离</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;execution.isolation.strategy&quot;, value = &quot;THREAD&quot;),</span><br><span class="hljs-meta">                // 当隔离策略选择信号池隔离的时候，用来设置信号池的大小（最大并发数）</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;execution.isolation.semaphore.maxConcurrentRequests&quot;, value = &quot;10&quot;),</span><br><span class="hljs-meta">                // 配置命令执行的超时时间</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;execution.isolation.thread.timeoutinMilliseconds&quot;, value = &quot;10&quot;),</span><br><span class="hljs-meta">                // 是否启用超时时间</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;execution.timeout.enabled&quot;, value = &quot;true&quot;),</span><br><span class="hljs-meta">                // 执行超时的时候是否中断</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;execution.isolation.thread.interruptOnTimeout&quot;, value = &quot;true&quot;),</span><br><span class="hljs-meta">                // 执行被取消的时候是否中断</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;execution.isolation.thread.interruptOnCancel&quot;, value = &quot;true&quot;),</span><br><span class="hljs-meta">                // 允许回调方法执行的最大并发数</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;fallback.isolation.semaphore.maxConcurrentRequests&quot;, value = &quot;10&quot;),</span><br><span class="hljs-meta">                // 服务降级是否启用，是否执行回调函数</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;fallback.enabled&quot;, value = &quot;true&quot;),</span><br><span class="hljs-meta">                // 是否启用断路器</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;circuitBreaker.enabled&quot;, value = &quot;true&quot;),</span><br><span class="hljs-meta">                // 该属性用来设置在滚动时间窗中，断路器熔断的最小请求数。例如，默认该值为 20 的时候，</span><br><span class="hljs-meta">                // 如果滚动时间窗（默认10秒）内仅收到了19个请求， 即使这19个请求都失败了，断路器也不会打开。</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;circuitBreaker.requestVolumeThreshold&quot;, value = &quot;20&quot;),</span><br><span class="hljs-meta">                // 该属性用来设置在滚动时间窗中，表示在滚动时间窗中，在请求数量超过</span><br><span class="hljs-meta">                // circuitBreaker.requestVolumeThreshold 的情况下，如果错误请求数的百分比超过50,</span><br><span class="hljs-meta">                                // 就把断路器设置为 &quot;打开&quot; 状态，否则就设置为 &quot;关闭&quot; 状态。</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;circuitBreaker.errorThresholdPercentage&quot;, value = &quot;50&quot;),</span><br><span class="hljs-meta">                // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后，</span><br><span class="hljs-meta">                // 会将断路器置为 &quot;半开&quot; 状态，尝试熔断的请求命令，如果依然失败就将断路器继续设置为 &quot;打开&quot; 状态，</span><br><span class="hljs-meta">                // 如果成功就设置为 &quot;关闭&quot; 状态。</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;circuitBreaker.sleepWindowinMilliseconds&quot;, value = &quot;5000&quot;),</span><br><span class="hljs-meta">                // 断路器强制打开</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;circuitBreaker.forceOpen&quot;, value = &quot;false&quot;),</span><br><span class="hljs-meta">                // 断路器强制关闭</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;circuitBreaker.forceClosed&quot;, value = &quot;false&quot;),</span><br><span class="hljs-meta">                // 滚动时间窗设置，该时间用于断路器判断健康度时需要收集信息的持续时间</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;metrics.rollingStats.timeinMilliseconds&quot;, value = &quot;10000&quot;),</span><br><span class="hljs-meta">                // 该属性用来设置滚动时间窗统计指标信息时划分&quot;桶&quot;的数量，断路器在收集指标信息的时候会根据</span><br><span class="hljs-meta">                // 设置的时间窗长度拆分成多个 &quot;桶&quot; 来累计各度量值，每个&quot;桶&quot;记录了一段时间内的采集指标。</span><br><span class="hljs-meta">                // 比如 10 秒内拆分成 10 个&quot;桶&quot;收集这样，所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;metrics.rollingStats.numBuckets&quot;, value = &quot;10&quot;),</span><br><span class="hljs-meta">                // 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;metrics.rollingPercentile.enabled&quot;, value = &quot;false&quot;),</span><br><span class="hljs-meta">                // 该属性用来设置百分位统计的滚动窗口的持续时间，单位为毫秒。</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;metrics.rollingPercentile.timeInMilliseconds&quot;, value = &quot;60000&quot;),</span><br><span class="hljs-meta">                // 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;metrics.rollingPercentile.numBuckets&quot;, value = &quot;60000&quot;),</span><br><span class="hljs-meta">                // 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数，</span><br><span class="hljs-meta">                // 就从最初的位置开始重写。例如，将该值设置为100, 滚动窗口为10秒，若在10秒内一个 “桶 ”中发生了500次执行，</span><br><span class="hljs-meta">                // 那么该 “桶” 中只保留 最后的100次执行的统计。另外，增加该值的大小将会增加内存量的消耗，并增加排序百分位数所需的计算时间。</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;metrics.rollingPercentile.bucketSize&quot;, value = &quot;100&quot;),</span><br><span class="hljs-meta">                // 该属性用来设置采集影响断路器状态的健康快照（请求的成功、 错误百分比）的间隔等待时间。</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;metrics.healthSnapshot.intervalinMilliseconds&quot;, value = &quot;500&quot;),</span><br><span class="hljs-meta">                // 是否开启请求缓存</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;requestCache.enabled&quot;, value = &quot;true&quot;),</span><br><span class="hljs-meta">                // HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;requestLog.enabled&quot;, value = &quot;true&quot;),</span><br><span class="hljs-meta">                        &#125;,</span><br><span class="hljs-meta">                        threadPoolProperties = &#123;</span><br><span class="hljs-meta">                // 该参数用来设置执行命令线程池的核心线程数，该值也就是命令执行的最大并发量</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;coreSize&quot;, value = &quot;10&quot;),</span><br><span class="hljs-meta">                // 该参数用来设置线程池的最大队列大小。当设置为 -1 时，线程池将使用 SynchronousQueue 实现的队列，</span><br><span class="hljs-meta">                // 否则将使用 LinkedBlockingQueue 实现的队列。</span><br><span class="hljs-meta">                @HystrixProperty(name = &quot;maxQueueSize&quot;, value = &quot;-1&quot;),</span><br><span class="hljs-meta">                // 该参数用来为队列设置拒绝阈值。 通过该参数， 即使队列没有达到最大值也能拒绝请求。</span><br><span class="hljs-meta">                // 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue</span><br><span class="hljs-meta">                                // 队列不能动态修改它的对象大小，而通过该属性就可以调整拒绝请求的队列大小了。</span><br><span class="hljs-meta">@HystrixProperty(name = &quot;queueSizeRejectionThreshold&quot;, value = &quot;5&quot;),</span><br><span class="hljs-meta">        &#125;</span><br><span class="hljs-meta">)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">strConsumer</span><span class="hljs-params">()</span> &#123;<br>  <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;hello 2020&quot;</span>;<br>&#125;<br><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">str_fallbackMethod</span><span class="hljs-params">()</span><br>&#123;<br>  <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;*****fall back str_fallbackMethod&quot;</span>;<br>&#125;<br><br></code></pre></td></tr></table></figure><p>服务限流，后面alibaba的sentinel记录</p><ul><li>服务监控hystrixDashboard</li></ul><ol><li>依赖</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;dependency&gt;<br>    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>    &lt;artifactId&gt;spring-cloud-starter-netflix-hystrix-dashboard&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><ol start="2"><li>启动类添加**@EnableHystrixDashboard**激活hystrixDashboard</li></ol><p>服务启动类中添加, 不过要确保添加@EnableCircuitBreaker开启了Hystrix</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">/**</span><br><span class="hljs-comment">    * 此配置是为了服务监控而配置，与服务容错本身无关，springcloud升级后的坑</span><br><span class="hljs-comment">    * ServletRegistrationBean因为springboot的默认路径不是&quot;/hystrix.stream&quot;，</span><br><span class="hljs-comment">    * 只要在自己的项目里配置上下面的servlet就可以了</span><br><span class="hljs-comment">    */</span><br>   <span class="hljs-meta">@Bean</span><br>   <span class="hljs-keyword">public</span> ServletRegistrationBean <span class="hljs-title function_">getServlet</span><span class="hljs-params">()</span> &#123;<br>       <span class="hljs-type">HystrixMetricsStreamServlet</span> <span class="hljs-variable">streamServlet</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HystrixMetricsStreamServlet</span>();<br>       <span class="hljs-type">ServletRegistrationBean</span> <span class="hljs-variable">registrationBean</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ServletRegistrationBean</span>(streamServlet);<br>       registrationBean.setLoadOnStartup(<span class="hljs-number">1</span>);<br>       registrationBean.addUrlMappings(<span class="hljs-string">&quot;/hystrix.stream&quot;</span>);<br>       registrationBean.setName(<span class="hljs-string">&quot;HystrixMetricsStreamServlet&quot;</span>);<br>       <span class="hljs-keyword">return</span> registrationBean;<br>   &#125;<br></code></pre></td></tr></table></figure><h3 id="网关"><a href="#网关" class="headerlink" title="网关"></a>网关</h3><h4 id="Gateway网关"><a href="#Gateway网关" class="headerlink" title="Gateway网关"></a>Gateway网关</h4><p>三大核心概念：</p><ol><li>Route（路由）</li><li>Predicate（断言）</li><li>Filter（过滤）</li></ol><p>Gateway工作流程：</p><p>客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由，将其发送到 Gateway Web Handler。</p><p>Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑，然后返回。</p><p>过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前（“pre”）或之后（“post”）执行业务逻辑。</p><p>Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等，</p><p>在“post”类型的过滤器中可以做响应内容、响应头的修改，日志的输出，流量监控等有着非常重要的作用。</p><h5 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h5><ol><li>依赖</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;!--gateway--&gt;<br>&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-cloud-starter-gateway&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><p>小细节：</p><p>引入了gateway就不用引入web和actuator依赖，不然会报错</p><ol start="2"><li>配置文件</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">cloud-gateway</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">gateway:</span><br>      <span class="hljs-attr">routes:</span><br>      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">payment_routh</span> <span class="hljs-comment">#payment_route    #路由的ID，没有固定规则但要求唯一，建议配合服务名</span><br>        <span class="hljs-attr">uri:</span> <span class="hljs-string">http://localhost:8001</span>          <span class="hljs-comment">#匹配后提供服务的路由地址</span><br>        <span class="hljs-attr">predicates:</span><br>          <span class="hljs-bullet">-</span> <span class="hljs-string">Path=/payment/get/**</span>         <span class="hljs-comment"># 断言，路径相匹配的进行路由</span><br>  <br>      <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">payment_routh2</span> <span class="hljs-comment">#payment_route    #路由的ID，没有固定规则但要求唯一，建议配合服务名</span><br>        <span class="hljs-attr">uri:</span> <span class="hljs-string">http://localhost:8001</span>          <span class="hljs-comment">#匹配后提供服务的路由地址</span><br>        <span class="hljs-attr">predicates:</span><br>          <span class="hljs-bullet">-</span> <span class="hljs-string">Path=/payment/lb/**</span>         <span class="hljs-comment"># 断言，路径相匹配的进行路由</span><br><br></code></pre></td></tr></table></figure><p>配置文件的两种配置方式</p><ol><li>直接在配置文件yml中配置如上，</li><li>代码中注入RouteLocator的Bean</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">GateWayConfig</span> &#123;<br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-keyword">public</span> RouteLocator <span class="hljs-title function_">customrouteLocator</span><span class="hljs-params">(RouteLocatorBuilder routeLocatorBuilder)</span>&#123;<br>        RouteLocatorBuilder.<span class="hljs-type">Builder</span> <span class="hljs-variable">routes</span> <span class="hljs-operator">=</span> routeLocatorBuilder.routes();<br>        routes.route(<span class="hljs-string">&quot;path_route&quot;</span>,<br>                r-&gt;r.path(<span class="hljs-string">&quot;/wiki&quot;</span>)<br>                        .uri(<span class="hljs-string">&quot;https://alan.org.cn&quot;</span>)).build();<br>        <span class="hljs-keyword">return</span> routes.build();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>上面存在的问题：</p><ol><li>地址写死</li><li>微服务中不可能只有一台机器，还需要负载均衡</li></ol><h5 id="配置动态路由"><a href="#配置动态路由" class="headerlink" title="配置动态路由"></a>配置动态路由</h5><p>动态路由其实就是把写死的地址换成我们注册中心的服务名</p><p>更改后的配置文件</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><br><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">9527</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">cloud-gateway</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">gateway:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">locator:</span><br>          <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span> <span class="hljs-comment">#开启从注册中心动态创建路由的功能，利用微服务名进行路由</span><br>      <span class="hljs-attr">routes:</span><br>        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">payment_routh</span> <span class="hljs-comment">#payment_route    #路由的ID，没有固定规则但要求唯一，建议配合服务名</span><br>          <span class="hljs-comment"># uri: http://localhost:8001          #匹配后提供服务的路由地址</span><br>          <span class="hljs-attr">uri:</span> <span class="hljs-string">lb://CLOUD-PAYMENT-SERVICE</span> <span class="hljs-comment">#匹配后提供服务的路由地址</span><br>          <span class="hljs-attr">predicates:</span><br>            <span class="hljs-bullet">-</span> <span class="hljs-string">Path=/payment/get/**</span>         <span class="hljs-comment"># 断言，路径相匹配的进行路由</span><br><br>        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">payment_routh2</span> <span class="hljs-comment">#payment_route    #路由的ID，没有固定规则但要求唯一，建议配合服务名</span><br>          <span class="hljs-comment"># uri: http://localhost:8001          #匹配后提供服务的路由地址</span><br>          <span class="hljs-attr">uri:</span> <span class="hljs-string">lb://CLOUD-PAYMENT-SERVICE</span> <span class="hljs-comment">#匹配后提供服务的路由地址</span><br>          <span class="hljs-attr">predicates:</span><br>            <span class="hljs-bullet">-</span> <span class="hljs-string">Path=/payment/lb/**</span>         <span class="hljs-comment"># 断言，路径相匹配的进行路由</span><br><br><span class="hljs-attr">eureka:</span><br>  <span class="hljs-attr">instance:</span><br>    <span class="hljs-attr">hostname:</span> <span class="hljs-string">cloud-gateway-service</span><br>  <span class="hljs-attr">client:</span> <span class="hljs-comment">#服务提供者provider注册进eureka服务列表内</span><br>    <span class="hljs-attr">service-url:</span><br>      <span class="hljs-attr">register-with-eureka:</span> <span class="hljs-literal">true</span><br>      <span class="hljs-attr">fetch-registry:</span> <span class="hljs-literal">true</span><br>      <span class="hljs-attr">defaultZone:</span> <span class="hljs-string">http://eureka7001.com:7001/eureka</span><br><br></code></pre></td></tr></table></figure><h5 id="常用的Route-Predicate"><a href="#常用的Route-Predicate" class="headerlink" title="常用的Route Predicate"></a>常用的Route Predicate</h5><p><img src="/assets/images/post/springcloud/1FAFD006-FB7A-4C24-AABE-877A0A082814.png" alt="img"></p><p><img src="/assets/images/post/springcloud/image-20250330195830549.png" alt="image-20250330195830549"></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><br><span class="hljs-attr">predicates:</span><br>  <span class="hljs-comment"># Path Route Predicate</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Path=/payment/lb/**</span>         <span class="hljs-comment"># 断言，路径相匹配的进行路由</span><br>  <span class="hljs-comment"># After Route Predicate</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]</span>         <span class="hljs-comment"># 断言，路径相匹配的进行路由</span><br>  <span class="hljs-comment"># Before Route Predicate</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]</span>         <span class="hljs-comment"># 断言，路径相匹配的进行路由</span><br>  <span class="hljs-comment"># Between Route Predicate</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]</span><br>  <span class="hljs-comment"># Cookie Route Predicate</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Cookie=username,zzyy</span><br>  <span class="hljs-comment"># Header Route Predicate</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Header=X-Request-Id,</span> <span class="hljs-string">\d+</span>  <span class="hljs-comment"># 请求头要有X-Request-Id属性并且值为整数的正则表达式</span><br>  <span class="hljs-comment"># Host Route Predicate</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Host=**.alan.org.cn</span><br>  <span class="hljs-comment"># Method Route Predicate</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Method=GET</span><br>  <span class="hljs-comment"># Query Route Predicate</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">Query=username,</span> <span class="hljs-string">\d+</span>  <span class="hljs-comment"># 要有参数名username并且值还要是整数才能路由</span><br></code></pre></td></tr></table></figure><h5 id="Filter"><a href="#Filter" class="headerlink" title="Filter"></a>Filter</h5><h6 id="常用的GatewayFilter"><a href="#常用的GatewayFilter" class="headerlink" title="常用的GatewayFilter"></a>常用的GatewayFilter</h6><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><br><br><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">9527</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">cloud-gateway</span><br>  <span class="hljs-attr">cloud:</span><br>    <span class="hljs-attr">gateway:</span><br>      <span class="hljs-attr">discovery:</span><br>        <span class="hljs-attr">locator:</span><br>          <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span> <span class="hljs-comment">#开启从注册中心动态创建路由的功能，利用微服务名进行路由</span><br>      <span class="hljs-attr">routes:</span><br>        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">payment_routh</span> <span class="hljs-comment">#payment_route    #路由的ID，没有固定规则但要求唯一，建议配合服务名</span><br>          <span class="hljs-comment"># uri: http://localhost:8001          #匹配后提供服务的路由地址</span><br>          <span class="hljs-attr">uri:</span> <span class="hljs-string">lb://CLOUD-PAYMENT-SERVICE</span> <span class="hljs-comment">#匹配后提供服务的路由地址</span><br>          <span class="hljs-attr">filters:</span><br>            <span class="hljs-bullet">-</span> <span class="hljs-string">AddRequestParameter=X-Request-Id,1024</span> <span class="hljs-comment">#过滤器工厂会在匹配的请求头加上一对请求头，名称为X-Request-Id值为1024</span><br>          <span class="hljs-attr">predicates:</span><br>            <span class="hljs-bullet">-</span> <span class="hljs-string">Path=/payment/get/**</span>         <span class="hljs-comment"># 断言，路径相匹配的进行路由</span><br><br>        <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">payment_routh2</span> <span class="hljs-comment">#payment_route    #路由的ID，没有固定规则但要求唯一，建议配合服务名</span><br>          <span class="hljs-comment"># uri: http://localhost:8001          #匹配后提供服务的路由地址</span><br>          <span class="hljs-attr">uri:</span> <span class="hljs-string">lb://CLOUD-PAYMENT-SERVICE</span> <span class="hljs-comment">#匹配后提供服务的路由地址</span><br>          <span class="hljs-attr">predicates:</span><br>            <span class="hljs-bullet">-</span> <span class="hljs-string">Path=/payment/lb/**</span>         <span class="hljs-comment"># 断言，路径相匹配的进行路由</span><br><br><span class="hljs-attr">eureka:</span><br>  <span class="hljs-attr">instance:</span><br>    <span class="hljs-attr">hostname:</span> <span class="hljs-string">cloud-gateway-service</span><br>  <span class="hljs-attr">client:</span> <span class="hljs-comment">#服务提供者provider注册进eureka服务列表内</span><br>    <span class="hljs-attr">service-url:</span><br>      <span class="hljs-attr">register-with-eureka:</span> <span class="hljs-literal">true</span><br>      <span class="hljs-attr">fetch-registry:</span> <span class="hljs-literal">true</span><br>      <span class="hljs-attr">defaultZone:</span> <span class="hljs-string">http://eureka7001.com:7001/eureka</span><br></code></pre></td></tr></table></figure><h6 id="自定义过滤器"><a href="#自定义过滤器" class="headerlink" title="自定义过滤器"></a>自定义过滤器</h6><p>主要两个接口implements <strong>GlobalFilter</strong>,<strong>Ordered</strong></p><p>案例</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Slf4j</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyLogGateWayFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">GlobalFilter</span>, Ordered &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> Mono&lt;Void&gt; <span class="hljs-title function_">filter</span><span class="hljs-params">(ServerWebExchange exchange, GatewayFilterChain chain)</span> &#123;<br>        log.info(<span class="hljs-string">&quot;Come in MyLogGateWayFilter&quot;</span>+<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>());<br>        <span class="hljs-type">String</span> <span class="hljs-variable">uname</span> <span class="hljs-operator">=</span> exchange.getRequest().getQueryParams().getFirst(<span class="hljs-string">&quot;uname&quot;</span>);<br>        <span class="hljs-keyword">if</span>(uname==<span class="hljs-literal">null</span>)&#123;<br>            log.info(<span class="hljs-string">&quot;用户名为null,非法用户&quot;</span>);<br>            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);<br>            <span class="hljs-keyword">return</span> exchange.getResponse().setComplete();<br><br>        &#125;<br>        <span class="hljs-keyword">return</span> chain.filter(exchange);<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getOrder</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-comment">// 加载过滤器的顺序，数字越小顺序越高</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h1 id="SpringCloud-Alibaba"><a href="#SpringCloud-Alibaba" class="headerlink" title="SpringCloud Alibaba"></a>SpringCloud Alibaba</h1><p>Coming soon…</p>]]></content>
    
  </entry>
  
  <entry>
    <title>Java 中的 Lambda 表达式</title>
    <link href="https://alan.org.cn/posts/lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F.html"/>
    <id>https://alan.org.cn/posts/lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F.html</id>
    <published>2024-04-30T07:25:15.000Z</published>
    <updated>2026-06-04T04:10:49.248Z</updated>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/categories/JAVA/"/>
    
    
    <category term="Java" scheme="https://alan.org.cn/tags/Java/"/>
    
    <category term="数据结构" scheme="https://alan.org.cn/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
    <category term="面向对象编程" scheme="https://alan.org.cn/tags/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B/"/>
    
    <category term="Lambda" scheme="https://alan.org.cn/tags/Lambda/"/>
    
    <category term="函数式编程" scheme="https://alan.org.cn/tags/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/"/>
    
    
    <summary type="html">&lt;p&gt;在本文中，我们将简单了解 Java 中的 Lambda 表达式。&lt;/p&gt;</summary>
    
    
    <content type="html"><![CDATA[<p>在本文中，我们将简单了解 Java 中的 Lambda 表达式。</p><span id="more"></span><h2 id="什么是-Lambda-表达式？"><a href="#什么是-Lambda-表达式？" class="headerlink" title="什么是 Lambda 表达式？"></a>什么是 Lambda 表达式？</h2><p>Lambda 表达式是 Java 8 中引入的一个新特性，它是一种匿名函数，可以用来简化代码，使代码更加简洁和易读。但是，在讲解 Lambda 表达式之前，我们先来了解一下函数式接口（Functional Interface）。</p><h3 id="函数式接口"><a href="#函数式接口" class="headerlink" title="函数式接口"></a>函数式接口</h3><p>简单的复习：一个接口（Interface）是一个抽象类，它定义了一组方法，但没有实现这些方法。一些类可以实现这个接口，从而实现接口中定义的方法。</p><p>如果一个 Java 接口<strong>有且仅有一个抽象方法</strong>，那么这个接口就是一个函数式接口。这唯一的一个抽象方法会定义这个接口的全部行为和功能。</p><p>例如 <code>java.lang.Runnable</code> 和 <code>java.util.Comparator</code> 都是函数式接口，因为它们都各自只有一个抽象方法（分别是 <code>run()</code> 和 <code>compare()</code>）。</p><p>一个函数式接口的定义如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.lang.FunctionalInterface;<br><br><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface</span> &#123;<br>    <span class="hljs-type">double</span> <span class="hljs-title function_">getValue</span><span class="hljs-params">()</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，接口 <code>MyFunctionalInterface</code> 仅有一个抽象方法 <code>double getValue()</code>，因此它是一个函数式接口。<code>@FunctionalInterface</code> 注解是可选的，不过这个注解明确的表示了这个接口是一个函数式接口，因此 Java 编译器会在编译器时检查这个接口是否符合函数式接口的定义（是否只有一个抽象方法），增加这个注解可以避免不小心添加多个抽象方法的错误，也增加了代码的可读性。</p><p>有趣的是，如果一个接口只有一个方法，那么为这个方法命名就不再非常重要了，所以 Labmda 表达式可以用来简化这个过程，省去不必要的定义。</p><h3 id="Lambda-表达式"><a href="#Lambda-表达式" class="headerlink" title="Lambda 表达式"></a>Lambda 表达式</h3><p>Lambda 表达式是一个匿名函数（anonymous function&#x2F;unnamed function），其并不会直接执行，而是作为一个函数式接口中的抽象方法的实现使用。Lambda 表达式的语法如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">(参数列表) -&gt; 方法体<br>(参数列表) -&gt; &#123; 方法体 &#125;<br></code></pre></td></tr></table></figure><p>运算符 <code>-&gt;</code> 同时被叫做 Lambda 操作符（Lambda operator）或箭头操作符（Arrow operator），它将 Lambda 表达式的参数列表和方法体分开。</p><p>下面是一个简单的 Lambda 表达式的例子：</p><p>考虑以下代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">double</span> <span class="hljs-title function_">getValue</span><span class="hljs-params">()</span> &#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-number">3.1415926</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>我们可以将这个传统的函数转换为 Lambda 表达式来简化这个方法的定义：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">() -&gt; <span class="hljs-number">3.1415926</span><br></code></pre></td></tr></table></figure><p>这个 Lambda 表达式的参数列表为空，方法体是 <code>3.1415926</code>。</p><h2 id="两种-Lambda-表达式的代码体"><a href="#两种-Lambda-表达式的代码体" class="headerlink" title="两种 Lambda 表达式的代码体"></a>两种 Lambda 表达式的代码体</h2><p>Lambda 表达式的代码体可以是一个表达式或一个代码块。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// Lambda 表达式的代码体是一个表达式</span><br>() -&gt; <span class="hljs-number">3.1415926</span>;<br>() -&gt; System.out.println(<span class="hljs-string">&quot;Hello, Lambda!&quot;</span>);<br></code></pre></td></tr></table></figure><p>上述代码中，两个 Lambda 表达式的代码体都是“表达式”，也就是说，它们都是一个单独的语句。这些语句直接写在 Lambda 表达式的右侧，不需要使用大括号 <code>&#123;&#125;</code>。这些表达式可以是一个常量、一个变量、一个方法调用、一个对象的创建等。其返回值或值会被自动返回。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// Lambda 表达式的代码体是一个代码块</span><br>() -&gt; &#123;<br>    <span class="hljs-type">double</span> <span class="hljs-variable">pi</span> <span class="hljs-operator">=</span> <span class="hljs-number">3.1415926</span>;<br>    <span class="hljs-keyword">return</span> pi;<br>&#125;;<br></code></pre></td></tr></table></figure><p>上述代码中，Lambda 表达式的代码体是一个代码块，代码块中包含了多个语句。在这种情况下，我们需要使用大括号 <code>&#123;&#125;</code> 来定义代码块，而且大括号结尾需要增加一个分号 <code>;</code>。如果使用代码块，我们需要使用 <code>return</code> 语句来返回值。</p><p>注意，我们并不需要定义返回值的类型，因为 Java 编译器可以根据上下文推断出返回值的类型，也就是说我们只需要在函数式接口中定义返回值的类型，Lambda 表达式就会自动返回这个类型的值。</p><p>由于 Lambda 表达式实际上是一个函数式接口的实现，因此我们需要提前定义这个函数式接口，然后将 Lambda 表达式作为这个接口的实现。</p><h2 id="Lambda-表达式的使用"><a href="#Lambda-表达式的使用" class="headerlink" title="Lambda 表达式的使用"></a>Lambda 表达式的使用</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.lang.FunctionalInterface;<br><br><span class="hljs-comment">// 定义一个函数式接口</span><br><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface</span> &#123;<br>    <span class="hljs-comment">// 定义一个抽象方法</span><br>    <span class="hljs-type">double</span> <span class="hljs-title function_">getValue</span><span class="hljs-params">()</span>;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LambdaExample</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-comment">// 使用 Lambda 表达式实现函数式接口（一个指向函数式接口的引用）</span><br>        <span class="hljs-type">MyFunctionalInterface</span> <span class="hljs-variable">myLambda1</span> <span class="hljs-operator">=</span> () -&gt; <span class="hljs-number">3.1415926</span>;<br>        <span class="hljs-comment">// 引用也可以分开写</span><br>        MyFunctionalInterface myLambda2;<br>        myLambda2 = () -&gt; <span class="hljs-number">2.7182818</span>;<br>        <span class="hljs-comment">// 调用 Lambda 表达式定义的方法（执行定义好的方法）</span><br>        System.out.println(<span class="hljs-string">&quot;Value of Pi: &quot;</span> + myLambda1.getValue());<br>        System.out.println(<span class="hljs-string">&quot;Value of e: &quot;</span> + myLambda2.getValue());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>输出结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">Value of Pi: 3.1415926<br>Value of e: 2.7182818<br></code></pre></td></tr></table></figure><p>注意，上述的 <code>MyFunctionalInterface myLambda1 = () -&gt; 3.1415926;</code> 并不是在创建一个<strong>新</strong>的 <code>MyFunctionalInterface</code> 对象（我们无法创建一个接口的对象），而是创建了一个指向 <code>MyFunctionalInterface</code> 的引用，这个引用指向了一个 Lambda 表达式的实现。这个引用可以调用 <code>MyFunctionalInterface</code> 中定义的抽象方法。</p><p>如果将上述的 Lambda 表达式改为传统的方法实现，代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.lang.FunctionalInterface;<br><br><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface</span> &#123;<br>    <span class="hljs-type">double</span> <span class="hljs-title function_">getValue</span><span class="hljs-params">()</span>;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ValueOfPi</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">MyFunctionalInterface</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getValue</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">3.1415926</span>;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ValueOfE</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">MyFunctionalInterface</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getValue</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">2.7182818</span>;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LambdaExample</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">ValueOfPi</span> <span class="hljs-variable">myLambda1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ValueOfPi</span>();<br>        <span class="hljs-comment">// 或者使用 MyFunctionalInterface myLambda1 = new ValueOfPi();</span><br>        <span class="hljs-type">MyFunctionalInterface</span> <span class="hljs-variable">myLambda2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ValueOfE</span>();<br>        <span class="hljs-comment">// 或者使用 ValueOfE myLambda2 = new ValueOfE();</span><br>        System.out.println(<span class="hljs-string">&quot;Value of Pi: &quot;</span> + myLambda1.getValue());<br>        System.out.println(<span class="hljs-string">&quot;Value of e: &quot;</span> + myLambda2.getValue());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="有参数的-Lambda-表达式"><a href="#有参数的-Lambda-表达式" class="headerlink" title="有参数的 Lambda 表达式"></a>有参数的 Lambda 表达式</h2><p>Lambda 表达式也可以有参数，参数列表写在 <code>()</code> 中，参数之间使用逗号 <code>,</code> 分隔。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">(n) -&gt; n % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>; <span class="hljs-comment">// 判断 n 是否为偶数，注意，我们并不一定需要写参数的类型，因为函数式接口已经定义了参数的类型</span><br>(a, b) -&gt; a + b; <span class="hljs-comment">// 计算 a 和 b 的和</span><br></code></pre></td></tr></table></figure><p>一个较为复杂的例子（返回一个反过来的字符串）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.lang.FunctionalInterface;<br><br><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">ReverseString</span> &#123;<br>    String <span class="hljs-title function_">reverse</span><span class="hljs-params">(String str)</span>;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LambdaExample</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">ReverseString</span> <span class="hljs-variable">reverseString</span> <span class="hljs-operator">=</span> (str) -&gt; &#123;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;&quot;</span>;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> str.length() - <span class="hljs-number">1</span>; i &gt;= <span class="hljs-number">0</span>; i--) &#123;<br>                result += str.charAt(i);<br>            &#125;<br>            <span class="hljs-keyword">return</span> result;<br>        &#125;;<br>        <span class="hljs-type">String</span> <span class="hljs-variable">str</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;Hello, Lambda!&quot;</span>;<br>        System.out.println(str + <span class="hljs-string">&quot; reversed is: &quot;</span> + reverseString.reverse(str));<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>输出结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">Hello, Lambda! reversed is: !adbmaL ,olleH<br></code></pre></td></tr></table></figure><p>另一个例子：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface1</span> &#123;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span>;<br>&#125;<br><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface2</span> &#123;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span>;<br>&#125;<br><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface3</span> &#123;<br>    <span class="hljs-type">int</span> <span class="hljs-title function_">show</span><span class="hljs-params">(<span class="hljs-type">int</span> a, <span class="hljs-type">int</span> b)</span>;<br>&#125;<br><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface4</span> &#123;<br>    String <span class="hljs-title function_">show</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span>;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LambdaExample</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">MyFunctionalInterface1</span> <span class="hljs-variable">myLambda1</span> <span class="hljs-operator">=</span> () -&gt; System.out.println(<span class="hljs-string">&quot;Hello, Lambda!&quot;</span>); <span class="hljs-comment">// 对函数式接口的实现</span><br>        <span class="hljs-type">MyFunctionalInterface2</span> <span class="hljs-variable">myLambda2</span> <span class="hljs-operator">=</span> (n) -&gt; System.out.println(<span class="hljs-string">&quot;Number: &quot;</span> + n);<br>        <span class="hljs-type">MyFunctionalInterface3</span> <span class="hljs-variable">myLambda3</span> <span class="hljs-operator">=</span> (a, b) -&gt; a + b;<br>        <span class="hljs-type">MyFunctionalInterface4</span> <span class="hljs-variable">myLambda4</span> <span class="hljs-operator">=</span> (<span class="hljs-type">int</span> n) -&gt; &#123; <span class="hljs-comment">// 如果你想的话，参数类型可以被明确指定</span><br>            <span class="hljs-keyword">if</span> (n == <span class="hljs-number">1</span> || n == <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;Not a prime number&quot;</span>;<br>            &#125;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">2</span>; i &lt; n; i++) &#123;<br>                <span class="hljs-keyword">if</span> (n % i == <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;Not a prime number&quot;</span>;<br>                &#125;<br>            &#125;<br>            <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;Prime number&quot;</span>;<br>        &#125;;<br><br>        <span class="hljs-comment">// 调用 Lambda 表达式定义的方法，我们仍然需要使用函数式接口里定义过的方法名</span><br>        myLambda1.show();<br>        myLambda2.show(<span class="hljs-number">42</span>);<br>        System.out.println(<span class="hljs-string">&quot;Sum of 3 and 5: &quot;</span> + myLambda3.show(<span class="hljs-number">3</span>, <span class="hljs-number">5</span>));<br>        System.out.println(myLambda4.show(<span class="hljs-number">5</span>));<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>输出结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">Hello, Lambda!<br>Number: 42<br>Sum of 3 and 5: 8<br>Prime number<br></code></pre></td></tr></table></figure><h2 id="将-Lambda-表达式作为参数传递"><a href="#将-Lambda-表达式作为参数传递" class="headerlink" title="将 Lambda 表达式作为参数传递"></a>将 Lambda 表达式作为参数传递</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface</span> &#123;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">calculate</span><span class="hljs-params">(<span class="hljs-type">int</span> a, <span class="hljs-type">int</span> b)</span>;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LambdaExample</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">operate</span><span class="hljs-params">(<span class="hljs-type">int</span> a, <span class="hljs-type">int</span> b, MyFunctionalInterface myFunctionalInterface)</span> &#123;<br>        myFunctionalInterface.calculate(a, b);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">MyFunctionalInterface</span> <span class="hljs-variable">addition</span> <span class="hljs-operator">=</span> (a, b) -&gt; System.out.println(a + b);<br>        <span class="hljs-type">MyFunctionalInterface</span> <span class="hljs-variable">subtraction</span> <span class="hljs-operator">=</span> (a, b) -&gt; System.out.println(a - b);<br><br>        operate(<span class="hljs-number">5</span>, <span class="hljs-number">3</span>, addition);<br>        operate(<span class="hljs-number">5</span>, <span class="hljs-number">3</span>, subtraction);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>输出结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">8<br>2<br></code></pre></td></tr></table></figure><p>在上述代码中，我们定义了一个 <code>operate</code> 方法，这个方法接受两个整数和一个函数式接口作为参数。我们可以将 Lambda 表达式的实现作为参数传递给 <code>operate</code> 方法，然后在 <code>operate</code> 方法中调用这个函数式接口的方法。</p><p>我们其实也可以直接将 Lambda 表达式作为参数传递给方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface</span> &#123;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">calculate</span><span class="hljs-params">(<span class="hljs-type">int</span> a, <span class="hljs-type">int</span> b)</span>;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LambdaExample</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">operate</span><span class="hljs-params">(<span class="hljs-type">int</span> a, <span class="hljs-type">int</span> b, MyFunctionalInterface myFunctionalInterface)</span> &#123;<br>        myFunctionalInterface.calculate(a, b);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        operate(<span class="hljs-number">5</span>, <span class="hljs-number">3</span>, (a, b) -&gt; System.out.println(a + b));<br>        operate(<span class="hljs-number">5</span>, <span class="hljs-number">3</span>, (a, b) -&gt; System.out.println(a - b));<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>这两个代码片段实际上是等价的。</p><h2 id="例题1"><a href="#例题1" class="headerlink" title="例题1"></a>例题1</h2><p>使用函数式接口和 Lambda 表达式，实现一个程序，确认一个数字是否存在与一个数组中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.ArrayList;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LambdaExample</span> &#123;<br>    <span class="hljs-meta">@FunctionalInterface</span><br>    <span class="hljs-keyword">interface</span> <span class="hljs-title class_">MyFunctionalInterface</span> &#123;<br>        <span class="hljs-type">boolean</span> <span class="hljs-title function_">find</span><span class="hljs-params">(ArrayList&lt;Integer&gt; list, <span class="hljs-type">int</span> n)</span>;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">MyFunctionalInterface</span> <span class="hljs-variable">lambda</span> <span class="hljs-operator">=</span> (list, n) -&gt; &#123;<br>            <span class="hljs-type">int</span> <span class="hljs-variable">left</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>, right = list.size(), mid;<br><br>            <span class="hljs-keyword">while</span> (left &lt;= right) &#123;<br>                mid = (left + right) / <span class="hljs-number">2</span>;<br>                <span class="hljs-keyword">if</span> (mid &gt;= list.size()) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>                <span class="hljs-keyword">if</span> (list.get(mid) == n) &#123;<br>                    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>                &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (n &lt; list.get(mid)) &#123;<br>                    right = mid - <span class="hljs-number">1</span>;<br>                &#125; <span class="hljs-keyword">else</span> &#123;<br>                    left = mid + <span class="hljs-number">1</span>;<br>                &#125;<br>            &#125;<br><br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>        &#125;;<br><br>        ArrayList&lt;Integer&gt; list = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();<br>        list.add(<span class="hljs-number">1</span>);<br>        list.add(<span class="hljs-number">2</span>);<br>        list.add(<span class="hljs-number">2</span>);<br>        list.add(<span class="hljs-number">3</span>);<br>        list.add(<span class="hljs-number">3</span>);<br>        list.add(<span class="hljs-number">3</span>);<br>        list.add(<span class="hljs-number">3</span>);<br>        list.add(<span class="hljs-number">5</span>);<br>        System.out.println(lambda.find(list, <span class="hljs-number">3</span>) ? <span class="hljs-string">&quot;Found&quot;</span> : <span class="hljs-string">&quot;Not Found&quot;</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><div class="tag-plugin quot"><p class="content" type="text"><span class="empty"></span><span class="text">GL & HF!</span><span class="empty"></span></p></div>]]></content>
    
  </entry>
  
  <entry>
    <title>Java 中的泛型</title>
    <link href="https://alan.org.cn/posts/java%E6%B3%9B%E5%9E%8B.html"/>
    <id>https://alan.org.cn/posts/java%E6%B3%9B%E5%9E%8B.html</id>
    <published>2024-04-02T07:06:43.000Z</published>
    <updated>2026-06-04T04:10:49.248Z</updated>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/categories/JAVA/"/>
    
    
    <category term="Java" scheme="https://alan.org.cn/tags/Java/"/>
    
    <category term="数据结构" scheme="https://alan.org.cn/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
    <category term="面向对象编程" scheme="https://alan.org.cn/tags/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B/"/>
    
    <category term="泛型" scheme="https://alan.org.cn/tags/%E6%B3%9B%E5%9E%8B/"/>
    
    <category term="Generics" scheme="https://alan.org.cn/tags/Generics/"/>
    
    
      
      
    <summary type="html">&lt;p&gt;在前两章中，我们提到了 Java 中的栈、队列和链表的实现，而一个问题随之而来 - 在我们的实现中，每个数据结构都只能存储一种类型的数据。但是，如果我们有多种数据都需要存储在同一个数据结构中，该怎么办呢？为每个类型都实现一遍每个数据结构吗？这显然是不现实的。这就是 Java</summary>
      
    
    
    <content type="html"><![CDATA[<p>在前两章中，我们提到了 Java 中的栈、队列和链表的实现，而一个问题随之而来 - 在我们的实现中，每个数据结构都只能存储一种类型的数据。但是，如果我们有多种数据都需要存储在同一个数据结构中，该怎么办呢？为每个类型都实现一遍每个数据结构吗？这显然是不现实的。这就是 Java 中泛型的作用。</p><p>泛型（Generics）是 Java 中的一个重要特性，自 Java 5.0 版本开始引入。它允许我们在定义类、接口和方法时使用类型参数。这样，我们就可以在使用这些类、接口和方法时指定具体的类型，从而实现代码的复用。</p><p>一个最简单的泛型类的定义如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Box</span>&lt;T&gt; &#123;<br>    <span class="hljs-keyword">private</span> T data;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Box</span><span class="hljs-params">(T data)</span> &#123;<br>        <span class="hljs-built_in">this</span>.data = data;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setData</span><span class="hljs-params">(T data)</span> &#123;<br>        <span class="hljs-built_in">this</span>.data = data;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> T <span class="hljs-title function_">getData</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> data;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在这个例子中，<code>Box</code> 是一个泛型类，<code>T</code> 是类型参数。在创建 <code>Box</code> 对象时，我们可以指定 <code>T</code> 的具体类型，而这个被指定的类型将会成为 <code>Box</code> 类中的使用 <code>T</code> 作为类型的地方的具体类型。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">Box&lt;Integer&gt; intBox = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Box</span>&lt;&gt;(<span class="hljs-number">42</span>);<br>intBox.setData(<span class="hljs-number">43</span>);<br>System.out.println(intBox.getData()); <span class="hljs-comment">// 43</span><br><br>Box&lt;String&gt; strBox = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Box</span>&lt;&gt;(<span class="hljs-string">&quot;Hello, World!&quot;</span>);<br>strBox.setData(<span class="hljs-string">&quot;Hello, Java!&quot;</span>);<br>System.out.println(strBox.getData()); <span class="hljs-comment">// Hello, Java!</span><br></code></pre></td></tr></table></figure><p>在上述代码中，<code>intBox</code> 中 <code>data</code> 的类型是 <code>Integer</code>，而 <code>strBox</code> 中 <code>data</code> 的类型是 <code>String</code>。</p><div class="tag-plugin colorful note" color="orange"><div class="title">注意！</div><div class="body"><p>泛型类中的类型参数不能是基本数据类型，只能是“类”类型。也就是说，我们不能创建一个 <code>Box&lt;int&gt;</code> 对象，而应该使用 <code>Box&lt;Integer&gt;</code>。但是，我们可以随便使用其他类作为类型参数，例如 <code>Box&lt;Toy&gt;</code>。</p></div></div><p>在 Java 中，泛型在集合（Collection）类中得到了广泛的应用。例如，<code>ArrayList</code>、<code>LinkedList</code>、<code>HashMap</code> 等都是泛型类。在使用这些类时，我们可以指定集合中元素的类型，从而避免了类型转换的麻烦。同时，泛型的使用提高了代码的复用性以及代码的安全性（由于类型已经在编写时确定，编译器可以在编译时检查类型是否匹配）。</p><h2 id="泛型的类型"><a href="#泛型的类型" class="headerlink" title="泛型的类型"></a>泛型的类型</h2><p>Java 中，泛型可以在多种地方使用，包括类、接口、方法等，包括：</p><ul><li><strong>泛型类</strong></li><li><strong>泛型接口</strong></li><li><strong>泛型方法</strong></li><li><strong>泛型通配符</strong></li></ul><h3 id="泛型类"><a href="#泛型类" class="headerlink" title="泛型类"></a>泛型类</h3><p>泛型类是一个拥有一个或者多个类型参数的类，这个类型参数可以在类中的成员变量、方法参数、方法返回值等地方使用。</p><p>要定义泛型类，需要在类名后面加上尖括号（<code>&lt;&gt;</code>），然后在尖括号中定义类型参数（通常使用大写字母例如 <code>T</code>、<code>K</code>、<code>V</code> 等）。</p><p>上述的 <code>Box</code> 类就是一个泛型类的例子。我们可以定义一个泛型类，然后在创建对象时指定具体的类型。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Box</span>&lt;T&gt; &#123;<br>    <span class="hljs-keyword">private</span> T data; <span class="hljs-comment">// 类型参数被作为成员变量的类型使用</span><br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Box</span><span class="hljs-params">(T data)</span> &#123; <span class="hljs-comment">// 类型参数被作为构造函数的参数的类型使用</span><br>        <span class="hljs-built_in">this</span>.data = data;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setData</span><span class="hljs-params">(T data)</span> &#123; <span class="hljs-comment">// 类型参数被作为类中一个方法的参数的类型使用</span><br>        <span class="hljs-built_in">this</span>.data = data;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> T <span class="hljs-title function_">getData</span><span class="hljs-params">()</span> &#123; <span class="hljs-comment">// 类型参数被作为类中一个方法的返回值的类型使用</span><br>        <span class="hljs-keyword">return</span> data;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>一个类可以有多个类型参数，只需要在类名后面的尖括号中定义多个类型参数即可。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Pair</span>&lt;K, V&gt; &#123;<br>    <span class="hljs-keyword">private</span> K key;<br>    <span class="hljs-keyword">private</span> V value;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Pair</span><span class="hljs-params">(K key, V value)</span> &#123;<br>        <span class="hljs-built_in">this</span>.key = key;<br>        <span class="hljs-built_in">this</span>.value = value;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> K <span class="hljs-title function_">getKey</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> key;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> V <span class="hljs-title function_">getValue</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> value;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在创建对象时，我们需要指定对应的类型参数。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java">Box&lt;Integer&gt; intBox = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Box</span>&lt;&gt;(<span class="hljs-number">42</span>);<br>Pair&lt;String, Integer&gt; pair = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Pair</span>&lt;&gt;(<span class="hljs-string">&quot;age&quot;</span>, <span class="hljs-number">18</span>);<br></code></pre></td></tr></table></figure><h3 id="泛型接口"><a href="#泛型接口" class="headerlink" title="泛型接口"></a>泛型接口</h3><p>泛型接口和泛型类类似，只是泛型接口定义的是一个接口，而不是一个类。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">List</span>&lt;T&gt; &#123;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">add</span><span class="hljs-params">(T element)</span>;<br>    T <span class="hljs-title function_">get</span><span class="hljs-params">(<span class="hljs-type">int</span> index)</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>例如，上述代码就定义了一个列表接口，这个列表接口可以存储任意类型的元素。这个接口有两个方法，一个是 <code>add</code> 方法，用于向列表中添加元素，使用类型参数 <code>T</code> 作为参数类型；另一个是 <code>get</code> 方法，用于获取列表中指定位置的元素，返回类型为 <code>T</code>。</p><h3 id="泛型方法"><a href="#泛型方法" class="headerlink" title="泛型方法"></a>泛型方法</h3><p>泛型方法是一个拥有一个或者多个类型参数的方法，这个类型参数可以在方法的参数、返回值等地方使用。</p><p>要定义泛型方法，需要在方法的返回值前面加上尖括号（<code>&lt;&gt;</code>），然后在尖括号中定义类型参数。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> &lt;T&gt; T <span class="hljs-title function_">getFirstElement</span><span class="hljs-params">(T[] array)</span> &#123;<br>    <span class="hljs-keyword">return</span> array[<span class="hljs-number">0</span>];<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述示例中，这个方法有一个类型参数 <code>T</code>，这个类型参数可以在方法的参数、返回值等地方使用。例如，这个方法接受一个由类型 <code>T</code> 组成的数组，然后返回这个数组的第一个元素。</p><h3 id="泛型通配符"><a href="#泛型通配符" class="headerlink" title="泛型通配符"></a>泛型通配符</h3><p>有的时候，我们不关心具体的类型，那么我们可以使用泛型通配符 <code>?</code>。泛型通配符 <code>?</code> 表示任意类型，可以用在泛型类、泛型接口、泛型方法等地方。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">printList</span><span class="hljs-params">(List&lt;?&gt; list)</span> &#123;<br>    <span class="hljs-keyword">for</span> (Object element : list) &#123;<br>        System.out.println(element);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述示例中，<code>printList</code> 方法接受一个 <code>List</code> 类型的参数，这个 <code>List</code> 类型的元素类型是未知的，而且我们也不关心它的具体类型。因此，我们使用了泛型通配符 <code>?</code>。这时，这个 <code>list</code> 参数的类型可以是任何元素组成的列表。</p><p>有的时候我们希望泛型参数是某个类型的子类，那么我们可以使用泛型通配符 <code>extends</code>。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">printList</span><span class="hljs-params">(List&lt;? extends Number&gt; list)</span> &#123;<br>    <span class="hljs-keyword">for</span> (Number element : list) &#123;<br>        System.out.println(element);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>此时，<code>list</code> 参数这个列表的元素的类型就只能是 <code>Number</code> 类型或者 <code>Number</code> 的子类类型了。</p><p>同时，我们也可以使用泛型通配符 <code>super</code>，表示泛型参数是某个类型的父类。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addNumbers</span><span class="hljs-params">(List&lt;? <span class="hljs-built_in">super</span> Integer&gt; list)</span> &#123;<br>    list.add(<span class="hljs-number">42</span>);<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述示例中，<code>addNumbers</code> 方法接受一个 <code>List</code> 类型的参数，这个 <code>List</code> 类型的元素类型只能是 <code>Integer</code> 或者 <code>Integer</code> 的父类类型。</p><div class="tag-plugin colorful note" color="red"><div class="title">注意！</div><div class="body"><p>泛型通配符 <code>?</code> 只能用于声明泛型类型，不能用于创建对象。例如，<code>List&lt;?&gt; list = new ArrayList&lt;&gt;()</code> 是错误的。</p></div></div><h2 id="例题"><a href="#例题" class="headerlink" title="例题"></a>例题</h2><p>Coming soon…</p><h2 id="挑战"><a href="#挑战" class="headerlink" title="挑战"></a>挑战</h2><p>使用泛型实现 <a href="/java-ds-oop/stack-and-queue">数据结构：栈与队列</a> 中的栈和队列以及 <a href="/java-ds-oop/linked-list">数据结构：链表</a> 中的链表。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>泛型是 Java 中的一个重要特性，它允许我们在定义类、接口和方法时使用类型参数。泛型的使用提高了代码的复用性（可以不用为每个类型都实现一遍数据结构）和代码的安全性（编译器可以在编译时检查类型是否匹配）。</p><div class="tag-plugin quot"><p class="content" type="text"><span class="empty"></span><span class="text">GL & HF!</span><span class="empty"></span></p></div>]]></content>
    
  </entry>
  
  <entry>
    <title>异常处理 (Exception Handling) - 第二部分</title>
    <link href="https://alan.org.cn/posts/exception-handling-2.html"/>
    <id>https://alan.org.cn/posts/exception-handling-2.html</id>
    <published>2024-03-01T12:46:29.000Z</published>
    <updated>2026-06-04T04:10:49.248Z</updated>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/categories/JAVA/"/>
    
    
    <category term="Java" scheme="https://alan.org.cn/tags/Java/"/>
    
    <category term="异常处理" scheme="https://alan.org.cn/tags/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/"/>
    
    
    <summary type="html">&lt;p&gt;在上一篇文章中，我们简单的讲述了异常处理的基本概念以及 &lt;code&gt;try-catch&lt;/code&gt;, &lt;code&gt;throw&lt;/code&gt;, &lt;code&gt;throws&lt;/code&gt; 等关键机制。在本篇文章中，我们将继续讨论异常处理的两个高级特性：自定义异常类以及 &lt;code&gt;catch&lt;/code&gt; 多个异常。&lt;/p&gt;</summary>
    
    
    <content type="html"><![CDATA[<p>在上一篇文章中，我们简单的讲述了异常处理的基本概念以及 <code>try-catch</code>, <code>throw</code>, <code>throws</code> 等关键机制。在本篇文章中，我们将继续讨论异常处理的两个高级特性：自定义异常类以及 <code>catch</code> 多个异常。</p><span id="more"></span><h2 id="自定义异常类"><a href="#自定义异常类" class="headerlink" title="自定义异常类"></a>自定义异常类</h2><p>正如我们在第一部分中提到的，有些时候我们需要在 Java 不认为有异常情况的时候依照程序设计主动抛出异常。虽然我们有的时候是可以直接使用已有的异常类的，但是由于我们通常希望异常类本身的名字就能够为异常提供一定的描述，因此我们通常会自定义异常类。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">checkGrade</span><span class="hljs-params">(<span class="hljs-type">int</span> grade)</span> &#123;<br>        <span class="hljs-keyword">if</span> (grade &lt; <span class="hljs-number">60</span>) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArithmeticException</span>(<span class="hljs-string">&quot;This grade is below passing grade!&quot;</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">grade</span> <span class="hljs-operator">=</span> <span class="hljs-number">50</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            checkGrade(grade);<br>        &#125; <span class="hljs-keyword">catch</span> (ArithmeticException arithmeticException) &#123;<br>            System.out.println(arithmeticException);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>这是上次我们使用过的示例，我们可以看到，在检查成绩的时候，我们直接使用了 <code>ArithmeticException</code> 这个异常类。但是数学错误其实并非我们真正想要表达的意思，我们更希望能够使用一个更加贴切的异常类名字。因此，我们可以自定义一个异常类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">GradeException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Exception</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">GradeException</span><span class="hljs-params">(String message)</span> &#123;<br>        <span class="hljs-built_in">super</span>(message);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">checkGrade</span><span class="hljs-params">(<span class="hljs-type">int</span> grade)</span> <span class="hljs-keyword">throws</span> GradeException &#123;<br>        <span class="hljs-keyword">if</span> (grade &lt; <span class="hljs-number">60</span>) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">GradeException</span>(<span class="hljs-string">&quot;This grade is below passing grade!&quot;</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">grade</span> <span class="hljs-operator">=</span> <span class="hljs-number">50</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            checkGrade(grade);<br>        &#125; <span class="hljs-keyword">catch</span> (GradeException gradeException) &#123;<br>            System.out.println(gradeException);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>这样，<code>checkGrade</code> 函数就不再会抛出 <code>ArithmeticException</code>，而是抛出我们自定义的 <code>GradeException</code>。这样，我们的异常处理就变得更加清晰了。你如果运行这段代码，你会发现输出的异常信息中包含的异常名也变成了 <code>GradeException</code>。</p><p>对于自定义异常类，其实它们就是继承自 <code>Exception</code> 类或者 <code>RuntimeException</code> 类的类。一般我们会选择继承 <code>Exception</code> 类使得我们的自定义异常成为一个检查异常 (因为我们通常希望会在某处处理我们自定义的异常)。</p><p>一个基础的自定义异常长这样：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Exception</span> &#123;<br>&#125;<br></code></pre></td></tr></table></figure><p>抛出这个自定义异常只需要：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CustomException</span>();<br></code></pre></td></tr></table></figure><p>注意，该函数中没有任何的带参构造函数，所以此处隐性的使用了默认的无参构造函数。这样的错误在被打印出来的时候只会显示异常名字，而不会显示任何的异常信息。它的输出长这样：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell">CustomException<br><br>Process finished with exit code 0<br></code></pre></td></tr></table></figure><p>你会发现输出里只有异常类名，任何关于异常的细节都没被提供。如果我们希望在抛出异常的时候能够提供一些额外的信息，我们可以使用 <code>toString</code> 方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Exception</span> &#123;<br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">toString</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;错误信息&quot;</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>（<strong>不要这么做！！！不要这么做！！！不要这么做！！！</strong>）</p><p>虽然任何显示错误信息的时候其实都是在调用 <code>Exception</code> 类的 <code>toString</code> 方法，而且重写 <code>toString</code> 方法可以使代码正常工作，但是这样做是不标准的。你应该在上面的代码中发现了，<code>ArithmeticException</code> 之类的异常类可以接受一个 String 类型的参数。没错，异常类有数个带参构造函数，这里我们只讨论最重要的一个，带有参数 <code>String message</code> 参数的构造函数。正如参数名所示，这个参数就是用来传递异常信息的。使用这个构造函数在打印异常信息的时候会显示这个参数的值。因此，我们应该使用这个构造函数来传递异常信息：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Exception</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">CustomException</span><span class="hljs-params">(String message)</span> &#123;<br>        <span class="hljs-built_in">super</span>(message);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>然后在抛出异常的时候使用：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CustomException</span>(<span class="hljs-string">&quot;错误信息&quot;</span>);<br></code></pre></td></tr></table></figure><p>输出就会成为：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell">CustomException: 错误信息<br><br>Process finished with exit code 0<br></code></pre></td></tr></table></figure><p>如果你希望为自定义异常类添加一个默认的错误，你可以自定义无参构造函数，然后让它去调用父类 <code>Exception</code> 的带参构造函数：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Exception</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">CustomException</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">super</span>(<span class="hljs-string">&quot;默认错误信息&quot;</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>这样就算你在抛出异常的时候没有提供任何的参数，异常信息也会显示为 “默认错误信息”。</p><p>这个默认信息的示例代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Exception</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">CustomException</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">super</span>(<span class="hljs-string">&quot;默认错误信息&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">throwException</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> CustomException &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CustomException</span>();<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            throwException();<br>        &#125; <span class="hljs-keyword">catch</span> (CustomException customException) &#123;<br>            System.out.println(customException);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>输出为：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell">CustomException: 默认错误信息<br><br>Process finished with exit code 0<br></code></pre></td></tr></table></figure><h2 id="catch-多个异常"><a href="#catch-多个异常" class="headerlink" title="catch 多个异常"></a><code>catch</code> 多个异常</h2><p>有的时候我们可能希望一个 <code>catch</code> 可以捕获多个异常，然后对这些不同的异常作出相同的处理，这个时候我们只需要在 <code>catch</code> 的括号中使用 <code>|</code> 分隔不同的异常类名即可：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-type">int</span>[] arr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">5</span>];<br>            <span class="hljs-type">int</span> <span class="hljs-variable">element</span> <span class="hljs-operator">=</span> arr[<span class="hljs-number">5</span>];<br>            <span class="hljs-type">int</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span> / <span class="hljs-number">0</span>;<br>        &#125; <span class="hljs-keyword">catch</span> (ArrayIndexOutOfBoundsException | ArithmeticException e) &#123;<br>            System.out.println(e);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述的代码中，同一个 <code>catch</code> 在数组下标越界异常和除零异常发生的时候都会被执行，且对这两个异常的处理都是一样的。这样的写法可以使得代码更加简洁，而且仍然允许我们不去使用 <code>Exception</code> 类的父类来捕获所有的异常。</p>]]></content>
    
  </entry>
  
  <entry>
    <title>异常处理 (Exception Handling) - 第一部分</title>
    <link href="https://alan.org.cn/posts/exception-handling-1.html"/>
    <id>https://alan.org.cn/posts/exception-handling-1.html</id>
    <published>2024-02-27T09:02:40.000Z</published>
    <updated>2026-06-04T04:10:49.248Z</updated>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/categories/JAVA/"/>
    
    
    <category term="Java" scheme="https://alan.org.cn/tags/Java/"/>
    
    <category term="异常处理" scheme="https://alan.org.cn/tags/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/"/>
    
    
    <summary type="html">&lt;p&gt;在程序执行的过程中，可能会出现一些无法被预见到的错误 (例如：用户输入错误等) ，这些错误被称为异常。在这种情况下，如果我们不去处理这些问题，程序就会终止 (崩溃) 。但是，我们通常不希望程序在出现异常时直接终止，而是希望能够尝试处理这些问题 (例如：显示错误信息等) ，而 Java 提供了一些机制来实现这一点。&lt;/p&gt;</summary>
    
    
    <content type="html"><![CDATA[<p>在程序执行的过程中，可能会出现一些无法被预见到的错误 (例如：用户输入错误等) ，这些错误被称为异常。在这种情况下，如果我们不去处理这些问题，程序就会终止 (崩溃) 。但是，我们通常不希望程序在出现异常时直接终止，而是希望能够尝试处理这些问题 (例如：显示错误信息等) ，而 Java 提供了一些机制来实现这一点。</p><span id="more"></span><h2 id="异常的分类"><a href="#异常的分类" class="headerlink" title="异常的分类"></a>异常的分类</h2><p>在 Java 中，我们一般将异常分为两类：<strong>检查异常</strong> (Checked Exception) 和<strong>非检查异常</strong> (Unchecked Exception)。</p><p>检查异常，或者编译时异常 (compile-time exception)，例如 <code>IOException</code>，通常是由于外部因素 (例如：文件不存在) 引起的。这类异常会在编译时被检查。这并不意味着编译器会在编译时检查需要的文件是否存在，但是编译器会检查这类异常是否已经被处理 (例如被 <code>try-catch</code> 代码块捕获，或者在函数签名中被使用 <code>throws</code> 关键字声明)，如果没有任何已有的处理措施，JDK 会拒绝编译。</p><p>非检查异常，或者运行时异常 (runtime exception)，通常由代码中的逻辑错误或者用户的错误引起 (例如访问越界数组元素)。 JDK 不会对这类问题进行检查，如果对应的异常处理没有被提供，JDK 也不会拒绝编译，但是当这类问题在运行时出现时，程序会直接终止。</p><div class="tag-plugin colorful note" color="red"><div class="body"><p>有时 IDE 甚至会提示已经一些可以被检测到的运行时异常！ (而编译器不会去进行检测) 请充分利用 IDE 提供的功能</p></div></div><h2 id="异常处理-try-catch-语句"><a href="#异常处理-try-catch-语句" class="headerlink" title="异常处理 - try-catch 语句"></a>异常处理 - <code>try-catch</code> 语句</h2><p>常见的异常处理关键字：<code>try</code>、<code>catch</code>、<code>finally</code>、<code>throw</code>、<code>throws</code>。</p><p>在 Java 中，常见的运行时异常包括 <code>ArithmeticException</code>、<code>ArrayIndexOutOfBoundsException</code>、<code>NullPointerException</code>、<code>NumberFormatException</code> 等。这些异常都是 <code>RuntimeException</code> 的子类 (<code>RuntimeException</code> 是 <code>Exception</code> 的子类，而 <code>Exception</code> 是 <code>Throwable</code> 的子类) 。</p><p>在 Java 中，当一个语句或者函数&#x2F;方法出现异常时，会抛出 (throw) 一个异常对象 (一个继承自 <code>Throwable</code> 的对象) 。这个异常对象会被传递给调用者，直到被捕获 (catch) 或者导致程序终止。为了捕获异常，我们可以使用 <code>try-catch</code> 语句。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span>[] array = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">12</span>];<br>        array[<span class="hljs-number">16</span>] = <span class="hljs-number">1</span>;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，我们创建了一个长度为 12 的数组，然后尝试将第 16 个元素赋值为 1。这会导致 <code>ArrayIndexOutOfBoundsException</code> 异常。目前这个异常没有被处理，所以程序会直接终止。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span>[] array = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">12</span>];<br>        <span class="hljs-keyword">try</span> &#123;<br>            array[<span class="hljs-number">16</span>] = <span class="hljs-number">1</span>;<br>        &#125; <span class="hljs-keyword">catch</span> (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) &#123;<br>            System.out.println(arrayIndexOutOfBoundsException); <span class="hljs-comment">// 我们可以通过直接打印异常对象来查看异常信息</span><br>            System.out.println(<span class="hljs-string">&quot;Trying to access out-of-bound array element.&quot;</span>); <span class="hljs-comment">// 也可以输出自定义的错误信息</span><br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，<code>ArrayIndexOutOfBoundsException</code> 异常被捕获并处理了。我们通过将可能出现的异常代码放在 <code>try</code> 代码块中，然后在 <code>catch</code> 代码块中处理异常。<code>catch</code> 代码块中的参数是异常对象。如果 <code>try</code> 代码块中的代码抛出了对应的异常，那么有对应异常对象的 <code>catch</code> 代码块中的代码就会被执行。</p><div class="tag-plugin colorful note" color="red"><div class="body"><p>我们可以使用 <code>catch (Exception exception)</code> 来捕获所有可能的异常，但是并不建议这么做<br/>有些 IDE 可以通过查看函数文档看到该函数会抛出哪些异常，你可以通过该功能查看到你可能需要处理哪些异常</p></div></div><h3 id="一些其他的情况"><a href="#一些其他的情况" class="headerlink" title="一些其他的情况"></a>一些其他的情况</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span>[] array = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">12</span>];<br>        <span class="hljs-keyword">try</span> &#123;<br>            array[<span class="hljs-number">16</span>] = <span class="hljs-number">1</span>;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">str</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>            System.out.println(str.length());<br>        &#125; <span class="hljs-keyword">catch</span> (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) &#123;<br>            System.out.println(arrayIndexOutOfBoundsException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access out-of-bound array element.&quot;</span>);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，我们尝试在访问越界数组元素后检查一个空字符串的长度，但这个代码<strong>不会</strong>导致 <code>NullPointerException</code> 异常。这是因为在尝试访问空字符串的长度之前，错误的数组访问已经导致了异常，所以程序不会继续执行后面的代码而会直接跳转到 <code>catch</code> 代码块。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span>[] array = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">12</span>];<br>        <span class="hljs-keyword">try</span> &#123;<br>            array[<span class="hljs-number">6</span>] = <span class="hljs-number">1</span>;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">str</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>            System.out.println(str.length());<br>        &#125; <span class="hljs-keyword">catch</span> (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) &#123;<br>            System.out.println(arrayIndexOutOfBoundsException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access out-of-bound array element.&quot;</span>);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，数组访问不再会导致 <code>ArrayIndexOutOfBoundsException</code> 异常，所以程序会继续执行后面的代码。这会导致 <code>NullPointerException</code> 异常，但是我们没有对这个异常进行处理，所以程序会直接终止。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span>[] array = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">12</span>];<br>        <span class="hljs-keyword">try</span> &#123;<br>            array[<span class="hljs-number">6</span>] = <span class="hljs-number">1</span>;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">str</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>            System.out.println(str.length());<br>        &#125; <span class="hljs-keyword">catch</span> (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) &#123;<br>            System.out.println(arrayIndexOutOfBoundsException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access out-of-bound array element.&quot;</span>);<br>        &#125; <span class="hljs-keyword">catch</span> (NullPointerException nullPointerException) &#123;<br>            System.out.println(nullPointerException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access a null string.&quot;</span>);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>但是，我们可以通过再添加一个 <code>catch</code> 代码块来处理 <code>NullPointerException</code> 异常，此时程序不会再崩溃。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span>[] array = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">12</span>];<br>        <span class="hljs-keyword">try</span> &#123;<br>            array[<span class="hljs-number">16</span>] = <span class="hljs-number">1</span>;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">str</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>            System.out.println(str.length());<br>        &#125; <span class="hljs-keyword">catch</span> (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) &#123;<br>            System.out.println(arrayIndexOutOfBoundsException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access out-of-bound array element.&quot;</span>);<br>        &#125; <span class="hljs-keyword">catch</span> (NullPointerException nullPointerException) &#123;<br>            System.out.println(nullPointerException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access a null string.&quot;</span>);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，就算两个异常都可以被处理，但是由于在访问数组时已经抛出了异常，所以程序不会继续执行后面的代码，所以本程序只会输出 <code>ArrayIndexOutOfBoundsException</code> 异常信息。</p><p>如果你希望分开输出两个异常信息，可以将两个可能出现异常的代码分开放在两个 <code>try</code> 代码块中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span>[] array = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">12</span>];<br>        <span class="hljs-keyword">try</span> &#123;<br>            array[<span class="hljs-number">16</span>] = <span class="hljs-number">1</span>;<br>        &#125; <span class="hljs-keyword">catch</span> (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) &#123;<br>            System.out.println(arrayIndexOutOfBoundsException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access out-of-bound array element.&quot;</span>);<br>        &#125;<br>        <br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-type">String</span> <span class="hljs-variable">str</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br>            System.out.println(str.length());<br>        &#125; <span class="hljs-keyword">catch</span> (NullPointerException nullPointerException) &#123;<br>            System.out.println(nullPointerException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access a null string.&quot;</span>);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>此时，程序会同时输出 <code>ArrayIndexOutOfBoundsException</code> 和 <code>NullPointerException</code> 异常信息。</p><h2 id="throw-和-throws"><a href="#throw-和-throws" class="headerlink" title="throw 和 throws"></a>throw 和 throws</h2><p>有的时候，我们希望在我们的逻辑代码中主动抛出一个异常 (因为 Java 可能不认为此处有错误，但是我们根据实际程序的需求认为某种情况应该是错误的) 。这时，我们可以使用 <code>throw</code> 关键字。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">checkGrade</span><span class="hljs-params">(<span class="hljs-type">int</span> grade)</span> &#123;<br>        <span class="hljs-keyword">if</span> (grade &lt; <span class="hljs-number">60</span>) &#123;<br>            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArithmeticException</span>(<span class="hljs-string">&quot;This grade is below passing grade!&quot;</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">int</span> <span class="hljs-variable">grade</span> <span class="hljs-operator">=</span> <span class="hljs-number">50</span>;<br>        <span class="hljs-keyword">try</span> &#123;<br>            checkGrade(grade);<br>        &#125; <span class="hljs-keyword">catch</span> (ArithmeticException arithmeticException) &#123;<br>            System.out.println(arithmeticException);<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，Java 并不认为 <code>grade</code> 不可以小于 60，但是，假如本程序的使用场景是检查学生成绩，那么 60 以下的成绩就是错误的。所以我们可以使用 <code>throw</code> 关键字来主动抛出一个 <code>ArithmeticException</code> 异常。此时如果我们不对这个异常进行处理，程序会仍然会崩溃。</p><p>异常抛出语法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ExceptionType</span>(<span class="hljs-string">&quot;Exception Message&quot;</span>);<br></code></pre></td></tr></table></figure><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>实际上，在这种情况下，我们可能会创建一个自定义异常 (例如 <code>FailedGradeExpection</code>，我们希望异常类名与异常信息都可以对情况进行简单描述) ，这点之后再讲</p></div></div><p>异常可以被向调用者传递，例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">method1</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;method1&quot;</span>);<br>        <span class="hljs-keyword">try</span> &#123;<br>            method2();<br>        &#125; <span class="hljs-keyword">catch</span> (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) &#123;<br>            System.out.println(arrayIndexOutOfBoundsException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access out-of-bound array element.&quot;</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">method2</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;method2&quot;</span>);<br>        method3();<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">method3</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;method3&quot;</span>);<br>        exceptionThrower();<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">exceptionThrower</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-type">int</span>[] array = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">12</span>];<br>        array[<span class="hljs-number">16</span>] = <span class="hljs-number">1</span>;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        method1();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，<code>main</code> 函数通过另外数个函数执行了可能会抛出异常的函数 <code>exceptionThrower</code>，如果我们不对此异常进行处理，那么程序会直接崩溃。但是，如果我们需要进行处理，处理逻辑可以被放置在任何调用 <code>exceptionThrower</code> 的函数中，例如 <code>method1</code>。</p><p>同时，由于异常可以被向上传递，有的时候异常处理逻辑可能需要由其他使用该函数的人来进行处理，而我们可以使用 <code>throws</code> 关键字来声明一个函数可能会抛出的异常。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">method1</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;method1&quot;</span>);<br>        <span class="hljs-keyword">try</span> &#123;<br>            method2();<br>        &#125; <span class="hljs-keyword">catch</span> (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) &#123;<br>            System.out.println(arrayIndexOutOfBoundsException);<br>            System.out.println(<span class="hljs-string">&quot;Trying to access out-of-bound array element.&quot;</span>);<br>        &#125; <span class="hljs-keyword">finally</span> &#123;<br>            System.out.println(<span class="hljs-string">&quot;Finally block.&quot;</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">method2</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;method2&quot;</span>);<br>        method3();<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">method3</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;method3&quot;</span>);<br>        exceptionThrower();<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">exceptionThrower</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> ArrayIndexOutOfBoundsException &#123;<br>        <span class="hljs-type">int</span>[] array = <span class="hljs-keyword">new</span> <span class="hljs-title class_">int</span>[<span class="hljs-number">12</span>];<br>        array[<span class="hljs-number">16</span>] = <span class="hljs-number">1</span>;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        method1();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，我们使用 <code>throws</code> 关键字来声明 <code>exceptionThrower</code> 可能会抛出 <code>ArrayIndexOutOfBoundsException</code> 异常。这样，函数文档中就会显示出该函数可能会抛出的异常，这样使用该函数的开发者就可以针对 <code>ArrayIndexOutOfBoundsException</code> 异常进行处理。</p><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>实际上，在上述情况下 (如果我们认为 <code>method1</code> 是另一位开发者写的函数，而 <code>method2</code> 和 <code>method3</code> 都是我们写的函数)，我们一般会将 <code>throws ArrayIndexOutOfBoundsException</code> 声明到 <code>method2</code> (也就是最上层的函数) 中，否则 <code>method2</code> 的使用者是看不到异常声明的</p></div></div><p>对于检查异常，我们<strong>必须</strong>在可能会抛出异常的函数以及所有会调用该函数但没有进行任何处理的函数的签名中使用 <code>throws</code> 声明该函数可能抛出异常。</p><p>同时，上面代码中出现了一个新的关键字：<code>finally</code>。<code>finally</code> 代码块中的代码会在 <code>try-catch</code> 代码块中的代码执行完毕后执行，无论是否出现异常。实际上，就算 <code>try</code> 代码块中的代码出现了异常，<code>catch</code> 代码块并未处理被抛出的异常，<code>finally</code> 代码块中的代码也会被执行，然后异常会继续向上传递（或者导致程序终止）。</p><h2 id="例题"><a href="#例题" class="headerlink" title="例题"></a>例题</h2><h3 id="例题-1"><a href="#例题-1" class="headerlink" title="例题 1"></a>例题 1</h3><p>要求用户输入两个整数 a 与 b，然后输出 <code>a/b</code>。使用异常处理机制，如果 b 为 0，输出一个消息要求用户重新输入两个整数。</p><p>除非用户输入两个可以被计算的数值，否则程序会一直要求用户重新输入。</p><details class="tag-plugin colorful folding" ><summary><p>解题方法</p></summary><div class="body"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.Scanner;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Scanner</span> <span class="hljs-variable">scanner</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Scanner</span>(System.in);<br><br>        System.out.println(<span class="hljs-string">&quot;a/b calculator&quot;</span>);<br><br>        <span class="hljs-type">boolean</span> <span class="hljs-variable">running</span> <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;<br><br>        <span class="hljs-keyword">while</span> (running) &#123;<br>            <span class="hljs-type">int</span> a, b;<br>            <span class="hljs-keyword">try</span> &#123;<br>                System.out.print(<span class="hljs-string">&quot;Enter number &#x27;a&#x27;: &quot;</span>);<br>                a = scanner.nextInt();<br>                System.out.print(<span class="hljs-string">&quot;Enter number &#x27;b&#x27;: &quot;</span>);<br>                b = scanner.nextInt();<br><br>                <span class="hljs-type">int</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> a / b;<br><br>                System.out.printf(<span class="hljs-string">&quot;%d / %d = %d&quot;</span>, a, b, result);<br><br>                running = <span class="hljs-literal">false</span>;<br>            &#125; <span class="hljs-keyword">catch</span> (ArithmeticException arithmeticException) &#123;<br>                System.out.println(arithmeticException);<br>                System.out.println(<span class="hljs-string">&quot;Please try with two other numbers.&quot;</span>);<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></div></details><h3 id="例题-2"><a href="#例题-2" class="headerlink" title="例题 2"></a>例题 2</h3><p>输入 10 个内容为整数的字符串，将它们存储在一个数组中。将每个字符串转换为整数并打印，如果字符串不能转换为整数，打印一个错误信息然后继续输出剩下的字符串 (中的整数) 。</p><details class="tag-plugin colorful folding" ><summary><p>解题方法</p></summary><div class="body"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.Scanner;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Scanner</span> <span class="hljs-variable">scanner</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Scanner</span>(System.in);<br><br>        String[] strings = <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>[<span class="hljs-number">10</span>];<br><br>        System.out.println(<span class="hljs-string">&quot;Enter 10 strings:&quot;</span>);<br><br>        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; strings.length; i++) &#123;<br>            System.out.printf(<span class="hljs-string">&quot;Enter string #%d: &quot;</span>, i + <span class="hljs-number">1</span>);<br>            strings[i] = scanner.nextLine();<br>        &#125;<br><br>        <span class="hljs-keyword">for</span> (String string : strings) &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                System.out.printf(<span class="hljs-string">&quot;Number: %d\n&quot;</span>, Integer.parseInt(string));<br>            &#125; <span class="hljs-keyword">catch</span> (NumberFormatException numberFormatException) &#123;<br>                System.out.printf(<span class="hljs-string">&quot;Cannot convert %s to number, continuing...\n&quot;</span>, string);<br>            &#125;<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></div></details>]]></content>
    
  </entry>
  
  <entry>
    <title>Java 多态 (polymorphism) - 继承与抽象类/接口</title>
    <link href="https://alan.org.cn/posts/%E5%A4%9A%E6%80%81.html"/>
    <id>https://alan.org.cn/posts/%E5%A4%9A%E6%80%81.html</id>
    <published>2024-02-22T14:20:28.000Z</published>
    <updated>2026-06-04T04:10:49.248Z</updated>
    
    
    <category term="JAVA" scheme="https://alan.org.cn/categories/JAVA/"/>
    
    
    <category term="Java" scheme="https://alan.org.cn/tags/Java/"/>
    
    <category term="数据结构" scheme="https://alan.org.cn/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
    <category term="面向对象编程" scheme="https://alan.org.cn/tags/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B/"/>
    
    <category term="继承" scheme="https://alan.org.cn/tags/%E7%BB%A7%E6%89%BF/"/>
    
    <category term="接口" scheme="https://alan.org.cn/tags/%E6%8E%A5%E5%8F%A3/"/>
    
    <category term="多态" scheme="https://alan.org.cn/tags/%E5%A4%9A%E6%80%81/"/>
    
    
    <summary type="html">&lt;p&gt;本文将简单介绍 Java 中的多态 (polymorphism) 特性，包括继承、抽象类和接口。&lt;/p&gt;</summary>
    
    
    <content type="html"><![CDATA[<p>本文将简单介绍 Java 中的多态 (polymorphism) 特性，包括继承、抽象类和接口。</p><span id="more"></span><h2 id="多态-Polymorphism"><a href="#多态-Polymorphism" class="headerlink" title="多态 (Polymorphism)"></a>多态 (Polymorphism)</h2><blockquote><p>在编程语言和类型论中，多态（英语：polymorphism）指为不同数据类型的实体提供统一的接口，或使用一个单一的符号来表示多个不同的类型。</p><p>多态的最常见主要类别有：</p><p>特设多态：为个体的特定类型的任意集合定义一个共同接口。<br>参数多态：指定一个或多个类型不靠名字而是靠可以标识任何类型的抽象符号。<br>子类型（也叫做子类型多态或包含多态）：一个名字指称很多不同的类的实例，这些类有某个共同的超类。</p><p><sub>维基百科编者. c2024. 多态 (计算机科学). 来源: 维基百科, 自由的百科全书. [Internet]: 维基百科, 自由的百科全书; [cited 2024 February 10]. Available from: <a href="https://zh.wikipedia.org/w/index.php?title=%E5%A4%9A%E6%80%81_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)&oldid=80877272">https://zh.wikipedia.org/w/index.php?title=%E5%A4%9A%E6%80%81_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)&amp;oldid=80877272</a></sub></p></blockquote><p>简单来讲，多态允许我们在程序的不同部分使用一样的符号来表述不同的东西（例如，具有同样名称不同实现的方法）。</p><p>多态一般来讲有两种形式，动态多态 (dynamic polymorphism) 和静态多态 (static polymorphism)。动态多态是指在运行时根据对象的实际类型来调用方法，而静态多态是指在编译时根据对象的引用类型来调用方法。</p><h2 id="函数重载-Function-Overloading"><a href="#函数重载-Function-Overloading" class="headerlink" title="函数重载 (Function Overloading)"></a>函数重载 (Function Overloading)</h2><p>我们先来看一下以前见到过的一种语言特性：函数重载。这是一种静态多态，允许我们在同一个类中定义多个同名方法，只要它们的参数类型和&#x2F;或数量不同即可。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isEqual</span><span class="hljs-params">(<span class="hljs-type">int</span> a, <span class="hljs-type">int</span> b)</span> &#123;<br>        <span class="hljs-keyword">return</span> a == b;<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isEqual</span><span class="hljs-params">(<span class="hljs-type">double</span> a, <span class="hljs-type">double</span> b)</span> &#123;<br>        <span class="hljs-keyword">return</span> a == b;<br>    &#125;<br><br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isEqual</span><span class="hljs-params">(String a, String b)</span> &#123;<br>        <span class="hljs-keyword">return</span> a.equals(b);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-comment">/* 代码 */</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>上述代码展现了典型的函数重载，我们定义了三个同名方法 <code>isEqual</code>，分别接受两个 <code>int</code>、两个 <code>double</code> 和两个 <code>String</code> 类型的参数。这样，我们可以在不同的情况下使用同一个方法名来比较不同类型的数据。</p><h2 id="继承-Inheritance"><a href="#继承-Inheritance" class="headerlink" title="继承 (Inheritance)"></a>继承 (Inheritance)</h2><p>继承是面向对象编程中的一个重要特性，它允许我们定义一个子类，这个子类可以继承另一个类（父类）的属性和方法。这样，我们可以在不同但相关的类之间共享一些方法的实现。</p><p>注意，在 Java 中，一个子类只能继承一个父类。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-type">String</span> <span class="hljs-variable">name</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;PARENT&quot;</span>;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;SHOW&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Child</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-type">int</span> <span class="hljs-variable">justANumber</span> <span class="hljs-operator">=</span> <span class="hljs-number">42</span>;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">display</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;DISPLAY&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Child</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Child</span>();<br>        c.show();<br>        c.display();<br>        System.out.println(c.name);<br>        System.out.println(c.justANumber);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上述代码中，我们定义了一个 <code>Parent</code> 类，它有一个 <code>show</code> 方法。然后我们定义了一个 <code>Child</code> 类，它通过 <code>extends</code> 关键字继承了 <code>Parent</code> 类。注意，就算我们并未在 <code>Child</code> 类中定义 <code>show</code> 方法，我们仍然可以在 <code>Child</code> 类的对象上调用 <code>show</code> 方法，因为 <code>Child</code> 类继承了 <code>Parent</code> 类的 <code>show</code> 方法。属性 <code>name</code> 同理，就算我们并未在 <code>Child</code> 类中定义 <code>name</code> 属性，我们仍然可以在 <code>Child</code> 类的对象上访问 <code>name</code> 属性，因为 <code>Child</code> 类继承了 <code>Parent</code> 类的 <code>name</code> 属性。</p><p>同时，我们也仍然可以为 <code>Child</code> 类定义自己的方法或者属性，例如 <code>display</code> 方法和 <code>justANumber</code> 属性。</p><p>但是，如果子类需要一个与父类有差别的方法，我们就需要进行方法重写 (method overriding)。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;SHOW PARENT&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Child</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;SHOW CHILD&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Child</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Child</span>();<br>        c.show();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上面的代码中，最终的输出是 <code>SHOW CHILD</code>。这是因为 <code>Child</code> 类重写了 <code>Parent</code> 类的 <code>show</code> 方法。在调用 <code>c.show()</code> 时，子类单独的的 <code>show</code> 方法会被调用。</p><p>注意，在重写方法时，我们可以函数签名前加上 <code>@Override</code> 注解来明确表示我们是在重写父类的方法（该注解并非强制要求）。这样有两个好处：</p><ol><li>如果子类的方法签名与父类的方法签名不同，编译器会报错。</li><li>增加了代码的可读性，让人一眼就能看出这是一个重写方法。</li></ol><p>对于子类与父类的构造器，如果子类没有显式地调用父类的构造器，那么会默认调用父类的无参构造器。如果父类没有无参构造器，那么子类必须显式地调用父类的构造器。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;SHOW PARENT&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Child</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-type">int</span> age;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Child</span><span class="hljs-params">(<span class="hljs-type">int</span> age)</span> &#123;<br>        <span class="hljs-built_in">super</span>(); <span class="hljs-comment">// 调用父类的无参构造器，这一行可以省略</span><br>        <span class="hljs-built_in">this</span>.age = age;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;SHOW CHILD&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Child</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Child</span>(<span class="hljs-number">10</span>);<br>        c.show();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Parent</span> &#123;<br>    String name;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Parent</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">this</span>.name = <span class="hljs-string">&quot;PARENT&quot;</span>;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;SHOW PARENT&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Child</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-type">int</span> age;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Child</span><span class="hljs-params">(String name, <span class="hljs-type">int</span> age)</span> &#123;<br>        <span class="hljs-built_in">super</span>(name); <span class="hljs-comment">// 父类没有无参构造器，所以必须显式调用父类的构造器，而且该调用必须放在子类构造器的第一行</span><br>        <span class="hljs-built_in">this</span>.age = age;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;SHOW CHILD&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Child</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Child</span>(<span class="hljs-string">&quot;Austin&quot;</span>, <span class="hljs-number">10</span>);<br>        c.show();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>正如我们可以使用 <code>this</code> 关键字来明确的引用当前对象的属性和方法，我们也可以使用 <code>super</code> 关键字来引用父类的属性和方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-type">int</span> age;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Parent</span><span class="hljs-params">(<span class="hljs-type">int</span> age)</span> &#123;<br>        <span class="hljs-built_in">this</span>.age = age;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;PARENT SHOW&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Child</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-type">int</span> age;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Child</span><span class="hljs-params">(<span class="hljs-type">int</span> age)</span> &#123;<br>        <span class="hljs-built_in">super</span>(age + <span class="hljs-number">25</span>);<br>        <span class="hljs-built_in">this</span>.age = age;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;Parent age: &quot;</span> + <span class="hljs-built_in">super</span>.age);<br>        System.out.println(<span class="hljs-string">&quot;Child age: &quot;</span> + age);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">display</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">super</span>.show();<br>        show();<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Child</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Child</span>(<span class="hljs-number">10</span>);<br>        c.display();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上面的代码中，最终的输出是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs plain">PARENT SHOW<br>Parent age: 35<br>Child age: 10<br></code></pre></td></tr></table></figure><p>因为 <code>display</code> 方法首先调用了父类的 <code>show</code> 方法，显示了 “PARENT SHOW”，然后调用了子类的 <code>show</code> 方法，显示了父类和子类的 <code>age</code> 属性。</p><p>你甚至可以在子类的重写方法中调用父类中被重写的方法。这在需要在子类中扩展父类方法的功能时非常有用。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Cube</span> &#123;<br>    <span class="hljs-type">int</span> length;<br>    <span class="hljs-type">int</span> breadth;<br>    <span class="hljs-type">int</span> height;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Cube</span><span class="hljs-params">(<span class="hljs-type">int</span> length, <span class="hljs-type">int</span> breadth, <span class="hljs-type">int</span> height)</span> &#123;<br>        <span class="hljs-built_in">this</span>.length = length;<br>        <span class="hljs-built_in">this</span>.breadth = breadth;<br>        <span class="hljs-built_in">this</span>.height = height;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getInfo</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;Length: &quot;</span> + length + <span class="hljs-string">&quot;, Breadth: &quot;</span> + breadth + <span class="hljs-string">&quot;, Height: &quot;</span> + height;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Pool</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Cube</span> &#123;<br>    String location;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Pool</span><span class="hljs-params">(<span class="hljs-type">int</span> length, <span class="hljs-type">int</span> breadth, <span class="hljs-type">int</span> height, String location)</span> &#123;<br>        <span class="hljs-built_in">super</span>(length, breadth, height);<br>        <span class="hljs-built_in">this</span>.location = location;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getInfo</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-built_in">super</span>.getInfo() + <span class="hljs-string">&quot;, Location: &quot;</span> + location;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Pool</span> <span class="hljs-variable">pool</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Pool</span>(<span class="hljs-number">10</span>, <span class="hljs-number">20</span>, <span class="hljs-number">30</span>, <span class="hljs-string">&quot;Backyard&quot;</span>);<br>        System.out.println(pool.getInfo());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在上面的代码中，我们在子类的 <code>getInfo</code> 方法中调用了父类的 <code>getInfo</code> 方法，利用了父类提供的信息，然后再加上了子类的信息。</p><p>最后，每个子类也都可以有自己的子类，这样就形成了一个继承的层级。而且就算每个子类只可以继承一个父类，但是一个父类可以有多个子类。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;SHOW PARENT&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ChildA</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">super</span>.show();<br>        System.out.println(<span class="hljs-string">&quot;SHOW CHILDA&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ChildB</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">super</span>.show();<br>        System.out.println(<span class="hljs-string">&quot;SHOW CHILDB&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">GrandChild</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">ChildA</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">super</span>.show();<br>        System.out.println(<span class="hljs-string">&quot;SHOW GRANDCHILD&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">GrandChild</span> <span class="hljs-variable">gc</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">GrandChild</span>();<br>        gc.show();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="例题"><a href="#例题" class="headerlink" title="例题"></a>例题</h3><ol><li><p>创建一个 <code>Animal</code> 类，它有一个 <code>getLegNumber</code> 方法，返回动物的腿的数量，默认为 2。然后分别创建一个 <code>Human</code>，一个 <code>Kangaroo</code>，一个 <code>Horse</code> 和一个 <code>Snake</code> 类，它们都继承自 <code>Animal</code> 类，但是它们的腿的数量分别为 2，2，4 和 0。最后，输出它们的腿的数量。</p> <details class="tag-plugin colorful folding" ><summary><p>解题方法</p></summary><div class="body"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Animal</span> &#123;<br>    String name;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Animal</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">this</span>.name = name;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getLegNumber</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">2</span>;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Human</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Animal</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Human</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">super</span>(name);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Kangaroo</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Animal</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Kangaroo</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">super</span>(name);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Horse</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Animal</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Horse</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">super</span>(name);<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getLegNumber</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">4</span>;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Snake</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Animal</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Snake</span><span class="hljs-params">(String name)</span> &#123;<br>        <span class="hljs-built_in">super</span>(name);<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getLegNumber</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Human</span> <span class="hljs-variable">human</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Human</span>(<span class="hljs-string">&quot;Human&quot;</span>);<br>        System.out.printf(<span class="hljs-string">&quot;%s has %d legs\n&quot;</span>, human.name, human.getLegNumber());<br><br>        <span class="hljs-type">Kangaroo</span> <span class="hljs-variable">kangaroo</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Kangaroo</span>(<span class="hljs-string">&quot;Kangaroo&quot;</span>);<br>        System.out.printf(<span class="hljs-string">&quot;%s has %d legs\n&quot;</span>, kangaroo.name, kangaroo.getLegNumber());<br><br>        <span class="hljs-type">Horse</span> <span class="hljs-variable">horse</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Horse</span>(<span class="hljs-string">&quot;Horse&quot;</span>);<br>        System.out.printf(<span class="hljs-string">&quot;%s has %d legs\n&quot;</span>, horse.name, horse.getLegNumber());<br><br>        <span class="hljs-type">Snake</span> <span class="hljs-variable">snake</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Snake</span>(<span class="hljs-string">&quot;Snake&quot;</span>);<br>        System.out.printf(<span class="hljs-string">&quot;%s has %d legs\n&quot;</span>, snake.name, snake.getLegNumber());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></div></details></li></ol><h3 id="冷知识-1"><a href="#冷知识-1" class="headerlink" title="冷知识 #1"></a>冷知识 #1</h3><p>你知道其实，就算你不写任何继承关系，所有的类也都是子类吗？</p><p>Java 中的所有类都继承自 <code>Object</code> 类。<code>Object</code> 类定义了一些通用的方法，例如 <code>toString</code>、<code>equals</code> 和 <code>hashCode</code> 等。这意味着，你可以在任何一个类的对象上调用 <code>toString</code> 方法，你每次在自己的类中实现一个 <code>toString</code> 方法时，你都是在重写 <code>Object</code> 类中的 <code>toString</code> 方法。</p><h3 id="冷知识-2"><a href="#冷知识-2" class="headerlink" title="冷知识 #2"></a>冷知识 #2</h3><p>你知道如果你声明一个类型为父类的对象，你实际上可以传入一个子类的对象吗？</p><p>这甚至能被用在数组中。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;SHOW PARENT&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ChildA</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">super</span>.show();<br>        System.out.println(<span class="hljs-string">&quot;SHOW CHILDA&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ChildB</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Parent</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">super</span>.show();<br>        System.out.println(<span class="hljs-string">&quot;SHOW CHILDB&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">GrandChild</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">ChildA</span> &#123;<br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-built_in">super</span>.show();<br>        System.out.println(<span class="hljs-string">&quot;SHOW GRANDCHILD&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Parent</span> <span class="hljs-variable">a</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ChildA</span>(); <span class="hljs-comment">// 这样是可以的</span><br><br>        Parent[] arr = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Parent</span>[<span class="hljs-number">4</span>]; <span class="hljs-comment">// 这样也是可以的</span><br>        arr[<span class="hljs-number">0</span>] = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Parent</span>();<br>        arr[<span class="hljs-number">1</span>] = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ChildA</span>();<br>        arr[<span class="hljs-number">2</span>] = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ChildB</span>();<br>        arr[<span class="hljs-number">3</span>] = <span class="hljs-keyword">new</span> <span class="hljs-title class_">GrandChild</span>();<br><br>        <span class="hljs-comment">// ChildA b = new Parent(); // 这样是不可以的</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="抽象类-Abstract-Class"><a href="#抽象类-Abstract-Class" class="headerlink" title="抽象类 (Abstract Class)"></a>抽象类 (Abstract Class)</h2><p>但是，有的时候我们并不希望父类提供一个默认的实现，而希望父类只给出一个对方法的定义 (抽象的含义就在此处)，然后要求子类去实现这个方法。</p><p>这种方式允许我们为一堆同类型但细节实现完全不相干的类定义一个共同的方法。</p><p>例如：定义一个类 <code>Shape</code>，而每个形状有一定有周长与面积，但是每个形状的周长与面积的计算方法都不一样。这时我们可以将 <code>Shape</code> 类定义为一个抽象类，然后定义一个抽象方法 <code>getPerimeter</code> 和一个抽象方法 <code>getArea</code>，然后让每个形状的类去继承 <code>Shape</code> 类，并实现这两个抽象方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Shape</span> &#123; <span class="hljs-comment">// 在 class 关键字前加上 abstract 关键字以声明该类为抽象类</span><br>    <span class="hljs-keyword">abstract</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getPerimeter</span><span class="hljs-params">()</span>; <span class="hljs-comment">// 在方法签名前加上 abstract 关键字以声明该方法为抽象方法，抽象方法没有方法体（具体实现），只有方法签名并以分号结束</span><br>    <span class="hljs-keyword">abstract</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getArea</span><span class="hljs-params">()</span>;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">showInfo</span><span class="hljs-params">()</span> &#123; <span class="hljs-comment">// 一个抽象类可以有非抽象方法（但只要有抽象方法的类一定是抽象类），这些方法可以有具体实现，只要不在签名开头加上 abstract 关键字的方法都是非抽象方法</span><br>        System.out.println(<span class="hljs-string">&quot;Perimeter: &quot;</span> + getPerimeter());<br>        System.out.println(<span class="hljs-string">&quot;Area: &quot;</span> + getArea());<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Circle</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Shape</span> &#123; <span class="hljs-comment">// 真实且有具体实现的类需要继承抽象类，并实现抽象方法</span><br>    <span class="hljs-keyword">private</span> <span class="hljs-type">double</span> radius;<br><br>    Circle(<span class="hljs-type">double</span> radius) &#123;<br>        <span class="hljs-built_in">this</span>.radius = radius;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span> <span class="hljs-comment">// 实际上，对抽象方法的实现就是在重写父类的，没有任何具体实现的抽象方法</span><br>    <span class="hljs-comment">// 如果子类没有实现父类的抽象方法且子类不是抽象类，那么编译器会报错</span><br>    <span class="hljs-type">double</span> <span class="hljs-title function_">getPerimeter</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * Math.PI * radius;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-type">double</span> <span class="hljs-title function_">getArea</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> Math.PI * radius * radius;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Rectangle</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Shape</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">double</span> width;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">double</span> height;<br><br>    Rectangle(<span class="hljs-type">double</span> width, <span class="hljs-type">double</span> height) &#123;<br>        <span class="hljs-built_in">this</span>.width = width;<br>        <span class="hljs-built_in">this</span>.height = height;<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-type">double</span> <span class="hljs-title function_">getPerimeter</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * (width + height);<br>    &#125;<br><br>    <span class="hljs-meta">@Override</span><br>    <span class="hljs-type">double</span> <span class="hljs-title function_">getArea</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> width * height;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Circle</span> <span class="hljs-variable">circle</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Circle</span>(<span class="hljs-number">5</span>);<br>        <span class="hljs-type">Rectangle</span> <span class="hljs-variable">rectangle</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Rectangle</span>(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>);<br><br>        System.out.println(<span class="hljs-string">&quot;Circle perimeter: &quot;</span> + circle.getPerimeter());<br>        System.out.println(<span class="hljs-string">&quot;Circle area: &quot;</span> + circle.getArea());<br>        System.out.println(<span class="hljs-string">&quot;Rectangle perimeter: &quot;</span> + rectangle.getPerimeter());<br>        System.out.println(<span class="hljs-string">&quot;Rectangle area: &quot;</span> + rectangle.getArea());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="接口-Interface"><a href="#接口-Interface" class="headerlink" title="接口 (Interface)"></a>接口 (Interface)</h2><p>但实际上，我们并不常使用抽象类，而是使用接口。接口是一种特殊的抽象类，它不需要你在抽象方法前加上 <code>abstract</code> 关键字，而是需要在非抽象方法前加上 <code>default</code> 关键字。在抽象类&#x2F;接口的日常使用情况中，大部分需要被定义的都是抽象方法，所以省去 <code>abstract</code> 关键字会使代码更加简洁。</p><p>值得注意的是，接口中仍然可以定义属性，这些属性默认是公开 (public) 静态 (static) 常量 (final) 。</p><p>同时，与继承不同，一个类可以实现多个接口。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">FirstInterface</span> &#123;<br>    <span class="hljs-type">int</span> <span class="hljs-variable">A</span> <span class="hljs-operator">=</span> <span class="hljs-number">10</span>; <span class="hljs-comment">// public static final 三个关键字由于在接口中定义属性时是默认的，所以可以省略，所以该定义等效于 public static final int A = 10;</span><br>                <span class="hljs-comment">// 由于这是一个常量，所以使用 SCREAMING_SNAKE_CASE 命名规则</span><br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span>;<br>&#125;<br><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">SecondInterface</span> &#123;<br>    <span class="hljs-type">int</span> <span class="hljs-variable">B</span> <span class="hljs-operator">=</span> <span class="hljs-number">20</span>;<br>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">display</span><span class="hljs-params">()</span>;<br><br>    <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">print</span><span class="hljs-params">()</span> &#123; <span class="hljs-comment">// 接口中可以定义静态方法，这些方法只能通过接口名调用</span><br>        System.out.println(<span class="hljs-string">&quot;Static method of SecondInterface&quot;</span>);<br>    &#125;<br>    <br>    <span class="hljs-keyword">default</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">printDefault</span><span class="hljs-params">()</span> &#123; <span class="hljs-comment">// 接口中可以定义默认方法，这些方法可以通过类的对象调用，而且实现该接口的类可以选择是否重写该方法</span><br>        System.out.println(<span class="hljs-string">&quot;Default method of SecondInterface&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyClass</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">FirstInterface</span>, SecondInterface &#123; <span class="hljs-comment">// 一个类可以实现多个接口</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123; <span class="hljs-comment">// 对接口中抽象方法的实现，实际上这仍然是在重写接口中的抽象方法，但是不同的是在这种情况下一般不加 @Override 注解</span><br>        System.out.println(<span class="hljs-string">&quot;Show method of FirstInterface&quot;</span>);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">display</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;Display method of SecondInterface&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">MyClass</span> <span class="hljs-variable">obj</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MyClass</span>();<br>        obj.show();<br>        obj.display();<br>        System.out.println(<span class="hljs-string">&quot;Value of a: &quot;</span> + obj.a); <span class="hljs-comment">// 接口中的静态常量属性可以直接通过类的对象访问</span><br>        System.out.println(<span class="hljs-string">&quot;Value of b: &quot;</span> + obj.b);<br>        System.out.println(<span class="hljs-string">&quot;Value of a: &quot;</span> + FirstInterface.a); <span class="hljs-comment">// 也可以通过接口名访问</span><br>        System.out.println(<span class="hljs-string">&quot;Value of b: &quot;</span> + SecondInterface.b);<br>        System.out.println(<span class="hljs-string">&quot;Value of b: &quot;</span> + MyClass.b); <span class="hljs-comment">// 也可以通过类名访问</span><br>        SecondInterface.print();<br>        obj.printDefault();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="例题-1"><a href="#例题-1" class="headerlink" title="例题"></a>例题</h3><ol><li><p>创建一个正多边形 (<code>RegularPolygon</code>) 接口，它有四个抽象方法 <code>getSideNum</code>、<code>getSideLength</code>、<code>showPerimeter</code> 和 <code>showInteriorAngle</code>。然后分别创建一个 <code>EquilateralTriangle</code> 和一个 <code>Square</code> 类，它们都实现了 <code>RegularPolygon</code> 接口。最后，输出他们的周长、面积以及内角角度。</p><ul><li><code>getSideNum</code> 方法返回正多边形的边数。</li><li><code>getSideLength</code> 方法返回正多边形的边长。</li><li><code>showPerimeter</code> 方法显示正多边形的周长。</li><li><code>showInteriorAngle</code> 方法显示正多边形的内角角度。<ul><li>角度制中，内角的角度为 $\frac{180 (n - 2)}{n} $，其中 $n$ 为正多边形的边数。</li><li>弧度制中，内角的角度为 $\frac{\pi (n - 2)}{n} $，其中 $n$ 为正多边形的边数。</li></ul></li></ul><details class="tag-plugin colorful folding" ><summary><p>解题方法</p></summary><div class="body"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">RegularPolygon</span> &#123;<br>    <span class="hljs-type">double</span> <span class="hljs-title function_">getSideNum</span><span class="hljs-params">()</span>;<br>    <span class="hljs-type">double</span> <span class="hljs-title function_">getSideLength</span><span class="hljs-params">()</span>;<br><br>    <span class="hljs-keyword">default</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">showPerimeter</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-type">double</span> <span class="hljs-variable">perimeter</span> <span class="hljs-operator">=</span> getSideNum() * getSideLength(); <span class="hljs-comment">// 是的，接口中的默认方法可以调用接口中的抽象方法，具体使用哪里的实现取决于你在哪个实现类的对象上调用该方法</span><br>        System.out.println(<span class="hljs-string">&quot;Perimeter: &quot;</span> + perimeter);<br>    &#125;<br>    <span class="hljs-keyword">default</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">showInteriorAngle</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-type">double</span> <span class="hljs-variable">interiorAngleDeg</span> <span class="hljs-operator">=</span> (getSideNum() - <span class="hljs-number">2</span>) * <span class="hljs-number">180</span> / getSideNum();<br>        <span class="hljs-type">double</span> <span class="hljs-variable">interiorAngleRad</span> <span class="hljs-operator">=</span> (getSideNum() - <span class="hljs-number">2</span>) * Math.PI / getSideNum();<br>        System.out.println(<span class="hljs-string">&quot;Interior angle: &quot;</span> + interiorAngleDeg + <span class="hljs-string">&quot;° or &quot;</span> + interiorAngleRad + <span class="hljs-string">&quot; rad&quot;</span>);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">EquilateralTriangle</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">RegularPolygon</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">double</span> <span class="hljs-variable">sideNum</span> <span class="hljs-operator">=</span> <span class="hljs-number">3</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">double</span> sideLength;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">EquilateralTriangle</span><span class="hljs-params">(<span class="hljs-type">double</span> sideLength)</span> &#123;<br>        <span class="hljs-built_in">this</span>.sideLength = sideLength;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getSideNum</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> sideNum;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getSideLength</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> sideLength;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Square</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">RegularPolygon</span> &#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">double</span> <span class="hljs-variable">sideNum</span> <span class="hljs-operator">=</span> <span class="hljs-number">4</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-type">double</span> sideLength;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Square</span><span class="hljs-params">(<span class="hljs-type">double</span> sideLength)</span> &#123;<br>        <span class="hljs-built_in">this</span>.sideLength = sideLength;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getSideNum</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> sideNum;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">double</span> <span class="hljs-title function_">getSideLength</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> sideLength;<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">EquilateralTriangle</span> <span class="hljs-variable">equilateralTriangle</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">EquilateralTriangle</span>(<span class="hljs-number">3</span>);<br>        equilateralTriangle.showPerimeter();<br>        equilateralTriangle.showInteriorAngle();<br><br>        <span class="hljs-type">Square</span> <span class="hljs-variable">square</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Square</span>(<span class="hljs-number">3</span>);<br>        square.showPerimeter();<br>        square.showInteriorAngle();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure></div></details></li><li><p>创建一个 <code>Employee</code> 类，它需要：</p><ul><li>实现三个接口<ul><li><code>PersonalInfo</code> 接口，它有一个抽象方法 <code>getPersonalInfo</code>，以 String 数组返回个人信息 (姓名、年龄、地址)。</li><li><code>EducationInfo</code> 接口，它有一个抽象方法 <code>getEducationInfo</code>，以 String 数组返回教育信息 (学校、专业、入学年份)。</li><li><code>WorkInfo</code> 接口，它有一个抽象方法 <code>getWorkInfo</code>，以 String 数组返回工作信息 (职位、薪资)。</li></ul></li><li>继承 <code>Family</code> 类，它有两个属性，一个是家庭人数，一个是本家庭是否为低收入家庭。它还有一个 <code>show</code> 方法显示相关信息。</li><li><code>Employee</code> 类需要有自己的构造器以及一个特殊方法 <code>isEligibleForPromotion</code>，它返回一个布尔值，表示该员工是否有资格晋升。<ul><li>晋升条件：<ul><li>年龄大于 45，家庭人数大于等于 2，职位是经理 (<code>manager</code>)，或者</li><li>年龄大于 50，学位是博士 (<code>Ph.D.</code>)，薪资小于 5000</li></ul></li></ul></li></ul><details class="tag-plugin colorful folding" ><summary><p>解题方法</p></summary><div class="body"><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">PersonalInfo</span> &#123;<br>    String[] getPersonalInfo(String name, <span class="hljs-type">int</span> age, String location);<br>&#125;<br><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">EducationInfo</span> &#123;<br>    String[] getEducationInfo(String school, String fieldOfStudy, <span class="hljs-type">int</span> graduationYear);<br>&#125;<br><br><span class="hljs-keyword">interface</span> <span class="hljs-title class_">WorkInfo</span> &#123;<br>    String[] getWorkInfo(String position, <span class="hljs-type">int</span> salary);<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Family</span> &#123;<br>    <span class="hljs-type">int</span> familyMemberNumber;<br>    <span class="hljs-type">boolean</span> isLowIncome;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Family</span><span class="hljs-params">(<span class="hljs-type">int</span> familyMemberNumber, <span class="hljs-type">boolean</span> isLowIncome)</span> &#123;<br>        <span class="hljs-built_in">this</span>.familyMemberNumber = familyMemberNumber;<br>        <span class="hljs-built_in">this</span>.isLowIncome = isLowIncome;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> &#123;<br>        System.out.println(<span class="hljs-string">&quot;Family member number: &quot;</span> + familyMemberNumber);<br>        System.out.println(<span class="hljs-string">&quot;Is low income: &quot;</span> + isLowIncome);<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">Employee</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Family</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">PersonalInfo</span>, EducationInfo, WorkInfo &#123;<br>    String[] personalInfo;<br>    String[] educationInfo;<br>    String[] workInfo;<br><br>    <span class="hljs-keyword">public</span> String[] getPersonalInfo(String name, <span class="hljs-type">int</span> age, String location) &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>[]&#123;name, String.valueOf(age), location&#125;;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> String[] getEducationInfo(String school, String fieldOfStudy, <span class="hljs-type">int</span> graduationYear) &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>[]&#123;school, fieldOfStudy, String.valueOf(graduationYear)&#125;;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> String[] getWorkInfo(String position, <span class="hljs-type">int</span> salary) &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">String</span>[]&#123;position, String.valueOf(salary)&#125;;<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-title function_">Employee</span><span class="hljs-params">(</span><br><span class="hljs-params">            String name, <span class="hljs-type">int</span> age, String location,</span><br><span class="hljs-params">            String school, String fieldOfStudy, <span class="hljs-type">int</span> graduationYear,</span><br><span class="hljs-params">            String position, <span class="hljs-type">int</span> salary,</span><br><span class="hljs-params">            <span class="hljs-type">int</span> familyMemberNumber, <span class="hljs-type">boolean</span> isLowIncome</span><br><span class="hljs-params">    )</span> &#123;<br>        <span class="hljs-built_in">super</span>(familyMemberNumber, isLowIncome);<br>        personalInfo = getPersonalInfo(name, age, location);<br>        educationInfo = getEducationInfo(school, fieldOfStudy, graduationYear);<br>        workInfo = getWorkInfo(position, salary);<br>    &#125;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isEligibleForPromotion</span><span class="hljs-params">()</span> &#123;<br>        <span class="hljs-keyword">return</span> (<br>                Integer.parseInt(personalInfo[<span class="hljs-number">1</span>]) &gt; <span class="hljs-number">45</span> &amp;&amp; familyMemberNumber &gt;= <span class="hljs-number">2</span> &amp;&amp; workInfo[<span class="hljs-number">0</span>].equalsIgnoreCase(<span class="hljs-string">&quot;manager&quot;</span>)<br>        ) || (<br>                Integer.parseInt(personalInfo[<span class="hljs-number">1</span>]) &gt; <span class="hljs-number">50</span> &amp;&amp; educationInfo[<span class="hljs-number">1</span>].equals(<span class="hljs-string">&quot;Ph.D.&quot;</span>) &amp;&amp; Integer.parseInt(workInfo[<span class="hljs-number">1</span>]) &lt; <span class="hljs-number">5000</span><br>        );<br>    &#125;<br>&#125;<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Main</span> &#123;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>        <span class="hljs-type">Employee</span> <span class="hljs-variable">employee1</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Employee</span>(<br>                <span class="hljs-string">&quot;John&quot;</span>, <span class="hljs-number">46</span>, <span class="hljs-string">&quot;New York&quot;</span>,<br>                <span class="hljs-string">&quot;Harvard&quot;</span>, <span class="hljs-string">&quot;Ph.D.&quot;</span>, <span class="hljs-number">2010</span>,<br>                <span class="hljs-string">&quot;Manager&quot;</span>, <span class="hljs-number">56000</span>,<br>                <span class="hljs-number">3</span>, <span class="hljs-literal">false</span><br>        );<br>        System.out.println(employee1.isEligibleForPromotion());<br><br>        <span class="hljs-type">Employee</span> <span class="hljs-variable">employee2</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Employee</span>(<br>                <span class="hljs-string">&quot;Alice&quot;</span>, <span class="hljs-number">52</span>, <span class="hljs-string">&quot;New York&quot;</span>,<br>                <span class="hljs-string">&quot;Harvard&quot;</span>, <span class="hljs-string">&quot;Ph.D.&quot;</span>, <span class="hljs-number">2010</span>,<br>                <span class="hljs-string">&quot;Manager&quot;</span>, <span class="hljs-number">4000</span>,<br>                <span class="hljs-number">3</span>, <span class="hljs-literal">true</span><br>        );<br>        System.out.println(employee2.isEligibleForPromotion());<br><br>        <span class="hljs-type">Employee</span> <span class="hljs-variable">employee3</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Employee</span>(<br>                <span class="hljs-string">&quot;Bob&quot;</span>, <span class="hljs-number">46</span>, <span class="hljs-string">&quot;New York&quot;</span>,<br>                <span class="hljs-string">&quot;Harvard&quot;</span>, <span class="hljs-string">&quot;Ph.D.&quot;</span>, <span class="hljs-number">2010</span>,<br>                <span class="hljs-string">&quot;Manager&quot;</span>, <span class="hljs-number">56000</span>,<br>                <span class="hljs-number">1</span>, <span class="hljs-literal">false</span><br>        );<br>        System.out.println(employee3.isEligibleForPromotion());<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>上述代码中最特殊的一点是：没错，一个类可以同时继承另一个类并实现一个或多个接口。具体语法是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyClass</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">ParentClass</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Interface1</span>, Interface2, Interface3<br><span class="hljs-comment">// class 类名 extends 父类名 implements 接口名1, 接口名2, 接口名3</span><br></code></pre></td></tr></table></figure></div></details></li></ol>]]></content>
    
  </entry>
  
  <entry>
    <title>Java-韩顺平笔记</title>
    <link href="https://alan.org.cn/posts/java-note.html"/>
    <id>https://alan.org.cn/posts/java-note.html</id>
    <published>2023-06-20T06:24:00.000Z</published>
    <updated>2026-06-04T04:10:49.244Z</updated>
    
    
    
    <category term="笔记" scheme="https://alan.org.cn/tags/%E7%AC%94%E8%AE%B0/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;基础知识&quot;&gt;&lt;a href=&quot;#基础知识&quot; class=&quot;headerlink&quot; title=&quot;基础知识&quot;&gt;&lt;/a&gt;基础知识&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;JDK：Java开发工具包&lt;/li&gt;
&lt;li&gt;JDK &amp;#x3D; JRE+java的开发工具[java,jav</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><ol><li>JDK：Java开发工具包</li><li>JDK &#x3D; JRE+java的开发工具[java,javac,javadoc,javap等]</li><li>JRE：java运行环境 JRE &#x3D; JVM+java核心类库[类]</li><li>JDK &#x3D; JRE + 开发工具集</li><li>jRE &#x3D; JVM + javaSE标准类库</li><li>如果只想运行开发好的.class文件，只需要JRE</li></ol><h2 id="流程分析"><a href="#流程分析" class="headerlink" title="流程分析"></a>流程分析</h2><p>.java 文件—javac—&gt;.class—java—&gt;结果</p><p>源文件–编译–&gt;字节码文件–运行–&gt;结果</p><h2 id="开发注意事项和细节说明"><a href="#开发注意事项和细节说明" class="headerlink" title="开发注意事项和细节说明"></a>开发注意事项和细节说明</h2><ol><li>java 源文件以.java 为扩展名。源文件的基本组成部分是类（class）</li><li>java 应用程序执行入口时 main()方法，</li><li>java 语言严格区分大小写</li><li>每条语句以;结尾</li><li>一个源文件最多<strong>只能有一个 public 类</strong>，其他类不限数量</li><li>打括号都是成对出现，缺一不可[习惯先写{}再写代码]</li><li>如果源文件包含一个 public 类，则<strong>文件名必须按该类名命名</strong></li><li>一个源文件最多只能有一个 public 类，其他类不限数量，也可以将 main 方法写在其他的非 public 类中，然后指定运行非 public 类，这样入口方法就是非 public 的 main 方法</li></ol><h2 id="转义字符"><a href="#转义字符" class="headerlink" title="转义字符"></a>转义字符</h2><table><thead><tr><th>转义字符</th><th>含义</th></tr></thead><tbody><tr><td>\t</td><td>一个制表符，实现对齐功能</td></tr><tr><td>\n</td><td>换行符</td></tr><tr><td>\\</td><td>一个\</td></tr><tr><td>\“</td><td>一个”</td></tr><tr><td>\‘</td><td>一个’</td></tr><tr><td>\r</td><td>一个回车</td></tr></tbody></table><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> javabase;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@version</span> 1.0</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">TransferredMeaning</span> &#123;<br>  <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>    System.out.println(<span class="hljs-string">&quot;北京\t天津\t上海&quot;</span>);<br>    System.out.println(<span class="hljs-string">&quot;jack\nsmith\nmary&quot;</span>);<br>    System.out.println(<span class="hljs-string">&quot;北京\\天津\\上海&quot;</span>);<br>    System.out.println(<span class="hljs-string">&quot;北京\r天津\r上海&quot;</span>);<br>    System.out.println(<span class="hljs-string">&quot;北京\&quot;天津\&#x27;上海&quot;</span>);<br><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><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><ul><li>将自己的思想通过注释先整理出来，再用代码去体现</li></ul><ol><li>单行注释</li><li>多行注释</li><li>文档注释</li></ol><ul><li><p>单行注释</p><ul><li>格式： &#x2F;&#x2F; 注释文本</li></ul></li><li><p>多行注释</p><ul><li>格式 ：&#x2F;* 注释文本 *&#x2F;</li></ul></li><li><p>文档注释</p><ul><li>格式：&#x2F;** 注释文本  *&#x2F;</li></ul></li></ul><p><strong>使用细节</strong>：</p><ol><li>被注释的文字，不会被 JVM 解释执行</li><li>多行注释里面不允许有多行注释嵌套</li></ol><h2 id="文档注释"><a href="#文档注释" class="headerlink" title="文档注释"></a>文档注释</h2><p>注释内容可以被 JDK 提供的工具 javadoc 所解析，生产一套以网页形式呈现的呈现的说明文档，一般写在类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> javabase;<br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@author</span> alan</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@version</span> 1.0</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">TransferredMeaning</span> &#123;<br>  <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> &#123;<br>    System.out.println(<span class="hljs-string">&quot;北京\t天津\t上海&quot;</span>);<br>    System.out.println(<span class="hljs-string">&quot;jack\nsmith\nmary&quot;</span>);<br>    System.out.println(<span class="hljs-string">&quot;北京\\天津\\上海&quot;</span>);<br>    System.out.println(<span class="hljs-string">&quot;北京\r天津\r上海&quot;</span>);<br>    System.out.println(<span class="hljs-string">&quot;北京\&quot;天津\&#x27;上海&quot;</span>);<br><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs base">javadoc -d . -author -version TransferredMeaning<br></code></pre></td></tr></table></figure><p>-d 指定地址</p><p>-author 指定作者标签</p><p>-version 指定版本标签</p><h2 id="java-代码规范"><a href="#java-代码规范" class="headerlink" title="java 代码规范"></a>java 代码规范</h2><ol><li>类、方法的注释要以 javadoc 的方式来写</li><li>非 javadoc 的注释往往时给代码的维护者看的，着重告诉读者如何修改，注意什么问题</li><li>使用 tab 操作，实现缩进，默认整体移动，时候用 shift+tab 整体向右移动</li><li>运算符和&#x3D;两边习惯性各加一个空格</li><li>源文件使用 utf-8 编码</li><li>行宽不要超过 80 字符</li><li>代码编写<strong>次行风格</strong>和<strong>行尾风格</strong>（推荐）</li></ol><h2 id="变量原理"><a href="#变量原理" class="headerlink" title="变量原理"></a>变量原理</h2><p>变量是程序基本组成单位</p><p>三个基本要素（类型+名称+值）</p><p>底层名称就像指针，指向内存中的值</p><h2 id="变量的概念"><a href="#变量的概念" class="headerlink" title="变量的概念"></a>变量的概念</h2><p>变量相当于内存中一个数据存储空间的表示，你可以把变量看做是一个房间的门牌号，通过门牌号外面可以找到房间，而通过变量名可以访问到变量值</p><p><strong>基本步骤</strong></p><p>1.声明变量</p><p>int a;</p><p>2.赋值</p><p>a&#x3D;10;</p><h2 id="变量使用细节"><a href="#变量使用细节" class="headerlink" title="变量使用细节"></a>变量使用细节</h2><ol><li>变量表示内存中的一个存储区域[不同的变量，类型不同，占用的空间大小不同]</li><li>该区域有自己的名称[变量名]和类型[数据类型]</li><li>变量必须先声明，后使用，即有顺序</li><li>该区域的数据&#x2F;值可以在同一类型范围内不断改变</li><li>变量在同一个作用于内不能重名</li><li>变量&#x3D;变量名+值+数据类型</li></ol><h2 id="加号的使用"><a href="#加号的使用" class="headerlink" title="加号的使用"></a>加号的使用</h2><p>当左右两边都是数值时，做加法运算</p><p>当左右两边为字符串，则做拼接运算</p><h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><p>每一种数据都定义了明确的数据类型，在内侧中分配了不同大小的内存空间</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-string">数据类型:</span><br>  <span class="hljs-string">基本数据类型:</span><br>    <span class="hljs-string">数值型:</span><br>      <span class="hljs-string">整数:</span><br>        <span class="hljs-string">byte[1]、short[2]、int[4]、long[8]</span><br>      <span class="hljs-string">浮点型:</span><br>        <span class="hljs-string">float[4]、double[8]</span><br>    <span class="hljs-string">字符型:</span><br>      <span class="hljs-string">char[2]</span><br>    <span class="hljs-string">布尔型:</span><br>      <span class="hljs-string">boolean[1]</span><br>  <span class="hljs-string">引用数据类型:</span><br>    <span class="hljs-string">类</span> <span class="hljs-string">class</span><br>    <span class="hljs-string">接口</span> <span class="hljs-string">interface</span><br>    <span class="hljs-string">数组</span> []<br></code></pre></td></tr></table></figure><h2 id="整数类型"><a href="#整数类型" class="headerlink" title="整数类型"></a>整数类型</h2><table><thead><tr><th>类型</th><th>占用空间</th><th>范围</th></tr></thead><tbody><tr><td>byte</td><td>1</td><td>-128~127</td></tr><tr><td>short</td><td>2</td><td>-2^15~2^15-1</td></tr><tr><td>int</td><td>4</td><td>-2^32~2^32-1</td></tr><tr><td>long</td><td>8</td><td>-2^63~2^63-1</td></tr></tbody></table><h2 id="整型细节"><a href="#整型细节" class="headerlink" title="整型细节"></a>整型细节</h2><ol><li>java 各整数类型有固定的范围和字段长度，不受具体 os 的影响，以保证 java 呈现的可以移植性</li><li>java 的<strong>整型常量默认为 int 型</strong>，声明 long 型出来需要在后面加**’l’或”L”**</li><li>java 程序中变量常声明 int 型，除非不足以表示大数，才使用 long</li><li>bit：计算机中最小存储单位，byte：计算机中基本存储单元，1byte &#x3D; 8bit</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">n1</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>; <span class="hljs-comment">//4个字节</span><br><span class="hljs-type">int</span> <span class="hljs-variable">n2</span> <span class="hljs-operator">=</span> <span class="hljs-number">1L</span>;<span class="hljs-comment">//对不对?不对</span><br><span class="hljs-type">long</span> <span class="hljs-variable">n3</span> <span class="hljs-operator">=</span> <span class="hljs-number">1L</span>;<span class="hljs-comment">//对</span><br> <br></code></pre></td></tr></table></figure><h2 id="浮点型使用"><a href="#浮点型使用" class="headerlink" title="浮点型使用"></a>浮点型使用</h2><table><thead><tr><th>类型</th><th>占用空间</th></tr></thead><tbody><tr><td>单精度 float</td><td>4 字节</td></tr><tr><td>双精度 double</td><td>8 字节</td></tr></tbody></table><p><strong>浮点数&#x3D;符号位+指数位+尾数位</strong></p><p>尾数部分可能丢失，造成精度损失（小数都是近似值）</p><p><em><strong>小数相加都是近似值</strong></em></p><h2 id="浮点型细节"><a href="#浮点型细节" class="headerlink" title="浮点型细节"></a>浮点型细节</h2><ol><li><p>与整数类型类似，java 浮点类型也有固定的范围和字段长度，不受具体 os 的影响</p></li><li><p>java 的<strong>浮点型常量默认为 double 型</strong>，声明 float 型常量，须后加’f’或’F’</p><ol><li>因为默认时 double 所以 float n1 &#x3D; 1.1;会报错加个 f 就不会报错了</li></ol></li><li><p>浮点型常量有两种形式</p><ol><li>十进制形式：如 5.12,   512.0f,   .512（必须要有小数点）。</li><li>科学计数形式：如 5.12e2[5.12*10 的 2 次方]         5.12E-2[5.12 除以 10 的 2 次方]</li><li>0.532 可以省略 0 但不能省略.</li></ol></li><li><p>通常情况下，应该使用 double 型因为它比 float 型更精确</p><ol><li><pre><code class="java">double num9 = 2.1234567851;//2.1234567851float num10 = 2.1234567851F;//2.1234567float num1 = 1.1;//错误float num2 = 1.1F;//对double num3 = 1.1;//对double num4 = 1.1f;//对double num5 = .123;//等价0.123System.out.println(5.12e2);//512.0System.out.println(5.12E-2);//0.0512<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"><br>5. 浮点数使用陷阱：2.7 和 8.1/3 比较<br>   1. ```java<br>      double num11 = 2.7;//2.7<br>      double num12 = 8/3;//2.6999999999999997<br>      //错误写法<br>      if( num11 == num12) &#123;<br>        System.out.println(&quot;num11 == num12 相等&quot;);<br>      &#125;<br>      //正确写法<br>      if(Math.abs(num11 - num12) &lt; 0.000001 ) &#123;<br>        System.out.println(&quot;差值非常小，到我的规定精度，认为相等...&quot;);<br>      &#125;<br></code></pre></td></tr></table></figure></code></pre></li><li><p>当我们运算结果时小数的进行相判断，要小心 错误写法 num11 &#x3D;&#x3D; num12</p></li><li><p>应该是以两个数的差值的绝对值，在某个精度范围类判断 Math.abs(num11 - num12)&lt;0.000001</p></li></ol></li></ol><p>单精度太长，会被舍去</p><h2 id="API-文档"><a href="#API-文档" class="headerlink" title="API 文档"></a>API 文档</h2><p>JDK-&gt;包-&gt;类、接口、异常-&gt;构造方法、字段、成员方法</p><h2 id="字符类型（char）"><a href="#字符类型（char）" class="headerlink" title="字符类型（char）"></a>字符类型（char）</h2><p>字符类型可以表示<strong>单个字符</strong>，字符类型是 char，char 是两个字节（可以存放汉字）多个字符我们用字符串 String</p><p>可以直接存放一个数字</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">char</span> <span class="hljs-variable">c1</span> <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;a&#x27;</span>;<br><span class="hljs-type">char</span> <span class="hljs-variable">c1</span> <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;\t&#x27;</span>;<br><span class="hljs-type">char</span> <span class="hljs-variable">c1</span> <span class="hljs-operator">=</span> <span class="hljs-number">97</span>;<br><span class="hljs-type">char</span> <span class="hljs-variable">c1</span> <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;韩&#x27;</span>;<br></code></pre></td></tr></table></figure><h2 id="字符类型细节"><a href="#字符类型细节" class="headerlink" title="字符类型细节"></a>字符类型细节</h2><ol><li>字符常量使用<strong>单引号(‘’)括</strong>起来的单个字符，<strong>双引号会报错</strong></li><li>java 中允许使用转义字符’\n’来将其后的字符转变为特殊字符常量</li><li>在 java 中，char 的本质是一个整数，在默认输出时<strong>Uniconde 码</strong>对应的字符，要输出数字须转型为 int</li><li>可以直接给 char 赋一个整数，然后输出时，会按照对应的 unicode 字符输出</li><li>char 类型是<strong>可以进行运算</strong>的，相当于一个整数，因为它都对应有 unicode 码</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">char</span> <span class="hljs-variable">c1</span> <span class="hljs-operator">=</span> <span class="hljs-number">97</span>;<br>System.out.println(c1); <span class="hljs-comment">// a</span><br><br><span class="hljs-type">char</span> <span class="hljs-variable">c2</span> <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;a&#x27;</span>; <span class="hljs-comment">//输出&#x27;a&#x27; 对应的数字</span><br>System.out.println((<span class="hljs-type">int</span>)c2);<span class="hljs-comment">//97</span><br><br>System.out.println(<span class="hljs-string">&#x27;a&#x27;</span> + <span class="hljs-number">10</span>);<span class="hljs-comment">//107</span><br><br><span class="hljs-type">chat</span> <span class="hljs-variable">c5</span> <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;b&#x27;</span> + <span class="hljs-number">1</span>;<br>System.out.println((<span class="hljs-type">int</span>)c5); <span class="hljs-comment">//99-&gt;对应的字符-&gt;编码表ASCII(规定好的)=&gt;c</span><br>SYstem.out.println(c5); <span class="hljs-comment">// c</span><br></code></pre></td></tr></table></figure><h2 id="字符型的本质"><a href="#字符型的本质" class="headerlink" title="字符型的本质"></a>字符型的本质</h2><ol><li>字符型存储到家睡觉中，需要将字符对于到码值（整数）找出来<ol><li>存储：’a’&#x3D;&#x3D;&gt;码值 97&#x3D;&#x3D;&gt;二进制&#x3D;&#x3D;&gt;存储</li><li>读取：二进制&#x3D;&#x3D;&gt;97&#x3D;&#x3D;&gt;’a’&#x3D;&gt;显示</li></ol></li><li>字符和码值的对应关系是通过字符编码表决定的</li><li>编码表<ol><li>ASCII（一个字节表示，一个 128 个字符）</li><li>Unicode（固定大小的编码，使用两个字节来表示，字符和汉字统一都是占用两个字节）</li><li>utf-8（大小可变，字母使用 1 个字节，汉字 3 个字节）</li><li>gbk（可以表示汉字，而且范围广，字母 1 字节，汉字 2 字节）</li><li>Gb2312（可以表示汉字，关闭 312&lt;gbk）</li><li>big5 码（繁体中文，台湾，香港）</li></ol></li></ol><h2 id="常用编码（了解）"><a href="#常用编码（了解）" class="headerlink" title="常用编码（了解）"></a>常用编码（了解）</h2><ol><li>ASCII 码 ，缺点不能完全显示</li><li>Unicode 的好处一种编码，将世界上所有字符的符号都纳入其中，每一个符号都给予一个独一无二的编码，使用 Unicode 没有乱码问题，缺点，一个英文和一个汉字都占 2 字节，对于存储空间来说是浪费</li><li>Unicode 码兼容 ASCII</li><li>UTF-8 是互联网上使用最广泛的一种 Unicode 的实现方式</li><li>使用大小可变的编码，字母 1 字节，汉字 3 字节</li></ol><h2 id="布尔类型"><a href="#布尔类型" class="headerlink" title="布尔类型"></a>布尔类型</h2><ol><li>布尔类型也叫 boolean，只允许取值 true 和 false，无 null</li><li>boolean 类型占 1 字节</li><li>boolean 适用于逻辑运算，一般用于程序流控制</li></ol><p>使用细节说明：</p><p>不可以 0 或非 0 的整数代替 false</p><h2 id="基本类型转换"><a href="#基本类型转换" class="headerlink" title="基本类型转换"></a>基本类型转换</h2><p>当java程序在进行赋值或者运算时，精度小的类型自动转换为精度大的类型，这就是自动类型转换</p><p>char -&gt; int -&gt;long -&gt; float -&gt; double</p><p>byte -&gt;short -&gt; int -&gt; long -&gt; float -&gt; double</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs ;">int a = &#x27;c&#x27;;<br>double d = 80;<br></code></pre></td></tr></table></figure><h2 id="基本数据类型转换细节"><a href="#基本数据类型转换细节" class="headerlink" title="基本数据类型转换细节"></a>基本数据类型转换细节</h2><ol><li><p>有多种类型的数据混合运算时，系统首先自动将所有数据转换成容量<strong>最大</strong>的那种数据类型，然后再进行计算</p></li><li><p>当我们把<strong>精度大</strong>的数据类型赋值给精度小的数据类型时，就会报错，<strong>反之</strong>就会进行<strong>自动类型转换</strong></p></li><li><p>(Byte,short)和char之间<strong>不会相互自动转换</strong></p></li><li><p>byte，short，char他们三者可以计算，在计算时首先转换为int类型</p></li><li><p>boolean<strong>不参与转换</strong></p></li><li><p>自动提升原则：表达式结果的类型自动提升为操作数中<strong>最大</strong>的类型</p></li><li><p>当把具体数赋给byte 时，(1)先判断该数是否在byte 范围内，如果是就可以，如果是变量赋值则不行</p></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">n1</span> <span class="hljs-operator">=</span> <span class="hljs-number">10</span>;<span class="hljs-comment">//ok</span><br><span class="hljs-type">float</span> <span class="hljs-variable">d1</span> <span class="hljs-operator">=</span> n1+ <span class="hljs-number">1.1</span>;<span class="hljs-comment">//错误n1+1.1-&gt;double</span><br><span class="hljs-type">double</span> <span class="hljs-variable">d1</span> <span class="hljs-operator">=</span> n1 + <span class="hljs-number">1.1</span>;<span class="hljs-comment">//对，结果为double</span><br><span class="hljs-type">float</span> <span class="hljs-variable">d1</span> <span class="hljs-operator">=</span> n1 + <span class="hljs-number">1.1F</span>;<span class="hljs-comment">//对n1+1.1F-&gt;float</span><br><span class="hljs-type">int</span> <span class="hljs-variable">n2</span> <span class="hljs-operator">=</span> <span class="hljs-number">1.1</span>;<span class="hljs-comment">//错误double-&gt;int</span><br><span class="hljs-type">byte</span> <span class="hljs-variable">b1</span> <span class="hljs-operator">=</span> <span class="hljs-number">10</span>;<span class="hljs-comment">//对 -128-127 当把具体数赋给byte 时，(1)先判断该数是否在byte 范围内，如果是就可以</span><br><span class="hljs-type">int</span> <span class="hljs-variable">n2</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<span class="hljs-comment">//n2时int</span><br><span class="hljs-type">byte</span> <span class="hljs-variable">b2</span> <span class="hljs-operator">=</span> n2;<span class="hljs-comment">//错误,原因，如果是变量赋值，判断类型</span><br><span class="hljs-type">char</span> <span class="hljs-variable">c1</span> <span class="hljs-operator">=</span> b1;<span class="hljs-comment">//错误原因byte不能自动转换成char</span><br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//细节4: byte，short，char 他们三者可以计算，在计算时首先转换为int 类型</span><br><span class="hljs-type">byte</span> <span class="hljs-variable">b2</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br><span class="hljs-type">byte</span> <span class="hljs-variable">b3</span> <span class="hljs-operator">=</span> <span class="hljs-number">2</span>;<br><span class="hljs-type">short</span> <span class="hljs-variable">s1</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br><span class="hljs-comment">//short s2 = b2 + s1;//错, b2 + s1 =&gt; int</span><br><span class="hljs-type">int</span> <span class="hljs-variable">s2</span> <span class="hljs-operator">=</span> b2 + s1;<span class="hljs-comment">//对, b2 + s1 =&gt; int</span><br><span class="hljs-comment">//byte b4 = b2 + b3; //错误: b2 + b3 =&gt; int</span><br><span class="hljs-comment">//</span><br><span class="hljs-comment">//boolean 不参与转换</span><br><span class="hljs-type">boolean</span> <span class="hljs-variable">pass</span> <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;<br><span class="hljs-comment">//int num100 = pass;// boolean 不参与类型的自动转换</span><br><span class="hljs-comment">//自动提升原则： 表达式结果的类型自动提升为操作数中最大的类型</span><br><span class="hljs-comment">//看一道题</span><br><span class="hljs-type">byte</span> <span class="hljs-variable">b4</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br><span class="hljs-type">short</span> <span class="hljs-variable">s3</span> <span class="hljs-operator">=</span> <span class="hljs-number">100</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">num200</span> <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br><span class="hljs-type">float</span> <span class="hljs-variable">num300</span> <span class="hljs-operator">=</span> <span class="hljs-number">1.1F</span>;<br><span class="hljs-type">double</span> <span class="hljs-variable">num500</span> <span class="hljs-operator">=</span> b4 + s3 + num200 + num300; <span class="hljs-comment">//float -&gt; double</span><br></code></pre></td></tr></table></figure><h2 id="强制类型转换-ForceConvent"><a href="#强制类型转换-ForceConvent" class="headerlink" title="强制类型转换(ForceConvent)"></a>强制类型转换(ForceConvent)</h2><p>自动类型转换的逆过程，将容量大的数据类型转换为容量小的数据类型，使用时要加上强制转换符（），但可能造成<strong>精度降低或溢出</strong>，格外要注意</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> (<span class="hljs-type">int</span>)<span class="hljs-number">1.9</span>; <span class="hljs-comment">//精度降低</span><br>System.out.println(i)<span class="hljs-comment">//1</span><br>  <br><span class="hljs-type">int</span> <span class="hljs-variable">j</span> <span class="hljs-operator">=</span> <span class="hljs-number">100</span>;<br><span class="hljs-type">byte</span> <span class="hljs-variable">b1</span> <span class="hljs-operator">=</span> (<span class="hljs-type">byte</span>)j;  <span class="hljs-comment">//溢出</span><br>System.out.println(b1); <span class="hljs-comment">//-48</span><br><br><span class="hljs-comment">//强转符号只针对于最近的操作数有效，往往会使用小括号提升优先级</span><br><span class="hljs-comment">//int x = (int)10*3.5+6*1.5;//编译错误： double -&gt; int</span><br><span class="hljs-type">int</span> <span class="hljs-variable">x</span> <span class="hljs-operator">=</span> (<span class="hljs-type">int</span>)(<span class="hljs-number">10</span>*<span class="hljs-number">3.5</span>+<span class="hljs-number">6</span>*<span class="hljs-number">1.5</span>);<span class="hljs-comment">// (int)44.0 -&gt; 44</span><br>System.out.println(x);<span class="hljs-comment">//44</span><br><span class="hljs-type">char</span> <span class="hljs-variable">c1</span> <span class="hljs-operator">=</span> <span class="hljs-number">100</span>; <span class="hljs-comment">//ok</span><br><span class="hljs-type">int</span> <span class="hljs-variable">m</span> <span class="hljs-operator">=</span> <span class="hljs-number">100</span>; <span class="hljs-comment">//ok</span><br><span class="hljs-comment">//char c2 = m; //错误</span><br><span class="hljs-type">char</span> <span class="hljs-variable">c3</span> <span class="hljs-operator">=</span> (<span class="hljs-type">char</span>)m; <span class="hljs-comment">//ok</span><br>System.out.println(c3);<span class="hljs-comment">//100 对应的字符, d 字符</span><br></code></pre></td></tr></table></figure><h2 id="强制类型转换细节"><a href="#强制类型转换细节" class="headerlink" title="强制类型转换细节"></a>强制类型转换细节</h2><ol><li><p>当进行数据的从<strong>大</strong>的类型转向<strong>小</strong>的类型时，就需要使用到强制转换</p></li><li><p>强制符号只针对<strong>最进</strong>的操作有效，往往会使用小括号提升优先级</p><ol><li><pre><code class="java">// int x = (int)10*3.5+6*1.5;int y = (int)(10*3.5+6*1.5);System.out.println(y);<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"><br>      <br><br>3. char类型可以保存int的常量**值**，但不能保存int的**变量**值，需要强转<br><br>   1. ``` java<br>      char c1 = 100; // ok<br>      int m = 100; // ok<br>      char c2 = m; // 错误<br>      char c3 = (char)m; // ok<br>      System.out.println(c3)// 100对应的字符<br></code></pre></td></tr></table></figure></code></pre></li></ol></li><li><p>byte、short、char类型进行<strong>运算</strong>时，当作<strong>int</strong>类型处理</p></li></ol><h2 id="练习题"><a href="#练习题" class="headerlink" title="练习题"></a>练习题</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">short</span> <span class="hljs-variable">s</span> <span class="hljs-operator">=</span> <span class="hljs-number">12</span>;<span class="hljs-comment">//ok 12虽然时整型，但是在sort范围内，所以赋值时，先看是否在范围内</span><br>s = s - <span class="hljs-number">9</span> ; <span class="hljs-comment">//错误 s-9 为int ,int -&gt; short，从大到小没有强转，所以错误</span><br><br><span class="hljs-type">byte</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> <span class="hljs-number">10</span>; <span class="hljs-comment">//ok 在范围内</span><br>b = b + <span class="hljs-number">11</span>;<span class="hljs-comment">// 错误 int-&gt;byte</span><br>b = (<span class="hljs-type">byte</span>)(b+<span class="hljs-number">11</span>);<span class="hljs-comment">//正确，大到小+强转3333</span><br><br><span class="hljs-type">char</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;a&#x27;</span>; <span class="hljs-comment">//ok</span><br><span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">16</span>;<span class="hljs-comment">//ok</span><br><span class="hljs-type">float</span> <span class="hljs-variable">d</span> <span class="hljs-operator">=</span> <span class="hljs-number">.3</span><span class="hljs-number">.14F</span>;<span class="hljs-comment">//ok</span><br><span class="hljs-type">double</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> c + i + d; <span class="hljs-comment">// ok float -&gt; double</span><br><br><span class="hljs-type">byte</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> <span class="hljs-number">16</span> ;<span class="hljs-comment">//ok</span><br><span class="hljs-type">short</span> <span class="hljs-variable">s</span> <span class="hljs-operator">=</span> <span class="hljs-number">14</span>; <span class="hljs-comment">//ok</span><br><span class="hljs-type">short</span> <span class="hljs-variable">t</span> <span class="hljs-operator">=</span> s + b; <span class="hljs-comment">//错误 byte short char参与运算会变成int 类型</span><br></code></pre></td></tr></table></figure><h2 id="基本数据类型和String类型的转换"><a href="#基本数据类型和String类型的转换" class="headerlink" title="基本数据类型和String类型的转换"></a>基本数据类型和String类型的转换</h2><p>基本类型转String类型</p><p>语法：基本类型的值 +””</p><p>String类型转基本数据类型</p><p>语法：通过基本类型的包装类调用parseXX方法即可</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//基本数据类型-&gt;String</span><br><span class="hljs-type">int</span> <span class="hljs-variable">n1</span> <span class="hljs-operator">=</span> <span class="hljs-number">100</span>;<br><span class="hljs-type">float</span> <span class="hljs-variable">f1</span> <span class="hljs-operator">=</span> <span class="hljs-number">1.1F</span>;<br><span class="hljs-type">double</span> <span class="hljs-variable">d1</span> <span class="hljs-operator">=</span> <span class="hljs-number">4.5</span>;<br><span class="hljs-type">boolean</span> <span class="hljs-variable">b1</span> <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;<br><span class="hljs-type">String</span> <span class="hljs-variable">s1</span> <span class="hljs-operator">=</span> n1 + <span class="hljs-string">&quot;&quot;</span>;<br><span class="hljs-type">String</span> <span class="hljs-variable">s2</span> <span class="hljs-operator">=</span> f1 + <span class="hljs-string">&quot;&quot;</span>;<br><span class="hljs-type">String</span> <span class="hljs-variable">s3</span> <span class="hljs-operator">=</span> d1 + <span class="hljs-string">&quot;&quot;</span>;<br><span class="hljs-type">String</span> <span class="hljs-variable">s4</span> <span class="hljs-operator">=</span> b1 + <span class="hljs-string">&quot;&quot;</span>;<br>System.out.println(s1 + <span class="hljs-string">&quot; &quot;</span> + s2 + <span class="hljs-string">&quot; &quot;</span> + s3 + <span class="hljs-string">&quot; &quot;</span> + s4);<br><span class="hljs-comment">//String-&gt;对应的基本数据类型</span><br><span class="hljs-type">String</span> <span class="hljs-variable">s5</span> <span class="hljs-operator">=</span> <span class="hljs-string">&quot;123&quot;</span>;<br><span class="hljs-comment">//会在OOP 讲对象和方法的时候回详细</span><br><span class="hljs-comment">//解读使用基本数据类型对应的包装类，的相应方法，得到基本数据类型</span><br><span class="hljs-type">int</span> <span class="hljs-variable">num1</span> <span class="hljs-operator">=</span> Integer.parseInt(s5);<br><span class="hljs-type">double</span> <span class="hljs-variable">num2</span> <span class="hljs-operator">=</span> Double.parseDouble(s5);<br><span class="hljs-type">float</span> <span class="hljs-variable">num3</span> <span class="hljs-operator">=</span> Float.parseFloat(s5);<br><span class="hljs-type">long</span> <span class="hljs-variable">num4</span> <span class="hljs-operator">=</span> Long.parseLong(s5);<br><span class="hljs-type">byte</span> <span class="hljs-variable">num5</span> <span class="hljs-operator">=</span> Byte.parseByte(s5);<br><span class="hljs-type">boolean</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> Boolean.parseBoolean(<span class="hljs-string">&quot;true&quot;</span>);<br><span class="hljs-type">short</span> <span class="hljs-variable">num6</span> <span class="hljs-operator">=</span> Short.parseShort(s5);<br>System.out.println(<span class="hljs-string">&quot;===================&quot;</span>);<br>System.out.println(num1);<span class="hljs-comment">//123</span><br>System.out.println(num2);<span class="hljs-comment">//123.0</span><br>System.out.println(num3);<span class="hljs-comment">//123.0</span><br>System.out.println(num4);<span class="hljs-comment">//123</span><br>System.out.println(num5);<span class="hljs-comment">//123</span><br>System.out.println(num6);<span class="hljs-comment">//123</span><br>System.out.println(b);<span class="hljs-comment">//true</span><br><span class="hljs-comment">//怎么把字符串转成字符char -&gt; 含义是指把字符串的第一个字符得到</span><br><span class="hljs-comment">//解读s5.charAt(0) 得到s5 字符串的第一个字符&#x27;1&#x27;</span><br>System.out.println(s5.charAt(<span class="hljs-number">0</span>));<br></code></pre></td></tr></table></figure><h2 id="基本数据类型和String类型的转换细节"><a href="#基本数据类型和String类型的转换细节" class="headerlink" title="基本数据类型和String类型的转换细节"></a>基本数据类型和String类型的转换细节</h2><ol><li>在将String类型转成基本数据类型时，要确保String类型能够转成有效的数据，比如我们把“123”转成一个整数，但是不能把“hello”转换成一个整数</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>运算符优先级</li></ol><p>用以表示数据的运算，赋值，比较</p><p><strong>算术运算符</strong></p><table><thead><tr><th>运算符</th><th>运算</th><th>范例</th><th>结果</th></tr></thead><tbody><tr><td>+</td><td>正号</td><td>+7</td><td>7</td></tr><tr><td>-</td><td>负号</td><td>b&#x3D;11;-b</td><td>-11</td></tr><tr><td>+</td><td>加</td><td>9+9</td><td>18</td></tr><tr><td>-</td><td>减</td><td>10-8</td><td>2</td></tr><tr><td>&#x2F;</td><td>除</td><td>9&#x2F;9</td><td>1</td></tr><tr><td>%</td><td>取模</td><td>11%9</td><td>2</td></tr><tr><td>++</td><td>自增（前）：先运算后取值，自增（后）：先取值后运算</td><td>a&#x3D;2;b&#x3D;++a;   a&#x3D;2;b&#x3D;a++</td><td>a&#x3D;3;b&#x3D;3</td></tr></tbody></table><h2 id="算术运算符的具体使用-ArithmeticOperator"><a href="#算术运算符的具体使用-ArithmeticOperator" class="headerlink" title="算术运算符的具体使用(ArithmeticOperator)"></a>算术运算符的具体使用(ArithmeticOperator)</h2><ol><li><p>+，-，*,&#x2F;,%,++,–,重点讲&#x2F;，%，++</p><ol><li><p><strong>在% 的本质看一个公式!!!! a % b &#x3D; a - a &#x2F; b * b</strong></p></li><li><pre><code class="java">//使用System.out.println(10 / 4); //从数学来看是2.5, java 中2System.out.println(10.0 / 4); //java 是2.5// 注释快捷键ctrl + /, 再次输入ctrl + / 取消注释double d = 10 / 4;//java 中10 / 4 = 2, 2=&gt;2.0System.out.println(d);// 是2.0// % 取模,取余// 在% 的本质看一个公式!!!! a % b = a - a / b * b// -10 % 3 =&gt; -10 - (-10) / 3 * 3 = -10 + 9 = -1// 10 % -3 = 10 - 10 / (-3) * (-3) = 10 - 9 = 1// -10 % -3 = (-10) - (-10) / (-3) * (-3) = -10 + 9 = -1System.out.println(10 % 3); //1System.out.println(-10 % 3); // -1System.out.println(10 % -3); //1System.out.println(-10 % -3);//-1//++的使用//int i = 10;i++;//自增等价于i = i + 1; =&gt; i = 11++i;//自增等价于i = i + 1; =&gt; i = 12System.out.println(&quot;i=&quot; + i);//12/*作为表达式使用前++：++i 先自增后赋值后++：i++先赋值后自增*/int j = 8;//int k = ++j; //等价j=j+1;k=j;int k = j++; // 等价k =j;j=j+1;System.out.println(&quot;k=&quot; + k + &quot;j=&quot; + j);//8 9<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"><br>2. 自增++<br><br>   1. 作为独立的语句使用<br><br>   2. 前++和后++都完全等价于i=i+1<br><br>   3. 作为表达式使用<br><br>      前++：++i 先自增后赋值<br><br>      后++：i++先赋值后自增<br><br>3. --，+，-，*是一个道理，完全可以类推<br><br><br><br>1. 对于除号&quot;/&quot;,它的整数除和小数除湿有区别的：整数之间做除法时，只保留整数部分，而舍去小数部分<br>2. 党对一个数取模时，可以等价a % b = a - a / b * b,这样我们可以看到取模的一个本质运算<br>3. 当自增当作一个独立语言使用时，不管是++i还是i++都是一样的，等价<br>4. 当自增当作一个表达式使用时j=++i等价?<br>5. 当自增当作一个表达式使用时j=i++等价?<br><br><br><br>1. 面试题<br><br>   1. ``` java<br>      int i = 1;//i-&gt;1<br>      i = i++;//规则使用变量(1)temp = i;(2)i=i+1;(3)i=temp;<br>      System.out.println(i);//1<br></code></pre></td></tr></table></figure></code></pre></li><li><pre><code class="java">int i = 1;//i-&gt;1i = ++i;//规则使用变量(1)i=i+1;(2)temp = i;(3)i=temp;System.out.println(i);//1<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"><br>``` JAVA<br>1) 假如还有59 天放假，问：合xx 个星期零xx 天<br>2) 定义一个变量保存华氏温度，华氏温度转换摄氏温度的公式为：5/9*(华氏温度-100),请求出华氏温度对应的摄氏温度。<br>[234.5]<br>  <br><br></code></pre></td></tr></table></figure></code></pre></li></ol></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//1.需求:</span><br><span class="hljs-comment">//假如还有59 天放假，问：合xx 个星期零xx 天</span><br><span class="hljs-comment">//2.思路分析</span><br><span class="hljs-comment">//(1) 使用int 变量days 保存天数</span><br><span class="hljs-comment">//(2) 一个星期是7 天星期数weeks： days / 7 零xx 天leftDays days % 7</span><br><span class="hljs-comment">//(3) 输出</span><br><span class="hljs-comment">//3.走代码</span><br><span class="hljs-type">int</span> <span class="hljs-variable">days</span> <span class="hljs-operator">=</span> <span class="hljs-number">25911</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">weeks</span> <span class="hljs-operator">=</span> days / <span class="hljs-number">7</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">leftDays</span> <span class="hljs-operator">=</span> days % <span class="hljs-number">7</span>;<br>System.out.println(days + <span class="hljs-string">&quot;天合&quot;</span> + weeks + <span class="hljs-string">&quot;星期零&quot;</span><br>+ leftDays + <span class="hljs-string">&quot;天&quot;</span>);<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//定义一个变量保存华氏温度，华氏温度转换摄氏温度的公式为</span><br><span class="hljs-comment">//：5/9*(华氏温度-100),请求出华氏温度对应的摄氏温度</span><br><span class="hljs-comment">//</span><br><span class="hljs-comment">//2 思路分析</span><br><span class="hljs-comment">//(1) 先定义一个double huaShi 变量保存华氏温度</span><br><span class="hljs-comment">//(2) 根据给出的公式，进行计算即可5/9*(华氏温度-100)</span><br><span class="hljs-comment">// 考虑数学公式和java 语言的特性</span><br><span class="hljs-comment">//(3) 将得到的结果保存到double sheShi</span><br><span class="hljs-comment">//3 走代码</span><br><span class="hljs-type">double</span> <span class="hljs-variable">huaShi</span> <span class="hljs-operator">=</span> <span class="hljs-number">1234.6</span>;<br><span class="hljs-type">double</span> <span class="hljs-variable">sheShi</span> <span class="hljs-operator">=</span> <span class="hljs-number">5.0</span> / <span class="hljs-number">9</span> * (huaShi - <span class="hljs-number">100</span>);<br>System.out.println(<span class="hljs-string">&quot;华氏温度&quot;</span> + huaShi<br>+ <span class="hljs-string">&quot; 对应的摄氏温度=&quot;</span> + sheShi);<br></code></pre></td></tr></table></figure><h2 id="关系运算符"><a href="#关系运算符" class="headerlink" title="关系运算符"></a>关系运算符</h2><ol><li><p>关系运算符的结果都是boolean 型，也就是要么是true，要么是false</p></li><li><p>关系表达式经常用在if 结构的条件中或循环结构的条件中</p></li></ol><p>&#x3D;&#x3D;,!&#x3D;,&lt;,&gt;&lt;&#x3D;,&gt;&#x3D;,instanceof检查是否是类的对象</p><ol><li>关系运算符的结果都是boolean 型，也就是要么是true，要么是false。</li></ol><ol start="2"><li><p>关系运算符组成的表达式，我们称为关系表达式。a &gt; b</p></li><li><p>比较运算符”&#x3D;&#x3D;”不能误写成”&#x3D;”</p></li></ol><h2 id="逻辑运算符"><a href="#逻辑运算符" class="headerlink" title="逻辑运算符"></a>逻辑运算符</h2><ol><li><p>短路与&amp;&amp; ， 短路或||，取反!</p></li><li><p>逻辑与&amp;，逻辑或|，^ 逻辑异或</p></li></ol><p> 说明逻辑运算规则：</p><ol><li><p>a&amp;b : &amp; 叫逻辑与：规则：当a 和b 同时为true ,则结果为true, 否则为false</p></li><li><p>a&amp;&amp;b : &amp;&amp; 叫短路与：规则：当a 和b 同时为true ,则结果为true,否则为false</p></li><li><p>a|b : | 叫逻辑或，规则：当a 和b ，有一个为true ,则结果为true,否则为false</p></li><li><p>a||b : || 叫短路或，规则：当a 和b ，有一个为true ,则结果为true,否则为false</p></li><li><p>!a : 叫取反，或者非运算。当a 为true, 则结果为false, 当a 为false 是，结果为true</p></li><li><p>a^b: 叫逻辑异或，当a 和b 不同时，则结果为true, 否则为false</p></li></ol><p>4.4.3&amp;&amp; 和&amp; 基本规则</p><p>名称语法特点</p><p>短路与&amp;&amp; 条件1&amp;&amp;条件2 两个条件都为true，结果为true,否则false</p><p>逻辑与&amp; 条件1&amp;条件2 两个条件都为true，结果为true,否则false</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//&amp;&amp;短路与和&amp; 案例演示</span><br><span class="hljs-type">int</span> <span class="hljs-variable">age</span> <span class="hljs-operator">=</span> <span class="hljs-number">50</span>;<br><span class="hljs-keyword">if</span>(age &gt; <span class="hljs-number">20</span> &amp;&amp; age &lt; <span class="hljs-number">90</span>) &#123;<br>  System.out.println(<span class="hljs-string">&quot;ok100&quot;</span>);<br>&#125;<br><span class="hljs-comment">//&amp;逻辑与使用</span><br><span class="hljs-keyword">if</span>(age &gt; <span class="hljs-number">20</span> &amp; age &lt; <span class="hljs-number">90</span>) &#123;<br>System.out.println(<span class="hljs-string">&quot;ok200&quot;</span>);<br>&#125;<br><span class="hljs-comment">//区别</span><br><span class="hljs-type">int</span> <span class="hljs-variable">a</span> <span class="hljs-operator">=</span> <span class="hljs-number">4</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> <span class="hljs-number">9</span>;<br><span class="hljs-comment">//对于&amp;&amp;短路与而言，如果第一个条件为false ,后面的条件不再判断</span><br><span class="hljs-comment">//对于&amp;逻辑与而言，如果第一个条件为false ,后面的条件仍然会判断</span><br><span class="hljs-keyword">if</span>(a &lt; <span class="hljs-number">1</span> &amp; ++b &lt; <span class="hljs-number">50</span>) &#123;<br>System.out.println(<span class="hljs-string">&quot;ok300&quot;</span>);<br>&#125;<br>System.out.println(<span class="hljs-string">&quot;a=&quot;</span> + a + <span class="hljs-string">&quot; b=&quot;</span> + b);<span class="hljs-comment">// 4 10</span><br></code></pre></td></tr></table></figure><ol><li><p>&amp;&amp;短路与：如果第一个条件为false，则第二个条件不会判断，最终结果为false，效率高</p></li><li><p>&amp; 逻辑与：不管第一个条件是否为false，第二个条件都要判断，效率低</p></li><li><p>开发中， 我们使用的基本是使用短路与&amp;&amp;, 效率高</p></li></ol><h2 id="或运算"><a href="#或运算" class="headerlink" title="或运算"></a>或运算</h2><p>4.4.6|| 和| 基本规则</p><p>名称语法特点</p><p>短路或|| 条件1||条件2 两个条件中只要有一个成立，结果为true,否则为false</p><p>|逻辑或条件1|条件2 只要有一个条件成立，结果为true,否则为false</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//||短路或和|逻辑或案例演示</span><br><span class="hljs-comment">//|| 规则: 两个条件中只要有一个成立，结果为true,否则为false</span><br><span class="hljs-comment">//| 规则: 两个条件中只要有一个成立，结果为true,否则为false</span><br><span class="hljs-type">int</span> <span class="hljs-variable">age</span> <span class="hljs-operator">=</span> <span class="hljs-number">50</span>;<br><span class="hljs-keyword">if</span>(age &gt; <span class="hljs-number">20</span> || age &lt; <span class="hljs-number">30</span>) &#123;<br>System.out.println(<span class="hljs-string">&quot;ok100&quot;</span>);<br>&#125;<br><span class="hljs-comment">//&amp;逻辑与使用</span><br><span class="hljs-keyword">if</span>(age &gt; <span class="hljs-number">20</span> | age &lt; <span class="hljs-number">30</span>) &#123;<br>  System.out.println(<span class="hljs-string">&quot;ok200&quot;</span>);<br>&#125;<br><span class="hljs-comment">//看看区别</span><br><span class="hljs-comment">//(1)||短路或：如果第一个条件为true，</span><br><span class="hljs-comment">//则第二个条件不会判断，最终结果为true，效率高</span><br><span class="hljs-comment">//(2)| 逻辑或：不管第一个条件是否为true，第二个条件都要判断，效率低</span><br><span class="hljs-type">int</span> <span class="hljs-variable">a</span> <span class="hljs-operator">=</span> <span class="hljs-number">4</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> <span class="hljs-number">9</span>;<br><span class="hljs-keyword">if</span>( a &gt; <span class="hljs-number">1</span> || ++b &gt; <span class="hljs-number">4</span>) &#123; <span class="hljs-comment">// 可以换成| 测试</span><br>System.out.println(<span class="hljs-string">&quot;ok300&quot;</span>);<br>&#125;<br>System.out.println(<span class="hljs-string">&quot;a=&quot;</span> + a + <span class="hljs-string">&quot; b=&quot;</span> + b); <span class="hljs-comment">//4 10</span><br></code></pre></td></tr></table></figure><p>4.4.8|| 和| 使用区别</p><ol><li><p>||短路或：如果第一个条件为true，则第二个条件不会判断，最终结果为true，效率高</p></li><li><p>| 逻辑或：不管第一个条件是否为true，第二个条件都要判断，效率低</p></li><li><p>开发中，我们基本使用||</p></li></ol><p>4.4.9! 取反基本规则</p><p>名称语法特点</p><p>! 非（取反） !条件如果条件本身成立，结果为false，否则为true</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//! 操作是取反T-&gt;F , F -&gt; T</span><br>System.out.println(<span class="hljs-number">60</span> &gt; <span class="hljs-number">20</span>); <span class="hljs-comment">//T</span><br>System.out.println(!(<span class="hljs-number">60</span> &gt; <span class="hljs-number">20</span>)); <span class="hljs-comment">//F</span><br><span class="hljs-comment">//a^b: 叫逻辑异或，当a 和b 不同时，则结果为true, 否则为false</span><br><span class="hljs-type">boolean</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> (<span class="hljs-number">10</span> &gt; <span class="hljs-number">1</span>) ^ ( <span class="hljs-number">3</span> &gt; <span class="hljs-number">5</span>);<br>System.out.println(<span class="hljs-string">&quot;b=&quot;</span> + b);<span class="hljs-comment">//T</span><br></code></pre></td></tr></table></figure><p>a^b: 叫逻辑异或，当a 和b 不同时，则结果为true, 否则为false</p><p>^逻辑异或，System.out.println( (4 &lt; 1) ^ (6 &gt; 3) ); &#x2F;&#x2F; ?</p><h2 id="练习"><a href="#练习" class="headerlink" title="练习"></a>练习</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">x</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">y</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br><span class="hljs-keyword">if</span> (x++==<span class="hljs-number">6</span> &amp; ++y == <span class="hljs-number">6</span>)&#123;<span class="hljs-comment">//false and true</span><br>  x = <span class="hljs-number">11</span>;<br>&#125;<br>System.out.println(<span class="hljs-string">&quot;x=&quot;</span>+x+,<span class="hljs-string">&quot;y=&quot;</span>+y)<span class="hljs-comment">// 6,6</span><br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">x</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">y</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br><span class="hljs-keyword">if</span> (x++==<span class="hljs-number">6</span> &amp;&amp; ++y == <span class="hljs-number">6</span>)&#123;<span class="hljs-comment">//false and true</span><br>  x = <span class="hljs-number">11</span>;<br>&#125;<br>System.out.println(<span class="hljs-string">&quot;x=&quot;</span>+x+,<span class="hljs-string">&quot;y=&quot;</span>+y)<span class="hljs-comment">// 6,5</span><br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">x</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">y</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br><span class="hljs-keyword">if</span> (x++==<span class="hljs-number">5</span> || ++y == <span class="hljs-number">5</span>)&#123;<span class="hljs-comment">//true and false</span><br>  x = <span class="hljs-number">11</span>;<br>&#125;<br>System.out.println(<span class="hljs-string">&quot;x=&quot;</span>+x+,<span class="hljs-string">&quot;y=&quot;</span>+y)<span class="hljs-comment">// 11,6</span><br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">x</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">y</span> <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br><span class="hljs-keyword">if</span> (x++==<span class="hljs-number">5</span> | ++y == <span class="hljs-number">5</span>)&#123;<span class="hljs-comment">//true and false</span><br>  x = <span class="hljs-number">11</span>;<br>&#125;<br>System.out.println(<span class="hljs-string">&quot;x=&quot;</span>+x+,<span class="hljs-string">&quot;y=&quot;</span>+y)<span class="hljs-comment">// 11,5</span><br></code></pre></td></tr></table></figure><h2 id="赋值运算符"><a href="#赋值运算符" class="headerlink" title="赋值运算符"></a>赋值运算符</h2><p>分类</p><ul><li>基本赋值运算符 int a &#x3D; 10;</li><li>复合运算符<ul><li>+&#x3D;，-&#x3D;，*&#x3D;，&#x2F;&#x3D;，%&#x3D;，重点讲+&#x3D;</li></ul></li></ul><h2 id="赋值运算符演示"><a href="#赋值运算符演示" class="headerlink" title="赋值运算符演示"></a>赋值运算符演示</h2><ol><li>运算顺序从右向左</li><li>赋值运算符的左边，只能是变量，右边可以是变量，表达式，常量值</li><li>复合赋值运算符等价于 a+&#x3D;3.   a&#x3D;a+3</li><li>复合运算符会进行类型转换 byte b &#x3D; 2;b +&#x3D; 3    b&#x3D;(byte)(b+2) </li><li>b++ b&#x3D;(byte)(b+1)</li></ol><h2 id="三元运算符"><a href="#三元运算符" class="headerlink" title="三元运算符"></a>三元运算符</h2><p>语法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java">条件表达式?表达式<span class="hljs-number">1</span>:表达式<span class="hljs-number">2</span>;<br></code></pre></td></tr></table></figure><p>运算规则：</p><p>1.如果条件表达式为true，运算后的结果是表达式1；</p><p>2.如果条件表达式为false，运算后的结果是表达式2；</p><h2 id="三元运算符细节"><a href="#三元运算符细节" class="headerlink" title="三元运算符细节"></a>三元运算符细节</h2><ol><li>表达式1和表达式2要诶可以赋值给接收变量的类型（或可以自动转换）</li><li>三元运算符可以转换成if-else语句</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">a</span> <span class="hljs-operator">=</span> <span class="hljs-number">3</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> <span class="hljs-number">8</span>;<br><span class="hljs-type">int</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> a &gt;b ? (<span class="hljs-type">int</span>)<span class="hljs-number">1.1</span> : (<span class="hljs-type">int</span>)<span class="hljs-number">3.4</span>;<br><span class="hljs-type">double</span> <span class="hljs-variable">d</span> <span class="hljs-operator">=</span> a &gt; b ? a:b + <span class="hljs-number">3</span>;<br></code></pre></td></tr></table></figure><h2 id="运算符的优先级"><a href="#运算符的优先级" class="headerlink" title="运算符的优先级"></a>运算符的优先级</h2><h2 id="标识符的命名规则和规范"><a href="#标识符的命名规则和规范" class="headerlink" title="标识符的命名规则和规范"></a>标识符的命名规则和规范</h2><ol><li>java对各种变量、方法和类等命名时使用字符序列称为标识符</li><li>凡是自己可以起名字的地方都叫标识符</li></ol><p>标识符命名规则</p><ol><li>由26个英文字母大小写，0～9，_或$组成</li><li>数字不可以开头</li><li>不可以使用关键字和保留字，但能包含关键字和保留字</li><li>java中严格区分大小写，长度无限制</li><li>标识符不能包含空格</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><p>被java语言赋予特殊含义，专门用途哦的字符串</p><p>特点：关键字中所有字母都为小写</p><p>保留字：现在java版本尚未使用，但以后版本可能会作为关键字使用，自己命名时应避免使用这些保留字</p><h2 id="键盘输入语句"><a href="#键盘输入语句" class="headerlink" title="键盘输入语句"></a>键盘输入语句</h2><p>扫描器：Scanner</p><p>步骤</p><ol><li>导入该类所在包 java.util.*</li><li>创建类对象（声明变量）</li><li>接收用户的输入，使用相关的方法</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">Scanner</span> <span class="hljs-variable">scanner</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Scanner</span>(System.in);<br><span class="hljs-type">String</span> <span class="hljs-variable">name</span> <span class="hljs-operator">=</span> scanner.next();<br><span class="hljs-type">int</span> <span class="hljs-variable">age</span> <span class="hljs-operator">=</span> scanner.nextInt();<br><span class="hljs-type">double</span> <span class="hljs-variable">sal</span> <span class="hljs-operator">=</span> scanner.nextDouble();<br></code></pre></td></tr></table></figure><h2 id="进制"><a href="#进制" class="headerlink" title="进制"></a>进制</h2><ol><li>二进制以0b或0B开头</li><li>十进制</li><li>八进制以0开头</li><li>十六进制一0x或0X开头，A-F不区分大小写</li></ol><h2 id="进制转换"><a href="#进制转换" class="headerlink" title="进制转换"></a>进制转换</h2><p>二转八、十六</p><p>三位一组，4位一组</p><h2 id="原码、反码、补码"><a href="#原码、反码、补码" class="headerlink" title="原码、反码、补码"></a>原码、反码、补码</h2><p>对于有符号的而言</p><ol><li>二进制的最高位是符号位：0表示正数，1表示负数（0-&gt;0,1-&gt;-)</li><li>正数的原码，反码，补码都一样（三合一）</li><li>负数的反码&#x3D;它原码符号位不变，其他位取反(0-&gt;1,1-&gt;0)</li><li>负数的补码&#x3D;它的反码+1，负数的反码&#x3D;负数的补码-1</li><li>0的反码，补码都是0</li><li>java没有无符号数，换言之，java中的数都是由符号的</li><li>在计算机运算的时候，都是以<strong>补码</strong>的方式来运算的</li><li>党我们看运算结果的时候，要看它的原码</li></ol><h2 id="位运算"><a href="#位运算" class="headerlink" title="位运算"></a>位运算</h2><p>java中有8个位运算符（&amp;,|,^,~,&gt;&gt;,&lt;&lt;,&gt;&gt;&gt;)</p><p>分别是按位与&amp;，按位或|，按位异或^按位取反~，他们的运算规则是：</p><p>按位与&amp;:两位全为1，结果为1，否则为0</p><p>按位或｜：两位有一个为1，结果为1，否则为0</p><p>按位异或^：两个一个为0一个为1，结果为1，否则为0</p><p>按位取反～：0&gt;1,1&gt;0</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java">System.out.println(~-<span class="hljs-number">2</span>); <span class="hljs-comment">//1</span><br><span class="hljs-comment">//-2原码  10000000 00000000 00000000 00000010</span><br><span class="hljs-comment">//-2的反码 11111111 11111111 11111111 11111101</span><br><span class="hljs-comment">//-2的补码 00000000 00000000 00000000 00000001</span><br><span class="hljs-comment">//运算后的原码 00000000 00000000 00000000 00000001</span><br>System.out.println(~<span class="hljs-number">2</span>); <span class="hljs-comment">// -3</span><br><span class="hljs-comment">//2原码  00000000 00000000 00000000 00000010</span><br><span class="hljs-comment">//~2的操作 11111111 11111111 11111111 11111101</span><br><span class="hljs-comment">//运算后的反码 11111111 11111111 11111111 11111100</span><br><span class="hljs-comment">//运算后的原码 10000000 00000000 00000000 00000011</span><br></code></pre></td></tr></table></figure><p>3个位运算符&gt;&gt;、&lt;&lt;和&gt;&gt;&gt;</p><ol><li>算术右移&gt;&gt;：低位溢出，符号位不变，并用符号位补溢出的高位</li><li>算术左移&lt;&lt;：符号位不变，低位补0</li><li>&gt;&gt;&gt;逻辑右移也叫无符号右移，运算规则是：低位溢出，高位补0</li><li>特变说明：没有&lt;&lt;&lt;</li></ol><p>向右边移动一位相当于除以2</p><p>向左移动一位相当于乘以2</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">-<span class="hljs-number">10.5</span>%<span class="hljs-number">3</span>=-<span class="hljs-number">1.5</span><br><br><span class="hljs-comment">//a%b当a是小数时，公式=a-(int)a/b*b</span><br><span class="hljs-comment">//有小数参与运算得到的是近似值</span><br>  <br><span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">66</span>;<br>System.out.println(++i+i)<span class="hljs-comment">//执行i=i+1=&gt;i+67=&gt;134</span><br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">int</span> <span class="hljs-variable">num1</span> <span class="hljs-operator">=</span> (<span class="hljs-type">int</span>)<span class="hljs-string">&quot;18&quot;</span>;<span class="hljs-comment">//错误 Interger.parseInt(&quot;18&quot;)</span><br><span class="hljs-type">int</span> <span class="hljs-variable">num2</span> <span class="hljs-operator">=</span> <span class="hljs-number">18.0</span>; <span class="hljs-comment">//错误double-&gt;int</span><br><span class="hljs-type">double</span> <span class="hljs-variable">num3</span> <span class="hljs-operator">=</span> <span class="hljs-number">3d</span>; <span class="hljs-comment">// ok.d代表double</span><br><span class="hljs-type">double</span> <span class="hljs-variable">num4</span> <span class="hljs-operator">=</span> <span class="hljs-number">8</span>;<span class="hljs-comment">//ok int-&gt;double</span><br><span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">48</span>; <span class="hljs-type">char</span> <span class="hljs-variable">ch</span> <span class="hljs-operator">=</span> i+<span class="hljs-number">1</span>;<span class="hljs-comment">//错误int -&gt;char</span><br><span class="hljs-type">byte</span> <span class="hljs-variable">b</span> <span class="hljs-operator">=</span> <span class="hljs-number">19</span>;<span class="hljs-type">short</span> <span class="hljs-variable">s</span> <span class="hljs-operator">=</span> b+<span class="hljs-number">2</span>;<span class="hljs-comment">//错误int -&gt; short</span><br><br></code></pre></td></tr></table></figure><h2 id="控制结构"><a href="#控制结构" class="headerlink" title="控制结构"></a>控制结构</h2><h2 id="顺序控制"><a href="#顺序控制" class="headerlink" title="顺序控制"></a>顺序控制</h2><p>查询从上到下逐行执行，中间没有任何判断和跳转</p><h2 id="分支控制（if-else"><a href="#分支控制（if-else" class="headerlink" title="分支控制（if-else)"></a>分支控制（if-else)</h2><p><strong>单分支</strong></p><p>基本语法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">if</span> (条件表达式)&#123;<br>  执行代码块;<br>&#125;<br></code></pre></td></tr></table></figure><p>当条件表达式为true时，就会执行{}中的代码，如果为false就不执行</p><p>如果{}中只有一条语句，则可以不用{}但建议写上</p><p><strong>双分支</strong></p><p>基本语法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">if</span> (条件表达式)&#123;<br>  执行代码<span class="hljs-number">1</span>;<br>&#125;<span class="hljs-keyword">else</span>&#123;<br>  执行代码<span class="hljs-number">2</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>多分支</strong></p><p>基本语法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">if</span> (条件表达式)&#123;<br>  代码块<span class="hljs-number">1</span>;<br>&#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (条件表达式<span class="hljs-number">2</span>) &#123;<br>  代码块<span class="hljs-number">2</span>；<br>&#125; <span class="hljs-keyword">else</span> &#123;<br>  代码块<span class="hljs-number">3</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>只能有一个执行入口</p><p>特别说明：多分支可以没有else,如果所有条件都不成立，则一个执行入口都没有</p><p>注意判断范围</p><h2 id="嵌套分支"><a href="#嵌套分支" class="headerlink" title="嵌套分支"></a>嵌套分支</h2><p>规范：不要超过3层</p><h2 id="Switch分支结构"><a href="#Switch分支结构" class="headerlink" title="Switch分支结构"></a>Switch分支结构</h2><ol><li>switch关键字，表示分支</li><li>表达式对应一个值</li><li>case常量1:当表达式的值等于常量1，就执行语句块1</li><li>break：表示退出swtich</li><li>如果和case常量1匹配，就执行语句块1，如果没有匹配就继续匹配2</li><li>如果一个都没匹配上就执行default</li></ol><p>没break会穿透</p><h2 id="细节讨论"><a href="#细节讨论" class="headerlink" title="细节讨论"></a>细节讨论</h2><ul><li><p>表达式数据类型，应和case后的常量类型一致，或者是可以自动转成可以互相比较多类型</p></li><li><p>switch（表达式）中表达式的返回值必须是（byte,short,int,char,enum,String）</p><ul><li><pre><code class="java">double c = 1.1;switch(c)&#123;//错误    case 1.1:        System.out.println(&quot;ok&quot;);        break;&#125;<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"><br>* case子句中的值必须是常量，而不能是变量<br>* default子句是可选的，当没有匹配的case时，执行default<br>* break语句用来在执行完一个case分支后使程序跳出switch语句块；如果没有写break，程序会顺序执行到switch结尾<br><br>## switch与if-else的区别<br><br>* 如果判断的具体数值不多而且符合byte、short、int、char、enum、String这6种类型。虽然两个语句都可以使用，建议使用switch语句<br>* 其他情况：对区间判断，对结果为boolean类型判断，使用if，if的使用范围更广 <br><br>## for循环控制<br><br>循环操作只有一条语句可以省略&#123;&#125;但不建议省略<br><br>## 细节<br><br>1. 循环条件时返回一个布尔值的表达式<br><br>2. for（;循环判断条件;）中的初始化和变量迭代可以写到其他地方，但两边的分号不能省略<br><br>3. 循环初始值可以有多条初始化语句，但要求类型一样，并且中间用**逗号**隔开，循环变量迭代也可以有多条变量迭代语句，中间用逗号隔开<br><br>4. ```<br>   int count = 3;<br>   for (int i = 0,j = 0; i &lt; count;i++, j+=2)&#123;<br>     System.out.println(i+&quot;&quot;+j);<br>   &#125;<br></code></pre></td></tr></table></figure></code></pre></li></ul></li></ul><h2 id="while循环控制"><a href="#while循环控制" class="headerlink" title="while循环控制"></a>while循环控制</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">while(循环条件)&#123;<br>  循环体;<br>  循环变量迭代;<br>&#125;<br></code></pre></td></tr></table></figure><p>while也有四要素</p><p>细节</p><p>循环条件返回的事一个布尔值</p><p>执行时先判断后执行</p><h2 id="do-while"><a href="#do-while" class="headerlink" title="do while"></a>do while</h2><h2 id="134-多重循环"><a href="#134-多重循环" class="headerlink" title="134 多重循环"></a>134 多重循环</h2><p>最好不要超过3次</p><p>设置外层循环次数为m次，内层为n次，则内层循环提实际上需要执行m*n次</p><h2 id="break"><a href="#break" class="headerlink" title="break"></a>break</h2><p>当某个条件满足时，终止小循环</p><p>细节</p><p>break语句出现在多层嵌套的语句块中时，可以通过标签指明要终止的时那一层的语句块</p><p>标签的基本使用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java">lable1:<br><span class="hljs-keyword">for</span>(<span class="hljs-type">int</span> <span class="hljs-variable">j</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; j &lt; <span class="hljs-number">4</span>;j++)&#123;<br>lable2:<br>  <span class="hljs-keyword">for</span>(<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;i&lt;<span class="hljs-number">10</span>;i++)&#123;<br>    <span class="hljs-keyword">if</span>(i==<span class="hljs-number">2</span>)&#123;<span class="hljs-keyword">break</span> lable1;&#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><ol><li>break语句可以指定退出那一层</li><li>lable1时标签，由程序员决定</li><li>break后指定到那个lable就退出到那里</li><li>在实际的开发中，尽量不要使用标签</li><li>如果没有指定break，默认退出最近的循环体</li></ol><h2 id="144-continue"><a href="#144-continue" class="headerlink" title="144 continue"></a>144 continue</h2><p>用于结束本次循环，继续执行下一次循环</p><p>continue语句出现在多层嵌套语句体中时，可以通过标签指明要逃过的时那一层循环，这个和签名的标签的使用的规则一样</p><p>跳转控制语句 return</p><p>用来方法中，跳出所在方法，如果在main方阵中则是退出程序</p><h2 id="158-数组"><a href="#158-数组" class="headerlink" title="158 数组"></a>158 数组</h2><p>第一种用法</p><p>定义</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-type">double</span>[] a = <span class="hljs-keyword">new</span> <span class="hljs-title class_">double</span>[<span class="hljs-number">5</span>];<br></code></pre></td></tr></table></figure><p>数组的引用</p><p>数组名[索引]</p><p>第二种用法</p><p>先声明</p><p>Double[] a;</p><p>创建数组</p><p>数组名&#x3D;new double[5];</p><p>第三种用法</p><p>数据类型[] 数组名&#x3D;{1,2,3….}</p><p>这个用法相当于int[] a &#x3D; new int[9];</p><p>a[0]&#x3D;1;a[1]&#x3D;2….</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ol><li>数组时多个相同类型数据的组合，实现对这个数据的统一管理</li><li>数组重点元素可以时任何数据类型，包括基本类型和引用类型，但是不能混用</li><li>数组创建后，如果没有赋值，有默认值int 0, short 0, byte 0,long 0,float 0.0,double 0.0,char \u0000boolean false, String null</li><li>使用数组的步骤，1.声明数组并开辟空间，2给数组个元素赋值，3.使用数组</li><li>数组的下表从0开始</li><li>数组下表必须在指定范围内使用，否则报下表越界异常</li><li>数组属引用来性，数组型数据时对象</li><li></li></ol><h2 id="数组的扩容，索减、排序、查找"><a href="#数组的扩容，索减、排序、查找" class="headerlink" title="数组的扩容，索减、排序、查找"></a>数组的扩容，索减、排序、查找</h2><p>183-273</p><h2 id="包原理"><a href="#包原理" class="headerlink" title="包原理"></a>包原理</h2><p>三大作用：</p><ol><li>区分相同名字的类</li><li>当类很多时，可以很好的管理类</li><li>控制访问范围</li></ol><p>包语法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.hspedu;<br></code></pre></td></tr></table></figure><h2 id="包的本质分析"><a href="#包的本质分析" class="headerlink" title="包的本质分析"></a>包的本质分析</h2><p>实际上包的本质就是创建不同的文件夹来保存类文件</p><h2 id="包的命名"><a href="#包的命名" class="headerlink" title="包的命名"></a>包的命名</h2><p>命名规则：</p><p>智能包含数字、字母、下划线、小圆点、但不能用一数字开头，不能时关键字或保留字</p><p>命名规范：</p><p>一般是小写字母+小圆点一般是</p><p>com.公司名.项目名.业务模块名</p><h2 id="常用的包"><a href="#常用的包" class="headerlink" title="常用的包"></a>常用的包</h2><p>Java.lang.*&#x2F;&#x2F;lang包是基本包是默认引入包</p><p>java.util.*&#x2F;&#x2F;util包是系统提供的工具包，工具类，使用Scanner</p><p>java.net.*&#x2F;&#x2F;网络包，网络开发</p><p>java.awt.*&#x2F;&#x2F;是做java的界面开发</p><h2 id="如何导入包"><a href="#如何导入包" class="headerlink" title="如何导入包"></a>如何导入包</h2><p>语法</p><p>Import 包;</p><h2 id="访问修饰符"><a href="#访问修饰符" class="headerlink" title="访问修饰符"></a>访问修饰符</h2><p>用于控制方法和属性（的访问权限</p><table><thead><tr><th>访问级别</th><th>访问控制修饰符</th><th>同类</th><th>同包</th><th>子类</th><th>不同包</th></tr></thead><tbody><tr><td>公开</td><td>public</td><td>1</td><td>1</td><td>1</td><td>1</td></tr><tr><td>受保护</td><td>protected</td><td>1</td><td>1</td><td>1</td><td>0</td></tr><tr><td>默认</td><td>没有修饰符</td><td>1</td><td>1</td><td>0</td><td>0</td></tr><tr><td>私有</td><td>private</td><td>1</td><td>0</td><td>0</td><td>0</td></tr></tbody></table><p>修饰符可以用来修饰类中的属性。成员方法以及类</p><p>只有默认的和public才能修饰类，并且尊顺上述访问权限的特点</p><p>成员方法的访问规则和属性完全一样</p><h2 id="封装"><a href="#封装" class="headerlink" title="封装"></a>封装</h2><p>就是吧抽象出来的数据和对数据的操作封装在一起，数据被保护在内部，查询的其他部分只有通过授权的操作，才能对数据操作</p><p>好处：</p><ol><li>隐藏实现的细节</li><li>可以对数据进行验证，保证安全合理</li></ol><h2 id="封装的步骤"><a href="#封装的步骤" class="headerlink" title="封装的步骤"></a>封装的步骤</h2><ol><li>将属性私有化private</li><li>提供一个公共的set方法，用于对属性判断并赋值</li><li>提供一个get方法，用于获取属性的值</li></ol><h2 id="构造器与set相结合"><a href="#构造器与set相结合" class="headerlink" title="构造器与set相结合"></a>构造器与set相结合</h2><p>如果直接用构造器指定属性</p><p>This.set</p><h2 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h2><p>继承可以解决代码复用，当类存在相同的属性和方法时可以从这些类中抽象出父类，在父类中定义这些相同的属性和方法，所有的子类不需要重新定义这些属性和方法，只需要通过extends来声明继承父类即可</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java">class 子类 extends 父类&#123;<br>  <br>&#125;<br></code></pre></td></tr></table></figure><ol><li>子类就会自动拥有父类的属性和方法</li><li>父类又叫超类和基类</li><li>子类又叫派生类</li></ol><h2 id="继承细节1"><a href="#继承细节1" class="headerlink" title="继承细节1"></a>继承细节1</h2><ol><li><p>代码的复用性提高了</p></li><li><p>代码的扩展性和维护性提高了</p></li><li><p>子类继承了所有的属性和方法，但是私有属性和方法不能在子类直接方法能间接访问，要通过公共的方法去访问（get，set）</p></li><li><p>子类必须掉用父类的狗朝气，完成父类的初始化</p></li><li><p>当创建子类对象时，不管使用子类的哪个构造器，默认情况下总会去掉用父类的无参构造器，如果父类没有提供无参构造器去，则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化规则，否则，编译不会通过</p></li></ol><h2 id="290"><a href="#290" class="headerlink" title="290"></a>290</h2><p>子类必须调用父类的构造器，完成父类的初始化</p><p>当创建子类对象时，不管使用子类的那个构造器，默认情况下总会去掉用父类的无参构造器，如果父类没有提供无参构造器，则必须在子类的构在器中用super去指定使用父类的那个构造器完成对父类的初始化工作，否则，编译不会通过</p><p>如果希望指定器掉用父类的某个构造器，则显示的调用一下:super（c参数列表)</p><p>super在使用时，需要放在构造器第一行</p><p>super()和this()都只能放在构造器第一行，因此这两个方法不能共存在一个构造器</p><p>292</p><p>java所有类都死object类的子类</p><p>父类构造器的调用不限于直接父类 将一致往上追溯到object类</p>]]></content>
    
  </entry>
  
  <entry>
    <title>JavaWeb</title>
    <link href="https://alan.org.cn/posts/2023615-JavaWeb.html"/>
    <id>https://alan.org.cn/posts/2023615-JavaWeb.html</id>
    <published>2023-04-17T08:38:00.000Z</published>
    <updated>2026-06-04T04:10:49.244Z</updated>
    
    
    
    <category term="技术分享" scheme="https://alan.org.cn/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;简单请求参数&quot;&gt;&lt;a href=&quot;#简单请求参数&quot; class=&quot;headerlink&quot; title=&quot;简单请求参数&quot;&gt;&lt;/a&gt;简单请求参数&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;http://localhost:8080/xxx?name=1&amp;age=2&quot;&gt;http:</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="简单请求参数"><a href="#简单请求参数" class="headerlink" title="简单请求参数"></a>简单请求参数</h2><p><a href="http://localhost:8080/xxx?name=1&age=2">http://localhost:8080/xxx?name=1&amp;age=2</a></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RequestMapping(&quot;/simpleParam&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">simpleParam</span><span class="hljs-params">(HttpServletRequest request)</span>&#123;<br>    <span class="hljs-type">String</span> <span class="hljs-variable">name</span> <span class="hljs-operator">=</span> request.getParameter(<span class="hljs-string">&quot;name&quot;</span>);<br>    <span class="hljs-type">String</span> <span class="hljs-variable">age</span> <span class="hljs-operator">=</span> request.getParameter(<span class="hljs-string">&quot;age&quot;</span>);<br>    <span class="hljs-keyword">return</span> name+age;<br>&#125;<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RequestMapping(&quot;/simpleParam&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">simpleParam</span><span class="hljs-params">(String name, Integer age)</span>&#123;<br><br>    <span class="hljs-keyword">return</span> name+age;<br>&#125;<br></code></pre></td></tr></table></figure><p>如果参数名不匹配，则使用@RequestParam建立映射</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RequestMapping(&quot;/simpleParam&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">simpleParam</span><span class="hljs-params">(<span class="hljs-meta">@RequestParam(name=&quot;name&quot;,required = false)</span>String username, Integer age)</span>&#123;<br><br>    <span class="hljs-keyword">return</span> username+age;<br>&#125;<br></code></pre></td></tr></table></figure><p>将参数封装到实体类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RequestMapping(&quot;/simplePojo&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">simplePojo</span><span class="hljs-params">(User user)</span>&#123;<br><br>    <span class="hljs-keyword">return</span> user.getName() +user.getAge();<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="接收数组参数"><a href="#接收数组参数" class="headerlink" title="接收数组参数"></a>接收数组参数</h2><p><a href="http://localhost:8080/xxx?hobby=1&hobby=2&hobby=3">http://localhost:8080/xxx?hobby=1&amp;hobby=2&amp;hobby=3</a></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RequestMapping(&quot;/arrayParam&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">arrayParam</span><span class="hljs-params">(String[] hobby)</span>&#123;<br><br>    System.out.println(Arrays.toString(hobby));<br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;ok&quot;</span>;<br>&#125;<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RequestMapping(&quot;/arrayParam2&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">arrayParam2</span><span class="hljs-params">(<span class="hljs-meta">@RequestParam</span> List&lt;String&gt; hobby)</span>&#123;<br><br>    System.out.println(hobby);<br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;ok&quot;</span>;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="日期参数"><a href="#日期参数" class="headerlink" title="日期参数"></a>日期参数</h2><p><a href="http://localhost:8080/xxx?updateTime-2022-12-12">http://localhost:8080/xxx?updateTime-2022-12-12</a> 10:05:45</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RequestMapping(&quot;/arrayParam3&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">arrayParam3</span><span class="hljs-params">(<span class="hljs-meta">@DateTimeFormat(pattern=&quot;yyyy-MM-dd HH:mm:ss&quot;)</span>LocalDateTime updateTime)</span>&#123;<br>    System.out.println(updateTime);<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;ok&quot;</span>;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="Json参数"><a href="#Json参数" class="headerlink" title="Json参数"></a>Json参数</h2><p>json数据键名与形参属姓名相同，定义pojo类型即可接收参数，需要使用@RequesBody标示</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-string">&quot;Tom&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;age&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-number">10</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;address&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">&#123;</span><br>        <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-string">&quot;shanghai&quot;</span><br>    <span class="hljs-punctuation">&#125;</span><br><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RequestMapping(&quot;/arrayParam4&quot;)</span><br><span class="hljs-keyword">public</span> String <span class="hljs-title function_">arrayParam4</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> User user)</span>&#123;<br>    System.out.println(user);<br><br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&quot;ok&quot;</span>;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="路径参数"><a href="#路径参数" class="headerlink" title="路径参数"></a>路径参数</h2><p>通过请求URL直接换地参数用{}来识别路径参数，需要使用@PathVariable来获取路径参数</p><p><a href="http://localhost:8080/arrayParam5/1">http://localhost:8080/arrayParam5/1</a></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">@RequestMapping(&quot;/arrayParam5/&#123;id&#125;&quot;)<br>public String arrayParam5(@PathVariable Integer id)&#123;<br>    System.out.println(id);<br><br>    return &quot;ok&quot;;<br>&#125;<br></code></pre></td></tr></table></figure><p><a href="http://localhost:8080/arrayParam5/1/2">http://localhost:8080/arrayParam5/1/2</a></p><p>@RequestMapping(“&#x2F;arrayParam5&#x2F;{id}&#x2F;{name}”)<br>public String arrayParam5(@PathVariable Integer id,@PathVariable Integer name){<br>    System.out.println(id+name);</p><pre><code>return &quot;ok&quot;;</code></pre><p>}</p><h2 id="相应数据"><a href="#相应数据" class="headerlink" title="相应数据"></a>相应数据</h2><p>都来源于注解@ResponseBody只不过@RestController中已经包含</p><p>返回字符串就直接但会</p><p>实体类对象会转json</p><p>数组依然是数组</p><p>统一都响应结果</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">public class Result&#123;<br>  //相应码，1代表成果，0代表失败<br>  private Integetr code;<br>  //提示信息<br>  private String msg;<br>  //返回数据<br>  private Object data;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="分层解耦"><a href="#分层解耦" class="headerlink" title="分层解耦"></a>分层解耦</h2><h3 id="分层"><a href="#分层" class="headerlink" title="分层"></a>分层</h3><p>三层架构：数据访问(Dao)、逻辑处理(Service)、接收请求响应数据(Controller)</p><ul><li>controller：控制层，，接收前端发送到请求，对请求进行处理，并响应数据。</li><li>service：业务逻辑层，处理具体的业务逻辑</li><li>dao：数据访问层（Data Access Object）（持久层），负责数据访问操作，包括增删改查</li></ul><h3 id="解藕"><a href="#解藕" class="headerlink" title="解藕"></a>解藕</h3><ul><li>内聚：软件中各个功能模块内部的功能联系</li><li>耦合：衡量软件中各个层&#x2F;模块之间的依赖、关联的层度。</li><li>软件设计原则：高内聚低耦合</li></ul><p>控制反转：IOC，对象的创建控制权由程序自身转移到外部（容器）</p><p>依赖注入：DI，容器为应用程序提供运行时，所依赖的资源</p><p>Bean对象：IOC容器中传教、管理的对象</p><h2 id="IOC-DI入门"><a href="#IOC-DI入门" class="headerlink" title="IOC&amp;DI入门"></a>IOC&amp;DI入门</h2><p>控制反转</p><p>@Component  将当前类交给IOC容器，成为IOC容器中的bean</p><p>依赖注入</p><p>@Autowired  运行时，IOC容器会提供该类型的bean对象，并复制给该变量</p><h2 id="IOC详解"><a href="#IOC详解" class="headerlink" title="IOC详解"></a>IOC详解</h2><table><thead><tr><th>@Componet</th><th>声明bean的基础注解</th></tr></thead><tbody><tr><td>@Controller</td><td>标注在控制器类上</td></tr><tr><td>@Service</td><td>标注在业务类上</td></tr><tr><td>@Repository</td><td>标注在数据访问类上（由于mybiatis整合，用到少）</td></tr></tbody></table><p>Bean默认名字为首字母小写的类名</p><p>如果需要改名字可以在注解后面加括号设置</p><p>@Repository(“daoA”)</p><p>@SpringBootApplication默认包含扫描器范围为当前包和子包</p><p>@ComponentScan为扫描</p><p>@ComponentScan({“dao”,”com.alan”})指定扫描那个包（不推荐）</p><h2 id="DI详解"><a href="#DI详解" class="headerlink" title="DI详解"></a>DI详解</h2><p>因为默认是按类型进行的，如果存在多个相同类型的bean会报错</p><p>通常使用一下几种方案来解决</p><ul><li>@Primary 优先级</li><li>@Qualifier 指定需要注入对应名字的bean</li><li>@Resource 指定需要注入对应名字的bean，且不需要autowired注解了</li></ul><h2 id="Mysql"><a href="#Mysql" class="headerlink" title="Mysql"></a>Mysql</h2><p>关系型数据库（RDBMS）建立在关系模型基础上，由多张互相连接的二维表组成的数据库</p><h3 id="Sql"><a href="#Sql" class="headerlink" title="Sql"></a>Sql</h3><ul><li>可以单行或多行书写，以分号结尾</li><li>可以使用空格&#x2F;缩进来增强可读性</li><li>不区分大小写</li><li>注释<ul><li>单行：–</li><li>多行&#x2F;* *&#x2F;</li></ul></li></ul><p>分类</p><table><thead><tr><th>分类</th><th>说明</th></tr></thead><tbody><tr><td>DDL</td><td>数据定义语言，定义数据库对象（数据库，表，字段）</td></tr><tr><td>DMl</td><td>数据操作语言，对数据库表中的数据CUDA</td></tr><tr><td>DQL</td><td>数据查询语言，用来查询数据库表中的记录</td></tr><tr><td>DCL</td><td>数据库控制语言，用来创建数据库用户、控制数据库的访问权限</td></tr></tbody></table><p><strong>需求+原型</strong></p><p><strong>设计</strong></p><p>概要设计、详细设计、接口设计、数据库设计</p><p>Java程序</p><p>优化-&gt;数据库优化</p><h2 id="DDL"><a href="#DDL" class="headerlink" title="DDL"></a>DDL</h2><p>**数据库 **</p><p>查询</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">查询所有数据库：show databases;<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">查询当前数据库: selsect database();<br></code></pre></td></tr></table></figure><p>使用</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">使用数据库：use 数据库名；<br></code></pre></td></tr></table></figure><p>创建</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">创建数据库：create database [if not exists] 数据库名;<br></code></pre></td></tr></table></figure><p>删除</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">删除数据库: drop database [if exists] 数据库名;<br></code></pre></td></tr></table></figure><p><strong>表</strong></p><p>[]可选</p><p>创建</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">create table</span> 表名(<br>  字段<span class="hljs-number">1</span> 字段类型 [约束] [comment 字段<span class="hljs-number">1</span>注释],<br>  ......<br>  字段n 字段类型 [约束] [comment 字段n注释],<br>)[comment 表注释]<br></code></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">create table</span> tb_user(<br>    id <span class="hljs-type">int</span> <span class="hljs-keyword">primary key</span> auto_increment comment <span class="hljs-string">&#x27;ID,唯一标示&#x27;</span>,<br>    username <span class="hljs-type">varchar</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">not null</span> <span class="hljs-keyword">unique</span> comment <span class="hljs-string">&#x27;用户名&#x27;</span>,<br>    name <span class="hljs-type">varchar</span>(<span class="hljs-number">10</span>) <span class="hljs-keyword">not null</span> comment <span class="hljs-string">&#x27;姓名&#x27;</span>,<br>    age <span class="hljs-type">int</span> comment <span class="hljs-string">&#x27;年龄&#x27;</span>,<br>    gender <span class="hljs-type">char</span>(<span class="hljs-number">1</span>) <span class="hljs-keyword">default</span> <span class="hljs-string">&#x27;男&#x27;</span> comment <span class="hljs-string">&#x27;性别&#x27;</span><br>    ) comment <span class="hljs-string">&#x27;用户表&#x27;</span>;<br></code></pre></td></tr></table></figure><p><strong>约束</strong></p><p>概念：约束是作用于表中字段上的规则，用于限制存储在表中的数据</p><p>目的：保证数据库中数据的正确性，有效性，完整性</p><table><thead><tr><th>约束</th><th>描述</th><th>关键字</th></tr></thead><tbody><tr><td>非空约束</td><td>限制改字段值不能为null</td><td>not null</td></tr><tr><td>唯一约束</td><td>保证字段的所有数据都是唯一，不重复的</td><td>unique</td></tr><tr><td>主键约束</td><td>主键是一行数据的唯一表示，要求非空且唯一，在后面添加auto_increment自动增加</td><td>primary key</td></tr><tr><td>默认约束</td><td>保存数据时，如果未指定该字段值，则采用默认值</td><td>default</td></tr><tr><td>外键约束</td><td>让两张表的数据建立连接，保证数据的一直性和完整性</td><td>Foreign key</td></tr></tbody></table><p>数据类型：查表</p><h2 id="表结构查询"><a href="#表结构查询" class="headerlink" title="表结构查询"></a>表结构查询</h2><ul><li>查询当前数据库下所有表：show tables;</li><li>查询表结构: desc 表名;</li><li>查询建表语句: show create table 表名;</li></ul><h2 id="修改表结构"><a href="#修改表结构" class="headerlink" title="修改表结构"></a>修改表结构</h2><ul><li>添加字段: alter table 表名 add 字段名 类型(长度) [comment 注释] [约束];</li><li>修改字段类型: alter table 表名 modify 字段名 新数据类型(长度);</li><li>修改字段名和字段类型: alter table 表名 change 旧字段名 新字段名 类型(长度) [comment 注释] [约束]</li><li>删除字段: alter table 表名 drop column 字段名;</li><li>修改表名: rename table 表名 to 新表名;</li><li>删除表: drop table if exists 表名;</li></ul><p>在删除表结构时，数据也会被删除</p><h2 id="DML"><a href="#DML" class="headerlink" title="DML"></a>DML</h2><p><strong>插入数据</strong></p><ul><li>指定字段添加数据: insert into 表名 (字段1,字段2) values (值1,值2);</li><li>全部字段添加数据: insert into 表名 values (值1,值2,…);</li><li>批量添加数据(指定字段): insert into 表名 (字段名1,字段名2) values (值1,值2),(值1,值2);</li><li>批量添加数据(全部字段):insert into 表名 values (值1，值2…),(值1，值2…);</li></ul><p>更新时间可以使用now();</p><p>前面有多少个字段，有面就需要多少个值</p><p>字符串和日期需要用引号阔起来</p><p>插入的数据大小应该在字段限制之内</p><p><strong>更新数据</strong></p><ul><li>修改数据: update 表名 set 字段名1&#x3D;值1，字段名2&#x3D;值2…. [where 条件];</li></ul><p>没有where会更新整张表</p><p><strong>删除</strong></p><ul><li>删除数据: delete from 表名 [where 条件];</li></ul><p>没有条件会清空整张表的数据</p><p>不能删除某一个字段的值，如果要操作使用update设置为null</p><h2 id="DLQ"><a href="#DLQ" class="headerlink" title="DLQ"></a>DLQ</h2><p>用来查询数据库中的记录Select</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span><br>  字段列表<br><span class="hljs-keyword">from</span><br>  表名列表<br><span class="hljs-keyword">where</span><br>  条件列表<br><span class="hljs-keyword">group</span> <span class="hljs-keyword">by</span><br>  分组字段列表<br><span class="hljs-keyword">having</span><br>  分组后条件列表<br><span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span><br>  排序字段列表<br>limit<br>  分页参数<br></code></pre></td></tr></table></figure><p><strong>基本查询</strong></p><ul><li>查询多个字段：select 字段1，字段2 from 表名;</li><li>查询所有字段: select * from 表名;</li><li>设置别名: select 字段1 [as 别名1] from 表名;</li><li>去除重复记录: select distinct 字段列表 from 表名;</li></ul><p><strong>条件查询</strong></p><ul><li>条件查询: select 字段列表 from 表名 where 条件列表;</li></ul><table><thead><tr><th>比较运算符</th><th>功能</th></tr></thead><tbody><tr><td>&gt;</td><td>大于</td></tr><tr><td>&gt;&#x3D;</td><td>大于等于</td></tr><tr><td>&lt;</td><td>小于</td></tr><tr><td>&lt;&#x3D;</td><td>小于等于</td></tr><tr><td>&#x3D;</td><td>等于</td></tr><tr><td>&lt;&gt;或!&#x3D;</td><td>不等于</td></tr><tr><td>between … and …</td><td>在某个范围之内（含最小，最大值）</td></tr><tr><td>in(…)</td><td>在in之后的列表中的值，多选一</td></tr><tr><td>like 占位符</td><td>模糊匹配使用通配符’_’单个字符‘%’任意字符</td></tr><tr><td>is null</td><td>是null</td></tr></tbody></table><table><thead><tr><th>逻辑运算符</th><th>功能</th></tr></thead><tbody><tr><td>and或&amp;&amp;</td><td>并且</td></tr><tr><td>or或||</td><td>或者</td></tr><tr><td>not或|</td><td>非</td></tr></tbody></table><p>select * from tb_tmp where name&#x3D;’杨逍’;</p><p>select * from tb_tmp where id &lt;&#x3D; 5;</p><p>select * from tb_tmp where job is null;</p><p>select * from tb_tmp where job is not null;</p><p>select * from tb_tmp where password !&#x3D; ‘123456’;</p><p>select * from tb_tmp where entry data &gt;&#x3D; ‘2000-01-01’ and data &lt;&#x3D;’2010-01-01’;</p><p>select * from tb_tmp where between ‘2000-01-01’ and ‘2010-01-01’;</p><p>select * from tb_tmp where between ‘2000-01-01’ and ‘2010-01-01’ and gender &#x3D;2;</p><p>select * from tb_tmp where  job &#x3D; 2 or job &#x3D; 3 or job&#x3D; 4;</p><p>select * from tb_tmp where job in (2,3,4) ;</p><p>select * from tb_tmp where name like ‘__’;</p><p>select * from tb_tmp where name like ‘张%’;</p><p><strong>分组查询</strong></p><table><thead><tr><th>函数</th><th>功能</th></tr></thead><tbody><tr><td>count</td><td>统计数量</td></tr><tr><td>max</td><td>最大值</td></tr><tr><td>min</td><td>最小值</td></tr><tr><td>avg</td><td>平均值</td></tr><tr><td>sum</td><td>求和</td></tr></tbody></table><p>语法：select 聚合函数(字段列表) from 表名;</p><p>统计企业员工数量</p><p>Select count(id) from tb_emp;&#x2F;&#x2F;不对null记述</p><p>Select count(0) from tb_emp;&#x2F;&#x2F;不对null记述</p><p>Select count(*) from tb_emp;&#x2F;&#x2F;不对null记述</p><p>推荐count(*)</p><p>最早入职的员工</p><p>select min(entrydate) from tb_emp;</p><p>最迟入职员工</p><p>select max(entry date) from tb_emp;</p><p>统计某列的平均值</p><p>select avg(xx) from table;</p><p>求和</p><p>select sum(xx) from table;</p><p><strong>分组查询</strong></p><p>只能返回： 分组字段或聚合函数</p><p>语法:select 字段列表 from 表名 [where 条件] group by 分组字段名 [having 分组后的条件];</p><p>根据性别分组，统计男生于女生的数量</p><p>select gender,count(*)  from tb_emp group by gender;</p><p>先查询入职之间在xxx-xxx 以前的员工，并结果根据职位分组，获取员工数量大于等于2的职位</p><p>select job, count(*) from tb_emp where entrydate &lt;&#x3D; ‘xxx’ group by job having count(*) &gt;&#x3D;2;</p><p><strong>排序查询</strong></p><p>select 字段列表 from 表名 [where 条件] [group by 分组字段] order by 字段1 排序方式，字段2 排序方式…;</p><p>asc升序</p><p>desc降序</p><p><strong>分页查询</strong></p><p>select 字段列表 from 表名 limit 起始索引，查询记录条数;</p><p>从启始索引0开始查询员工数据，每页展示5条记录</p><p>select * from tb_emp limit 0,5;</p><p>从启始第1页开始查询员工数据，每页展示5条记录</p><p>select * from tb_emp limit 0,5;</p><p>从启始第2页开始查询员工数据，每页展示5条记录</p><p>select * from tb_emp limit 5,5;</p><p>从启始第3页开始查询员工数据，每页展示5条记录</p><p>select * from tb_emp limit 10,5;</p><p>启始索引（页码-1）*每页展示记录数</p><p><strong>案例</strong></p><p>按需求完成员工管理的条件分页查询，根据输入条件，查询第一页数据，每页展示10条记录</p><p>select * from tb_emp where name like ‘张%’ and gender &#x3D; 1 and entrydate between ‘xxx’ and ‘xxx’ order by update desc limit 0,10 ;</p><ul><li>if(条件，true取值，false取值)</li></ul><p>select if(gender&#x3D;1,’男性员工’,’女性员工’)  性别 ,count(*) from tb_emp group by gender;</p><ul><li>case 表达式 when 值1 then结果1 when 值2 then 结果2 else … end.</li></ul><p>select (case job when 1 then ‘班主任’ when 2 then ‘讲师’ else ‘未分配职位’ end) 职位,count(*) from tb_emp froup by job;</p><h2 id="多表设计"><a href="#多表设计" class="headerlink" title="多表设计"></a>多表设计</h2><p><strong>一对多</strong></p><p>多的一方关联一的一方</p><p>问题分析：两张表，在数据库层面并未建立关联，所以是无法保证数据的一致性和完整性的</p><p><strong>外键语法</strong></p><p>创建表时指定(所有字段罗列完后在最后添加)</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">create table</span> 表名(<br>  字段名 数据类型<br>  ...<br>  [<span class="hljs-keyword">constraint</span>] [外键名称] <span class="hljs-keyword">foreign key</span> (外键字段名) <span class="hljs-keyword">references</span> 主表 (字段名称)<br>);<br></code></pre></td></tr></table></figure><p>建完表后，添加外键</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">alter table</span> 表名 <span class="hljs-keyword">add constraint</span> 外键名称 <span class="hljs-keyword">foreign key</span> (外键字段名) <span class="hljs-keyword">references</span> 主表(字段名);<br></code></pre></td></tr></table></figure><p>这种外键叫物理外键</p><p>概念：使用foreign key 定义外键关联另外一张表</p><p>缺点：</p><ul><li>影响CRUD</li><li>仅用于单节点数据库，不适用分别是，集群场景</li><li>容易引发数据库的死锁问题，消耗性能</li></ul><p>逻辑外键</p><p>概念：在业务层逻辑中，解决外键关联</p><p>通过逻辑外键，就可以很方便的解决上述问题</p><p><strong>一对一</strong></p><p>关系：一对一关系，多用于单表拆分，将一张表的基础字段放在一张表中，其他字段放在另一张表中，以提升操作效率</p><p>如果基本信息查询特别高，查询身份信息很低，可以拆分为两张表</p><p>实现：在任意一方加入外键，关联另一方的主键，并且设置外键为唯一的（UNIQUE）</p><p>*<em>多对多</em></p><p>实现：建立第三张中间表，中间表至少包含两个外键，分别关联两方主键</p><h2 id="多表查询"><a href="#多表查询" class="headerlink" title="多表查询"></a>多表查询</h2><p><strong>内连接查询</strong></p><p>查询交集部分</p><p>表起别名在表后空格直接加名字</p><p>隐式内连接</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> 字段列表 <span class="hljs-keyword">from</span> 表<span class="hljs-number">1</span>,表<span class="hljs-number">2</span> <span class="hljs-keyword">where</span> 条件...;<br></code></pre></td></tr></table></figure><p>显式内连接</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> 字段列表 <span class="hljs-keyword">from</span> 表<span class="hljs-number">1</span> [<span class="hljs-keyword">inner</span>] <span class="hljs-keyword">join</span> 表<span class="hljs-number">2</span> <span class="hljs-keyword">on</span> 连接条件...;<br></code></pre></td></tr></table></figure><p><strong>外连接</strong></p><p>左外连接(完全包含左表数据)</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> 字段列表 <span class="hljs-keyword">from</span> 表<span class="hljs-number">1</span> <span class="hljs-keyword">left</span> [<span class="hljs-keyword">outer</span>] <span class="hljs-keyword">join</span> 表<span class="hljs-number">2</span> <span class="hljs-keyword">on</span> 连接条件...;<br></code></pre></td></tr></table></figure><p>右外连接(完全包含右表数据)</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> 字段列表 <span class="hljs-keyword">from</span> 表<span class="hljs-number">1</span> <span class="hljs-keyword">right</span> [<span class="hljs-keyword">outer</span>] <span class="hljs-keyword">join</span> 表<span class="hljs-number">2</span> <span class="hljs-keyword">on</span> 连接条件...;<br></code></pre></td></tr></table></figure><p><strong>子查询</strong></p><p>SQL语句中嵌套select语句，成为嵌套查询，又称子查询</p><p>形式</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> t1 <span class="hljs-keyword">where</span> column1<span class="hljs-operator">=</span>(<span class="hljs-keyword">select</span> column1 <span class="hljs-keyword">from</span> t2 ...);<br></code></pre></td></tr></table></figure><p>子查询外部的语句可以式insert&#x2F;update&#x2F;delete&#x2F;select</p><p>分类</p><ul><li>标量子查询：子查询返回的结果为单个值</li></ul><p>常用的操作符：&#x3D; 、&lt;&gt;、 &gt;、 &gt;&#x3D;、 &lt;、 &lt;&#x3D;</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> tb_emp <span class="hljs-keyword">where</span> dept_id <span class="hljs-operator">=</span> (<span class="hljs-keyword">select</span> id <span class="hljs-keyword">from</span> tb_dept <span class="hljs-keyword">where</span> name <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;教研部&#x27;</span>);<br></code></pre></td></tr></table></figure><ul><li>列子查询：子查询返回的结果为一列</li></ul><p>常用操作符：in 、not in</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> tb_emp <span class="hljs-keyword">where</span> dept_id <span class="hljs-keyword">in</span> (<span class="hljs-keyword">select</span> id <span class="hljs-keyword">from</span> tb_dept <span class="hljs-keyword">where</span> name <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;教研部&#x27;</span> <span class="hljs-keyword">or</span> name <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;咨询部&#x27;</span>);<br></code></pre></td></tr></table></figure><ul><li>行子查询返回的结果为一行</li></ul><p>常用操作符：&#x3D;、&lt;&gt;、in 、not in</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> tb_emp <span class="hljs-keyword">where</span> (entrydate,job)<span class="hljs-operator">=</span>(<span class="hljs-keyword">select</span> entrydate,job <span class="hljs-keyword">from</span> tb_emp <span class="hljs-keyword">where</span> name<span class="hljs-operator">=</span><span class="hljs-string">&#x27;韦一笑&#x27;</span>);<br></code></pre></td></tr></table></figure><ul><li>表子查询：子查询返回的结果为多行多列</li></ul><p>常用操作符：in</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">select</span> e.<span class="hljs-operator">*</span>,d.name <span class="hljs-keyword">from</span> (<span class="hljs-keyword">select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> tb_emp <span class="hljs-keyword">where</span> entrydate <span class="hljs-operator">&gt;</span> <span class="hljs-string">&#x27;2006-01-01&#x27;</span>) e, tb_dept d <span class="hljs-keyword">where</span> e.dept_id <span class="hljs-operator">=</span> d.id;<br></code></pre></td></tr></table></figure><h2 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h2><p>事务是一组操作的集合，它是一个不可分割的工作单位。事物会吧所有的操作作为一个整体一起向系统提交或撤销操作请求，即这些操作要么同时成果，要么同时失效</p><p>注意事项：默认mysql的事物是自动提交的，也就是说，当执行一条dml语句，mysql会立即隐式提交事务</p><p>事务控制</p><ul><li>开启事务：start transaction;&#x2F;begin;</li><li>提交事物: commit;</li><li>回滚事物: rollback;</li></ul><p>先开启事务，然后执行多条sql如果成功就使用commit，如果失败则使用rollback</p><p>四大特性</p><ul><li>原子性:最小单元，要么全部成功，要么全部失败</li><li>一致性：事物完成时，必须使所有数据都保持一致状态</li><li>隔离性：数据库系统提供的隔离机制，保证事物不受外部兵法操作影响的独立环境下运行</li><li>持久性：事物一旦提交或回滚，它对数据库中数据的改变就是永久的</li></ul><h2 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h2><p>index时帮助数据库高效获取数据的数据结构</p><p>优点：</p><ul><li>提高数据库查询的效率，降低数据库的IO成本</li><li>通过索引列对数据进行排序，降低数据排序的成本，降低CPU消耗</li></ul><p>缺点</p><ul><li>索引会占用存储空间</li><li>索引大大提高了查询效率，同时也降低了insert、updata、delete的效率</li></ul><p>B+tree（多路平衡搜索树)</p><p><strong>语法</strong></p><ul><li>创建索引：</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">create</span> [<span class="hljs-keyword">unique</span>] index 索引名 <span class="hljs-keyword">on</span> 表名（字段名）;<br></code></pre></td></tr></table></figure><ul><li>查看索引</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">show</span> index <span class="hljs-keyword">from</span> 表名;<br></code></pre></td></tr></table></figure><ul><li>删除索引</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">drop</span> index 索引名 <span class="hljs-keyword">on</span> 表名;<br></code></pre></td></tr></table></figure><h2 id="MyBatis"><a href="#MyBatis" class="headerlink" title="MyBatis"></a>MyBatis</h2><p><strong>快速入门</strong></p><p>使用Mybatis查询所有用户数据</p><p>1、准备工作（创建springboot工程、数据包user、实体类User）</p><p>2、引入Mybatis的相关依赖，配置Mybatis(数据库连接信息)</p><p>3、编写SQL语句（注解&#x2F;XML）</p><p><strong>四要素</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">#驱动类名称<br>spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver<br>#数据库连接的url<br>spring.datasource.url=jdbc:mysql://localhost:3306/mybatis<br>#连接数据库的用户名<br>spring.datasource.username=root<br>#连接数据库的密码<br>spring.datasource.password=1234<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Mapper</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">UserMapper</span>&#123;<br>  <span class="hljs-meta">@Select(&quot;select * from user&quot;)</span><br>  <span class="hljs-keyword">public</span> List&lt;User&gt; <span class="hljs-title function_">list</span><span class="hljs-params">()</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p><strong>JDBC</strong></p><p>jdbc就是使用java语言操作关系型数据库的一套API</p><p>是一套操作关系型数据库的规范，即接口</p><p>各个数据库厂商起实现这套接口，体空数据库的java包</p><p>我们可以使用这套接口编程，真正执行的代码是驱动jar包中的实现类</p><p><strong>数据库连接池</strong></p><p>数据库连接池是个容器，复制分配、管理数据库连接（Connection）</p><p>它运行应用程序重复使用一个现有的数据库连接，而不是再重新建立一个</p><p>释放空闲时间超过最大空闲时间的连接，来避免因为没有释放连接而引起的数据库连接泄漏</p><p>标准接口</p><p>功能：获取链接：Connection getConnection() throws SQLException;</p><p>常见产品</p><p>C3P0、DBCP、DRuid（阿里巴巴开源）、Hikari（springboot默认）</p><p>切换链接池</p><ul><li>引入依赖</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;dependency&gt;<br>   &lt;groupId&gt;com.alibaba&lt;/groupId&gt;<br>   &lt;artifactId&gt;druid-spring-boot-starter&lt;/artifactId&gt;<br>   &lt;version&gt;1.2.8&lt;/version&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><ul><li>配置链接信息</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">#驱动类名称<br>spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver<br>#数据库连接的url<br>spring.datasource.url=jdbc:mysql://localhost:3306/mybatis<br>#连接数据库的用户名<br>spring.datasource.username=root<br>#连接数据库的密码<br>spring.datasource.password=1234<br></code></pre></td></tr></table></figure><p><strong>lombok</strong></p><p>lombok是一个实用的java类库，能通过注解的形式自动生成构造器，getter&#x2F;setter、equals、hashcode、toString等方法，并可以自动化生产日志变了，简化java开发，提高效率</p><table><thead><tr><th>注解</th><th>作用</th></tr></thead><tbody><tr><td>@Getter&#x2F;@Setter</td><td>为所有的属性提供get&#x2F;set方法</td></tr><tr><td>@ToString</td><td>会给类自动生成一阅读的toString方法</td></tr><tr><td>@EqualsAndHashCode</td><td>根据类所拥有的非静态字段自动重写equals方法和hashCOde方法</td></tr><tr><td>@Data</td><td>提供了更综合的生成代码功能（@Getter+@Setter+@ToString+@EqualsAndHashCode）</td></tr><tr><td>@NoArgsConstructor</td><td>为实体类生成无参的构造器方法</td></tr><tr><td>@AllArgsConstructor</td><td>为实体类生成处理static修饰的字段之外代有各参数的构造器方法</td></tr></tbody></table><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;dependency&gt;<br>    &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;<br>    &lt;artifactId&gt;lombok&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><p>lombok会在编译时，自动生成对于的java代码。我们使用lombok时，还需要安装一个lombok的插件（idea自带）</p><h2 id="基础操作"><a href="#基础操作" class="headerlink" title="基础操作"></a>基础操作</h2><p><strong>删除</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Delete(&quot;delete from emp where id = #&#123;id&#125;&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">delete</span><span class="hljs-params">(Integer id)</span>;<br></code></pre></td></tr></table></figure><p>日志输入</p><p>可以在application.properties中，打开mybatis的日志，并输出到控制台</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl<br></code></pre></td></tr></table></figure><p>预编译sql的优势</p><p>性能更高</p><p>更安全（防止sql注入）</p><p>#{id}就会生成预编译sql</p><p>${id}会直接将参数拼接在后面，存在sql注入问题</p><p><strong>新增</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Insert(&quot;insert into emp(username,name,gender,image,job,entrydate,dept_id,create_time,update_time&quot;+</span><br><span class="hljs-meta">       &quot; values (#&#123;username&#125;,#&#123;name&#125;,#&#123;gender&#125;,#&#123;image&#125;,#&#123;job&#125;,#&#123;entrydate&#125;,#&#123;dept_id&#125;,#&#123;createTime&#125;,#&#123;updateTime&#125;)&quot;)</span><br><span class="hljs-meta">public void insert(Emp emp);</span><br></code></pre></td></tr></table></figure><p><strong>主键返回</strong></p><p>在数据添加成功后，需要获取插入数据库数据的主键</p><p>需要在添加的方法上添加注解</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Options(keyProperty = &quot;id&quot;,useGeneratedKeys=true)</span><br></code></pre></td></tr></table></figure><p>会将主键封装到id属性中，代表我们需要拿到主键值</p><p><strong>更新</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Update(&quot;update emp set username=#&#123;username&#125;,name=#&#123;name&#125;,gender=#&#123;gender&#125;,image=#&#123;image&#125;,job=#&#123;job&#125;,entrydate=#&#123;entrydate&#125;,dept_id=#&#123;deptid&#125;,update_time=#&#123;updateTime where id=#&#123;id&#125;&#125;&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">update</span><span class="hljs-params">(Emp emp)</span>;<br></code></pre></td></tr></table></figure><p><strong>查询</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Select(&quot;select * from emp where id=#&#123;id&#125;&quot;)</span><br><span class="hljs-keyword">public</span> Emp <span class="hljs-title function_">getById</span><span class="hljs-params">(Integer id)</span>;<br></code></pre></td></tr></table></figure><p>数据封装</p><p>实体类书姓名和数据库表查询返回到字段名一致，mybatis会自动封装</p><p>如果实体类书姓名和数据库表查询返回的字段名不一致，不能自动封装</p><ul><li>方案一：给字段起别名，让别名于实体类属性一致</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Select(&quot;select xxx aaa,xxxx aaaa from emp where id=#&#123;id&#125;&quot;)</span><br><span class="hljs-keyword">public</span> Emp <span class="hljs-title function_">getById</span><span class="hljs-params">(Integer id)</span>;<br></code></pre></td></tr></table></figure><ul><li>方案二，通过@Results,@Result注解手动映射封装</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Results(&#123;</span><br><span class="hljs-meta">  @Result(column = &quot;dept_id&quot;,property = &quot;deptId&quot;),</span><br><span class="hljs-meta">  @Result(column = &quot;create_time&quot;,property = &quot;createTime&quot;),</span><br><span class="hljs-meta">  @Result(column = &quot;update_time&quot;,property = &quot;updateTime&quot;),</span><br><span class="hljs-meta">&#125;)</span><br><span class="hljs-meta">@Select(&quot;select * from emp where id=#&#123;id&#125;&quot;)</span><br><span class="hljs-keyword">public</span> Emp <span class="hljs-title function_">getById</span><span class="hljs-params">(Integer id)</span>;<br><br></code></pre></td></tr></table></figure><ul><li>方案三：开启mybatis的驼峰命名自动映射开关 a_column —&gt;aColumn</li></ul><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs properties"><span class="hljs-attr">mybatis.configuration.map-underscore-to-camel-case</span>=<span class="hljs-string">true</span><br></code></pre></td></tr></table></figure><p><strong>条件查询</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Select(&quot;select * from emp where name like concat(&#x27;%&#x27;,#&#123;name&#125;,&#x27;%&#x27;) and gender = #&#123;gender&#125; and entrydate between #&#123;begin&#125; and #&#123;end&#125; order by update_time desc&quot;)</span><br><span class="hljs-keyword">public</span> List&lt;Emp&gt; <span class="hljs-title function_">list</span><span class="hljs-params">(String name,Short gender,LocalDate begin,LocalDate end)</span>;<br></code></pre></td></tr></table></figure><h2 id="XML映射文件"><a href="#XML映射文件" class="headerlink" title="XML映射文件"></a>XML映射文件</h2><p><strong>规范</strong></p><ul><li>XML映射文件的名称与Mapper接口名称一致，并且将XML映射文件和Mapper接口放置在相同包下（同包同名）</li><li>XML映射文件的namespace属性为Mapper接口全限定名一致</li><li>XML映射文件中sql语句的id与Mapper接口中的方法名一致，并保持返回类型一致</li></ul><p>在resources下创建包级目录用‘&#x2F;’</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta">&lt;?xml version=<span class="hljs-string">&quot;1.0&quot;</span> encoding=<span class="hljs-string">&quot;UTF-8&quot;</span> ?&gt;</span><br><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-keyword">mapper</span></span><br><span class="hljs-meta">          <span class="hljs-keyword">PUBLIC</span> <span class="hljs-string">&quot;-//mybatis.org//DTD Mapper 3.0//EN&quot;</span></span><br><span class="hljs-meta">          <span class="hljs-string">&quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">mappser</span> <span class="hljs-attr">namespace</span>=<span class="hljs-string">&quot;com.alan.mapper.EmpMapper&quot;</span>&gt;</span><br>      <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;list(方法名)&quot;</span> <span class="hljs-attr">resultType</span>=<span class="hljs-string">&quot;com.alan.pojo.Emp(返回类型)&quot;</span>&gt;</span><br>          select * from emp where name like concat(&#x27;%&#x27;,#&#123;name&#125;,&#x27;%&#x27;) and gender = #&#123;gender&#125; and entrydate between #&#123;begin&#125; and #&#123;end&#125; order by update_time desc<br>  <br>      <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">mappser</span>&gt;</span><br></code></pre></td></tr></table></figure><h2 id="动态SQL"><a href="#动态SQL" class="headerlink" title="动态SQL"></a>动态SQL</h2><ul><li><if></li><li><where>：条件都不成立去除where，而且会自动去除开头多余的and和or</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">where</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">test</span>=<span class="hljs-string">&quot;name != null&quot;</span>&gt;</span><br>    name like concat(&#x27;%&#x27;,#&#123;name&#125;,&#x27;%&#x27;)<br>  <span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br>   <span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">test</span>=<span class="hljs-string">&quot;gender != null&quot;</span>&gt;</span><br>    and gender = #&#123;gender&#125;<br>  <span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br>   <span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">test</span>=<span class="hljs-string">&quot;begin != null and end != null&quot;</span>&gt;</span><br>    and entrydate between #&#123;begin&#125; and #&#123;end&#125;<br>  <span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br>   <span class="hljs-tag">&lt;<span class="hljs-name">if</span> <span class="hljs-attr">test</span>=<span class="hljs-string">&quot;name != null&quot;</span>&gt;</span><br>    name like concat(&#x27;%&#x27;,#&#123;name&#125;,&#x27;%&#x27;)<br>  <span class="hljs-tag">&lt;/<span class="hljs-name">if</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">where</span>&gt;</span><br></code></pre></td></tr></table></figure><p><strong>案例</strong></p><p>动态更新</p><p>alt+end，mybtisx插件自动生成sql</p><ul><li><set>，会自动去除后面多余的逗号</li></ul><p><strong>forech</strong></p><ul><li><foreach>遍历元素</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml">delete from emp where id in<br><span class="hljs-tag">&lt;<span class="hljs-name">foreach</span> <span class="hljs-attr">collection</span>=<span class="hljs-string">&quot;ids&quot;</span> <span class="hljs-attr">item</span>=<span class="hljs-string">&quot;id&quot;</span> <span class="hljs-attr">separator</span>=<span class="hljs-string">&quot;,&quot;</span> <span class="hljs-attr">open</span>=<span class="hljs-string">&quot;(&quot;</span> <span class="hljs-attr">close</span>=<span class="hljs-string">&quot;)&quot;</span>&gt;</span><br>  #&#123;id&#125;<br><span class="hljs-tag">&lt;/<span class="hljs-name">foreach</span>&gt;</span><br></code></pre></td></tr></table></figure><p>Collection:遍历的集合</p><p>Item:遍历出来的元素</p><p>separator:分隔符</p><p>open:遍历开始前拼接的SQl片段</p><p>close:遍历结束后凭借的SQL片段</p><p><strong>sql,include</strong></p><ul><li><sql>定义可重复使用的SQL片段</li><li><include>通过属性refid，指定包含的SQL片段</li></ul><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">sql</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;xx&quot;</span>&gt;</span><br>这里方重复使用的sql片段<br><span class="hljs-tag">&lt;/<span class="hljs-name">sql</span>&gt;</span><br><br><span class="hljs-tag">&lt;<span class="hljs-name">include</span> <span class="hljs-attr">refid</span>=<span class="hljs-string">&quot;xx&quot;</span>/&gt;</span>引用sql<br></code></pre></td></tr></table></figure><h2 id="开发规范-Restful"><a href="#开发规范-Restful" class="headerlink" title="开发规范-Restful"></a>开发规范-Restful</h2><ul><li>REST 表述性状态转换，他是一种软件架构风格</li></ul><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs http">http://localhost:8080/users/1 GET:查询id为1到用户<br>http://localhost:8080/users    POST：新增用户<br>http://localhost:8080/users    PUT：修改用户<br>http://localhost:8080/users/1  DELTETE：删除id为1到用户<br><br></code></pre></td></tr></table></figure><ul><li>url定位资源</li><li>HTTP动词描述操作</li></ul><p>REST是风格，是约定方式，约定不是规定，可以打破</p><p>描述模块的功能通常使用复数，也就是加s的格式来描述，表示此类资源，而非耽搁资源如：users、emps、books…</p><p>统一响应结果</p><p>code：响应码</p><p>msg：响应信息</p><p>data：返回数据</p><p><strong>开发流程</strong></p><ol><li>查看页面原型明确需求</li><li>阅读接口文档</li><li>思路分析</li><li>接口开发</li><li>接口测试</li><li>前后端链调</li><li>开发流程</li></ol><p>明确需求 接口文档 思路分析 接口开发</p><ol start="2"><li>接口调试</li></ol><p>postman测试 前后端联调</p><ol start="3"><li>日志小技巧</li></ol><p>@Slf4j</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">log.info()<br></code></pre></td></tr></table></figure><h2 id="pageHelper插件"><a href="#pageHelper插件" class="headerlink" title="pageHelper插件"></a>pageHelper插件</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// 员工信息查询</span><br><span class="hljs-meta">@Select(&quot;select * from emp&quot;)</span><br><span class="hljs-keyword">public</span> List&lt;Emp&gt; <span class="hljs-title function_">list</span><span class="hljs-params">()</span>;<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br><span class="hljs-keyword">public</span> PageBean <span class="hljs-title function_">page</span><span class="hljs-params">(Integer page, Integer pageSize)</span>&#123;<br>  <span class="hljs-comment">// 设置分页参数</span><br>  PageHelper.startPage(page,pageSize);<br>  <span class="hljs-comment">// 执行查询</span><br>  List&lt;Emp&gt; empList = empMapper.list();<br>  Page&lt;Emp&gt; p = (Page&lt;Emp&gt;) empList;<br>  <span class="hljs-comment">// 封装PageBean对象</span><br>  <span class="hljs-type">PageBean</span> <span class="hljs-variable">pageBean</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">PageBean</span>(p.getTotal(),p.getResult());<br>  <span class="hljs-keyword">return</span> pageBean;<br>&#125;<br></code></pre></td></tr></table></figure><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-comment">&lt;!--PageHelper分页插件--&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.github.pagehelper<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>pagehelper-spring-boot-starter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.4.2<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure><p>springboot文件上传，默认单个文件最大允许为1M，如果需要上传大文杰，可以进行如下配置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">配置耽搁文件最大上传大小<br>spring.servlet.multipart.max-file-size=10MB<br>配置单个请求最大上传大小（一次请求可以上传多个文件）<br>spring.servlet.multipart.max-request-size=100MB<br></code></pre></td></tr></table></figure><table><thead><tr><th>函数名</th><th>功能</th></tr></thead><tbody><tr><td>String getOriginalFilename();</td><td>获取原始文件名</td></tr><tr><td>void transferTo(File dest);</td><td>将接收的文件转存到磁盘文件中</td></tr><tr><td>Long getSize()</td><td>获取文件的大小，单位，字节</td></tr><tr><td>Byte[] getBytes();</td><td>获取文件内容的字节数组</td></tr><tr><td>InputStream getInputStream();</td><td>接收到的文件内容的输入流</td></tr></tbody></table><h2 id="yml"><a href="#yml" class="headerlink" title="yml"></a>yml</h2><ul><li>大小写敏感</li><li>数值前边必须有空格作为分隔符</li><li>使用缩进表示层级关系，缩进时，不允许使用Tab键，只能使用空格</li><li>缩进的空格数目不重要，只要相同层级的元素左侧对其即可</li><li>#表示注释，从这个自负一致到行尾，都会被解析器忽略</li><li>对象&#x2F;Map集合</li></ul><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">user:</span><br>  <span class="hljs-string">name:zhangsan</span><br>  <span class="hljs-attr">age:</span> <span class="hljs-number">18</span><br>  <span class="hljs-attr">password:</span> <span class="hljs-number">123456</span><br></code></pre></td></tr></table></figure><ul><li>数组&#x2F;LIst&#x2F;Set集合</li></ul><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">hobby:</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">java</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">game</span><br>  <span class="hljs-bullet">-</span> <span class="hljs-string">sport</span><br></code></pre></td></tr></table></figure><p>配置文件遍历注入</p><p>@Value(“${key}”)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">@Data //提供get set方法<br>@Component //成为bean<br>@ConfigurationProperties(prefix=&quot;前缀&quot;)//提供变量名的前缀<br></code></pre></td></tr></table></figure><p>解决报错</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&lt;dependency&gt;<br>  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;<br>  &lt;artifactId&gt;spring-boot-configuration-processor&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br></code></pre></td></tr></table></figure><h2 id="数据校验"><a href="#数据校验" class="headerlink" title="数据校验"></a>数据校验</h2><h2 id="会话"><a href="#会话" class="headerlink" title="会话"></a>会话</h2><ul><li><p>会话：用户打开浏览器，访问web服务器的资源，会话建立，知道一方断开连接，会话结束，在一次会话中可以包括多次请求和响应</p></li><li><p>会话跟踪：一种维护浏览器状态的方法，服务器需要识别多次请求是否来自于同一浏览器，以便在同一次会话多次请求间共享数据</p></li><li><p>会话跟踪方案：</p><ul><li>客户端会话跟踪技术：Cookie</li><li>服务端会话跟踪技术：Session</li><li>令牌技术</li></ul></li></ul><p><strong>Cookie</strong></p><p>优点：HTTP协议中支持的技术</p><p>缺点：</p><ul><li>移动端APP无法使用Cookie</li><li>不安全，用户可以自己禁用Cookie</li><li>Cookie不能跨域</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//设置Cookie</span><br><span class="hljs-meta">@GetMapping(&quot;/c1&quot;)</span><br><span class="hljs-keyword">public</span> Result <span class="hljs-title function_">cookie1</span><span class="hljs-params">(HttpServletResponse response)</span>&#123;<br>  response.addCookie(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Cookie</span>(<span class="hljs-string">&quot;login_username&quot;</span>,<span class="hljs-string">&quot;itheima&quot;</span>)); <span class="hljs-comment">//设置Cookie/响应Cookie</span><br>  <span class="hljs-keyword">return</span> Result.success();<br>&#125;<br><br><span class="hljs-comment">//获取Cookie</span><br><span class="hljs-meta">@GetMapping(&quot;/c2&quot;)</span><br><span class="hljs-keyword">public</span> Result <span class="hljs-title function_">cookie2</span><span class="hljs-params">(HttpServletRequest request)</span>&#123;<br>  Cookie[] cookies = request.getCookies();<br>  <span class="hljs-keyword">for</span> (Cookie cookie : cookies) &#123;<br>    <span class="hljs-keyword">if</span>(cookie.getName().equals(<span class="hljs-string">&quot;login_username&quot;</span>))&#123;<br>      System.out.println(<span class="hljs-string">&quot;login_username: &quot;</span>+cookie.getValue()); <span class="hljs-comment">//输出name为login_username的cookie</span><br>    &#125;<br>  &#125;<br>  <span class="hljs-keyword">return</span> Result.success();<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="跨域"><a href="#跨域" class="headerlink" title="跨域"></a>跨域</h2><p>跨域区分三个维度：协议、ip&#x2F;域名、端口号</p><p>任何一个不一样，都会跨域</p><h2 id="Session"><a href="#Session" class="headerlink" title="Session"></a>Session</h2><p>优点：存储在服务端，安全</p><p>缺点：</p><ul><li>服务器集群环境下无法直接使用Session</li><li>Cookie的缺点</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@GetMapping(&quot;/s1&quot;)</span><br><span class="hljs-keyword">public</span> Result <span class="hljs-title function_">session1</span><span class="hljs-params">(HttpSession session)</span>&#123;<br>  log.info(<span class="hljs-string">&quot;HttpSession-s1: &#123;&#125;&quot;</span>, session.hashCode());<br><br>  session.setAttribute(<span class="hljs-string">&quot;loginUser&quot;</span>, <span class="hljs-string">&quot;tom&quot;</span>); <span class="hljs-comment">//往session中存储数据</span><br>  <span class="hljs-keyword">return</span> Result.success();<br>&#125;<br><br><span class="hljs-meta">@GetMapping(&quot;/s2&quot;)</span><br><span class="hljs-keyword">public</span> Result <span class="hljs-title function_">session2</span><span class="hljs-params">(HttpServletRequest request)</span>&#123;<br>  <span class="hljs-type">HttpSession</span> <span class="hljs-variable">session</span> <span class="hljs-operator">=</span> request.getSession();<br>  log.info(<span class="hljs-string">&quot;HttpSession-s2: &#123;&#125;&quot;</span>, session.hashCode());<br><br>  <span class="hljs-type">Object</span> <span class="hljs-variable">loginUser</span> <span class="hljs-operator">=</span> session.getAttribute(<span class="hljs-string">&quot;loginUser&quot;</span>); <span class="hljs-comment">//从session中获取数据</span><br>  log.info(<span class="hljs-string">&quot;loginUser: &#123;&#125;&quot;</span>, loginUser);<br>  <span class="hljs-keyword">return</span> Result.success(loginUser);<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="令牌技术"><a href="#令牌技术" class="headerlink" title="令牌技术"></a>令牌技术</h2><p><strong>优势</strong>：</p><ul><li>支持PC端，移动端</li><li>解决集群环境下的认证问题</li><li>减轻服务器存储压力</li></ul><p><strong>缺点</strong>：需要自己实现</p><h2 id="JWT-（Json-Web-Token）"><a href="#JWT-（Json-Web-Token）" class="headerlink" title="JWT （Json Web Token）"></a>JWT （Json Web Token）</h2><p>官网：<a href="https://jwt.io/">https://jwt.io/</a></p><p>定义了一种简洁的，自包含的格式，用于通信双方一json数据格式安全的传输信息。由于数字签名的存在，这些信息是可靠的</p><p><strong>组成</strong></p><ul><li>第一部分：Header（头），记录令牌类型、签名算法等</li><li>第二部分：Payload（有效载荷），携带一些自定义信息、默认信息等</li><li>第三部分：Signature（签名），防止Token被篡改、确保安全性、将header、payload、并加入指定密钥，通过指定签名算法计算而来</li></ul><p>Base64:是一种基于64个可打印字符（A-Z，a-z，0-9+&#x2F;）来表示二进制数据等编码方式，&#x3D;补位符号</p><p><strong>引入JWT</strong></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>io.jsonwebtoken<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>jjwt<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.9.1<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure><p>JWT校验时使用的签名密钥，必须和生成JWT令牌时使用的密钥时配套的</p><p>如果JWT令牌解析校验时报错说明被篡改或过期</p><h2 id="过滤器Filter"><a href="#过滤器Filter" class="headerlink" title="过滤器Filter"></a>过滤器Filter</h2><p>概念：Filter过滤器时javaWeb三大组件（Servlet、Filter、Listenser）之一</p><p>过滤器可以把对资源的请求拦截下来，从二实现一些特殊的功能</p><p>过滤器一般完成一些通用的操作，比如登陆校验，统一编码处理，敏感字符处理</p><p><strong>快速入门</strong></p><ol><li>定义Filter：定义一个类，实现Filter接口，并重写其所有方法</li><li>配置Filter：Filter类上加@WebFilter(urlPatterns &#x3D; “&#x2F;*”)注解，配置拦截资源路径。引导类上加@ServletComponentScan开启Servlet组件支持</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@WebFilter(urlPatterns = &quot;/*&quot;)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DemoFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Filter</span> &#123;<br><br><span class="hljs-meta">@ServletComponentScan</span><br><span class="hljs-meta">@SpringBootApplication</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SpringbootItemQuickstartApplication</span> &#123;<br></code></pre></td></tr></table></figure><p>放行后访问对呀资源，资源访问完成后，还会回到Filter中嘛？ 会</p><p>如果回到Filter中，时重新执行还是执行放行后到逻辑呢？执行放行后逻辑</p><table><thead><tr><th>拦截路径</th><th>urlPatterns值</th></tr></thead><tbody><tr><td>拦截具体</td><td>&#x2F;login</td></tr><tr><td>拦截目录</td><td>&#x2F;emps&#x2F;*</td></tr><tr><td>拦截所有</td><td>&#x2F;*</td></tr></tbody></table><p>一个web应用中，可以配置多个过滤器，这个过滤器就形成了一个过滤器链</p><p>顺序：注解配置的Filter，优先级时按照过滤器类名（字符串）的自然排序</p><p>快速捕获异常command+option+t</p><h2 id="拦截器interceptor"><a href="#拦截器interceptor" class="headerlink" title="拦截器interceptor"></a>拦截器interceptor</h2><p><strong>快速入门</strong></p><p>概念：是一种动态拦截方法调用的机制，类似于过滤器。Spring框架中提供的，用来动态拦截控制方法的执行</p><p>作用：拦截请求，在指定的方法调用前后，根据业务需要执行预先设定的代码</p><p><strong>定义拦截器</strong> 实现HandlerIterceptor接口，并重写所有方法</p><p><strong>注册拦截器</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">WebConfig</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">WebMvcConfigurer</span>&#123;<br>  <span class="hljs-meta">@Autowired</span><br>  <span class="hljs-keyword">private</span> LoginCheckInterceptor loginCheckInterceptor;<br>  <span class="hljs-meta">@Override</span><br>  <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addInterceptors</span><span class="hljs-params">(InterceptorRegistry registry)</span>&#123;<br>    registry.addIntercepttor(LoginCheckInterceptor).addPathPatterns(<span class="hljs-string">&quot;/**&quot;</span>);<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>addPathPatterns**(“&#x2F;**”)需要拦截</p><p>excludePathPatterns**(“&#x2F;login”)不需要拦截</p><table><thead><tr><th>拦截路径</th><th>含义</th></tr></thead><tbody><tr><td>&#x2F;*</td><td>一级路径</td></tr><tr><td>&#x2F;**</td><td>任意级路径</td></tr><tr><td>&#x2F;depts&#x2F;*</td><td>&#x2F;depts下的一级路径</td></tr><tr><td>&#x2F;depts&#x2F;**</td><td>&#x2F;depts下的任意级路径</td></tr></tbody></table><p><strong>Fillter于Interceptor</strong></p><p>接口规范不同：过滤器需要实现Filter接口，二拦截器需要实现HandlerInterceptor接口</p><p>拦截范围不同：过滤器Filter会拦截所有的资源，而Intercetor只会拦截Spring环境中的资源</p><h2 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h2><p>方法一：在controller的方法中进行try…catch</p><p>方案二：全局异常处理器 @TestControllerAdvice</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RestControllerAdvice</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">GlobalExceptionHandler</span>&#123;<br>  <span class="hljs-meta">@ExceptionHandler(Exception.class)</span><br>  <span class="hljs-keyword">public</span> Result <span class="hljs-title function_">ex</span><span class="hljs-params">(Exception ex)</span>&#123;<br>    ex.printStackTrace();<br>    <span class="hljs-keyword">return</span> Result.error(<span class="hljs-string">&quot;对不起，操作失败，请联系管理员&quot;</span>)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="事物处理-AOP"><a href="#事物处理-AOP" class="headerlink" title="事物处理&amp;AOP"></a>事物处理&amp;AOP</h2><p>事物是一组操作的集合，它是一个不可分割的工作单位，这些操作，要么同时成功，要么同时失败</p><p>操作：</p><ol><li>开启事务（一组操作开始前，开始事物）：start transaction&#x2F;begin；</li><li>提交事物（这组操作全部成功后，提交事物）：commit；</li><li>回滚事物（中间任何一个操作出现异常，回滚事物）：rollback；</li></ol><p><strong>@Transactional</strong></p><p>位置：业务层的方法上、类上、接口上</p><p>作用：将当前方法交给spring进行事物管理，方法执行前，开启事务；成功执行完毕，提交事物，出现异常，回滚事物</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-comment">#spring事务管理日志</span><br><span class="hljs-attr">logging:</span><br>  <span class="hljs-attr">level:</span><br>    <span class="hljs-attr">org.springframework.jdbc.support.JdbcTransactionManager:</span> <span class="hljs-string">debug</span><br></code></pre></td></tr></table></figure><h2 id="事物进阶"><a href="#事物进阶" class="headerlink" title="事物进阶"></a>事物进阶</h2><p><strong>rollbackFor</strong></p><p>默认情况下，只有出现RuntimeExcception才回滚异常，rollbackFor属性用于控制出现何种异常类型，回滚事物</p><p><strong>propagation</strong></p><p>事物传播行为：指的就是当一个事物方法被另一个事物方法调用时，这个事物方法一个如何进行事物控制</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Transactional</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">a</span><span class="hljs-params">()</span>&#123;<br>  UserService.b();<br>&#125;<br><br><span class="hljs-meta">@Transactional(propagation = Propagation.REQUIRED)</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">b</span><span class="hljs-params">()</span>&#123;<br>  <br>&#125;<br></code></pre></td></tr></table></figure><table><thead><tr><th>属性值</th><th>含义</th></tr></thead><tbody><tr><td>REQUIRED</td><td>默认值，需要事物，有则加入，无则创建新事物</td></tr><tr><td>REQUIRES_NEW</td><td>需要新事物，无论有无，总是创建新事物</td></tr><tr><td>SUPPORTS</td><td>支持事物有则加入，无则在无事物状态中运行</td></tr><tr><td>NOT_SUPPORTED</td><td>不支持事物，在无事物状态下运行，如果当前存在已有事物，则挂起当前事物</td></tr><tr><td>MANDATORY</td><td>必须有事物，否则抛异常</td></tr><tr><td>NEVER</td><td>必须每事物，否则抛异常</td></tr></tbody></table><h2 id="AOP"><a href="#AOP" class="headerlink" title="AOP"></a>AOP</h2><p>快速入门：面向切面编程，其实就是面向特定方法编程</p><p>动态代理是面向切面编程最主流的实现，二springAOP是Spring框架的高级技术，旨在管理bean对象的过程中，主要通过代理机制，对特定的方法进行编程</p><p>导入aop依赖</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Slf4j</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-meta">@Aspect</span> <span class="hljs-comment">//声明AOP</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">TimeAspect</span> &#123;<br>    <span class="hljs-meta">@Around(&quot;execution(* com.itheima.*.*(..))&quot;)</span><span class="hljs-comment">//指定方法</span><br>    <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">recordTime</span><span class="hljs-params">(ProceedingJoinPoint joinPoint)</span> <span class="hljs-keyword">throws</span> Throwable &#123;<br>        <span class="hljs-type">long</span> <span class="hljs-variable">begin</span> <span class="hljs-operator">=</span> System.currentTimeMillis();<br>        <span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> joinPoint.proceed();<span class="hljs-comment">//执行方法</span><br>        <span class="hljs-type">long</span> <span class="hljs-variable">end</span> <span class="hljs-operator">=</span> System.currentTimeMillis();<br>        log.info(joinPoint.getSignature()+<span class="hljs-string">&quot;方法执行耗时:&#123;&#125;ms&quot;</span>,end-begin);<br>        <span class="hljs-keyword">return</span> result;<br>    &#125;<br><br>&#125;<br><br></code></pre></td></tr></table></figure><h2 id="AOP核心概念"><a href="#AOP核心概念" class="headerlink" title="AOP核心概念"></a>AOP核心概念</h2><p>连接点：JoinPoint,可以被AOP控制的方法（暗含执行时的相关信息)</p><p>通知：Advice，指哪些重复的逻辑，也就是共性功能（最终体现为一个方法）</p><p>切入点：PointCut，匹配连接点的条件，通知仅在切入点方法执行时被应用</p><p>切面：Aspect，描述通知于切入点点对应关系（通知+切入点）</p><p>目标对象：Target，通知所有应用对象</p><h2 id="通知类型"><a href="#通知类型" class="headerlink" title="通知类型"></a>通知类型</h2><p>@Around环绕通知，次注解标注的通知方法在目标方法前，后都被执行</p><p>@Before前置通知，此注解的通知方法在目标方法前执行</p><p>@After 后置通知，此注解标注的通知方法后被执行，无论是否异常都会被执行</p><p>@AfterReturing 返回通知，次注解标注的通知方法在目标方法后被执行，有异常不会被执行</p><p>@AfterThrowing：异常后通知，此注解的通知方法发生异常后执行</p><p>注意事项</p><ul><li>@Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行，其他通知不需要考虑目标执行方法</li><li>@Around环绕通知方法的返回值，必须指定为Object，来接收原始方法的返回值</li></ul><p>切入点表达式</p><p>@PointCut该注解的作用时将公共的切点表达式抽取出来，需要用到时引用该切点表达式即可</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Pointcut(&quot;excution(* com.itheima.service.impl.DeptServiceImpl.*(..))&quot;)</span><br><br>Private <span class="hljs-keyword">void</span> <span class="hljs-title function_">pt</span><span class="hljs-params">()</span><br><br><br><br><span class="hljs-meta">@Before(&quot;pt()&quot;)</span><br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">before</span><span class="hljs-params">()</span>&#123;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>通知的执行顺序</p><p>当有多个切面的切入点都匹配到了目标方法，目标方法运行时，多个通知方法都会被执行</p><p>和类名字母排序有关</p><p>1.不同切面类中，默认按照切面类的类名字母排序；</p><p>目标方法前的通知方法：字母排名靠前的先执行</p><p>目标方法后的通知方法：字母排名靠前的先执行</p><p>2.@Order(num)设置执行顺序</p><p>目标方法前的通知：数字小的先执行</p><p>目标方法后的通知：数字小的后执行</p><h2 id="切入点表达式"><a href="#切入点表达式" class="headerlink" title="切入点表达式"></a>切入点表达式</h2><p>切入点表达式：描述切入点方法的一种表达式</p><p>作用：主要用来决定项目中哪些方法需要加入通知</p><p>常见形式：</p><ul><li>execution(…)根据方法的签名来匹配</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)<br></code></pre></td></tr></table></figure><p>其中带？的表示可以省略的部分</p><p>访问修饰符：可省略</p><p>包名.类名：可省略（不建议省略）</p><p>Throws 异常：可省略</p><ul><li>可以使哟童年通配符</li></ul><p>*：单个独立的任意符号，可以通配任意返回值、包名、类名、方法名、任意类型的一个参数，也可以通配包、类、方法名的一部分</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">execution(* com.*.service.*.update*(*))<br></code></pre></td></tr></table></figure><p>..：多个连续的任意符号，可以通配任意层级的包、或任意类型、任意个数的参数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">execution(* com.itheima..DeptService.*(..))<br></code></pre></td></tr></table></figure><p>|| 或</p><p>书写建议：</p><p>所有业务方法名在命名时尽量规范，方便切入点表达式快速匹配</p><p>描述切入点方法通常给予接口描述，而不时直接描述实现类，增强拓展性</p><p>在满足业务需要的前提下，尽量缩小切入点的匹配范围。</p><ul><li>@annotation(…)根据注解匹配</li></ul><p>第一步新建一个自定义注解Mylog</p><p>加两个原注解描述</p><p>@Retention(RetentionPoliycy.RUNTIME)&#x2F;&#x2F;运行时有效</p><p>@Target(ElementType.METHOD)&#x2F;&#x2F;作用目标（方法）</p><p>第二部在业务方法上加@Mylog注解</p><p>然后在切入点表达式</p><p>@Pointcut(“@annotation(com.itheima.aop.Mylog)”)去引入这个注解</p><h2 id="连接点"><a href="#连接点" class="headerlink" title="连接点"></a>连接点</h2><p>在Spring中用JoinPoint抽象了连接点，用它可以获得方法执行时的相关信息、方法名、方法参数等</p><p>对于@Around通知，获取连接点信息只能使用ProceedingJoinPoint</p><p>对于其他四种通知，获取连接点信息只能使用JoinPoint，它是ProceedingJoinPoint的父类型</p><p>获取目标对象的类名 obj.getTarget().getClass().getName()</p><p>获取目标方法的方法名obj.joinPoint.getSignature().getName();</p><p>获取目标方法运行时传入的参数obj.getArgs()</p><p>放行目标方法执行obj.proceed()</p><p>获取目标方法运行的返回值obj就是上面proceed的返回值</p><h2 id="配置优先级"><a href="#配置优先级" class="headerlink" title="配置优先级"></a>配置优先级</h2><h2 id="Maven分模块开发"><a href="#Maven分模块开发" class="headerlink" title="Maven分模块开发"></a>Maven分模块开发</h2><p>分模块开发需要先正对模块功能进行设计，再进行编码，不会先将工程开发完毕，然后进行拆分</p><p>创建maven模块tlias-pojo，存放实体类</p><p>创建maven模块tlias-utils，存放工具类</p><p>1.创建一个maven模块，最外面的包名保持一致，吧pojo全部类复制到爆虾</p><p>2.引入xml</p><h2 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h2><p><parent></parent>子标签使用这个标签说明那个是父工程</p><ol><li>创建maven模块tlias-parent,该工程为父工程，设置打包方式为pom：<packaging>pom</packaging></li><li></li><li></li></ol><p>一个工程只能继承一个父工程，单支持多重继承</p><p>jar：普通模块打包，springboot项目基本都是jar包内嵌tomaacat运行</p><p>war：普通web程序打包，需要部署在外部的tomacat服务器中运行</p><p>pom：父工程或聚合工程，该模块不屑代码，进行依赖管理</p><p>在子工程中，配置了继承关系之后，坐标中groupId是可以省略的，业务会自动继承父工程的</p><p>relativePath指定父工程的pom文件的相对位置（如果不指定，将从本地仓库&#x2F;远程仓库查找该工程）</p><p>若父子工程都配置了同一个依赖的不同版本，以子工程的为标准</p><h2 id="版本锁定"><a href="#版本锁定" class="headerlink" title="版本锁定"></a>版本锁定</h2><p><dependencyManagement>来统一管理依赖版本</p><p>在父工程使用此标签，在子工程引入时就可以不用指定版本</p><p><properties>中通过自定义集中维护版本号</p><h2 id="聚合"><a href="#聚合" class="headerlink" title="聚合"></a>聚合</h2><p>将多个模块组装成一个整体，同时进行项目构建</p><p>聚合工程：一个不具有任何业务功能的“空”工程（有切仅于一个pom文件）</p><p>作用：快速构建项目</p><p>maven中可以通过<modules>设置当前聚合工程所包含的子模块名称</p><p>在父工程中</p>]]></content>
    
  </entry>
  
  <entry>
    <title>VSCode 最全实用插件目录</title>
    <link href="https://alan.org.cn/posts/vscode%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6.html"/>
    <id>https://alan.org.cn/posts/vscode%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6.html</id>
    <published>2023-04-10T06:24:00.000Z</published>
    <updated>2026-06-04T04:10:49.244Z</updated>
    
    
    
    <category term="技术分享" scheme="https://alan.org.cn/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
      
      
    <summary type="html">&lt;p&gt;一、必备插件&lt;/p&gt;
&lt;p&gt;🌾Chinese（中文）&lt;br&gt;🤡Settings Sync（配置同步到云端）&lt;br&gt;🌈wakatime（编程时间及行为跟踪统计）&lt;br&gt;二、效率神器&lt;/p&gt;
&lt;p&gt;✨HTML Snippets（代码提示）&lt;br&gt;🍎Auto Close </summary>
      
    
    
    <content type="html"><![CDATA[<p>一、必备插件</p><p>🌾Chinese（中文）<br>🤡Settings Sync（配置同步到云端）<br>🌈wakatime（编程时间及行为跟踪统计）<br>二、效率神器</p><p>✨HTML Snippets（代码提示）<br>🍎Auto Close Tag（自动闭合标签）<br>🌈Bracket Pair Colorizer（括号做颜色区分）<br>🤙Browser Preview（VSCode里面打开浏览器）<br>🎯REST Client（接口调试）<br>💎CSS Peek（css样式查看器）<br>🥝Partial Diff（文件比较）<br>🤙Npm Intellisense（自动完成导入语句中的npm模块）<br>🎯open in browser（快速打开html文件到浏览器预览）<br>💎Path Intellisense（快速引入文件）<br>🐳Image preview（预览图片）<br>三、Git集成</p><p>🏓GitHub Pull requests（ 查看和管理GitHub拉取请求和问题）<br>🏓Git Graph（Git 图形化显示和操作）<br>🐳GitLens（快速查看更改行或代码块的对象）<br>🐳GitHistory（可查看和搜索git日志以及图形和详细信息）<br>四、美化</p><p>🌈（黑白两款皮肤）<br>🥝vscode-icons（漂亮的目录树图标主题）<br>🌈Beautify（右键鼠标一键格式化）<br>🥝Vetur（官方钦定Vue插件）<br>🌈Better Align（代码优雅排版）<br>🥝Better Comments（丰富注释颜色）<br>五、代码规范</p><p>🧣change-case（变量命名规范）<br>🧣JavaScript Booster（代码改进）<br>🧣JavaScript (ES6) code snippets（智能提示与快速输入）<br>🧣ESlint（严谨的规范书写）<br>🧣TSLint（书写规范）<br>🧣Code Spell Checker（拼写检查程序）<br>🧣koroFileHeader（生成文件头部注释和函数注释）<br>六、装X神器</p><p>🎽Markdown All in One（书写Markdown）<br>🤡vscode-drawio（画流程图）<br>🍋Polacode-2020（转化成一张逼格满满的图片）<br>🍎Live Share（与他人实时进行协作式编辑和调试）</p>]]></content>
    
  </entry>
  
  <entry>
    <title>vim使用笔记</title>
    <link href="https://alan.org.cn/posts/note-neovim.html"/>
    <id>https://alan.org.cn/posts/note-neovim.html</id>
    <published>2023-04-06T02:26:32.000Z</published>
    <updated>2026-06-04T04:10:49.244Z</updated>
    
    
    
    <category term="技术分享" scheme="https://alan.org.cn/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;vim-替换语法&quot;&gt;&lt;a href=&quot;#vim-替换语法&quot; class=&quot;headerlink&quot; title=&quot;vim 替换语法&quot;&gt;&lt;/a&gt;vim 替换语法&lt;/h2&gt;&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;t</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="vim-替换语法"><a href="#vim-替换语法" class="headerlink" title="vim 替换语法"></a>vim 替换语法</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">:[range]s/目标字符串/替换字符串/[option]<br></code></pre></td></tr></table></figure><ul><li>range:表示搜索范围，默认表示当前行;</li><li>range字段值1,10表示从第1到第10行;</li><li>%表示整个文件(相当于1,$);</li><li>而.,$代表从当前行到本文件的末尾</li><li>s：substitute的简写，表示执行替换字符串操作;</li><li>option:表示操作类型，默认只对第一个匹配的字符进行替换；</li><li>option字段值g(global)表示全局替换;</li><li>c(comfirm)表示操作时需要确认;</li><li>i(ignorecase)表示不区分大小写;</li></ul><h2 id="宏使用"><a href="#宏使用" class="headerlink" title="宏使用"></a>宏使用</h2><ul><li>录制宏<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">q&lt;name&gt; # 按q+宏名开始录制<br></code></pre></td></tr></table></figure></li><li>使用宏<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">@&lt;name&gt; # 使用@来使用宏<br></code></pre></td></tr></table></figure></li></ul><h2 id="书签使用"><a href="#书签使用" class="headerlink" title="书签使用"></a>书签使用</h2><ul><li>定义书签<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">m&lt;name&gt; # 使用m来定义书签<br></code></pre></td></tr></table></figure></li><li>使用书签<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">&#x27;&lt;name&gt; 使用&#x27;来跳转<br></code></pre></td></tr></table></figure></li><li>删除书签<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">:delm&#123;marks&#125; # 删除一个书签<br></code></pre></td></tr></table></figure></li><li>显示全部书签<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">:marks<br></code></pre></td></tr></table></figure></li></ul><h2 id="代码折叠"><a href="#代码折叠" class="headerlink" title="代码折叠"></a>代码折叠</h2><ul><li>zf 启动折叠<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">zfi&#123;      # 折叠光标当前所在的大括号&#123;里面的文本<br>zfa&#123;      # 折叠光标当前所在的大括号&#123;里面的文本和大括号本身<br>zfG       # 从当前光标所在行开始，折叠到文件尾<br>zf10j     # 从当前光标所在行开始，继续向下折叠10行<br>zfip      # 折叠内部段落<br></code></pre></td></tr></table></figure></li><li>展开光标当前所在折叠zo,o估计是open</li><li>收缩光标当前所在折叠zc,c估计是close</li><li>其他<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">za    # 切换(alternative)折叠状态,只能用在已折叠/未折叠的行<br>zR    # 展开所有折叠<br>zM    # 收缩所有折叠<br></code></pre></td></tr></table></figure></li></ul><h2 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h2><ul><li>gcc注释前期行</li><li>gc注释选中的区域</li></ul>]]></content>
    
  </entry>
  
  <entry>
    <title>vim配置自动补全及代码高亮</title>
    <link href="https://alan.org.cn/posts/2023-2-2_vim.html"/>
    <id>https://alan.org.cn/posts/2023-2-2_vim.html</id>
    <published>2023-02-01T18:04:23.000Z</published>
    <updated>2026-06-04T04:10:49.244Z</updated>
    
    
    
    <category term="技术分析" scheme="https://alan.org.cn/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E6%9E%90/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;代码高亮&quot;&gt;&lt;a href=&quot;#代码高亮&quot; class=&quot;headerlink&quot; title=&quot;代码高亮&quot;&gt;&lt;/a&gt;代码高亮&lt;/h2&gt;&lt;h3 id=&quot;第一步&quot;&gt;&lt;a href=&quot;#第一步&quot; class=&quot;headerlink&quot; title=&quot;第一步&quot;&gt;&lt;/a&gt;第一</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="代码高亮"><a href="#代码高亮" class="headerlink" title="代码高亮"></a>代码高亮</h2><h3 id="第一步"><a href="#第一步" class="headerlink" title="第一步"></a>第一步</h3><p>我们先在～目录下创建一个新的用户级vimrc文件，所以我们要先在终端中输入：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cp</span> /usr/share/vim/vimrc ~/.vimrc<br></code></pre></td></tr></table></figure><p>用此代码在～目录下创建一个新的用户级vimrc文件</p><h3 id="第二步"><a href="#第二步" class="headerlink" title="第二步"></a>第二步</h3><p>我们要打开新创建的文件.vimrc，所以我们要先在终端中输入：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">vi ~/.vimrc<br></code></pre></td></tr></table></figure><h3 id="第三步："><a href="#第三步：" class="headerlink" title="第三步："></a>第三步：</h3><p>我们要在我们打开的文件的末尾加上两行命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">set</span> nu<br>syntax on<br></code></pre></td></tr></table></figure><p>最后附上其它vim设置(从网上找来的)：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">set</span> nocompatible <span class="hljs-string">&quot; 关闭 vi 兼容模式 </span><br><span class="hljs-string">syntax on &quot;</span> 自动语法高亮 <br>colorscheme molokai <span class="hljs-string">&quot; 设定配色方案 </span><br><span class="hljs-string">set number &quot;</span> 显示行号 <br><span class="hljs-built_in">set</span> cursorline <span class="hljs-string">&quot; 突出显示当前行 </span><br><span class="hljs-string">set ruler &quot;</span> 打开状态栏标尺 <br><span class="hljs-built_in">set</span> shiftwidth=4 <span class="hljs-string">&quot; 设定 &lt;&lt; 和 &gt;&gt; 命令移动时的宽度为 4 </span><br><span class="hljs-string">set softtabstop=4 &quot;</span> 使得按退格键时可以一次删掉 4 个空格 <br><span class="hljs-built_in">set</span> tabstop=4 <span class="hljs-string">&quot; 设定 tab 长度为 4 </span><br><span class="hljs-string">set nobackup &quot;</span> 覆盖文件时不备份 <br><span class="hljs-built_in">set</span> autochdir <span class="hljs-string">&quot; 自动切换当前目录为当前文件所在的目录 </span><br><span class="hljs-string">filetype plugin indent on &quot;</span> 开启插件 <br><span class="hljs-built_in">set</span> backupcopy=<span class="hljs-built_in">yes</span> <span class="hljs-string">&quot; 设置备份时的行为为覆盖 </span><br><span class="hljs-string">set ignorecase smartcase &quot;</span> 搜索时忽略大小写，但在有一个或以上大写字母时仍保持对大小写敏感 <br><span class="hljs-built_in">set</span> nowrapscan <span class="hljs-string">&quot; 禁止在搜索到文件两端时重新搜索 </span><br><span class="hljs-string">set incsearch &quot;</span> 输入搜索内容时就显示搜索结果 <br><span class="hljs-built_in">set</span> hlsearch <span class="hljs-string">&quot; 搜索时高亮显示被找到的文本 </span><br><span class="hljs-string">set noerrorbells &quot;</span> 关闭错误信息响铃 <br><span class="hljs-built_in">set</span> novisualbell <span class="hljs-string">&quot; 关闭使用可视响铃代替呼叫 </span><br><span class="hljs-string">set t_vb= &quot;</span> 置空错误铃声的终端代码 <br><span class="hljs-string">&quot; set showmatch &quot;</span> 插入括号时，短暂地跳转到匹配的对应括号 <br><span class="hljs-string">&quot; set matchtime=2 &quot;</span> 短暂跳转到匹配括号的时间 <br><span class="hljs-built_in">set</span> magic <span class="hljs-string">&quot; 设置魔术 </span><br><span class="hljs-string">set hidden &quot;</span> 允许在有未保存的修改时切换缓冲区，此时的修改由 vim 负责保存 <br><span class="hljs-built_in">set</span> guioptions-=T <span class="hljs-string">&quot; 隐藏工具栏 </span><br><span class="hljs-string">set guioptions-=m &quot;</span> 隐藏菜单栏 <br><span class="hljs-built_in">set</span> smartindent <span class="hljs-string">&quot; 开启新行时使用智能自动缩进 </span><br><span class="hljs-string">set backspace=indent,eol,start </span><br><span class="hljs-string">&quot;</span> 不设定在插入状态无法用退格键和 Delete 键删除回车符 <br><span class="hljs-built_in">set</span> cmdheight=1 <span class="hljs-string">&quot; 设定命令行的行数为 1 </span><br><span class="hljs-string">set laststatus=2 &quot;</span> 显示状态栏 (默认值为 1, 无法显示状态栏) <br><span class="hljs-built_in">set</span> statusline=\ %&lt;%F[%1*%M%*%n%R%H]%=\ %y\ %0(%&#123;&amp;fileformat&#125;\ %&#123;&amp;encoding&#125;\ %c:%l/%L%)\ <br><span class="hljs-string">&quot; 设置在状态行显示的信息 </span><br><span class="hljs-string">set foldenable &quot;</span> 开始折叠 <br><span class="hljs-built_in">set</span> foldmethod=syntax <span class="hljs-string">&quot; 设置语法折叠 </span><br><span class="hljs-string">set foldcolumn=0 &quot;</span> 设置折叠区域的宽度 <br>setlocal foldlevel=1 <span class="hljs-string">&quot; 设置折叠层数为 </span><br><span class="hljs-string">&quot;</span> <span class="hljs-built_in">set</span> foldclose=all <span class="hljs-string">&quot; 设置为自动关闭折叠 </span><br><span class="hljs-string">&quot;</span> nnoremap &lt;space&gt; @=((foldclosed(line(&#x27;.&#x27;)) &lt; 0) ? <span class="hljs-string">&#x27;zc&#x27;</span> : <span class="hljs-string">&#x27;zo&#x27;</span>)&lt;CR&gt; <br><span class="hljs-string">&quot; 用空格键来开关折叠</span><br></code></pre></td></tr></table></figure><h2 id="代码自动补全-Python"><a href="#代码自动补全-Python" class="headerlink" title="代码自动补全(Python)"></a>代码自动补全(Python)</h2><p>1.下载插件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">wget https://github.com/rkulla/pydiction/archive/master.zip<br></code></pre></td></tr></table></figure><p>2.解压缩</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">unzip -q master<br></code></pre></td></tr></table></figure><p>3.创建插件路径</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">mkdir</span> -p ~/.vim/tools/pydiction<br><span class="hljs-built_in">cp</span> -r pydiction-master/after ~/.vim<br><span class="hljs-built_in">cp</span> pydiction-master/complete-dict ~/.vim/tools/pydiction<br></code></pre></td></tr></table></figure><p>4.在vimrc中添加路径</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> vim ~/.vimrc<br></code></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash">filetype plugin on<br><span class="hljs-built_in">let</span> g:pydiction_location = <span class="hljs-string">&#x27;~/.vim/tools/pydiction/complete-dict&#x27;</span><br><span class="hljs-built_in">let</span> g:pydiction_menu_height =5<br></code></pre></td></tr></table></figure><h2 id="代码自动补全-Java"><a href="#代码自动补全-Java" class="headerlink" title="代码自动补全(Java)"></a>代码自动补全(Java)</h2><ol><li>下载：</li></ol><p>官方链接</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://www.vim.org/scripts/script.php?script_id=1785<br></code></pre></td></tr></table></figure><p>2.在Linux系统的home目录（就是盘符为~的这个目录）下新建.vim文件夹</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cd</span> ~<br><span class="hljs-built_in">mkdir</span> .vim<br></code></pre></td></tr></table></figure><p>解压文件到.vim目录下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">unzip javacomplete0.77.1.2.zip -d ~/.vim<br></code></pre></td></tr></table></figure><p>此时会得到autoload 和 doc两个文件夹</p><p>进入autoload文件夹：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cd</span> <span class="hljs-built_in">autoload</span><br></code></pre></td></tr></table></figure><p>里面有一个Reflection.java文件，编译它生成Reflection.class:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">javac Reflection.java<br></code></pre></td></tr></table></figure><p>如果编译不了，表示你还没安装jdk，请先安装jdk和配置好java的环境变量。</p><p>3.配置环境变量</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">vim /etc/profile<br></code></pre></td></tr></table></figure><p>在Java的CLASSPATH后面添加 ~&#x2F;.vim&#x2F;autoload<br>代码如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">export</span> JAVA_HOME=/usr/local/jdk1.8.0_311<br><span class="hljs-built_in">export</span> CLASSPATH=.:<span class="hljs-variable">$&#123;JAVA_HOME&#125;</span>/jre/lib/rt.jar:<span class="hljs-variable">$&#123;JAVA_HOME&#125;</span>/lib/dt.jar:<span class="hljs-variable">$&#123;JAVA_HOME&#125;</span>/lib/tools.jar:~/.vim/autoload<br><span class="hljs-built_in">export</span> PATH=<span class="hljs-variable">$PATH</span>:<span class="hljs-variable">$&#123;JAVA_HOME&#125;</span>/bin<br></code></pre></td></tr></table></figure><p>退出文件编辑，在命令行窗口输入如下命令，让配置文件生效：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">source</span> /etc/profile<br></code></pre></td></tr></table></figure><ol start="4"><li>修改vim的配置文件.vimrc</li></ol><p>不同系统这个vim的配置文件是不一样的，我用的是CentOS8,默认是没有这个配置文件的，需要在当前账号的home目录下新建这个配置文件。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cd</span> ~<br><span class="hljs-built_in">mkdir</span> .vimrc<br></code></pre></td></tr></table></figure><p>然后打开配置文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">vim .vimrc<br></code></pre></td></tr></table></figure><p>添加如下代码：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">setlocal omnifunc=javacomplete#Complete<br>autocmd Filetype java <span class="hljs-built_in">set</span> omnifunc=javacomplete#Complete <br>autocmd Filetype java <span class="hljs-built_in">set</span> completefunc=javacomplete#CompleteParamsInf<br>inoremap &lt;buffer&gt; &lt;C-X&gt;&lt;C-U&gt; &lt;C-X&gt;&lt;C-U&gt;&lt;C-P&gt; <br>inoremap &lt;buffer&gt; &lt;C-S-Space&gt; &lt;C-X&gt;&lt;C-U&gt;&lt;C-P&gt;<br>autocmd Filetype java,javascript,jsp inoremap &lt;buffer&gt;  .  .&lt;C-X&gt;&lt;C-O&gt;&lt;C-P&gt;<br></code></pre></td></tr></table></figure><p>还可以添加其他命令，具体的可以去官网看：<a href="https://www.vim.org/scripts/script.php?script_id=1785%E3%80%82">https://www.vim.org/scripts/script.php?script_id=1785。</a></p>]]></content>
    
  </entry>
  
  <entry>
    <title>Java 学习路线</title>
    <link href="https://alan.org.cn/posts/java%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF.html"/>
    <id>https://alan.org.cn/posts/java%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF.html</id>
    <published>2023-01-30T14:09:00.000Z</published>
    <updated>2026-06-04T04:10:49.244Z</updated>
    
    
    
    <category term="技术路线" scheme="https://alan.org.cn/tags/%E6%8A%80%E6%9C%AF%E8%B7%AF%E7%BA%BF/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;摘要&quot;&gt;&lt;a href=&quot;#摘要&quot; class=&quot;headerlink&quot; title=&quot;摘要&quot;&gt;&lt;/a&gt;摘要&lt;/h1&gt;&lt;p&gt;此篇文章搬运自 程序员鱼皮。&lt;/p&gt;
&lt;div class=&quot;tag-plugin copy&quot;&gt;&lt;input class=&quot;copy-are</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h1><p>此篇文章搬运自 程序员鱼皮。</p><div class="tag-plugin copy"><input class="copy-area" id="copy_1" value="https://luxian.yupi.icu/"><button class="copy-btn" onclick="util.copy(&quot;copy_1&quot;,&quot;复制成功&quot;)"><svg class="icon copy-btn" viewBox="0 0 16 16" version="1.1" width="16" height="16" ><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg></button></div><h1 id="Java-学习路线"><a href="#Java-学习路线" class="headerlink" title="Java 学习路线"></a>Java 学习路线</h1><blockquote><p>Java 学习路线一条龙版 by 程序员鱼皮</p></blockquote><p>💂 + 💻 &#x3D; 👴🏽</p><p>建议先观看视频导读：<a href="https://www.bilibili.com/video/BV1Qf4y1K7ff/">https://www.bilibili.com/video/BV1Qf4y1K7ff/</a></p><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><p><img src="https://qiniuyun.code-nav.cn/img/image-20211127235325557.png" alt="image-20211127235325557"></p><h2 id="路线特点"><a href="#路线特点" class="headerlink" title="路线特点"></a>路线特点</h2><ol><li>最新，完整一条龙的大厂 Java 学习路线，从入门到入土</li><li>同时适用于想全面学习 &#x2F; 快速求职的同学，可以根据符号来定制自己的专属学习路线</li><li>给出目标、学习建议、关键知识点、最优资源以及各类资源推荐（视频、书籍、文档、项目、工具等）</li><li>鱼皮自己是 Java 开发者，融入个人自学和从业的经历 + 思考，让路线有灵魂</li><li>划分阶段、更有计划，且在最后给出持续学习的方向、探索 Java 程序员发展的无限可能</li><li>完全开源，回馈社区，持续更新！</li></ol><h2 id="符号表"><a href="#符号表" class="headerlink" title="符号表"></a>符号表</h2><p>可以通过路线知识点前的表情字符，根据自己的实际情况选择学习：</p><ul><li>🌕 所有同学必须学习！！！</li><li>🌖 非常急着找工作，才可不学；目标大厂，必须学习！</li><li>🌗 急着找工作的话，可不学；目标大厂，建议学习</li><li>🌘 时间充足的话，再去学</li><li>⭐ 表示推荐资源</li></ul><h2 id="写在开头"><a href="#写在开头" class="headerlink" title="写在开头"></a>写在开头</h2><p>首先呢，我们要了解 Java 的应用场景和就业方向，看看和自己的学习目的是否一致。</p><p>目前，Java 由于其优秀的特性以及火爆的生态，岗位需求量大，是后台开发、安卓开发、大数据开发的主流编程语言，因此学习资源也很丰富，值得学习！</p><p>有关更多编程语言的选择，大家可以看我的视频了解：<a href="https://www.bilibili.com/video/BV1y3411r7pX/">https://www.bilibili.com/video/BV1y3411r7pX/</a></p><h2 id="阶段-0：前期准备"><a href="#阶段-0：前期准备" class="headerlink" title="阶段 0：前期准备"></a>阶段 0：前期准备</h2><ol><li>准备好一款在线、随时随地写代码的工具，不用在本地去安装和配置，是初学者的好帮手。<ul><li>推荐网站 - 菜鸟工具：<a href="https://c.runoob.com/compile/10">https://c.runoob.com/compile/10</a></li></ul></li><li>准备一款记笔记的软件，学编程的过程中老师的思路、自己遇到的问题、解决问题的方法、心得感悟、遇到的好资料，都要记下来哦，好记性不如烂笔头，可以帮助你事半功倍。<ul><li>推荐本地软件 Typora：<a href="https://www.typora.io/">https://www.typora.io/</a> （线上可以用 xx 文档）</li><li>推荐使用 Markdown 语法编写，鱼皮教程：<a href="https://www.bilibili.com/video/BV1no4y1k7YT/">https://www.bilibili.com/video/BV1no4y1k7YT/</a></li></ul></li><li>想学好编程，别背代码，要多写，把这句话刻到脑子里。</li><li>选视频和找资料的小技巧：多看评论区、多看隔壁评论区、不要看过期的视频。</li><li>如何访问 GitHub：<a href="https://mp.weixin.qq.com/s/uBFsuThsZ4kdkZgqfX12qA">https://mp.weixin.qq.com/s/uBFsuThsZ4kdkZgqfX12qA</a> （上面有很多免费的编程学习资源、项目源码，程序员必备）</li></ol><h2 id="阶段-1：Java-入门"><a href="#阶段-1：Java-入门" class="headerlink" title="阶段 1：Java 入门"></a>阶段 1：Java 入门</h2><h3 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h3><p>培养兴趣、快速上手，能运行和编写简单的 Java 程序。</p><p>学完本阶段后，可以试着用 Java 解决一些数学计算问题、编写图书管理系统等桌面端 GUI 程序，甚至是五子棋之类的小游戏。</p><h3 id="🌕-Java-编程基础（45-天）"><a href="#🌕-Java-编程基础（45-天）" class="headerlink" title="🌕 Java 编程基础（45 天）"></a>🌕 Java 编程基础（45 天）</h3><h4 id="知识"><a href="#知识" class="headerlink" title="知识"></a>知识</h4><ul><li>Java 特点（看不懂没事，别背！）</li><li>环境搭建</li><li>IDEA 开发工具<ul><li>新建项目</li><li>运行调试</li><li>界面配置</li><li>插件管理</li></ul></li><li><strong>Java 基础语法</strong><ul><li>数据类型</li><li>流程控制</li></ul></li><li>数组</li><li><strong>面向对象</strong><ul><li>方法</li><li>重载</li><li>封装</li><li>继承</li><li>多态</li></ul></li><li>抽象类</li><li>接口</li><li>枚举</li><li>常用类<ul><li>String</li><li>日期时间</li></ul></li><li>集合类</li><li>泛型</li><li>注解</li><li>异常处理</li><li>多线程</li><li>IO 流</li><li>反射</li></ul><h4 id="学习建议"><a href="#学习建议" class="headerlink" title="学习建议"></a>学习建议</h4><p>初学一门语言时，一定要持续学习，不能中断！</p><p>想要学好编程，一定要多敲代码！建议先跟着书上的例子敲一遍代码，然后试着自主编写代码，并完成课后练习。</p><p>不理解代码也没关系，可以学习 Debug 后，一行一行地打断点执行，查看程序的执行过程。千万不要觉得麻烦，养成习惯后真的能节省很多重复学习的时间。</p><h4 id="经典面试题"><a href="#经典面试题" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>为什么重写 equals 还要重写 hashcode？</li><li>&#x3D;&#x3D; 和 equals 比较的区别</li><li>为啥有时会出现 4.0 - 3.6 &#x3D; 0.40000001 这种现象？</li><li>final 关键字的作用</li><li>介绍 Java 的集合类</li><li>ArrayList 和 LinkedList 的区别</li></ol><h4 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li>⭐ 韩顺平 - 零基础 30 天学会 Java：<a href="https://www.bilibili.com/video/BV1fh411y7R8">https://www.bilibili.com/video/BV1fh411y7R8</a> （900 多集，顺序安排很合理，每个知识真正的打碎了，通俗、有示例、有实战、有思想）</li></ul></li><li><p>文档</p><ul><li><p>⭐ 菜鸟教程：<a href="https://www.runoob.com/java/java-tutorial.html">https://www.runoob.com/java/java-tutorial.html</a> （可以在线练习）</p></li><li><p>廖雪峰 Java 教程：<a href="https://www.liaoxuefeng.com/wiki/1252599548343744">https://www.liaoxuefeng.com/wiki/1252599548343744</a></p></li><li><p>IDEA 中文教程：<a href="https://github.com/judasn/IntelliJ-IDEA-Tutorial">https://github.com/judasn/IntelliJ-IDEA-Tutorial</a></p></li><li><p>IDEA Mac 快捷键指南，地址：<a href="https://www.jianshu.com/p/454c71172c46">https://www.jianshu.com/p/454c71172c46</a></p></li><li><p>IDEA Win 常用快捷键，地址：<a href="https://www.jianshu.com/p/5de7cca0fefc">https://www.jianshu.com/p/5de7cca0fefc</a></p></li></ul></li><li><p>书籍</p><ul><li>《Head First Java》</li></ul></li><li><p>在线游戏</p><ul><li>Codegym：<a href="https://codegym.cc/zh">https://codegym.cc/zh</a> （玩玩前几关培养兴趣不错，但后面收费了）</li></ul></li></ul><h3 id="🌖-Java-8（3-天）"><a href="#🌖-Java-8（3-天）" class="headerlink" title="🌖 Java 8（3 天）"></a>🌖 Java 8（3 天）</h3><h4 id="知识-1"><a href="#知识-1" class="headerlink" title="知识"></a>知识</h4><ul><li>Stream API</li><li>Lambda 表达式</li><li>新日期时间 API</li><li>接口默认方法</li></ul><h4 id="学习建议-1"><a href="#学习建议-1" class="headerlink" title="学习建议"></a>学习建议</h4><p>Java 8 是如今企业开发中最主流的 Java 稳定版本，在这个版本出现了很多实用的新特性，虽然面试考点不多，但能够提升编程效率，建议学习。</p><h4 id="经典面试题-1"><a href="#经典面试题-1" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>java 8 有哪些新特性？</li><li>HashMap 在 jdk 1.7 和 1.8 的区别？</li></ol><h4 id="资源-1"><a href="#资源-1" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li>⭐ 宋红康 - 全网最全Java零基础入门教程：<a href="https://www.bilibili.com/video/BV1Kb411W75N">https://www.bilibili.com/video/BV1Kb411W75N</a> （只看 Java 8 部分即可）</li></ul></li><li><p>文档</p><ul><li>⭐ 菜鸟教程：<a href="https://www.runoob.com/java/java8-new-features.html">https://www.runoob.com/java/java8-new-features.html</a></li></ul></li><li><p>书籍</p><ul><li>《Java 8 实战》</li></ul></li><li><p>工具</p><ul><li>⭐ 在线编写运行  Java 8：<a href="https://www.tutorialspoint.com/compile_java8_online.php">https://www.tutorialspoint.com/compile_java8_online.php</a></li></ul></li><li><p>其他</p><ul><li>Java 8 小代码片段：<a href="https://github.com/biezhi/30-seconds-of-java8">https://github.com/biezhi/30-seconds-of-java8</a></li></ul></li></ul><h3 id="练手项目"><a href="#练手项目" class="headerlink" title="练手项目"></a>练手项目</h3><ul><li>Java 实现简单计算器：<a href="https://www.lanqiao.cn/courses/185">https://www.lanqiao.cn/courses/185</a></li><li>Eclipse 实现 Java 编辑器：<a href="https://www.lanqiao.cn/courses/287">https://www.lanqiao.cn/courses/287</a></li><li>一本糊涂账：<a href="https://how2j.cn/module/104.html">https://how2j.cn/module/104.html</a></li><li>Java 五子棋：<a href="https://blog.csdn.net/cnlht/article/details/8176130">https://blog.csdn.net/cnlht/article/details/8176130</a></li><li>Java 中国象棋：<a href="https://blog.csdn.net/cnlht/article/details/8205733">https://blog.csdn.net/cnlht/article/details/8205733</a></li><li>JAVA GUI 图书馆管理系统：<a href="https://github.com/uboger/LibraryManager">https://github.com/uboger/LibraryManager</a></li><li>JAVA 坦克大战小游戏：<a href="https://github.com/wangzhengyi/TankWar">https://github.com/wangzhengyi/TankWar</a></li><li>Swing 编写的俄罗斯方块：<a href="https://github.com/HelloClyde/Tetris-Swing">https://github.com/HelloClyde/Tetris-Swing</a></li><li>小小记账本：<a href="https://github.com/xenv/SmallAccount">https://github.com/xenv/SmallAccount</a> （适合了解数据库的同学）</li></ul><h3 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h3><p>学完了 Java 基础后，有些同学会感到迷茫了啊，感觉好像啥也做不出来，不知道下一步做什么，我这一身的本领该如何施展啊？</p><p>不要慌，也不要急着去学新技术，接下来我们要多用 Java 来写代码了，巩固基础，但是写什么呢？</p><p>当然是数据结构和算法！</p><h2 id="阶段-2：巩固基础"><a href="#阶段-2：巩固基础" class="headerlink" title="阶段 2：巩固基础"></a>阶段 2：巩固基础</h2><h3 id="目标-1"><a href="#目标-1" class="headerlink" title="目标"></a>目标</h3><p>想学好编程，计算机基础知识要学好。</p><p>比如算法，是程序员的灵魂。学好算法有助于我们理解程序、开拓思路，因此也是很多公司面试时考察的关键，在找工作前，还是要刷个上百道算法题目的。</p><p>我们这个阶段的目标是：熟练使用 Java 语言来编写程序，巩固 Java 基础。（那直接用 Java 来写算法题目，一举两得，岂不美哉？）</p><p>此外，建议大家利用零碎时间多去了解 <strong>计算机基础知识</strong> ，比如操作系统、计算机网络等，对你后面学习开发框架之类的知识都有帮助。</p><h3 id="Java-基础（30-天）"><a href="#Java-基础（30-天）" class="headerlink" title="Java 基础（30 天）"></a>Java 基础（30 天）</h3><h4 id="学习建议-2"><a href="#学习建议-2" class="headerlink" title="学习建议"></a>学习建议</h4><p>建议大家去阅读 《Java 核心技术卷 1》，这本书堪称经典，是帮助你复习巩固  Java 的不二之选，其中图形界面章节可以选择不看。</p><p>之后可以刷牛客的 Java 练习题，检验自己的水平，我当时刷了两遍 1000 题（每天 30 题，1 个月也就刷完了，二刷会更快！）。虽然都是选择题，但能学到很多 Java 语言的特性、避免写代码时容易犯的错误。</p><p>如果你时间不够，只是想快速找到工作，那么本章节甚至可以完全跳过，后面慢慢弥补即可。</p><h4 id="资源-2"><a href="#资源-2" class="headerlink" title="资源"></a>资源</h4><ul><li>书籍<ul><li>⭐《Java 核心技术卷 1》</li></ul></li><li>练习<ul><li>⭐ 牛客题库：<a href="https://www.nowcoder.com/intelligentTest">https://www.nowcoder.com/intelligentTest</a></li></ul></li></ul><h3 id="🌖-数据结构与算法"><a href="#🌖-数据结构与算法" class="headerlink" title="🌖 数据结构与算法"></a>🌖 数据结构与算法</h3><h4 id="知识-2"><a href="#知识-2" class="headerlink" title="知识"></a>知识</h4><ul><li><p><strong>时间 &#x2F; 空间复杂度分析</strong></p></li><li><p>数据结构</p><ul><li>数组</li><li>字符串</li><li>队列</li><li>栈</li><li>链表</li><li>集合</li><li>哈希表</li><li>树<ul><li>二叉树</li><li>多叉树</li><li>字典树</li></ul></li></ul></li><li><p>算法</p><ul><li>排序</li><li>双指针</li><li>查找</li><li>分治</li><li>动态规划</li><li>递归</li><li>回溯</li><li>贪心</li><li>位运算</li><li>DFS</li><li>BFS</li><li>图</li></ul></li></ul><h4 id="学习建议-3"><a href="#学习建议-3" class="headerlink" title="学习建议"></a>学习建议</h4><p>学算法一定要多思考、多练习！！！</p><p>在复习 Java、巩固基础的过程中，每天可以坚持用 Java 做 2 - 3 道算法题目。</p><p>不用担心看不懂，直接进入 LeetCode 学习板块 LeetBook，提供了免费的教程，文字、图解、动画讲算法、在线练习应有尽有，从 0 开始，跟着学习基础知识、跟着教程刷一些同类题目，培养算法思路。</p><p>之后再由简单到复杂、由通过率高到低，自主刷题，多看题解，举一反三。一定要把自己做过的每道题目代码都自行保存好，定期复习。时间足够的话，也可以试着自己写写题解，在网上分享一下自己的刷题思路，找到自己坚持的动力。</p><p>有条件的朋友也可以试着参与竞赛，没什么门槛，可以帮助你集中精神，而且周赛的题目还是挺友好的~</p><h4 id="经典面试题-2"><a href="#经典面试题-2" class="headerlink" title="经典面试题"></a>经典面试题</h4><ul><li>LeetCode 精选 100 道：<a href="https://leetcode-cn.com/problem-list/2cktkvj/">https://leetcode-cn.com/problem-list/2cktkvj/</a></li><li>LeetCode 精选算法 200 题：<a href="https://leetcode-cn.com/problem-list/qg88wci/">https://leetcode-cn.com/problem-list/qg88wci/</a></li></ul><h4 id="资源-3"><a href="#资源-3" class="headerlink" title="资源"></a>资源</h4><ul><li>文章<ul><li>⭐ 刷了 1000 多道算法题，一点心得：<a href="https://t.1yb.co/oT9q">https://t.1yb.co/oT9q</a> （鱼皮原创）</li></ul></li><li>在线教程<ul><li>⭐ LeetCode LeetBook：<a href="https://leetcode-cn.com/leetbook/">https://leetcode-cn.com/leetbook/</a></li></ul></li><li>在线刷题<ul><li>⭐ LeetCode：<a href="https://leetcode-cn.com/">https://leetcode-cn.com/</a></li></ul></li><li>书籍<ul><li>⭐《小灰的漫画算法》</li><li>⭐《剑指 Offer》</li><li>《程序员代码面试指南》</li><li>图解算法数据结构：<a href="https://leetcode-cn.com/leetbook/detail/illustration-of-algorithm/">https://leetcode-cn.com/leetbook/detail/illustration-of-algorithm/</a></li><li>LeetCode 101（C++）：<a href="https://github.com/changgyhub/leetcode_101">https://github.com/changgyhub/leetcode_101</a></li><li>LeetCode 题解（C++）：<a href="https://github.com/soulmachine/leetcode">https://github.com/soulmachine/leetcode</a></li><li>LeetCode Cookbook（Go 语言）：<a href="https://github.com/halfrost/LeetCode-Go">https://github.com/halfrost/LeetCode-Go</a></li></ul></li><li>视频<ul><li>尚硅谷 Java 数据结构与算法：<a href="https://www.bilibili.com/video/BV1E4411H73v">https://www.bilibili.com/video/BV1E4411H73v</a> （难度比面试的要求大一些，适合希望更全面学习的朋友）</li><li>Leetcode 真题解析：<a href="https://www.bilibili.com/video/BV1a54y1b74k">https://www.bilibili.com/video/BV1a54y1b74k</a></li></ul></li><li>工具<ul><li>VisuAlgo 数据结构和算法动态可视化：<a href="https://visualgo.net/zh">https://visualgo.net/zh</a></li><li>数据结构可视化：<a href="https://www.cs.usfca.edu/~galles/visualization/Algorithms.html">https://www.cs.usfca.edu/~galles/visualization/Algorithms.html</a></li><li>RegExr：<a href="https://www.code-nav.cn/rd/?rid=79550af2601114e9012110711798772b">https://www.code-nav.cn/rd/?rid=79550af2601114e9012110711798772b</a> （学习、创建和测试正则表达式的在线可视化工具）</li></ul></li><li>网站<ul><li>五分钟学算法：<a href="https://www.cxyxiaowu.com/">https://www.cxyxiaowu.com/</a></li></ul></li></ul><h3 id="🌘-计算机导论"><a href="#🌘-计算机导论" class="headerlink" title="🌘 计算机导论"></a>🌘 计算机导论</h3><h4 id="知识-3"><a href="#知识-3" class="headerlink" title="知识"></a>知识</h4><ul><li>计算机发展历史</li><li>计算机应用领域</li><li>计算机发展方向</li><li>计算机基本组成</li><li>二进制</li><li>编程语言发展</li></ul><h4 id="学习建议-4"><a href="#学习建议-4" class="headerlink" title="学习建议"></a>学习建议</h4><p>大学计算机专业的同学一般刚开学就会上这门课，虽说学习它并不会直接提高你的编程技能，但能够让你更了解计算机和编程，从而在一定程度上帮助你培养学习兴趣、确定学习方向。</p><p>自学的话，不用刻意去学习计算机导论，而是可以通过看视频、阅读课外读物的方式慢慢地了解计算机的故事。</p><h4 id="资源-4"><a href="#资源-4" class="headerlink" title="资源"></a>资源</h4><ul><li>视频<ul><li>《计算机科学速成课》：<a href="https://www.bilibili.com/video/BV1EW411u7th">https://www.bilibili.com/video/BV1EW411u7th</a> （从底层到上层的计算机知识科普，强烈推荐）</li></ul></li><li>书籍<ul><li>《半小时漫画计算机》</li></ul></li></ul><h3 id="🌖-操作系统"><a href="#🌖-操作系统" class="headerlink" title="🌖 操作系统"></a>🌖 操作系统</h3><h4 id="知识-4"><a href="#知识-4" class="headerlink" title="知识"></a>知识</h4><ul><li>操作系统的组成</li><li>进程、线程</li><li>进程 &#x2F; 线程间通讯方式</li><li>进程调度算法</li><li>进程 &#x2F; 线程同步方式</li><li>进程 &#x2F; 线程状态</li><li>死锁</li><li>内存管理</li><li>局部性原理</li></ul><h4 id="学习建议-5"><a href="#学习建议-5" class="headerlink" title="学习建议"></a>学习建议</h4><p>说实话，操作系统这一块知识挺枯燥的。你说说我现在项目都不会做，你又让我看这些理论，是不是想让我头秃？</p><p>我的建议是，可以先利用课余时间看一些网课或者有趣的课外书，对一些操作系统的概念先有个大致的印象，比如进程、线程、死锁，等后面有时间了再系统学习、等到找工作了再去背相关八股文。</p><p>还在校园就跟着学校的进度学习就成，自学的话可以看下《清华操作系统原理》视频，有实力的小伙伴，能看懂大黑书就更好了，但如果看不懂也别担心，这并不影响你后续知识的学习。</p><h4 id="经典面试题-3"><a href="#经典面试题-3" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>什么是死锁？死锁产生的条件？</li><li>线程有哪几种状态？</li><li>有哪些进程调度算法？</li><li>什么是缓冲区溢出？</li></ol><h4 id="资源-5"><a href="#资源-5" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li>《清华操作系统原理》：<a href="https://www.bilibili.com/video/BV1uW411f72n">https://www.bilibili.com/video/BV1uW411f72n</a></li></ul></li><li><p>书籍</p><ul><li>《编码》</li><li>《30天自制操作系统》</li><li>《现代操作系统》（难度较大，不推荐新手看）</li><li>《深入理解计算机系统》（难度较大，不推荐新手看）</li><li>《 自己动手写操作系统》（国产好书，网上可以下载）</li></ul></li><li><p>大学课件</p><ul><li>浙大操作系统：<a href="https://github.com/QSCTech/zju-icicles">https://github.com/QSCTech/zju-icicles</a></li></ul></li></ul><h3 id="🌖-计算机网络"><a href="#🌖-计算机网络" class="headerlink" title="🌖 计算机网络"></a>🌖 计算机网络</h3><h4 id="知识-5"><a href="#知识-5" class="headerlink" title="知识"></a>知识</h4><ul><li>网络分层模型</li><li>网络传输过程</li><li>IP、端口</li><li>HTTP &#x2F; HTTPS 协议</li><li>UDP &#x2F; TCP 协议</li><li>ARP 地址解析协议</li><li>网络安全</li><li>DNS 域名解析</li></ul><h4 id="学习建议-6"><a href="#学习建议-6" class="headerlink" title="学习建议"></a>学习建议</h4><p>很多学习 Java 开发的同学最后都是从事 <strong>后端开发</strong> 的工作，而计算机网络知识是后端开发的重点。</p><p>和操作系统一样，自学网络可能会很枯燥，建议先看有趣的课外书，比如《图解 HTTP》；或者有趣的视频，比如《计算机网络微课堂》。后面要找工作面试前，再重点去背一些八股文就好了。还在学校的同学好好上课一般就没问题。</p><p>学习基础能帮助自己今后发展更稳定，且更容易接受新知识，所以请不要相信基础无用论。</p><h4 id="经典面试题-4"><a href="#经典面试题-4" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>计算机网络各层有哪些协议？</li><li>TCP 和 UDP 协议的区别？</li><li>TCP 为什么需要三次握手和四次挥手？</li><li>HTTP 和 HTTPS 协议的区别？</li></ol><h4 id="资源-6"><a href="#资源-6" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li>《计算机网络微课堂》：<a href="https://www.bilibili.com/video/BV1c4411d7jb">https://www.bilibili.com/video/BV1c4411d7jb</a></li></ul></li><li><p>书籍</p><ul><li>⭐《图解 HTTP》</li><li>《网络是怎样连接的》</li><li>⭐《图解 TCP &#x2F; IP》</li></ul></li><li><p>大学课件</p><ul><li>浙大计算机网络基础：<a href="https://github.com/QSCTech/zju-icicles">https://github.com/QSCTech/zju-icicles</a></li></ul></li></ul><h3 id="尾声-1"><a href="#尾声-1" class="headerlink" title="尾声"></a>尾声</h3><p>巩固基础要花至少 1 个月的时间，当你读完《Java 核心技术卷1》并且不用查询文档也能熟练地用 Java 做题时，就可以接着往下了。</p><h2 id="阶段-3：Java-企业开发基础"><a href="#阶段-3：Java-企业开发基础" class="headerlink" title="阶段 3：Java 企业开发基础"></a>阶段 3：Java 企业开发基础</h2><h3 id="目标-2"><a href="#目标-2" class="headerlink" title="目标"></a>目标</h3><p>面向薪资编程，学习实际后台开发工作要用的基础技术和框架，并能 <strong>独立</strong> 做出一个具有完整功能的 Java Web 项目。</p><p>学完这个阶段后，你应该已经能独立开发出大多数常见的后台系统了，比如各种管理系统、商城系统等。</p><h3 id="🌕-MySQL-数据库（7-天）"><a href="#🌕-MySQL-数据库（7-天）" class="headerlink" title="🌕 MySQL 数据库（7 天）"></a>🌕 MySQL 数据库（7 天）</h3><p>企业中大部分业务数据都是用关系型数据库存储的，因此数据库是后台开发同学的必备技能，其中 MySQL 数据库是目前的主流，也是面试时的重点。</p><h4 id="知识-6"><a href="#知识-6" class="headerlink" title="知识"></a>知识</h4><ul><li>基本概念</li><li>MySQL 搭建</li><li>SQL 语句编写</li><li>约束</li><li>索引</li><li>事务</li><li>锁机制</li><li>设计数据库表</li><li>性能优化</li></ul><h4 id="学习建议-7"><a href="#学习建议-7" class="headerlink" title="学习建议"></a>学习建议</h4><p>其中，<strong>SQL 语句编写</strong> 和 <strong>设计数据库表</strong> 这两个能力一定要有！</p><p>比如让你做一个学生管理系统，你要能想到需要哪些表，比如学生表、班级表；每个表需要哪些字段、字段类型。</p><p>这就要求大家多写 SQL、多根据实际的业务场景去练习设计能力。</p><h4 id="经典面试题-5"><a href="#经典面试题-5" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>MySQL 索引的最左原则</li><li>InnoDB 和 MyIsam 引擎的区别？</li><li>有哪些优化数据库性能的方法？</li><li>如何定位慢查询？</li><li>MySQL 支持行锁还是表锁？分别有哪些优缺点？</li></ol><h4 id="资源-7"><a href="#资源-7" class="headerlink" title="资源"></a>资源</h4><ul><li>视频<ul><li>⭐ 老杜 - mysql入门基础 + 数据库实战：<a href="https://www.bilibili.com/video/BV1Vy4y1z7EX">https://www.bilibili.com/video/BV1Vy4y1z7EX</a> （较新、内容相对精炼，有习题）</li><li>尚硅谷 - MySQL基础教程：<a href="https://www.bilibili.com/video/BV1xW411u7ax">https://www.bilibili.com/video/BV1xW411u7ax</a> （小姐姐讲课，但感觉音质一般）</li></ul></li><li>在线练习<ul><li>⭐ SQL 自学网：<a href="http://xuesql.cn/">http://xuesql.cn/</a></li><li>⭐ SQL 在线运行：<a href="https://www.bejson.com/runcode/sql/">https://www.bejson.com/runcode/sql/</a></li></ul></li><li>文档<ul><li>SQL - 菜鸟教程：<a href="https://www.runoob.com/sql/sql-tutorial.html">https://www.runoob.com/sql/sql-tutorial.html</a></li><li>MySQL - 菜鸟教程：<a href="https://www.runoob.com/mysql/mysql-tutorial.html">https://www.runoob.com/mysql/mysql-tutorial.html</a></li></ul></li><li>网站<ul><li><a href="https://www.code-nav.cn/rd/?rid=b00064a76012546b016e274a3724c5f0">数据库大全</a>：果创云收录的各种数据库表设计</li></ul></li></ul><h3 id="🌕-开发框架（60-天）"><a href="#🌕-开发框架（60-天）" class="headerlink" title="🌕 开发框架（60 天）"></a>🌕 开发框架（60 天）</h3><p>Java 之所以能成为主流的企业开发语言，很大一部分原因是它完善的框架生态，用好框架，不仅能够大大提升开发效率，还能提高项目的稳定性、减少维护成本。</p><p>开发框架是后台开发工作中不可或缺的，也是面试考察的重点，一定要好好学！</p><p>不知道 Java 能做什么的朋友们，学完开发框架，就会有答案啦。</p><p>下面给大家推荐的都是企业中应用最多的主流开发框架，知识点比较零碎，就放在一起讲了。</p><h4 id="知识-7"><a href="#知识-7" class="headerlink" title="知识"></a>知识</h4><h5 id="🌕-Java-Web"><a href="#🌕-Java-Web" class="headerlink" title="🌕 Java Web"></a>🌕 Java Web</h5><ul><li><p>描述：Java 网页应用开发基础</p></li><li><p>一丢丢前端基础</p></li><li><p>XML</p></li><li><p>JSON</p></li><li><p>Servlet</p></li><li><p>Filter</p></li><li><p>Listener</p></li><li><p>JSP</p></li><li><p>JSTL</p></li><li><p>Cookie</p></li><li><p>Session</p></li></ul><h5 id="🌕-Spring-5"><a href="#🌕-Spring-5" class="headerlink" title="🌕 Spring 5"></a>🌕 Spring 5</h5><ul><li>描述：Java 轻量级应用框架</li><li>IOC</li><li>AOP</li><li>事务</li></ul><h5 id="🌕-SpringMVC"><a href="#🌕-SpringMVC" class="headerlink" title="🌕 SpringMVC"></a>🌕 SpringMVC</h5><ul><li>描述：Java 轻量级 web 开发框架</li><li>什么是 MVC？</li><li>请求与响应</li><li>Restful API</li><li>拦截器</li><li>配置</li><li>执行过程</li></ul><h5 id="🌕-MyBatis"><a href="#🌕-MyBatis" class="headerlink" title="🌕 MyBatis"></a>🌕 MyBatis</h5><ul><li>描述：数据访问框架，操作数据库进行增删改查等操作</li><li>增删改查</li><li>全局配置</li><li>动态 SQL</li><li>缓存</li><li>和其他框架的整合</li><li>逆向工程</li></ul><h5 id="🌗-MyBatis-Plus"><a href="#🌗-MyBatis-Plus" class="headerlink" title="🌗 MyBatis Plus"></a>🌗 MyBatis Plus</h5><ul><li>描述：Mybatis 的增强工具，能够简化开发、提高效率</li><li>引入</li><li>通用 CRUD</li><li>条件构造器</li><li>代码生成器</li><li>插件扩展</li><li>自定义全局操作</li></ul><h5 id="🌕-SpringBoot-2"><a href="#🌕-SpringBoot-2" class="headerlink" title="🌕 SpringBoot 2"></a>🌕 SpringBoot 2</h5><ul><li>描述：简化 Spring 应用的初始搭建以及开发过程，提高效率</li><li>常用注解</li><li>资源整合</li><li>高级特性</li><li>本地热部署</li></ul><h5 id="🌗-Spring-Security"><a href="#🌗-Spring-Security" class="headerlink" title="🌗 Spring Security"></a>🌗 Spring Security</h5><ul><li>描述：Spring 的安全管理框架</li><li>用户认证</li><li>权限管理</li><li>相关技术：Shiro</li></ul><h5 id="🌗-Maven-Gradle"><a href="#🌗-Maven-Gradle" class="headerlink" title="🌗 Maven &#x2F; Gradle"></a>🌗 Maven &#x2F; Gradle</h5><ul><li>描述：项目管理工具</li><li>构建</li><li>依赖管理</li><li>插件</li><li>配置</li><li>子父工程</li><li>多模块打包构建</li><li>Nexus 私服搭建</li></ul><h4 id="学习建议-8"><a href="#学习建议-8" class="headerlink" title="学习建议"></a>学习建议</h4><p>由于技术较多，且框架之间存在一定的联系，因此建议大家看同一系列的视频教程（尚硅谷、狂神说等等都可以），以保证学习内容的连续以及体验上的一致。</p><p>学这些技术的时候，<strong>千万不能懒</strong> ！一定要多记笔记，并且跟着老师写代码。原理部分不要太过纠结，先以能跟着敲出代码、写出可运行的项目为主，有些东西做出来也能帮助你更好地理解理论。</p><p>学习顺序挺重要的，建议按我推荐的顺序学，不要一上手就学 Spring Boot。只有先学习下自己整合框架的方法，才能帮你理解 SpringBoot 解决的问题，感受到它的方便和高效。</p><p>Maven &#x2F; Gradle 当成工具用就好，面试基本不问，跟着框架教程去用就行了，急着找工作的话，先不用花太多时间去深入学。大厂面试问这个的也不多。</p><h4 id="经典面试题-6"><a href="#经典面试题-6" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>Spring 的 IOC 和 AOP 是什么，有哪些优点？</li><li>Spring 框架用到了哪些设计模式？</li><li>介绍 Spring Bean 的生命周期</li><li>MyBatis 如何实现延迟加载？</li><li>介绍 MyBatis 的多级缓存机制</li></ol><h4 id="资源-8"><a href="#资源-8" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频（按顺序看）</p><ul><li><p>⭐ 尚硅谷最新版 JavaWeb 全套教程：<a href="https://www.bilibili.com/video/BV1Y7411K7zz">https://www.bilibili.com/video/BV1Y7411K7zz</a> （前端部分最好也看下）</p></li><li><p>⭐ 尚硅谷 - Spring 5 框架最新版教程（idea版）：<a href="https://www.bilibili.com/video/BV1Vf4y127N5">https://www.bilibili.com/video/BV1Vf4y127N5</a></p></li><li><p>⭐ 尚硅谷 - SpringMVC 2021 最新教程：<a href="https://www.bilibili.com/video/BV1Ry4y1574R">https://www.bilibili.com/video/BV1Ry4y1574R</a></p></li><li><p>⭐ 尚硅谷 - MyBatis 实战教程全套完整版：<a href="https://www.bilibili.com/video/BV1mW411M737">https://www.bilibili.com/video/BV1mW411M737</a></p></li><li><p>⭐ 尚硅谷 - MyBatisPlus 教程：<a href="https://www.bilibili.com/video/BV1Ds411E76Y">https://www.bilibili.com/video/BV1Ds411E76Y</a></p></li><li><p>Maven 零基础入门教程：<a href="https://www.bilibili.com/video/BV1TW411g7hP">https://www.bilibili.com/video/BV1TW411g7hP</a> （搞不懂 Maven 可以看看）</p></li><li><p>⭐ 雷丰阳 2021 版 SpringBoot2 零基础入门：<a href="https://www.bilibili.com/video/BV19K4y1L7MT">https://www.bilibili.com/video/BV19K4y1L7MT</a></p></li><li><p>尚硅谷 - SpringSecurity 框架教程：<a href="https://www.bilibili.com/video/BV15a411A7kP">https://www.bilibili.com/video/BV15a411A7kP</a></p></li></ul></li></ul><h3 id="🌘-开发规范（3-天）"><a href="#🌘-开发规范（3-天）" class="headerlink" title="🌘 开发规范（3 天）"></a>🌘 开发规范（3 天）</h3><p>开发不规范，同事两行泪。</p><p>开发规范是团队开发中必须遵守的，有利于提高项目的开发效率、降低维护成本。</p><h4 id="知识-8"><a href="#知识-8" class="headerlink" title="知识"></a>知识</h4><ul><li>代码规范<ul><li>代码风格</li><li>命名</li><li>其他规则</li></ul></li><li>代码校验（CheckStyle）</li><li>提交规范</li></ul><h4 id="学习建议-9"><a href="#学习建议-9" class="headerlink" title="学习建议"></a>学习建议</h4><p>有时间的话，简单过一遍大厂团队的代码规范手册就好了，以后做项目的时候能想起来的话就去使用，或者从书中、网上查规范文档，再去遵守。</p><p>项目做得多了，自然会养成好的习惯，不用刻意去记（毕竟每个团队规范也不完全相同，背了也没用）。也可以直接利用开发工具自带的一些代码检查插件，帮忙养成好的编码习惯。</p><h4 id="资源-9"><a href="#资源-9" class="headerlink" title="资源"></a>资源</h4><ul><li><p>书籍</p><ul><li>⭐ 阿里巴巴 Java 开发手册：<a href="https://developer.aliyun.com/graph/java">https://developer.aliyun.com/graph/java</a> （搜索《Java开发手册》）</li></ul></li><li><p>视频</p><ul><li>华山版《Java开发手册》独家讲解：<a href="https://developer.aliyun.com/live/1201">https://developer.aliyun.com/live/1201</a></li></ul></li><li><p>文档</p><ul><li>Google Java Style Guide：<a href="https://google.github.io/styleguide/javaguide.html">https://google.github.io/styleguide/javaguide.html</a> （谷歌 Java 代码规范）</li></ul></li></ul><h3 id="🌕-Git（3-天）"><a href="#🌕-Git（3-天）" class="headerlink" title="🌕 Git（3 天）"></a>🌕 Git（3 天）</h3><p>此前大家可能听说过 GitHub，一流的代码开源托管平台。</p><p>Git 和它可不一样，是一个版本控制工具，可以更好地管理和共享项目代码，比如把自己的代码传到 GitHub 上、或者从远程下载。</p><p>无论自己做项目、还是团队开发，Git 都是现在不可或缺的神器。</p><h4 id="知识-9"><a href="#知识-9" class="headerlink" title="知识"></a>知识</h4><ul><li>区分 Git 和 GitHub</li><li>工作区</li><li>分支</li><li>代码提交、推送、拉取、回退、重置</li><li>分支操作</li><li>代码合并、解决冲突</li><li>标签</li><li>cherry-pick</li><li>Git Flow</li><li>相关技术：SVN（比较老）</li></ul><h4 id="学习建议-10"><a href="#学习建议-10" class="headerlink" title="学习建议"></a>学习建议</h4><p>每个命令跟着敲一遍，有个大致的印象，会用即可。</p><p>建议平时大家可以多把自己的代码使用 Git 命令上传到 GitHub 上，用的多了自然就熟悉了。</p><h4 id="经典面试题-7"><a href="#经典面试题-7" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>如何解决提交冲突？</li><li>提交不小心出现误操作，如何撤销？</li><li>什么是 Git Flow，它有什么好处？</li></ol><h4 id="资源-10"><a href="#资源-10" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li>⭐【尚硅谷】5h打通Git全套教程丨2021最新IDEA版 <a href="https://www.bilibili.com/video/BV1vy4y1s7k6">https://www.bilibili.com/video/BV1vy4y1s7k6</a></li></ul></li><li><p>书籍</p><ul><li>猴子都能懂的 Git 入门 <a href="https://backlog.com/git-tutorial/cn/">https://backlog.com/git-tutorial/cn/</a></li><li>⭐ GitHub 漫游指南 <a href="https://github.phodal.com/">https://github.phodal.com/</a></li></ul></li><li><p>文档</p><ul><li>GitHub 官方文档：<a href="https://docs.github.com/cn">https://docs.github.com/cn</a></li></ul></li><li><p>游戏</p><ul><li>Learning Git Branching：<a href="https://learngitbranching.js.org/?locale=zh_CN">https://learngitbranching.js.org/?locale=zh_CN</a></li></ul></li></ul><h3 id="🌖-Linux（10-天）"><a href="#🌖-Linux（10-天）" class="headerlink" title="🌖 Linux（10 天）"></a>🌖 Linux（10 天）</h3><p>企业中的很多前后台项目都是部署在 Linux 服务器上的，因此很有必要熟悉 Linux 的操作和脚本的编写。</p><p>后面学微服务、学架构都是在多台服务器操作，如果你不熟悉 Linux，会有点吃力。</p><h4 id="知识-10"><a href="#知识-10" class="headerlink" title="知识"></a>知识</h4><ul><li>Linux 系统安装</li><li>环境变量</li><li>文件管理</li><li>用户管理</li><li>内存管理</li><li>磁盘管理</li><li>进程管理</li><li>网络管理</li><li>软件包管理</li><li>服务管理</li><li>日志管理</li><li>Linux 内核</li><li><strong>常用命令</strong></li><li><strong>常用环境搭建</strong></li><li><strong>Shell 脚本编程</strong></li><li><strong>VIM 的使用</strong></li></ul><h4 id="学习建议-11"><a href="#学习建议-11" class="headerlink" title="学习建议"></a>学习建议</h4><p>多动手实践，建议自己购买一台云服务器，并且在本地搭建 Linux 虚拟机环境。</p><p>一定要自己从 0 开始手敲命令安装软件、部署服务，熟悉整个项目的上线流程。</p><p>每个命令至少要跟着敲一遍，了解它们的作用，并通过自然地练习，熟悉常用的 Linux 命令。</p><p>记不住没关系，用文档查就行了。</p><p>先会用，再理解。一般面试问的 Linux 题目也不会很难，面试前去背一下八股文就没什么问题，感兴趣的话可以去看看 Linux 内核设计。</p><h4 id="经典面试题-8"><a href="#经典面试题-8" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>如何查看某个进程的运行状态？</li><li>如何在 Linux 上查看 2 G 的大文件？</li><li>Linux 软链接和硬链接的区别</li></ol><h4 id="资源-11"><a href="#资源-11" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li>⭐ 2021 韩顺平 一周学会Linux <a href="https://www.bilibili.com/video/BV1Sv411r7vd">https://www.bilibili.com/video/BV1Sv411r7vd</a> （基于 CentOS 7.6 版本较新）</li></ul></li><li><p>书籍</p><ul><li>《鸟哥的 Linux 私房菜 —— 基础篇》<a href="http://cn.linux.vbird.org/linux_basic/linux_basic.php">http://cn.linux.vbird.org/linux_basic/linux_basic.php</a> （经典）</li></ul></li><li><p>文档</p><ul><li>Linux 工具快速教程：<a href="https://linuxtools-rst.readthedocs.io/">https://linuxtools-rst.readthedocs.io</a> （基础、工具进阶、工具参考）</li></ul></li><li><p>实战</p><ul><li>⭐ 蓝桥云课 Linux 基础入门：<a href="https://www.lanqiao.cn/courses/1">https://www.lanqiao.cn/courses/1</a></li><li>腾讯云动手实验室：<a href="https://cloud.tencent.com/developer/labs">https://cloud.tencent.com/developer/labs</a></li><li>阿里云体验实验室：<a href="https://developer.aliyun.com/adc/labs/">https://developer.aliyun.com/adc/labs/</a></li><li>阿里云知行实验室：<a href="https://start.aliyun.com/">https://start.aliyun.com/</a></li><li>华为云沙箱实验室：<a href="https://lab.huaweicloud.com/">https://lab.huaweicloud.com/</a></li></ul></li><li><p>社区</p><ul><li>Linux 中国：<a href="https://linux.cn/">https://linux.cn/</a></li></ul></li><li><p>工具</p><ul><li>⭐ Linux 命令搜索：<a href="https://wangchujiang.com/linux-command">https://wangchujiang.com/linux-command</a></li><li>Linux 命令大全手册：<a href="https://www.linuxcool.com/">https://www.linuxcool.com/</a></li><li>Linux 命令示例：<a href="http://linux-commands-examples.com/">http://linux-commands-examples.com/</a></li><li>宝塔 Linux 面板：<a href="https://www.bt.cn/">https://www.bt.cn/</a></li></ul></li></ul><h3 id="🌘-前端基础（14-天）"><a href="#🌘-前端基础（14-天）" class="headerlink" title="🌘 前端基础（14 天）"></a>🌘 前端基础（14 天）</h3><p>虽然 Java 程序员面试时基本不会出现前端相关问题，但是在企业中，往往需要前后端程序员配合完成工作。会一些前端，不仅可以提高你们的协作效率，还能提高自己对整个项目的了解和掌控力，甚至能独立开发出一个完整项目！这点也是能给面试加分的。</p><h4 id="知识-11"><a href="#知识-11" class="headerlink" title="知识"></a>知识</h4><ul><li>HTML</li><li>CSS</li><li>JavaScript<ul><li>Ajax</li></ul></li><li>Vue</li></ul><h4 id="学习建议-12"><a href="#学习建议-12" class="headerlink" title="学习建议"></a>学习建议</h4><p>不需要学习太多的前端技术，熟悉下基础的前端三件套，了解前端是如何向后端发送请求来做数据交互的一般就够了。有时间的话可以学下 Vue ，是比较容易上手的主流前端开发框架，Vue + SpringBoot 还是很香的。</p><h4 id="资源-12"><a href="#资源-12" class="headerlink" title="资源"></a>资源</h4><p>参见鱼皮原创的前端学习路线：<a href="https://mp.weixin.qq.com/s/HbzLEt6NOP9Is9F3GOnbUQ">https://mp.weixin.qq.com/s/HbzLEt6NOP9Is9F3GOnbUQ</a></p><h3 id="练手项目-1"><a href="#练手项目-1" class="headerlink" title="练手项目"></a>练手项目</h3><blockquote><p>更多请见本路线阶段 5：[项目源码（50 套）](###项目源码（50 套）) 。</p></blockquote><ul><li>HotelSystem：<a href="https://github.com/misterchaos/HotelSystem">https://github.com/misterchaos/HotelSystem</a> （酒店管理系统 Java,tomcat,mysql,servlet,jsp实现，没有使用任何框架）</li><li>超市管理系统：<a href="https://github.com/zhanglei-workspace/shopping-management-system">https://github.com/zhanglei-workspace/shopping-management-system</a></li><li>Mall4j：<a href="https://github.com/gz-yami/mall4j">https://github.com/gz-yami/mall4j</a> （Spring Boot 电商系统）</li><li>newbee-mall：<a href="https://github.com/newbee-ltd/newbee-mall">https://github.com/newbee-ltd/newbee-mall</a> （基于 Spring Boot 2.X 的全栈电商系统）</li><li>litemall：<a href="https://github.com/linlinjava/litemall">https://github.com/linlinjava/litemall</a> （小商城系统，Spring Boot 后端 + Vue 管理员前端 + 微信小程序用户前端 + Vue 用户移动端）</li><li>forum-java：<a href="https://github.com/Qbian61/forum-java">https://github.com/Qbian61/forum-java</a> （一款用 Java Spring Boot 实现的现代化社区系统）</li></ul><h3 id="尾声-2"><a href="#尾声-2" class="headerlink" title="尾声"></a>尾声</h3><p>学完这个阶段的知识后，一定要再串起来回忆一遍，必须自己独立开发一个 Java Web 项目（量级可以不大，但你学过的技术尽可能地用上），能发布到 Linux 服务器上让其他小伙伴访问就更好了~</p><p>如果你只是对 Java 感兴趣、或者只是想试着自己开发后台，并不是想靠 Java 找工作的话，学到这里就可以了。可以把更多时间投入到你主方向的学习中。</p><p>但如果你是想找 Java 方向的工作，尤其是想进大厂的话，一定要继续努力，用心学习下个阶段的企业开发进阶知识。</p><h2 id="阶段-4：Java-企业开发进阶"><a href="#阶段-4：Java-企业开发进阶" class="headerlink" title="阶段 4：Java 企业开发进阶"></a>阶段 4：Java 企业开发进阶</h2><h3 id="目标-3"><a href="#目标-3" class="headerlink" title="目标"></a>目标</h3><p>学习更多企业级开发技术和编程思想，能够结合多种技术，独立开发出架构合理的完整系统，<strong>解决实际问题</strong> 。</p><p>要了解为什么需要这个技术？什么时候用这个技术？某个需求该用哪些技术？</p><p>当然，这个阶段的内容有些过于丰富，不是所有的东西都要学，大家可以根据自己的实际情况（时间），有选择地学习。</p><h3 id="🌘-软件工程"><a href="#🌘-软件工程" class="headerlink" title="🌘 软件工程"></a>🌘 软件工程</h3><p>软件开发和管理的一些概念、原则、技术、方法、工具和经验。</p><h4 id="知识-12"><a href="#知识-12" class="headerlink" title="知识"></a>知识</h4><ul><li>软件的本质</li><li>软件特性</li><li>软件过程</li><li>软件开发原则<ul><li>开闭原则</li><li>里氏替换原则</li><li>依赖倒置原则</li><li>单一职责原则</li><li>接口隔离原则</li><li>迪米特法则</li></ul></li><li>软件过程模型</li><li>敏捷开发</li><li>软件开发模型</li><li>需求建模</li><li>软件设计</li><li>UML</li><li>体系结构设计</li><li>设计模式</li><li>软件质量管理</li><li>评审</li><li>软件质量保证</li><li>软件测试<ul><li>单元测试</li><li>集成测试</li><li>系统测试</li><li>压力测试</li><li>部署测试</li></ul></li><li>软件配置管理</li><li>软件项目管理</li><li>软件项目估算</li><li>项目进度安排</li><li>风险管理</li><li>软件过程改进<ul><li>成熟度模型</li></ul></li></ul><h4 id="学习建议-13"><a href="#学习建议-13" class="headerlink" title="学习建议"></a>学习建议</h4><p>大学软件专业的必修课，偏理论，能学到很多企业软件开发的方法，也是对软件开发同学综合能力的提升，有时间的话可以了解下。但对想要快速找工作的同学来说，忽略即可，面试基本不会问。</p><h4 id="资源-13"><a href="#资源-13" class="headerlink" title="资源"></a>资源</h4><ul><li><p>书籍</p><ul><li>《软件工程：实践者的研究方法》：<a href="https://book.douban.com/subject/6047742/">https://book.douban.com/subject/6047742/</a> （经典大黑书，讲的非常全面，但同样完整读下来还是有些难度的）</li><li>《软件开发的 201 个原则》：<a href="https://book.douban.com/subject/35628886/">https://book.douban.com/subject/35628886/</a> （工具书，看一遍挺好的）</li></ul></li><li><p>视频</p><ul><li>清华大学软件工程：<a href="https://www.bilibili.com/video/BV1eE411V7Cr">https://www.bilibili.com/video/BV1eE411V7Cr</a></li><li>北京大学软件工程：<a href="https://www.bilibili.com/video/BV1Vt411V72G">https://www.bilibili.com/video/BV1Vt411V72G</a></li><li>大连工业大学软件工程：<a href="https://www.bilibili.com/video/BV1Ns41177VM">https://www.bilibili.com/video/BV1Ns41177VM</a></li></ul></li><li><p>大学课件</p><ul><li>浙大计算机软件工程：<a href="https://github.com/QSCTech/zju-icicles">https://github.com/QSCTech/zju-icicles</a></li></ul></li></ul><h3 id="🌖-设计模式（21-天）"><a href="#🌖-设计模式（21-天）" class="headerlink" title="🌖 设计模式（21 天）"></a>🌖 设计模式（21 天）</h3><p>设计模式是软件开发中解决一类问题的通用方法。</p><p>使用设计模式能让你写出更优雅、可维护的代码，也正因如此，很多框架源码都用到了设计模式，你不学很难看懂。</p><h4 id="知识-13"><a href="#知识-13" class="headerlink" title="知识"></a>知识</h4><ul><li><p>创建型模式：对象实例化的模式，创建型模式用于解耦对象的实例化过程</p><ul><li>单例模式</li><li>工厂方法模式</li><li>抽象工厂</li><li>建造者模式</li><li>原型模式</li></ul></li><li><p>结构型模式：把类或对象结合在一起形成一个更大的结构</p><ul><li>适配器模式</li><li>组合模式</li><li>装饰器模式</li><li>代理模式</li><li>享元模式</li><li>外观模式</li><li>桥接模式</li></ul></li><li><p>行为型模式：类和对象如何交互，及划分责任和算法</p><ul><li>迭代器模式</li><li>模板方法模式</li><li>策略模式</li><li>命令模式</li><li>状态模式</li><li>责任链模式</li><li>备忘录模式</li><li>观察者模式</li><li>访问者模式</li><li>中介者模式</li><li>解释器模式</li></ul></li></ul><h4 id="学习建议-14"><a href="#学习建议-14" class="headerlink" title="学习建议"></a>学习建议</h4><p>先理解概念，了解每个设计模式的特点和应用场景，再多加练习，运用到实际项目。</p><h4 id="经典面试题-9"><a href="#经典面试题-9" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>单例模式有哪些实现方式？有哪些优缺点？请手写其中一种</li><li>你用过哪些设计模式，为什么用它？</li></ol><h4 id="资源-14"><a href="#资源-14" class="headerlink" title="资源"></a>资源</h4><ul><li><p>书籍</p><ul><li>⭐《图解设计模式》（强烈推荐这本书，正是用 Java 语言实现，图多、有示例代码、有习题和答案，很不错）</li><li>《Head First 设计模式》</li><li>《大话设计模式》</li><li>《设计模式：可复用面向对象软件的基础》（大黑书，有能力的话也可以去读）</li></ul></li><li><p>视频</p><ul><li>尚硅谷图解 Java 设计模式：<a href="https://www.bilibili.com/video/BV1G4411c7N4">https://www.bilibili.com/video/BV1G4411c7N4</a></li></ul></li><li><p>文档</p><ul><li>菜鸟教程：<a href="https://www.runoob.com/design-pattern/design-pattern-tutorial.html">https://www.runoob.com/design-pattern/design-pattern-tutorial.html</a></li><li>图说设计模式：<a href="https://design-patterns.readthedocs.io/zh_CN/latest/">https://design-patterns.readthedocs.io/zh_CN/latest/</a></li></ul></li></ul><h3 id="🌕-缓存（14-天）"><a href="#🌕-缓存（14-天）" class="headerlink" title="🌕 缓存（14 天）"></a>🌕 缓存（14 天）</h3><p>缓存是高并发系统不可或缺的技术，可以提高系统的性能和并发，因此是后台开发必学的知识点，也是面试重点。</p><h4 id="知识-14"><a href="#知识-14" class="headerlink" title="知识"></a>知识</h4><ul><li>什么是缓存？</li><li>本地缓存<ul><li>Caffeine 库</li></ul></li><li>多级缓存</li><li>Redis 分布式缓存<ul><li>数据类型</li><li>常用操作</li><li>Java 操作 Redis<ul><li>Spring Boot Redis Template</li><li>Redisson</li></ul></li><li>主从模型搭建</li><li>哨兵集群搭建</li><li>日志持久化</li></ul></li><li>缓存（Redis）应用场景<ul><li>数据共享</li><li>单点登录</li><li>计数器</li><li>限流</li><li>点赞</li><li>实时排行榜</li><li>分布式锁</li></ul></li><li>缓存常见问题<ul><li>缓存雪崩</li><li>缓存击穿</li><li>缓存穿透</li><li>缓存更新一致性</li></ul></li><li>相关技术：Memcached、Ehcache</li></ul><h4 id="学习建议-15"><a href="#学习建议-15" class="headerlink" title="学习建议"></a>学习建议</h4><p>学会如何简单地使用缓存并不难，和数据库类似，无非就是调用 API 对数据进行增删改查。</p><p>因此，建议先能够独立使用它，了解缓存的应用场景；再学习如何在 Java 中操作缓存中间件，并尝试和项目相结合，提高系统的性能。</p><p>跟着视频教程实操一遍即可，可以等到面试前再去深入了解原理和高级特性。</p><h4 id="经典面试题-10"><a href="#经典面试题-10" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>Redis 为什么快？</li><li>Redis 有哪些常用的数据结构？</li><li>Redis RDB 和 AOF 持久化的区别，如何选择？</li><li>如何解决缓存击穿、缓存穿透、雪崩问题？</li><li>如何用 Redis 实现点赞功能，怎么设计 Key &#x2F; Value？</li></ol><h4 id="资源-15"><a href="#资源-15" class="headerlink" title="资源"></a>资源</h4><ul><li>视频<ul><li>⭐ 尚硅谷 - 2021 最新 Redis 6 入门到精通教程：<a href="https://www.bilibili.com/video/BV1Rv41177Af">https://www.bilibili.com/video/BV1Rv41177Af</a> （基于 Redis 6 的，推荐）</li></ul></li><li>文档<ul><li>Redis 命令参考：<a href="http://redisdoc.com/">http://redisdoc.com/</a></li><li>Redis 面试题整理：<a href="https://github.com/lokles/Web-Development-Interview-With-Java/blob/main/Redis%E9%97%AE%E9%A2%98.md">https://github.com/lokles/Web-Development-Interview-With-Java/blob/main/Redis%E9%97%AE%E9%A2%98.md</a></li></ul></li><li>书籍<ul><li>《Redis 实战》（经典）</li></ul></li><li>工具<ul><li>⭐ Redis 在线练习：<a href="https://try.redis.io/">https://try.redis.io/</a> （强烈推荐）</li></ul></li></ul><h3 id="🌖-消息队列（14-天）"><a href="#🌖-消息队列（14-天）" class="headerlink" title="🌖 消息队列（14 天）"></a>🌖 消息队列（14 天）</h3><p>消息队列是用于传输和保存消息的容器，也是大型分布式系统中常用的技术，主要解决应用耦合、异步消息、流量削锋等问题。后台开发必学，也是面试重点。</p><h4 id="知识-15"><a href="#知识-15" class="headerlink" title="知识"></a>知识</h4><ul><li>消息队列的作用</li><li>RabbitMQ 消息队列<ul><li>生产消费模型</li><li>交换机模型</li><li>死信队列</li><li>延迟队列</li><li>消息持久化</li><li>Java 操作</li><li>集群搭建</li></ul></li><li>相关技术：Kafka、ActiveMQ、TubeMQ、RocketMQ</li></ul><h4 id="学习建议-16"><a href="#学习建议-16" class="headerlink" title="学习建议"></a>学习建议</h4><p>和缓存一样，学会如何使用消息队列并不难，无非就是调用 API 去生产、转发和消费消息。</p><p>因此，建议先能够独立使用它，了解消息队列的应用场景；再学习如何在 Java 中操作消息队列中间件，并尝试和项目相结合，感受消息队列带来的好处。</p><p>这里我建议初学者先学习 RabbitMQ，比 Kafka 要好理解一些。跟着视频教程实操一遍即可，可以等到面试前再去深入了解原理和高级特性。</p><h4 id="经典面试题-11"><a href="#经典面试题-11" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>使用消息队列有哪些优缺点？</li><li>如何保证消息消费的幂等性？</li><li>消息队列有哪些路由模型？</li><li>你是否用过消息队列，解决过什么问题？</li></ol><h4 id="资源-16"><a href="#资源-16" class="headerlink" title="资源"></a>资源</h4><ul><li>视频<ul><li>⭐ 尚硅谷 - 2021 最新 RabbitMQ 教程：<a href="https://www.bilibili.com/video/BV1cb4y1o7zz">https://www.bilibili.com/video/BV1cb4y1o7zz</a> （很新很全面）</li></ul></li><li>文档<ul><li>RabbitMQ 中文文档：<a href="http://rabbitmq.mr-ping.com/">http://rabbitmq.mr-ping.com/</a></li></ul></li><li>书籍<ul><li>《RabbitMQ 实战：高效部署分布式消息队列》（经典）</li></ul></li><li>工具<ul><li>⭐ RabbitMQ 在线模拟器：<a href="http://tryrabbitmq.com/">http://tryrabbitmq.com/</a></li></ul></li></ul><h3 id="🌖-Nginx（14-天）"><a href="#🌖-Nginx（14-天）" class="headerlink" title="🌖 Nginx（14 天）"></a>🌖 Nginx（14 天）</h3><p>Nginx 是主流的、开源的、高性能的 HTTP 和反向代理 web 服务器，可以用于挂载网站、请求转发、负载均衡、网关路由等。前后端开发同学都需要学习，在后端开发的面试中有时会考到。</p><h4 id="知识-16"><a href="#知识-16" class="headerlink" title="知识"></a>知识</h4><ul><li>Nginx 作用</li><li>正向代理</li><li>反向代理（负载均衡）</li><li>常用命令</li><li>配置</li><li>动静分离（网站部署）</li><li>集群搭建</li><li>相关技术：HAProxy、Apache</li></ul><h4 id="学习建议-17"><a href="#学习建议-17" class="headerlink" title="学习建议"></a>学习建议</h4><p>Nginx 的基本使用非常简单，甚至不需要看任何的视频，跟着一篇文章就能够用它来提供网站访问能力、实现反向代理。</p><p>但是在企业中，Nginx 的使用往往没那么简单，作为负载均衡、请求转发的重要组件，往往需要针对实际场景去写一些特定的配置。因此建议有时间的话，实践下 Nginx 的配置方法，了解 Nginx 基本的设计思想，对今后自己设计系统时也有帮助。</p><h4 id="经典面试题-12"><a href="#经典面试题-12" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>Nginx 有哪些作用？</li><li>Nginx 为什么支持高并发？</li><li>Nginx 有哪些负载均衡策略？</li><li>什么是 Nginx 惊群问题，如何解决它？</li></ol><h4 id="资源-17"><a href="#资源-17" class="headerlink" title="资源"></a>资源</h4><ul><li>视频<ul><li>⭐ 尚硅谷 - Nginx 教程由浅入深：<a href="https://www.bilibili.com/video/BV1zJ411w7SV">https://www.bilibili.com/video/BV1zJ411w7SV</a> （讲的比较全面）</li><li>⭐ Nginx 鱼皮 - 手把手带你从0搭建个人网站：<a href="https://www.bilibili.com/video/BV1rU4y1J785/">https://www.bilibili.com/video/BV1rU4y1J785/</a> （简单演示 Nginx 部署网站）</li></ul></li><li>文档<ul><li>Nginx 中文文档：<a href="https://www.nginx.cn/doc/index.html">https://www.nginx.cn/doc/index.html</a></li></ul></li><li>工具<ul><li>Nginx 配置在线生成：<a href="https://www.digitalocean.com/community/tools/nginx?global.app.lang=zhCN">https://www.digitalocean.com/community/tools/nginx?global.app.lang=zhCN</a></li></ul></li></ul><h3 id="🌗-Netty-网络编程（21-天）"><a href="#🌗-Netty-网络编程（21-天）" class="headerlink" title="🌗 Netty 网络编程（21 天）"></a>🌗 Netty 网络编程（21 天）</h3><p>开源的 Java 网络编程框架，用于开发高性能（事件驱动、异步非阻塞）、高可靠的网络服务器和客户端程序。</p><p>很多网络框架和服务器程序都用到了 Netty 作为底层，学好 Netty 不仅可以让我们自己实现高性能服务器，也能更好地理解其他的框架应用、阅读源码。</p><h4 id="知识-17"><a href="#知识-17" class="headerlink" title="知识"></a>知识</h4><ul><li>IO 模型（BIO &#x2F; NIO）</li><li>Channel</li><li>Buffer</li><li>Seletor</li><li>Netty 模型</li><li>WebSocket 编程（动手做个聊天室）</li><li>相关技术：Vertx（中文文档：<a href="http://vertxchina.github.io/vertx-translation-chinese/">http://vertxchina.github.io/vertx-translation-chinese/</a> ，比 Netty 简单多了，实在看不懂 Netty 也可以学习下这个）</li></ul><h4 id="学习建议-18"><a href="#学习建议-18" class="headerlink" title="学习建议"></a>学习建议</h4><p>不同于之前学的 SSM 框架，Netty 还是需要一定学习成本的，一方面是国内资源太缺乏，另一方面很多重要的概念（比如 NIO）还是要多动手写代码调试才能理解。</p><p>还是建议先从视频入门，并且不建议在 Netty 上花太多时间，面试的时候一般也就考察一些 Netty 背后的思想（比如 NIO）而非框架本身的语法细节。</p><h4 id="经典面试题-13"><a href="#经典面试题-13" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>Netty 有哪些优点？</li><li>什么是 NIO？</li><li>介绍 Netty 的零拷贝机制</li></ol><h4 id="资源-18"><a href="#资源-18" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li><p>⭐ 尚硅谷Netty教程：<a href="https://www.bilibili.com/video/BV1DJ411m7NR">https://www.bilibili.com/video/BV1DJ411m7NR</a></p></li><li><p>Netty 技术演讲（中文字幕版）：<a href="https://www.youtube.com/watch?v=I8yy2Cy7dDI">https://www.youtube.com/watch?v=I8yy2Cy7dDI</a></p></li></ul></li><li><p>文档</p><ul><li>Netty 4 用户指南：<a href="https://www.w3cschool.cn/netty_4_user_guide/">https://www.w3cschool.cn/netty_4_user_guide/</a></li></ul></li><li><p>书籍</p><ul><li>《Netty 实战》</li></ul></li></ul><h3 id="🌖-微服务（60-天）"><a href="#🌖-微服务（60-天）" class="headerlink" title="🌖 微服务（60 天）"></a>🌖 微服务（60 天）</h3><p>随着互联网的发展，项目越来越复杂，单机且庞大的巨石项目已无法满足开发、运维、并发、可靠性等需求。</p><p>因此，后台架构不断演进，可以将庞大的项目拆分成一个个职责明确、功能独立的细小模块，模块可以部署在多台服务器上，相互配合协作，提供完整的系统能力。</p><p>换言之，想做大型项目，这块儿一定要好好学！</p><h4 id="知识-18"><a href="#知识-18" class="headerlink" title="知识"></a>知识</h4><h5 id="Dubbo"><a href="#Dubbo" class="headerlink" title="Dubbo"></a>Dubbo</h5><ul><li>架构演进</li><li>RPC</li><li>Zookeeper</li><li>服务提供者</li><li>服务消费者</li><li>项目搭建</li><li>相关技术：DubboX（对 Dubbo 的扩展）</li></ul><h5 id="🌖-微服务"><a href="#🌖-微服务" class="headerlink" title="🌖 微服务"></a>🌖 微服务</h5><ul><li>微服务概念</li><li>Spring Cloud 框架<ul><li>子父工程</li><li>服务注册与发现</li><li>注册中心 Eureka、Zookeeper、Consul</li><li>Ribbon 负载均衡</li><li>Feign 服务调用</li><li>Hystrix 服务限流、降级、熔断</li><li>Resilience4j 服务容错</li><li>Gateway（Zuul）微服务网关</li><li>Config 分布式配置中心</li><li>分布式服务总线</li><li>Sleuth + Zipkin 分布式链路追踪</li></ul></li><li>Spring Cloud Alibaba<ul><li>Nacos 注册、配置中心</li><li>OpenFeign 服务调用</li><li>Sentinel 流控</li><li>Seata 分布式事务</li></ul></li></ul><h5 id="接口管理"><a href="#接口管理" class="headerlink" title="接口管理"></a>接口管理</h5><ul><li>Swagger 接口文档</li><li>Postman 接口测试</li><li>相关技术：YApi、ShowDoc</li></ul><h4 id="学习建议-19"><a href="#学习建议-19" class="headerlink" title="学习建议"></a>学习建议</h4><p>时间不急的话，建议先从 Dubbo 学起，对分布式、RPC、微服务有些基本的了解，再去食用 Spring Cloud 全家桶会更香。学完 Spring Cloud 全家桶后，再去学 Spring Cloud Alibaba 就很简单了。</p><p>这部分内容的学习，原理 + 实践都很重要，也不要被各种高大上的词汇唬住了，都是上层（应用层）的东西，基本没有什么算法，跟着视频教程学，其实还是很好理解的。</p><p>分布式相关知识非常多，但这里不用刻意去背，先通过视频教程实战使用一些微服务框架，也能对其中的概念有基本的了解。</p><p>大厂面试的时候很少问 Spring Cloud 框架的细节，更多的是微服务以及各组件的一些思想，比如网关的好处、消息总线的好处等。</p><h4 id="经典面试题-14"><a href="#经典面试题-14" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>什么是微服务，有哪些优缺点？</li><li>什么是注册中心，能解决什么问题？</li></ol><h4 id="资源-19"><a href="#资源-19" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li><p>⭐ 尚硅谷 Dubbo 教程：<a href="https://www.bilibili.com/video/BV1ns411c7jV">https://www.bilibili.com/video/BV1ns411c7jV</a></p></li><li><p>⭐ 尚硅谷 SpringCloud（H版&amp;alibaba）框架开发教程（微服务分布式架构）：<a href="https://www.bilibili.com/video/BV18E411x7eT">https://www.bilibili.com/video/BV18E411x7eT</a> （把国外的 Spring Cloud 和国内的 Spring Cloud Alibaba 结合在一起对比着去讲，主流技术栈、知识点都讲到了，真心不错！）</p></li></ul></li><li><p>文档</p><ul><li>Apache Dubbo 官方文档：<a href="https://dubbo.apache.org/zh/">https://dubbo.apache.org/zh/</a></li><li>Spring Cloud Alibaba 官方文档：<a href="https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md">https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md</a></li><li>⭐ Swagger 教学文档：<a href="https://doc.xiaominfo.com/">https://doc.xiaominfo.com/</a> （跟着快速开始直接用就好了）</li></ul></li></ul><h3 id="🌖-容器（7-天）"><a href="#🌖-容器（7-天）" class="headerlink" title="🌖 容器（7 天）"></a>🌖 容器（7 天）</h3><p>将应用和环境进行封装，相互隔离、独立部署、便于移植，提高安全性、提高开发和维护效率。</p><p>便于实现微服务、持续集成和交付。</p><h4 id="知识-19"><a href="#知识-19" class="headerlink" title="知识"></a>知识</h4><ul><li>🌖 Docker<ul><li>容器概念</li><li>镜像</li><li>部署服务</li><li>Dockerfile</li><li>Docker Compose</li><li>Docker Machine</li><li>Docker Swarm</li><li>多阶段构建</li></ul></li><li>🌘 K8S（Kubernetes）<ul><li>K8S 架构</li><li>工作负载<ul><li>资源类型</li><li>Pod</li><li>Pod 生命周期</li><li>Pod 安全策略</li></ul></li><li>K8S 组件</li><li>K8S 对象</li><li>部署应用</li><li>服务<ul><li>Ingress</li></ul></li><li>Kubectl 命令行</li><li>集群管理</li></ul></li><li>相关技术：Apache Mesos、Mesosphere</li></ul><h4 id="学习建议-20"><a href="#学习建议-20" class="headerlink" title="学习建议"></a>学习建议</h4><p>业务上云是趋势，但是对于开发同学来说，会用 Docker &#x2F; K8S 部署项目和服务就行。实际工作中，企业一般都有现成的平台直接用，面试考察的也不多，不用花太多时间。</p><h4 id="经典面试题-15"><a href="#经典面试题-15" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>什么是容器？</li><li>使用  Docker 有哪些好处？</li><li>如何快速启动多个 Docker 节点？</li></ol><h4 id="资源-20"><a href="#资源-20" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li>⭐【狂神说】Docker 最新超详细版教程通俗易懂：<a href="https://www.bilibili.com/video/BV1og4y1q7M4">https://www.bilibili.com/video/BV1og4y1q7M4</a></li><li>K8S 视频教程：<a href="https://www.bilibili.com/video/BV1K5411N7j8">https://www.bilibili.com/video/BV1K5411N7j8</a></li></ul></li><li><p>书籍</p><ul><li>《深入浅出 Docker》：<a href="https://www.zhihu.com/pub/reader/119632192">https://www.zhihu.com/pub/reader/119632192</a></li></ul></li><li><p>文档</p><ul><li>Docker — 从入门到实践：<a href="https://vuepress.mirror.docker-practice.com/">https://vuepress.mirror.docker-practice.com/</a></li><li>菜鸟教程 Docker：<a href="https://www.runoob.com/docker/docker-tutorial.html">https://www.runoob.com/docker/docker-tutorial.html</a></li><li>国外 Docker 官方文档：<a href="https://docs.docker.com/get-started/">https://docs.docker.com/get-started/</a></li></ul></li><li><p>网站</p><ul><li>Docker 中文社区：<a href="https://www.docker.org.cn/">https://www.docker.org.cn/</a> （强烈推荐，有很多 Docker 技术文章和学习笔记）</li><li>Docker Blog：<a href="https://www.docker.com/blog/">https://www.docker.com/blog/</a> （国外 Docker 技术博客）</li></ul></li><li><p>镜像源</p><ul><li>Docker Hub：<a href="https://hub.docker.com/">https://hub.docker.com</a></li><li>腾讯软件源：<a href="http://mirrors.tencent.com/#/index">http://mirrors.tencent.com/#/index</a></li><li>阿里云 Docker 仓库：<a href="https://cr.console.aliyun.com/">https://cr.console.aliyun.com/</a></li></ul></li></ul><h3 id="🌗-CI-CD（3-天）"><a href="#🌗-CI-CD（3-天）" class="headerlink" title="🌗 CI &#x2F; CD（3 天）"></a>🌗 CI &#x2F; CD（3 天）</h3><p>持续集成 &#x2F; 持续交付，贯穿整个研发到项目上线的过程，提高效率。</p><p>大公司一般都有自己的 CI &#x2F; CD 平台。</p><h4 id="知识-20"><a href="#知识-20" class="headerlink" title="知识"></a>知识</h4><ul><li>什么是 CI &#x2F; CD</li><li>CI &#x2F; CD 有什么好处</li><li>使用任一 CI &#x2F; CD 平台</li><li>相关技术：Jenkins、GitLab、微信云托管</li></ul><h4 id="学习建议-21"><a href="#学习建议-21" class="headerlink" title="学习建议"></a>学习建议</h4><p>了解它是什么，并且实战使用任一 CI &#x2F; CD 平台，感受它和传统开发运维到底有什么不同，就足够了。其实很简单，不要花太多时间。</p><p>真正要自己去搭建的时候，跟着官方文档来就行。</p><h4 id="资源-21"><a href="#资源-21" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li>⭐【狂神说】CI&#x2F;CD到底是什么？十分钟理解企业级DevOps：<a href="https://www.bilibili.com/video/BV1zf4y127vu">https://www.bilibili.com/video/BV1zf4y127vu</a> （讲的非常棒！带你快速理解 CI &#x2F; CD 的作用和部分原理）</li><li>尚硅谷 - Jenkins 持续集成工具教程：<a href="https://www.bilibili.com/video/BV1GW411w7pn">https://www.bilibili.com/video/BV1GW411w7pn</a> （实在要用到 Jenkins 再去学）</li></ul></li><li><p>文档</p><ul><li>Jenkins 官方文档：<a href="https://www.jenkins.io/zh/">https://www.jenkins.io/zh/</a> （有很多案例，要用的时候查一下就行了）</li></ul></li><li><p>实战平台</p><ul><li>⭐ 微信云托管：<a href="https://cloud.weixin.qq.com/cloudrun">https://cloud.weixin.qq.com/cloudrun</a> （直播演示过，鱼皮后面会给大家出视频教程）</li><li>前端托管 Webify：<a href="https://cloud.tencent.com/product/webify">https://cloud.tencent.com/product/webify</a> （鱼皮给大家出过视频教程的 <a href="https://www.bilibili.com/video/BV11V411p7vf/%EF%BC%89">https://www.bilibili.com/video/BV11V411p7vf/）</a></li></ul></li></ul><h3 id="练手项目-2"><a href="#练手项目-2" class="headerlink" title="练手项目"></a>练手项目</h3><blockquote><p>更多请见本路线阶段 5：[项目源码（50 套）](###项目源码（50 套）) 。</p></blockquote><ul><li><p>mall-learning：<a href="https://github.com/macrozheng/mall-learningmall">https://github.com/macrozheng/mall-learningmall</a> （学习教程，架构、业务、技术要点全方位解析）</p></li><li><p>miaosha：<a href="https://github.com/qiurunze123/miaosha">https://github.com/qiurunze123/miaosha</a> （秒杀系统设计与实现）</p></li><li><p>Mall：<a href="https://github.com/macrozheng/mall">https://github.com/macrozheng/mall</a> （包括前台商城系统及后台管理系统，基于 SpringBoot+MyBatis 实现）</p></li><li><p>Spring-Cloud-Admin：<a href="https://github.com/wxiaoqi/Spring-Cloud-AdminCloud-Admin">https://github.com/wxiaoqi/Spring-Cloud-AdminCloud-Admin</a> （基于 Spring Cloud 微服务化开发平台，具有统一授权、认证的后台管理系统）</p></li><li><p>paascloud-master：<a href="https://github.com/paascloud/paascloud-master">https://github.com/paascloud/paascloud-master</a> （基于 spring cloud + vue + oAuth2.0，前后端分离商城系统）</p></li><li><p>mall-swarm：<a href="https://github.com/macrozheng/mall-swarm">https://github.com/macrozheng/mall-swarm</a> （一套微服务商城系统，采用了 Spring Cloud Greenwich、Spring Boot 2、MyBatis、Docker、Elasticsearch 等核心技术，同时提供了基于 Vue 的管理后台方便快速搭建系统）</p></li><li><p>SecKill：<a href="https://github.com/hfbin/Seckill">https://github.com/hfbin/Seckill</a> （基于 SpringBoot+Mybatis+Redis+RabbitMQ 秒杀系统）</p></li><li><p>jeecg-boot：<a href="https://github.com/zhangdaiscott/jeecg-boot">https://github.com/zhangdaiscott/jeecg-boot</a> （低代码开发平台）</p></li><li><p>PassJava-Platform：<a href="https://github.com/Jackson0714/PassJava-Platform">https://github.com/Jackson0714/PassJava-Platform</a> （一款面试刷题的 Spring Cloud 开源系统）</p></li></ul><h3 id="尾声-3"><a href="#尾声-3" class="headerlink" title="尾声"></a>尾声</h3><p>看到这里，相信你已经感叹：编程语言一辈子学不完了！</p><p>但是，不用担心，通过对这么多知识点的学习，相信你已经有了一定的积累，也不知不觉地锻炼了自主学习能力、资源检索能力、代码阅读能力、问题解决能力，之后的学习会越来越轻松。</p><p>接下来，可以试着用你学到的技术来解决实际的问题，自主从 0 开始做一些项目，保持编程手感。</p><h2 id="阶段-5：项目实战"><a href="#阶段-5：项目实战" class="headerlink" title="阶段 5：项目实战"></a>阶段 5：项目实战</h2><h3 id="目标-4"><a href="#目标-4" class="headerlink" title="目标"></a>目标</h3><p>综合所学技术从 0 到 1 开发和上线一个全面、有特色的、可以写进简历的个人项目。</p><h3 id="学习建议-22"><a href="#学习建议-22" class="headerlink" title="学习建议"></a>学习建议</h3><p>其实在之前的框架学习视频中应该就做过几个项目了，但相对不够完整和体系化。</p><p>有想法的同学可以做任何自己想做的项目，推荐参加一些作品类竞赛，练手、拿奖、收获项目经历一举三得。</p><p>暂时没想法的朋友，建议先跟着专门带做项目的视频教程做 1 - 2 套 Java 完整项目，一定要从 0 到 1 自己手写！</p><p>有能力的朋友也可以试着用 GitHub 上的源码来学习啦~</p><p>做一个完整的项目的确很不容易，建议大家根据自己的 <strong>时间、兴趣</strong> 选择 <strong>较新的</strong>、<strong>有配套源码的</strong> 教程，保持耐心。</p><p>如何选择编程学习资源，可以看下我的原创文章：<a href="https://mp.weixin.qq.com/s/mlMql9RJCd7THt6rpGb8UA">https://mp.weixin.qq.com/s/mlMql9RJCd7THt6rpGb8UA</a></p><p>下面推荐一些优质的、较新的项目实战视频教程 + 50 套项目源码。</p><h3 id="视频教程"><a href="#视频教程" class="headerlink" title="视频教程"></a>视频教程</h3><ul><li><p>尚硅谷 - 谷粒学院 - 微服务 + 全栈 - 在线教育实战项目：<a href="https://www.bilibili.com/video/BV1dQ4y1A75e">https://www.bilibili.com/video/BV1dQ4y1A75e</a> （全栈项目，前后端讲得都很全面）</p></li><li><p>尚硅谷 - 尚筹网 - Java项目实战开发教程 - SSM 框架 + 微服务架构：<a href="https://www.bilibili.com/video/BV1bE411T7oZ%EF%BC%88500">https://www.bilibili.com/video/BV1bE411T7oZ（500</a> 多集，包含完整的用户权限管理，功能丰富、讲得很细致全面）</p></li><li><p>黑马 - 24 小时搞定 Java 毕设电商项目：<a href="https://www.bilibili.com/video/BV13f4y1r7fS">https://www.bilibili.com/video/BV13f4y1r7fS</a></p></li><li><p>黑马程序员 Java 项目《传智健康》，超完整的企业级医疗行业项目：<a href="https://www.bilibili.com/video/BV1Bo4y117zV">https://www.bilibili.com/video/BV1Bo4y117zV</a></p></li><li><p>黑马程序员 Java 项目《万信金融》企业级开发实战，互联网金融行业解决方案：<a href="https://www.bilibili.com/video/BV1Ub4y1f7rk">https://www.bilibili.com/video/BV1Ub4y1f7rk</a></p></li><li><p>黑马程序员 Java 项目 SaaS 移动办公完整版《iHRM 人力资源管理系统》，跨行业 SaaS 办公整合性解决方案：<a href="https://www.bilibili.com/video/BV18A411L7UX">https://www.bilibili.com/video/BV18A411L7UX</a></p></li><li><p>黑马程序员 Java 大型分布式微服务闪聚支付项目，Java 移动支付全生态系统：<a href="https://www.bilibili.com/video/BV17v411V79c">https://www.bilibili.com/video/BV17v411V79c</a></p></li><li><p>黑马程序员 Java 项目《好客租房》：<a href="https://www.bilibili.com/video/BV1sZ4y1F7PD">https://www.bilibili.com/video/BV1sZ4y1F7PD</a></p></li></ul><h3 id="常用类库"><a href="#常用类库" class="headerlink" title="常用类库"></a>常用类库</h3><h5 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h5><ul><li>Guava：谷歌开发的 Java 工具库（<a href="https://github.com/google/guava%EF%BC%89">https://github.com/google/guava）</a></li><li>Apache Commons：各类工具库，比如 commons-lang、commons-io、commons-collections 等（<a href="https://github.com/apache/commons-lang%EF%BC%89">https://github.com/apache/commons-lang）</a></li><li>Hutool：Java 工具集库（<a href="https://github.com/looly/hutool%EF%BC%89">https://github.com/looly/hutool）</a></li><li>Lombok：Java 增强库（<a href="https://github.com/projectlombok/lombok%EF%BC%89">https://github.com/projectlombok/lombok）</a></li><li>Apache HttpComponents Client：HTTP 客户端库（<a href="https://github.com/apache/httpcomponents-client%EF%BC%89">https://github.com/apache/httpcomponents-client）</a></li><li>OkHttp：适用于 JVM、Android 等平台的 Http 客户端（<a href="https://github.com/square/okhttp%EF%BC%89">https://github.com/square/okhttp）</a></li><li>Gson：谷歌的 JSON 处理库（<a href="https://github.com/google/gson%EF%BC%89">https://github.com/google/gson）</a></li><li>Jcommander：Java 命令行参数解析框架（<a href="https://github.com/cbeust/jcommander%EF%BC%89">https://github.com/cbeust/jcommander）</a></li><li>Apache PDFBox：PDF 操作库（<a href="https://github.com/apache/pdfbox%EF%BC%89">https://github.com/apache/pdfbox）</a></li><li>EasyExcel：阿里的 Excel 处理库（<a href="https://github.com/alibaba/easyexcel%EF%BC%89">https://github.com/alibaba/easyexcel）</a></li><li>Apache POI：表格文件处理库（<a href="https://github.com/apache/poi%EF%BC%89">https://github.com/apache/poi）</a></li></ul><h5 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h5><ul><li>JUnit：Java 测试框架（<a href="https://github.com/junit-team/junit4%EF%BC%89">https://github.com/junit-team/junit4）</a></li><li>Mockito：Java 单元测试 Mock 框架（<a href="https://github.com/mockito/mockito%EF%BC%89">https://github.com/mockito/mockito）</a></li><li>Selenium：浏览器自动化框架（<a href="https://github.com/SeleniumHQ/selenium%EF%BC%89">https://github.com/SeleniumHQ/selenium）</a></li><li>htmlunit：Java 模拟浏览器（<a href="https://github.com/HtmlUnit/htmlunit%EF%BC%89">https://github.com/HtmlUnit/htmlunit）</a></li><li>TestNG：Java 测试框架（<a href="https://github.com/cbeust/testng%EF%BC%89">https://github.com/cbeust/testng）</a></li><li>Jacoco：Java 代码覆盖度库（<a href="https://github.com/jacoco/jacoco%EF%BC%89">https://github.com/jacoco/jacoco）</a></li></ul><h5 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h5><ul><li>cglib：字节码生成库（<a href="https://github.com/cglib/cglib%EF%BC%89">https://github.com/cglib/cglib）</a></li><li>Arthas：Java 诊断工具（<a href="https://github.com/alibaba/arthas%EF%BC%89">https://github.com/alibaba/arthas）</a></li><li>config：针对 JVM 的配置库（<a href="https://github.com/lightbend/config%EF%BC%89">https://github.com/lightbend/config）</a></li><li>Quasar：Java 纤程库（<a href="https://github.com/puniverse/quasar%EF%BC%89">https://github.com/puniverse/quasar）</a></li><li>drools：Java 规则引擎（<a href="https://github.com/kiegroup/drools%EF%BC%89">https://github.com/kiegroup/drools）</a></li><li>Caffeine：Java 高性能缓存库（<a href="https://github.com/ben-manes/caffeine%EF%BC%89">https://github.com/ben-manes/caffeine）</a></li><li>Disruptor：高性能线程间消息传递库（<a href="https://github.com/LMAX-Exchange/disruptor%EF%BC%89">https://github.com/LMAX-Exchange/disruptor）</a></li><li>Knife4j：Swagger 文档增强（<a href="https://doc.xiaominfo.com/%EF%BC%89">https://doc.xiaominfo.com/）</a></li><li>Thumbnailator：Java 缩略图生成库（<a href="https://github.com/coobird/thumbnailator%EF%BC%89">https://github.com/coobird/thumbnailator）</a></li><li>Logback：Java 日志库（<a href="https://github.com/qos-ch/logback%EF%BC%89">https://github.com/qos-ch/logback）</a></li><li>Apache Camel：消息传输集成框架（<a href="https://github.com/apache/camel%EF%BC%89">https://github.com/apache/camel）</a></li><li>Quartz：定时任务调度库（<a href="https://github.com/quartz-scheduler/quartz%EF%BC%89">https://github.com/quartz-scheduler/quartz）</a></li><li>Apache Mahout：机器学习库（<a href="https://github.com/apache/mahout%EF%BC%89">https://github.com/apache/mahout）</a></li><li>Apache OpenNLP：NLP 工具库（<a href="https://github.com/apache/opennlp%EF%BC%89">https://github.com/apache/opennlp）</a></li><li>RxJava：JVM 反应式编程框架（<a href="https://github.com/ReactiveX/RxJava%EF%BC%89">https://github.com/ReactiveX/RxJava）</a></li><li>JProfiler：性能分析库（<a href="https://www.ej-technologies.com/products/jprofiler/overview.html%EF%BC%89">https://www.ej-technologies.com/products/jprofiler/overview.html）</a></li><li>jsoup：HTML 文档解析库（<a href="https://jsoup.org/%EF%BC%89">https://jsoup.org/）</a></li><li>webmagic：Java 爬虫框架（<a href="https://github.com/code4craft/webmagic/%EF%BC%89">https://github.com/code4craft/webmagic/）</a></li></ul><h3 id="IDEA-插件"><a href="#IDEA-插件" class="headerlink" title="IDEA 插件"></a>IDEA 插件</h3><p>综合实用性、易用性、流行度、维护频率，精选了以下插件，简单分类为天地二品。</p><h4 id="精选地品插件"><a href="#精选地品插件" class="headerlink" title="精选地品插件"></a>精选地品插件</h4><p>简单好用、增强功能</p><ul><li><p>Background Image Plus +</p><ul><li>描述：给编辑器设置背景图片（小姐姐图片）</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/11247-background-image-plus-">https://plugins.jetbrains.com/plugin/11247-background-image-plus-</a></li></ul></li><li><p>Chinese Language Pack</p><ul><li>描述：中文支持</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack----">https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack----</a></li></ul></li><li><p>Translation</p><ul><li>描述：翻译插件，鼠标选中文本，点击右键即可自动翻译成多国语言。</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8579-translation/">https://plugins.jetbrains.com/plugin/8579-translation/</a></li></ul></li><li><p>Key Promoter X</p><ul><li>描述：快捷键提示插件。当你执行鼠标操作时，如果该操作可被快捷键代替，会给出提示，帮助你自然形成使用快捷键的习惯，告别死记硬背。</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/9792-key-promoter-x/">https://plugins.jetbrains.com/plugin/9792-key-promoter-x/</a></li></ul></li><li><p>Rainbow Brackets</p><ul><li>描述：给括号添加彩虹色，使开发者通过颜色区分括号嵌套层级，便于阅读</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10080-rainbow-brackets/">https://plugins.jetbrains.com/plugin/10080-rainbow-brackets/</a></li></ul></li><li><p>CodeGlance</p><ul><li>描述：在编辑器右侧生成代码小地图，可以拖拽小地图光标快速定位代码，阅读行数很多的代码文件时非常实用。</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7275-codeglance/">https://plugins.jetbrains.com/plugin/7275-codeglance/</a></li></ul></li><li><p>WakaTime</p><ul><li>描述：代码统计和跟踪插件</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7425-wakatime">https://plugins.jetbrains.com/plugin/7425-wakatime</a></li></ul></li><li><p>Statistic</p><ul><li>描述：代码统计</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/4509-statistic">https://plugins.jetbrains.com/plugin/4509-statistic</a></li></ul></li></ul><h4 id="精选天品插件"><a href="#精选天品插件" class="headerlink" title="精选天品插件"></a>精选天品插件</h4><p>提高开发效率和代码质量</p><ul><li><p>String Manipulation</p><ul><li>描述：字符串快捷处理</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/2162-string-manipulation">https://plugins.jetbrains.com/plugin/2162-string-manipulation</a></li></ul></li><li><p>Tabnine AI Code Completion</p><ul><li>描述：使用 AI 去自动提示和补全代码，比 IDEA 自带的代码补全更加智能化</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/12798-tabnine-ai-code-completion-js-java-python-ts-rust-go-php--more">https://plugins.jetbrains.com/plugin/12798-tabnine-ai-code-completion-js-java-python-ts-rust-go-php--more</a></li></ul></li><li><p>GsonFormatPlus</p><ul><li>描述：根据 json 生成对象</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/14949-gsonformatplus">https://plugins.jetbrains.com/plugin/14949-gsonformatplus</a></li></ul></li><li><p>JUnitGenerator V2.0</p><ul><li>描述：自动生成单元测试</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/3064-junitgenerator-v2-0">https://plugins.jetbrains.com/plugin/3064-junitgenerator-v2-0</a></li></ul></li><li><p>RestfulTool</p><ul><li>描述：辅助 web 开发的工具集</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/14280-restfultool">https://plugins.jetbrains.com/plugin/14280-restfultool</a></li></ul></li><li><p>SequenceDiagram</p><ul><li>描述：自动生成方法调用时序图</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8286-sequencediagram">https://plugins.jetbrains.com/plugin/8286-sequencediagram</a></li></ul></li><li><p>CheckStyle-IDEA</p><ul><li>描述：自动检查 Java 代码规范</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/1065-checkstyle-idea">https://plugins.jetbrains.com/plugin/1065-checkstyle-idea</a></li></ul></li><li><p>Alibaba Java Coding Guidelines</p><ul><li>描述：代码规范检查插件</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines">https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines</a></li></ul></li><li><p>SonarLint</p><ul><li>描述：帮助你发现和修复代码的错误和漏洞</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7973-sonarlint">https://plugins.jetbrains.com/plugin/7973-sonarlint</a></li></ul></li><li><p>MybatisX</p><ul><li>描述：MyBatis 增强插件，支持自动生成 entity、mapper、service 等常用操作的代码，优化体验</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10119-mybatisx">https://plugins.jetbrains.com/plugin/10119-mybatisx</a></li></ul></li></ul><h4 id="美化类"><a href="#美化类" class="headerlink" title="美化类"></a>美化类</h4><ul><li><p>Extra Icons</p><ul><li>描述：图标美化插件，支持更多文件类型</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/11058-extra-icons">https://plugins.jetbrains.com/plugin/11058-extra-icons</a></li></ul></li><li><p>Chinese Language Pack</p><ul><li>描述：中文支持</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack----">https://plugins.jetbrains.com/plugin/13710-chinese-simplified-language-pack----</a></li></ul></li><li><p>Grep Console</p><ul><li>描述：自定义设置控制台输出颜色</li><li>官网：<a href="https://plugins.jetbrains.com/idea/plugin/7125-grep-console">https://plugins.jetbrains.com/idea/plugin/7125-grep-console</a></li></ul></li><li><p>Material Theme UI</p><ul><li>描述：美观的主题</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8006-material-theme-ui">https://plugins.jetbrains.com/plugin/8006-material-theme-ui</a></li></ul></li><li><p>Nyan Progress Bar</p><ul><li>描述：进度条美化</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8575-nyan-progress-bar">https://plugins.jetbrains.com/plugin/8575-nyan-progress-bar</a></li></ul></li><li><p>activate-power-mode</p><ul><li>描述：打字特效</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8330-activate-power-mode">https://plugins.jetbrains.com/plugin/8330-activate-power-mode</a></li></ul></li><li><p>Power Mode II</p><ul><li>描述：打字特效插件升级版</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8251-power-mode-ii">https://plugins.jetbrains.com/plugin/8251-power-mode-ii</a></li></ul></li><li><p>Rainbow Brackets</p><ul><li>描述：给括号添加彩虹色，使开发者通过颜色区分括号嵌套层级，便于阅读</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10080-rainbow-brackets/">https://plugins.jetbrains.com/plugin/10080-rainbow-brackets/</a></li></ul></li><li><p>Background Image Plus +</p><ul><li>描述：给编辑器设置背景图片</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/11247-background-image-plus-">https://plugins.jetbrains.com/plugin/11247-background-image-plus-</a></li></ul></li></ul><h4 id="代码生成类"><a href="#代码生成类" class="headerlink" title="代码生成类"></a>代码生成类</h4><ul><li><p>Codota AI Autocomplete for Java and JavaScript</p><ul><li>描述：使用 AI 去自动提示和补全代码，比 IDEA 自带的代码补全更加智能化</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7638-codota-ai-autocomplete-for-java-and-javascript">https://plugins.jetbrains.com/plugin/7638-codota-ai-autocomplete-for-java-and-javascript</a></li></ul></li><li><p>Tabnine AI Code Completion</p><ul><li>描述：使用 AI 来自动提示和补全代码，比 IDEA 自带的代码补全更加智能化</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/12798-tabnine-ai-code-completion-js-java-python-ts-rust-go-php--more">https://plugins.jetbrains.com/plugin/12798-tabnine-ai-code-completion-js-java-python-ts-rust-go-php--more</a></li></ul></li><li><p>UUID Generator</p><ul><li>描述：生成随机的 UUID</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8320-uuid-generator">https://plugins.jetbrains.com/plugin/8320-uuid-generator</a></li></ul></li><li><p>Randomness</p><ul><li>描述：生成随机值</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/9836-randomness">https://plugins.jetbrains.com/plugin/9836-randomness</a></li></ul></li><li><p>.ignore</p><ul><li>描述：生成不同语言的版本控制忽略文件</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7495--ignore">https://plugins.jetbrains.com/plugin/7495--ignore</a></li></ul></li><li><p>Auto filling Java call arguments</p><ul><li>描述：通过快捷键自动补全函数的调用参数</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8638-auto-filling-java-call-arguments/">https://plugins.jetbrains.com/plugin/8638-auto-filling-java-call-arguments/</a></li></ul></li><li><p>GenerateAllSetter</p><ul><li>描述：快速生成调用对象 setter 方法的代码</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/9360-generateallsetter">https://plugins.jetbrains.com/plugin/9360-generateallsetter</a></li></ul></li><li><p>GenerateSerialVersionUID</p><ul><li>描述：一键为实现 Serializable 接口的类生成 SerialVersionUID</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/185-generateserialversionuid/">https://plugins.jetbrains.com/plugin/185-generateserialversionuid/</a></li></ul></li><li><p>GsonFormatPlus</p><ul><li>描述：根据 json 生成对象</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/14949-gsonformatplus">https://plugins.jetbrains.com/plugin/14949-gsonformatplus</a></li></ul></li><li><p>Lombok</p><ul><li><p>描述：配合 Lombok 依赖及注解使用，能够大大减少 Java 对象的代码量。IDEA 已自带，无需安装。</p></li><li><p>官网：<a href="https://plugins.jetbrains.com/plugin/6317-lombok/">https://plugins.jetbrains.com/plugin/6317-lombok/</a></p></li></ul></li><li><p>Easy Code</p><ul><li>描述：根据数据库表生成 Java entity、dao、mapper、service、controller 等代码</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10954-easy-code">https://plugins.jetbrains.com/plugin/10954-easy-code</a></li></ul></li><li><p>Easy Javadoc</p><ul><li>描述：快速生成 Javadoc 文档</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/12977-easy-javadoc">https://plugins.jetbrains.com/plugin/12977-easy-javadoc</a></li></ul></li><li><p>JavaDoc</p><ul><li>描述：生成 Javadoc 文档</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7157-javadoc">https://plugins.jetbrains.com/plugin/7157-javadoc</a></li></ul></li><li><p>Json2Pojo</p><ul><li>描述：根据 JSON 生成 POJO，根据 POJO 生成 JSON</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8533-json2pojo">https://plugins.jetbrains.com/plugin/8533-json2pojo</a></li></ul></li><li><p>JUnitGenerator V2.0</p><ul><li>描述：自动生成单元测试</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/3064-junitgenerator-v2-0">https://plugins.jetbrains.com/plugin/3064-junitgenerator-v2-0</a></li></ul></li><li><p>Jindent - Source Code Formatter</p><ul><li>描述：根据自定义模板来生成 javadoc 注释</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8309-jindent--source-code-formatter">https://plugins.jetbrains.com/plugin/8309-jindent--source-code-formatter</a></li></ul></li><li><p>MybatisX</p><ul><li>描述：MyBatis 增强插件，支持自动生成 entity、mapper、service 等常用操作的代码，优化体验</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10119-mybatisx">https://plugins.jetbrains.com/plugin/10119-mybatisx</a></li></ul></li><li><p>MyBatisCodeHelperPro</p><ul><li>描述：MyBatis 增强插件，支持自动生成 entity、mapper、service 等常用操作的代码（收费）</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/9837-mybatiscodehelperpro">https://plugins.jetbrains.com/plugin/9837-mybatiscodehelperpro</a></li></ul></li></ul><h4 id="功能提效类"><a href="#功能提效类" class="headerlink" title="功能提效类"></a>功能提效类</h4><h5 id="源码阅读"><a href="#源码阅读" class="headerlink" title="源码阅读"></a>源码阅读</h5><ul><li>Call Graph<ul><li>描述：生成调用关系图，便于查看源码</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/12304-call-graph">https://plugins.jetbrains.com/plugin/12304-call-graph</a></li></ul></li><li>Jump to Line<ul><li>描述：支持跳转到任意行进行调试</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/14877-jump-to-line">https://plugins.jetbrains.com/plugin/14877-jump-to-line</a></li></ul></li><li>SequenceDiagram<ul><li>描述：自动生成方法调用时序图</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8286-sequencediagram">https://plugins.jetbrains.com/plugin/8286-sequencediagram</a></li></ul></li><li>jclasslib Bytecode Viewer<ul><li>描述：字节码查看器</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/9248-jclasslib-bytecode-viewer">https://plugins.jetbrains.com/plugin/9248-jclasslib-bytecode-viewer</a></li></ul></li><li>BrowseWordAtCaret<ul><li>描述： 快速在相同单词间进行跳转</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/201-browsewordatcaret">https://plugins.jetbrains.com/plugin/201-browsewordatcaret</a></li></ul></li><li>CodeGlance<ul><li>描述：在编辑器右侧生成代码小地图，可以拖拽小地图光标快速定位代码，阅读行数很多的代码文件时非常实用。</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7275-codeglance/">https://plugins.jetbrains.com/plugin/7275-codeglance/</a></li></ul></li></ul><h5 id="代码规范"><a href="#代码规范" class="headerlink" title="代码规范"></a>代码规范</h5><ul><li>CheckStyle-IDEA<ul><li>描述：自动检查 Java 代码规范</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/1065-checkstyle-idea">https://plugins.jetbrains.com/plugin/1065-checkstyle-idea</a></li></ul></li><li>Alibaba Java Coding Guidelines<ul><li>描述：代码规范检查插件</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines">https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines</a></li></ul></li><li>SonarLint<ul><li>描述：帮助你发现和修复代码的错误和漏洞</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7973-sonarlint">https://plugins.jetbrains.com/plugin/7973-sonarlint</a></li></ul></li><li>FindBugs-IDEA<ul><li>描述：帮你找出代码中潜藏的 Bug</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/3847-findbugs-idea">https://plugins.jetbrains.com/plugin/3847-findbugs-idea</a></li></ul></li><li>MetricsReloaded<ul><li>描述：代码复杂度分析</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/93?pr=idea">https://plugins.jetbrains.com/plugin/93?pr=idea</a></li></ul></li></ul><h5 id="格式处理"><a href="#格式处理" class="headerlink" title="格式处理"></a>格式处理</h5><ul><li><p>CamelCase</p><ul><li>描述：快速字符串格式转换工具，如驼峰式</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7160-camelcase">https://plugins.jetbrains.com/plugin/7160-camelcase</a></li></ul></li><li><p>Json Parser</p><ul><li>描述：验证和格式化 Json 字符串的工具</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10650-json-parser">https://plugins.jetbrains.com/plugin/10650-json-parser</a></li></ul></li><li><p>Properties to YAML Converter</p><ul><li>描述：把 Properties 文件的格式转为 YAML 格式</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8000-properties-to-yaml-converter">https://plugins.jetbrains.com/plugin/8000-properties-to-yaml-converter</a></li></ul></li><li><p>String Manipulation</p><ul><li>描述：字符串快捷处理</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/2162-string-manipulation">https://plugins.jetbrains.com/plugin/2162-string-manipulation</a></li></ul></li></ul><h5 id="代码统计"><a href="#代码统计" class="headerlink" title="代码统计"></a>代码统计</h5><ul><li>WakaTime<ul><li>描述：代码统计和跟踪插件</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7425-wakatime">https://plugins.jetbrains.com/plugin/7425-wakatime</a></li></ul></li><li>Statistic<ul><li>描述：代码统计</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/4509-statistic">https://plugins.jetbrains.com/plugin/4509-statistic</a></li></ul></li></ul><h5 id="代码检索"><a href="#代码检索" class="headerlink" title="代码检索"></a>代码检索</h5><ul><li>OnlineSearch<ul><li>描述：选择文本后支持不同搜索引擎进行搜索</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8298-onlinesearch/">https://plugins.jetbrains.com/plugin/8298-onlinesearch/</a></li></ul></li><li>CodeSearch<ul><li>描述：快捷地打开搜索引擎进行搜索</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/12578-codesearch">https://plugins.jetbrains.com/plugin/12578-codesearch</a></li></ul></li></ul><h5 id="其他-1"><a href="#其他-1" class="headerlink" title="其他"></a>其他</h5><ul><li><p>Leetcode Editor</p><ul><li>描述：在 IDEA 里刷算法题</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/12132-leetcode-editor">https://plugins.jetbrains.com/plugin/12132-leetcode-editor</a></li></ul></li><li><p>File Watchers</p><ul><li>描述：监听文件，当其发生变化时自动执行命令</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7177-file-watchers">https://plugins.jetbrains.com/plugin/7177-file-watchers</a></li></ul></li><li><p>PlantUML integration</p><ul><li>描述：UML 建模工具，只需简单的代码就能画出复杂的 UML 类图</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7017-plantuml-integration">https://plugins.jetbrains.com/plugin/7017-plantuml-integration</a></li></ul></li><li><p>Bytecode Editor</p><ul><li>描述：字节码编辑工具</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8461-bytecode-editor">https://plugins.jetbrains.com/plugin/8461-bytecode-editor</a></li></ul></li><li><p>Alibaba Cloud Toolkit</p><ul><li>描述：阿里云工具集</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/11386-alibaba-cloud-toolkit">https://plugins.jetbrains.com/plugin/11386-alibaba-cloud-toolkit</a></li></ul></li><li><p>Tencent Cloud Toolkit</p><ul><li>描述：腾讯云工具集</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/15653-tencent-cloud-toolkit">https://plugins.jetbrains.com/plugin/15653-tencent-cloud-toolkit</a></li></ul></li><li><p>EduTools</p><ul><li>描述：面向教育者，允许创建、分享和加入练习</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10081-edutools">https://plugins.jetbrains.com/plugin/10081-edutools</a></li></ul></li><li><p>IDE Features Trainer</p><ul><li>描述：IntelliJ IDEA 官方出的学习辅助插件</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8554-ide-features-trainer">https://plugins.jetbrains.com/plugin/8554-ide-features-trainer</a></li></ul></li><li><p>JRebel Plugin</p><ul><li>描述：项目热更新，提高效率（可惜要收费）</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/4441-jrebel-and-xrebel-for-intellij">https://plugins.jetbrains.com/plugin/4441-jrebel-and-xrebel-for-intellij</a></li></ul></li><li><p>Key Promoter X</p><ul><li>描述：快捷键提示插件。当你执行鼠标操作时，如果该操作可被快捷键代替，会给出提示，帮助你自然形成使用快捷键的习惯，告别死记硬背。</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/9792-key-promoter-x/">https://plugins.jetbrains.com/plugin/9792-key-promoter-x/</a></li></ul></li><li><p>Translation</p><ul><li>描述：翻译插件，鼠标选中文本，点击右键即可自动翻译成多国语言。</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8579-translation/">https://plugins.jetbrains.com/plugin/8579-translation/</a></li></ul></li><li><p>idea_seat</p><ul><li>描述：自动提醒，防止你久坐不动</li><li>官网：<a href="https://github.com/s-unscrupulous/idea_seat">https://github.com/s-unscrupulous/idea_seat</a></li></ul></li><li><p>RestfulTool</p><ul><li>描述：辅助 web 开发的工具集</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/14280-restfultool">https://plugins.jetbrains.com/plugin/14280-restfultool</a></li></ul></li><li><p>Multirun</p><ul><li>描述：同时启动多应用</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7248-multirun">https://plugins.jetbrains.com/plugin/7248-multirun</a></li></ul></li></ul><h4 id="扩展增强类"><a href="#扩展增强类" class="headerlink" title="扩展增强类"></a>扩展增强类</h4><ul><li>Free Mybatis Plugin<ul><li>描述：MyBatis 增强插件，支持 mapper &#x3D;&gt; xml 的跳转、代码生成等功能</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8321-free-mybatis-plugin">https://plugins.jetbrains.com/plugin/8321-free-mybatis-plugin</a></li></ul></li><li>MyBatis Log Plugin<ul><li>描述：MyBatis SQL 提取和格式化输出</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10065-mybatis-log-plugin">https://plugins.jetbrains.com/plugin/10065-mybatis-log-plugin</a></li></ul></li><li>Maven Helper<ul><li>描述：Maven 辅助插件</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7179-maven-helper">https://plugins.jetbrains.com/plugin/7179-maven-helper</a></li></ul></li><li>Gradle View<ul><li>描述：Gradle 项目管理工具的扩展</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7150-gradle-view">https://plugins.jetbrains.com/plugin/7150-gradle-view</a></li></ul></li><li>Arthas Idea<ul><li>描述： Arthas 命令生成插件。Arthas 是阿里开源的 Java 在线诊断工具，该插件可以自动生成 Arthas 在线 Java 代码诊断命令，不用再到官网翻文档拼命令啦！</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/13581-arthas-idea/">https://plugins.jetbrains.com/plugin/13581-arthas-idea/</a></li></ul></li><li>GitToolBox<ul><li>描述： Git 增强插件。在自带的 Git 功能之上，新增了查看 Git 状态、自动拉取代码、提交通知等功能。最好用的是可以查看到每一行代码的最近一次提交信息。</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7499-gittoolbox/">https://plugins.jetbrains.com/plugin/7499-gittoolbox/</a></li></ul></li><li>BashSupport<ul><li>描述：支持 Bash 脚本文件的高亮和提示等</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/4230-bashsupport">https://plugins.jetbrains.com/plugin/4230-bashsupport</a></li></ul></li><li>Git Flow Integration<ul><li>描述：Git Flow 的图形界面操作</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7315-git-flow-integration">https://plugins.jetbrains.com/plugin/7315-git-flow-integration</a></li></ul></li><li>Gitee<ul><li>描述：开源中国的码云插件</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8383-gitee">https://plugins.jetbrains.com/plugin/8383-gitee</a></li></ul></li><li>Drools<ul><li>描述：规则引擎的扩展</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/16871-drools">https://plugins.jetbrains.com/plugin/16871-drools</a></li></ul></li><li>EnvFile<ul><li>描述：对多环境配置文件的支持</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7861-envfile">https://plugins.jetbrains.com/plugin/7861-envfile</a></li></ul></li><li>Kubernetes<ul><li>描述：容器管理的扩展</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10485-kubernetes">https://plugins.jetbrains.com/plugin/10485-kubernetes</a></li></ul></li><li>Vue.js<ul><li>描述：Vue.js 前端框架的扩展</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/9442-vue-js">https://plugins.jetbrains.com/plugin/9442-vue-js</a></li></ul></li><li>Scala<ul><li>描述：Scala 语言的扩展</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/1347-scala">https://plugins.jetbrains.com/plugin/1347-scala</a></li></ul></li><li>Zookeeper<ul><li>描述：Zookeeper 中间件的扩展</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/7364-zookeeper">https://plugins.jetbrains.com/plugin/7364-zookeeper</a></li></ul></li><li>Jms messenger<ul><li>描述：对 Java 消息服务的扩展</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10949-jms-messenger">https://plugins.jetbrains.com/plugin/10949-jms-messenger</a></li></ul></li><li>MapStruct Support<ul><li>描述：对 MapStruct 对象转换的支持</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10036-mapstruct-support">https://plugins.jetbrains.com/plugin/10036-mapstruct-support</a></li></ul></li><li>Big Data Tools<ul><li>描述：提供了 HDFS 等大数据框架的工具集</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/12494-big-data-tools">https://plugins.jetbrains.com/plugin/12494-big-data-tools</a></li></ul></li><li>CSV<ul><li>描述：支持 CSV 文件的高亮和提示等</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/10037-csv">https://plugins.jetbrains.com/plugin/10037-csv</a></li></ul></li><li>Ideolog<ul><li>描述：识别和格式化 log 日志文件</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/9746-ideolog">https://plugins.jetbrains.com/plugin/9746-ideolog</a></li></ul></li><li>Toml<ul><li>描述：支持 toml 文件的高亮和提示等</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/8195-toml">https://plugins.jetbrains.com/plugin/8195-toml</a></li></ul></li><li>.env files support<ul><li>描述：支持 .env 文件的高亮和提示等</li><li>官网：<a href="https://plugins.jetbrains.com/plugin/9525--env-files-support">https://plugins.jetbrains.com/plugin/9525--env-files-support</a></li></ul></li></ul><h3 id="常用软件"><a href="#常用软件" class="headerlink" title="常用软件"></a>常用软件</h3><p><strong>以下部分软件可在【公众号：程序员鱼皮】回复【软件】获取~</strong></p><h5 id="开发相关"><a href="#开发相关" class="headerlink" title="开发相关"></a>开发相关</h5><ul><li>JetBrains IDEA：Java 集成开发环境，凭学生邮箱可申请免费使用（<a href="https://www.jetbrains.com/idea/%EF%BC%89">https://www.jetbrains.com/idea/）</a></li><li>Reset Jetbrains：你懂的</li><li>Visual Studio Code：插件化代码编辑器（<a href="https://code.visualstudio.com/%EF%BC%89">https://code.visualstudio.com/）</a></li><li>Sublime Text：轻量代码编辑器（<a href="https://www.sublimetext.com/%EF%BC%89">https://www.sublimetext.com/）</a></li><li>Navicat：数据库管理软件（<a href="https://www.navicat.com.cn/%EF%BC%89">https://www.navicat.com.cn/）</a></li><li>JMeter：Java 性能测试工具（<a href="https://jmeter.apache.org/%EF%BC%89">https://jmeter.apache.org/）</a></li><li>JVisual VM：Java 运行状态可视化工具（<a href="https://visualvm.github.io/%EF%BC%89">https://visualvm.github.io/）</a></li><li>XShell：SSH 连接软件（<a href="https://www.netsarang.com/zh/xshell/%EF%BC%89">https://www.netsarang.com/zh/xshell/）</a></li><li>XFtp：FTP 连接软件（<a href="https://www.netsarang.com/zh/xftp/%EF%BC%89">https://www.netsarang.com/zh/xftp/）</a></li><li>Redis Desktop Manager：Redis 可视化管理工具（<a href="https://github.com/uglide/RedisDesktopManager%EF%BC%89">https://github.com/uglide/RedisDesktopManager）</a></li><li>Postman：接口测试工具（<a href="https://www.postman.com/%EF%BC%89">https://www.postman.com/）</a></li><li>VMware：虚拟机软件（<a href="https://www.vmware.com/%EF%BC%89">https://www.vmware.com/）</a></li><li>Chocolatey：Windows 软件包管理器（<a href="https://chocolatey.org/%EF%BC%89">https://chocolatey.org/）</a></li><li>Typora：写文档软件（<a href="https://typora.io/%EF%BC%89">https://typora.io/）</a></li></ul><h5 id="效率"><a href="#效率" class="headerlink" title="效率"></a>效率</h5><ul><li>Ditto：强大的剪切板（<a href="https://ditto-cp.sourceforge.io/%EF%BC%89">https://ditto-cp.sourceforge.io/）</a></li><li>uTools：插件化的效率工具（<a href="https://u.tools/%EF%BC%89">https://u.tools/）</a></li><li>XMind：思维导图软件（<a href="https://www.xmind.cn/%EF%BC%89">https://www.xmind.cn/）</a></li><li>Qdir：Windows 多窗口管理器（<a href="https://q-dir.en.softonic.com/%EF%BC%89">https://q-dir.en.softonic.com/）</a></li></ul><h3 id="项目源码（50-套）"><a href="#项目源码（50-套）" class="headerlink" title="项目源码（50 套）"></a>项目源码（50 套）</h3><h4 id="电商秒杀"><a href="#电商秒杀" class="headerlink" title="电商秒杀"></a>电商秒杀</h4><ul><li><p>天猫整站 J2EE：<a href="https://how2j.cn/module/115.htm">https://how2j.cn/module/115.htm</a></p></li><li><p>天猫整站 SSM：<a href="https://how2j.cn/module/134.html">https://how2j.cn/module/134.html</a></p></li><li><p>天猫整站 Springboot：<a href="https://how2j.cn/module/156.html">https://how2j.cn/module/156.html</a></p></li><li><p>mall-learning：<a href="https://github.com/macrozheng/mall-learningmall">https://github.com/macrozheng/mall-learningmall</a> （学习教程，架构、业务、技术要点全方位解析）</p></li><li><p>SpringBoot 电商商城系统 Mall4j：<a href="https://github.com/gz-yami/mall4j">https://github.com/gz-yami/mall4j</a></p></li><li><p>SpringBoot 完整电商系统 Mall：<a href="https://github.com/macrozheng/mall">https://github.com/macrozheng/mall</a> （包括前台商城系统及后台管理系统，基于 SpringBoot+MyBatis 实现）</p></li><li><p>newbee-mall：<a href="https://github.com/newbee-ltd/newbee-mall">https://github.com/newbee-ltd/newbee-mall</a> （一套电商系统，包括 newbee-mall 商城系统及 newbee-mall-admin 商城后台管理系统，基于 Spring Boot 2.X 及相关技术栈开发）</p></li><li><p>paascloud-master：<a href="https://github.com/paascloud/paascloud-master">https://github.com/paascloud/paascloud-master</a> （基于 spring cloud + vue + oAuth2.0，前后端分离商城系统）</p></li><li><p>mall-swarm：<a href="https://github.com/macrozheng/mall-swarm">https://github.com/macrozheng/mall-swarm</a> （一套微服务商城系统，采用了 Spring Cloud Greenwich、Spring Boot 2、MyBatis、Docker、Elasticsearch 等核心技术，同时提供了基于 Vue 的管理后台方便快速搭建系统）</p></li><li><p>onemall：<a href="https://github.com/YunaiV/onemall">https://github.com/YunaiV/onemall</a> （mall 商城，基于微服务的思想，构建在 B2C 电商场景下的项目实战。核心技术栈，是 Spring Boot + Dubbo 。未来，会重构成 Spring Cloud Alibaba）</p></li><li><p>litemall：<a href="https://github.com/linlinjava/litemall">https://github.com/linlinjava/litemall</a> （又一个小商城，litemall &#x3D; Spring Boot 后端 + Vue 管理员前端 + 微信小程序用户前端 + Vue 用户移动端）</p></li><li><p>xmall：<a href="https://github.com/Exrick/xmall">https://github.com/Exrick/xmall</a> （基于SOA架构的分布式电商购物商城 前后端分离 前台商城:Vue全家桶 后台管理系统）</p></li><li><p>miaosha：<a href="https://github.com/qiurunze123/miaosha">https://github.com/qiurunze123/miaosha</a> （秒杀系统设计与实现）</p></li><li><p>SecKill：<a href="https://github.com/hfbin/Seckill">https://github.com/hfbin/Seckill</a> （基于 SpringBoot+Mybatis+Redis+RabbitMQ 秒杀系统）</p></li></ul><h4 id="博客论坛"><a href="#博客论坛" class="headerlink" title="博客论坛"></a>博客论坛</h4><ul><li><p><a href="https://github.com/langhsu/mblog">Mblog</a>：开源 Java 博客系统</p></li><li><p><a href="https://github.com/halo-dev/halo">halo</a>：一个优秀的开源博客发布应用</p></li><li><p><a href="https://github.com/Qbian61/forum-java">forum-java</a>：一款用 Java（spring boot） 实现的现代化社区（论坛&#x2F;问答&#x2F;BBS&#x2F;社交网络&#x2F;博客）系统平台</p></li><li><p><a href="https://github.com/lenve/vhr">vhr</a>：微人事是一个前后端分离的人力资源管理系统，项目采用 SpringBoot+Vue 开发。</p></li><li><p><a href="https://github.com/cloudfavorites/favorites-web">favorites-web</a>:云收藏 Spring Boot 2.X 开源项目。云收藏是一个使用 Spring Boot 构建的开源网站，可以让用户在线随时随地收藏的一个网站，在网站上分类整理收藏的网站或者文章。</p></li><li><p><a href="https://github.com/codedrinker/community">community</a>：码问，开源论坛、问答系统，现有功能提问、回复、通知、最新、最热、消除零回复功能。技术栈 Spring、Spring Boot、MyBatis、MySQL&#x2F;H2、Bootstrap</p></li><li><p><a href="https://github.com/yourkevin/NiterForum">NiterForum</a>：尼特社区-NiterForum-一个论坛&#x2F;社区程序。后端Springboot&#x2F;MyBatis&#x2F;Maven&#x2F;MySQL，前端Thymeleaf&#x2F;Layui。可供初学者，学习、交流使用。</p></li><li><p><a href="https://github.com/lenve/VBlog">VBlog</a>：V部落，Vue+SpringBoot实现的多用户博客管理平台!</p></li><li><p><a href="https://github.com/damoqiongqiu/NiceFish">NiceFish</a>：SpringBoot&#x2F;SpringCloud 前后端分离项目</p></li><li><p><a href="https://github.com/ZHENFENG13/My-Blog">My-Blog</a>： My Blog 是由 SpringBoot + Mybatis + Thymeleaf 等技术实现的 Java 博客系统，页面美观、功能齐全、部署简单及完善的代码，一定会给使用者无与伦比的体验。</p></li><li><p><a href="https://github.com/ZHENFENG13/My-Blog-layui">My-Blog-layui</a>：layui 版本的 My-Blog : A simple &amp; beautiful blogging system implemented with spring-boot &amp; layui &amp; thymeleaf &amp; mybatis My Blog 是由  SpringBoot + Layui + Mybatis + Thymeleaf 等技术实现的 Java  博客系统，页面美观、功能齐全、部署简单及完善的代码，一定会给使用者无与伦比的体验</p></li><li><p><a href="https://github.com/88250/symphony">symphony</a>：Java 实现的现代化社区</p></li></ul><h4 id="管理系统"><a href="#管理系统" class="headerlink" title="管理系统"></a>管理系统</h4><ul><li><p><a href="https://github.com/wxiaoqi/Spring-Cloud-Admin">Spring-Cloud-Admin</a>：Cloud-Admin 是国内首个基于 Spring Cloud 微服务化开发平台，具有统一授权、认证后台管理系统，其中包含具备用户管理、资源权限管理、网关 API 管理等多个模块，支持多业务系统并行开发，可以作为后端服务的开发脚手架。代码简洁，架构清晰，适合学习和直接项目中使用。核心技术采用 Spring Boot2 以及 Spring Cloud Gateway 相关核心组件，前端采用 vue-element-admin 组件。</p></li><li><p><a href="https://github.com/tomsun28/bootshiro">bootshiro</a>：基于 springboot+shiro+jwt 的资源无状态认证权限管理系统后端</p></li><li><p><a href="https://github.com/72crm/72crm-java">悟空CRM</a>：基于jfinal+vue+ElementUI的前后端分离CRM系统</p></li><li><p><a href="https://github.com/elunez/eladmin">EL-ADMIN</a>：基于 SpringBoot 的后台管理系统</p></li><li><p><a href="https://gitee.com/log4j/pig">pig</a>：基于 Spring Boot 2.2、 Spring Cloud Hoxton &amp; Alibaba、 OAuth2 的 RBAC 权限管理系统。</p></li><li><p><a href="https://github.com/wuyouzhuguli/FEBS-Shiro">FEBS-Shiro</a>：Spring Boot 2.1.3，Shiro1.4.0 &amp; Layui 2.5.4 权限管理系统。</p></li><li><p><a href="https://github.com/Heeexy/SpringBoot-Shiro-Vue">Spring Boot-Shiro-Vue</a>：基于Spring Boot-Shiro-Vue 的权限管理</p></li><li><p><a href="https://github.com/ZeroWdd/studentmanager">studentmanager</a>：基于springboot+mybatis学生管理系统</p></li><li><p><a href="https://github.com/jishenghua/jshERP">jshERP</a>：华夏ERP基于SpringBoot框架和SaaS模式，立志为中小企业提供开源好用的ERP软件，目前专注进销存+财务功能。主要模块有零售管理、采购管理、销售管理、仓库管理、财务管理、报表查询、系统管理等。支持预付款、收入支出、仓库调拨、组装拆卸、订单等特色功能。拥有库存状况、出入库统计等报表。同时对角色和权限进行了细致全面控制，精确到每个按钮和菜单。</p></li><li><p><a href="https://github.com/misterchaos/HotelSystem">HotelSystem</a>：酒店管理系统 Java,tomcat,mysql,servlet,jsp实现，没有使用任何框架</p></li></ul><h4 id="开发平台"><a href="#开发平台" class="headerlink" title="开发平台"></a>开发平台</h4><ul><li><a href="https://github.com/2014shijina2014/open-capacity-platform">open-capacity-platform</a>：微服务能力开发平台</li><li><a href="https://github.com/zhangdaiscott/jeecg-boot">jeecg-boot</a>：JeecgBoot是一款基于BPM的低代码平台！前后端分离架构 SpringBoot 2.x，SpringCloud，Ant  Design&amp;Vue，Mybatis-plus，Shiro，JWT，支持微服务。强大的代码生成器让前后端代码一键生成，实现低代码开发！</li></ul><h4 id="其他-2"><a href="#其他-2" class="headerlink" title="其他"></a>其他</h4><ul><li><p><a href="https://github.com/mindskip/xzs">学之思在线考试系统</a>：一款 java + vue 的前后端分离的考试系统</p></li><li><p><a href="https://github.com/Jackson0714/PassJava-Platform">PassJava-Platform</a>：一款面试刷题的 Spring Cloud 开源系统</p></li><li><p><a href="https://github.com/kekingcn/kkFileView">kkFileView</a>：使用spring boot打造文件文档在线预览项目</p></li><li><p><a href="https://github.com/baomidou/dynamic-datasource-spring-boot-starter">dynamic-datasource</a>：一个基于springboot的快速集成多数据源的启动器</p></li><li><p><a href="https://github.com/373675032/moti-cloud">moti-cloud</a>：莫提网盘，基于 SpringBoot+MyBatis+ThymeLeaf+BootStrap，适合初学者</p></li><li><p><a href="https://github.com/qiurunze123/threadandjuc">threadandjuc</a>：three-high-import 高可用\高可靠\高性能，三高多线程导入系统（该项目意义为理论贯通)</p></li><li><p><a href="https://github.com/proxyee-down-org/proxyee-down">proxyee-down</a>：http下载工具，基于http代理，支持多连接分块下载</p></li><li><p><a href="https://github.com/sfturing/hosp_order">hosp_order</a>：医院预约挂号系统，基于 SSM 框架</p></li><li><p><a href="https://how2j.cn/module/170.html">趋势投资 SpringCloud</a></p></li><li><p><a href="https://how2j.cn/module/176.html">DiyTomcat</a></p></li></ul><h2 id="阶段-6：Java-高级"><a href="#阶段-6：Java-高级" class="headerlink" title="阶段 6：Java 高级"></a>阶段 6：Java 高级</h2><h3 id="目标-5"><a href="#目标-5" class="headerlink" title="目标"></a>目标</h3><p>不满足于能做，而是通过更 <strong>深入</strong> 和 <strong>广泛</strong> 的学习，实现高质量的代码和更优秀的架构，培养解决问题的能力。</p><p>已经到了这个阶段，建议除了看完成的教程外，平时多自主搜索信息去学习，积少成多。比如遇到了一个不了解的名词，可以去网上搜一下，感兴趣的话再进行下一步的学习。</p><h3 id="🌖-并发编程（21-天）"><a href="#🌖-并发编程（21-天）" class="headerlink" title="🌖 并发编程（21 天）"></a>🌖 并发编程（21 天）</h3><p>对 Java 后端开发程序员来说，懂得如何利用有限的系统资源来提高系统的性能是很重要的，也是大厂面试考察的重点，因此并发编程（尤其是 Java 并发包的使用）这块的知识很重要。</p><p>把它放到高级，是因为在学并发编程前，需要有一定的编程经验、了解一定的操作系统知识。</p><h4 id="知识-21"><a href="#知识-21" class="headerlink" title="知识"></a>知识</h4><ul><li>线程和进程</li><li>线程状态</li><li>并行和并发</li><li>同步和异步</li><li>Synchronized</li><li>Volatile 关键字</li><li>Lock 锁</li><li>死锁</li><li>可重入锁</li><li>线程安全</li><li>线程池</li><li>JUC 的使用</li><li>AQS</li><li>Fork Join</li><li>CAS</li></ul><h4 id="学习建议-23"><a href="#学习建议-23" class="headerlink" title="学习建议"></a>学习建议</h4><p>并发编程入门不难，依然是 <strong>先学会使用</strong> 基础的 Java 并发包， 再通过大量地实践和测试，了解一些原理，才能真正掌握何时使用、如何更合理地使用并发编程。而不是张口闭口多线程，上天入地高并发。</p><h4 id="经典面试题-16"><a href="#经典面试题-16" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>volatile 关键字的作用</li><li>使用线程池有哪些好处？</li><li>线程池参数如何设置？</li><li>什么是线程安全问题，如何解决？</li><li>介绍 synchronized 的锁升级机制</li><li>CopyOnWriteArrayList 适用于哪种场景？</li></ol><h4 id="资源-22"><a href="#资源-22" class="headerlink" title="资源"></a>资源</h4><ul><li><p>视频</p><ul><li>⭐【尚硅谷】大厂必备技术之JUC并发编程2021最新版：<a href="https://www.bilibili.com/video/BV1Kw411Z7dF">https://www.bilibili.com/video/BV1Kw411Z7dF</a> （特点是短、精、新）</li><li>黑马程序员全面深入学习Java并发编程：<a href="https://www.bilibili.com/video/BV16J411h7Rd">https://www.bilibili.com/video/BV16J411h7Rd</a> （讲的很细、全面深入）</li></ul></li><li><p>书籍</p><ul><li>⭐《Java并发编程实战》（国外的经典）</li><li>⭐《Java 并发编程艺术》（国人写的，理论思想内容较多，有时间建议反复看）</li></ul></li><li><p>项目</p><ul><li>⭐ Java 并发知识点总结：<a href="https://github.com/CL0610/Java-concurrency">https://github.com/CL0610/Java-concurrency</a></li></ul></li></ul><h3 id="🌖-JVM（30-天）"><a href="#🌖-JVM（30-天）" class="headerlink" title="🌖 JVM（30 天）"></a>🌖 JVM（30 天）</h3><p>想要深入理解 Java，探秘 Java 跨平台的奥秘，一定要了解 Java 底层的虚拟机技术。</p><p>了解虚拟机、掌握虚拟机性能调优方法，有助于你写出更高性能、资源占用更小的优质程序。</p><p>在学习 JVM 的过程中，也能学到很多精妙的设计，开拓思路。</p><h4 id="知识-22"><a href="#知识-22" class="headerlink" title="知识"></a>知识</h4><ul><li>JVM 内存结构</li><li>JVM 生命周期</li><li>主流虚拟机</li><li>Java 代码执行流程</li><li>类加载<ul><li>类加载器</li><li>类加载过程</li><li>双亲委派机制</li></ul></li><li>垃圾回收<ul><li>垃圾回收器</li><li>垃圾回收策略</li><li>垃圾回收算法</li><li>StopTheWorld</li></ul></li><li>字节码</li><li>内存分配和回收</li><li>JVM 性能调优<ul><li>性能分析方法</li><li>常用工具</li><li>参数设置</li></ul></li><li>Java 探针</li><li>线上故障分析</li></ul><h4 id="学习建议-24"><a href="#学习建议-24" class="headerlink" title="学习建议"></a>学习建议</h4><p>JVM 的知识略显枯燥，建议先看视频，有实操的地方一定要实操！自己多去分析。</p><p>第一遍不理解没有关系，可以再看书来巩固，想要真正学好，《深入理解 Java 虚拟机（第三版）》一定要读。</p><p>如果只是为了通过面试可以直接看更精简的视频，比如狂神的。</p><h4 id="经典面试题-17"><a href="#经典面试题-17" class="headerlink" title="经典面试题"></a>经典面试题</h4><ol><li>介绍 JVM 的内存模型？</li><li>JVM 内存为什么要分代？</li><li>介绍一次完整的 GC 流程</li><li>介绍双亲委派模型，为什么需要它？</li></ol><h4 id="资源-23"><a href="#资源-23" class="headerlink" title="资源"></a>资源</h4><ul><li>视频<ul><li>⭐ 尚硅谷宋红康 - JVM 全套教程详解：<a href="https://www.bilibili.com/video/BV1PJ411n7xZ">https://www.bilibili.com/video/BV1PJ411n7xZ</a> （讲得相当全面！附有实操）</li><li>【狂神说Java】JVM快速入门篇：<a href="https://www.bilibili.com/video/BV1iJ411d7jS">https://www.bilibili.com/video/BV1iJ411d7jS</a> （讲得有点浅，但都是面试重点，时间紧的小伙伴可以直接看这个）</li></ul></li><li>书籍<ul><li>⭐《深入理解 Java 虚拟机（第三版）》（有理论有实践，内容丰富，不可多得的 JVM 学习神书，就是有点废头发）</li></ul></li><li>项目<ul><li>⭐ Java 虚拟机底层原理知识总结：<a href="https://github.com/doocs/jvm">https://github.com/doocs/jvm</a></li></ul></li><li>实战<ul><li>阿里云 JVM 实战：<a href="https://developer.aliyun.com/graph/java/point/64">https://developer.aliyun.com/graph/java/point/64</a></li></ul></li><li>工具<ul><li>Arthas 开源 Java 诊断工具：<a href="https://github.com/alibaba/arthas">https://github.com/alibaba/arthas</a></li></ul></li></ul><h3 id="架构设计"><a href="#架构设计" class="headerlink" title="架构设计"></a>架构设计</h3><h4 id="🌖-分布式"><a href="#🌖-分布式" class="headerlink" title="🌖 分布式"></a>🌖 分布式</h4><ul><li>分布式理论<ul><li>CAP</li><li>BASE</li></ul></li><li>分布式缓存<ul><li>Redis</li><li>Memcached</li><li>Etcd</li></ul></li><li>一致性算法<ul><li>Raft</li><li>Paxos</li><li>一致性哈希</li></ul></li><li>分布式事务<ul><li>解决方案<ul><li>2PC</li><li>3PC</li><li>TCC</li><li>本地消息表</li><li>MQ 事务消息</li><li>最大努力通知</li></ul></li><li>LCN 分布式事务框架：<a href="https://github.com/codingapi/tx-lcn">https://github.com/codingapi/tx-lcn</a></li></ul></li><li>分布式 id 生成<ul><li>Leaf 分布式 id 生成服务：<a href="https://github.com/Meituan-Dianping/Leaf">https://github.com/Meituan-Dianping/Leaf</a></li></ul></li><li>分布式任务调度<ul><li>XXL-JOB 调度平台：<a href="https://www.xuxueli.com/xxl-job/">https://www.xuxueli.com/xxl-job/</a></li><li>elastic-job：<a href="https://gitee.com/elasticjob/elastic-job">https://gitee.com/elasticjob/elastic-job</a></li></ul></li><li>分布式服务调用<ul><li>trpc</li></ul></li><li>分布式存储<ul><li>HDFS</li><li>Ceph</li></ul></li><li>分布式数据库<ul><li>TiDB</li><li>OceanBase</li></ul></li><li>分布式文件系统<ul><li>HDFS</li></ul></li><li>分布式协调<ul><li>Zookeeper</li></ul></li><li>分布式监控<ul><li>Prometheus</li><li>Zabbix</li></ul></li><li>分布式消息队列<ul><li>RabbitMQ</li><li>Kafka</li><li>Apache Pulsar</li></ul></li><li>分布式日志收集<ul><li>Elastic Stack</li><li>Loki</li></ul></li><li>分布式搜索引擎<ul><li>Elasticsearch</li></ul></li><li>分布式链路追踪<ul><li>Apache SkyWalking</li></ul></li><li>分布式配置中心<ul><li>Apollo</li><li>Nacos</li></ul></li></ul><h4 id="🌗-高可用"><a href="#🌗-高可用" class="headerlink" title="🌗 高可用"></a>🌗 高可用</h4><ul><li>限流</li><li>降级熔断</li><li>冷备</li><li>双机热备</li><li>同城双活</li><li>异地双活</li><li>异地多活</li><li>容灾备份</li></ul><h4 id="🌗-高并发"><a href="#🌗-高并发" class="headerlink" title="🌗 高并发"></a>🌗 高并发</h4><ul><li>数据库<ul><li>分库分表<ul><li>MyCat 中间件</li><li>Apache ShardingSphere 中间件</li></ul></li><li>读写分离</li></ul></li><li>缓存<ul><li>缓存雪崩</li><li>缓存击穿</li><li>缓存穿透</li></ul></li><li>负载均衡<ul><li>负载均衡算法</li><li>软硬件负载均衡（2、3、4、7 层）</li></ul></li></ul><h4 id="🌘-服务网格"><a href="#🌘-服务网格" class="headerlink" title="🌘 服务网格"></a>🌘 服务网格</h4><p>服务网格用来描述组成应用程序的微服务网络以及它们之间的交互。服务网格的规模和复杂性不断的增长，它将会变得越来越难以理解和管理，常见的需求包括服务发现、负载均衡、故障恢复、度量和监控等。</p><h5 id="知识-23"><a href="#知识-23" class="headerlink" title="知识"></a>知识</h5><ul><li>Istio<ul><li>流量管理</li><li>安全性</li><li>可观测性</li></ul></li><li>Envoy（开源的边缘和服务代理）</li></ul><h5 id="资源-24"><a href="#资源-24" class="headerlink" title="资源"></a>资源</h5><ul><li>资料大全：<a href="https://www.servicemesher.com/awesome-servicemesh/">https://www.servicemesher.com/awesome-servicemesh/</a></li><li>istio 官方文档：<a href="https://preliminary.istio.io/latest/zh">https://preliminary.istio.io/latest/zh</a></li><li>istio 视频教程：<a href="https://www.bilibili.com/video/BV1Lf4y1x7j8">https://www.bilibili.com/video/BV1Lf4y1x7j8</a></li><li>在线体验 istio：<a href="https://katacoda.com/courses/istio/">https://katacoda.com/courses/istio/</a></li></ul><h4 id="🌘-DDD-领域驱动设计"><a href="#🌘-DDD-领域驱动设计" class="headerlink" title="🌘 DDD 领域驱动设计"></a>🌘 DDD 领域驱动设计</h4><p>将数据、业务流程抽象成容易理解的领域模型，通过用代码实现领域模型，来组成完整的业务系统。</p><h5 id="知识-24"><a href="#知识-24" class="headerlink" title="知识"></a>知识</h5><ul><li>DDD 的优势</li><li>DDD 的适用场景</li><li>DDD 核心概念<ul><li>领域模型分类：失血、贫血、充血、涨血</li><li>子域划分：核心域、通用域、支撑域</li><li>限界上下文</li><li>实体和值对象</li><li>聚合设计</li><li>领域事件</li></ul></li><li>DDD 实践</li></ul><h5 id="资源-25"><a href="#资源-25" class="headerlink" title="资源"></a>资源</h5><ul><li>DDD 入门：<a href="https://juejin.cn/post/7004002483601145863">https://juejin.cn/post/7004002483601145863</a></li><li>DDD 编码实践：<a href="https://juejin.cn/post/6844903903104860174">https://juejin.cn/post/6844903903104860174</a></li><li>DDD 实践教程：<a href="https://www.bilibili.com/video/BV1Y341167Xp">https://www.bilibili.com/video/BV1Y341167Xp</a></li></ul><h4 id="🌘-其他"><a href="#🌘-其他" class="headerlink" title="🌘 其他"></a>🌘 其他</h4><ul><li>Sidecar</li><li>Serverless</li><li>云原生</li></ul><h4 id="学习建议-25"><a href="#学习建议-25" class="headerlink" title="学习建议"></a>学习建议</h4><p>架构设计的学习没有顶点，多看文章，思考每种设计的优缺点和适用场景，有机会的话在企业中实践即可。</p><p>还在学校、或者初入这行的同学切记，千万不要一味地去背诵架构设计的八股文。你可以背，但是这一块的知识只有结合具体的项目才有意义，所以要多做项目去实践设计的合理性，而不是什么设计都咔咔往系统里去怼。比如面试问到分布式事务，能结合自己项目中用分布式事务解决问题的经验去回答最好。</p><h3 id="🌘-其他技术"><a href="#🌘-其他技术" class="headerlink" title="🌘 其他技术"></a>🌘 其他技术</h3><ul><li>热数据探测技术：京东 HotKey</li><li>数据库流水订阅：阿里 Canal</li><li>监控告警</li><li>应用安全</li><li>故障演练</li><li>流量回放</li></ul><h2 id="阶段-7：Java-求职"><a href="#阶段-7：Java-求职" class="headerlink" title="阶段 7：Java 求职"></a>阶段 7：Java 求职</h2><h3 id="目标-6"><a href="#目标-6" class="headerlink" title="目标"></a>目标</h3><p>找到好工作</p><h3 id="建议"><a href="#建议" class="headerlink" title="建议"></a>建议</h3><ol><li>尽早做规划，可以通过大厂招聘官网的岗位描述来了解岗位的要求</li><li>雕琢一份优秀的简历，推荐阅读：<a href="https://mp.weixin.qq.com/s/wfabzdpOPdq89faFFpZ4NA">https://mp.weixin.qq.com/s/wfabzdpOPdq89faFFpZ4NA</a></li><li>多读面经，坚持刷算法</li><li>多参与面试，持续复盘总结</li></ol><h3 id="资源-26"><a href="#资源-26" class="headerlink" title="资源"></a>资源</h3><h4 id="校招岗位"><a href="#校招岗位" class="headerlink" title="校招岗位"></a>校招岗位</h4><ul><li>⭐ 牛客求职校招版：<a href="https://www.nowcoder.com/recommend/campus">https://www.nowcoder.com/recommend/campus</a></li><li>阿里 Java 开发：<a href="https://campus.alibaba.com/position.htm?refno=12699">https://campus.alibaba.com/position.htm?refno=12699</a></li><li>腾讯后台开发：<a href="https://join.qq.com/post_detail.html?pid=1&id=101&tid=2">https://join.qq.com/post_detail.html?pid=1&amp;id=101&amp;tid=2</a></li><li>腾讯全栈开发：<a href="https://join.qq.com/post_detail.html?pid=1&id=137&tid=2">https://join.qq.com/post_detail.html?pid=1&amp;id=137&amp;tid=2</a></li><li>腾讯运营开发：<a href="https://join.qq.com/post_detail.html?pid=1&id=105&tid=2">https://join.qq.com/post_detail.html?pid=1&amp;id=105&amp;tid=2</a></li><li>美团后端开发：<a href="https://campus.meituan.com/jobs?jobFamily=1&jobId=4005&jobType=1&pageNo=2">https://campus.meituan.com/jobs?jobFamily=1&amp;jobId=4005&amp;jobType=1&amp;pageNo=2</a></li><li>华为软件开发：<a href="https://career.huawei.com/reccampportal/portal5/campus-recruitment-detail.html?jobId=176044">https://career.huawei.com/reccampportal/portal5/campus-recruitment-detail.html?jobId=176044</a></li></ul><h4 id="社招岗位"><a href="#社招岗位" class="headerlink" title="社招岗位"></a>社招岗位</h4><ul><li>⭐ 牛客求职社招版：<a href="https://www.nowcoder.com/recommend/fulltime">https://www.nowcoder.com/recommend/fulltime</a></li><li>阿里社招：<a href="https://job.alibaba.com/zhaopin/positionList.htm">https://job.alibaba.com/zhaopin/positionList.htm</a></li><li>腾讯社招：<a href="https://careers.tencent.com/search.html">https://careers.tencent.com/search.html</a></li></ul><h4 id="实习"><a href="#实习" class="headerlink" title="实习"></a>实习</h4><ul><li>⭐ 牛客实习广场：<a href="https://nowpick.nowcoder.com/w/intern/center">https://nowpick.nowcoder.com/w/intern/center</a></li><li>实习僧：<a href="https://www.shixiseng.com/">https://www.shixiseng.com/</a></li></ul><h4 id="内推"><a href="#内推" class="headerlink" title="内推"></a>内推</h4><ul><li>牛客内推：<a href="https://www.nowcoder.com/discuss/referral/index">https://www.nowcoder.com/discuss/referral/index</a></li></ul><h4 id="鱼皮经历"><a href="#鱼皮经历" class="headerlink" title="鱼皮经历"></a>鱼皮经历</h4><ul><li>从 0 开始的编程学习进大厂经历<ul><li>⭐ 我学计算机的四年，共勉：<a href="https://mp.weixin.qq.com/s/Nc7Ee2UyrPg6hGseQUCzeg">https://mp.weixin.qq.com/s/Nc7Ee2UyrPg6hGseQUCzeg</a></li></ul></li><li>实习经历<ul><li><p>⭐ 我的第一份实习：<a href="https://mp.weixin.qq.com/s/SwGiRzyTkCOOCUXfXPR1Pw">https://mp.weixin.qq.com/s/SwGiRzyTkCOOCUXfXPR1Pw</a></p></li><li><p>⭐ 我的第二份实习，字节跳动：<a href="https://mp.weixin.qq.com/s/3q-YIfuZBjPjSnamNwOraw">https://mp.weixin.qq.com/s/3q-YIfuZBjPjSnamNwOraw</a></p></li></ul></li></ul><h4 id="知识总结"><a href="#知识总结" class="headerlink" title="知识总结"></a>知识总结</h4><ul><li>⭐ 牛客 Java 面试宝典：<a href="https://www.nowcoder.com/tutorial/94">https://www.nowcoder.com/tutorial/94</a></li><li>⭐ JavaGuide：<a href="https://snailclimb.gitee.io/javaguide/#/">https://snailclimb.gitee.io/javaguide/#/</a></li><li>⭐ CS-Notes：<a href="http://www.cyc2018.xyz/">http://www.cyc2018.xyz/</a></li><li>阿里 Java 技术图谱：<a href="https://developer.aliyun.com/graph/java">https://developer.aliyun.com/graph/java</a></li></ul><h4 id="面经"><a href="#面经" class="headerlink" title="面经"></a>面经</h4><ul><li><p>⭐ 牛客面经：<a href="https://www.nowcoder.com/discuss/experience?tagId=639">https://www.nowcoder.com/discuss/experience?tagId=639</a></p></li><li><p>秋招总结&amp;Java心得（双非硕士，已拿11+5家Offer）：<a href="https://www.nowcoder.com/discuss/327041">https://www.nowcoder.com/discuss/327041</a></p></li><li><p>被疫情逼得gap year的完全菜鸡的春招之路。：<a href="https://www.nowcoder.com/discuss/427812">https://www.nowcoder.com/discuss/427812</a></p></li><li><p>谨以此文纪念2019年春招：<a href="https://www.nowcoder.com/discuss/181212">https://www.nowcoder.com/discuss/181212</a></p></li><li><p>2019秋招经验谈：<a href="https://www.nowcoder.com/discuss/122463">https://www.nowcoder.com/discuss/122463</a></p></li><li><p>秋招心得：<a href="https://www.nowcoder.com/discuss/317888">https://www.nowcoder.com/discuss/317888</a></p></li><li><p>非科班本科菜鸡投的337家(Java后台)：<a href="https://www.nowcoder.com/discuss/133333">https://www.nowcoder.com/discuss/133333</a></p></li><li><p>2018 这一年：<a href="https://www.nowcoder.com/discuss/137593">https://www.nowcoder.com/discuss/137593</a></p></li><li><p>双非的秋招，感谢牛客：<a href="https://www.nowcoder.com/discuss/142181">https://www.nowcoder.com/discuss/142181</a></p></li><li><p>二本本科Java上岸(滴滴京东58)2019秋招心得+面经：<a href="https://www.nowcoder.com/discuss/333912">https://www.nowcoder.com/discuss/333912</a></p></li><li><p>碎碎念流水账特别长 of 自己的 Java 秋招：<a href="https://www.nowcoder.com/discuss/354154">https://www.nowcoder.com/discuss/354154</a></p></li><li><p>秋招之路：个人历程以及面经总结：<a href="https://www.nowcoder.com/discuss/344311">https://www.nowcoder.com/discuss/344311</a></p></li><li><p>【秋招面经总结】菜逼的坎坷之路（JAVA后台研发）：<a href="https://www.nowcoder.com/discuss/352030">https://www.nowcoder.com/discuss/352030</a></p></li><li><p>阿里11面+EMC+网易+美团面经：<a href="https://www.nowcoder.com/discuss/105302">https://www.nowcoder.com/discuss/105302</a></p></li><li><p>2018秋招阿里巴巴java笔试试题：<a href="https://www.nowcoder.com/discuss/67379">https://www.nowcoder.com/discuss/67379</a></p></li><li><p>我的秋招经验分享（已拿BAT头条网易）：<a href="https://www.nowcoder.com/discuss/117071">https://www.nowcoder.com/discuss/117071</a></p></li><li><p>20届的阿里 头条 网易  滴滴 百度 小米等Java面经：<a href="https://www.nowcoder.com/discuss/68289">https://www.nowcoder.com/discuss/68289</a></p></li><li><p>拿了5个offer，回馈牛友发一波面经（长文详细）：<a href="https://www.nowcoder.com/discuss/79051">https://www.nowcoder.com/discuss/79051</a></p></li><li><p>【总结】零基础学习一年拿到BAT三家offer：<a href="https://www.nowcoder.com/discuss/188241">https://www.nowcoder.com/discuss/188241</a></p></li><li><p>本科生秋招经验总结帖：<a href="https://www.nowcoder.com/discuss/296586">https://www.nowcoder.com/discuss/296586</a></p></li><li><p>双非渣硕校招总结+Java面经（网易阿里百度等）：<a href="https://www.nowcoder.com/discuss/134850">https://www.nowcoder.com/discuss/134850</a></p></li><li><p>2018秋招笔试科大讯飞java笔试试题：<a href="https://www.nowcoder.com/discuss/67684">https://www.nowcoder.com/discuss/67684</a></p></li><li><p>头条后台研发面经（共三面）：<a href="https://www.nowcoder.com/discuss/94233">https://www.nowcoder.com/discuss/94233</a></p></li><li><p>一点点小经验和几家Java面经：<a href="https://www.nowcoder.com/discuss/138434">https://www.nowcoder.com/discuss/138434</a></p></li><li><p>感谢牛客网！发一波面经！阿里、网易游戏、京东等offer。：<a href="https://www.nowcoder.com/discuss/101712">https://www.nowcoder.com/discuss/101712</a></p></li><li><p>非科班水硕的春招实习总结- Java 后台开发：<a href="https://www.nowcoder.com/discuss/186528">https://www.nowcoder.com/discuss/186528</a></p></li><li><p>十面阿里，七面头条，六个Offer，春招结束：<a href="https://www.nowcoder.com/discuss/80156">https://www.nowcoder.com/discuss/80156</a></p></li><li><p>成功上岸，最后一波回馈：<a href="https://www.nowcoder.com/discuss/176434">https://www.nowcoder.com/discuss/176434</a></p></li><li><p>【Java后台开发面经】阿里|网易|头条|腾讯|华为：<a href="https://www.nowcoder.com/discuss/108655">https://www.nowcoder.com/discuss/108655</a></p></li><li><p>三跨学生的面经（头条、拼多多、华为、vivo）：<a href="https://www.nowcoder.com/discuss/195640">https://www.nowcoder.com/discuss/195640</a></p></li></ul><h4 id="面试题解"><a href="#面试题解" class="headerlink" title="面试题解"></a>面试题解</h4><ul><li>⭐ 2021 最新 Java 笔试、面试题合集：<a href="https://pan.baidu.com/s/1DE4HAu-DS80n7VOaGpU_KQ">https://pan.baidu.com/s/1DE4HAu-DS80n7VOaGpU_KQ</a> （密码：lkw1）</li><li>牛客网 Java开发校招面试考点汇总（附面试题和答案）：<a href="https://www.nowcoder.com/discuss/161991">https://www.nowcoder.com/discuss/161991</a></li><li>50w字+的技术类校招面试题汇总（附答案+考点）：<a href="https://www.nowcoder.com/discuss/164726">https://www.nowcoder.com/discuss/164726</a></li><li>硬件，软件（后端开发，算法，数据分析等）历年企业笔试真题汇总：<a href="https://www.nowcoder.com/discuss/212783">https://www.nowcoder.com/discuss/212783</a></li></ul><h4 id="视频"><a href="#视频" class="headerlink" title="视频"></a>视频</h4><ul><li><p>⭐ 尚硅谷 2021 逆袭版 Java 面试题第三季：<a href="https://www.bilibili.com/video/BV1Hy4y1B78T">https://www.bilibili.com/video/BV1Hy4y1B78T</a></p></li><li><p>⭐ 阿里大佬透彻讲解 Java 面试 500 道必考题：<a href="https://www.bilibili.com/video/BV1yU4y1J7Bb">https://www.bilibili.com/video/BV1yU4y1J7Bb</a></p></li></ul><h2 id="阶段-8：持续学习"><a href="#阶段-8：持续学习" class="headerlink" title="阶段 8：持续学习"></a>阶段 8：持续学习</h2><h3 id="目标-7"><a href="#目标-7" class="headerlink" title="目标"></a>目标</h3><p>持续追求技术的深度和广度，培养自己的 <strong>核心竞争力</strong> 和 <strong>不可替代性</strong> ，学无止境！</p><h3 id="学习建议-26"><a href="#学习建议-26" class="headerlink" title="学习建议"></a>学习建议</h3><p>自主学习，自主搜索教程，多看书，多阅读技术博客，多实践。</p><h3 id="学习方向"><a href="#学习方向" class="headerlink" title="学习方向"></a>学习方向</h3><h4 id="框架源码"><a href="#框架源码" class="headerlink" title="框架源码"></a>框架源码</h4><ul><li>Spring</li><li>SpringBoot</li><li>SpringMVC</li><li>MyBatis</li><li>Netty</li><li>Dubbo</li><li>SpringCloud</li></ul><h4 id="计算机原理"><a href="#计算机原理" class="headerlink" title="计算机原理"></a>计算机原理</h4><ul><li>《算法导论》：<a href="https://www.bilibili.com/video/av48922404">https://www.bilibili.com/video/av48922404</a></li><li>《现代操作系统》：<a href="https://www.bilibili.com/video/av9555596">https://www.bilibili.com/video/av9555596</a></li><li>《深入理解计算机系统》：<a href="https://www.bilibili.com/video/av31289365">https://www.bilibili.com/video/av31289365</a></li><li>《编译原理》： <a href="https://www.bilibili.com/video/av17649289">https://www.bilibili.com/video/av17649289</a></li><li>《计算机网络：自顶向下方法》：<a href="https://www.bilibili.com/video/BV1JV411t7ow">https://www.bilibili.com/video/BV1JV411t7ow</a></li><li>《计算机程序的构造和解释》：<a href="https://www.bilibili.com/video/av8515129">https://www.bilibili.com/video/av8515129</a></li><li>《数据库系统概论》：<a href="https://www.bilibili.com/video/BV1G54y1d7ZK">https://www.bilibili.com/video/BV1G54y1d7ZK</a></li></ul><h4 id="数据库-中间件-分布式"><a href="#数据库-中间件-分布式" class="headerlink" title="数据库 &#x2F; 中间件 &#x2F; 分布式"></a>数据库 &#x2F; 中间件 &#x2F; 分布式</h4><ul><li>数据库<ul><li>MySQL</li><li>PostgreSQL</li></ul></li><li>缓存<ul><li>Redis</li></ul></li><li>队列<ul><li>Apache Kafka</li><li>Apache Pulsar</li></ul></li><li>搜索引擎<ul><li>Elastic Stack<ul><li>Elasticsearch</li><li>logstash</li><li>kibana</li><li>beats</li></ul></li></ul></li><li>容器<ul><li>Docker</li><li>K8S</li></ul></li></ul><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><ul><li>广告系统</li><li>电商系统</li><li>搜索系统</li><li>支付转账</li><li>游戏后台</li><li>即时通讯</li><li>社交系统</li><li>CMS 系统</li><li>ERP 系统</li><li>OA 系统</li><li>代码生成</li><li>权限管理</li><li>秒杀活动</li></ul><h4 id="架构设计-1"><a href="#架构设计-1" class="headerlink" title="架构设计"></a>架构设计</h4><p>同阶段 6 架构设计部分</p><h4 id="大数据"><a href="#大数据" class="headerlink" title="大数据"></a>大数据</h4><ul><li>5V 特点</li><li>Hadoop</li><li>HDFS</li><li>MapReduce</li><li>Spark</li><li>Flink</li><li>Storm</li><li>Hive</li><li>HBase</li><li>Druid</li><li>Kylin</li><li>Pig</li><li>Mahout</li></ul><h4 id="前沿技术"><a href="#前沿技术" class="headerlink" title="前沿技术"></a>前沿技术</h4><ul><li>云原生：<a href="https://www.jianshu.com/p/a37baa7c3eff">https://www.jianshu.com/p/a37baa7c3eff</a><ul><li>Quasar Framework：<a href="http://www.quasarchs.com/">http://www.quasarchs.com/</a></li></ul></li><li>服务网格：<a href="https://www.redhat.com/zh/topics/microservices/what-is-a-service-mesh">https://www.redhat.com/zh/topics/microservices/what-is-a-service-mesh</a><ul><li>istio：<a href="https://github.com/istio/istio">https://github.com/istio/istio</a></li></ul></li><li>GraalVM：<a href="https://juejin.cn/post/6850418120570437646">https://juejin.cn/post/6850418120570437646</a> （高性能的通用虚拟机）<ul><li>官网：<a href="https://www.graalvm.org/">https://www.graalvm.org/</a></li></ul></li><li>ZGC：<a href="https://juejin.cn/post/6859276583656980493">https://juejin.cn/post/6859276583656980493</a> （新一代垃圾回收器）<ul><li>官网：<a href="http://openjdk.java.net/projects/zgc/">http://openjdk.java.net/projects/zgc/</a></li></ul></li></ul><h3 id="自学-Java-专题资源"><a href="#自学-Java-专题资源" class="headerlink" title="自学 Java 专题资源"></a>自学 Java 专题资源</h3><ul><li>⭐ GitHub Java 专区：<a href="https://github.com/topics/java">https://github.com/topics/java</a></li><li>⭐ GitHub Java 合集：<a href="https://github.com/akullpp/awesome-java">https://github.com/akullpp/awesome-java</a></li><li>StackOverflow：<a href="https://stackoverflow.com/questions/tagged/java">https://stackoverflow.com/questions/tagged/java</a> （解决问题必备）</li><li>⭐ 掘金 Java 专区：<a href="https://juejin.cn/tag/Java">https://juejin.cn/tag/Java</a></li><li>美团技术团队：<a href="https://tech.meituan.com/">https://tech.meituan.com/</a></li><li>阿里技术团队：<a href="https://102.alibaba.com/tech/list/">https://102.alibaba.com/tech/list/</a></li><li>有赞技术团队：<a href="https://tech.youzan.com/tag/back-end/">https://tech.youzan.com/tag/back-end/</a></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>团队协作能力</p></li><li><p>高效沟通能力</p></li><li><p>需求分析能力</p></li><li><p>架构设计能力</p></li><li><p>抽象复用能力</p></li><li><p>独立创造能力</p></li><li><p>问题解决能力</p></li><li><p>归纳总结能力</p></li><li><p>自主学习能力</p></li><li><p>工具利用能力</p></li><li><p>高效编码能力</p></li><li><p>信息检索能力</p></li><li><p>开源建设能力</p></li><li><p>源码阅读能力</p></li><li><p>自测审查能力</p></li><li><p>文档编写能力</p></li><li><p>知识表达能力</p></li><li><p>绘图描述能力</p></li><li><p>兴趣驱动（长期学习能力）</p></li><li><p>保持好奇心</p></li><li><p>其他（狗头）</p><ul><li><p>复制粘贴能力</p></li><li><p>打架能力</p></li><li><p>PPT 制作能力</p></li></ul></li></ol><h3 id="编程误区"><a href="#编程误区" class="headerlink" title="编程误区"></a>编程误区</h3><ol><li>我不适合学编程？❌（兴趣比天赋更重要，不要怀疑自己）</li><li>忽视基础，急于求成 ❌（勿以浮沙筑高台）</li><li>数学、英语差，就学不好编程 ❌（开发工作基本不用高等数学，英语可以使用翻译软件、工作中提升）</li><li>编程时，刻意记忆代码 ❌（知道每行代码的意义，能看文档写出来就行）</li><li>每行代码都要追求完美 ❌（何为完美？每个人的定义不同）</li><li>用百度搜索、复制粘贴的都是菜鸡程序员 ❌（艹）</li><li>我做的项目太小了，就不整理了 ❌（自己的每个项目都要整理，可以记录自己的成长）</li><li>跟别人交流很麻烦，我更愿意自己写代码 ❌（1 + 1 &gt; 2，切勿闭门造车，不过也要找到合适的同学去交流）</li><li>做别人做过的项目没有意义 ❌（连别人做过的项目都做不出，还想造新轮子？）</li><li>遇到 Bug，想办法绕过而不是寻找解决方案 ❌（Bug 最能使人成长）</li><li>遇到问题，第一时间去问别人而非自己解决 ❌（建议阅读《提问的智慧》）</li><li>花了很多时间、看了很多书，就能学好编程 ❌（编程最忌讳纸上谈兵）</li><li>只向前学习，不整理学过的知识 ❌（建议每学完一个知识，都去回想、总结、写心得，这个时间的花费是值得的）</li><li>单元测试不重要 ❌（一棍打死）</li><li>写文档不重要 ❌（一棍打死）</li><li>和我无关的工作，就不需要了解 ❌（了解团队、同行是很重要的，全局视野）</li><li>收藏的资源从来不看，就不收藏了 ❌（万一以后用到了呢？）</li><li>不去考虑需求的价值，而是一味寻求解决方案 ❌（不要成为需求机器，要有自己的独立思考）</li></ol><h3 id="高效法则"><a href="#高效法则" class="headerlink" title="高效法则"></a>高效法则</h3><ol><li>不要过分追求完美，完成比完美更重要。</li><li>有现成的代码，就不要自己写，避免重复劳动（学习除外）。</li><li>学会使用软件及快捷键来提升自己的编码效率。</li><li>做项目前，要先想清楚怎么做，做好充分的预研和设计。这样不仅便于后续的程序扩展，也能避免无意义的返工。</li><li>将大的目标进行拆解，做好计划，分清主次。不要因为觉得目标遥不可及而拖延，也不要盲目乐观而松懈。</li><li>养成好的作息习惯，找到自己适合工作的黄金时间。</li><li>记性不好，就多记录、多总结、定期复习。哪怕实在记不住，也可以借助文档、收藏夹软件来当自己的第二大脑。</li><li>积累属于自己的工具库，配合搜索软件实现随用随取。</li><li>多和其他同行交流或订阅技术推送，拓宽知识渠道，从而了解更多的工具和方法。有些东西你只要听说过，要用时也许就能节省大量查找时间。</li><li>找到让自己进入专注做事的状态的方法，比如戴上耳机、嚼口香糖等。</li><li>做事高效，离不开经验的积累，因此要多写代码、多做项目。</li></ol><h3 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h3><ol><li><p>按照这份路线学习，每天要学多少个小时，总共要学多久？</p><p>回答：每个人学习能力不同，当然没有固定标准。我当时平均每天可以学习 6 个小时，寒暑假能保证 8 小时以上的学习，路线中的时长是我当时学习时长 * 1.5 倍的参考值。实话说，我的学习效率真的不高，对编程感兴趣，多学就好了。与其纠结这个时长，倒不妨多抽点时间，每天多学一点。</p></li><li><p>这份路线上推荐的资源，鱼皮都看完了么？</p><p>回答：我是特么神仙啊？怎么可能都看完了！这份路线的本质是以网上现有的、死板的 Java 大学习框架为基础，结合我自己实际学习的知识、经验和感悟给它注入灵魂，从而希望给大家带来更大的帮助和价值。路线中的资源都是利用我自己的资源检索能力和渠道，通过试看、反复对比和参考评价精选出来的，不是主观臆断、空穴来风。</p></li><li><p>我学到这份路线的哪个位置，可以找工作？</p><p>回答：这和你自己的领悟能力和教育背景有关。夸张点说，有些清华大学的同学，可能之前都没学过 Java，照样能找到大厂 Java 开发工作。学到同一阶段的同学，学习过程中的认真程度、积累总结数、做项目个数、基础学习的好坏不同，找到的工作肯定也不同。但一般情况下，想要进大厂，建议至少学完阶段 4 的微服务部分，然后去做一个完整的项目，用上之前学习的技术；不想进大厂的同学，学完第 3 阶段后，学下缓存和队列，一般也能找到 Java 开发的工作。</p></li><li><p>学完这份路线就能进大厂么？</p><p>回答：你先学完，再问这个问题。没有绝对进大厂的保证，但是相信我，把这份路线上的大部分知识学了，再做做项目，哪怕进不了大厂，你拿到的薪水也绝对比松散学习的同学高！</p></li><li><p>我可以相信你么？</p><p>回答：不要相信我，应该相信你自己。想进大厂，就不要怀疑自己的能力！</p></li></ol>]]></content>
    
  </entry>
  
  <entry>
    <title>《提问的智慧》</title>
    <link href="https://alan.org.cn/posts/%E6%8F%90%E9%97%AE%E7%9A%84%E6%99%BA%E6%85%A7.html"/>
    <id>https://alan.org.cn/posts/%E6%8F%90%E9%97%AE%E7%9A%84%E6%99%BA%E6%85%A7.html</id>
    <published>2023-01-30T04:03:00.000Z</published>
    <updated>2026-06-04T04:10:49.244Z</updated>
    
    
    
    <category term="文章分享" scheme="https://alan.org.cn/tags/%E6%96%87%E7%AB%A0%E5%88%86%E4%BA%AB/"/>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;提问的智慧&quot;&gt;&lt;a href=&quot;#提问的智慧&quot; class=&quot;headerlink&quot; title=&quot;提问的智慧&quot;&gt;&lt;/a&gt;提问的智慧&lt;/h1&gt;&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gut</summary>
      
    
    
    <content type="html"><![CDATA[<h1 id="提问的智慧"><a href="#提问的智慧" class="headerlink" title="提问的智慧"></a>提问的智慧</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"># 提问的艺术<br><br>1. 确保你知道问题。在提问之前，确保你已经理解了问题的背景和相关信息。<br>2. 简洁表达你的问题。不要过多地使用复杂的词汇或长句子，以免让人听不懂。<br>3. 具体明确。尽可能具体和明确地描述你的问题。使用具体的实例或细节可以帮助其他人更好地理解你的问题。<br>4. 不要假设。不要假设其他人已经知道你所知道的信息。提问时要尽可能清晰地表达问题，以免造成误解。<br>5. 尊重别人的时间。在提问之前，确保你已经做了必要的研究。不要问一些显而易见的问题，这会浪费其他人的时间。<br>6. 尝试多种途径。如果你在某个平台上无法得到答案，可以尝试在其他平台上提问。不同的平台有不同的人群和文化，这可能会导致你得到不同的答案。<br>7. 感谢他人的帮助。无论是否得到了满意的答案，都要感谢那些帮助过你的人。这可以展现你的礼貌和感激之情，也可以促使他们更乐于再次帮助你。<br></code></pre></td></tr></table></figure><h4 id="艾瑞克-史蒂文-雷蒙德（Eric-Steven-Raymond）"><a href="#艾瑞克-史蒂文-雷蒙德（Eric-Steven-Raymond）" class="headerlink" title="艾瑞克.史蒂文.雷蒙德（Eric Steven Raymond）"></a>艾瑞克.史蒂文.雷蒙德（Eric Steven Raymond）</h4><p>Thyrsus Enterprises</p><p><a href="mailto:&#x65;&#115;&#x72;&#x40;&#116;&#104;&#121;&#114;&#x73;&#117;&#x73;&#x2e;&#99;&#x6f;&#109;">&#x65;&#115;&#x72;&#x40;&#116;&#104;&#121;&#114;&#x73;&#117;&#x73;&#x2e;&#99;&#x6f;&#109;</a></p><h4 id="瑞克-莫恩（Rick-Moen）"><a href="#瑞克-莫恩（Rick-Moen）" class="headerlink" title="瑞克.莫恩（Rick Moen）"></a>瑞克.莫恩（Rick Moen）</h4><p><a href="mailto:&#x72;&#101;&#115;&#112;&#111;&#x6e;&#x64;&#x2d;&#97;&#x75;&#116;&#111;&#x40;&#108;&#105;&#x6e;&#117;&#x78;&#x6d;&#x61;&#x66;&#x69;&#97;&#46;&#x63;&#x6f;&#x6d;">&#x72;&#101;&#115;&#112;&#111;&#x6e;&#x64;&#x2d;&#97;&#x75;&#116;&#111;&#x40;&#108;&#105;&#x6e;&#117;&#x78;&#x6d;&#x61;&#x66;&#x69;&#97;&#46;&#x63;&#x6f;&#x6d;</a><br>版权©2001, 2006 Eric S. Raymond, Rick Moen</p><h3 id="修订历史"><a href="#修订历史" class="headerlink" title="修订历史"></a>修订历史</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">修订版 3.9  2013年4月23日esr<br>修正链接<br>修订版 3.8  2012年6月19日esr<br>修正链接<br>修订版 3.7  2010年12月6日esr<br>对于英语为第二语言人士的有益建议<br>修订版 3.7  2010年11月2日esr<br>几种翻译不见了<br>修订版 3.6  2008年3月19日esr<br>小更新及新链接<br>修订版 3.5  2008年1月2日esr<br>勘误及一些翻译链接<br>修订版 3.4  2007年3月24日esr<br>新章节：“关于代码的问题”<br>修订版 3.3  2006年9月29日esr<br>增加凯.尼格曼（Kai Niggemann）的一个好建议<br>修订版 3.2  2006年1月10日esr<br>加入瑞克.莫恩（Rick Moen）编写的内容<br>修订版 3.1  2004年10月28日esr<br>文档“谷歌是你的朋友！”<br>修订版 3.0  2004年2月2日esr<br>主要新增在网页论坛应有的礼节<br></code></pre></td></tr></table></figure><h3 id="原文：How-To-Ask-Questions-The-Smart-Way"><a href="#原文：How-To-Ask-Questions-The-Smart-Way" class="headerlink" title="原文：How To Ask Questions The Smart Way"></a><a href="http://www.catb.org/~esr/faqs/smart-questions.html">原文：How To Ask Questions The Smart Way</a></h3><p>翻译：王刚 &lt;yafrank at 126 dot com &gt;<br>时间：2013年10月26日<br>内容</p><h3 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h3><ul><li><a href="#%E5%BC%83%E6%9D%83%E7%94%B3%E6%98%8E">弃权申明</a></li><li><a href="#%E5%BC%95%E8%A8%80">引言</a></li><li><a href="#%E6%8F%90%E9%97%AE%E5%89%8D">提问前</a></li><li><a href="#%E6%8F%90%E9%97%AE%E6%97%B6">提问时</a><ul><li><a href="#%E4%BB%94%E7%BB%86%E6%8C%91%E9%80%89%E8%AE%BA%E5%9D%9B">仔细挑选论坛</a></li><li><a href="#%E9%9D%A2%E5%90%91%E6%96%B0%E6%89%8B%E7%9A%84%E8%AE%BA%E5%9D%9B%E5%92%8C%E4%BA%92%E8%81%94%E7%BD%91%E4%B8%AD%E7%BB%A7%E8%81%8A%E5%A4%A9irc%E9%80%9A%E5%B8%B8%E5%93%8D%E5%BA%94%E6%9C%80%E5%BF%AB">面向新手的论坛和互联网中继聊天（IRC）通常响应最快</a></li><li><a href="#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E4%BD%BF%E7%94%A8%E9%A1%B9%E7%9B%AE%E7%9A%84%E9%82%AE%E4%BB%B6%E5%88%97%E8%A1%A8">第二步，使用项目的邮件列表</a></li><li><a href="#%E4%BD%BF%E7%94%A8%E6%9C%89%E6%84%8F%E4%B9%89%E4%B8%94%E6%98%8E%E7%A1%AE%E7%9A%84%E4%B8%BB%E9%A2%98">使用有意义且明确的主题</a></li><li><a href="#%E4%BD%BF%E9%97%AE%E9%A2%98%E5%AE%B9%E6%98%93%E5%9B%9E%E5%A4%8D">使问题容易回复</a></li><li><a href="#%E7%94%A8%E6%B8%85%E6%99%B0%E8%AF%AD%E6%B3%95%E6%8B%BC%E5%86%99%E6%AD%A3%E7%A1%AE%E7%9A%84%E8%AF%AD%E5%8F%A5%E4%B9%A6%E5%86%99">用清晰、语法、拼写正确的语句书写</a></li><li><a href="#%E4%BD%BF%E7%94%A8%E6%98%93%E4%BA%8E%E8%AF%BB%E5%8F%96%E4%B8%94%E6%A0%87%E5%87%86%E7%9A%84%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E5%8F%91%E9%80%81%E9%97%AE%E9%A2%98">使用易于读取且标准的文件格式发送问题</a></li><li><a href="#%E6%8F%8F%E8%BF%B0%E9%97%AE%E9%A2%98%E5%BA%94%E5%87%86%E7%A1%AE%E4%B8%94%E6%9C%89%E5%86%85%E5%AE%B9">描述问题应准确且有内容</a></li><li><a href="#%E9%87%8F%E4%B8%8D%E5%9C%A8%E5%A4%9A%E7%B2%BE%E7%82%BC%E5%88%99%E7%81%B5">量不在多，精炼则灵</a></li><li><a href="#%E5%88%AB%E6%80%A5%E4%BA%8E%E5%AE%A3%E7%A7%B0%E6%89%BE%E5%88%B0Bug">别急于宣称找到Bug</a></li><li><a href="#%E4%BD%8E%E5%A3%B0%E4%B8%8B%E6%B0%94%E4%BB%A3%E6%9B%BF%E4%B8%8D%E4%BA%86%E5%81%9A%E8%87%AA%E5%B7%B1%E7%9A%84%E5%AE%B6%E5%BA%AD%E4%BD%9C%E4%B8%9A">低声下气代替不了做自己的家庭作业</a></li><li><a href="#%E6%8F%8F%E8%BF%B0%E9%97%AE%E9%A2%98%E7%97%87%E7%8A%B6%E8%80%8C%E4%B8%8D%E6%98%AF%E7%8C%9C%E6%B5%8B">描述问题症状而不是猜测</a></li><li><a href="#%E6%8C%89%E6%97%B6%E9%97%B4%E5%85%88%E5%90%8E%E7%BD%97%E5%88%97%E9%97%AE%E9%A2%98%E7%97%87%E7%8A%B6">按时间先后罗列问题症状</a></li><li><a href="#%E6%8F%8F%E8%BF%B0%E7%9B%AE%E6%A0%87%E8%80%8C%E4%B8%8D%E6%98%AF%E8%BF%87%E7%A8%8B">描述目标而不是过程</a></li><li><a href="#%E5%88%AB%E8%A6%81%E6%B1%82%E7%A7%81%E4%B8%8B%E5%9B%9E%E5%A4%8D%E7%94%B5%E9%82%AE">别要求私下回复电邮</a></li><li><a href="#%E6%8F%90%E9%97%AE%E5%BA%94%E6%98%8E%E7%A1%AE">提问应明确</a></li><li><a href="#%E5%85%B3%E4%BA%8E%E4%BB%A3%E7%A0%81%E7%9A%84%E9%97%AE%E9%A2%98">关于代码的问题</a></li><li><a href="#%E5%88%AB%E5%BC%A0%E8%B4%B4%E5%AE%B6%E5%BA%AD%E4%BD%9C%E4%B8%9A%E5%BC%8F%E9%97%AE%E9%A2%98">别张贴家庭作业式问题</a></li><li><a href="#%E5%88%A0%E9%99%A4%E6%97%A0%E6%84%8F%E4%B9%89%E7%9A%84%E8%A6%81%E6%B1%82">删除无意义的要求</a></li><li><a href="#%E4%B8%8D%E8%A6%81%E6%8A%8A%E9%97%AE%E9%A2%98%E6%A0%87%E8%AE%B0%E4%B8%BA%E7%B4%A7%E6%80%A5%E5%8D%B3%E4%BD%BF%E5%AF%B9%E4%BD%A0%E8%80%8C%E8%A8%80%E7%9A%84%E7%A1%AE%E5%A6%82%E6%AD%A4">不要把问题标记为“紧急”，即使对你而言的确如此</a></li><li><a href="#%E7%A4%BC%E8%B2%8C%E6%80%BB%E6%98%AF%E6%9C%89%E7%9B%8A%E7%9A%84">礼貌总是有益的</a></li><li><a href="#%E7%A4%BC%E8%B2%8C%E6%80%BB%E6%98%AF%E6%9C%89%E7%9B%8A%E7%9A%84">问题解决后追加一条简要说明</a></li></ul></li><li><a href="#%E5%A6%82%E4%BD%95%E8%A7%A3%E8%AF%BB%E5%9B%9E%E7%AD%94">如何解读回答</a><ul><li><a href="#%E8%AF%BB%E8%AF%BB%E8%AF%A5%E6%AD%BB%E7%9A%84%E6%89%8B%E5%86%8Crtfm%E5%92%8C%E6%90%9C%E6%90%9C%E8%AF%A5%E6%AD%BB%E7%9A%84%E7%BD%91%E7%BB%9Cstfw%E5%A6%82%E4%BD%95%E6%98%8E%E7%99%BD%E4%BD%A0%E5%B7%B2%E5%AE%8C%E5%85%A8%E6%90%9E%E7%A0%B8">“读读该死的手册”（RTFM）和“搜搜该死的网络”（STFW）：如何明白你已完全搞砸</a></li><li><a href="#%E5%A6%82%E6%9E%9C%E8%BF%98%E4%B8%8D%E6%98%8E%E7%99%BD">如果还不明白……</a></li><li><a href="#%E5%AF%B9%E5%BE%85%E6%97%A0%E7%A4%BC">对待无礼</a></li></ul></li><li><a href="#%E5%88%AB%E8%B1%A1%E5%A4%B1%E8%B4%A5%E8%80%85%E9%82%A3%E6%A0%B7%E5%8F%8D%E5%BA%94">别象失败者那样反应</a></li><li><a href="#%E5%88%AB%E8%B1%A1%E5%A4%B1%E8%B4%A5%E8%80%85%E9%82%A3%E6%A0%B7%E5%8F%8D%E5%BA%94">提问禁忌</a></li><li><a href="#%E5%A5%BD%E9%97%AE%E9%A2%98%E4%B8%8E%E5%9D%8F%E9%97%AE%E9%A2%98">好问题与坏问题</a></li><li><a href="#%E5%A6%82%E6%9E%9C%E5%BE%97%E4%B8%8D%E5%88%B0%E5%9B%9E%E7%AD%94">如果得不到回答</a></li><li><a href="#%E5%A6%82%E4%BD%95%E6%9B%B4%E5%A5%BD%E5%9C%B0%E5%9B%9E%E7%AD%94">如何更好地回答</a></li><li><a href="#%E7%9B%B8%E5%85%B3%E8%B5%84%E6%BA%90">相关资源</a></li><li><a href="#%E9%B8%A3%E8%B0%A2">鸣谢</a></li></ul><blockquote><p>译文： 印尼语 白俄罗斯语 巴西葡萄牙语 简体中文 荷兰语 法语 乔治亚语 德语 希腊语 希伯来语 日语 波兰语 葡萄牙语 罗马尼亚语 俄语 西班牙语 泰语 如果你想复制、镜像、翻译或引用本文，请参阅我的 复制协议。</p></blockquote><h3 id="弃权申明"><a href="#弃权申明" class="headerlink" title="弃权申明"></a>弃权申明</h3><p>许多项目的网站在如何取得帮助的部分链接了本文，这没有关系，也正是我们想要的。但如果你是该项目生成此链接的网管，请在链接附近显著位置注明：我们不提供该项目的服务支持！</p><p>我们已经领教了没有此说明带来的痛苦，我们将不停地被一些白痴纠缠，他们认为既然我们发布了本文，那么我们就有责任解决世上所有的技术问题。</p><p>如果你是因为需要帮助正在阅读本文，然后就带着可以直接从作者那取得帮助的印象离开，那么 你 就不幸成了我们所说的白痴之一。 别向 我们 提问，我们不会理睬的。 我们只是在这教你如何从那些真正懂得你软硬件问题的人那里取得帮助，但 99.9％ 的时间我们不会是那些人。除非你非常地 确定 本文的作者是你遇到问题方面的专家，请不要打搅，这样大家都更开心一点。</p><h3 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h3><p>在 黑客 的世界里，你所提技术问题的解答很大程度上取决于你提问的方式与解决此问题的难度，本文将教你如何提问才更有可能得到满意的答复。</p><p>开源程序的应用已经很广，你通常可以从其他更有经验的用户而不是黑客那里得到解答。这是好事，他们一般对新手常有的毛病更容忍一点。然尔，使用我们推荐的方法，象对待黑客那样对待这些有经验的用户，通常能最有效地得到问题的解答。</p><p>第一件需要明白的事是黑客喜欢难题和激发思考的好问题。假如不是这样，我们也不会写本文了。如果你能提出一个有趣的问题让我们咀嚼玩味，我们会感激你。好问题是种激励与礼物，帮助我们发展认知，揭示没有注意或想到的问题。在黑客中，“好问题！” 是非常热烈而真挚的赞许。</p><p>此外，黑客还有遇到简单问题就表现出敌视或傲慢的名声。有时，我们看起来还对新手和愚蠢的家伙有条件反射式的无礼，但事情并不真是这样。</p><p>我们只是毫无歉意地敌视那些提问前不愿思考、不做自己家庭作业的人。这种人就象时间无底洞──他们只知道索取，不愿意付出，他们浪费了时间，这些时间本可用于其它更有趣的问题或更值得回答的人。我们将这种人叫做 “失败者（loser）” （由于历史原因，我们有时将“loser”拼写为“lusers” 。）</p><p>我们意识到许多人只是想使用我们写的软件，他们对学习技术细节没有兴趣。对大多数人而言，计算机只是种工具，是种达到目的的手段而已。他们有自己的生活并且有更要紧的事要做，我们承认这点，也从不指望每个人都对这些让我们着迷的技术问题感兴趣。不过，我们回答问题的风格是为了适应那些真正对此有兴趣并愿意主动参与解决问题的人，这一点不会变，也不该变。如果连这都变了，我们就会在自己能做得最好的事情上不再那么犀利。</p><p>我们（大多数）是自愿者， 从自己繁忙的生活中抽时间来回答问题，有时会力不从心。因此，我们会毫不留情地滤除问题，特别是那些看起来象是失败者提的，以便更有效地把回答问题的时间留给那些胜利者。</p><p>如果你认为这种态度令人反感、以施惠者自居或傲慢自大，请检查你的假设，我们并未要求你屈服──事实上，假如你做了该做的努力，我们中的大多数将非常乐意平等地与你交流，并欢迎你接纳我们的文化。试图去帮助那些不愿自救的人对我们简直没有效率。不懂没有关系，但愚蠢地做事不行。</p><p>所以，你不必在技术上很在行才能吸引我们的注意，但你 必须 表现出能引导你在行的姿态──机 敏、有想法、善于观察、乐于主动参与问题的解决。如果你做不到这些使你与众不同的事情，我们建议你付钱跟别人签商业服务合同，而不是要求黑客无偿帮助。</p><p>如果你决定向我们求助，你不会想成为一名失败者，你也不想被看成一个失败者。得到快速有效回答的最好方法是使提问者看起来象个聪明、自信和有想法的人，并且暗示只是碰巧在某一特别问题上需要帮助。</p><p>（欢迎对本文指正，可以将建议发至 <a href="mailto:&#101;&#115;&#114;&#64;&#116;&#x68;&#121;&#114;&#115;&#x75;&#x73;&#x2e;&#99;&#x6f;&#109;">&#101;&#115;&#114;&#64;&#116;&#x68;&#121;&#114;&#115;&#x75;&#x73;&#x2e;&#99;&#x6f;&#109;</a> 或 <a href="mailto:&#x72;&#101;&#x73;&#x70;&#x6f;&#x6e;&#100;&#45;&#97;&#117;&#x74;&#111;&#x40;&#x6c;&#x69;&#x6e;&#117;&#x78;&#x6d;&#97;&#102;&#105;&#97;&#46;&#x63;&#x6f;&#109;">&#x72;&#101;&#x73;&#x70;&#x6f;&#x6e;&#100;&#45;&#97;&#117;&#x74;&#111;&#x40;&#x6c;&#x69;&#x6e;&#117;&#x78;&#x6d;&#97;&#102;&#105;&#97;&#46;&#x63;&#x6f;&#109;</a>。 请注意，本文不想成为一般性的 网络礼仪 指南，我一般会拒绝那些与引出技术论坛中有用的回答不特别相关的建议。）</p><h3 id="提问前"><a href="#提问前" class="headerlink" title="提问前"></a>提问前</h3><p>在通过电邮、新闻组或论坛提技术问题以前，做以下事情：</p><ul><li>尝试在你准备提问论坛的历史文档中搜索答案</li><li>尝试搜索互联网以找到答案</li><li>尝试阅读手册以找到答案</li><li>尝试阅读“常见问题文档”（FAQ）以找到答案</li><li>尝试自己检查或试验以找到答案</li><li>尝试请教懂行的朋友以找到答案</li><li>如果你是程序员，尝试阅读源代码以找到答案</li></ul><p>提问时，请先表明你已做了上述事情，这将有助于建立你不是寄生虫与浪费别人时间的印象。最好再表述你从中 学到的东西 ，我们喜欢回答那些表现出能从答案中学习的人。</p><p>运用某些策略，比如用谷歌（Google）搜索你遇到的各种错误提示（既搜索 谷歌论坛，也搜索网页）， 这样很可能直接就找到了解决问题的文档或邮件列表线索。 即使没有结果，在邮件列表或新闻组寻求帮助时提一句“我在谷歌中搜过下列句子但没有找到什么有用的东西” 也是件好事，至少它表明了搜索引擎不能提供哪些帮助。将搜索关键词与你的问题及可能的解决方案联系起来，还有助于引导其他有类似问题的人。</p><p>别着急，不要指望几秒钟的谷歌搜索就能解决一个复杂的问题。读一下常见问题文档。在向专家提问之前，先向后靠靠放松一下，再思考一下问题。相信我们，他们能从你的提问看出你做了多少阅读与思考，如果你是有备而来，将更有可能得到解答。不要将所有问题一股脑抛出，只因你的第一次搜索没有结果（或者结果太多）。</p><p>认真地思考，准备好你的问题。轻率的提问只能得到轻率的回答，或者压根没有。在提问时，你越是表现出在此前做过思考与努力去解决自己的问题，你越有可能得到真正的帮助。</p><p>注意别提错问题。如果提问基于错误的假设，某黑客多半会一边想 “愚蠢的问题……”，一边按将错就错的答案回复你，并且希望这种只是得到你自己“问的问题”而非真正所需的解答，给你一个教训。</p><p>永远不要假设你 有资格 得到解答。你没有这种资格，毕竟你没有为此服务付费。如果你能够提出有内容、有趣和激励思考的问题──那种毫无疑问能够向社区贡献经验，而不仅仅是消极地要求从别人那获取知识的问题，你将“挣到”答案。</p><p>另一方面，表明你有能力也乐意参与问题的解决是个很好的开端。“有没有人能指个方向？”，我这还差点什么？”，“我应该查哪个网站？”，通常要比 “请给出我可以用的完整步骤”更容易得到回复，因为你表明了只要有人能指个方向，你就很乐意完成剩下的过程。</p><h3 id="提问时"><a href="#提问时" class="headerlink" title="提问时"></a>提问时</h3><h4 id="仔细挑选论坛"><a href="#仔细挑选论坛" class="headerlink" title="仔细挑选论坛"></a>仔细挑选论坛</h4><p>要对在哪提问留心，如果你做了下述事情，多半会被一笔勾销或被看成“失败者”：</p><ul><li>张贴与论坛主题无关的问题</li><li>在面向高级技术问题的论坛上张贴肤浅的问题，或者反之。</li><li>在太多不同的新闻组同时张贴</li><li>给既非熟人也没有义务解决你问题的人发送你私人的电邮</li></ul><p>为保护通信的渠道不被无关的东西淹没，黑客会除掉那些没有找对地方的问题，你不会想让这种事落到自己头上的。</p><p>因此，第一步是找对论坛。谷歌和其它搜索引擎还是你的朋友，可以用它们搜索你遇到困难的软硬件问题最相关的项目网站。那里通常都有项目的常见问题（FAQ）、邮件列表及文档的链接。如果你的努力（包括 阅读 FAQ）都没有结果，这些邮件列表就是最后能取得帮助的地方。项目的网站也许还有报告Bug的流程或链接，如果是这样，去看看。</p><p>向陌生的人或论坛发送邮件极有可能是在冒险。譬如，不要假设一个内容丰富的网页的作者想充当你的免费顾问，不要对你的问题是否会受到欢迎做太乐观的估计──如果你不确定，向别处发或者压根别发。</p><p>在选择论坛、新闻组或邮件列表时，别太相信名字，先看看 FAQ 或者许可书以明确你的问题是否切题。发贴前先翻翻已有的帖子，这样可以让你感受一下那里行事的方式。事实上，张贴前在新闻组或邮件列表的历史文档中搜索与你问题相关的关键词是个极好的主意，也许就找到答案了。即使没有，也能帮助你归纳出更好的问题。</p><p>别象机关枪似的一次性“扫射”所有的帮助渠道，这就象大喊大叫一样会令人不快，温柔地一个一个来。</p><p>弄懂主题！最典型的错误之一是在某种致立于跨平台可移植的语言、库或工具的论坛中提关于 Unix 或 Windows 操作系统程序接口的问题。如果你不明白为什么这是大错，最好在搞清楚概念前什么也别问。</p><p>一般来说，在仔细挑选的公共论坛中提问比在私有论坛中提同样的问题更容易得到有用的回答。有几个道理支持这点，一是看潜在的回复者有多少，二是看论坛的参与者有多少，黑客更愿回答能启发多数人的问题。</p><p>可以理解，老练的黑客和一些流行软件的作者正在承受过多的不当消息。就象那根最后压垮骆驼背的稻草一样，你的加入也有可能使情况走向极端──已经好几次了，一些流行软件的作者退出了对自己软件的支持，因为伴随而来的涌入其私人邮箱的垃圾邮件变得无法忍受。</p><h4 id="面向新手的论坛和互联网中继聊天（IRC）通常响应最快"><a href="#面向新手的论坛和互联网中继聊天（IRC）通常响应最快" class="headerlink" title="面向新手的论坛和互联网中继聊天（IRC）通常响应最快"></a>面向新手的论坛和互联网中继聊天（IRC）通常响应最快</h4><p>本地的用户组织或者你所用的 Linux 发行版也许正在宣传新手取得帮助的论坛或 IRC 通道（在一些非英语国家，新手论坛很可能还是邮件列表），这些地方是开始提问的好去处，特别是当你觉得遇到的也许只是相对简单或者很普通的问题时。经过宣传的 IRC 通道是公开邀请提问的地方，通常可以得到实时的回复。</p><p>事实上，如果出问题的程序来自某发行版（这很常见），最好先去该发行版的论坛或邮件列表中提问，再到程序本身的项目论坛或邮件列表，（否则）该项目的黑客可能仅仅回复“用 我们的 代码”。</p><p>在任何论坛发贴以前，先看看有没有搜索功能。如果有，就试着用问题的几个关键词搜索一下，也许就有帮助。如果在此之前你已做过全面的网页搜索（你应该这样去做），还是再搜索一下论坛，搜索引擎有可能没来得及索引此论坛的全部内容。</p><p>通过论坛或 IRC 通道提供项目的用户支持有增长的趋势，电子邮件交流则更多地为项目开发者保留。所以先在论坛或 IRC 中寻求与该项目相关的帮助。</p><h4 id="第二步，使用项目的邮件列表"><a href="#第二步，使用项目的邮件列表" class="headerlink" title="第二步，使用项目的邮件列表"></a>第二步，使用项目的邮件列表</h4><p>当某个项目存在开发者邮件列表时，要向列表而不是其中的个别成员提问，即使你确信他能最好地回答你的问题。查一查项目的文档和主页，找到项目的邮件列表并使用它。采用这种办法有几个很好的理由：</p><ul><li>向个别开发者提的问题（如果）足够好，也将对整个项目组有益。相反，如果你认为自己的问题对整个项目组来说太愚蠢，这也不能成为骚扰个别开发者的理由。</li><li>向列表提问可以分散开发者的负担，个别开发者（尤其是项目领导）也许太忙以至于没法回答你的问题。</li><li>大多数邮件列表都要存档，那些存档将被搜索引擎索引，如果你向列表提问并得到解答，将来其它人可以通过网页搜索找到你的问题和答案，也就不用再次发问了。</li><li>如果某些问题经常被问到，开发者可以利用此信息改进文档或软件本身，以使其更清楚。如果只是私下提问，就没有人能看到最常见问题的完整场景。</li></ul><p>如果一个项目既有 “用户” 也有“开发者”（或 “黑客”）邮件列表或论坛，而你又不摆弄那些代码，向“用户”列表或论坛提问。不要假设自己会在开发者列表中受到欢迎，那些人多半会遭受你的噪音干扰。</p><p>然尔，如果你 确信 你的问题不一般，而且在“用户” 列表或论坛中几天都没有回复，可以试试“开发者”列表或论坛。建议你在张贴前最好先暗暗地观察几天,至少看看最近几天保存的帖子,以了解那的行事方式（事实上这是参与任何私有或半私有列表的好主意）</p><p>如果你找不到一个项目的邮件列表，而只能查到项目维护者的地址，只管向其发信。即便在这种情况下，也别假设（项目）邮件列表不存在。在你的电子邮件中陈述你已经试过但没有找到合适的邮件列表，也提及你不反对将自己的邮件转发给他人（许多人认为，即使没什么秘密，私人电子邮件也不应该被公开。通过允许将你的电子邮件转发他人，你给了相应人员处置你邮件的选择）。</p><h4 id="使用有意义且明确的主题"><a href="#使用有意义且明确的主题" class="headerlink" title="使用有意义且明确的主题"></a>使用有意义且明确的主题</h4><p>在邮件列表、新闻组或论坛中，主题是你在五十个或更少的字以内吸引有资格专家注意的黄金机会，不要用诸如 “请帮我” （更别提大写的 “请帮我！！！！”，这种主题的消息会被条件反射式地删掉）之类的唠叨浪费机会。不要用你痛苦的深度来打动我们，相反，要在这点空间中使用超级简明扼要的问题描述。</p><p>使用主题的好惯例是“对象──偏差”（式的描述），许多技术支持组织就是这样做的。在“对象”部分指明是哪一个或哪一组东西有问题，在“偏差”部分则描述与期望的行为不一致的地方。</p><p>愚蠢：</p><blockquote><p>救命啊！我的笔记本视频工作不正常！</p></blockquote><p>明智：</p><blockquote><p>X.org 6.8.1 扭曲鼠标光标，MV1005 型号的某显卡芯片组</p></blockquote><p>更明智：</p><blockquote><p>使用 MV1005 型号的某显卡芯片组在 X.org 6.8.1 的鼠标光标被扭曲</p></blockquote><p>编写 “对象──偏差”式描述的过程有助于你组织对问题的细致思考。是什么被影响了？仅仅是鼠标光标或者还有其它图形？只在 X.org 中出现？或只是在其 6.8.1 版中？是针对某显卡芯片组？或者只是其中的 MV1005 型号？一个黑客只需描一眼就能够立即明白什么是你遇到的问题，什么是你自己的问题。</p><p>更一般地，想象一下在一个只显示主题的文档索引中查找。让你的主题更好地反映问题，可以使下一个搜索类似问题的人能够在文档中直接就找到答案的线索，而不用再次发贴提问。</p><p>如果你想在回复中提问，确保改变主题以表明你是在问一个问题，一个主题象 <code>Re: 测试</code> 或者 <code>Re: 新Bug</code> 的消息不太可能引起足够的注意。同时，将回复中与新主题不甚相关的引用内容尽量删除。</p><p>对于列表消息，不要直接点击回复（按钮）来开始一个全新的线索，这将限制你的观众。有些邮件阅读程序，比如 mutt，允许用户按线索排序并通过折叠线索来隐藏消息，这样做的人永远看不到你发的消息。</p><p>仅仅改变主题还不够。mutt 和其它一些邮件阅读程序还要检查邮件头主题以外的其它信息，以便为其指定线索，所以宁可发一个全新的邮件。</p><p>在论坛，因为消息与特定的线索紧密结合，并且通常在线索之外不可见，好的提问方式略有不同，通过回复提问并不要紧。不是所有论坛都允许在回复中出现分离的主题，而且这样做了基本上没有人会去看。不过，通过回复提问本身就是令人怀疑的做法，因为它们只会被正在查看该线索的人读到。所以，除非你 只想 在该线索当前活跃的人群中提问，还是另起炉灶比较好。</p><h4 id="使问题容易回复"><a href="#使问题容易回复" class="headerlink" title="使问题容易回复"></a>使问题容易回复</h4><p>以<code>请向……回复</code>来结束问题多半会使你得不到回答。如果你觉得花几秒钟在邮件客户端设置一下回复地址都麻烦，我们也觉得花几秒钟考虑你的问题更麻烦。如果你的邮件客户端程序不支持这样做，换个好点的；如果是操作系统不支持所有这种邮件客户端程序，也换个好点的。</p><p>在论坛，要求通过电子邮件回复是完全无礼的，除非你确信回复的信息也许是敏感的（而且有人会为了某些未知的原因，只让你而不是整个论坛知道答案）。如果你只是想在有人回复线索时得到电子邮件提醒，可以要求论坛发送。几乎所有论坛都支持诸如<code>留意本线索</code>、<code>有回复发送邮件</code>等功能。</p><h4 id="用清晰、语法、拼写正确的语句书写"><a href="#用清晰、语法、拼写正确的语句书写" class="headerlink" title="用清晰、语法、拼写正确的语句书写"></a>用清晰、语法、拼写正确的语句书写</h4><p>经验告诉我们，粗心与草率的作者通常也粗心与草率地思考和编程（我敢打赌）。为这些粗心与草率的思考者回答问题没有什么好处，我们宁可将时间花在其它地方。</p><p>清楚、良好地表达你的问题非常重要。如果你觉得这样做麻烦，我们也觉得注意（你的问题）麻烦。花点额外的精力斟酌一下字句，用不着太僵硬与正式──事实上，黑客文化很看重能准确地使用非正式、俚语和幽默的语句。但它 必须 很准确，而且有迹象表明你是在思考和关注问题。</p><p>正确地拼写、使用标点和大小写，不要将 <code>its</code> 混淆为 <code>it&#39;s</code>，<code>loose</code> 搞成 <code>lose</code> 或者将 “discrete” 弄成 “discreet”。不要全部用大写，这会被视为无礼的大声嚷嚷 （全部小写也好不到哪去，因为不易阅读。Alan Cox [注：著名黑客，Linux 内核的重要参与者] 也许可以这样做，但你不行。）</p><p>一般而言，如果你写得象个半文盲似的傻子，多半得不到理睬。也不要使用即时通讯中的简写，如将 <code>you</code> 简化为 <code>u</code> 会使你看起来象一个为了节约二次击键的半文盲式的傻子。更糟的是，如果象个小孩似地鬼画桃符那绝对是在找死，可以肯定没人会理你（或者最多是给你一大堆指责与挖苦）。</p><p>如果在非母语论坛提问，你的拼写与语法错误会得到有限的宽容，但懒惰完全不会被容忍（是的，我们通常看得出其中的差别）。同时，除非你知道回复者使用的语言，请使用英语书写。繁忙的黑客一般会直接删除用他们看不懂语言写的消息。在互联网上英语是工作语言，用英语书写可以将你的问题不被阅读就被直接删除的可能性降到最低。</p><p>如果你用英语书写但它是你的第二语言，最好提醒潜在的回复者语言上可能的困难以便绕过这个问题，比如：</p><ul><li>英语不是我的母语，请谅解拼写错误。</li><li>如果您使用某某语言，请电邮&#x2F;私聊我，也许我需要您的协助翻译我的问题。</li><li>对于这个技术术语本身我很熟悉，但对于它的一些俚语或习惯表达方式就不太明白了。</li><li>我已经同时用某某语及英语提问，如果您使用两者之一回复，我很乐意翻译。</li></ul><h4 id="使用易于读取且标准的文件格式发送问题"><a href="#使用易于读取且标准的文件格式发送问题" class="headerlink" title="使用易于读取且标准的文件格式发送问题"></a>使用易于读取且标准的文件格式发送问题</h4><p>如果你人为地将问题搞得难以阅读，它多半会被忽略，人们更愿读易懂的问题，所以：</p><ul><li><p>使用纯文本而不是 HTML（超文本标注语言）（ 关闭HTML 并不难）</p></li><li><p>使用 MIME（多用途互联网邮件扩展）附件通常没有问题，前提是真正有内容（譬如附带的源文件或补丁），而不仅仅是邮件客户端程序生成的模板（譬如只是消息内容的拷贝）。</p></li><li><p>不要发送整段只是单行句子但多次折回的邮件（这使得回复部分内容非常困难）。设想你的读者是在80个字符宽的文本终端阅读邮件，设置你的行折回点小于 80 列。</p></li><li><p>但是，也 不要 用任何固定列折回数据（譬如日志文件拷贝或会话记录）。数据应该原样包含，使回复者确信他们看到的是与你看到的一样的东西。</p></li><li><p>在英语论坛中，不要使用’Quoted-Printable’ MIME 编码发送消息。这种编码对于张贴非 ASCII 语言可能是必须的，但很多邮件程序并不支持。当它们分断时，那些文本中四处散布的 “&#x3D;20”符号既难看也分散注意力，甚至有可能破坏内容的语意。</p></li><li><p>永远不要 指望黑客们阅读使用封闭的专用格式编写的文档，诸如微软公司的 Word 或 Excel 文件等。大多数黑客对此的反应就象有人将还在冒热气的猪粪倒在你门口时你的反应一样。即使他们能够处理，也很厌恶这么做。</p></li><li><p>如果你从使用视窗的电脑发送电子邮件，关闭问题颇多的微软“聪明引用”功能（在“工具” -&gt; “自动纠正选项”的“输入时自动格式化”下去掉聪明引用的选框），以免在你的邮件中到处散布垃圾字符。</p></li><li><p>在论坛，勿滥用“表情符号”和“HTML”功能(当它们提供时)。一两个表情符号通常没有问题，但花哨的彩色文本倾向于使人认为你是个无能之辈。过滥地使用表情符号、色彩和字体会使你看来象个傻笑的小姑娘。这通常不是个好主意，除非你只是对性而不是有用的回复更有兴趣。</p></li><li><p>如果你使用图形用户界面的邮件客户端程序(如网景公司的 Messenger、微软公司的 Outlook 或者其它类似的)，注意它们的缺省配置不一定满足这些要求。大多数这类程序有基于菜单的<code>查看源码</code>命令，用它来检查发送文件夹中的消息，以确保发送的是没有多余杂质的纯文本文件。</p></li></ul><h4 id="描述问题应准确且有内容"><a href="#描述问题应准确且有内容" class="headerlink" title="描述问题应准确且有内容"></a>描述问题应准确且有内容</h4><ul><li>仔细、清楚地描述问题的症状</li><li>描述问题发生的环境(主机、操作系统、应用程序，任何相关的)，提供销售商的发行版和版本号（如：“Fedora Core 7”、“Slackware 9.1”等）</li><li>描述提问前做过的研究及其理解。</li><li>描述提问前为确定问题而采取的诊断步骤。</li><li>描述最近对计算机或软件配置的任何相关改变。</li><li>如果可能，提供在可控环境下重现问题的方法。</li><li>尽最大努力预测黑客会提到的问题，并提前备好答案。</li></ul><p>如果你认为是代码有问题，向黑客提供在可控环境下重现问题的方法尤其重要。当你这么做时，得到有用且及时回复的可能性将大大增加。</p><p><a href="http://www.chiark.greenend.org.uk/%7Esgtatham/">西蒙.泰瑟姆（Simon Tatham）</a>写过一篇《<a href="http://www.chiark.greenend.org.uk/%7Esgtatham/bugs-tw.html">如何有效报告Bug》</a>的文章，我强烈推荐各位阅读。</p><h4 id="量不在多，精炼则灵"><a href="#量不在多，精炼则灵" class="headerlink" title="量不在多，精炼则灵"></a>量不在多，精炼则灵</h4><p>你应该（写得）精炼且有内容，简单地将一大堆代码或数据罗列在求助消息中达不到目的。如果你有一个很大且复杂的测试样例让程序崩溃，尝试将其裁剪得越小越好。</p><p>至少有三个理由支持这点。第一，让别人看到你在努力简化问题使你更有可能得到回复。第二，简化问题使你更有可能得到 <code>有用的</code> 回复。第三，在提纯Bug报告的过程中，你可能自己就找到了解决办法或权宜之计。</p><h4 id="别急于宣称找到Bug"><a href="#别急于宣称找到Bug" class="headerlink" title="别急于宣称找到Bug"></a>别急于宣称找到Bug</h4><p>当你在一个软件中遇到问题，除非你 非常、非常 的有根据，不要动辄声称找到了Bug。提示：除非你能提供解决问题的源代码补丁，或者对前一版本的回归测试表现出不正确的行为，否则你都多半不够完全确信。对于网页和文档也如此，如果你（声称）发现了文档的“Bug”，你应该能提供相应位置的替代文本。</p><p>记住，还有许多其它用户并未经历你遇到的问题，否则你在阅读文档或搜索网页时就应该发现了（[你在报怨前已经做了这些，是吧 ？](#你在报怨前已经做了这些，是吧 ？)）。这也意味着很有可能是你弄错了而不是软件本身有问题。</p><p>编写软件的人总是非常辛苦地使它尽可能完美。如果你声称找到了Bug，也就置疑了他们的能力，即使你是对的，也有可能会使其中的部分人感到不快。（此外，）在主题中嚷嚷“Bug”也是特别不老练的。</p><p>提问时，即使你私下非常确信已经发现一个真正的Bug，最好写得象是 你 做错了什么。如果真的有Bug，你会在回复中看到这点。这样做的话，如果真有虫子，维护者就会向你道歉，这总比你弄砸了然后欠别人一个道歉要强。</p><h4 id="低声下气代替不了做自己的家庭作业"><a href="#低声下气代替不了做自己的家庭作业" class="headerlink" title="低声下气代替不了做自己的家庭作业"></a>低声下气代替不了做自己的家庭作业</h4><p>有些人明白他们不应该粗鲁或傲慢地行事并要求得到答复，但他们退到相反的低声下气的极端：“我知道我只是个可怜的新丁，一个失败者，但……”。这既使人困扰，也没有用，当伴随着对实际问题含糊的描述时还特别令人反感。</p><p>别用低级灵长类动物的办法浪费你我的时间，相反，尽可能清楚地描述背景情况和你的问题，这比低声下气更好地摆正了你的位置。</p><p>有时，论坛设有单独的初学者提问版面，如果你真的认为遇到了肤浅的问题，到那去就是了，但一样别低声下气。</p><h4 id="描述问题症状而不是猜测"><a href="#描述问题症状而不是猜测" class="headerlink" title="描述问题症状而不是猜测"></a>描述问题症状而不是猜测</h4><p>告诉黑客是什么导致了问题是没用的（如果你的诊断理论是了不起的东西，你还会向别人咨询求助吗？）。所以，确保只是告诉他们问题的原始症状，而不是你的解释和理论，让他们来解释和诊断。如果你认为陈述自己的猜测很重要，应清楚地说明这只是你的猜测并描述为什么它们不起作用。</p><p>愚蠢：</p><blockquote><p>我在编译内核时接连遇到 SIG11 错误，怀疑主板上的某根电路丝断了，找到它们的最好办法是什么？</p></blockquote><p>明智：</p><blockquote><p>我组装的电脑（K6&#x2F;233 CPU、FIC-PA2007 主板[威盛 Apollo VP2 芯片组]、Corsair PC133 SDRAM 256Mb 内存）最近在开机 20 分钟左右、做内核编译时频繁地报 SIG11 错，但在头 20 分钟内从不出问题。重启动不会复位时钟，但整夜关机会。更换所有内存未解决问题，相关的典型编译会话日志附后。</p></blockquote><p>由于以上这点许多人似乎难以掌握，这里有句话可以提醒你：“所有的诊断专家都来自密苏里州”。美国国务院的官方座右铭则是“让我看看”（出自国会议员威勒德.D.范迪弗［Willard D. Vandiver］在1899年时的讲话：“我来自一个出产玉米、棉花、牛蒡和民主党人的国家，滔滔雄辩既不能说服我，也不会让我满意。我来自密苏里州，你必须让我看看。”）针对诊断者而言，这并不是怀疑，而只是一种真实而有用的需求，以便让他们看到与你看到的原始证据尽可能一致的东西，而不是你的猜测与总结。（所以，）让我们看看。</p><h4 id="按时间先后罗列问题症状"><a href="#按时间先后罗列问题症状" class="headerlink" title="按时间先后罗列问题症状"></a>按时间先后罗列问题症状</h4><p>刚出问题之前发生的事情通常包含有解决问题最有效的线索。所以，记录中应准确地描述你、电脑和软件在崩溃前都做了什么。在命令行处理的情况下，有会话日志（如运行脚本工具生成的）并引用相关的若干（如20）行记录会非常有帮助。</p><p>如果崩溃的程序有诊断选项（如-v详述开关），试着选择这些能在记录中增加排错信息的选项。记住，“多”不等于“好”。试着选取适当的排错级别以便提供有用的信息而不是将阅读者淹没在垃圾中。</p><p>如果你的记录很长（如超过四段），在开头简述问题随后按时间先后罗列详细过程也许更有用。这样，黑客在读你的记录时就知道该注意哪些内容了。</p><h4 id="描述目标而不是过程"><a href="#描述目标而不是过程" class="headerlink" title="描述目标而不是过程"></a>描述目标而不是过程</h4><p>如果你想弄清楚如何做某事（而不是报告一个Bug），在开头就描述你的目标，然后才陈述遇到问题的特定步骤。</p><p>经常出现这种情况，寻求技术帮助的人在脑袋里有个更高层次的目标，他们在自以为能达到目标的特定道路上被卡住了，然后跑来问该怎么走，但没有意识到这条路本身有问题，结果要费很大的劲才能通过。</p><p>愚蠢：</p><blockquote><p>我怎样才能让某图形程序的颜色拾取器取得十六进制的 RGB 值？</p></blockquote><p>明智：</p><blockquote><p>我正试着用自己选定数值的颜色替换一幅图片的色表，我现在知道的唯一方法是编辑每个表槽，但却无法让某图形程序的颜色拾取器取得十六进制的 RGB 值。</p></blockquote><p>第二种提法是明智的，它使得建议采用更合适的工具以完成任务的回复成为可能。</p><h4 id="别要求私下回复电邮"><a href="#别要求私下回复电邮" class="headerlink" title="别要求私下回复电邮"></a>别要求私下回复电邮</h4><p>黑客们认为问题的解决过程应该公开、透明，此过程中如果更有才能的人注意到不完整或者不当之处，最初的回复才能够、也应该被纠正。同时，作为回复者也因为能力和学识被其它同行看到而得到某种回报。</p><p>当你要求私下回复时，此过程和回报都被中止。别这样做，让 回复者 来决定是否私下回答──如果他真这么做了，通常是因为他认为问题编写太差或者太肤浅，以至于对其它人毫无意义。</p><p>对这条规则存在一条有限的例外，如果你确信提问可能会引来大量雷同的回复时，那么“向我发电邮，我将为论坛归纳这些回复”将是神奇的句子。试着将邮件列表或新闻组从洪水般雷同的回复中解救出来是非常有礼貌的──但你必须信守诺言。</p><h4 id="提问应明确"><a href="#提问应明确" class="headerlink" title="提问应明确"></a>提问应明确</h4><p>漫无边际的问题通常也被视为没有明确限制的时间无底洞。最有可能给你有用答案的人通常也是最忙的人（假如只是因为他们承担了太多工作的话），这些人对于没有止境的时间无底洞极其敏感，所以他们也倾向于讨厌那些漫无边际的问题。</p><p>如果你明确了想让回复者做的事（如指点方向、发送代码、检查补丁或其它），你更有可能得到有用的回复。（因为）这样可以让他们集中精力并间接地设定了他们为帮助你需要花费的时间和精力上限，这很好。</p><p>要想理解专家生活的世界，可以这样设想：那里有丰富的专长资源但稀缺的响应时间。你暗中要求他们奉献的时间越少，你越有可能从这些真正懂行也真正很忙的专家那里得到解答。</p><p>所以限定你的问题以使专家回答时需要付出的时间最少──这通常与简化问题还不太一样。举个例，“请问可否指点一下哪有好一点的 X 解释？”通常要比“请解释一下 X”明智。如果你的代码不运行了，通常请别人看看哪有问题比叫他们帮你改正更明智。</p><h4 id="关于代码的问题"><a href="#关于代码的问题" class="headerlink" title="关于代码的问题"></a>关于代码的问题</h4><p>别要求他人给你出问题的代码排错而不提及应该从何入手。张贴几百行的代码，然后说一声“它不能运行”会让你得不到理睬。只贴几十行代码，然后说一句“在第七行以后，本应该显示<x>，但实际出现的是<y>”非常有可能让你得到回复。</p><p>最精确描述代码问题的方法是提供一个能展示问题的最小测试样例。什么是最小测试样例？它是对问题的展现，只需要刚好能够重现非预期行为的代码即可。如何生成一个最小测试样例？如果你知道哪一行或哪一段代码会产生问题，将其复制并提供刚好够用的外围支撑代码以构成一个完整的样例（够用是指源码刚好能被编译器、解释器或任何处理它的程序所接受）。如果你不能将问题缩小到特定的段落，复制源码并去除那些与问题无关的代码段。你能提供的最小测试样例越小越好（参见 量不在多，精炼则灵 ）。</p><p>生成一个非常小的最小测试样例并不总是可能，但尽力去做是很好的锻练，这有可能帮助你找到需要自己解决的问题。即使你找不到，黑客们喜欢看到你努力过，这将使他们更合作。</p><p>如果你只是想让别人帮忙审一下代码，在最开头就要说出来，并且一定要提到你认为哪一部分特别需要关注以及为什么。</p><h4 id="别张贴家庭作业式问题"><a href="#别张贴家庭作业式问题" class="headerlink" title="别张贴家庭作业式问题"></a>别张贴家庭作业式问题</h4><p>黑客们善于发现“家庭作业”式的问题。我们中的大多数人已经做了自己的家庭作业，那是该 你 做的，以便从中学到东西。问一下提示没有关系，但不是要求完整的解决方案。</p><p>如果你怀疑自己碰到了一个家庭作业式的问题，但仍然无法解决，试试在用户组、论坛或（作为最后一招）在项目的“用户”邮件列表或论坛中提问。尽管黑客们 会 看出来，一些老用户也许仍会给你提示。</p><h4 id="删除无意义的要求"><a href="#删除无意义的要求" class="headerlink" title="删除无意义的要求"></a>删除无意义的要求</h4><p>抵制这种诱惑，即在求助消息末尾加上诸如“有人能帮我吗？”或“有没有答案？”之类在语义上毫无意义的东西。第一，如果问题描述还不完整，这些附加的东西最多也只能是多余的。第二，因为它们是多余的，黑客们会认为这些东西烦人──就很有可能用逻辑上无误但打发人的回复，诸如“是的，你可以得到帮助”和“不，没有给你的帮助”。</p><p>一般来说，避免提“是或否”类型的问题，除非你想得到 “是或否”类型的回答。</p><h4 id="不要把问题标记为“紧急”，即使对你而言的确如此"><a href="#不要把问题标记为“紧急”，即使对你而言的确如此" class="headerlink" title="不要把问题标记为“紧急”，即使对你而言的确如此"></a>不要把问题标记为“紧急”，即使对你而言的确如此</h4><p>这是你的问题，不要我们的。宣称“紧急”极有可能事与愿违：大多数黑客会直接删除这种消息，他们认为这是无礼和自私地企图得到即时与特殊的关照。而且“紧急”或其它有类似含义的主题有可能触发垃圾过滤规则，潜在的回复者可能永远看不到你的问题！</p><p>有一点点局部的例外，如果你是在一些知名度很高、会使黑客们激动的地方使用程序，也许值得这样去做。在这种情况下，如果你有期限压力，也很有礼貌地提到这点，人们也许会有足够的兴趣快一点回答。</p><p>当然，这是非常冒险的，因为黑客们对什么是令人激动的标准多半与你的不同。譬如从国际空间站这样张贴没有问题，但代表感觉良好的慈善或政治原因这样做几乎肯定不行。事实上，张贴诸如“紧急：帮我救救这个毛绒绒的小海豹！”肯定会被黑客回避或光火，即使他们认为毛绒绒的小海豹很重要。</p><p>如果你觉得这不可思议，再把剩下的内容多读几遍，直到弄懂了再发贴也不迟。</p><h4 id="礼貌总是有益的"><a href="#礼貌总是有益的" class="headerlink" title="礼貌总是有益的"></a>礼貌总是有益的</h4><p>礼貌一点，使用 <code>请</code> 和 <code>谢谢你的关注</code> 或者 <code>谢谢你的关照</code>，让别人明白你感谢他们无偿花时间帮助你。</p><p>坦率地讲，这一点没有语法正确、文字清晰、准确、有内容和避免使用专用格式重要（同时也不能替代它们）。黑客们一般宁可读有点唐突但技术鲜明的Bug报告，而不是那种有礼但含糊的报告。（如果这点让你不解，记住我们是按问题能教我们什么来评价它的）</p><p>然尔，如果你已经谈清楚了技术问题，客气一点肯定会增加你得到有用回复的机会。</p><p>（我们必须指出，本文唯一受到一些老黑客认真反对的地方是以前曾经推荐过的“提前谢了”，一些黑客认为这隐含着事后不用再感谢任何人的暗示。我们的建议是要么先说 <code>提前谢了</code>，事后 再 对回复者表示感谢，要么换种方式表达，譬如用 <code>谢谢你的关注</code> 或 <code>谢谢你的关照</code>）。</p><h4 id="问题解决后追加一条简要说明"><a href="#问题解决后追加一条简要说明" class="headerlink" title="问题解决后追加一条简要说明"></a>问题解决后追加一条简要说明</h4><p>问题解决后向所有帮助过的人追加一条消息，让他们知道问题是如何解决的并再次感谢。如果问题在邮件列表或新闻组中受到广泛关注，在那里追加此消息比较恰当。</p><p>最理想的方式是向最初提问的线索回复此消息，并在主题中包含 <code>已解决</code>、<code>已搞定</code> 或其它同等含义的明显标记。在人来人往的邮件列表里，一个看见线索 <code>问题 X</code> 和 <code>问题 X-已解决</code> 的潜在回复者就明白不用再浪费时间了（除非他个人觉得“问题 X”有趣），因此可以利用此时间去解决其它问题。</p><p>追加的消息用不着太长或太复杂，一句简单的“你好──是网线坏了！谢谢大家──比尔”就比什么都没有要强。事实上，除非解决问题的技术真正高深，一条简短而亲切的总结比长篇大论要好。说明是什么行动解决了问题，用不着重演整个排错的故事。</p><p>对于有深度的问题，张贴排错历史的摘要是恰当的。描述问题的最终状态，说明是什么解决了问题，在此之后 才指明可以避免的弯路。应避免的弯路部分应放在正确的解决方案和其它总结材料之后，而不要将此消息搞成侦探推理小说。列出那些帮助过你的名字，那样你会交到朋友的。</p><p>除了有礼貌、有内容以外，这种类型的追帖将帮助其他人在邮件列表、新闻组或论坛文档中搜索到真正解决你问题的方案，从而也让他们受益。</p><p>最后，此类追帖还让每位参与协助的人因问题的解决而产生一种满足感。如果你自己不是技术专家或黑客，相信我们，这种感觉对于你寻求帮助的老手和专家是非常重要的。问题叙述到最后不知所终总是令人沮丧的，黑客们痒痒地渴望它们被解决。<code>挠痒痒</code> 为你挣到的信誉将对你下次再次张贴提问非常非常的有帮助。</p><p>考虑一下怎样才能避免他人将来也遇到类似的问题，问问自己编一份文档或 FAQ 补丁会不会有帮助，如果是的话就将补丁发给维护者。</p><p>在黑客中，这种良好的后继行动实际上比传统的礼貌更重要，也是你善待他人而赢得声誉的方式，这是非常有价值的财富。</p><h3 id="如何解读回答"><a href="#如何解读回答" class="headerlink" title="如何解读回答"></a>如何解读回答</h3><h4 id="“读读该死的手册”（RTFM）和“搜搜该死的网络”（STFW）：如何明白你已完全搞砸"><a href="#“读读该死的手册”（RTFM）和“搜搜该死的网络”（STFW）：如何明白你已完全搞砸" class="headerlink" title="“读读该死的手册”（RTFM）和“搜搜该死的网络”（STFW）：如何明白你已完全搞砸"></a>“读读该死的手册”（RTFM）和“搜搜该死的网络”（STFW）：如何明白你已完全搞砸</h4><p>有一个古老而神圣的传统：如果你收到 <code>读读该死的手册</code>（RTFM） 的回复，发信人认为你应该去“读读该死的手册”。他或她多半是对的，去读一下吧。</p><p>“读读该死的手册”（RTFM）有个年轻一点的亲戚，如果你收到“搜搜该死的网络”（STFW）的回复，发信人认为你应该“搜搜该死的网络”。那人多半也是对的，去搜一下吧。(更温和一点的说法是“<a href="http://lmgtfy.com/">谷歌是你的朋友！</a>”)</p><p>在论坛，你也可能被要求去搜索论坛的文档。事实上，有人甚至可能热心地为你提供以前解决此问题的线索。但不要依赖这种关照，提问前应该先搜索一下文档。</p><p>通常，叫你搜索的人已经打开了能解决你问题的手册或网页，正在一边看一边敲键盘。这些回复意味着他认为：</p><ul><li>第一，你要的信息很容易找到。</li><li>第二，自已找要比别人喂到嘴里能学得更多。</li></ul><p>你不应该觉得这样就被冒犯了，按黑客的标准，回复者没有不理你就是在向你表示某种尊敬，你反而应该感谢他热切地想帮助你。</p><h4 id="如果还不明白……"><a href="#如果还不明白……" class="headerlink" title="如果还不明白……"></a>如果还不明白……</h4><p>如果你看不懂回答，不要马上回复一个要求说明的消息，先试试那些最初提问时用过的相同工具（如手册、FAQ、网页、懂行的朋友等）试着搞懂回答。如果还是需要说明，展现你已经明白的。</p><p>譬如，假如我告诉你：“看起来象是某输入项有问题，你需要清除它”，接着是个 不好 的回帖：“什么是某输入项？”。而这是一个 很好 的跟帖：“是的，我读了手册，某某输入项只在 -z 和 -p 开关中被提到，但都没有涉及到如何清除它们，你指的是哪一个还是我弄错了什么？”</p><h4 id="对待无礼"><a href="#对待无礼" class="headerlink" title="对待无礼"></a>对待无礼</h4><p>很多黑客圈子中看似无礼的行为并不是存心冒犯。相反，它是直接了当、一针见血式的交流风格，这种风格对于更关注解决问题而不是使别人感觉舒服而混乱的人是很自然的。</p><p>如果你觉得被冒犯了，试着平静地反应。如果有人真的做了过格的事，邮件列表、新闻组或论坛中的前辈多半会招呼他。如果这 没有 发生而你却光火了，那么你发火对象的言语可能在黑客社区中看起来是正常的，而 你 将被视为有错的一方，这将伤害到你获取信息或帮助的机会。</p><p>另一方面，你会偶而真的碰到无礼和无聊的言行。与上述相反，对真正的冒犯者狠狠地打击、用犀利的语言将其驳得体无完肤都是可以接受的。然尔，在行事之前一定要非常非常的有根据。纠正无礼的言论与开始一场毫无意义的口水战仅一线之隔，黑客们自己莽撞地越线的情况并不鲜见。如果你是新手或外来者，避开这种莽撞的机会并不高。如果你想得到的是信息而不是消磨时光，这时最好不要把手放在键盘上以免冒险。</p><p>（有些人断言很多黑客都有轻度的自闭症或阿斯伯格综合症，缺少用于润滑人类社会“正常”交往所需的脑电路。这既可能是真也可能是假。如果你自己不是黑客，兴许你认为我们脑袋有问题还能帮助你应付我们的古怪行为。只管这么干好了，我们不在乎。我们 喜欢 现在这个样子，并且一般都对病号标记有站得住脚的怀疑。）</p><p>在下一节，我们会谈到另一个问题，当 你 行为不当时会受到的“冒犯”。</p><h3 id="别象失败者那样反应"><a href="#别象失败者那样反应" class="headerlink" title="别象失败者那样反应"></a>别象失败者那样反应</h3><p>在黑客社区的论坛中有那么几次你可能会搞砸──以本文描述或类似的方式。你会被示众是如何搞砸的，也许言语中还会带点颜色。</p><p>这种事发生以后，你能做的最糟糕的事莫过于哀嚎你的遭遇、宣称被口头攻击、要求道歉、高声尖叫、憋闷气、威胁诉诸法律、向其雇主报怨、忘了关马桶盖等等。相反，你该这样去做：</p><p>熬过去，这很正常。事实上，它是有益健康与恰当的。</p><p>社区的标准不会自己维持，它们是通过参与者积极而 公开 地执行来维持的。不要哭嚎所有的批评都应该通过私下的邮件传送，这不是事情运作的方式。当有人评论你的一个说法有误或者提出不同看法时，坚持声称受到个人攻击也毫无益处，这些都是失败者的态度。</p><p>也有其它的黑客论坛，受过高礼节要求的误导，禁止参与者张贴任何对别人帖子挑毛病的消息，并声称“如果你不想帮助用户就闭嘴”。有思路的参与者纷纷离开的结果只会使它们变成了毫无意义的唠叨与无用的技术论坛。</p><p>是夸张的“友谊”（以上述方式）还是有用？挑一个。</p><p>记着：当黑客说你搞砸了，并且(无论多么刺耳地)告诉你别再这样做时，他正在为关心你和他的社区而行动。对他而言，不理你并将你从他的生活中滤除要容易得多。如果你无法做到感谢，至少要有点尊严，别大声哀嚎，也别因为自己是个有戏剧性超级敏感的灵魂和自以为有资格的新来者，就指望别人象对待脆弱的洋娃娃那样对你。</p><p>有时候，即使你没有搞砸（或者只是别人想象你搞砸了）， 有些人也会无缘无故地攻击你本人。在这种情况下，报怨倒是 真的 会把问题搞砸。</p><p>这些找茬者要么是毫无办法但自以为是专家的不中用家伙，要么就是测试你是否真会搞砸的心理专家。其它读者要么不理睬，要么用自己的方式对付他们。这些找茬者在给自己找麻烦，这点你不用操心。</p><p>也别让自己卷入口水战，大多数口水战最好不要理睬──当然，是在你核实它们只是口水战、没有指出你搞砸的地方，而且没有巧妙地将问题真正的答案藏于其中之后（这也是可能的）。</p><h3 id="提问禁忌"><a href="#提问禁忌" class="headerlink" title="提问禁忌"></a>提问禁忌</h3><p>下面是些典型的愚蠢问题和黑客不回答它们时的想法。</p><p>问：我到哪可以找到某程序或 X 资源？</p><p>问：我怎样用 X 做 Y？</p><p>问：如何配置我的 shell 提示？</p><p>问：我可以用 Bass-o-matic 文件转换工具将 AcmeCorp 文档转为 TeX 格式吗？</p><p>问：我的{程序、配置、SQL 语句}不运行了</p><p>问：我的视窗电脑出问题了，你能帮忙吗？</p><p>问：我的程序不运行了，我认为系统工具X有问题</p><p>问：我安装 Linux 或 X 遇到困难，你能帮忙吗？</p><p>问：我如何才能破解超级用户口令&#x2F;盗取通道操作员的特权&#x2F;查看某人的电子邮件？</p><hr><p>问：</p><blockquote><p>我到哪可以找到某程序或 X 资源？</p></blockquote><p>答：</p><blockquote><p>在我找到它的同样地方，笨旦──在网页搜索引擎上。上帝啊，难道还有人不知道如何使用 谷歌 吗？</p></blockquote><p>问：</p><blockquote><p>我怎样用 X 做 Y？</p></blockquote><p>答：</p><blockquote><p>如果你想解决的是 Y，提问时别给出可能并不恰当的方法。这种问题说明提问者不但对 X 完全无知，也对要解决的 Y 问题糊涂，还被特定形势禁锢了思维。等他们把问题弄好再说。</p></blockquote><p>问：</p><blockquote><p>如何配置我的 shell 提示？</p></blockquote><p>答：</p><blockquote><p>如果你有足够的智慧提这个问题，你也该有足够的智慧去 “读读该死的手册”（RTFM），然后自己去找出来。</p></blockquote><p>问：</p><blockquote><p>我可以用 Bass-o-matic 文件转换工具将 AcmeCorp 文档转为 TeX 格式吗？</p></blockquote><p>答：</p><blockquote><p>试试就知道了。如果你试过，你既知道了答案，又不用浪费我的时间了。</p></blockquote><p>问：</p><blockquote><p>我的{程序、配置、SQL 语句}不运行了</p></blockquote><p>答：</p><blockquote><p>这不是一个问题，我也没有兴趣去猜你有什么问题──我有更要紧的事要做。看到这种东西，我的反应一般如下：</p></blockquote><ul><li>你还有什么补充吗？</li><li>噢，太糟了，希望你能搞定。</li><li>这跟我究竟有什么关系？</li></ul><p>问：</p><blockquote><p>我的视窗电脑出问题了，你能帮忙吗？</p></blockquote><p>答：</p><blockquote><p>是的，把视窗垃圾删了，装个象 Linux 或 BSD 的开源操作系统吧。</p></blockquote><p>注意：如果程序有官方的视窗版或者与视窗有交互(如 Samba)，你 可以 问与视窗相关的问题，只是别对问题是由视窗操作系统而不是程序本身造成的回复感到惊讶，因为视窗一般来说太差，这种说法一般都成立。</p><p>问：</p><blockquote><p>我的程序不运行了，我认为系统工具 X 有问题</p></blockquote><p>答：</p><blockquote><p>你完全有可能是第一个注意到被成千上万用户反复使用的系统调用与库文件有明显缺陷的人，更有可能的是你完全没有根据。不同凡响的说法需要不同凡响的证据，当你这样声称时，你必须有清楚而详尽的缺陷说明文档作后盾。</p></blockquote><p>问：</p><blockquote><p>我安装 Linux 或 X 遇到困难，你能帮忙吗？</p></blockquote><p>答：</p><blockquote><p>不行，我需要亲手操作你的电脑才能帮你排错，去向当地的 Linux 用户组寻求方便的帮助（你可以在 这里 找到用户组列表）</p></blockquote><p>注意：如果安装问题与某 Linux 发行版有关，在针对 它 的邮件列表、论坛或本地用户组织中提问也许是恰当的。此时，应描述问题的准确细节。在此之前，先用 “linux”和 所有 被怀疑的硬件 [作关键词] 仔细搜索。</p><p>问：</p><blockquote><p>我如何才能破解超级用户口令&#x2F;盗取通道操作员的特权&#x2F;查看某人的电子邮件？</p></blockquote><p>答：</p><blockquote><p>想做这种事情说明你是个卑劣的家伙，想让黑客教你做这种事情说明你是个白痴。</p></blockquote><h4 id="好问题与坏问题"><a href="#好问题与坏问题" class="headerlink" title="好问题与坏问题"></a>好问题与坏问题</h4><p>最后，我将通过举例来演示提问的智慧。同样的问题两种提法，一种愚蠢，另一种明智。</p><p>愚蠢：我在哪能找到关于 Foonly Flurbamatic 设备的东西？</p><blockquote><p>这个问题在乞求得到 “搜搜该死的网络”（STFW） 式的回复。</p></blockquote><p>明智： 我用谷歌搜索过“Foonly Flurbamatic 2600”，但没有找到什么有用的，有谁知道在哪能找到这种设备的编程信息？</p><blockquote><p>这个人已经搜索过网络了，而且听起来他可能真的遇到了问题。</p></blockquote><p>愚蠢： 我不能编译某项目的源代码，它为什么这么破？</p><blockquote><p>提问者假设是别人搞砸了，太自大了。</p></blockquote><p>明智： 某项目的源代码不能在某 Linux 6.2 版下编译。我读了常见问题文档，但其中没有与某 Linux 相关的内容。这是编译时的记录，我做错了什么吗？</p><blockquote><p>提问者已经指明了运行环境，读了常见问题文档（FAQ），列出了错误，也没有假设问题是别人的过错，这家伙值得注意。</p></blockquote><p>愚蠢： 我的主板有问题，谁能帮我？</p><blockquote><p>某黑客对此的反应可能是：“是的，还需要帮你拍背和换尿布吗？”，然后是敲下删除键。</p></blockquote><p>明智： 我在 S2464 主板上试过 X、Y 和 Z，当它们都失败后，又试了 A、B 和 C。注意我试 C 时的奇怪症状，显然某某东西正在做某某事情，这不是期望的行为。通常在 Athlon MP 主板上导致某某事情的原因是什么？有谁知道我还能再试点什么以确定问题？</p><blockquote><p>相反地，这个人看来值得回答。他或她展现了解决问题的能力而不是坐等天上掉馅饼。</p></blockquote><p>在最后那个问题中，注意“给我一个回答”与“请帮我看看我还能再做点什么测试以得到启发”之间细微但重要的差别。</p><p>事实上，最后那个问题基本上源于 2001 年 8 月 Linux 内核邮件列表（lkml）上的真实事件，是我（Eric）当时提了那个问题，我发现 Tyan S2462 主板有神秘的死机现象，邮件列表成员给我提供了解决此问题的关键信息。</p><p>通过这种提问方式，我给了别人可以咀嚼玩味的东西。我设法使之对参与者既轻松又有吸引力，也表明了对同行能力的尊敬并邀请他们与我一起协商。通过告诉他们我已经走过的弯路，我还表明了对他们宝贵时间的尊重。</p><p>事后，当我感谢大家并评论这次良好的经历时，一个 Linux 内核邮件列表的成员谈到，他认为我得到答案并不是因为我的名字挂在列表上，而只是因为我正确的提问方式。</p><p>黑客们在某种方面是非常不留情面的精英分子。我想在这事上他是对的，如果我 表现得 象个不劳而获的寄生虫，不管我是谁都会被忽略或斥责。他建议将整个事件作为对其它人提问的指导，这直接导致了本文的编写。</p><h3 id="如果得不到回答"><a href="#如果得不到回答" class="headerlink" title="如果得不到回答"></a>如果得不到回答</h3><p>如果得不到回答，请不要认为我们不想帮你，有时只是因为被问到的小组成员的确不知道答案。没有回复不等于不被理睬，当然必须承认从外面很难看出两者的差别。</p><p>一般而言，直接将问题再张贴一次不好，这会被视为毫无意义的骚扰。耐心一点，知道你问题答案的人可能生活在不同的时区，有可能正在睡觉，也有可能你的问题一开始就没有组织好。</p><p>还有其它资源可以寻求帮助，通常是在一些面向新手的资源中。</p><p>有许多在线与本地的用户组织，虽然它们自己不编写任何软件，但是对软件很热心。这些用户组通常因互助和帮助新手而形成。</p><p>还有众多大小商业公司提供签约支持服务，别因为要付点钱才有支持就感到沮丧！毕竟，如果你车子的汽缸垫烧了，你多半还得花钱找个修理店把它弄好。即使软件没花你一分钱，你总不能指望服务支持都是免费的。</p><p>象 Linux 这样流行的软件，每个开发者至少有一万个以上的用户，一个人不可能应付这么多用户的服务要求。记住，即使你必须付费才能得到支持，也比你还得额外花钱买软件要少得多（而且对封闭源代码软件的服务支持与开源软件相比通常还要贵一点，也要差一点）。</p><h3 id="如何更好地回答"><a href="#如何更好地回答" class="headerlink" title="如何更好地回答"></a>如何更好地回答</h3><p>态度和善一点。问题带来的压力常使人显得无礼或愚蠢，其实并不是这样。</p><p>对初犯者私下回复。 对那些坦诚犯错之人没有必要当众羞辱，一个真正的新手也许连怎么搜索或在哪找 FAQ 都不知道。</p><p>如果你不确定，一定要说出来！ 一个听起来权威的错误回复比没有还要糟，别因为听起来象个专家好玩就给别人乱指路。要谦虚和诚实，给提问者与同行都树个好榜样。</p><p>如果帮不了忙，别妨碍。 不要在具体步骤上开玩笑，那样也许会毁了用户的安装──有些可怜的呆瓜会把它当成真的指令。</p><p>探索性的反问以引出更多的细节。 如果你做得好，提问者可以学到点东西──你也可以。试试将很差的问题转变成好问题，别忘了我们都曾是新手。</p><p>尽管对那些懒虫报怨一声“读读该死的手册”（RTFM）是正当的，指出文档的位置（即使只是建议做个谷歌关键词搜索）会更好</p><p>如果你决意回答，给出好的答案。 当别人正在用错误的工具或方法时别建议笨拙的权宜之计，应推荐更好的工具，重新组织问题。</p><p>请回答真正的问题！如果提问者已经做了自己该做的研究，并且说明尝试过<code> X，Y，Z，A，B 与 C</code> 都没有得到想要的結果，那么回复 <code>试试 A 或 B</code> 或者给出一个内容为 <code>试一下 X，Y，Z，A，B 或 C</code> 的链接将极其无益！</p><p>帮助你的社区从中学习。当回复一个好问题时，问问自己 <code>如何修改相关文件或 FAQ 文档以免再次解答同样的问题？</code>，接着再向文档维护者发一份补丁。</p><p>如果你是在研究一番后才做出的回答，展现你的技巧而不是直接端出结果。毕竟“授人以鱼，不如授人以渔”。</p><h3 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h3><p>如果需要个人电脑、Unix 和互联网如何工作的基础知识，参阅 <a href="http://en.tldp.org/HOWTO/Unix-and-Internet-Fundamentals-HOWTO/">Unix 和互联网工作的基本原理</a>。</p><p>当你发布软件或补丁时，试着按 <a href="http://en.tldp.org/HOWTO/Software-Release-Practice-HOWTO/index.html">软件发布实践</a> 操作。</p><h3 id="鸣谢"><a href="#鸣谢" class="headerlink" title="鸣谢"></a>鸣谢</h3><p>伊夫林.米切尔（Evelyn Mitchell）贡献了一些愚蠢问题例子并启发了编写<code>如何更好地回答问题</code>这一节，米哈伊尔.罗门迪克（Mikhail Ramendik）贡献了一些特别有价值的建议和改进。</p><h3 id="声明"><a href="#声明" class="headerlink" title="声明"></a>声明</h3><p>此篇文章来源于于Github<br><img src="https://blog-alan.oss-cn-hangzhou.aliyuncs.com/questioning/git_questioning.png"></p>]]></content>
    
  </entry>
  
  <entry>
    <title>Gitbook的搭建过程</title>
    <link href="https://alan.org.cn/posts/2023-1-27_gitbook_in.html"/>
    <id>https://alan.org.cn/posts/2023-1-27_gitbook_in.html</id>
    <published>2023-01-27T04:03:00.000Z</published>
    <updated>2026-06-04T04:10:49.244Z</updated>
    
    
    
    <category term="技术分享" scheme="https://alan.org.cn/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;Gitbook搭建&quot;&gt;&lt;a href=&quot;#Gitbook搭建&quot; class=&quot;headerlink&quot; title=&quot;Gitbook搭建&quot;&gt;&lt;/a&gt;Gitbook搭建&lt;/h2&gt;&lt;h3 id=&quot;1、安装Nodejs&quot;&gt;&lt;a href=&quot;#1、安装Nodejs&quot; cla</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="Gitbook搭建"><a href="#Gitbook搭建" class="headerlink" title="Gitbook搭建"></a>Gitbook搭建</h2><h3 id="1、安装Nodejs"><a href="#1、安装Nodejs" class="headerlink" title="1、安装Nodejs"></a>1、安装Nodejs</h3><p>1.1、ubantu安装</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">apt-get install nodejs npm -y<br></code></pre></td></tr></table></figure><p>1.2 mac安装<br>官网下载安装</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://nodejs.org/en/<br></code></pre></td></tr></table></figure><p>1.2 Window安装<br>官网下载安装</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://nodejs.org/en/<br></code></pre></td></tr></table></figure><h3 id="2、-安装Gitbook"><a href="#2、-安装Gitbook" class="headerlink" title="2、 安装Gitbook"></a>2、 安装Gitbook</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">npm install Gitbook-cli -g<br></code></pre></td></tr></table></figure><h3 id="3、-初始化目录"><a href="#3、-初始化目录" class="headerlink" title="3、 初始化目录"></a>3、 初始化目录</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">Gitbook init book<br></code></pre></td></tr></table></figure><h3 id="4-启动Gitbook服务"><a href="#4-启动Gitbook服务" class="headerlink" title="4. 启动Gitbook服务"></a>4. 启动Gitbook服务</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">Gitbook serve<br></code></pre></td></tr></table></figure><p>参数:<br>&ensp;&ensp;&ensp;&ensp; –port&#x3D;8081<br>1</p><h3 id="5、Gitbook使用篇"><a href="#5、Gitbook使用篇" class="headerlink" title="5、Gitbook使用篇"></a>5、Gitbook使用篇</h3><p>Summary</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs plaintext"># Summary<br><br>* [目录](README.md)<br><br>* [第一章](notes/1-0.md)<br><br>  * [第1节：](notes/1-1.md)<br><br>  * [第2节：](notes/1-2.md)<br><br>  * [第3节：](notes/1-3.md)<br><br>  * [第4节：](notes/1-4.md)<br><br>* [第二章](notes/2-0.md)<br><br>* [第三章](notes/3-0.md)<br><br>* [第四章](notes/4-0.md)<br></code></pre></td></tr></table></figure><p>生成pdf格式：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">Gitbook pdf ./ ./mybook.pdf<br></code></pre></td></tr></table></figure>]]></content>
    
  </entry>
  
  <entry>
    <title>Hexo博客的搭建与部署</title>
    <link href="https://alan.org.cn/posts/hexo%E5%8D%9A%E5%AE%A2%E7%9A%84%E6%90%AD%E5%BB%BA%E4%B8%8E%E9%83%A8%E7%BD%B2.html"/>
    <id>https://alan.org.cn/posts/hexo%E5%8D%9A%E5%AE%A2%E7%9A%84%E6%90%AD%E5%BB%BA%E4%B8%8E%E9%83%A8%E7%BD%B2.html</id>
    <published>2023-01-26T04:03:00.000Z</published>
    <updated>2026-06-04T04:10:49.244Z</updated>
    
    
    <category term="设计开发" scheme="https://alan.org.cn/categories/%E8%AE%BE%E8%AE%A1%E5%BC%80%E5%8F%91/"/>
    
    <category term="技术分享" scheme="https://alan.org.cn/categories/%E8%AE%BE%E8%AE%A1%E5%BC%80%E5%8F%91/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="技术分享" scheme="https://alan.org.cn/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、Hexo简介&quot;&gt;&lt;a href=&quot;#一、Hexo简介&quot; class=&quot;headerlink&quot; title=&quot;一、Hexo简介&quot;&gt;&lt;/a&gt;一、Hexo简介&lt;/h2&gt;&lt;p&gt;Hexo快速、简洁且高效的博客框架，属于静态网站，因为是开源的，所以官网有许多精致的主题供大</summary>
      
    
    
    <content type="html"><![CDATA[<h2 id="一、Hexo简介"><a href="#一、Hexo简介" class="headerlink" title="一、Hexo简介"></a>一、Hexo简介</h2><p>Hexo快速、简洁且高效的博客框架，属于静态网站，因为是开源的，所以官网有许多精致的主题供大家选择，这款博客框架可以部署到github和gitee上，非常适合我们计算机从业者记录生活与笔记。下面我们开始搭建把<br>首先我先概述下我们接下来的流程：<br>&ensp;&ensp; &ensp;&ensp;&ensp;&ensp; <strong>1、安装hexo</strong><br>&ensp;&ensp; &ensp;&ensp;&ensp;&ensp; &ensp;&ensp;&ensp;&ensp; 安装git<br>&ensp;&ensp; &ensp;&ensp;&ensp;&ensp; &ensp;&ensp;&ensp;&ensp; 安装nodejs<br>&ensp;&ensp; &ensp;&ensp;&ensp;&ensp; &ensp;&ensp;&ensp;&ensp; 安装hexo<br>&ensp;&ensp; &ensp;&ensp;&ensp;&ensp; <strong>2、搭建博客</strong><br>&ensp;&ensp; &ensp;&ensp;&ensp;&ensp; <strong>3、部署博客</strong></p><p>提示：在部署博客时我们推荐使用另一个托管平台名叫Vercel，后面我会专门写一章关于此平台的简介</p><h2 id="二、安装Git"><a href="#二、安装Git" class="headerlink" title="二、安装Git"></a>二、安装Git</h2><p>Git是目前比较流行分布式版本控制系统，可以有效、高速的处理从很小到非常大的项目版本管理<br><strong>ubantu命令:</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">apt-get install git -y<br></code></pre></td></tr></table></figure><p><strong>Windows下载地址:</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://git-scm.com<br></code></pre></td></tr></table></figure><p><strong>Mac在安装Xcode时会自动安装git，或使用breaw命令进行安装</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">brew install git<br></code></pre></td></tr></table></figure><h2 id="三、安装nodejs"><a href="#三、安装nodejs" class="headerlink" title="三、安装nodejs"></a>三、安装nodejs</h2><p>Hexo是基于nodejss所开发的，所以我们需要安装一下nodeJs和里面的npm工具<br><strong>ubantu命令:</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">apt-get install nodejs npm -y<br></code></pre></td></tr></table></figure><p><strong>windows和mac可以进入官网直接下载安装包</strong><br>如果速度较慢建议采用迅雷下载</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://nodejs.org/en/<br></code></pre></td></tr></table></figure><p>安装完后，输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">node -v<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">npm -v<br></code></pre></td></tr></table></figure><p>检查是否安装成功</p><p>如果接下来的安装hexo速度较慢，建议更换npm源为国内的淘宝源</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">npm config set registry https://registry.npm.taobao.org  <br></code></pre></td></tr></table></figure><h2 id="四、安装hexo"><a href="#四、安装hexo" class="headerlink" title="四、安装hexo"></a>四、安装hexo</h2><p>此时我们不区分系统，都是在终端输入命令安装hexo</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">npm install -g hexo-cli<br></code></pre></td></tr></table></figure><p>输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">hexo -v<br></code></pre></td></tr></table></figure><p>查看版本信息</p><p>初始化hexo</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">hexo init filename<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">cd filename<br></code></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">npm install<br></code></pre></td></tr></table></figure><p>filename为博客文件夹的名字，可以进行更改<br>cd 是切换用户命令，这里我们通过终端进入到博客的文件夹中<br>hexo init 是初始化我们的博客目录，简单来说，就是从网上拉去我们所需要的文件<br>npm install #说明：安装依赖包<br>hexo generate #说明：构建，会在hexo1中创建public文件夹<br>执行完以上命令后，会多出以下文件和文件夹<br>例如</p><p>新建完成后，指定文件夹目录下有：</p><p>_config.yml：站点的配置文件<br>themes：主题文件夹<br>source：博客文章存放的文件夹<br>scaffolds：文章的模板<br>package.json：安装包的名称<br>.gitignore：限定在 push 时哪些文件可以忽略<br>.git：主题和站点都有，标志这是一个 git 项目<br>node_modules：是安装包的目录，在执行 npm install 的时候会重新生成<br>public：是 hexo g 生成的静态网页<br>.deploy_git：同上，hexo g 也会生成<br>db.json：文件</p><h2 id="五、本地运行"><a href="#五、本地运行" class="headerlink" title="五、本地运行"></a>五、本地运行</h2><p>这里我们的博客就搭建起来了，接下来我们来运行博客</p><p>输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">hexo g<br></code></pre></td></tr></table></figure><p>这是将原文件生成静态网页</p><p>输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">hexo s<br></code></pre></td></tr></table></figure><p>这里是运行我们的博客</p><p>然后我们在浏览器输入</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">http://localhost:4000<br></code></pre></td></tr></table></figure><p>就可以看到我们的内容了</p><p>如果需要停止<br>我们需要在终端按ctrl+c键盘</p><h2 id="六、部署"><a href="#六、部署" class="headerlink" title="六、部署"></a>六、部署</h2><p>接下来是我们的部署博客</p><h2 id="1、vercel部署"><a href="#1、vercel部署" class="headerlink" title="1、vercel部署"></a>1、vercel部署</h2><p>首先我们需要进入vercel注册一个账户</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://vercel.com/<br></code></pre></td></tr></table></figure><p>这里我们采用github登陆<br>所以我们还需要去github注册一个账户</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">https://github.com<br></code></pre></td></tr></table></figure><p>然后我们需要在GitHub创建一个个人仓库<br>注册登录github官网，点击右上角加号，点击New repository，新建仓库<br>这里不要勾选初始化README.md<br>点击create repository<br>然后github会提示通过git上传文件夹内容到github仓库<br>接下来我们需要将hexo的博客目录里的内容，通过git上传到github仓库<br>然后我们在vercel中点击右边的Add New…<br>然后点击project去拉去我们的github中的仓库<br>然后vercel就会自动部署我们的hexo博客了，在外面部署后，vercel会提示我们的访问链接<br>如果需要通过自己的域名访问，可以在设置中的第二选项里设置</p><h2 id="2、Hexo-Github"><a href="#2、Hexo-Github" class="headerlink" title="2、Hexo+Github"></a>2、Hexo+Github</h2><p>首先我们需要在GitHub创建一个个人仓库</p><p>注册登录github官网，点击右上角加号，点击New repository，新建仓库</p><p>创建一个和用户名相同的仓库,即<a href="http://xxxx.github.io,其中xxx是github的用户名/">http://xxxx.github.io，其中xxx是github的用户名</a></p><p>点击create repository</p><p>Git初始化设置</p><p>接下来我们要在终端中输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git config --global user.name &quot;yourname&quot;   <br>git config --global user.email &quot;youremail&quot;  <br>(yourname是github用户名，youremail是注册github的邮箱)  <br></code></pre></td></tr></table></figure><p>检查是否正确，输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git config user.name<br>git config user.email<br></code></pre></td></tr></table></figure><p>生成SSH添加到GitHub</p><p>输入命令，创建SSH,一路回车</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">ssh-keygen -t rsa -C &quot;youremail&quot;<br></code></pre></td></tr></table></figure><p>查看SSH KEY，输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">cat ~/.ssh/id_rsa.pub<br></code></pre></td></tr></table></figure><p>windows用户请在用户文件夹中寻找，该文件默认隐藏，需打开隐藏文件与文件夹</p><p>然后复制id_rsa.pub里面的全部内容</p><p>在github的setting中，找到SSH keys的设置选项，点击New SSH key，粘贴id_rsa.pub里面的全部内容</p><p>输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">ssh -T git@github.com<br></code></pre></td></tr></table></figure><p>查看是否连接成功</p><p>打开站点配置文件 _config.yml，修改添加以下内容</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">deploy:<br>  type: git<br>  repo: git@github.com:yourgithubname/yourgithubname.github.io.git  <br>  branch: master<br></code></pre></td></tr></table></figure><p>安装deploy-git ，也就是部署的命令,这样才能用命令部署到github</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">npm install hexo-deployer-git --save<br></code></pre></td></tr></table></figure><p>输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">hexo clean 清除缓存<br><br>hexo g = hexo generate 生成静态文件<br><br>hexo deploy 部署网站  <br><br>hexo clean &amp;&amp;　hexo g -d 　这是上面三句的缩写<br></code></pre></td></tr></table></figure><p>然后在github的设置中配置GitHub pages就可以通过一下网址访问到我们的网站了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">http://yourname.github.io<br></code></pre></td></tr></table></figure><p>关联Git仓库</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git clone https://github.com/你的用户名/你的用户名.github.io.git  <br></code></pre></td></tr></table></figure><p>执行之后会在当前目录生成’你的用户名.github.io’的文件夹，这是关联github仓库的文件夹，需要上传的文件都会移动到这里</p><h2 id="3、Hexo-Gitee"><a href="#3、Hexo-Gitee" class="headerlink" title="3、Hexo+Gitee"></a>3、Hexo+Gitee</h2><p>Gitee创建个人仓库</p><p>打开码云官网，注册登陆，创建项目，点击右上角加号，新建仓库<br>开启 Gitee Pages<br>点击启动<br>启动后，点击蓝色链接打开网址<br>初始化Git设置<br>输入命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">git config --global user.name &quot;这里输入你的Gitee注册名&quot;// 按回车  <br>git config --global user.email &quot;这里输你的Gitee邮箱&quot;  <br></code></pre></td></tr></table></figure><p>生成SSH密钥文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">ssh-keygen -t rsa -C &quot;你的Gitee注册邮箱&quot;  <br>// 可不输入，三个回车<br></code></pre></td></tr></table></figure><p>复制粘贴到码云<br>配置 _config.yml<br>点击复制克隆&#x2F;下载里面的https的内容<br>修改添加_config.yml以下内容</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">url: Gitee Pages 服务，网站地址： https://空间名.gitee.io/仓库名(粘贴)  <br>root: /仓库名/<br>wp-block-code<br>deploy:<br>  type: git<br>  repo: https://gitee.com/空间名/仓库名(粘贴)  <br>  branch: master<br></code></pre></td></tr></table></figure><p>基础配置可以参考官方文档的配置说明</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">hexo clean &amp;&amp;　hexo g -d 　缩写 清缓存<br><br>hexo g = hexo generate 生成静态文件<br><br>hexo generate -deploy 生成静态文件后立即部署网站自动上传到gitee  <br>打开Gitee Pages 服务 ，每次上传或改动，都要点击“更新”打开网址访问  <br></code></pre></td></tr></table></figure>]]></content>
    
  </entry>
  
</feed>