<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>wshunli 博客</title>
  
  <subtitle>深自缄默，如云漂泊</subtitle>
  <link href="https://www.wshunli.com/atom.xml" rel="self"/>
  
  <link href="https://www.wshunli.com/"/>
  <updated>2026-06-17T23:42:59.822Z</updated>
  <id>https://www.wshunli.com/</id>
  
  <author>
    <name>wshunli</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>EventBus 源码解析</title>
    <link href="https://www.wshunli.com/posts/a23e91ca.html"/>
    <id>https://www.wshunli.com/posts/a23e91ca.html</id>
    <published>2021-02-27T10:15:10.000Z</published>
    <updated>2026-06-17T23:42:59.822Z</updated>
    
    <content type="html"><![CDATA[<p>EventBus 是一款针对 Android 优化的发布&#x2F;订阅事件总线</p><span id="more"></span><h1 id="一、使用方法"><a href="#一、使用方法" class="headerlink" title="一、使用方法"></a>一、使用方法</h1><p>1、EventBus 的三要素：</p><ul><li><p>事件、任意类型对象</p></li><li><p>事件订阅者、任意 <code>@Subscribe</code> 注解方法</p></li><li><p>事件发布者、EventBus 对象，默认 <code>EventBus.getDefault()</code></p></li></ul><p>2、代码示例</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainActivity</span> <span class="keyword">extends</span> <span class="title class_">Activity</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">(Bundle savedInstanceState)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>.onCreate(savedInstanceState);</span><br><span class="line">        setContentView(R.layout.activity_main);</span><br><span class="line">        <span class="comment">// 1、注册</span></span><br><span class="line">        EventBus.getDefault().register(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 3、接收消息</span></span><br><span class="line">    <span class="meta">@Subscribe(threadMode = ThreadMode.MAIN)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onSubscribeEvent</span><span class="params">(MessageEvent messageEvent)</span> &#123;</span><br><span class="line">        Log.i(TAG, <span class="string">&quot;onSubscribeEvent: &quot;</span> + messageEvent.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onDestroy</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 4、注销</span></span><br><span class="line">        EventBus.getDefault().unregister(<span class="built_in">this</span>);</span><br><span class="line">        <span class="built_in">super</span>.onDestroy();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2、发送消息</span></span><br><span class="line">EventBus.getDefault().post(<span class="keyword">new</span> <span class="title class_">MessageEvent</span>(<span class="string">&quot;hello wshunli&quot;</span>));</span><br></pre></td></tr></table></figure><p>3、EventBus 的 ThreadMode（线程模型）</p><ul><li>POSTING、和发送同一线程</li><li>MAIN、主线程</li><li>MAIN_ORDERED、主线程顺序执行</li><li>BACKGROUND、非主线程；如果主线程发出，在子线程执行，如果子线程发出，在相同线程执行</li><li>ASYNC、无论从什么线程发出，总是在<code>新建</code>子线程执行</li></ul><p>4、粘性事件（先发送，后注册接收）</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">EventBus.getDefault().postSticky(<span class="keyword">new</span> <span class="title class_">MessageEvent</span>(<span class="string">&quot;粘性事件&quot;</span>));</span><br><span class="line"><span class="meta">@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onSubscribeStickyEvent</span><span class="params">(MessageEvent messageEvent)</span> &#123;</span><br><span class="line">    Log.i(TAG, <span class="string">&quot;onSubscribeStickyEvent: &quot;</span> + messageEvent.getMessage());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight groovy"><table><tr><td class="code"><pre><span class="line">implementation <span class="string">&#x27;org.greenrobot:eventbus:3.2.0&#x27;</span></span><br></pre></td></tr></table></figure><h1 id="二、源码分析"><a href="#二、源码分析" class="headerlink" title="二、源码分析"></a>二、源码分析</h1><p>2.1、EventBus 构造</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EventBus</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">volatile</span> EventBus defaultInstance;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">EventBusBuilder</span> <span class="variable">DEFAULT_BUILDER</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">EventBusBuilder</span>();</span><br><span class="line">    <span class="comment">// 默认实例，单例、双重校验锁</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> EventBus <span class="title function_">getDefault</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">EventBus</span> <span class="variable">instance</span> <span class="operator">=</span> defaultInstance;</span><br><span class="line">        <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (EventBus.class) &#123;</span><br><span class="line">                instance = EventBus.defaultInstance;</span><br><span class="line">                <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;</span><br><span class="line">                    instance = EventBus.defaultInstance = <span class="keyword">new</span> <span class="title class_">EventBus</span>();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 构造方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">EventBus</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>(DEFAULT_BUILDER);</span><br><span class="line">    &#125;</span><br><span class="line">    EventBus(EventBusBuilder builder) &#123;</span><br><span class="line">        <span class="comment">// ***</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>EventBus 对象之间，订阅方法是隔离的，一般使用 <code>getDefault()</code>即可。</p><p>2.2、注册订阅</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// EventBus</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> SubscriberMethodFinder subscriberMethodFinder;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(Object subscriber)</span> &#123;</span><br><span class="line">    Class&lt;?&gt; subscriberClass = subscriber.getClass();</span><br><span class="line">    <span class="comment">// （1）获取订阅方法</span></span><br><span class="line">    List&lt;SubscriberMethod&gt; subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);</span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (SubscriberMethod subscriberMethod : subscriberMethods) &#123;</span><br><span class="line">            <span class="comment">// （2）订阅方法注册</span></span><br><span class="line">            subscribe(subscriber, subscriberMethod);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（1）查找订阅方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// SubscriberMethodFinder</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Map&lt;Class&lt;?&gt;, List&lt;SubscriberMethod&gt;&gt; METHOD_CACHE = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">private</span> List&lt;SubscriberInfoIndex&gt; subscriberInfoIndexes;</span><br><span class="line">List&lt;SubscriberMethod&gt; <span class="title function_">findSubscriberMethods</span><span class="params">(Class&lt;?&gt; subscriberClass)</span> &#123;</span><br><span class="line">    <span class="comment">// 从缓存中获取，订阅方法</span></span><br><span class="line">    List&lt;SubscriberMethod&gt; subscriberMethods = METHOD_CACHE.get(subscriberClass);</span><br><span class="line">    <span class="keyword">if</span> (subscriberMethods != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> subscriberMethods;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 是否忽略订阅信息索引</span></span><br><span class="line">    <span class="keyword">if</span> (ignoreGeneratedIndex) &#123;</span><br><span class="line">        <span class="comment">// 通过反射</span></span><br><span class="line">        subscriberMethods = findUsingReflection(subscriberClass);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 通过索引</span></span><br><span class="line">        subscriberMethods = findUsingInfo(subscriberClass);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (subscriberMethods.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">EventBusException</span>(<span class="string">&quot;Subscriber &quot;</span> + subscriberClass</span><br><span class="line">                                    + <span class="string">&quot; and its super classes have no public methods with the @Subscribe annotation&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        METHOD_CACHE.put(subscriberClass, subscriberMethods);</span><br><span class="line">        <span class="keyword">return</span> subscriberMethods;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Eventbus 支持 通过在编译期创建索引（SubscriberInfoIndex）以提高程序运行性能</p><p><a href="https://greenrobot.org/eventbus/documentation/subscriber-index/">https://greenrobot.org/eventbus/documentation/subscriber-index/</a></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用方法，编译时生成 MySubscriberInfoIndex 类</span></span><br><span class="line">EventBus.builder().addIndex(<span class="keyword">new</span> <span class="title class_">MySubscriberInfoIndex</span>()).installDefaultEventBus();</span><br><span class="line"></span><br><span class="line"><span class="comment">// EventBusBuilder,添加索引信息</span></span><br><span class="line">List&lt;SubscriberInfoIndex&gt; subscriberInfoIndexes;</span><br><span class="line"><span class="keyword">public</span> EventBusBuilder <span class="title function_">addIndex</span><span class="params">(SubscriberInfoIndex index)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (subscriberInfoIndexes == <span class="literal">null</span>) &#123;</span><br><span class="line">        subscriberInfoIndexes = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">    subscriberInfoIndexes.add(index);</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// EventBus 构造时存入 SubscriberMethodFinder</span></span><br><span class="line">EventBus(EventBusBuilder builder) &#123;</span><br><span class="line">    <span class="comment">//***</span></span><br><span class="line">    subscriberMethodFinder = <span class="keyword">new</span> <span class="title class_">SubscriberMethodFinder</span>(</span><br><span class="line">        builder.subscriberInfoIndexes,</span><br><span class="line">        builder.strictMethodVerification, </span><br><span class="line">        builder.ignoreGeneratedIndex);</span><br><span class="line">    <span class="comment">//***</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// SubscriberMethodFinder 获取订阅方法</span></span><br><span class="line"><span class="keyword">private</span> List&lt;SubscriberMethod&gt; <span class="title function_">findUsingInfo</span><span class="params">(Class&lt;?&gt; subscriberClass)</span> &#123;</span><br><span class="line">    <span class="type">FindState</span> <span class="variable">findState</span> <span class="operator">=</span> prepareFindState();</span><br><span class="line">    findState.initForSubscriber(subscriberClass);</span><br><span class="line">    <span class="keyword">while</span> (findState.clazz != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 从索引中中获取 SubscriberInfo </span></span><br><span class="line">        findState.subscriberInfo = getSubscriberInfo(findState);</span><br><span class="line">        <span class="keyword">if</span> (findState.subscriberInfo != <span class="literal">null</span>) &#123;</span><br><span class="line">            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();</span><br><span class="line">            <span class="keyword">for</span> (SubscriberMethod subscriberMethod : array) &#123;</span><br><span class="line">                <span class="keyword">if</span> (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) &#123;</span><br><span class="line">                    findState.subscriberMethods.add(subscriberMethod);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 索引中没有，反射获取</span></span><br><span class="line">            findUsingReflectionInSingleClass(findState);</span><br><span class="line">        &#125;</span><br><span class="line">        findState.moveToSuperclass();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> getMethodsAndRelease(findState);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> SubscriberInfo <span class="title function_">getSubscriberInfo</span><span class="params">(FindState findState)</span> &#123;</span><br><span class="line">    <span class="comment">// ***</span></span><br><span class="line">    <span class="comment">// 构造时传入 subscriberInfoIndexes 对象</span></span><br><span class="line">    <span class="keyword">if</span> (subscriberInfoIndexes != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (SubscriberInfoIndex index : subscriberInfoIndexes) &#123;</span><br><span class="line">            <span class="type">SubscriberInfo</span> <span class="variable">info</span> <span class="operator">=</span> index.getSubscriberInfo(findState.clazz);</span><br><span class="line">            <span class="keyword">if</span> (info != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> info;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>反射方式比较简单，不再粘贴代码。</p><p>（2）订阅方法注册</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 事件 =&gt; Subscription 列表</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map&lt;Class&lt;?&gt;, CopyOnWriteArrayList&lt;Subscription&gt;&gt; subscriptionsByEventType;</span><br><span class="line"><span class="comment">// 订阅者 =&gt; 事件列表</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map&lt;Object, List&lt;Class&lt;?&gt;&gt;&gt; typesBySubscriber;</span><br><span class="line"><span class="comment">// 存储粘性事件</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map&lt;Class&lt;?&gt;, Object&gt; stickyEvents; </span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">subscribe</span><span class="params">(Object subscriber, SubscriberMethod subscriberMethod)</span> &#123;</span><br><span class="line">    <span class="comment">// 事件，eventType 订阅方法参数类型</span></span><br><span class="line">    Class&lt;?&gt; eventType = subscriberMethod.eventType;</span><br><span class="line">    <span class="type">Subscription</span> <span class="variable">newSubscription</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Subscription</span>(subscriber, subscriberMethod);</span><br><span class="line">    <span class="comment">// 事件对应的 Subscription 列表</span></span><br><span class="line">    CopyOnWriteArrayList&lt;Subscription&gt; subscriptions = subscriptionsByEventType.get(eventType);</span><br><span class="line">    <span class="comment">// ***</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> subscriptions.size();</span><br><span class="line">    <span class="comment">// 遍历，根据优先级插入</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= size; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (i == size || subscriberMethod.priority &gt; subscriptions.get(i).subscriberMethod.priority) &#123;</span><br><span class="line">            subscriptions.add(i, newSubscription);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 存储订阅者及对应的事件列表</span></span><br><span class="line">    List&lt;Class&lt;?&gt;&gt; subscribedEvents = typesBySubscriber.get(subscriber);</span><br><span class="line">    <span class="keyword">if</span> (subscribedEvents == <span class="literal">null</span>) &#123;</span><br><span class="line">        subscribedEvents = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">        typesBySubscriber.put(subscriber, subscribedEvents);</span><br><span class="line">    &#125;</span><br><span class="line">    subscribedEvents.add(eventType);</span><br><span class="line">    <span class="comment">// 粘性事件处理</span></span><br><span class="line">    <span class="keyword">if</span> (subscriberMethod.sticky) &#123;</span><br><span class="line">        <span class="keyword">if</span> (eventInheritance) &#123;</span><br><span class="line">            Set&lt;Map.Entry&lt;Class&lt;?&gt;, Object&gt;&gt; entries = stickyEvents.entrySet();</span><br><span class="line">            <span class="keyword">for</span> (Map.Entry&lt;Class&lt;?&gt;, Object&gt; entry : entries) &#123;</span><br><span class="line">                Class&lt;?&gt; candidateEventType = entry.getKey();</span><br><span class="line">                <span class="keyword">if</span> (eventType.isAssignableFrom(candidateEventType)) &#123;</span><br><span class="line">                    <span class="type">Object</span> <span class="variable">stickyEvent</span> <span class="operator">=</span> entry.getValue();</span><br><span class="line">                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">Object</span> <span class="variable">stickyEvent</span> <span class="operator">=</span> stickyEvents.get(eventType);</span><br><span class="line">            checkPostStickyEventToSubscription(newSubscription, stickyEvent);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2.3、事件的发送、处理</p><p>（1）普通事件</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// EventBus</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ThreadLocal&lt;PostingThreadState&gt; currentPostingThreadState = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;PostingThreadState&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> PostingThreadState <span class="title function_">initialValue</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PostingThreadState</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// PostingThreadState</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">PostingThreadState</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> List&lt;Object&gt; eventQueue = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="type">boolean</span> isPosting;</span><br><span class="line">    <span class="type">boolean</span> isMainThread;</span><br><span class="line">    Subscription subscription;</span><br><span class="line">    Object event;</span><br><span class="line">    <span class="type">boolean</span> canceled;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 发送普通事件</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">post</span><span class="params">(Object event)</span> &#123;</span><br><span class="line">    <span class="comment">// 当前线程 发送状态</span></span><br><span class="line">    <span class="type">PostingThreadState</span> <span class="variable">postingState</span> <span class="operator">=</span> currentPostingThreadState.get();</span><br><span class="line">    List&lt;Object&gt; eventQueue = postingState.eventQueue;</span><br><span class="line">    <span class="comment">// 添加至事件队列</span></span><br><span class="line">    eventQueue.add(event);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!postingState.isPosting) &#123;</span><br><span class="line">        postingState.isMainThread = isMainThread();</span><br><span class="line">        postingState.isPosting = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (postingState.canceled) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">EventBusException</span>(<span class="string">&quot;Internal error. Abort state was not reset&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (!eventQueue.isEmpty()) &#123;</span><br><span class="line">                <span class="comment">// 发送方法</span></span><br><span class="line">                postSingleEvent(eventQueue.remove(<span class="number">0</span>), postingState);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            postingState.isPosting = <span class="literal">false</span>;</span><br><span class="line">            postingState.isMainThread = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">postSingleEvent</span><span class="params">(Object event, PostingThreadState postingState)</span> <span class="keyword">throws</span> Error &#123;</span><br><span class="line">    <span class="comment">// ***</span></span><br><span class="line">    subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);</span><br><span class="line">    <span class="comment">// ***</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">postSingleEventForEventType</span><span class="params">(Object event, PostingThreadState postingState, Class&lt;?&gt; eventClass)</span> &#123;</span><br><span class="line">    CopyOnWriteArrayList&lt;Subscription&gt; subscriptions;</span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">        <span class="comment">// 获取事件对应的 subscriptions</span></span><br><span class="line">        subscriptions = subscriptionsByEventType.get(eventClass);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (subscriptions != <span class="literal">null</span> &amp;&amp; !subscriptions.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">for</span> (Subscription subscription : subscriptions) &#123;</span><br><span class="line">            postingState.event = event;</span><br><span class="line">            postingState.subscription = subscription;</span><br><span class="line">            <span class="type">boolean</span> aborted;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 发送方法</span></span><br><span class="line">                postToSubscription(subscription, event, postingState.isMainThread);</span><br><span class="line">                aborted = postingState.canceled;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                postingState.event = <span class="literal">null</span>;</span><br><span class="line">                postingState.subscription = <span class="literal">null</span>;</span><br><span class="line">                postingState.canceled = <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (aborted) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">postToSubscription</span><span class="params">(Subscription subscription, Object event, <span class="type">boolean</span> isMainThread)</span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> (subscription.subscriberMethod.threadMode) &#123;</span><br><span class="line">            <span class="comment">// 当前线程处理</span></span><br><span class="line">        <span class="keyword">case</span> POSTING:</span><br><span class="line">            invokeSubscriber(subscription, event);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="comment">// 主线程处理</span></span><br><span class="line">        <span class="keyword">case</span> MAIN:</span><br><span class="line">            <span class="keyword">if</span> (isMainThread) &#123;</span><br><span class="line">                invokeSubscriber(subscription, event);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// Handler 发送消息</span></span><br><span class="line">                mainThreadPoster.enqueue(subscription, event);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="comment">// 主线程顺序</span></span><br><span class="line">        <span class="keyword">case</span> MAIN_ORDERED:</span><br><span class="line">            <span class="keyword">if</span> (mainThreadPoster != <span class="literal">null</span>) &#123;</span><br><span class="line">                mainThreadPoster.enqueue(subscription, event);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                invokeSubscriber(subscription, event);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="comment">// 子线程</span></span><br><span class="line">        <span class="keyword">case</span> BACKGROUND:</span><br><span class="line">            <span class="keyword">if</span> (isMainThread) &#123;</span><br><span class="line">                backgroundPoster.enqueue(subscription, event);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                invokeSubscriber(subscription, event);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="comment">// 在新建子线程</span></span><br><span class="line">        <span class="keyword">case</span> ASYNC:</span><br><span class="line">            asyncPoster.enqueue(subscription, event);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Unknown thread mode: &quot;</span> + subscription.subscriberMethod.threadMode);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 调用订阅方法</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">invokeSubscriber</span><span class="params">(Subscription subscription, Object event)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        subscription.subscriberMethod.method.invoke(subscription.subscriber, event);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InvocationTargetException e) &#123;</span><br><span class="line">        handleSubscriberException(subscription, event, e.getCause());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IllegalAccessException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Unexpected exception&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（2）粘性事件</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postSticky</span><span class="params">(Object event)</span> &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (stickyEvents) &#123;</span><br><span class="line">        stickyEvents.put(event.getClass(), event);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 普通事件</span></span><br><span class="line">    post(event);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>粘性事件仅将事件存入 stickyEvents 对象，真正调用在注册方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// EventBus#subscribe</span></span><br><span class="line"><span class="keyword">if</span> (subscriberMethod.sticky) &#123;</span><br><span class="line">    <span class="keyword">if</span> (eventInheritance) &#123;</span><br><span class="line">        Set&lt;Map.Entry&lt;Class&lt;?&gt;, Object&gt;&gt; entries = stickyEvents.entrySet();</span><br><span class="line">        <span class="keyword">for</span> (Map.Entry&lt;Class&lt;?&gt;, Object&gt; entry : entries) &#123;</span><br><span class="line">            Class&lt;?&gt; candidateEventType = entry.getKey();</span><br><span class="line">            <span class="keyword">if</span> (eventType.isAssignableFrom(candidateEventType)) &#123;</span><br><span class="line">                <span class="type">Object</span> <span class="variable">stickyEvent</span> <span class="operator">=</span> entry.getValue();</span><br><span class="line">                checkPostStickyEventToSubscription(newSubscription, stickyEvent);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">stickyEvent</span> <span class="operator">=</span> stickyEvents.get(eventType);</span><br><span class="line">        checkPostStickyEventToSubscription(newSubscription, stickyEvent);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">checkPostStickyEventToSubscription</span><span class="params">(Subscription newSubscription, Object stickyEvent)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (stickyEvent != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 普通事件相同</span></span><br><span class="line">        postToSubscription(newSubscription, stickyEvent, isMainThread());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2.4、注销订阅</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">unregister</span><span class="params">(Object subscriber)</span> &#123;</span><br><span class="line">    List&lt;Class&lt;?&gt;&gt; subscribedTypes = typesBySubscriber.get(subscriber);</span><br><span class="line">    <span class="keyword">if</span> (subscribedTypes != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (Class&lt;?&gt; eventType : subscribedTypes) &#123;</span><br><span class="line">            unsubscribeByEventType(subscriber, eventType);</span><br><span class="line">        &#125;</span><br><span class="line">        typesBySubscriber.remove(subscriber);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        logger.log(Level.WARNING, <span class="string">&quot;Subscriber to unregister was not registered before: &quot;</span> + subscriber.getClass());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将订阅者、订阅方法从列表中移除</p><h1 id="三、总结"><a href="#三、总结" class="headerlink" title="三、总结"></a>三、总结</h1><p>优点，组件间通信解耦，预编译生成效率较高</p><p>缺点，代码可读性变差，逻辑分散</p><p>源码解析：</p><ul><li><p>Android事件总线（一）EventBus3.0用法全解析 | BATcoder - 刘望舒：<a href="http://liuwangshu.cn/application/eventbus/1-eventbus.html">http://liuwangshu.cn/application/eventbus/1-eventbus.html</a></p></li><li><p>Android事件总线（二）EventBus3.0源码解析 | 刘望舒的博客：<a href="http://liuwangshu.cn/application/eventbus/2-eventbus-sourcecode.html">http://liuwangshu.cn/application/eventbus/2-eventbus-sourcecode.html</a></p></li><li><p>Android主流三方库源码分析（九、深入理解EventBus源码） | Deep into Android：<a href="https://jsonchao.github.io/2019/01/28/Android%E4%B8%BB%E6%B5%81%E4%B8%89%E6%96%B9%E5%BA%93%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%EF%BC%88%E4%B9%9D%E3%80%81%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3EventBus%E6%BA%90%E7%A0%81%EF%BC%89/">链接</a></p></li><li><p>【Bugly干货分享】老司机教你 “飙” EventBus 3 - 腾讯bugly - 博客园：<a href="https://www.cnblogs.com/bugly/p/5475034.html">https://www.cnblogs.com/bugly/p/5475034.html</a></p></li><li><p>EventBus3.0 性能提升之添加索引_zhangke-CSDN博客：<a href="https://blog.csdn.net/kaifa1321/article/details/79761507">https://blog.csdn.net/kaifa1321/article/details/79761507</a></p></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;EventBus 是一款针对 Android 优化的发布&amp;#x2F;订阅事件总线&lt;/p&gt;</summary>
    
    
    
    <category term="源码解析" scheme="https://www.wshunli.com/categories/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    
    <category term="Android" scheme="https://www.wshunli.com/tags/Android/"/>
    
    <category term="EventBus" scheme="https://www.wshunli.com/tags/EventBus/"/>
    
    <category term="网络框架" scheme="https://www.wshunli.com/tags/%E7%BD%91%E7%BB%9C%E6%A1%86%E6%9E%B6/"/>
    
    <category term="源码解析" scheme="https://www.wshunli.com/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
  </entry>
  
  <entry>
    <title>Python 语言入门</title>
    <link href="https://www.wshunli.com/posts/fa771b3.html"/>
    <id>https://www.wshunli.com/posts/fa771b3.html</id>
    <published>2019-06-23T08:03:51.000Z</published>
    <updated>2026-06-17T23:42:59.824Z</updated>
    
    <content type="html"><![CDATA[<p>Python 在各领域的应用已经非常广泛了，尤其是最近热门的人工智能等领域。</p><p>现在开始学习 Python 语言基础，后面利用强大的工具库，想必事半功倍。</p><span id="more"></span><h2 id="Python-基础语法"><a href="#Python-基础语法" class="headerlink" title="Python 基础语法"></a>Python 基础语法</h2><p>1、Python 的注释：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 单行注释</span></span><br><span class="line"><span class="built_in">print</span> (<span class="string">&quot;Hello, Python!&quot;</span>) <span class="comment"># 单行注释</span></span><br><span class="line"></span><br><span class="line"><span class="string">&#x27;&#x27;&#x27;</span></span><br><span class="line"><span class="string">多行注释</span></span><br><span class="line"><span class="string">多行注释</span></span><br><span class="line"><span class="string">&#x27;&#x27;&#x27;</span></span><br><span class="line"> </span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">多行注释</span></span><br><span class="line"><span class="string">多行注释</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br></pre></td></tr></table></figure><p>单行注释使用 <code>#</code> 号，多行注释使用 <code>&#39;&#39;&#39;</code> 和 <code>&quot;&quot;&quot;</code>。</p><p>2、Python 的行与缩进</p><p>Python 中使用缩进表示代码块，不使用 <code>{}</code>。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="built_in">print</span> (<span class="string">&quot;True&quot;</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    <span class="built_in">print</span> (<span class="string">&quot;False&quot;</span>)</span><br></pre></td></tr></table></figure><p>如果缩进不一致，会导致错误。</p><p>Python 中一条语句换行需要使用反斜杠（<code>\</code>）表示：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">total = item_one + \</span><br><span class="line">        item_two + \</span><br><span class="line">        item_three</span><br></pre></td></tr></table></figure><p>3、end 关键字</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Fibonacci series: 斐波纳契数列</span></span><br><span class="line">a, b = <span class="number">0</span>, <span class="number">1</span></span><br><span class="line"><span class="keyword">while</span> b &lt; <span class="number">1000</span>:</span><br><span class="line">    <span class="built_in">print</span>(b, end=<span class="string">&#x27;,&#x27;</span>)</span><br><span class="line">    a, b = b, a+b</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,</span></span><br></pre></td></tr></table></figure><p>关键字 end 可以用于将结果输出到同一行，或者在末尾添加不同的字符。</p><h2 id="Python-基本数据类型"><a href="#Python-基本数据类型" class="headerlink" title="Python 基本数据类型"></a>Python 基本数据类型</h2><p>Python 中的变量不需要声明。</p><p>Python3 中有六个标准的数据类型：</p><p>Number（数字）、String（字符串）、Tuple（元组）、List（列表）、Set（集合）、Dictionary（字典）。</p><p>其中前三个为不可变数据类型，后三个为可变数据类型。</p><p>1、Number（数字）：Python3 支持 int、float、bool、complex（复数）。</p><p>其中 在 Python3 中 True 和 False 为关键字，值分别为 1 和 0，可以和数字相加。</p><p>2、String（字符串）：使用单引号 <code>&#39;</code> 或双引号 <code>&quot;</code> 括起来，使用反斜杠 <code>\</code> 转义特殊字符。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="built_in">str</span> = <span class="string">&#x27;wshunli&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">str</span>)           <span class="comment"># 输出字符串</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">str</span>[<span class="number">0</span>])        <span class="comment"># 输出字符串第一个字符</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">str</span>[<span class="number">2</span>:<span class="number">4</span>])      <span class="comment"># 输出第三个开始到第四个的字符</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">str</span>[<span class="number">0</span>:-<span class="number">1</span>])     <span class="comment"># 输出第一个到倒数第二个的所有字符</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">str</span>[<span class="number">2</span>:])       <span class="comment"># 输出从第三个开始的后的所有字符</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">str</span> * <span class="number">2</span>)       <span class="comment"># 输出字符串两次</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">str</span> + <span class="string">&quot;.com&quot;</span>)  <span class="comment"># 连接字符串</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># wshunli</span></span><br><span class="line"><span class="comment"># w</span></span><br><span class="line"><span class="comment"># hu</span></span><br><span class="line"><span class="comment"># wshunl</span></span><br><span class="line"><span class="comment"># hunli</span></span><br><span class="line"><span class="comment"># wshunliwshunli</span></span><br><span class="line"><span class="comment"># wshunli.com</span></span><br></pre></td></tr></table></figure><p>Python 三引号：一个字符串跨多行。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">para_str = <span class="string">&quot;&quot;&quot;这是一个多行字符串的实例</span></span><br><span class="line"><span class="string">多行字符串可以使用制表符</span></span><br><span class="line"><span class="string">TAB ( \t )。</span></span><br><span class="line"><span class="string">也可以使用换行符 [ \n ]。</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="built_in">print</span>(para_str)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 这是一个多行字符串的实例</span></span><br><span class="line"><span class="comment"># 多行字符串可以使用制表符</span></span><br><span class="line"><span class="comment"># TAB (  )。</span></span><br><span class="line"><span class="comment"># 也可以使用换行符 [ </span></span><br><span class="line"><span class="comment">#  ]。</span></span><br></pre></td></tr></table></figure><p>3、List（列表）：列表可以完成大多数集合类的数据结构实现。方括号 <code>[]</code> 表示。</p><p>列表中元素的类型可以不同，支持数字，字符串以及列表（嵌套）。</p><p>列表的索引和截取和字符串类似；但是支持第3个参数表示截取步长。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">letters = [<span class="string">&#x27;w&#x27;</span>,<span class="string">&#x27;s&#x27;</span>,<span class="string">&#x27;h&#x27;</span>,<span class="string">&#x27;u&#x27;</span>,<span class="string">&#x27;n&#x27;</span>,<span class="string">&#x27;l&#x27;</span>,<span class="string">&#x27;i&#x27;</span>]</span><br><span class="line">letters[<span class="number">1</span>:<span class="number">4</span>:<span class="number">2</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># [&#x27;s&#x27;,&#x27;u&#x27;]</span></span><br></pre></td></tr></table></figure><p>4、Tuple（元组）：元素不能修改的列表。小括号 <code>()</code> 表示。</p><p>虽然 Tuple 的元素不可改变，但可以包含可变的对象，比如 List 列表。</p><p>String、List 和 Tuple 都属于 Sequence（序列）。</p><p>5、Set（集合）：集合元素之间不相同、不可变、无序。大括号 <code>{}</code> 表示。</p><p>集合运算：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">a = <span class="built_in">set</span>(<span class="string">&#x27;abracadabra&#x27;</span>)</span><br><span class="line">b = <span class="built_in">set</span>(<span class="string">&#x27;alacazam&#x27;</span>)</span><br><span class="line"> </span><br><span class="line"><span class="built_in">print</span>(a)</span><br><span class="line"><span class="built_in">print</span>(a - b)     <span class="comment"># a 和 b 的差集</span></span><br><span class="line"><span class="built_in">print</span>(a | b)     <span class="comment"># a 和 b 的并集</span></span><br><span class="line"><span class="built_in">print</span>(a &amp; b)     <span class="comment"># a 和 b 的交集</span></span><br><span class="line"><span class="built_in">print</span>(a ^ b)     <span class="comment"># a 和 b 中不同时存在的元素</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># &#123;&#x27;b&#x27;, &#x27;a&#x27;, &#x27;c&#x27;, &#x27;r&#x27;, &#x27;d&#x27;&#125;</span></span><br><span class="line"><span class="comment"># &#123;&#x27;b&#x27;, &#x27;d&#x27;, &#x27;r&#x27;&#125;</span></span><br><span class="line"><span class="comment"># &#123;&#x27;l&#x27;, &#x27;r&#x27;, &#x27;a&#x27;, &#x27;c&#x27;, &#x27;z&#x27;, &#x27;m&#x27;, &#x27;b&#x27;, &#x27;d&#x27;&#125;</span></span><br><span class="line"><span class="comment"># &#123;&#x27;a&#x27;, &#x27;c&#x27;&#125;</span></span><br><span class="line"><span class="comment"># &#123;&#x27;l&#x27;, &#x27;r&#x27;, &#x27;z&#x27;, &#x27;m&#x27;, &#x27;b&#x27;, &#x27;d&#x27;&#125;</span></span><br></pre></td></tr></table></figure><p>6、Dictionary（字典）：无序的 <code>键(key) : 值(value)</code> 的集合。大括号 <code>{}</code> 表示。</p><p>其中 键(key) 为不可变类型。在同一个字典中，键(key) 必须是唯一的。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">tinydict = &#123;<span class="string">&#x27;name&#x27;</span>: <span class="string">&#x27;wshunli&#x27;</span>, <span class="number">2</span>: <span class="number">1</span>, <span class="string">&#x27;site&#x27;</span>: <span class="string">&#x27;wshunli.com&#x27;</span>&#125;</span><br><span class="line"> </span><br><span class="line"><span class="built_in">print</span> (tinydict)          <span class="comment"># 输出完整的字典</span></span><br><span class="line"><span class="built_in">print</span> (tinydict[<span class="string">&#x27;name&#x27;</span>])  <span class="comment"># 输出键为 &#x27;one&#x27; 的值</span></span><br><span class="line"><span class="built_in">print</span> (tinydict[<span class="number">2</span>])       <span class="comment"># 输出键为 2 的值</span></span><br><span class="line"><span class="built_in">print</span> (tinydict.keys())   <span class="comment"># 输出所有键</span></span><br><span class="line"><span class="built_in">print</span> (tinydict.values()) <span class="comment"># 输出所有值</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># &#123;&#x27;name&#x27;: &#x27;wshunli&#x27;, 2: 1, &#x27;site&#x27;: &#x27;wshunli.com&#x27;&#125;</span></span><br><span class="line"><span class="comment"># wshunli</span></span><br><span class="line"><span class="comment"># 1</span></span><br><span class="line"><span class="comment"># dict_keys([&#x27;name&#x27;, 2, &#x27;site&#x27;])</span></span><br><span class="line"><span class="comment"># dict_values([&#x27;wshunli&#x27;, 1, &#x27;wshunli.com&#x27;])</span></span><br></pre></td></tr></table></figure><p>基本数据类型的创建：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="built_in">list</span> = []    <span class="comment"># 构造 0 个的列表</span></span><br><span class="line">tup1 = ()    <span class="comment"># 构造 0 个的元组</span></span><br><span class="line">tup2 = (<span class="number">20</span>,) <span class="comment"># 构造 1 个的元组</span></span><br><span class="line"><span class="built_in">set</span> = <span class="built_in">set</span>()  <span class="comment"># 构造 0 个的集合</span></span><br><span class="line">dirt = []    <span class="comment"># 构造 0 个的字典</span></span><br></pre></td></tr></table></figure><h2 id="Python-运算符"><a href="#Python-运算符" class="headerlink" title="Python 运算符"></a>Python 运算符</h2><p>（1）Python 算术运算符：+、-、*、&#x2F;、%、**、&#x2F;&#x2F;</p><p><code>**</code>幂 - 返回 x 的 y 次幂</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">a**b       <span class="comment"># a 的 b 次方</span></span><br></pre></td></tr></table></figure><p><code>//</code>取整除 - 向下取接近除数的整数</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">&gt;&gt;&gt; 9//2   <span class="comment"># 4</span></span><br><span class="line">&gt;&gt;&gt; -9//2  <span class="comment"># -5</span></span><br></pre></td></tr></table></figure><p>（2）Python 比较运算符：&#x3D;&#x3D;、!&#x3D;、&gt;、&gt;&#x3D;、&lt;、&lt;&#x3D;</p><p>（3）Python 赋值运算符：&#x3D;、+&#x3D;、-&#x3D;、*&#x3D;、&#x2F;&#x3D;、%&#x3D;、**、&#x2F;&#x2F;&#x3D;</p><p>（4）Python 位运算符：&amp;、|、^、~、&lt;&lt;、&gt;&gt;</p><p>（5）Python 逻辑运算符：and、or、not</p><p>（6）Python 成员运算符：in、not in</p><p>（7）Python 身份运算符：is、is not</p><p>is 是判断两个标识符是不是引用自一个对象；</p><p>is not 是判断两个标识符是不是引用自不同对象</p><h2 id="Python-条件控制"><a href="#Python-条件控制" class="headerlink" title="Python 条件控制"></a>Python 条件控制</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> condition_1:</span><br><span class="line">    statement_block_1</span><br><span class="line"><span class="keyword">elif</span> condition_2:</span><br><span class="line">    statement_block_2</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    statement_block_3</span><br></pre></td></tr></table></figure><p>在 Python 中没有 <code>switch – case</code> 语句。</p><h2 id="Python-循环语句"><a href="#Python-循环语句" class="headerlink" title="Python 循环语句"></a>Python 循环语句</h2><p>1、While 循环</p><p>在 <code>while … else</code> 在条件语句为 <code>false</code> 时执行 <code>else</code> 的语句块：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">count = <span class="number">0</span></span><br><span class="line"><span class="keyword">while</span> count &lt; <span class="number">3</span>:</span><br><span class="line">   <span class="built_in">print</span> (count, <span class="string">&quot; 小于 3&quot;</span>)</span><br><span class="line">   count = count + <span class="number">1</span></span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">   <span class="built_in">print</span> (count, <span class="string">&quot; 大于或等于 3&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 0  小于 3</span></span><br><span class="line"><span class="comment"># 1  小于 3</span></span><br><span class="line"><span class="comment"># 2  小于 3</span></span><br><span class="line"><span class="comment"># 3  大于或等于 3</span></span><br></pre></td></tr></table></figure><p>2、For 循环：遍历任何序列的项目，如列表、字符串。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> &lt;variable&gt; <span class="keyword">in</span> &lt;sequence&gt;:</span><br><span class="line">    &lt;statements&gt;</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    &lt;statements&gt;</span><br></pre></td></tr></table></figure><p>3、range() 函数</p><p>range() 函数用于生成数列：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">5</span>):</span><br><span class="line">    <span class="built_in">print</span>(i, end=<span class="string">&#x27;,&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">5</span>, <span class="number">9</span>):</span><br><span class="line">    <span class="built_in">print</span>(i, end=<span class="string">&#x27;,&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>, <span class="number">10</span>, <span class="number">3</span>):</span><br><span class="line">    <span class="built_in">print</span>(i, end=<span class="string">&#x27;,&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(-<span class="number">1</span>, -<span class="number">10</span>, -<span class="number">3</span>):</span><br><span class="line">    <span class="built_in">print</span>(i, end=<span class="string">&#x27;,&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">list</span>(<span class="built_in">range</span>(<span class="number">5</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 0,1,2,3,4,</span></span><br><span class="line"><span class="comment"># 5,6,7,8,</span></span><br><span class="line"><span class="comment"># 0,3,6,9,</span></span><br><span class="line"><span class="comment"># -1,-4,-7,</span></span><br><span class="line"><span class="comment"># [0, 1, 2, 3, 4]</span></span><br></pre></td></tr></table></figure><p>结合 range() 和 len() 函数遍历一个序列的索引：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">a = [<span class="string">&#x27;Google&#x27;</span>, <span class="string">&#x27;Baidu&#x27;</span>, <span class="string">&#x27;Runoob&#x27;</span>, <span class="string">&#x27;Taobao&#x27;</span>, <span class="string">&#x27;QQ&#x27;</span>]</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(a)):</span><br><span class="line">     <span class="built_in">print</span>(i, a[i])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 0 Google</span></span><br><span class="line"><span class="comment"># 1 Baidu</span></span><br><span class="line"><span class="comment"># 2 Runoob</span></span><br><span class="line"><span class="comment"># 3 Taobao</span></span><br><span class="line"><span class="comment"># 4 QQ</span></span><br></pre></td></tr></table></figure><p>4、pass 语句</p><p>Python pass是空语句，是为了保持程序结构的完整性。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="keyword">pass</span>  <span class="comment"># 等待键盘中断 (Ctrl+C)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> letter <span class="keyword">in</span> <span class="string">&#x27;wshunli&#x27;</span>:</span><br><span class="line">    <span class="keyword">if</span> letter == <span class="string">&#x27;n&#x27;</span>:</span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;执行 pass 块&#x27;</span>)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;当前字母 :&#x27;</span>, letter)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Good bye!&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 当前字母 : w</span></span><br><span class="line"><span class="comment"># 当前字母 : s</span></span><br><span class="line"><span class="comment"># 当前字母 : h</span></span><br><span class="line"><span class="comment"># 当前字母 : u</span></span><br><span class="line"><span class="comment"># 执行 pass 块</span></span><br><span class="line"><span class="comment"># 当前字母 : n</span></span><br><span class="line"><span class="comment"># 当前字母 : l</span></span><br><span class="line"><span class="comment"># 当前字母 : i</span></span><br><span class="line"><span class="comment"># Good bye!</span></span><br></pre></td></tr></table></figure><h2 id="Python-迭代器与生成器"><a href="#Python-迭代器与生成器" class="headerlink" title="Python 迭代器与生成器"></a>Python 迭代器与生成器</h2><p>1、迭代器：用于遍历集合元素。</p><p>（1）迭代器的使用</p><p>迭代器有两个基本的方法：<code>iter()</code> 和 <code>next()</code>。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="built_in">list</span> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line">it = <span class="built_in">iter</span>(<span class="built_in">list</span>)             <span class="comment"># 创建迭代器对象</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">next</span>(it), end=<span class="string">&#x27;,&#x27;</span>)    <span class="comment"># 输出迭代器的下一个元素</span></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">next</span>(it), end=<span class="string">&#x27;,&#x27;</span>)    <span class="comment"># 输出迭代器的下一个元素</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 1,2,</span></span><br></pre></td></tr></table></figure><p>迭代器可以使用 for 语句进行遍历：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="built_in">list</span>=[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line">it = <span class="built_in">iter</span>(<span class="built_in">list</span>)    <span class="comment"># 创建迭代器对象</span></span><br><span class="line"><span class="keyword">for</span> x <span class="keyword">in</span> it:</span><br><span class="line">    <span class="built_in">print</span>(x, end=<span class="string">&quot;,&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1,2,3,4,</span></span><br></pre></td></tr></table></figure><p>（2）迭代器的创建</p><p>在类中实现 <code>__iter__()</code> 与 <code>__next__()</code> 两个方法。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyIter</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__iter__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.a = <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__next__</span>(<span class="params">self</span>):</span><br><span class="line">        x = <span class="variable language_">self</span>.a</span><br><span class="line">        <span class="variable language_">self</span>.a += <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> x</span><br><span class="line"></span><br><span class="line">myIter = MyIter()</span><br><span class="line"><span class="built_in">iter</span> = <span class="built_in">iter</span>(myIter)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">next</span>(<span class="built_in">iter</span>), end=<span class="string">&quot;,&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">next</span>(<span class="built_in">iter</span>), end=<span class="string">&quot;,&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">next</span>(<span class="built_in">iter</span>), end=<span class="string">&quot;,&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">next</span>(<span class="built_in">iter</span>), end=<span class="string">&quot;,&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">next</span>(<span class="built_in">iter</span>), end=<span class="string">&quot;,&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1,2,3,4,5,</span></span><br></pre></td></tr></table></figure><p>（3）StopIteration 异常</p><p>StopIteration 异常用于标识迭代的完成，防止出现无限循环的情况。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyIter</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__iter__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.a = <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__next__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.a &lt;= <span class="number">10</span>:</span><br><span class="line">            x = <span class="variable language_">self</span>.a</span><br><span class="line">            <span class="variable language_">self</span>.a += <span class="number">1</span></span><br><span class="line">            <span class="keyword">return</span> x</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">raise</span> StopIteration</span><br><span class="line"></span><br><span class="line">myIter = MyIter()</span><br><span class="line"><span class="built_in">iter</span> = <span class="built_in">iter</span>(myIter)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">iter</span>:</span><br><span class="line">    <span class="built_in">print</span>(x, end=<span class="string">&quot;,&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1,2,3,4,5,6,7,8,9,10,</span></span><br></pre></td></tr></table></figure><p>2、生成器</p><p>在 Python 中，使用了 yield 的函数被称为生成器（generator）。</p><p>调用一个生成器函数，返回的是一个迭代器对象。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fibonacci</span>(<span class="params">n</span>):  <span class="comment"># 生成器函数 - 斐波那契</span></span><br><span class="line">    a, b, counter = <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span></span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        <span class="keyword">if</span> (counter &gt; n):</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="keyword">yield</span> a</span><br><span class="line">        a, b = b, a + b</span><br><span class="line">        counter += <span class="number">1</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">f = fibonacci(<span class="number">10</span>)  <span class="comment"># f 是一个迭代器，由生成器返回生成</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="built_in">next</span>(f), end=<span class="string">&quot; &quot;</span>)</span><br><span class="line">    <span class="keyword">except</span> StopIteration:</span><br><span class="line">        sys.exit()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 0 1 1 2 3 5 8 13 21 34 55</span></span><br></pre></td></tr></table></figure><p>每次调用生成器函数都会在 <code>yield</code> 处暂停并保存运行状态，返回 <code>yield</code> 结果；在下次执行 <code>next()</code> 函数时从暂停位置继续运行。</p><h2 id="Python-函数"><a href="#Python-函数" class="headerlink" title="Python 函数"></a>Python 函数</h2><p>1、函数的定义</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>() :</span><br><span class="line">   <span class="built_in">print</span>(<span class="string">&quot;Hello World!&quot;</span>)</span><br><span class="line"></span><br><span class="line">hello()</span><br><span class="line"></span><br><span class="line"><span class="comment"># Hello World!</span></span><br></pre></td></tr></table></figure><p>2、函数的参数及其传递</p><p>Python 参数类型：必需参数、关键字参数、默认参数、不定长参数</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">printinfo</span>(<span class="params">arg1, arg2, arg3=<span class="string">&quot;默认值&quot;</span>, *arg4, **arg5</span>):</span><br><span class="line">    <span class="built_in">print</span>(arg1, end=<span class="string">&quot;;&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(arg2, end=<span class="string">&quot;;&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(arg3, end=<span class="string">&quot;;&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(arg4, end=<span class="string">&quot;;&quot;</span>)</span><br><span class="line">    <span class="built_in">print</span>(arg5)</span><br><span class="line"></span><br><span class="line"><span class="comment"># printinfo();</span></span><br><span class="line">printinfo(<span class="string">&quot;欢迎&quot;</span>, <span class="string">&quot;wshunli&quot;</span>)</span><br><span class="line">printinfo(arg2=<span class="string">&quot;欢迎&quot;</span>, arg1=<span class="string">&quot;wshunli&quot;</span>)</span><br><span class="line">printinfo(<span class="string">&quot;欢迎&quot;</span>, <span class="string">&quot;wshunli&quot;</span>, <span class="string">&quot;访问&quot;</span>)</span><br><span class="line">printinfo(<span class="string">&quot;欢迎&quot;</span>, <span class="string">&quot;wshunli&quot;</span>, <span class="string">&quot;访问&quot;</span>, <span class="string">&quot;wshunli&quot;</span>, <span class="string">&quot;.com&quot;</span>)</span><br><span class="line">printinfo(<span class="string">&quot;欢迎&quot;</span>, <span class="string">&quot;wshunli&quot;</span>, <span class="string">&quot;访问&quot;</span>, <span class="string">&quot;wshunli&quot;</span>, <span class="string">&quot;.com&quot;</span>, domain=<span class="string">&quot;wshunli&quot;</span>, suffix=<span class="string">&quot;.com&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 欢迎;wshunli;默认值;();&#123;&#125;</span></span><br><span class="line"><span class="comment"># wshunli;欢迎;默认值;();&#123;&#125;</span></span><br><span class="line"><span class="comment"># 欢迎;wshunli;访问;();&#123;&#125;</span></span><br><span class="line"><span class="comment"># 欢迎;wshunli;访问;(&#x27;wshunli&#x27;, &#x27;.com&#x27;);&#123;&#125;</span></span><br><span class="line"><span class="comment"># 欢迎;wshunli;访问;(&#x27;wshunli&#x27;, &#x27;.com&#x27;);&#123;&#x27;domain&#x27;: &#x27;wshunli&#x27;, &#x27;suffix&#x27;: &#x27;.com&#x27;&#125;</span></span><br></pre></td></tr></table></figure><p>其中 <code>arg1</code>, <code>arg2</code> 属于必需参数，通过关键字传递时可以交换顺序；<br><code>arg3</code> 属于默认参数，在没有传值时使用默认值；<br><code>arg4</code>, <code>arg5</code> 属于可变参数，<code>*</code> 元祖形式，<code>**</code> 字典形式</p><p>声明函数时，参数中星号 <code>*</code> 可以单独出现，例如:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">f</span>(<span class="params">a, b, *, c</span>):</span><br><span class="line">    <span class="keyword">return</span> a + b + c</span><br><span class="line"></span><br><span class="line">f(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>)    <span class="comment"># 报错</span></span><br><span class="line">f(<span class="number">1</span>, <span class="number">2</span>, c=<span class="number">3</span>)  <span class="comment"># 正常</span></span><br></pre></td></tr></table></figure><p>星号 <code>*</code> 后的参数必须用关键字传入。</p><p>不可变类型类似于<strong>传值</strong>传递；可变类型类似于<strong>传址</strong>传递。</p><p>3、匿名函数</p><p>Python 使用 <code>lambda</code> 来创建匿名函数。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sum</span> = <span class="keyword">lambda</span> arg1, arg2: arg1 + arg2</span><br><span class="line"><span class="comment"># 调用 sum 函数</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;相加后的值为 : &quot;</span>, <span class="built_in">sum</span>(<span class="number">10</span>, <span class="number">20</span>))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;相加后的值为 : &quot;</span>, <span class="built_in">sum</span>(<span class="number">20</span>, <span class="number">20</span>))</span><br></pre></td></tr></table></figure><p>4、变量作用域</p><p>Python 的作用域一共有 4 种：局部作用域、闭包函数外的函数中、全局作用域、内置作用域（内置函数所在模块的范围）</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">g_count = <span class="number">0</span>  <span class="comment"># 全局作用域</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">outer</span>():</span><br><span class="line">    o_count = <span class="number">1</span>  <span class="comment"># 闭包函数外的函数中</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">inner</span>():</span><br><span class="line">        i_count = <span class="number">2</span>  <span class="comment"># 局部作用域</span></span><br></pre></td></tr></table></figure><p>内置作用域是通过一个名为 builtin 的标准模块来实现的：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> builtins</span><br><span class="line"><span class="built_in">dir</span>(builtins)</span><br></pre></td></tr></table></figure><p>改变变量的作用域：使用 <code>global</code> 和 <code>nonlocal</code> 关键字。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">num1 = <span class="number">1</span></span><br><span class="line">num2 = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">outer</span>():</span><br><span class="line">    <span class="keyword">global</span> num1        <span class="comment"># global 关键字</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;outer&gt;&gt;num1=&quot;</span>, num1)</span><br><span class="line">    num1 = <span class="number">123</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;outer&gt;&gt;num1=&quot;</span>, num1)</span><br><span class="line"></span><br><span class="line">    num2 = <span class="number">20</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">inner</span>():</span><br><span class="line">        <span class="keyword">nonlocal</span> num2  <span class="comment"># nonlocal 关键字</span></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;inner&gt;&gt;num2=&quot;</span>, num2)</span><br><span class="line">        num2 = <span class="number">456</span></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;inner&gt;&gt;num2=&quot;</span>, num2)</span><br><span class="line"></span><br><span class="line">    inner()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;outer&gt;&gt;num2=&quot;</span>, num2)</span><br><span class="line"></span><br><span class="line">outer()</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;num1=&quot;</span>, num1)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;num2=&quot;</span>, num2)</span><br><span class="line"></span><br><span class="line"><span class="comment"># outer&gt;&gt;num1= 1</span></span><br><span class="line"><span class="comment"># outer&gt;&gt;num1= 123</span></span><br><span class="line"><span class="comment"># inner&gt;&gt;num2= 20</span></span><br><span class="line"><span class="comment"># inner&gt;&gt;num2= 456</span></span><br><span class="line"><span class="comment"># outer&gt;&gt;num2= 456</span></span><br><span class="line"><span class="comment"># num1= 123</span></span><br><span class="line"><span class="comment"># num2= 2</span></span><br></pre></td></tr></table></figure><p>其中 <code>global</code>   : 局部作用域 -&gt; 全局作用域；<code>nonlocal</code> : 局部作用域 -&gt; 闭包函数外的函数中</p><h2 id="Python-模块"><a href="#Python-模块" class="headerlink" title="Python 模块"></a>Python 模块</h2><p>在 Python 中将一些方法和变量保存在文件中以供其他程序使用。</p><p>1、import 语句</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> module1[, module2[,... moduleN]</span><br></pre></td></tr></table></figure><p>2、from … import 语句：仅导入指定的部分（函数、变量）到当前命名空间中。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> modelname <span class="keyword">import</span> name1[, name2[, ... nameN]]</span><br></pre></td></tr></table></figure><p><code>__name__</code> 属性：值为 <code>&#39;__main__&#39;</code> 代表模块自身在运行，否则是被引入的。</p><p><code>dir()</code> 函数：列出模块内定义的所有名称。</p><h2 id="Python-异常处理"><a href="#Python-异常处理" class="headerlink" title="Python 异常处理"></a>Python 异常处理</h2><p>Python 中使用 <code>try</code> 及 <code>except</code> 处理异常：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        x = <span class="built_in">int</span>(<span class="built_in">input</span>(<span class="string">&quot;Please enter a number: &quot;</span>))</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">except</span> ValueError:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Oops!  That was no valid number.  Try again   &quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Please enter a number: w</span></span><br><span class="line"><span class="comment"># Oops!  That was no valid number.  Try again   </span></span><br><span class="line"><span class="comment"># Please enter a number: 1</span></span><br></pre></td></tr></table></figure><p>同时处理多个异常，可使用元组元组表示：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">except</span> (RuntimeError, TypeError, NameError):</span><br><span class="line">        <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><p>Python 使用 <code>raise</code> 语句抛出一个指定的异常。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Python 在各领域的应用已经非常广泛了，尤其是最近热门的人工智能等领域。&lt;/p&gt;
&lt;p&gt;现在开始学习 Python 语言基础，后面利用强大的工具库，想必事半功倍。&lt;/p&gt;</summary>
    
    
    
    <category term="语言基础" scheme="https://www.wshunli.com/categories/%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="Python" scheme="https://www.wshunli.com/tags/Python/"/>
    
    <category term="Python3" scheme="https://www.wshunli.com/tags/Python3/"/>
    
  </entry>
  
  <entry>
    <title>基于 Flarum  搭建论坛网站实践</title>
    <link href="https://www.wshunli.com/posts/c628f26d.html"/>
    <id>https://www.wshunli.com/posts/c628f26d.html</id>
    <published>2019-01-09T02:01:41.000Z</published>
    <updated>2026-06-17T23:42:59.829Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍在 Nginx 服务器下基于 Flarum  搭建论坛网站。</p><p>Flarum 是一款现代的，优雅的，简洁的，强大的论坛软件。Flarum 让在线交流变得更加轻松愉快。</p><span id="more"></span><p>官网地址：<a href="http://flarum.org/">http://flarum.org</a></p><p>中文网：<a href="http://flarum.org.cn/">http://flarum.org.cn</a></p><p>Flarumchina：<a href="https://flarumchina.org/">https://flarumchina.org</a></p><h1 id="安装部署-Flarum"><a href="#安装部署-Flarum" class="headerlink" title="安装部署 Flarum"></a>安装部署 Flarum</h1><p>1、官方推荐使用 Composer 安装，</p><p>这里需要安装 Composer 工具</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -sS https://getcomposer.org/installer | php</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">mv</span> composer.phar /usr/local/bin/composer</span><br><span class="line">composer config -g repo.packagist composer https://packagist.phpcomposer.com <span class="comment"># 替换中国镜像源 </span></span><br></pre></td></tr></table></figure><p>然后执行如下命令即可安装，注意需要在空目录下执行</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">composer create-project flarum/flarum . --stability=beta</span><br></pre></td></tr></table></figure><p>2、这里推荐使用 Flarumchina 的中文版安装包</p><p>源码地址 <a href="https://github.com/skywalker512/FlarumChina">https://github.com/skywalker512/FlarumChina</a></p><p>我们把源文件上传到服务器，解压即可完成安装。</p><p>针对 Nginx 服务器，需要配置写规则。</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">location / &#123;</span><br><span class="line">    index  index.php index.html index.htm;</span><br><span class="line">    try_files <span class="variable">$uri</span> <span class="variable">$uri</span>/ /index.php?<span class="variable">$query_string</span>;</span><br><span class="line">&#125;</span><br><span class="line">location ~ .php$ &#123;</span><br><span class="line">    fastcgi_split_path_info ^(.+.php)(/.+)$;</span><br><span class="line">    fastcgi_pass unix:/var/run/php5-fpm.sock;</span><br><span class="line">    fastcgi_param SCRIPT_FILENAME $document_root<span class="variable">$fastcgi_script_name</span>;</span><br><span class="line">    fastcgi_index index.php;</span><br><span class="line">    include fastcgi_params;</span><br><span class="line">&#125;</span><br><span class="line">location /api &#123; </span><br><span class="line">        try_files <span class="variable">$uri</span> <span class="variable">$uri</span>/ /api.php?<span class="variable">$query_string</span>; </span><br><span class="line">&#125;</span><br><span class="line">location /admin &#123;</span><br><span class="line">        try_files <span class="variable">$uri</span> <span class="variable">$uri</span>/ /admin.php?<span class="variable">$query_string</span>; </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">location /flarum &#123;</span><br><span class="line">        deny all;</span><br><span class="line">        <span class="built_in">return</span> 404;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="配置-HTTPS-访问"><a href="#配置-HTTPS-访问" class="headerlink" title="配置 HTTPS 访问"></a>配置 HTTPS 访问</h2><p>在根目录下 config.php 文件中，将 url 修改为 https 协议即可。</p><p>最后，欢迎访问我搭建的论坛 <a href="http://bbs.wshunli.com/">http://bbs.wshunli.com</a></p><blockquote><p>参考资料<br>1、轻论坛：Flarum 程序安装指南<br><a href="https://jsthon.com/flarum-installation-guide/">https://jsthon.com/flarum-installation-guide/</a><br>2、遇见最美社区——Flarum | MIKELIN<br><a href="https://mikelin.cn/740.html">https://mikelin.cn/740.html</a><br>3、Installation | Flarum Documentation<br><a href="https://flarum.org/docs/install.html#installing">https://flarum.org/docs/install.html#installing</a><br>4、Installation<br><a href="https://www.flarumchina.org/docs/installation/">https://www.flarumchina.org/docs/installation/</a><br>5、Ubuntu 16.04 &amp; Nginx 下配置 Flarum | Homulilly<br><a href="https://homulilly.com/post/install-flarum-on-ubuntu-16-04-with-nginx.html">https://homulilly.com/post/install-flarum-on-ubuntu-16-04-with-nginx.html</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍在 Nginx 服务器下基于 Flarum  搭建论坛网站。&lt;/p&gt;
&lt;p&gt;Flarum 是一款现代的，优雅的，简洁的，强大的论坛软件。Flarum 让在线交流变得更加轻松愉快。&lt;/p&gt;</summary>
    
    
    
    <category term="前端技术" scheme="https://www.wshunli.com/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="Flarum" scheme="https://www.wshunli.com/tags/Flarum/"/>
    
    <category term="论坛" scheme="https://www.wshunli.com/tags/%E8%AE%BA%E5%9D%9B/"/>
    
  </entry>
  
  <entry>
    <title>Cesium 相关资料汇总</title>
    <link href="https://www.wshunli.com/posts/9f50f2b3.html"/>
    <id>https://www.wshunli.com/posts/9f50f2b3.html</id>
    <published>2019-01-08T01:39:51.000Z</published>
    <updated>2026-06-17T23:42:59.822Z</updated>
    
    <content type="html"><![CDATA[<p>【公开全文】汇总 Cesium 相关框架及其数据组织处理资料。</p><span id="more"></span><p>Cesium 是一个用于显示三维地球和地图的开源 JS 库。<br>它可以用来显示海量三维模型数据、影像数据、地形高程数据、矢量数据等等。</p><h1 id="Cesium-相关开源库"><a href="#Cesium-相关开源库" class="headerlink" title="Cesium 相关开源库"></a>Cesium 相关开源库</h1><h2 id="Cesium-官方资料"><a href="#Cesium-官方资料" class="headerlink" title="Cesium 官方资料"></a>Cesium 官方资料</h2><p>1、Cesium 官网(包含三维引擎 + 云服务)</p><p><a href="https://cesium.com/">https://cesium.com/</a></p><p>2、Cesiumjs 三维引擎官网</p><p>官方主页 <a href="https://cesiumjs.org/">https://cesiumjs.org/</a></p><p>官方教程 <a href="https://cesiumjs.org/tutorials/">https://cesiumjs.org/tutorials/</a></p><p>API 参考 <a href="https://cesiumjs.org/refdoc/">https://cesiumjs.org/refdoc/</a></p><p>示例代码 <a href="https://cesiumjs.org/Cesium/Build/Apps/Sandcastle/index.html">https://cesiumjs.org/Cesium/Build/Apps/Sandcastle/index.html</a></p><p>3、Cesium 源码仓库</p><p>官方仓库 <a href="https://github.com/AnalyticalGraphicsInc/cesium">https://github.com/AnalyticalGraphicsInc/cesium</a></p><p>3D Tiles <a href="https://github.com/AnalyticalGraphicsInc/3d-tiles">https://github.com/AnalyticalGraphicsInc/3d-tiles</a></p><p>glTF <a href="https://github.com/KhronosGroup/glTF">https://github.com/KhronosGroup/glTF</a></p><h2 id="Cesium-国内移植"><a href="#Cesium-国内移植" class="headerlink" title="Cesium 国内移植"></a>Cesium 国内移植</h2><p>1、SuperMap</p><p>官方主页 <a href="http://support.supermap.com.cn:8090/webgl/index.html">http://support.supermap.com.cn:8090/webgl/index.html</a></p><p>API 参考 <a href="http://support.supermap.com.cn:8090/webgl/WebGL_API/webgl_chm.html">http://support.supermap.com.cn:8090/webgl/WebGL_API/webgl_chm.html</a></p><p>API 参考（包含 Cesium） <a href="http://support.supermap.com.cn:8090/webgl/Build/Documentation/index.html">http://support.supermap.com.cn:8090/webgl/Build/Documentation/index.html</a></p><p>示例代码 <a href="http://support.supermap.com.cn:8090/webgl/examples/examples.html#layer">http://support.supermap.com.cn:8090/webgl/examples/examples.html#layer</a></p><p>2、MarsGIS</p><p>官方主页 <a href="http://cesium.marsgis.cn/docs/index.html">http://cesium.marsgis.cn/docs/index.html</a></p><p>官方教程翻译 <a href="http://cesium.marsgis.cn/docs/go.html?id=12">http://cesium.marsgis.cn/docs/go.html?id=12</a></p><p>相关链接 <a href="http://cesium.marsgis.cn/docs/friendlylink.html">http://cesium.marsgis.cn/docs/friendlylink.html</a></p><p>3、Wish3D</p><p>官方主页 <a href="http://www.wish3d.com/">http://www.wish3d.com/</a></p><p>4、Cesiumlab</p><p>官方主页 <a href="https://www.cesiumlab.com/">https://www.cesiumlab.com/</a></p><p>5、Skyline</p><p>官方示例 <a href="http://skylineglobe.com/sg/TerraExplorerWeb/TerraExplorer.html">http://skylineglobe.com/sg/TerraExplorerWeb/TerraExplorer.html</a></p><p>6、其他</p><p>鸿业科技 <a href="http://www.hongye.com.cn/">http://www.hongye.com.cn/</a><br>圣伟合众 <a href="http://www.swhztech.com/">http://www.swhztech.com/</a><br>Holo3DForWeb <a href="https://forweb.gbim360.com/">https://forweb.gbim360.com/</a></p><h2 id="WebGL-相关开源库"><a href="#WebGL-相关开源库" class="headerlink" title="WebGL 相关开源库"></a>WebGL 相关开源库</h2><p>1、threejs</p><p>官方主页 <a href="https://threejs.org/">https://threejs.org/</a></p><p>2、ArcGIS</p><p>官方主页 <a href="https://developers.arcgis.com/javascript/">https://developers.arcgis.com/javascript/</a></p><p>3、luma.gl</p><p>官方主页 <a href="https://luma.gl/">https://luma.gl/</a></p><p>4、Maptalks</p><p>官方主页 <a href="http://maptalks.org/">http://maptalks.org/</a></p><h1 id="Cesium-相关教程"><a href="#Cesium-相关教程" class="headerlink" title="Cesium 相关教程"></a>Cesium 相关教程</h1><h2 id="相关社区网站"><a href="#相关社区网站" class="headerlink" title="相关社区网站"></a>相关社区网站</h2><p>1、Cesium中文网</p><p><a href="http://cesiumcn.org/">http://cesiumcn.org/</a></p><h2 id="学习教程博客"><a href="#学习教程博客" class="headerlink" title="学习教程博客"></a>学习教程博客</h2><p>1、Cesium教程系列汇总 - fu*k - 博客园</p><p><a href="http://www.cnblogs.com/fuckgiser/p/5706842.html">http://www.cnblogs.com/fuckgiser/p/5706842.html</a></p><p>2、cesium中文网 | 学习cesiumjs 的好地方–伐罗密</p><p><a href="http://cesium.xin/">http://cesium.xin/</a></p><p>3、kamijawa的个人空间 - 开源中国</p><p><a href="https://my.oschina.net/u/1585572">https://my.oschina.net/u/1585572</a></p><p>4、Cesium学习笔记汇总_cumtzheNo1_新浪博客</p><p><a href="http://blog.sina.com.cn/s/blog_15e866bbe0102xu2f.html">http://blog.sina.com.cn/s/blog_15e866bbe0102xu2f.html</a></p><h2 id="源码分析汇总"><a href="#源码分析汇总" class="headerlink" title="源码分析汇总"></a>源码分析汇总</h2><p>1、Cesium - 标签 - 自由战士 - 博客园</p><p><a href="https://www.cnblogs.com/webgl-angela/tag/Cesium/">https://www.cnblogs.com/webgl-angela/tag/Cesium/</a></p><p>2、cesium 归档 - GIS开发者</p><p><a href="https://www.giserdqy.com/gis/opengis/3d/cesium">https://www.giserdqy.com/gis/opengis/3d/cesium</a></p><h2 id="视频投放源码分析"><a href="#视频投放源码分析" class="headerlink" title="视频投放源码分析"></a>视频投放源码分析</h2><p>1、Cesium源码剖析—视频投影 - 自由战士 - 博客园</p><p><a href="https://www.cnblogs.com/webgl-angela/p/9809307.html">https://www.cnblogs.com/webgl-angela/p/9809307.html</a></p><p>2、Cesium开发：在平面上播放视频 - 知乎</p><p><a href="https://zhuanlan.zhihu.com/p/41862445">https://zhuanlan.zhihu.com/p/41862445</a></p><p><a href="https://chaossoong.github.io/2017/01/13/cesium%E5%9B%BE%E5%BD%A2%E6%B7%BB%E5%8A%A0%E8%A7%86%E9%A2%91/">https://chaossoong.github.io/2017/01/13/cesium图形添加视频/</a></p><p>3、Clamping to 3D Tiles by lilleyse · Pull Request #6934 · AnalyticalGraphicsInc&#x2F;cesium</p><p><a href="https://github.com/AnalyticalGraphicsInc/cesium/pull/6934">https://github.com/AnalyticalGraphicsInc/cesium/pull/6934</a></p><p>5、Cesium 1.50重量级新功能测评 - 知乎</p><p><a href="https://zhuanlan.zhihu.com/p/46190355">https://zhuanlan.zhihu.com/p/46190355</a></p><p>6、Cesium(三) 几何图形与外观 - 多多 - CSDN博客</p><p><a href="https://blog.csdn.net/happyduoduo1/article/details/51868042">https://blog.csdn.net/happyduoduo1/article/details/51868042</a></p><p>7、AnalyticalGraphicsInc&#x2F;cesium-materials-pack: A Cesium plugin with procedurally-shaded materials such as bricks, wood, and noise patterns</p><p><a href="https://github.com/AnalyticalGraphicsInc/cesium-materials-pack">https://github.com/AnalyticalGraphicsInc/cesium-materials-pack</a></p><p><a href="https://github.com/MikesWei/CesiumMeshVisualizer">https://github.com/MikesWei/CesiumMeshVisualizer</a></p><p><a href="http://52.4.31.236/plugins/">http://52.4.31.236/plugins/</a></p><p>8、Fabric · AnalyticalGraphicsInc&#x2F;cesium Wiki</p><p><a href="https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric">https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric</a></p><p>9、Drawing on Terrain - Cesium Sandcastle</p><h1 id="数据组织处理"><a href="#数据组织处理" class="headerlink" title="数据组织处理"></a>数据组织处理</h1><h2 id="glTF-相关工具"><a href="#glTF-相关工具" class="headerlink" title="glTF 相关工具"></a>glTF 相关工具</h2><p>1、COLLADA2GLTF </p><p>官方 三维模型 转 glTF 的插件</p><p><a href="https://github.com/KhronosGroup/COLLADA2GLTF">https://github.com/KhronosGroup/COLLADA2GLTF</a></p><p>2、obj2gltf</p><p><a href="https://github.com/AnalyticalGraphicsInc/obj2gltf">https://github.com/AnalyticalGraphicsInc/obj2gltf</a></p><h2 id="3D-Tiles-相关工具"><a href="#3D-Tiles-相关工具" class="headerlink" title="3D Tiles 相关工具"></a>3D Tiles 相关工具</h2><p>1、3d-tiles-tools</p><p>官方 3d tiles 转换工具</p><p><a href="https://github.com/AnalyticalGraphicsInc/3d-tiles-tools">https://github.com/AnalyticalGraphicsInc/3d-tiles-tools</a></p><p>2、Cesiumlab</p><p><a href="https://www.cesiumlab.com/">https://www.cesiumlab.com/</a></p><blockquote><p>参考资料<br>1、Cesium资料大全 - 知乎<br><a href="https://zhuanlan.zhihu.com/p/34217817">https://zhuanlan.zhihu.com/p/34217817</a><br>2、MarsGIS for Cesium|三维地图<br><a href="http://cesium.marsgis.cn/docs/friendlylink.html">http://cesium.marsgis.cn/docs/friendlylink.html</a><br>3、CZM6.com – Cesium相关资料和产品收集<br><a href="https://www.czm6.com/">https://www.czm6.com/</a><br>4、cesium编程入门(十)优秀资源 | cesium中文网<br><a href="http://cesium.xin/wordpress/archives/130.html">http://cesium.xin/wordpress/archives/130.html</a></p></blockquote><p><strong>2019年06月23日公开全文</strong>。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;【公开全文】汇总 Cesium 相关框架及其数据组织处理资料。&lt;/p&gt;</summary>
    
    
    
    <category term="三维技术" scheme="https://www.wshunli.com/categories/%E4%B8%89%E7%BB%B4%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="Cesium" scheme="https://www.wshunli.com/tags/Cesium/"/>
    
  </entry>
  
  <entry>
    <title>在 Hexo 中插入 cPlayer 播放器</title>
    <link href="https://www.wshunli.com/hexo-tag-mplayer"/>
    <id>https://www.wshunli.com/hexo-tag-mplayer</id>
    <published>2019-01-04T03:08:24.000Z</published>
    <updated>2026-06-17T23:42:59.829Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍在 Hexo 中插入 cPlayer 播放器</p><span id="more"></span><center><div id="mplayer9086"></div><script src="https://cdn.jsdelivr.net/gh/MoePlayer/cPlayer/dist/cplayer.js"></script><script>    new cplayer({      element: document.getElementById('mplayer9086'),      playlist: [    {        src: 'https://img.wshunli.com/about/阿婆说.mp3',        poster: 'https://img.wshunli.com/about/阿婆说.jpg',        name: '阿婆说',        artist: '陈一发儿',        lyric: '[00:03.85]阿婆说-陈一发儿\n[00:04.86]作曲:暗杠\n[00:05.29]作词:暗杠/古道背棺人\n[00:05.81]编曲：暗杠\n[00:06.25]器乐演奏：暗杠\n[00:06.73]和声编配：暗杠\n[00:07.30]混音后期：A.Q.Studio\n[00:16.33]囡囡呀不要调皮\n[00:19.15]坐下听听阿婆说\n[00:23.12]这个季节天气转凉地上雨水多\n[00:30.17]囡囡呀不要惊慌\n[00:33.13]过来听听阿婆说\n[00:36.91]睡个觉雷声过后就能看云朵\n[00:44.58]囡囡别怕\n[00:46.26]囡囡别哭\n[00:47.84]快快睡咯\n[00:51.23]你静静听首歌\n[00:58.51]蛐蛐轻些\n[00:59.94]静静安歇\n[01:01.73]月儿圆哟\n[01:05.02]你乖乖呀抱阿婆\n[01:11.93]风铃呀轻响鸟儿轻唱远处谁在和\n[01:18.75]亲了彩虹惊了云朵\n[01:21.81]我已成归客\n[01:25.88]囡囡呀你会长大会走很远会觉得累了\n[01:33.02]只要记得河婆话“阿婆”怎么说\n[02:07.56]囡囡呀你会困惑\n[02:10.65]慢些脚步别忘了\n[02:14.39]慢慢的你会明白丢了是什么\n[02:21.46]人生路本就是场获得与失的选择\n[02:28.14]迷路时想想当年阿婆怎么说\n[02:35.85]回头看看\n[02:37.46]雨水过后\n[02:39.26]云彩很多\n[02:42.49]来吧阿婆帮你偷偷摘一朵\n[02:49.67]等你老了\n[02:51.29]阿婆走了\n[02:53.09]你要记得\n[02:56.64]把这乡音教会娃儿怎么说\n[03:03.66]把这乡音教给你的囡囡哟\n[03:38.37]回头看看\n[03:40.11]雨水过后\n[03:41.75]少了冷漠\n[03:45.09]来吧阿婆等你还在那村落'    },    {        src: 'https://img.wshunli.com/about/童话镇.mp3',        poster: 'https://img.wshunli.com/about/童话镇.jpg',        name: '童话镇',        artist: '陈一发儿',        lyric: '[00:00.00]童话镇\n[00:05.00]演唱：陈一发\n[00:10.00]作曲:暗杠\n[00:15.00]作词:竹君\n[00:22.93]听说白雪公主在逃跑\n[00:26.43]小红帽在担心大灰狼\n[00:29.83]听说疯帽喜欢爱丽丝\n[00:33.17]丑小鸭会变成白天鹅\n[00:36.34]听说彼得潘总长不大\n[00:40.23]杰克他有竖琴和魔法\n[00:43.56]听说森林里有糖果屋\n[00:46.82]灰姑娘丢了心爱的玻璃鞋\n[00:50.39]只有睿智的河水知道\n[00:53.68]白雪是因为贪玩跑出了城堡\n[00:57.31]小红帽有件抑制自己\n[01:00.73]变成狼的大红袍\n[01:03.80]总有一条蜿蜒在童话镇里七彩的河\n[01:11.00]沾染魔法的乖张气息\n[01:14.42]却又在爱里曲折\n[01:17.76]川流不息扬起水\n[01:20.87]又卷入一帘时光入水\n[01:24.68]让全部很久很久以前\n[01:28.12]都走到幸福结局的时刻\n[01:33.18]music....\n[01:47.00]听说睡美人被埋藏\n[01:50.44]小人鱼在眺望金殿堂\n[01:53.79]听说阿波罗变成金乌\n[01:57.12]草原有奔跑的剑齿虎\n[02:00.73]听说匹诺曹总说着谎\n[02:04.16]侏儒怪拥有宝石满箱\n[02:07.57]听说悬崖有颗长生树\n[02:10.80]红鞋子不知疲倦地在跳舞\n[02:14.43]只有睿智的河水知道\n[02:17.84]睡美人逃避了生活的煎熬\n[02:21.14]小人鱼把阳光抹成眼影\n[02:24.58]投进泡沫的怀抱\n[02:27.77]总有一条蜿蜒在童话镇里七彩的河\n[02:35.06]沾染魔法的乖张气息\n[02:38.43]却又在爱里曲折\n[02:41.82]川流不息扬起水花\n[02:44.87]又卷入一帘时光入水\n[02:48.69]让全部很久很久以前\n[02:52.00]都走到幸福结局的时刻\n[02:55.46]总有一条蜿蜒在童话镇里梦幻的河\n[03:02.47]分隔了理想分隔现实\n[03:05.82]又在前方的山口汇合\n[03:09.22]川流不息扬起水花\n[03:12.36]又卷入一帘时光入水\n[03:16.23]让全部很久很久以前\n[03:19.38]都走到幸福结局的时刻\n[03:22.72]又陌生\n[03:24.52]啊~~啊~~啊~~啊~~'    }],autoplay: true,volume: 0.75,big: true,dark: false,zoomOutKana: false,playmode: "listloop",point: 0,showPlaylist: false,dropDownMenuMode: 'bottom'    })</script></center><h1 id="cPlayer-介绍"><a href="#cPlayer-介绍" class="headerlink" title="cPlayer 介绍"></a>cPlayer 介绍</h1><p>A beautiful and clean WEB Music Player by HTML5. <a href="http://cplayer.js.org/">http://cplayer.js.org/</a></p><h1 id="Hexo-中使用-cPlayer"><a href="#Hexo-中使用-cPlayer" class="headerlink" title="Hexo 中使用 cPlayer"></a>Hexo 中使用 cPlayer</h1><p><a href="https://github.com/wshunli/hexo-tag-mplayer">hexo-tag-mplayer</a> 是一款在 Hexo 中使用 cPlayer 的插件。</p><p>插件的安装和使用非常的简单，只需要进入 Hexo 博客根目录，然后安装：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm install hexo-tag-mplayer --save</span><br></pre></td></tr></table></figure><p>之后在文章内使用 <code>mplayer</code> 的 tag 就可以了：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">&#123;% mplayer %&#125;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// TODO mplayer options goes here</span></span><br><span class="line">&#125;</span><br><span class="line">&#123;% endmplayer %&#125;</span><br></pre></td></tr></table></figure><p>其中：<br><code>mplayer</code> 和 <code>endmplayer</code> 是 Hexo 的标签，不需要修改；<br><code>options</code> 部分是图表的配置。</p><h1 id="快速上手"><a href="#快速上手" class="headerlink" title="快速上手"></a>快速上手</h1><p>可参考如下配置，使用 <code>hexo-tag-mplayer</code> 插件。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">&#123;% mplayer %&#125;</span><br><span class="line">    <span class="attr">playlist</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="attr">src</span>: <span class="string">&#x27;歌曲资源链接...&#x27;</span>,</span><br><span class="line">            <span class="attr">poster</span>: <span class="string">&#x27;封面链接...&#x27;</span>,</span><br><span class="line">            <span class="attr">name</span>: <span class="string">&#x27;歌曲名称...&#x27;</span>,</span><br><span class="line">            <span class="attr">artist</span>: <span class="string">&#x27;歌手名称...&#x27;</span>,</span><br><span class="line">            <span class="attr">lyric</span>: <span class="string">&#x27;歌词...&#x27;</span>,</span><br><span class="line">            <span class="attr">sublyric</span>: <span class="string">&#x27;副歌词，一般为翻译...&#x27;</span>,</span><br><span class="line">            <span class="attr">album</span>: <span class="string">&#x27;专辑，唱片...&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            ...</span><br><span class="line">        &#125;,</span><br><span class="line">        ...</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">autoplay</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">volume</span>: <span class="number">0.75</span>,</span><br><span class="line">    <span class="attr">playmode</span>: <span class="string">&quot;listloop&quot;</span>,</span><br><span class="line">    <span class="attr">big</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">dark</span>: <span class="literal">false</span></span><br><span class="line">&#123;% endmplayer %&#125;</span><br></pre></td></tr></table></figure><p>更详细配置可参考官方文档：<a href="https://github.com/MoePlayer/cPlayer#option">cPlayer Option</a></p><h1 id="实例参考"><a href="#实例参考" class="headerlink" title="实例参考"></a>实例参考</h1><p>下面我们来看插件的一些使用样例：</p><p>1、默认样式</p><div id="mplayer9531"></div><script src="https://cdn.jsdelivr.net/gh/MoePlayer/cPlayer/dist/cplayer.js"></script><script>    new cplayer({      element: document.getElementById('mplayer9531'),      playlist: [    {        src: 'https://img.wshunli.com/about/阿婆说.mp3',        poster: 'https://img.wshunli.com/about/阿婆说.jpg',        name: '阿婆说',        artist: '陈一发儿',        lyric: '[00:03.85]阿婆说-陈一发儿\n[00:04.86]作曲:暗杠\n[00:05.29]作词:暗杠/古道背棺人\n[00:05.81]编曲：暗杠\n[00:06.25]器乐演奏：暗杠\n[00:06.73]和声编配：暗杠\n[00:07.30]混音后期：A.Q.Studio\n[00:16.33]囡囡呀不要调皮\n[00:19.15]坐下听听阿婆说\n[00:23.12]这个季节天气转凉地上雨水多\n[00:30.17]囡囡呀不要惊慌\n[00:33.13]过来听听阿婆说\n[00:36.91]睡个觉雷声过后就能看云朵\n[00:44.58]囡囡别怕\n[00:46.26]囡囡别哭\n[00:47.84]快快睡咯\n[00:51.23]你静静听首歌\n[00:58.51]蛐蛐轻些\n[00:59.94]静静安歇\n[01:01.73]月儿圆哟\n[01:05.02]你乖乖呀抱阿婆\n[01:11.93]风铃呀轻响鸟儿轻唱远处谁在和\n[01:18.75]亲了彩虹惊了云朵\n[01:21.81]我已成归客\n[01:25.88]囡囡呀你会长大会走很远会觉得累了\n[01:33.02]只要记得河婆话“阿婆”怎么说\n[02:07.56]囡囡呀你会困惑\n[02:10.65]慢些脚步别忘了\n[02:14.39]慢慢的你会明白丢了是什么\n[02:21.46]人生路本就是场获得与失的选择\n[02:28.14]迷路时想想当年阿婆怎么说\n[02:35.85]回头看看\n[02:37.46]雨水过后\n[02:39.26]云彩很多\n[02:42.49]来吧阿婆帮你偷偷摘一朵\n[02:49.67]等你老了\n[02:51.29]阿婆走了\n[02:53.09]你要记得\n[02:56.64]把这乡音教会娃儿怎么说\n[03:03.66]把这乡音教给你的囡囡哟\n[03:38.37]回头看看\n[03:40.11]雨水过后\n[03:41.75]少了冷漠\n[03:45.09]来吧阿婆等你还在那村落'    }],autoplay: false,volume: 0.75,big: false,dark: false    })</script><p>源码参考如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&#123;% mplayer %&#125;</span><br><span class="line">    playlist: [</span><br><span class="line">        &#123;</span><br><span class="line">            src: &#x27;https://img.wshunli.com/about/阿婆说.mp3&#x27;,</span><br><span class="line">            poster: &#x27;https://img.wshunli.com/about/阿婆说.jpg&#x27;,</span><br><span class="line">            name: &#x27;阿婆说&#x27;,</span><br><span class="line">            artist: &#x27;陈一发儿&#x27;,</span><br><span class="line">            lyric: &#x27;[00:03.85]阿婆说-陈一发儿\n[00:04.86]作曲:暗杠\n[00:05.29]作词:暗杠/古道背棺人\n[00:05.81]编曲：暗杠\n[00:06.25]器乐演奏：暗杠\n[00:06.73]和声编配：暗杠\n[00:07.30]混音后期：A.Q.Studio\n[00:16.33]囡囡呀不要调皮\n[00:19.15]坐下听听阿婆说\n[00:23.12]这个季节天气转凉地上雨水多\n[00:30.17]囡囡呀不要惊慌\n[00:33.13]过来听听阿婆说\n[00:36.91]睡个觉雷声过后就能看云朵\n[00:44.58]囡囡别怕\n[00:46.26]囡囡别哭\n[00:47.84]快快睡咯\n[00:51.23]你静静听首歌\n[00:58.51]蛐蛐轻些\n[00:59.94]静静安歇\n[01:01.73]月儿圆哟\n[01:05.02]你乖乖呀抱阿婆\n[01:11.93]风铃呀轻响鸟儿轻唱远处谁在和\n[01:18.75]亲了彩虹惊了云朵\n[01:21.81]我已成归客\n[01:25.88]囡囡呀你会长大会走很远会觉得累了\n[01:33.02]只要记得河婆话“阿婆”怎么说\n[02:07.56]囡囡呀你会困惑\n[02:10.65]慢些脚步别忘了\n[02:14.39]慢慢的你会明白丢了是什么\n[02:21.46]人生路本就是场获得与失的选择\n[02:28.14]迷路时想想当年阿婆怎么说\n[02:35.85]回头看看\n[02:37.46]雨水过后\n[02:39.26]云彩很多\n[02:42.49]来吧阿婆帮你偷偷摘一朵\n[02:49.67]等你老了\n[02:51.29]阿婆走了\n[02:53.09]你要记得\n[02:56.64]把这乡音教会娃儿怎么说\n[03:03.66]把这乡音教给你的囡囡哟\n[03:38.37]回头看看\n[03:40.11]雨水过后\n[03:41.75]少了冷漠\n[03:45.09]来吧阿婆等你还在那村落&#x27;</span><br><span class="line">        &#125;</span><br><span class="line">    ],</span><br><span class="line">    autoplay: false,</span><br><span class="line">    volume: 0.75,</span><br><span class="line">    big: false,</span><br><span class="line">    dark: false</span><br><span class="line">&#123;% endmplayer %&#125;</span><br></pre></td></tr></table></figure><p>2、暗黑样式</p><div id="mplayer9380"></div><script src="https://cdn.jsdelivr.net/gh/MoePlayer/cPlayer/dist/cplayer.js"></script><script>    new cplayer({      element: document.getElementById('mplayer9380'),      playlist: [    {        src: 'https://img.wshunli.com/about/阿婆说.mp3',        poster: 'https://img.wshunli.com/about/阿婆说.jpg',        name: '阿婆说',        artist: '陈一发儿',        lyric: '[00:03.85]阿婆说-陈一发儿\n[00:04.86]作曲:暗杠\n[00:05.29]作词:暗杠/古道背棺人\n[00:05.81]编曲：暗杠\n[00:06.25]器乐演奏：暗杠\n[00:06.73]和声编配：暗杠\n[00:07.30]混音后期：A.Q.Studio\n[00:16.33]囡囡呀不要调皮\n[00:19.15]坐下听听阿婆说\n[00:23.12]这个季节天气转凉地上雨水多\n[00:30.17]囡囡呀不要惊慌\n[00:33.13]过来听听阿婆说\n[00:36.91]睡个觉雷声过后就能看云朵\n[00:44.58]囡囡别怕\n[00:46.26]囡囡别哭\n[00:47.84]快快睡咯\n[00:51.23]你静静听首歌\n[00:58.51]蛐蛐轻些\n[00:59.94]静静安歇\n[01:01.73]月儿圆哟\n[01:05.02]你乖乖呀抱阿婆\n[01:11.93]风铃呀轻响鸟儿轻唱远处谁在和\n[01:18.75]亲了彩虹惊了云朵\n[01:21.81]我已成归客\n[01:25.88]囡囡呀你会长大会走很远会觉得累了\n[01:33.02]只要记得河婆话“阿婆”怎么说\n[02:07.56]囡囡呀你会困惑\n[02:10.65]慢些脚步别忘了\n[02:14.39]慢慢的你会明白丢了是什么\n[02:21.46]人生路本就是场获得与失的选择\n[02:28.14]迷路时想想当年阿婆怎么说\n[02:35.85]回头看看\n[02:37.46]雨水过后\n[02:39.26]云彩很多\n[02:42.49]来吧阿婆帮你偷偷摘一朵\n[02:49.67]等你老了\n[02:51.29]阿婆走了\n[02:53.09]你要记得\n[02:56.64]把这乡音教会娃儿怎么说\n[03:03.66]把这乡音教给你的囡囡哟\n[03:38.37]回头看看\n[03:40.11]雨水过后\n[03:41.75]少了冷漠\n[03:45.09]来吧阿婆等你还在那村落'    }],autoplay: false,volume: 0.75,big: false,dark: true    })</script><p>源码参考如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&#123;% mplayer %&#125;</span><br><span class="line">    playlist: [</span><br><span class="line">        &#123;</span><br><span class="line">            src: &#x27;https://img.wshunli.com/about/阿婆说.mp3&#x27;,</span><br><span class="line">            poster: &#x27;https://img.wshunli.com/about/阿婆说.jpg&#x27;,</span><br><span class="line">            name: &#x27;阿婆说&#x27;,</span><br><span class="line">            artist: &#x27;陈一发儿&#x27;,</span><br><span class="line">            lyric: &#x27;省略歌词...&#x27;</span><br><span class="line">        &#125;</span><br><span class="line">    ],</span><br><span class="line">    autoplay: false,</span><br><span class="line">    volume: 0.75,</span><br><span class="line">    big: false,</span><br><span class="line">    dark: true</span><br><span class="line">&#123;% endmplayer %&#125;</span><br></pre></td></tr></table></figure><p>3、大图样式</p><div id="mplayer5097"></div><script src="https://cdn.jsdelivr.net/gh/MoePlayer/cPlayer/dist/cplayer.js"></script><script>    new cplayer({      element: document.getElementById('mplayer5097'),      playlist: [    {        src: 'https://img.wshunli.com/about/阿婆说.mp3',        poster: 'https://img.wshunli.com/about/阿婆说.jpg',        name: '阿婆说',        artist: '陈一发儿',        lyric: '[00:03.85]阿婆说-陈一发儿\n[00:04.86]作曲:暗杠\n[00:05.29]作词:暗杠/古道背棺人\n[00:05.81]编曲：暗杠\n[00:06.25]器乐演奏：暗杠\n[00:06.73]和声编配：暗杠\n[00:07.30]混音后期：A.Q.Studio\n[00:16.33]囡囡呀不要调皮\n[00:19.15]坐下听听阿婆说\n[00:23.12]这个季节天气转凉地上雨水多\n[00:30.17]囡囡呀不要惊慌\n[00:33.13]过来听听阿婆说\n[00:36.91]睡个觉雷声过后就能看云朵\n[00:44.58]囡囡别怕\n[00:46.26]囡囡别哭\n[00:47.84]快快睡咯\n[00:51.23]你静静听首歌\n[00:58.51]蛐蛐轻些\n[00:59.94]静静安歇\n[01:01.73]月儿圆哟\n[01:05.02]你乖乖呀抱阿婆\n[01:11.93]风铃呀轻响鸟儿轻唱远处谁在和\n[01:18.75]亲了彩虹惊了云朵\n[01:21.81]我已成归客\n[01:25.88]囡囡呀你会长大会走很远会觉得累了\n[01:33.02]只要记得河婆话“阿婆”怎么说\n[02:07.56]囡囡呀你会困惑\n[02:10.65]慢些脚步别忘了\n[02:14.39]慢慢的你会明白丢了是什么\n[02:21.46]人生路本就是场获得与失的选择\n[02:28.14]迷路时想想当年阿婆怎么说\n[02:35.85]回头看看\n[02:37.46]雨水过后\n[02:39.26]云彩很多\n[02:42.49]来吧阿婆帮你偷偷摘一朵\n[02:49.67]等你老了\n[02:51.29]阿婆走了\n[02:53.09]你要记得\n[02:56.64]把这乡音教会娃儿怎么说\n[03:03.66]把这乡音教给你的囡囡哟\n[03:38.37]回头看看\n[03:40.11]雨水过后\n[03:41.75]少了冷漠\n[03:45.09]来吧阿婆等你还在那村落'    }],autoplay: false,volume: 0.75,big: true,dark: false    })</script><p>源码参考如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&#123;% mplayer %&#125;</span><br><span class="line">    playlist: [</span><br><span class="line">        &#123;</span><br><span class="line">            src: &#x27;https://img.wshunli.com/about/阿婆说.mp3&#x27;,</span><br><span class="line">            poster: &#x27;https://img.wshunli.com/about/阿婆说.jpg&#x27;,</span><br><span class="line">            name: &#x27;阿婆说&#x27;,</span><br><span class="line">            artist: &#x27;陈一发儿&#x27;,</span><br><span class="line">            lyric: &#x27;省略歌词...&#x27;</span><br><span class="line">        &#125;</span><br><span class="line">    ],</span><br><span class="line">    autoplay: false,</span><br><span class="line">    volume: 0.75,</span><br><span class="line">    big: true,</span><br><span class="line">    dark: false</span><br><span class="line">&#123;% endmplayer %&#125;</span><br></pre></td></tr></table></figure><p>4、大图暗黑样式</p><div id="mplayer3900"></div><script src="https://cdn.jsdelivr.net/gh/MoePlayer/cPlayer/dist/cplayer.js"></script><script>    new cplayer({      element: document.getElementById('mplayer3900'),      playlist: [    {        src: 'https://img.wshunli.com/about/阿婆说.mp3',        poster: 'https://img.wshunli.com/about/阿婆说.jpg',        name: '阿婆说',        artist: '陈一发儿',        lyric: '[00:03.85]阿婆说-陈一发儿\n[00:04.86]作曲:暗杠\n[00:05.29]作词:暗杠/古道背棺人\n[00:05.81]编曲：暗杠\n[00:06.25]器乐演奏：暗杠\n[00:06.73]和声编配：暗杠\n[00:07.30]混音后期：A.Q.Studio\n[00:16.33]囡囡呀不要调皮\n[00:19.15]坐下听听阿婆说\n[00:23.12]这个季节天气转凉地上雨水多\n[00:30.17]囡囡呀不要惊慌\n[00:33.13]过来听听阿婆说\n[00:36.91]睡个觉雷声过后就能看云朵\n[00:44.58]囡囡别怕\n[00:46.26]囡囡别哭\n[00:47.84]快快睡咯\n[00:51.23]你静静听首歌\n[00:58.51]蛐蛐轻些\n[00:59.94]静静安歇\n[01:01.73]月儿圆哟\n[01:05.02]你乖乖呀抱阿婆\n[01:11.93]风铃呀轻响鸟儿轻唱远处谁在和\n[01:18.75]亲了彩虹惊了云朵\n[01:21.81]我已成归客\n[01:25.88]囡囡呀你会长大会走很远会觉得累了\n[01:33.02]只要记得河婆话“阿婆”怎么说\n[02:07.56]囡囡呀你会困惑\n[02:10.65]慢些脚步别忘了\n[02:14.39]慢慢的你会明白丢了是什么\n[02:21.46]人生路本就是场获得与失的选择\n[02:28.14]迷路时想想当年阿婆怎么说\n[02:35.85]回头看看\n[02:37.46]雨水过后\n[02:39.26]云彩很多\n[02:42.49]来吧阿婆帮你偷偷摘一朵\n[02:49.67]等你老了\n[02:51.29]阿婆走了\n[02:53.09]你要记得\n[02:56.64]把这乡音教会娃儿怎么说\n[03:03.66]把这乡音教给你的囡囡哟\n[03:38.37]回头看看\n[03:40.11]雨水过后\n[03:41.75]少了冷漠\n[03:45.09]来吧阿婆等你还在那村落'    }],autoplay: false,volume: 0.75,big: true,dark: true    })</script><p>源码参考如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&#123;% mplayer %&#125;</span><br><span class="line">    playlist: [</span><br><span class="line">        &#123;</span><br><span class="line">            src: &#x27;https://img.wshunli.com/about/阿婆说.mp3&#x27;,</span><br><span class="line">            poster: &#x27;https://img.wshunli.com/about/阿婆说.jpg&#x27;,</span><br><span class="line">            name: &#x27;阿婆说&#x27;,</span><br><span class="line">            artist: &#x27;陈一发儿&#x27;,</span><br><span class="line">            lyric: &#x27;省略歌词...&#x27;</span><br><span class="line">        &#125;</span><br><span class="line">    ],</span><br><span class="line">    autoplay: false,</span><br><span class="line">    volume: 0.75,</span><br><span class="line">    big: true,</span><br><span class="line">    dark: true</span><br><span class="line">&#123;% endmplayer %&#125;</span><br></pre></td></tr></table></figure><p>非常感谢 <a href="https://github.com/MoePlayer/cPlayer">cPlayer</a> 及 <a href="https://github.com/EYHN/hexo-tag-cplayer">hexo-tag-cplayer</a> 作者。</p><p><font color="red">最后欢迎 star 或者提交 PR 到本仓库。</font></p><p><a href="https://github.com/wshunli/hexo-tag-mplayer">https://github.com/wshunli/hexo-tag-mplayer</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍在 Hexo 中插入 cPlayer 播放器&lt;/p&gt;</summary>
    
    
    
    <category term="前端技术" scheme="https://www.wshunli.com/categories/%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="Hexo" scheme="https://www.wshunli.com/tags/Hexo/"/>
    
    <category term="mplayer" scheme="https://www.wshunli.com/tags/mplayer/"/>
    
    <category term="cPlayer" scheme="https://www.wshunli.com/tags/cPlayer/"/>
    
    <category term="hexo-tag-mplayer" scheme="https://www.wshunli.com/tags/hexo-tag-mplayer/"/>
    
  </entry>
  
  <entry>
    <title>Drone持续集成服务私有部署</title>
    <link href="https://www.wshunli.com/posts/b9adca20.html"/>
    <id>https://www.wshunli.com/posts/b9adca20.html</id>
    <published>2018-12-31T09:44:59.000Z</published>
    <updated>2026-06-17T23:42:59.822Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍基于 Github 的 Drone 持续集成服务单机部署。</p><span id="more"></span><h1 id="Docker-环境"><a href="#Docker-环境" class="headerlink" title="Docker 环境"></a>Docker 环境</h1><p>首先应该安装 Docker 及 docker-compose 环境。</p><p><a href="https://www.wshunli.com/posts/89bfe8.html">https://www.wshunli.com/posts/89bfe8.html</a></p><h1 id="Github-应用注册"><a href="#Github-应用注册" class="headerlink" title="Github 应用注册"></a>Github 应用注册</h1><p>打开 <a href="https://github.com/settings/applications/new">Github New OAuth Application</a> 注册应用程序。</p><p><img src="https://img.wshunli.com/qnap/drone/github_application_create.png" alt="github_application_create"></p><p>创建完成，查看相应的 <code>Client ID</code> 及 <code>Client Secret</code> 值。</p><p><img src="https://img.wshunli.com/qnap/drone/github_application.png" alt="github_application"></p><h1 id="创建-Drone-服务"><a href="#创建-Drone-服务" class="headerlink" title="创建 Drone 服务"></a>创建 Drone 服务</h1><p>直接执行以下明命令即可</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">docker run \</span><br><span class="line">  --volume=/var/run/docker.sock:/var/run/docker.sock \</span><br><span class="line">  --volume=/var/lib/drone:/data \</span><br><span class="line">  --env=DRONE_GITHUB_SERVER=https://github.com \</span><br><span class="line">  --env=DRONE_GITHUB_CLIENT_ID=xxx \</span><br><span class="line">  --env=DRONE_GITHUB_CLIENT_SECRET=xxxxxx \</span><br><span class="line">  --env=DRONE_RUNNER_CAPACITY=2 \</span><br><span class="line">  --env=DRONE_SERVER_HOST=drone.wshunli.com \</span><br><span class="line">  --env=DRONE_SERVER_PROTO=http \</span><br><span class="line">  --env=DRONE_GIT_ALWAYS_AUTH=true \</span><br><span class="line">  --env=DRONE_TLS_AUTOCERT=true \</span><br><span class="line">  --publish=30080:80 \</span><br><span class="line">  --publish=30443:443 \</span><br><span class="line">  --restart=always \</span><br><span class="line">  --detach=true \</span><br><span class="line">  --name=drone \</span><br><span class="line">  drone/drone:1.0.0-rc.3</span><br></pre></td></tr></table></figure><p>其中<br>DRONE_GITHUB_CLIENT_ID 替换为自己应用的 <code>Client ID</code> 值；<br>DRONE_GITHUB_CLIENT_SECRET  替换为 <code>Client Secret</code> 值；<br>DRONE_SERVER_HOST  替换为欲解析的域名。</p><h1 id="Drone-服务的使用"><a href="#Drone-服务的使用" class="headerlink" title="Drone 服务的使用"></a>Drone 服务的使用</h1><p>1、打开域名地址，授予权限</p><p><img src="https://img.wshunli.com/qnap/drone/github_authorize_application.png" alt="github_authorize_application"></p><p>2、点击中间的 ACTICVE 按钮，即可开启持续集成服务。</p><p><img src="https://img.wshunli.com/qnap/drone/drone_active_repositories.png" alt="drone_active_repositories"></p><p>3、然后在 Github 源码仓库中，添加 <code>.drone.yml</code> 文件</p><figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">kind:</span> <span class="string">pipeline</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">default</span></span><br><span class="line"></span><br><span class="line"><span class="attr">steps:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">test</span></span><br><span class="line">  <span class="attr">image:</span> <span class="string">node</span></span><br><span class="line">  <span class="attr">commands:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">npm</span> <span class="string">install</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">npm</span> <span class="string">test</span></span><br></pre></td></tr></table></figure><p>具体可参考官方文档。</p><p>4、以下是持续集成结果示例</p><p><img src="https://img.wshunli.com/qnap/drone/drone_continuous_integration.png" alt="drone_continuous_integration"></p><blockquote><p>参考资料<br>1、Drone Single Machine<br><a href="https://docs.drone.io/installation/github/single-machine/">https://docs.drone.io/installation/github/single-machine/</a><br>2、如何在Ubuntu上安装Drone持续集成环境 - 云+社区 - 腾讯云<br><a href="https://cloud.tencent.com/developer/article/1180481">https://cloud.tencent.com/developer/article/1180481</a><br>3、Drone安装指南 - 怡红院落<br><a href="https://imnerd.org/drone-installation.html">https://imnerd.org/drone-installation.html</a><br>4、基于Gogs+Drone搭建的私有CI&#x2F;CD平台 | DongSheng’s Blog<br><a href="http://www.mdslq.cn/archives/1a623683.html">http://www.mdslq.cn/archives/1a623683.html</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍基于 Github 的 Drone 持续集成服务单机部署。&lt;/p&gt;</summary>
    
    
    
    <category term="后端技术" scheme="https://www.wshunli.com/categories/%E5%90%8E%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="qnap" scheme="https://www.wshunli.com/tags/qnap/"/>
    
    <category term="drone" scheme="https://www.wshunli.com/tags/drone/"/>
    
  </entry>
  
  <entry>
    <title>Docker环境搭建从入门到放弃</title>
    <link href="https://www.wshunli.com/posts/89bfe8.html"/>
    <id>https://www.wshunli.com/posts/89bfe8.html</id>
    <published>2018-12-31T08:47:28.000Z</published>
    <updated>2026-06-17T23:42:59.822Z</updated>
    
    <content type="html"><![CDATA[<p>本文基于 CentOS 搭建 Docker CE 环境，并完成 docker-compose 的安装。</p><span id="more"></span><h1 id="安装-Docker-环境"><a href="#安装-Docker-环境" class="headerlink" title="安装 Docker 环境"></a>安装 Docker 环境</h1><p>1、安装一些必要的依赖</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo yum install -y yum-utils \</span><br><span class="line">  device-mapper-persistent-data \</span><br><span class="line">  lvm2</span><br></pre></td></tr></table></figure><p>2、添加 Docker 存储库</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo yum-config-manager \</span><br><span class="line">    --add-repo \</span><br><span class="line">    https://download.docker.com/linux/centos/docker-ce.repo</span><br></pre></td></tr></table></figure><p>因为众所周知的原因，这里推荐使用阿里云的镜像。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo yum-config-manager \</span><br><span class="line">    --add-repo \</span><br><span class="line">    http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo</span><br></pre></td></tr></table></figure><p>3、安装 Docker CE 环境</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo yum install docker-ce</span><br></pre></td></tr></table></figure><p>4、启动 Docker 服务</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo systemctl start docker</span><br></pre></td></tr></table></figure><p>设置 Docker 服务为开机自启</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo systemctl enable docker.service</span><br></pre></td></tr></table></figure><p>5、测试 Docker 安装</p><p>查看 Docker 信息,执行如下命令</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">docker version</span><br></pre></td></tr></table></figure><p><img src="https://img.wshunli.com/qnap/docker/docker-version.png" alt="version"></p><p>Hello World Docker ! ,执行如下命令</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo docker run hello-world</span><br></pre></td></tr></table></figure><p><img src="https://img.wshunli.com/qnap/docker/docker-hello.png" alt="hello"></p><h1 id="使用安装脚本一键安装-Docker"><a href="#使用安装脚本一键安装-Docker" class="headerlink" title="使用安装脚本一键安装 Docker"></a>使用安装脚本一键安装 Docker</h1><p>官方提供了一键安装脚本</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">curl -fsSL https://get.docker.com -o get-docker.sh</span><br><span class="line">sudo sh get-docker.sh</span><br></pre></td></tr></table></figure><p>同样众所周知的原因，这里推荐使用阿里云的镜像。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun</span><br></pre></td></tr></table></figure><p>这样安装的 Docker 没有灵魂，哈哈。</p><h1 id="配置阿里云镜像加速器"><a href="#配置阿里云镜像加速器" class="headerlink" title="配置阿里云镜像加速器"></a>配置阿里云镜像加速器</h1><p>直接从仓库拉取可能会比较慢，这里配置阿里云镜像加速。</p><p>登陆 <a href="https://cr.console.aliyun.com/">容器镜像服务控制台</a> ，左侧 镜像加速器 。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">sudo mkdir -p /etc/docker</span><br><span class="line">sudo tee /etc/docker/daemon.json &lt;&lt;-&#x27;EOF&#x27;</span><br><span class="line">&#123;</span><br><span class="line">  &quot;registry-mirrors&quot;: [&quot;https://xxxxxx.mirror.aliyuncs.com&quot;]</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line">sudo systemctl daemon-reload</span><br><span class="line">sudo systemctl restart docker</span><br></pre></td></tr></table></figure><h1 id="安装-docker-compose-服务启动器"><a href="#安装-docker-compose-服务启动器" class="headerlink" title="安装 docker-compose 服务启动器"></a>安装 docker-compose 服务启动器</h1><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">pip install docker-compose</span><br></pre></td></tr></table></figure><blockquote><p>参考资料<br>1、Get Docker CE for CentOS | Docker Documentation<br><a href="https://docs.docker.com/install/linux/docker-ce/centos/">https://docs.docker.com/install/linux/docker-ce/centos/</a><br>2、安装Docker - 快速入门| 阿里云<br><a href="https://www.alibabacloud.com/help/zh/doc-detail/60742.htm">https://www.alibabacloud.com/help/zh/doc-detail/60742.htm</a><br>3、如何在Ubuntu上安装使用Docker - 云+社区 - 腾讯云<br><a href="https://cloud.tencent.com/developer/article/1167995">https://cloud.tencent.com/developer/article/1167995</a><br>4、Docker CE 镜像源站-云栖社区-阿里云<br><a href="https://yq.aliyun.com/articles/110806/">https://yq.aliyun.com/articles/110806/</a><br>5、镜像加速器 · Docker —— 从入门到实践镜像加速器 · Docker —— 从入门到实践<br><a href="https://yeasy.gitbooks.io/docker_practice/install/mirror.html">https://yeasy.gitbooks.io/docker_practice/install/mirror.html</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文基于 CentOS 搭建 Docker CE 环境，并完成 docker-compose 的安装。&lt;/p&gt;</summary>
    
    
    
    <category term="后端技术" scheme="https://www.wshunli.com/categories/%E5%90%8E%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="qnap" scheme="https://www.wshunli.com/tags/qnap/"/>
    
    <category term="docker" scheme="https://www.wshunli.com/tags/docker/"/>
    
  </entry>
  
  <entry>
    <title>阿婆说</title>
    <link href="https://www.wshunli.com/posts/eb2a7b89.html"/>
    <id>https://www.wshunli.com/posts/eb2a7b89.html</id>
    <published>2018-12-09T10:00:00.000Z</published>
    <updated>2026-06-17T23:42:59.831Z</updated>
    
    <content type="html"><![CDATA[<p>阿婆说</p><span id="more"></span><center><div id="mplayer3927"></div><script src="https://cdn.jsdelivr.net/gh/MoePlayer/cPlayer/dist/cplayer.js"></script><script>    new cplayer({      element: document.getElementById('mplayer3927'),      playlist: [    {        src: 'https://img.wshunli.com/about/阿婆说.mp3',        poster: 'https://img.wshunli.com/about/阿婆说.jpg',        name: '阿婆说',        artist: '陈一发儿',        lyric: '[00:03.85]阿婆说-陈一发儿\n[00:04.86]作曲:暗杠\n[00:05.29]作词:暗杠/古道背棺人\n[00:05.81]编曲：暗杠\n[00:06.25]器乐演奏：暗杠\n[00:06.73]和声编配：暗杠\n[00:07.30]混音后期：A.Q.Studio\n[00:16.33]囡囡呀不要调皮\n[00:19.15]坐下听听阿婆说\n[00:23.12]这个季节天气转凉地上雨水多\n[00:30.17]囡囡呀不要惊慌\n[00:33.13]过来听听阿婆说\n[00:36.91]睡个觉雷声过后就能看云朵\n[00:44.58]囡囡别怕\n[00:46.26]囡囡别哭\n[00:47.84]快快睡咯\n[00:51.23]你静静听首歌\n[00:58.51]蛐蛐轻些\n[00:59.94]静静安歇\n[01:01.73]月儿圆哟\n[01:05.02]你乖乖呀抱阿婆\n[01:11.93]风铃呀轻响鸟儿轻唱远处谁在和\n[01:18.75]亲了彩虹惊了云朵\n[01:21.81]我已成归客\n[01:25.88]囡囡呀你会长大会走很远会觉得累了\n[01:33.02]只要记得河婆话“阿婆”怎么说\n[02:07.56]囡囡呀你会困惑\n[02:10.65]慢些脚步别忘了\n[02:14.39]慢慢的你会明白丢了是什么\n[02:21.46]人生路本就是场获得与失的选择\n[02:28.14]迷路时想想当年阿婆怎么说\n[02:35.85]回头看看\n[02:37.46]雨水过后\n[02:39.26]云彩很多\n[02:42.49]来吧阿婆帮你偷偷摘一朵\n[02:49.67]等你老了\n[02:51.29]阿婆走了\n[02:53.09]你要记得\n[02:56.64]把这乡音教会娃儿怎么说\n[03:03.66]把这乡音教给你的囡囡哟\n[03:38.37]回头看看\n[03:40.11]雨水过后\n[03:41.75]少了冷漠\n[03:45.09]来吧阿婆等你还在那村落'    }],autoplay: true,volume: 0.75,big: true,dark: false    })</script></center>]]></content>
    
    
    <summary type="html">&lt;p&gt;阿婆说&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>基于 frp 的内网穿透实践</title>
    <link href="https://www.wshunli.com/posts/be4c3c8c.html"/>
    <id>https://www.wshunli.com/posts/be4c3c8c.html</id>
    <published>2018-11-09T12:41:15.000Z</published>
    <updated>2026-06-17T23:42:59.829Z</updated>
    
    <content type="html"><![CDATA[<p>frp 是一个可用于内网穿透的高性能的反向代理应用，支持 tcp, udp, http, https 协议。</p><span id="more"></span><p>1、利用处于内网或防火墙后的机器，对外网环境提供 http 或 https 服务。<br>2、对于 http, https 服务支持基于域名的虚拟主机，支持自定义域名绑定，使多个域名可以共用一个80端口。<br>3、利用处于内网或防火墙后的机器，对外网环境提供 tcp 和 udp 服务。</p><p>frp 搭建需要一台具有公网 IP 的服务器，并且访问效果和服务器的带宽和内网的上行带宽有关系。</p><h1 id="内网穿透实现方法"><a href="#内网穿透实现方法" class="headerlink" title="内网穿透实现方法"></a>内网穿透实现方法</h1><p>内网穿透有很多商业的软件比如花生壳、NATAPP 等，这里不多介绍。</p><p>还有一些半开源的工具比如 ZeroTier ，配置简单，但是需要额外安装软件。</p><p>再者就是 frp 、ngrok 等开源工具，利用具有公网 IP 的服务器搭建。</p><h1 id="frp-内网穿透实践"><a href="#frp-内网穿透实践" class="headerlink" title="frp 内网穿透实践"></a>frp 内网穿透实践</h1><p>frp：<a href="https://github.com/fatedier/frp">https://github.com/fatedier/frp</a></p><p>中文文档：<a href="https://github.com/fatedier/frp/blob/master/README_zh.md">https://github.com/fatedier/frp/blob/master/README_zh.md</a></p><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>做内网穿透前需要提前准备一些东西的</p><p>1、一台公网服务器，建议国外的 VPS ，带宽大一些；<br>2、一台内网服务器，我这里是 QNAP 的 NAS ，Linux 系统；<br>3、域名，用于解析，访问内网；<br>4、内网服务，访问的主要内容。</p><h2 id="frp-的安装配置"><a href="#frp-的安装配置" class="headerlink" title="frp 的安装配置"></a>frp 的安装配置</h2><p>这里 具有公网 IP 的机器称为服务端，处于内网环境的机器称为客户端。</p><p>1、下载 frp 文件</p><p>在 <a href="https://github.com/fatedier/frp/releases">https://github.com/fatedier/frp/releases</a> 下载最新版即可。</p><p>下载并解压文件</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"> <span class="built_in">cd</span> usr/local/</span><br><span class="line">wget https://github.com/fatedier/frp/releases/download/v0.21.0/frp_0.21.0_linux_amd64.tar.gz</span><br><span class="line">tar -zxvf  frp_0.21.0_linux_amd64.tar.gz</span><br><span class="line"><span class="built_in">cd</span> frp_0.21.0_linux_amd64</span><br></pre></td></tr></table></figure><p>这里主要有 4 个文件，分别是 frpc、frpc.ini 和frps、frps.ini 。</p><p>将 frps 及 <strong>frps.ini</strong> 放到具有公网 IP 的机器上。<br>将 frpc 及 <strong>frpc.ini</strong> 放到处于内网环境的机器上。</p><p>2、配置具有公网 IP 的机器，也就是 frps.ini 文件</p><p>使用 <code>vim frps.ini</code> 命令修改配置文件</p><figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">[common]</span><br><span class="line">bind_port = 7000            # 内网穿透服务端口</span><br><span class="line">bind_udp_port = 7001        # 点对点内网穿透</span><br><span class="line"></span><br><span class="line">vhost_http_port = 10080     # 外部访问的 http 端口</span><br><span class="line">vhost_https_port = 10443    # 外部访问的 https 端口</span><br><span class="line"></span><br><span class="line">dashboard_port = 7500       # 管理面板端口</span><br><span class="line">dashboard_user = admin      # 管理面板用户名</span><br><span class="line">dashboard_pwd = admin       # 管理面板密码</span><br><span class="line"></span><br><span class="line">token = asdfgh              # 身份验证</span><br></pre></td></tr></table></figure><p>更多内容参考：<a href="https://github.com/fatedier/frp/blob/master/conf/frps_full.ini">frps 完整配置文件</a></p><p>启动 frps 服务</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">./frps -c ./frps.ini</span><br></pre></td></tr></table></figure><p>后台启动方法</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">nohup</span> ./frps -c ./frps.ini &amp;</span><br></pre></td></tr></table></figure><p>3、配置处于内网环境的机器，也就是 frpc.ini 文件</p><p>使用 <code>vim frpc.ini</code> 命令修改配置文件</p><figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">[common]</span><br><span class="line">server_addr = X.X.X.X           # 内网穿透服务地址</span><br><span class="line">server_port = 7000              # 内网穿透服务端口</span><br><span class="line"></span><br><span class="line">token = asdfgh                  # 身份验证，与服务端一致</span><br><span class="line"></span><br><span class="line">[qnap-web]</span><br><span class="line">type = http</span><br><span class="line">local_ip = 127.0.0.1            # 内网服务地址</span><br><span class="line">local_port = 8080               # 内网服务端口</span><br><span class="line">use_encryption = true           # 加密传输</span><br><span class="line">use_compression = true          # 压缩传输</span><br><span class="line">custom_domains = x.wshunli.com  # 访问域名</span><br></pre></td></tr></table></figure><p>这里 <code>custom_domains</code> 是外网服务器解析的域名，否则无法访问；可根据 <code>vhost_http_port</code> 端口反向代理解析。</p><p>更多内容参考：<a href="https://github.com/fatedier/frp/blob/master/conf/frpc_full.ini">frpc 完整配置文件</a></p><p>启动 frpc 服务方法与 frps 类似。</p><p>前台启动：<code>./frpc -c ./frpc.ini</code></p><p>后台启动：<code>nohup ./frpc -c ./frpc.ini &amp;</code></p><p>4、frp 控制面板的使用</p><p>这里需要根据公网 IP 服务器访问，即 <code>http://{server_addr}:{dashboard_port}</code> </p><p>更多内容参考 <a href="https://github.com/fatedier/frp/blob/master/README_zh.md">frp 中文文档</a></p><h1 id="Linux-服务进程停止命令"><a href="#Linux-服务进程停止命令" class="headerlink" title="Linux 服务进程停止命令"></a>Linux 服务进程停止命令</h1><p>1、查找被占用的端口</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@localhost ~]# netstat -tln | grep 30080</span><br><span class="line">tcp6       0      0 :::30080                :::*                    LISTEN    </span><br></pre></td></tr></table></figure><p>2、查看被占用端口的PID</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@localhost ~]# lsof -i:30080</span><br><span class="line">COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME</span><br><span class="line">docker-pr 4390 root    4u  IPv6  36195      0t0  TCP *:30080 (LISTEN)</span><br><span class="line">docker-pr 4390 root    7u  IPv6 170434      0t0  TCP localhost:30080-&gt;localhost:42162 (ESTABLISHED)</span><br><span class="line">frpc      4509 root    6u  IPv4 170433      0t0  TCP localhost:42162-&gt;localhost:30080 (ESTABLISHED)</span><br></pre></td></tr></table></figure><p>3、杀掉相关进程</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@localhost ~]# kill -9 4509</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img src="https://img.wshunli.com/qnap/frp/linux.png" alt="服务停止启动"></p><blockquote><p>参考资料<br>1、一分钟实现内网穿透（ngrok服务器搭建） - 学习笔记 - CSDN博客<br><a href="https://blog.csdn.net/zhangguo5/article/details/77848658">https://blog.csdn.net/zhangguo5/article/details/77848658</a><br>2、十分钟教你配置frp实现内网穿透 - 诗雨远方的博客 - CSDN博客<br><a href="https://blog.csdn.net/u013144287/article/details/78589643">https://blog.csdn.net/u013144287/article/details/78589643</a><br>3、突破电信局域网：frp内网穿透教程（客户端：lede&#x2F;win） - 电脑讨论 - Chiphell - 分享与交流用户体验<br><a href="https://www.chiphell.com/thread-1853360-1-1.html">https://www.chiphell.com/thread-1853360-1-1.html</a><br>4、使用frp进行内网穿透入门 - ＱＱ小冰 - CSDN博客<br><a href="https://blog.csdn.net/weixin_36241363/article/details/78457359">https://blog.csdn.net/weixin_36241363/article/details/78457359</a><br>5、威联通折腾篇二：使用 frp 内网穿透 | Verne in GitHub<br><a href="https://blog.einverne.info/post/2018/06/qnap-frp-usage.html">https://blog.einverne.info/post/2018/06/qnap-frp-usage.html</a><br>6、nohup和&amp;后台运行，进程查看及终止 - 弥尘 - 博客园<br><a href="https://www.cnblogs.com/baby123/p/6477429.html">https://www.cnblogs.com/baby123/p/6477429.html</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;frp 是一个可用于内网穿透的高性能的反向代理应用，支持 tcp, udp, http, https 协议。&lt;/p&gt;</summary>
    
    
    
    <category term="后端技术" scheme="https://www.wshunli.com/categories/%E5%90%8E%E7%AB%AF%E6%8A%80%E6%9C%AF/"/>
    
    
    <category term="qnap" scheme="https://www.wshunli.com/tags/qnap/"/>
    
    <category term="frp" scheme="https://www.wshunli.com/tags/frp/"/>
    
  </entry>
  
  <entry>
    <title>基于 WinSW 将 Java 程序部署为 Windows 自启动服务</title>
    <link href="https://www.wshunli.com/posts/762f39b0.html"/>
    <id>https://www.wshunli.com/posts/762f39b0.html</id>
    <published>2018-10-20T03:22:36.000Z</published>
    <updated>2026-06-17T23:42:59.829Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍基于 WinSW 将 Java 程序部署 为 Windows 自启动服务。</p><span id="more"></span><p>我们使用 Java -jar file.jar 将 Java 程序运行，起来，但是窗口关闭，服务就停止了。</p><p>WinSW 可以将 Windows 上的任何一个程序注册为服务，如果不需要，也可以方便的卸载服务。</p><p><a href="https://github.com/kohsuke/winsw">https://github.com/kohsuke/winsw</a></p><p>1、下载 winsw 文件</p><p>下载 winsw-2.1.2-bin.exe 文件</p><p><a href="http://repo.jenkins-ci.org/releases/com/sun/winsw/winsw/">http://repo.jenkins-ci.org/releases/com/sun/winsw/winsw/</a></p><p>最好修改下文件名称，本文修改为 <code>winsw-ai-server</code> </p><p>2、添加配置文件</p><p>根据 winsw 文件名，新建相同名称的 xml 文件</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">service</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">id</span>&gt;</span>phcj-ai-server<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>phcj-ai-server<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">description</span>&gt;</span>系统 AI 后台服务<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">executable</span>&gt;</span>java<span class="tag">&lt;/<span class="name">executable</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">onfailure</span> <span class="attr">action</span>=<span class="string">&quot;restart&quot;</span> <span class="attr">delay</span>=<span class="string">&quot;10 sec&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">arguments</span>&gt;</span> -jar &quot;D:\PHCJ\phcj-ai-server\target\phcj-ai-server-0.0.1-SNAPSHOT.jar&quot;<span class="tag">&lt;/<span class="name">arguments</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">log</span> <span class="attr">mode</span>=<span class="string">&quot;roll-by-size&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">sizeThreshold</span>&gt;</span>20480<span class="tag">&lt;/<span class="name">sizeThreshold</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">keepFiles</span>&gt;</span>8<span class="tag">&lt;/<span class="name">keepFiles</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">log</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">service</span>&gt;</span></span><br></pre></td></tr></table></figure><p>3、安装并启动服务</p><p>注意以管理员身份运行</p><p>安装服务 <code>./winsw-ai-server.exe install </code></p><p>启动服务 <code>net start phcj-ai-server</code></p><p>停止服务 <code>net stop phcj-ai-server</code></p><p>卸载服务 <code>./winsw-ai-server.exe uninstall</code> </p><blockquote><p>参考资料：<br>1、winsw将可运行的jar文件做成后台服务 - qq_31451471的博客 - CSDN博客<br><a href="https://blog.csdn.net/qq_31451471/article/details/79298665">https://blog.csdn.net/qq_31451471/article/details/79298665</a><br>2、用winsw让任何Windows程序都能运行为服务 - 简书<br><a href="https://www.jianshu.com/p/fc9e4ea61e13">https://www.jianshu.com/p/fc9e4ea61e13</a><br>3、使用WinSW将SpringBoot程序安装成Windows自启动服务 - blvyoucan的专栏 - CSDN博客<br><a href="https://blog.csdn.net/blvyoucan/article/details/81131234">https://blog.csdn.net/blvyoucan/article/details/81131234</a><br>4、Spring Boot项目生成jar包，并在windows服务器中注册成服务，开机启动 - LiveYourLife - 博客园<br><a href="https://www.cnblogs.com/LiveYourLife/p/8681137.html">https://www.cnblogs.com/LiveYourLife/p/8681137.html</a><br>5、springboot解决第三方依赖jar包的问题 - 浅夏丶未央 - 博客园<br><a href="https://www.cnblogs.com/xiaosiyuan/p/6894766.html">https://www.cnblogs.com/xiaosiyuan/p/6894766.html</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍基于 WinSW 将 Java 程序部署 为 Windows 自启动服务。&lt;/p&gt;</summary>
    
    
    
    <category term="语言基础" scheme="https://www.wshunli.com/categories/%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="Java" scheme="https://www.wshunli.com/tags/Java/"/>
    
    <category term="WinSW" scheme="https://www.wshunli.com/tags/WinSW/"/>
    
  </entry>
  
  <entry>
    <title>Java 调用 Matlab 程序（Java 和 Matlab 混合编程）</title>
    <link href="https://www.wshunli.com/posts/45399dc2.html"/>
    <id>https://www.wshunli.com/posts/45399dc2.html</id>
    <published>2018-10-20T01:24:10.000Z</published>
    <updated>2026-06-17T23:42:59.823Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍 Java 调用 Matlab 程序（Java 和 Matlab 混合编程）流程方法。</p><span id="more"></span><h1 id="环境搭建"><a href="#环境搭建" class="headerlink" title="环境搭建"></a>环境搭建</h1><p>1、 Java 环境，建议使用如下版本 。</p><p>Java™ SE Development Kit 8, Update 151 (JDK 8u151)</p><p>2、Matlab 工具（开发环境）</p><p>编写 Mattlab 代码，本文以最新版 Matlab 2018b 为例。</p><p>3、MATLAB Runtime（部署环境）</p><p>已经安装过 Matlab 就不需要再安装了，用来执行编译后的 Matlab 程序。</p><p>从以下网址下载即可，本文以 R2018b (9.5) 为例。</p><p><a href="https://ww2.mathworks.cn/products/compiler/matlab-runtime.html">https://ww2.mathworks.cn/products/compiler/matlab-runtime.html</a></p><p>MATLAB Runtime 相比 Matlab 更加轻量级，免费。</p><p><strong>环境确认：</strong></p><p>本文建议 Java 版本和 Matlab 自带 JVM 保持一致。</p><p>在 Matlab 命令中输入 <code>version -java</code> 查看机器 Matlab 版本。</p><p><img src="https://img.wshunli.com/Java/Matlab/matlab-version.png" alt="java-version"></p><p>在 CMD 中执行 <code>java -version</code> 查看机器 Java 版本，</p><p><img src="https://img.wshunli.com/Java/Matlab/java-version.png" alt="java-version"></p><h1 id="Matlab-程序打包"><a href="#Matlab-程序打包" class="headerlink" title="Matlab 程序打包"></a>Matlab 程序打包</h1><p>Matlab 支持 C++ 、Java 、.Net 等语言的打包。</p><p>我们将 .m 文件打包为 Java 支持的库，如下图：</p><p><img src="https://img.wshunli.com/Java/Matlab/matlab-package.png" alt="matlab-package"></p><p>感觉和 JVM 虚拟机很类似，Matlab 提供基础环境，我们编写 Matlab 代码即可。</p><h1 id="Java-调用-Matlab-方法"><a href="#Java-调用-Matlab-方法" class="headerlink" title="Java 调用 Matlab 方法"></a>Java 调用 Matlab 方法</h1><p>在 Java 中调用 Matlab 的方法时，有三种方式。</p><p>1、functionName(int arg0, Object… agr1): Object[] </p><p>其中 arg0 表示返回数据的个数，agr1 为数组；返回值也是数组。</p><p>2、functionName(List arg0, List agr1): void</p><p>其中 arg0 、agr1 为列表；返回值为空。</p><p>3、functionName(Object[] arg0, Object[] agr1): void</p><p>其中 arg0 、agr1 为数组；返回值为空。</p><h2 id="方式一，输入返回个数"><a href="#方式一，输入返回个数" class="headerlink" title="方式一，输入返回个数"></a>方式一，输入返回个数</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 定义输出结果  </span></span><br><span class="line">Object[] results = <span class="literal">null</span>;  </span><br><span class="line"><span class="type">Magic</span> <span class="variable">magic</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Magic</span>(); <span class="comment">// 实例化 </span></span><br><span class="line">  </span><br><span class="line"><span class="comment">// &#x27;2&#x27; 表示 2 个输出结果，类似 results=new Object[2];  </span></span><br><span class="line"><span class="comment">// &#x27;5&#x27; 表示输入的参数 </span></span><br><span class="line">results = magic.makesqr(<span class="number">2</span>, <span class="number">5</span>);  </span><br><span class="line"><span class="comment">// 输出第一个返回内容  </span></span><br><span class="line">System.out.println(results[<span class="number">0</span>]);  </span><br><span class="line"><span class="comment">// 输出第二个返回内容  </span></span><br><span class="line">System.out.println(results[<span class="number">1</span>]);  </span><br></pre></td></tr></table></figure><h2 id="方式二，列表参数"><a href="#方式二，列表参数" class="headerlink" title="方式二，列表参数"></a>方式二，列表参数</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 定义结果List  </span></span><br><span class="line">List&lt;Object&gt; results = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;Object&gt;(<span class="number">2</span>);  </span><br><span class="line"><span class="comment">// 定义参数List  </span></span><br><span class="line">List&lt;Object&gt; inputs = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;Object&gt;(<span class="number">1</span>);  </span><br><span class="line">  </span><br><span class="line"><span class="type">Magic</span> <span class="variable">magic</span> <span class="operator">=</span> <span class="literal">null</span>;  </span><br><span class="line">  </span><br><span class="line">magic = <span class="keyword">new</span> <span class="title class_">Magic</span>();  </span><br><span class="line"><span class="comment">// 注意:结果List要预先加入内容，Null即可  </span></span><br><span class="line">results.add(<span class="literal">null</span>);  </span><br><span class="line">results.add(<span class="literal">null</span>);  </span><br><span class="line"><span class="comment">// 输入的参数  </span></span><br><span class="line">inputs.add(<span class="number">5</span>);  </span><br><span class="line"><span class="comment">// 调用方法  </span></span><br><span class="line">magic.makesqr(results, inputs);  </span><br><span class="line"><span class="comment">// 显示结果  </span></span><br><span class="line">System.out.println(results.get(<span class="number">0</span>));  </span><br><span class="line">System.out.println(results.get(<span class="number">1</span>));  </span><br></pre></td></tr></table></figure><h2 id="方式三，数组参数"><a href="#方式三，数组参数" class="headerlink" title="方式三，数组参数"></a>方式三，数组参数</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 注意：定义输出结果的时候，要定义数组大小  </span></span><br><span class="line">Object[] results = <span class="keyword">new</span> <span class="title class_">Object</span>[<span class="number">2</span>];  </span><br><span class="line"><span class="type">Magic</span> <span class="variable">magic</span> <span class="operator">=</span> <span class="literal">null</span>;  </span><br><span class="line">magic = <span class="keyword">new</span> <span class="title class_">Magic</span>();  </span><br><span class="line">  </span><br><span class="line"><span class="comment">// 这种方式，第二参数必须为数组，而不能为可变数组  </span></span><br><span class="line">Object[] inputs = <span class="keyword">new</span> <span class="title class_">Object</span>[]&#123;<span class="number">5</span>&#125;;  </span><br><span class="line">magic.makesqr(results, inputs);  </span><br><span class="line"><span class="comment">// 输出第一个返回内容  </span></span><br><span class="line">System.out.println(results[<span class="number">0</span>]);  </span><br><span class="line"><span class="comment">// 输出第二个返回内容  </span></span><br><span class="line">System.out.println(results[<span class="number">1</span>]);  </span><br></pre></td></tr></table></figure><h1 id="Java-与-Matlab-数据转换"><a href="#Java-与-Matlab-数据转换" class="headerlink" title="Java 与 Matlab 数据转换"></a>Java 与 Matlab 数据转换</h1><p>数据转换内容比较多，这里主要介绍<strong>数组</strong>的转换。</p><p>1、将 Java 数据转化为 Matlab 数据</p><p>这里以输入 <a href="https://ww2.mathworks.cn/help/javabuilder/MWArrayAPI/com/mathworks/toolbox/javabuilder/MWNumericArray.html">MWNumericArray</a> 数据为例</p><p>使用 newInstance 方法实例化</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//new int[]&#123;11,2&#125; 代表矩阵为 11 行 2 列的矩阵</span></span><br><span class="line"><span class="comment">//MWClassID.DOUBLE 代表矩阵中数为 double 类型，MWComplexity.REAL 代表矩阵中是实数</span></span><br><span class="line"><span class="type">MWNumericArray</span> <span class="variable">f</span> <span class="operator">=</span> MWNumericArray.newInstance(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;<span class="number">11</span>,<span class="number">2</span>&#125;, MWClassID.DOUBLE, MWComplexity.REAL);</span><br><span class="line"><span class="type">MWNumericArray</span> <span class="variable">f</span> <span class="operator">=</span> MWNumericArray.newInstance(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;<span class="number">11</span>,<span class="number">2</span>&#125;, data, MWClassID.DOUBLE);</span><br></pre></td></tr></table></figure><p>另外一种方式：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span>[] dims = &#123;<span class="number">3</span>, <span class="number">6</span>&#125;;</span><br><span class="line"><span class="type">double</span>[] Adata = &#123;<span class="number">1</span>, <span class="number">7</span>, <span class="number">13</span>, <span class="number">2</span>, <span class="number">8</span>, <span class="number">14</span>, <span class="number">3</span>, <span class="number">9</span>, <span class="number">15</span>, <span class="number">4</span>, <span class="number">10</span>, <span class="number">16</span>, <span class="number">5</span>, <span class="number">11</span>, <span class="number">17</span>, <span class="number">6</span>, <span class="number">12</span>, <span class="number">18</span>&#125;;</span><br><span class="line"><span class="type">MWNumericArray</span> <span class="variable">A</span> <span class="operator">=</span> MWNumericArray.newInstance(dims, Adata, MWClassID.DOUBLE);</span><br><span class="line">System.out.println(<span class="string">&quot;A = &quot;</span>);</span><br><span class="line">System.out.println(A);</span><br></pre></td></tr></table></figure><p>2、将 Matlab 数据转化为 Java 数据</p><p>这里以返回 <a href="https://www.mathworks.com/help/javabuilder/MWArrayAPI/com/mathworks/toolbox/javabuilder/MWCellArray.html">MWCellArray</a> 数据为例</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">MWCellArray</span> <span class="variable">cellArray</span> <span class="operator">=</span> (MWCellArray) result[<span class="number">0</span>];</span><br><span class="line"><span class="type">double</span>[] output = <span class="keyword">new</span> <span class="title class_">double</span>[cellArray.numberOfElements()];</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= cellArray.numberOfElements(); i++) &#123;</span><br><span class="line">    output[i - <span class="number">1</span>] = ((<span class="type">double</span>[][]) cellArray.get(i))[<span class="number">0</span>][<span class="number">0</span>];</span><br><span class="line">&#125;</span><br><span class="line">System.out.print(<span class="string">&quot;输出数据：&quot;</span>);</span><br><span class="line">System.out.println(Arrays.toString(output));</span><br></pre></td></tr></table></figure><h1 id="实战"><a href="#实战" class="headerlink" title="实战"></a>实战</h1><p>输入数据为 <code>17行1列</code> 的 MWNumericArray 数据，返回 <code>1行15列</code> 的 MWCellArray 数据。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">System.out.println(<span class="string">&quot;Running the JAVA client application!!&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">AiNet</span> <span class="variable">cls</span> <span class="operator">=</span> <span class="literal">null</span>; <span class="comment">// 调用对象</span></span><br><span class="line"><span class="type">MWNumericArray</span> <span class="variable">input</span> <span class="operator">=</span> <span class="literal">null</span>; <span class="comment">// 输入</span></span><br><span class="line">Object[] result = <span class="keyword">new</span> <span class="title class_">Object</span>[<span class="number">1</span>]; <span class="comment">// 输出结果</span></span><br><span class="line"></span><br><span class="line"><span class="type">double</span>[] data = <span class="keyword">new</span> <span class="title class_">double</span>[]&#123; <span class="comment">// 初始数据</span></span><br><span class="line">        <span class="number">3.43300000000000</span>,</span><br><span class="line">        <span class="number">3.40500000000000</span>,</span><br><span class="line">        <span class="number">3.37200000000000</span>,</span><br><span class="line">        <span class="number">3.35700000000000</span>,</span><br><span class="line">        <span class="number">3.34800000000000</span>,</span><br><span class="line">        <span class="number">3.31500000000000</span>,</span><br><span class="line">        <span class="number">3.28800000000000</span>,</span><br><span class="line">        <span class="number">3.28000000000000</span>,</span><br><span class="line">        <span class="number">3.26800000000000</span>,</span><br><span class="line">        <span class="number">3.24600000000000</span>,</span><br><span class="line">        <span class="number">3.23900000000000</span>,</span><br><span class="line">        <span class="number">3.24600000000000</span>,</span><br><span class="line">        <span class="number">3.25800000000000</span>,</span><br><span class="line">        <span class="number">3.27100000000000</span>,</span><br><span class="line">        <span class="number">3.28200000000000</span>,</span><br><span class="line">        <span class="number">3.28600000000000</span>,</span><br><span class="line">        <span class="number">3.30000000000000</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">input = MWNumericArray.newInstance(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;<span class="number">17</span>, <span class="number">1</span>&#125;, data, MWClassID.DOUBLE);</span><br><span class="line"></span><br><span class="line">System.out.print(<span class="string">&quot;输入数据：&quot;</span>);</span><br><span class="line">System.out.println(Arrays.toString(input.getDoubleData()));</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    cls = <span class="keyword">new</span> <span class="title class_">AiNet</span>();</span><br><span class="line">    result = cls.Net(<span class="number">1</span>, input);</span><br><span class="line"></span><br><span class="line">    <span class="type">MWCellArray</span> <span class="variable">cellArray</span> <span class="operator">=</span> (MWCellArray) result[<span class="number">0</span>];</span><br><span class="line">    <span class="type">double</span>[] output = <span class="keyword">new</span> <span class="title class_">double</span>[cellArray.numberOfElements()];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= cellArray.numberOfElements(); i++) &#123;</span><br><span class="line">        output[i - <span class="number">1</span>] = ((<span class="type">double</span>[][]) cellArray.get(i))[<span class="number">0</span>][<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    System.out.print(<span class="string">&quot;输出数据：&quot;</span>);</span><br><span class="line">    System.out.println(Arrays.toString(output));</span><br><span class="line">    MWArray.disposeArray(output);</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">catch</span> (MWException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    MWArray.disposeArray(input);</span><br><span class="line">    MWArray.disposeArray(result);</span><br><span class="line">    <span class="keyword">if</span> (cls != <span class="literal">null</span>) &#123;</span><br><span class="line">        cls.dispose();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意使用 MWArray.disposeArray() 方法释放资源。</p><blockquote><p>参考资料<br>1、Java调用MATLAB - 窃·格瓦拉 - CSDN博客<br><a href="https://blog.csdn.net/golden1314521/article/details/43526581">https://blog.csdn.net/golden1314521/article/details/43526581</a><br>2、Java调用Matlab方法的三种方式 - 非技术流 - ITeye博客<br><a href="http://xiaolongfeixiang.iteye.com/blog/1893621">http://xiaolongfeixiang.iteye.com/blog/1893621</a><br>3、ML02_09_calling_java_from_MATLAB_CH.ppt<br><a href="http://read.pudn.com/downloads85/doc/329263/matlab%E8%AE%B2%E4%B9%89/ML02(11-28)/ML02_09_calling_java_from_MATLAB_CH.pdf">http://read.pudn.com/downloads85/doc/329263/matlab%E8%AE%B2%E4%B9%89/ML02(11-28)/ML02_09_calling_java_from_MATLAB_CH.pdf</a><br>4、Java调用Matlab程序 - 移动的天坑的个人空间 - 开源中国<br><a href="https://my.oschina.net/pierrecai/blog/829641">https://my.oschina.net/pierrecai/blog/829641</a><br>5、JAVA调用matlab程序 输入输出数据转换 - 简书<br><a href="https://www.jianshu.com/p/c008bd9d5a25">https://www.jianshu.com/p/c008bd9d5a25</a><br>6、(1)在java中使用matlab的函数_东东-forest_新浪博客<br><a href="http://blog.sina.com.cn/s/blog_86aea377010171yq.html">http://blog.sina.com.cn/s/blog_86aea377010171yq.html</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍 Java 调用 Matlab 程序（Java 和 Matlab 混合编程）流程方法。&lt;/p&gt;</summary>
    
    
    
    <category term="语言基础" scheme="https://www.wshunli.com/categories/%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="Java" scheme="https://www.wshunli.com/tags/Java/"/>
    
    <category term="Matlab" scheme="https://www.wshunli.com/tags/Matlab/"/>
    
  </entry>
  
  <entry>
    <title>Glide 图片加载框架源码解析</title>
    <link href="https://www.wshunli.com/posts/c47606cc.html"/>
    <id>https://www.wshunli.com/posts/c47606cc.html</id>
    <published>2018-09-17T05:18:23.000Z</published>
    <updated>2026-06-17T23:42:59.823Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍 Glide 图片加载框架，包含简单的使用和源码解析。<strong>本文内容基于 Glide 4.7.1 版本</strong>。</p><p>Glide 是一个快速高效的 Android 图片加载库，注重于平滑的滚动。Glide 提供了易用的 API，高性能、可扩展的图片解码管道，以及自动的资源池技术。<a href="https://muyangmin.github.io/glide-docs-cn/">https://muyangmin.github.io/glide-docs-cn/</a></p><span id="more"></span><p>Glide 支持拉取，解码和展示视频快照，图片，和 GIF 动画。Glide 的 API 是如此的灵活，开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下，Glide 使用的是一个定制化的基于 HttpUrlConnection 的栈，但同时也提供了与 Google Volley 和 Square OkHttp 快速集成的工具库。</p><h1 id="Glide-的简单使用"><a href="#Glide-的简单使用" class="headerlink" title="Glide 的简单使用"></a>Glide 的简单使用</h1><p>前面有比较详细的介绍，这里不再赘述。</p><p>Android 图片加载框架 Glide 简单使用 | CirGIS</p><p><a href="https://www.wshunli.com/posts/d82d8606.html">https://www.wshunli.com/posts/d82d8606.html</a></p><h1 id="Glide-的源码解析"><a href="#Glide-的源码解析" class="headerlink" title="Glide 的源码解析"></a>Glide 的源码解析</h1><p>1、我们先看 Glide 的 with() 方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@NonNull</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> RequestManager <span class="title function_">with</span><span class="params">(<span class="meta">@NonNull</span> Context context)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> getRetriever(context).get(context);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@NonNull</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> RequestManager <span class="title function_">with</span><span class="params">(<span class="meta">@NonNull</span> Activity activity)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> getRetriever(activity).get(activity);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@NonNull</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> RequestManager <span class="title function_">with</span><span class="params">(<span class="meta">@NonNull</span> FragmentActivity activity)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> getRetriever(activity).get(activity);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@NonNull</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> RequestManager <span class="title function_">with</span><span class="params">(<span class="meta">@NonNull</span> Fragment fragment)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> getRetriever(fragment.getActivity()).get(fragment);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@NonNull</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> RequestManager <span class="title function_">with</span><span class="params">(<span class="meta">@NonNull</span> View view)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> getRetriever(view.getContext()).get(view);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>未完待续。。</p><blockquote><p>参考资料<br>1、Glide源码分析 | lightSky’Blog<br><a href="http://www.lightskystreet.com/2015/10/12/glide_source_analysis/">http://www.lightskystreet.com/2015/10/12/glide_source_analysis/</a><br>2、Android Glide源码解析 - 简书<br><a href="https://www.jianshu.com/p/0c383eaa5675">https://www.jianshu.com/p/0c383eaa5675</a><br>3、Android Glide源码解析 | Frodo’s Blog<br><a href="http://frodoking.github.io/2015/10/10/android-glide/">http://frodoking.github.io/2015/10/10/android-glide/</a><br>4、Android源码分析：手把手带你分析 Glide的缓存功能 - CSDN博客<br><a href="https://blog.csdn.net/carson_ho/article/details/79256892">https://blog.csdn.net/carson_ho/article/details/79256892</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍 Glide 图片加载框架，包含简单的使用和源码解析。&lt;strong&gt;本文内容基于 Glide 4.7.1 版本&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Glide 是一个快速高效的 Android 图片加载库，注重于平滑的滚动。Glide 提供了易用的 API，高性能、可扩展的图片解码管道，以及自动的资源池技术。&lt;a href=&quot;https://muyangmin.github.io/glide-docs-cn/&quot;&gt;https://muyangmin.github.io/glide-docs-cn/&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="源码解析" scheme="https://www.wshunli.com/categories/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    
    <category term="Android" scheme="https://www.wshunli.com/tags/Android/"/>
    
    <category term="Glide" scheme="https://www.wshunli.com/tags/Glide/"/>
    
    <category term="源码解析" scheme="https://www.wshunli.com/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    <category term="图片加载" scheme="https://www.wshunli.com/tags/%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD/"/>
    
  </entry>
  
  <entry>
    <title>Retrofit 网络框架源码解析</title>
    <link href="https://www.wshunli.com/posts/2bda06ba.html"/>
    <id>https://www.wshunli.com/posts/2bda06ba.html</id>
    <published>2018-09-16T01:38:40.000Z</published>
    <updated>2026-06-17T23:42:59.824Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍 Retrofit 网络框架，包含简单的使用和源码解析。<strong>本文内容基于 Retrofit 2.4.0 版本</strong>。</p><p>Type-safe HTTP client for Android and Java by Square, Inc. <a href="http://square.github.io/retrofit/">http://square.github.io/retrofit/</a></p><span id="more"></span><p>前面介绍过 OkHttp ，Retrofit 是对 OkHttp 网络请求框架的封装，前者专注于接口的封装，后者专注于真正的网络请求。</p><p><img src="https://img.wshunli.com/Android/Retrofit/Retrofit_OkHttp.png" alt="封装流程图"></p><p>应用程序通过 Retrofit 请求网络，实际上是由 Retrofit 接口层封装请求参数、Header、Url 等信息，由 OkHttp 完成实际的请求操作；在服务端返回数据后，OkHttp 将原始的结果交给 Retrofit，Retrofit 根据用户的需求对结果进行解析。</p><h1 id="Retrofit-的简单使用"><a href="#Retrofit-的简单使用" class="headerlink" title="Retrofit 的简单使用"></a>Retrofit 的简单使用</h1><p>参考官网的介绍：</p><p>1、创建 HTTP API 接口</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">GitHubService</span> &#123;</span><br><span class="line">  <span class="meta">@GET(&quot;users/&#123;user&#125;/repos&quot;)</span></span><br><span class="line">  Call&lt;List&lt;Repo&gt;&gt; <span class="title function_">listRepos</span><span class="params">(<span class="meta">@Path(&quot;user&quot;)</span> String user)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、创建 Retrofit 实例，并实现接口实例</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">Retrofit</span> <span class="variable">retrofit</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Retrofit</span>.Builder()</span><br><span class="line">    .baseUrl(<span class="string">&quot;https://api.github.com/&quot;</span>)</span><br><span class="line">    .build();</span><br><span class="line"><span class="type">GitHubService</span> <span class="variable">service</span> <span class="operator">=</span> retrofit.create(GitHubService.class);</span><br></pre></td></tr></table></figure><p>3、创建请求实例</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Call&lt;List&lt;Repo&gt;&gt; call = service.listRepos(<span class="string">&quot;wshunli&quot;</span>);</span><br></pre></td></tr></table></figure><p>4、发送网络请求</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 同步请求</span></span><br><span class="line">call.execute();</span><br><span class="line"><span class="comment">// 异步请求</span></span><br><span class="line">call.enqueue(<span class="keyword">new</span> <span class="title class_">Callback</span>&lt;List&lt;Repo&gt;&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onResponse</span><span class="params">(Call&lt;List&lt;Repo&gt;&gt; call, Response&lt;List&lt;Repo&gt;&gt; response)</span> &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onFailure</span><span class="params">(Call&lt;List&lt;Repo&gt;&gt; call, Throwable t)</span> &#123;</span><br><span class="line">        Log.d(TAG, <span class="string">&quot;onFailure: &quot;</span>);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>和 OkHttp 流程差不多，特别是发送请求方法名字都没有变。</p><h1 id="Retrofit-的源码分析"><a href="#Retrofit-的源码分析" class="headerlink" title="Retrofit 的源码分析"></a>Retrofit 的源码分析</h1><p>Retrofit 网络请求完整的流程图如下：</p><p><img src="https://img.wshunli.com/Android/Retrofit/retrofit_full_process.min.png" alt="Retrofit 流程图"></p><p>下面详细介绍。</p><h2 id="创建-Retrofit-实例"><a href="#创建-Retrofit-实例" class="headerlink" title="创建 Retrofit 实例"></a>创建 Retrofit 实例</h2><p>Retrofit 实例化，也是使用的建造者模式。</p><p><img src="https://img.wshunli.com/Android/Retrofit/retrofit_builder.png" alt="retrofit_builder"></p><p>我们先看 Builder 成员变量的含义：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Retrofit#Builder</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Builder</span> &#123;</span><br><span class="line"><span class="comment">// 当前系统环境</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Platform platform;</span><br><span class="line"><span class="comment">// 网络请求器的工厂</span></span><br><span class="line"><span class="keyword">private</span> <span class="meta">@Nullable</span> okhttp3.Call.Factory callFactory;</span><br><span class="line"><span class="comment">// 网络请求地址</span></span><br><span class="line"><span class="keyword">private</span> HttpUrl baseUrl;</span><br><span class="line"><span class="comment">// 数据转换器工厂集合</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> List&lt;Converter.Factory&gt; converterFactories = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="comment">// 网络请求适配器工厂集合</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> List&lt;CallAdapter.Factory&gt; callAdapterFactories = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="comment">// 回调方法执行器</span></span><br><span class="line"><span class="keyword">private</span> <span class="meta">@Nullable</span> Executor callbackExecutor;</span><br><span class="line"><span class="comment">// 标志位</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> validateEagerly;</span><br></pre></td></tr></table></figure><p>1、首先构造函数中通过 <code>Platform.get()</code> 初始化了平台参数</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Builder(Platform platform) &#123;</span><br><span class="line">    <span class="built_in">this</span>.platform = platform;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="title function_">Builder</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>(Platform.get());</span><br><span class="line">&#125;</span><br><span class="line">Builder(Retrofit retrofit) &#123;</span><br><span class="line">    platform = Platform.get();</span><br><span class="line">    callFactory = retrofit.callFactory;</span><br><span class="line">    baseUrl = retrofit.baseUrl;</span><br><span class="line"></span><br><span class="line">    converterFactories.addAll(retrofit.converterFactories);</span><br><span class="line">    <span class="comment">// Remove the default BuiltInConverters instance added by build().</span></span><br><span class="line">    converterFactories.remove(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    callAdapterFactories.addAll(retrofit.callAdapterFactories);</span><br><span class="line">    <span class="comment">// Remove the default, platform-aware call adapter added by build().</span></span><br><span class="line">    callAdapterFactories.remove(callAdapterFactories.size() - <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    callbackExecutor = retrofit.callbackExecutor;</span><br><span class="line">    validateEagerly = retrofit.validateEagerly;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可以看下判断方法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Platform</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Platform</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Platform</span> <span class="variable">PLATFORM</span> <span class="operator">=</span> findPlatform();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> Platform <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> PLATFORM;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> Platform <span class="title function_">findPlatform</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      Class.forName(<span class="string">&quot;android.os.Build&quot;</span>);</span><br><span class="line">      <span class="keyword">if</span> (Build.VERSION.SDK_INT != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Android</span>();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (ClassNotFoundException ignored) &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      Class.forName(<span class="string">&quot;java.util.Optional&quot;</span>);</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Java8</span>();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (ClassNotFoundException ignored) &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Platform</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">/* 省略部分无关代码 */</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>后面如果有需要，我们也可以直接拷贝。</p><p>2、然后设置 Retrofit 所需的参数即可</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Builder <span class="title function_">baseUrl</span><span class="params">(String baseUrl)</span> &#123;</span><br><span class="line">    checkNotNull(baseUrl, <span class="string">&quot;baseUrl == null&quot;</span>);</span><br><span class="line">    <span class="type">HttpUrl</span> <span class="variable">httpUrl</span> <span class="operator">=</span> HttpUrl.parse(baseUrl);</span><br><span class="line">    <span class="keyword">if</span> (httpUrl == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Illegal URL: &quot;</span> + baseUrl);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> baseUrl(httpUrl);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> Builder <span class="title function_">baseUrl</span><span class="params">(HttpUrl baseUrl)</span> &#123;</span><br><span class="line">    checkNotNull(baseUrl, <span class="string">&quot;baseUrl == null&quot;</span>);</span><br><span class="line">    List&lt;String&gt; pathSegments = baseUrl.pathSegments();</span><br><span class="line">    <span class="keyword">if</span> (!<span class="string">&quot;&quot;</span>.equals(pathSegments.get(pathSegments.size() - <span class="number">1</span>))) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;baseUrl must end in /: &quot;</span> + baseUrl);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">this</span>.baseUrl = baseUrl;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Add converter factory for serialization and deserialization of objects. */</span></span><br><span class="line"><span class="keyword">public</span> Builder <span class="title function_">addConverterFactory</span><span class="params">(Converter.Factory factory)</span> &#123;</span><br><span class="line">    converterFactories.add(checkNotNull(factory, <span class="string">&quot;factory == null&quot;</span>));</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> Builder <span class="title function_">addCallAdapterFactory</span><span class="params">(CallAdapter.Factory factory)</span> &#123;</span><br><span class="line">    callAdapterFactories.add(checkNotNull(factory, <span class="string">&quot;factory == null&quot;</span>));</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3、最后是 build() 方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Retrofit <span class="title function_">build</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (baseUrl == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Base URL required.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    okhttp3.Call.<span class="type">Factory</span> <span class="variable">callFactory</span> <span class="operator">=</span> <span class="built_in">this</span>.callFactory;</span><br><span class="line">    <span class="keyword">if</span> (callFactory == <span class="literal">null</span>) &#123;</span><br><span class="line">    callFactory = <span class="keyword">new</span> <span class="title class_">OkHttpClient</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">Executor</span> <span class="variable">callbackExecutor</span> <span class="operator">=</span> <span class="built_in">this</span>.callbackExecutor;</span><br><span class="line">    <span class="keyword">if</span> (callbackExecutor == <span class="literal">null</span>) &#123;</span><br><span class="line">    callbackExecutor = platform.defaultCallbackExecutor();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Make a defensive copy of the adapters and add the default Call adapter.</span></span><br><span class="line">    List&lt;CallAdapter.Factory&gt; callAdapterFactories = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="built_in">this</span>.callAdapterFactories);</span><br><span class="line">    callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Make a defensive copy of the converters.</span></span><br><span class="line">    List&lt;Converter.Factory&gt; converterFactories =</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">1</span> + <span class="built_in">this</span>.converterFactories.size());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Add the built-in converter factory first. This prevents overriding its behavior but also</span></span><br><span class="line">    <span class="comment">// ensures correct behavior when using converters that consume all types.</span></span><br><span class="line">    converterFactories.add(<span class="keyword">new</span> <span class="title class_">BuiltInConverters</span>());</span><br><span class="line">    converterFactories.addAll(<span class="built_in">this</span>.converterFactories);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Retrofit</span>(callFactory, baseUrl, unmodifiableList(converterFactories),</span><br><span class="line">        unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="创建-API-实例"><a href="#创建-API-实例" class="headerlink" title="创建 API 实例"></a>创建 API 实例</h2><p>获取 API 实例使用 Retrofit 的 <code>create()</code> 方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Retrofit#create()</span></span><br><span class="line"><span class="keyword">public</span> &lt;T&gt; T <span class="title function_">create</span><span class="params">(<span class="keyword">final</span> Class&lt;T&gt; service)</span> &#123;</span><br><span class="line">    Utils.validateServiceInterface(service);</span><br><span class="line">    <span class="keyword">if</span> (validateEagerly) &#123;</span><br><span class="line">        eagerlyValidateMethods(service);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> (T) Proxy.newProxyInstance(service.getClassLoader(), <span class="keyword">new</span> <span class="title class_">Class</span>&lt;?&gt;[] &#123; service &#125;,</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">InvocationHandler</span>() &#123;</span><br><span class="line">            <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Platform</span> <span class="variable">platform</span> <span class="operator">=</span> Platform.get();</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span> <span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, <span class="meta">@Nullable</span> Object[] args)</span></span><br><span class="line">                <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">            <span class="comment">// If the method is a method from Object then defer to normal invocation.</span></span><br><span class="line">            <span class="keyword">if</span> (method.getDeclaringClass() == Object.class) &#123;</span><br><span class="line">                <span class="keyword">return</span> method.invoke(<span class="built_in">this</span>, args);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (platform.isDefaultMethod(method)) &#123;</span><br><span class="line">                <span class="keyword">return</span> platform.invokeDefaultMethod(method, service, proxy, args);</span><br><span class="line">            &#125;</span><br><span class="line">            ServiceMethod&lt;Object, Object&gt; serviceMethod =</span><br><span class="line">                (ServiceMethod&lt;Object, Object&gt;) loadServiceMethod(method);</span><br><span class="line">            OkHttpCall&lt;Object&gt; okHttpCall = <span class="keyword">new</span> <span class="title class_">OkHttpCall</span>&lt;&gt;(serviceMethod, args);</span><br><span class="line">            <span class="keyword">return</span> serviceMethod.adapt(okHttpCall);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>创建 API 实例使用的是 <strong>动态代理</strong> 设计模式。</p><h2 id="创建请求实例"><a href="#创建请求实例" class="headerlink" title="创建请求实例"></a>创建请求实例</h2><p>创建请求实例，跟钱买你的动态代理有关。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Retrofit#create()</span></span><br><span class="line">ServiceMethod&lt;Object, Object&gt; serviceMethod =</span><br><span class="line">    (ServiceMethod&lt;Object, Object&gt;) loadServiceMethod(method);</span><br><span class="line">OkHttpCall&lt;Object&gt; okHttpCall = <span class="keyword">new</span> <span class="title class_">OkHttpCall</span>&lt;&gt;(serviceMethod, args);</span><br><span class="line"><span class="keyword">return</span> serviceMethod.adapt(okHttpCall);</span><br></pre></td></tr></table></figure><p>1、<code>loadServiceMethod()</code> 方法</p><p>一个 <code>ServiceMethod</code> 对应于一个 API 接口的一个方法，<code>loadServiceMethod()</code> 方法负责加载 <code>ServiceMethod</code></p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Retrofit#loadServiceMethod()</span></span><br><span class="line">ServiceMethod&lt;?, ?&gt; loadServiceMethod(Method method) &#123;</span><br><span class="line">    ServiceMethod&lt;?, ?&gt; result = serviceMethodCache.get(method);</span><br><span class="line">    <span class="keyword">if</span> (result != <span class="literal">null</span>) <span class="keyword">return</span> result;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">synchronized</span> (serviceMethodCache) &#123;</span><br><span class="line">        result = serviceMethodCache.get(method);</span><br><span class="line">        <span class="keyword">if</span> (result == <span class="literal">null</span>) &#123;</span><br><span class="line">        result = <span class="keyword">new</span> <span class="title class_">ServiceMethod</span>.Builder&lt;&gt;(<span class="built_in">this</span>, method).build();</span><br><span class="line">        serviceMethodCache.put(method, result);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、<code>OkHttpCall</code> 类</p><p><code>OkHttpCall</code> 实现了 <code>retrofit2.Call</code> ，我们通常会使用它的 <code>execute()</code> 和 <code>enqueue()</code> 接口。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">OkHttpCall(ServiceMethod&lt;T, ?&gt; serviceMethod, <span class="meta">@Nullable</span> Object[] args) &#123;</span><br><span class="line">    <span class="built_in">this</span>.serviceMethod = serviceMethod;</span><br><span class="line">    <span class="built_in">this</span>.args = args;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>构造方法也没有什么好看的。</p><h2 id="发送网络请求"><a href="#发送网络请求" class="headerlink" title="发送网络请求"></a>发送网络请求</h2><p>发送网络请求其实也就是 <code>OkHttpCall</code> 类中的方法。</p><p>1、<strong>同步请求</strong> 使用 <code>execute()</code> 方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// OkHttpCall#execute()</span></span><br><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Response&lt;T&gt; <span class="title function_">execute</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    okhttp3.Call call;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (executed) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Already executed.&quot;</span>);</span><br><span class="line">        executed = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (creationFailure != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (creationFailure <span class="keyword">instanceof</span> IOException) &#123;</span><br><span class="line">                <span class="keyword">throw</span> (IOException) creationFailure;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (creationFailure <span class="keyword">instanceof</span> RuntimeException) &#123;</span><br><span class="line">                <span class="keyword">throw</span> (RuntimeException) creationFailure;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">throw</span> (Error) creationFailure;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        call = rawCall;</span><br><span class="line">        <span class="keyword">if</span> (call == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                call = rawCall = createRawCall();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException | RuntimeException | Error e) &#123;</span><br><span class="line">                throwIfFatal(e); <span class="comment">//  Do not assign a fatal error to creationFailure.</span></span><br><span class="line">                creationFailure = e;</span><br><span class="line">                <span class="keyword">throw</span> e;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (canceled) &#123;</span><br><span class="line">        call.cancel();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> parseResponse(call.execute());</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这里就是 Retrofit 和 OkHttp 交互的核心了，分为三步：</p><p>（1）创建 <code>okhttp3.Call</code> ，包括构造参数</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> okhttp3.Call <span class="title function_">createRawCall</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    okhttp3.<span class="type">Call</span> <span class="variable">call</span> <span class="operator">=</span> serviceMethod.toCall(args);</span><br><span class="line">    <span class="keyword">if</span> (call == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>(<span class="string">&quot;Call.Factory returned null.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> call;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（2）执行网络请求，也就是 OkHttp 的同步网络请求</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">call.execute()</span><br></pre></td></tr></table></figure><p>（3）解析返回的结果</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Response&lt;T&gt; <span class="title function_">parseResponse</span><span class="params">(okhttp3.Response rawResponse)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">ResponseBody</span> <span class="variable">rawBody</span> <span class="operator">=</span> rawResponse.body();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Remove the body&#x27;s source (the only stateful object) so we can pass the response along.</span></span><br><span class="line">    rawResponse = rawResponse.newBuilder()</span><br><span class="line">        .body(<span class="keyword">new</span> <span class="title class_">NoContentResponseBody</span>(rawBody.contentType(), rawBody.contentLength()))</span><br><span class="line">        .build();</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">code</span> <span class="operator">=</span> rawResponse.code();</span><br><span class="line">    <span class="keyword">if</span> (code &lt; <span class="number">200</span> || code &gt;= <span class="number">300</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// Buffer the entire body to avoid future I/O.</span></span><br><span class="line">        <span class="type">ResponseBody</span> <span class="variable">bufferedBody</span> <span class="operator">=</span> Utils.buffer(rawBody);</span><br><span class="line">        <span class="keyword">return</span> Response.error(bufferedBody, rawResponse);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        rawBody.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (code == <span class="number">204</span> || code == <span class="number">205</span>) &#123;</span><br><span class="line">        rawBody.close();</span><br><span class="line">        <span class="keyword">return</span> Response.success(<span class="literal">null</span>, rawResponse);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">ExceptionCatchingRequestBody</span> <span class="variable">catchingBody</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExceptionCatchingRequestBody</span>(rawBody);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">T</span> <span class="variable">body</span> <span class="operator">=</span> serviceMethod.toResponse(catchingBody);</span><br><span class="line">        <span class="keyword">return</span> Response.success(body, rawResponse);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RuntimeException e) &#123;</span><br><span class="line">        <span class="comment">// If the underlying source threw an exception, propagate that rather than indicating it was</span></span><br><span class="line">        <span class="comment">// a runtime exception.</span></span><br><span class="line">        catchingBody.throwIfCaught();</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、<strong>异步请求</strong> 使用 <code>enqueue()</code> 方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">enqueue</span><span class="params">(<span class="keyword">final</span> Callback&lt;T&gt; callback)</span> &#123;</span><br><span class="line">    checkNotNull(callback, <span class="string">&quot;callback == null&quot;</span>);</span><br><span class="line"></span><br><span class="line">    okhttp3.Call call;</span><br><span class="line">    Throwable failure;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (executed) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Already executed.&quot;</span>);</span><br><span class="line">        executed = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        call = rawCall;</span><br><span class="line">        failure = creationFailure;</span><br><span class="line">        <span class="keyword">if</span> (call == <span class="literal">null</span> &amp;&amp; failure == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            call = rawCall = createRawCall();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">            throwIfFatal(t);</span><br><span class="line">            failure = creationFailure = t;</span><br><span class="line">        &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (failure != <span class="literal">null</span>) &#123;</span><br><span class="line">        callback.onFailure(<span class="built_in">this</span>, failure);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (canceled) &#123;</span><br><span class="line">        call.cancel();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    call.enqueue(<span class="keyword">new</span> <span class="title class_">okhttp3</span>.Callback() &#123;</span><br><span class="line">        <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onResponse</span><span class="params">(okhttp3.Call call, okhttp3.Response rawResponse)</span> &#123;</span><br><span class="line">        Response&lt;T&gt; response;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            response = parseResponse(rawResponse);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">            callFailure(e);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            callback.onResponse(OkHttpCall.<span class="built_in">this</span>, response);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">            t.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onFailure</span><span class="params">(okhttp3.Call call, IOException e)</span> &#123;</span><br><span class="line">        callFailure(e);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">callFailure</span><span class="params">(Throwable e)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            callback.onFailure(OkHttpCall.<span class="built_in">this</span>, e);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">            t.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可以看到和同步请求是一致的，实际请求交给了 <code>okhttp3.Call#enqueue(Callback responseCallback)</code> 来实现，并在它的 <code>callback</code> 中调用 <code>parseResponse()</code> 解析响应数据，并转发给传入的 <code>callback</code> 。</p><p>Retrofit 源码就先介绍到这里了，后面有机会再详细介绍。</p><blockquote><p>参考资料<br>1、Retrofit分析-漂亮的解耦套路 - 简书<br><a href="https://www.jianshu.com/p/45cb536be2f4">https://www.jianshu.com/p/45cb536be2f4</a><br>2、Android：手把手带你 深入读懂 Retrofit 2.0 源码 - 简书<br><a href="https://www.jianshu.com/p/0c055ad46b6c">https://www.jianshu.com/p/0c055ad46b6c</a><br>3、Retrofit源码分析（超详细） - 简书<br><a href="https://www.jianshu.com/p/097947afddaf">https://www.jianshu.com/p/097947afddaf</a><br>4、拆轮子系列：拆 Retrofit - Piasy的博客 | Piasy Blog<br><a href="https://blog.piasy.com/2016/06/25/Understand-Retrofit/">https://blog.piasy.com/2016/06/25/Understand-Retrofit/</a><br>5、Retrofit源码解析 | mundane的幻想空间<br><a href="https://mundane799699.github.io/2018/03/13/retrofit-analysis/">https://mundane799699.github.io/2018/03/13/retrofit-analysis/</a><br>6、Retrofit源码解析 - 掘金<br><a href="https://juejin.im/post/5acee62c6fb9a028df22ffee">https://juejin.im/post/5acee62c6fb9a028df22ffee</a><br>7、Retrofit源码解析 | 俞其荣的博客 | Qirong Yu’s Blog<br><a href="https://yuqirong.me/2017/08/03/Retrofit%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/">https://yuqirong.me/2017/08/03/Retrofit源码解析/</a><br>8、android-cn&#x2F;android-open-project-analysis<br><a href="https://github.com/android-cn/android-open-project-analysis/tree/master/tool-lib/network/retrofit">https://github.com/android-cn/android-open-project-analysis/tree/master/tool-lib/network/retrofit</a><br>9、【Android】Retrofit源码分析 - CSDN博客<br><a href="https://blog.csdn.net/u010983881/article/details/79933220">https://blog.csdn.net/u010983881/article/details/79933220</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍 Retrofit 网络框架，包含简单的使用和源码解析。&lt;strong&gt;本文内容基于 Retrofit 2.4.0 版本&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Type-safe HTTP client for Android and Java by Square, Inc. &lt;a href=&quot;http://square.github.io/retrofit/&quot;&gt;http://square.github.io/retrofit/&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="源码解析" scheme="https://www.wshunli.com/categories/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    
    <category term="Android" scheme="https://www.wshunli.com/tags/Android/"/>
    
    <category term="网络框架" scheme="https://www.wshunli.com/tags/%E7%BD%91%E7%BB%9C%E6%A1%86%E6%9E%B6/"/>
    
    <category term="源码解析" scheme="https://www.wshunli.com/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    <category term="Retrofit" scheme="https://www.wshunli.com/tags/Retrofit/"/>
    
  </entry>
  
  <entry>
    <title>OkHttp 网络框架源码解析</title>
    <link href="https://www.wshunli.com/posts/5bd2f229.html"/>
    <id>https://www.wshunli.com/posts/5bd2f229.html</id>
    <published>2018-09-13T08:28:11.000Z</published>
    <updated>2026-06-17T23:42:59.824Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍 OkHttp 网络框架，包含简单的使用和源码解析。<strong>本文内容基于 OkHttp 3.11.0 版本</strong>。</p><p>网上关于 OkHttp 源码解析的文章有很多，我在这里参考他们的资料，形成自己的知识体系。</p><span id="more"></span><p>只是停留在应用层面，会使用一些框架是不行的，还需要深入源码、剖析结构。</p><p>An HTTP+HTTP&#x2F;2 client for Android and Java applications. <a href="http://square.github.io/okhttp/">http://square.github.io/okhttp/</a></p><blockquote><p>支持 HTTP&#x2F;2 协议，允许连接到同一个主机地址的所有请求共享 Socket 。<br>在 HTTP&#x2F;2 协议不可用的情况下，通过连接池减少请求的延迟。<br>支持 GZip 透明压缩，减少传输的数据包大小。<br>支持响应缓存，避免同一个重复的网络请求。</p></blockquote><h1 id="OkHttp-的简单使用"><a href="#OkHttp-的简单使用" class="headerlink" title="OkHttp 的简单使用"></a>OkHttp 的简单使用</h1><p>一般情况下，对于网络框架有两种常见的使用场景，同步请求和异步请求。</p><p><strong>同步请求</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">OkHttpClient</span> <span class="variable">okHttpClient</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OkHttpClient</span>.Builder().build();</span><br><span class="line"><span class="type">Request</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Request</span>.Builder().url(<span class="string">&quot;https://wshunli.com&quot;</span>).build();</span><br><span class="line"><span class="type">Call</span> <span class="variable">call</span> <span class="operator">=</span> okHttpClient.newCall(request);</span><br><span class="line"><span class="type">Response</span> <span class="variable">response</span> <span class="operator">=</span> call.execute();</span><br><span class="line">Log.d(TAG, <span class="string">&quot;onCreate: &quot;</span> + response.body().string());</span><br></pre></td></tr></table></figure><p><strong>异步请求</strong>：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">OkHttpClient</span> <span class="variable">okHttpClient</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OkHttpClient</span>.Builder().build();</span><br><span class="line"><span class="type">Request</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Request</span>.Builder().url(<span class="string">&quot;https://wshunli.com&quot;</span>).build();</span><br><span class="line"><span class="type">Call</span> <span class="variable">call</span> <span class="operator">=</span> okHttpClient.newCall(request);</span><br><span class="line">call.enqueue(<span class="keyword">new</span> <span class="title class_">Callback</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onFailure</span><span class="params">(Call call, IOException e)</span> &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onResponse</span><span class="params">(Call call, Response response)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        Log.d(TAG, <span class="string">&quot;onCreate: &quot;</span> + response.body().string());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>同步请求和异步请求类似，先实例化 OkHttpClient 和 Request 对象，然后使用 OkHttpClient 对象的 newCall() 方法创建 Call 对象，只不过最后执行 enqueue() 方法，整体和网络请求的思路相似。</p><h1 id="OkHttp-的源码分析"><a href="#OkHttp-的源码分析" class="headerlink" title="OkHttp 的源码分析"></a>OkHttp 的源码分析</h1><p>OkHttp 网络请求完整的流程图如下：</p><div align="center">     <img src="https://img.wshunli.com/Android/OkHttp/okhttp_full_process.min.png" title="OkHttp 流程图" alt="OkHttp 流程图"></div><p>下面详细介绍。</p><h2 id="同步请求"><a href="#同步请求" class="headerlink" title="同步请求"></a>同步请求</h2><p>同步请求，先实例化 OkHttpClient 和 Request 对象，然后使用 OkHttpClient 对象的 newCall() 方法创建 Call 对象，最后执行 execute() 方法，整体和网络请求的思路相似。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">OkHttpClient</span> <span class="variable">okHttpClient</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OkHttpClient</span>.Builder().build();</span><br><span class="line"><span class="type">Request</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Request</span>.Builder().url(<span class="string">&quot;https://wshunli.com&quot;</span>).build();</span><br><span class="line"><span class="type">Call</span> <span class="variable">call</span> <span class="operator">=</span> okHttpClient.newCall(request);</span><br><span class="line"><span class="type">Response</span> <span class="variable">response</span> <span class="operator">=</span> call.execute();</span><br></pre></td></tr></table></figure><h3 id="创建-OkHttpClient-对象"><a href="#创建-OkHttpClient-对象" class="headerlink" title="创建 OkHttpClient 对象"></a>创建 OkHttpClient 对象</h3><p>我们先看 OkHttp 的构造函数：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">OkHttpClient</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="built_in">this</span>(<span class="keyword">new</span> <span class="title class_">Builder</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里是直接实例化，实质上是使用 <strong>建造者模式</strong> 构建 OkHttpClient 实例。 </p><p>下面是 OkHttpClient 内部类 Builder 的构造方法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">Builder</span><span class="params">()</span> &#123;</span><br><span class="line">  dispatcher = <span class="keyword">new</span> <span class="title class_">Dispatcher</span>();</span><br><span class="line">  protocols = DEFAULT_PROTOCOLS;</span><br><span class="line">  connectionSpecs = DEFAULT_CONNECTION_SPECS;</span><br><span class="line">  eventListenerFactory = EventListener.factory(EventListener.NONE);</span><br><span class="line">  proxySelector = ProxySelector.getDefault();</span><br><span class="line">  cookieJar = CookieJar.NO_COOKIES;</span><br><span class="line">  socketFactory = SocketFactory.getDefault();</span><br><span class="line">  hostnameVerifier = OkHostnameVerifier.INSTANCE;</span><br><span class="line">  certificatePinner = CertificatePinner.DEFAULT;</span><br><span class="line">  proxyAuthenticator = Authenticator.NONE;</span><br><span class="line">  authenticator = Authenticator.NONE;</span><br><span class="line">  connectionPool = <span class="keyword">new</span> <span class="title class_">ConnectionPool</span>();</span><br><span class="line">  dns = Dns.SYSTEM;</span><br><span class="line">  followSslRedirects = <span class="literal">true</span>;</span><br><span class="line">  followRedirects = <span class="literal">true</span>;</span><br><span class="line">  retryOnConnectionFailure = <span class="literal">true</span>;</span><br><span class="line">  connectTimeout = <span class="number">10_000</span>;</span><br><span class="line">  readTimeout = <span class="number">10_000</span>;</span><br><span class="line">  writeTimeout = <span class="number">10_000</span>;</span><br><span class="line">  pingInterval = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> OkHttpClient <span class="title function_">build</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">OkHttpClient</span>(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里 <code>OkHttpClient.Builder</code> 有很多参数，后面再介绍。</p><h3 id="创建-Request-对象"><a href="#创建-Request-对象" class="headerlink" title="创建 Request 对象"></a>创建 Request 对象</h3><p>和 OkHttpClient 类似，Request 也是是使用 <strong>建造者模式</strong> 创建实例。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">Builder</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="built_in">this</span>.method = <span class="string">&quot;GET&quot;</span>;</span><br><span class="line">  <span class="built_in">this</span>.headers = <span class="keyword">new</span> <span class="title class_">Headers</span>.Builder();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> Request <span class="title function_">build</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (url == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;url == null&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Request</span>(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中配置默认请求方法为 <code>GET</code> ，还有一些头部的默认参数。</p><h3 id="创建-Call-对象"><a href="#创建-Call-对象" class="headerlink" title="创建 Call 对象"></a>创建 Call 对象</h3><p>OkHttpClient 实现了 <code>Call.Factory</code> ，负责根据请求创建新的 Call 对象。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Call <span class="title function_">newCall</span><span class="params">(Request request)</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> RealCall.newRealCall(<span class="built_in">this</span>, request, <span class="literal">false</span> <span class="comment">/* for web socket */</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Call 只是个接口，实际是实例化的 RealCall 对象。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">RealCall</span><span class="params">(OkHttpClient client, Request originalRequest, <span class="type">boolean</span> forWebSocket)</span> &#123;</span><br><span class="line">  <span class="built_in">this</span>.client = client;</span><br><span class="line">  <span class="built_in">this</span>.originalRequest = originalRequest;</span><br><span class="line">  <span class="built_in">this</span>.forWebSocket = forWebSocket;</span><br><span class="line">  <span class="built_in">this</span>.retryAndFollowUpInterceptor = <span class="keyword">new</span> <span class="title class_">RetryAndFollowUpInterceptor</span>(client, forWebSocket);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> RealCall <span class="title function_">newRealCall</span><span class="params">(OkHttpClient client, Request originalRequest, <span class="type">boolean</span> forWebSocket)</span> &#123;</span><br><span class="line">  <span class="comment">// Safely publish the Call instance to the EventListener.</span></span><br><span class="line">  <span class="type">RealCall</span> <span class="variable">call</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RealCall</span>(client, originalRequest, forWebSocket);</span><br><span class="line">  call.eventListener = client.eventListenerFactory().create(call);</span><br><span class="line">  <span class="keyword">return</span> call;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="发送同步网络请求"><a href="#发送同步网络请求" class="headerlink" title="发送同步网络请求"></a>发送同步网络请求</h3><p>发送请求也是在 <code>RealCall</code> 的 <code>execute()</code> 方法中执行的。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// RealCall#execute()</span></span><br><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Response <span class="title function_">execute</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">  <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (executed) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Already Executed&quot;</span>);</span><br><span class="line">    executed = <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  captureCallStackTrace();</span><br><span class="line">  eventListener.callStart(<span class="built_in">this</span>);</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    client.dispatcher().executed(<span class="built_in">this</span>);</span><br><span class="line">    <span class="type">Response</span> <span class="variable">result</span> <span class="operator">=</span> getResponseWithInterceptorChain();</span><br><span class="line">    <span class="keyword">if</span> (result == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IOException</span>(<span class="string">&quot;Canceled&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">  &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">    eventListener.callFailed(<span class="built_in">this</span>, e);</span><br><span class="line">    <span class="keyword">throw</span> e;</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    client.dispatcher().finished(<span class="built_in">this</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这里主要做了四件事：</p><p>1、检查 Call 是否执行过，没有执行将 <code>executed</code> 赋值为 true ，保证每个请求只执行一次；<br>2、使用 <code>client.dispatcher().executed(this)</code> 来进行实际的请求；<br>3、调用 <code>getResponseWithInterceptorChain()</code> 方法，获取请求响应的结果；<br>4、最后 <code>dispatcher</code> 结束自己。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Dispatcher#executed()</span></span><br><span class="line"><span class="comment">/** Used by &#123;<span class="doctag">@code</span> Call#execute&#125; to signal it is in-flight. */</span></span><br><span class="line"><span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">executed</span><span class="params">(RealCall call)</span> &#123;</span><br><span class="line">  runningSyncCalls.add(call);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在同步请求中 <code>dispatcher</code> 只是负责判断请求执行的状态，在异步请求中参与内容过多。</p><p>下面我们来看 <code>getResponseWithInterceptorChain()</code> 方法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Response <span class="title function_">getResponseWithInterceptorChain</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">  <span class="comment">// Build a full stack of interceptors.</span></span><br><span class="line">  List&lt;Interceptor&gt; interceptors = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">  interceptors.addAll(client.interceptors()); <span class="comment">// 加入用户自定义的拦截器</span></span><br><span class="line">  interceptors.add(retryAndFollowUpInterceptor); <span class="comment">// 重试和重定向拦截器</span></span><br><span class="line">  interceptors.add(<span class="keyword">new</span> <span class="title class_">BridgeInterceptor</span>(client.cookieJar())); <span class="comment">// 加入转化请求响应的拦截器</span></span><br><span class="line">  interceptors.add(<span class="keyword">new</span> <span class="title class_">CacheInterceptor</span>(client.internalCache())); <span class="comment">// 加入缓存拦截器</span></span><br><span class="line">  interceptors.add(<span class="keyword">new</span> <span class="title class_">ConnectInterceptor</span>(client)); <span class="comment">// 加入连接拦截器</span></span><br><span class="line">  <span class="keyword">if</span> (!forWebSocket) &#123;</span><br><span class="line">      interceptors.addAll(client.networkInterceptors()); <span class="comment">// 加入用户自定义的网络拦截器</span></span><br><span class="line">  &#125;</span><br><span class="line">  interceptors.add(<span class="keyword">new</span> <span class="title class_">CallServerInterceptor</span>(forWebSocket)); <span class="comment">// 加入请求响应的拦截器</span></span><br><span class="line">  </span><br><span class="line">  Interceptor.<span class="type">Chain</span> <span class="variable">chain</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RealInterceptorChain</span>(interceptors, <span class="literal">null</span>, <span class="literal">null</span>, <span class="literal">null</span>, <span class="number">0</span>,</span><br><span class="line">          originalRequest, <span class="built_in">this</span>, eventListener, client.readTimeoutMillis());</span><br><span class="line">  <span class="comment">// 利用 chain 来链式调用拦截器，最后的返回结果就是 Response 对象</span></span><br><span class="line">  <span class="keyword">return</span> chain.proceed(originalRequest);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们都知道，拦截器是 OkHttp 的精髓。</p><p>1、<code>client.interceptors()</code> ，首先加入 <code>interceptors</code> 的是用户自定义的拦截器，比如修改请求头的拦截器等；<br>2、<code>RetryAndFollowUpInterceptor</code> 是用来重试和重定向的拦截器，在下面我们会讲到；<br>3、<code>BridgeInterceptor</code> 是用来将用户友好的请求转化为向服务器的请求，之后又把服务器的响应转化为对用户友好的响应；<br>4、<code>CacheInterceptor</code> 是缓存拦截器，若存在缓存并且可用就直接返回该缓存，否则会向服务器请求；<br>5、<code>ConnectInterceptor</code> 用来建立连接的拦截器；<br>6、<code>client.networkInterceptors()</code> 加入用户自定义的 <code>networkInterceptors</code> ；<br>7、<code>CallServerInterceptor </code>是真正向服务器发出请求且得到响应的拦截器；</p><p>最后在聚合了这些拦截器后，利用 <code>RealInterceptorChain</code> 来链式调用这些拦截器，利用的就是 <strong>责任链模式</strong> 。</p><p><font font size="3" color="#FF0000"> 下面介绍 OkHttp 中的 拦截器 </font></p><p>拦截器 <code>Interceptor</code> 是 OkHttp 的核心，<strong>实际上它把实际的网络请求、缓存、透明压缩等功能都统一了起来</strong>，每一个功能都只是一个 <code>Interceptor</code>，它们再连接成一个 <code>Interceptor.Chain</code>，环环相扣，最终圆满完成一次网络请求。</p><div align="center">     <img src="https://img.wshunli.com/Android/OkHttp/okhttp_interceptors.jpg" title="OkHttp 拦截器" alt="OkHttp 拦截器"></div><p>1、<code>RealInterceptorChain</code> 拦截器链</p><p>拦截器链 <code>RealInterceptorChain</code> 是真正把这些拦截器串起来的一个角色，调用 <code>proceed()</code> 方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Response <span class="title function_">proceed</span><span class="params">(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,</span></span><br><span class="line"><span class="params">    RealConnection connection)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">  <span class="keyword">if</span> (index &gt;= interceptors.size()) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AssertionError</span>();</span><br><span class="line"></span><br><span class="line">  calls++;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// If we already have a stream, confirm that the incoming request will use it.</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">this</span>.httpCodec != <span class="literal">null</span> &amp;&amp; !<span class="built_in">this</span>.connection.supportsUrl(request.url())) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;network interceptor &quot;</span> + interceptors.get(index - <span class="number">1</span>)</span><br><span class="line">        + <span class="string">&quot; must retain the same host and port&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// If we already have a stream, confirm that this is the only call to chain.proceed().</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">this</span>.httpCodec != <span class="literal">null</span> &amp;&amp; calls &gt; <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;network interceptor &quot;</span> + interceptors.get(index - <span class="number">1</span>)</span><br><span class="line">        + <span class="string">&quot; must call proceed() exactly once&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// Call the next interceptor in the chain.</span></span><br><span class="line">  <span class="comment">// 得到下一次对应的 RealInterceptorChain</span></span><br><span class="line">  <span class="type">RealInterceptorChain</span> <span class="variable">next</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RealInterceptorChain</span>(interceptors, streamAllocation, httpCodec,</span><br><span class="line">      connection, index + <span class="number">1</span>, request, call, eventListener, connectTimeout, readTimeout,</span><br><span class="line">      writeTimeout);</span><br><span class="line">  <span class="comment">// 当前次数的 interceptor</span></span><br><span class="line">  <span class="type">Interceptor</span> <span class="variable">interceptor</span> <span class="operator">=</span> interceptors.get(index);</span><br><span class="line">  <span class="comment">// 进行拦截处理，并且在 interceptor 链式调用 next 的 proceed 方法</span></span><br><span class="line">  <span class="type">Response</span> <span class="variable">response</span> <span class="operator">=</span> interceptor.intercept(next);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Confirm that the next interceptor made its required call to chain.proceed().</span></span><br><span class="line">  <span class="comment">// 确认下一次的 interceptor 调用过 chain.proceed()</span></span><br><span class="line">  <span class="keyword">if</span> (httpCodec != <span class="literal">null</span> &amp;&amp; index + <span class="number">1</span> &lt; interceptors.size() &amp;&amp; next.calls != <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;network interceptor &quot;</span> + interceptor</span><br><span class="line">        + <span class="string">&quot; must call proceed() exactly once&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Confirm that the intercepted response isn&#x27;t null.</span></span><br><span class="line">  <span class="keyword">if</span> (response == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>(<span class="string">&quot;interceptor &quot;</span> + interceptor + <span class="string">&quot; returned null&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (response.body() == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(</span><br><span class="line">        <span class="string">&quot;interceptor &quot;</span> + interceptor + <span class="string">&quot; returned a response with no body&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> response;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在代码中是一次次链式调用拦截器。</p><p>2、<code>RetryAndFollowUpInterceptor</code> 重试和重定向的拦截器</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Response <span class="title function_">intercept</span><span class="params">(Chain chain)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">  <span class="type">Request</span> <span class="variable">request</span> <span class="operator">=</span> chain.request();</span><br><span class="line">  <span class="type">RealInterceptorChain</span> <span class="variable">realChain</span> <span class="operator">=</span> (RealInterceptorChain) chain;</span><br><span class="line">  <span class="type">Call</span> <span class="variable">call</span> <span class="operator">=</span> realChain.call();</span><br><span class="line">  <span class="type">EventListener</span> <span class="variable">eventListener</span> <span class="operator">=</span> realChain.eventListener();</span><br><span class="line"></span><br><span class="line">  <span class="type">StreamAllocation</span> <span class="variable">streamAllocation</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StreamAllocation</span>(client.connectionPool(),</span><br><span class="line">      createAddress(request.url()), call, eventListener, callStackTrace);</span><br><span class="line">  <span class="built_in">this</span>.streamAllocation = streamAllocation;</span><br><span class="line"></span><br><span class="line">  <span class="type">int</span> <span class="variable">followUpCount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">  <span class="type">Response</span> <span class="variable">priorResponse</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 如果取消，就释放资源</span></span><br><span class="line">    <span class="keyword">if</span> (canceled) &#123;</span><br><span class="line">      streamAllocation.release();</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IOException</span>(<span class="string">&quot;Canceled&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Response response;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">releaseConnection</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="comment">// 调用下一个拦截器</span></span><br><span class="line">      response = realChain.proceed(request, streamAllocation, <span class="literal">null</span>, <span class="literal">null</span>);</span><br><span class="line">      releaseConnection = <span class="literal">false</span>;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RouteException e) &#123;</span><br><span class="line">      <span class="comment">// The attempt to connect via a route failed. The request will not have been sent.</span></span><br><span class="line">      <span class="comment">// 路由连接失败，请求将不会被发送</span></span><br><span class="line">      <span class="keyword">if</span> (!recover(e.getLastConnectException(), streamAllocation, <span class="literal">false</span>, request)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> e.getFirstConnectException();</span><br><span class="line">      &#125;</span><br><span class="line">      releaseConnection = <span class="literal">false</span>;</span><br><span class="line">      <span class="keyword">continue</span>;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">      <span class="comment">// An attempt to communicate with a server failed. The request may have been sent.</span></span><br><span class="line">      <span class="comment">// 服务器连接失败，请求可能已被发送</span></span><br><span class="line">      <span class="type">boolean</span> <span class="variable">requestSendStarted</span> <span class="operator">=</span> !(e <span class="keyword">instanceof</span> ConnectionShutdownException);</span><br><span class="line">      <span class="keyword">if</span> (!recover(e, streamAllocation, requestSendStarted, request)) <span class="keyword">throw</span> e;</span><br><span class="line">      releaseConnection = <span class="literal">false</span>;</span><br><span class="line">      <span class="keyword">continue</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">      <span class="comment">// We&#x27;re throwing an unchecked exception. Release any resources.</span></span><br><span class="line">      <span class="comment">// 抛出未检查的异常，释放资源</span></span><br><span class="line">      <span class="keyword">if</span> (releaseConnection) &#123;</span><br><span class="line">        streamAllocation.streamFailed(<span class="literal">null</span>);</span><br><span class="line">        streamAllocation.release();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Attach the prior response if it exists. Such responses never have a body.</span></span><br><span class="line">    <span class="keyword">if</span> (priorResponse != <span class="literal">null</span>) &#123;</span><br><span class="line">      response = response.newBuilder()</span><br><span class="line">          .priorResponse(priorResponse.newBuilder()</span><br><span class="line">                  .body(<span class="literal">null</span>)</span><br><span class="line">                  .build())</span><br><span class="line">          .build();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果不需要重定向，那么 followUp 为空，会根据响应码判断</span></span><br><span class="line">    Request followUp;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      followUp = followUpRequest(response, streamAllocation.route());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">      streamAllocation.release();</span><br><span class="line">      <span class="keyword">throw</span> e;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 释放资源，返回 response</span></span><br><span class="line">    <span class="keyword">if</span> (followUp == <span class="literal">null</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (!forWebSocket) &#123;</span><br><span class="line">        streamAllocation.release();</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> response;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 关闭 response 的 body</span></span><br><span class="line">    closeQuietly(response.body());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (++followUpCount &gt; MAX_FOLLOW_UPS) &#123;</span><br><span class="line">      streamAllocation.release();</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ProtocolException</span>(<span class="string">&quot;Too many follow-up requests: &quot;</span> + followUpCount);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (followUp.body() <span class="keyword">instanceof</span> UnrepeatableRequestBody) &#123;</span><br><span class="line">      streamAllocation.release();</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">HttpRetryException</span>(<span class="string">&quot;Cannot retry streamed HTTP body&quot;</span>, response.code());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// response 和 followUp 比较是否为同一个连接</span></span><br><span class="line">    <span class="comment">// 若为重定向就销毁旧连接，创建新连接</span></span><br><span class="line">    <span class="keyword">if</span> (!sameConnection(response, followUp.url())) &#123;</span><br><span class="line">      streamAllocation.release();</span><br><span class="line">      streamAllocation = <span class="keyword">new</span> <span class="title class_">StreamAllocation</span>(client.connectionPool(),</span><br><span class="line">          createAddress(followUp.url()), call, eventListener, callStackTrace);</span><br><span class="line">      <span class="built_in">this</span>.streamAllocation = streamAllocation;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (streamAllocation.codec() != <span class="literal">null</span>) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Closing the body of &quot;</span> + response</span><br><span class="line">          + <span class="string">&quot; didn&#x27;t close its backing stream. Bad interceptor?&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 将重定向操作得到的新请求设置给 request</span></span><br><span class="line">    request = followUp;</span><br><span class="line">    priorResponse = response;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>总体来说，<code>RetryAndFollowUpInterceptor</code> 是用来失败重试以及重定向的拦截器。</p><p>3、<code>BridgeInterceptor</code> 桥街和适配拦截器</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Response <span class="title function_">intercept</span><span class="params">(Chain chain)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">  <span class="type">Request</span> <span class="variable">userRequest</span> <span class="operator">=</span> chain.request();</span><br><span class="line">  Request.<span class="type">Builder</span> <span class="variable">requestBuilder</span> <span class="operator">=</span> userRequest.newBuilder();</span><br><span class="line">  <span class="comment">// 将用户友好的 request 构造为发送给服务器的 request</span></span><br><span class="line">  <span class="type">RequestBody</span> <span class="variable">body</span> <span class="operator">=</span> userRequest.body();</span><br><span class="line">  <span class="comment">// 若有请求体，则构造</span></span><br><span class="line">  <span class="keyword">if</span> (body != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="type">MediaType</span> <span class="variable">contentType</span> <span class="operator">=</span> body.contentType();</span><br><span class="line">    <span class="keyword">if</span> (contentType != <span class="literal">null</span>) &#123;</span><br><span class="line">      requestBuilder.header(<span class="string">&quot;Content-Type&quot;</span>, contentType.toString());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">contentLength</span> <span class="operator">=</span> body.contentLength();</span><br><span class="line">    <span class="keyword">if</span> (contentLength != -<span class="number">1</span>) &#123;</span><br><span class="line">      requestBuilder.header(<span class="string">&quot;Content-Length&quot;</span>, Long.toString(contentLength));</span><br><span class="line">      requestBuilder.removeHeader(<span class="string">&quot;Transfer-Encoding&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      requestBuilder.header(<span class="string">&quot;Transfer-Encoding&quot;</span>, <span class="string">&quot;chunked&quot;</span>);</span><br><span class="line">      requestBuilder.removeHeader(<span class="string">&quot;Content-Length&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (userRequest.header(<span class="string">&quot;Host&quot;</span>) == <span class="literal">null</span>) &#123;</span><br><span class="line">    requestBuilder.header(<span class="string">&quot;Host&quot;</span>, hostHeader(userRequest.url(), <span class="literal">false</span>));</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (userRequest.header(<span class="string">&quot;Connection&quot;</span>) == <span class="literal">null</span>) &#123;</span><br><span class="line">    requestBuilder.header(<span class="string">&quot;Connection&quot;</span>, <span class="string">&quot;Keep-Alive&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// If we add an &quot;Accept-Encoding: gzip&quot; header field we&#x27;re responsible for also decompressing</span></span><br><span class="line">  <span class="comment">// the transfer stream.</span></span><br><span class="line">  <span class="comment">// 使用 gzip 压缩</span></span><br><span class="line">  <span class="type">boolean</span> <span class="variable">transparentGzip</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">if</span> (userRequest.header(<span class="string">&quot;Accept-Encoding&quot;</span>) == <span class="literal">null</span> &amp;&amp; userRequest.header(<span class="string">&quot;Range&quot;</span>) == <span class="literal">null</span>) &#123;</span><br><span class="line">    transparentGzip = <span class="literal">true</span>;</span><br><span class="line">    requestBuilder.header(<span class="string">&quot;Accept-Encoding&quot;</span>, <span class="string">&quot;gzip&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 设置 cookie</span></span><br><span class="line">  List&lt;Cookie&gt; cookies = cookieJar.loadForRequest(userRequest.url());</span><br><span class="line">  <span class="keyword">if</span> (!cookies.isEmpty()) &#123;</span><br><span class="line">    requestBuilder.header(<span class="string">&quot;Cookie&quot;</span>, cookieHeader(cookies));</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 设置 UA</span></span><br><span class="line">  <span class="keyword">if</span> (userRequest.header(<span class="string">&quot;User-Agent&quot;</span>) == <span class="literal">null</span>) &#123;</span><br><span class="line">    requestBuilder.header(<span class="string">&quot;User-Agent&quot;</span>, Version.userAgent());</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 构造完后，将 request 交给下一个拦截器去处理。最后又得到服务端响应 networkResponse</span></span><br><span class="line">  <span class="type">Response</span> <span class="variable">networkResponse</span> <span class="operator">=</span> chain.proceed(requestBuilder.build());</span><br><span class="line">  <span class="comment">// 保存 networkResponse 的 cookie</span></span><br><span class="line">  HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());</span><br><span class="line">  <span class="comment">// 将 networkResponse 构造为对用户友好的 response</span></span><br><span class="line">  Response.<span class="type">Builder</span> <span class="variable">responseBuilder</span> <span class="operator">=</span> networkResponse.newBuilder()</span><br><span class="line">      .request(userRequest);</span><br><span class="line">  <span class="comment">// 如果 networkResponse 使用 gzip 并且有响应体的话，给用户友好的 response 设置响应体</span></span><br><span class="line">  <span class="keyword">if</span> (transparentGzip</span><br><span class="line">      &amp;&amp; <span class="string">&quot;gzip&quot;</span>.equalsIgnoreCase(networkResponse.header(<span class="string">&quot;Content-Encoding&quot;</span>))</span><br><span class="line">      &amp;&amp; HttpHeaders.hasBody(networkResponse)) &#123;</span><br><span class="line">    <span class="type">GzipSource</span> <span class="variable">responseBody</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GzipSource</span>(networkResponse.body().source());</span><br><span class="line">    <span class="type">Headers</span> <span class="variable">strippedHeaders</span> <span class="operator">=</span> networkResponse.headers().newBuilder()</span><br><span class="line">        .removeAll(<span class="string">&quot;Content-Encoding&quot;</span>)</span><br><span class="line">        .removeAll(<span class="string">&quot;Content-Length&quot;</span>)</span><br><span class="line">        .build();</span><br><span class="line">    responseBuilder.headers(strippedHeaders);</span><br><span class="line">    <span class="type">String</span> <span class="variable">contentType</span> <span class="operator">=</span> networkResponse.header(<span class="string">&quot;Content-Type&quot;</span>);</span><br><span class="line">    responseBuilder.body(<span class="keyword">new</span> <span class="title class_">RealResponseBody</span>(contentType, -<span class="number">1L</span>, Okio.buffer(responseBody)));</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> responseBuilder.build();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>BridgeInterceptor</code> 这一步，先把用户友好的请求进行重新构造，变成了向服务器发送的请求。</p><p>之后调用 <code>chain.proceed(requestBuilder.build())</code> 进行下一个拦截器的处理。</p><p>等到后面的拦截器都处理完毕，得到响应。再把 <code>networkResponse</code> 转化成对用户友好的 <code>response</code> 。</p><p>4、<code>CacheInterceptor</code> 缓存拦截器</p><p>分析 <code>CacheInterceptor</code> 拦截器 <code>intercept()</code> 方法的源代码</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Response <span class="title function_">intercept</span><span class="params">(Chain chain)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="comment">// 得到 request 对应缓存中的 response</span></span><br><span class="line">    <span class="type">Response</span> <span class="variable">cacheCandidate</span> <span class="operator">=</span> cache != <span class="literal">null</span></span><br><span class="line">            ? cache.get(chain.request())</span><br><span class="line">            : <span class="literal">null</span>;</span><br><span class="line">    <span class="comment">// 获取当前时间，会和之前缓存的时间进行比较</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    <span class="comment">// 得到缓存策略</span></span><br><span class="line">    <span class="type">CacheStrategy</span> <span class="variable">strategy</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CacheStrategy</span>.Factory(now, chain.request(), cacheCandidate).get();</span><br><span class="line">    <span class="type">Request</span> <span class="variable">networkRequest</span> <span class="operator">=</span> strategy.networkRequest;</span><br><span class="line">    <span class="type">Response</span> <span class="variable">cacheResponse</span> <span class="operator">=</span> strategy.cacheResponse;</span><br><span class="line">    <span class="comment">// 追踪缓存，其实就是计数</span></span><br><span class="line">    <span class="keyword">if</span> (cache != <span class="literal">null</span>) &#123;</span><br><span class="line">        cache.trackResponse(strategy);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 缓存不适用，关闭</span></span><br><span class="line">    <span class="keyword">if</span> (cacheCandidate != <span class="literal">null</span> &amp;&amp; cacheResponse == <span class="literal">null</span>) &#123;</span><br><span class="line">        closeQuietly(cacheCandidate.body()); <span class="comment">// The cache candidate wasn&#x27;t applicable. Close it.</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// If we&#x27;re forbidden from using the network and the cache is insufficient, fail.</span></span><br><span class="line">    <span class="comment">// 禁止网络并且没有缓存的话，返回失败</span></span><br><span class="line">    <span class="keyword">if</span> (networkRequest == <span class="literal">null</span> &amp;&amp; cacheResponse == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Response</span>.Builder()</span><br><span class="line">                .request(chain.request())</span><br><span class="line">                .protocol(Protocol.HTTP_1_1)</span><br><span class="line">                .code(<span class="number">504</span>)</span><br><span class="line">                .message(<span class="string">&quot;Unsatisfiable Request (only-if-cached)&quot;</span>)</span><br><span class="line">                .body(Util.EMPTY_RESPONSE)</span><br><span class="line">                .sentRequestAtMillis(-<span class="number">1L</span>)</span><br><span class="line">                .receivedResponseAtMillis(System.currentTimeMillis())</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// If we don&#x27;t need the network, we&#x27;re done.</span></span><br><span class="line">    <span class="comment">// 不用网络请求，返回缓存</span></span><br><span class="line">    <span class="keyword">if</span> (networkRequest == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> cacheResponse.newBuilder()</span><br><span class="line">                .cacheResponse(stripBody(cacheResponse))</span><br><span class="line">                .build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">Response</span> <span class="variable">networkResponse</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 交给下一个拦截器，返回 networkResponse</span></span><br><span class="line">        networkResponse = chain.proceed(networkRequest);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// If we&#x27;re crashing on I/O or otherwise, don&#x27;t leak the cache body.</span></span><br><span class="line">        <span class="keyword">if</span> (networkResponse == <span class="literal">null</span> &amp;&amp; cacheCandidate != <span class="literal">null</span>) &#123;</span><br><span class="line">            closeQuietly(cacheCandidate.body());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果我们同时有缓存和 networkResponse ，根据情况使用</span></span><br><span class="line">    <span class="keyword">if</span> (cacheResponse != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (networkResponse.code() == HTTP_NOT_MODIFIED) &#123;</span><br><span class="line">            <span class="type">Response</span> <span class="variable">response</span> <span class="operator">=</span> cacheResponse.newBuilder()</span><br><span class="line">                    .headers(combine(cacheResponse.headers(), networkResponse.headers()))</span><br><span class="line">                    .sentRequestAtMillis(networkResponse.sentRequestAtMillis())</span><br><span class="line">                    .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())</span><br><span class="line">                    .cacheResponse(stripBody(cacheResponse))</span><br><span class="line">                    .networkResponse(stripBody(networkResponse))</span><br><span class="line">                    .build();</span><br><span class="line">            networkResponse.body().close();</span><br><span class="line">            <span class="comment">// 更新原来的缓存至最新</span></span><br><span class="line">            <span class="comment">// Update the cache after combining headers but before stripping the</span></span><br><span class="line">            <span class="comment">// Content-Encoding header (as performed by initContentStream()).</span></span><br><span class="line">            cache.trackConditionalCacheHit();</span><br><span class="line">            cache.update(cacheResponse, response);</span><br><span class="line">            <span class="keyword">return</span> response;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            closeQuietly(cacheResponse.body());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">Response</span> <span class="variable">response</span> <span class="operator">=</span> networkResponse.newBuilder()</span><br><span class="line">            .cacheResponse(stripBody(cacheResponse))</span><br><span class="line">            .networkResponse(stripBody(networkResponse))</span><br><span class="line">            .build();</span><br><span class="line">    <span class="comment">// 保存之前未缓存的缓存</span></span><br><span class="line">    <span class="keyword">if</span> (cache != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (HttpHeaders.hasBody(response) &amp;&amp; CacheStrategy.isCacheable(response, networkRequest)) &#123;</span><br><span class="line">            <span class="comment">// Offer this request to the cache.</span></span><br><span class="line">            <span class="type">CacheRequest</span> <span class="variable">cacheRequest</span> <span class="operator">=</span> cache.put(response);</span><br><span class="line">            <span class="keyword">return</span> cacheWritingResponse(cacheRequest, response);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (HttpMethod.invalidatesCache(networkRequest.method())) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                cache.remove(networkRequest);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException ignored) &#123;</span><br><span class="line">                <span class="comment">// The cache cannot be written.</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> response;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>CacheInterceptor</code> 做的事情就是根据请求拿到缓存，若没有缓存或者缓存失效，就进入网络请求阶段，否则会返回缓存。</p><p>5、<code>ConnectInterceptor</code> 拦截器</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Response <span class="title function_">intercept</span><span class="params">(Chain chain)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">  <span class="type">RealInterceptorChain</span> <span class="variable">realChain</span> <span class="operator">=</span> (RealInterceptorChain) chain;</span><br><span class="line">  <span class="type">Request</span> <span class="variable">request</span> <span class="operator">=</span> realChain.request();</span><br><span class="line">  <span class="type">StreamAllocation</span> <span class="variable">streamAllocation</span> <span class="operator">=</span> realChain.streamAllocation();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// We need the network to satisfy this request. Possibly for validating a conditional GET.</span></span><br><span class="line">  <span class="type">boolean</span> <span class="variable">doExtensiveHealthChecks</span> <span class="operator">=</span> !request.method().equals(<span class="string">&quot;GET&quot;</span>);</span><br><span class="line">  <span class="type">HttpCodec</span> <span class="variable">httpCodec</span> <span class="operator">=</span> streamAllocation.newStream(client, chain, doExtensiveHealthChecks);</span><br><span class="line">  <span class="type">RealConnection</span> <span class="variable">connection</span> <span class="operator">=</span> streamAllocation.connection();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> realChain.proceed(request, streamAllocation, httpCodec, connection);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实际上建立连接就是创建了一个 <code>HttpCodec</code> 对象，它是对 <code>HTTP</code> 协议操作的抽象，有两个实现：<code>Http1Codec</code> 和 <code>Http2Codec</code>，顾名思义，它们分别对应 HTTP&#x2F;1.1 和 HTTP&#x2F;2 版本的实现。</p><p>6、<code>CallServerInterceptor</code> 拦截器，发送和接收数据</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> Response <span class="title function_">intercept</span><span class="params">(Chain chain)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">  <span class="type">RealInterceptorChain</span> <span class="variable">realChain</span> <span class="operator">=</span> (RealInterceptorChain) chain;</span><br><span class="line">  <span class="type">HttpCodec</span> <span class="variable">httpCodec</span> <span class="operator">=</span> realChain.httpStream();</span><br><span class="line">  <span class="type">StreamAllocation</span> <span class="variable">streamAllocation</span> <span class="operator">=</span> realChain.streamAllocation();</span><br><span class="line">  <span class="type">RealConnection</span> <span class="variable">connection</span> <span class="operator">=</span> (RealConnection) realChain.connection();</span><br><span class="line">  <span class="type">Request</span> <span class="variable">request</span> <span class="operator">=</span> realChain.request();    </span><br><span class="line"></span><br><span class="line">  <span class="type">long</span> <span class="variable">sentRequestMillis</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">  <span class="comment">// 整理请求头并写入</span></span><br><span class="line">  httpCodec.writeRequestHeaders(request);</span><br><span class="line"></span><br><span class="line">  Response.<span class="type">Builder</span> <span class="variable">responseBuilder</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">  <span class="comment">// 检查是否为有 body 的请求方法</span></span><br><span class="line">  <span class="keyword">if</span> (HttpMethod.permitsRequestBody(request.method()) &amp;&amp; request.body() != <span class="literal">null</span>) &#123;</span><br><span class="line">      <span class="comment">// If there&#x27;s a &quot;Expect: 100-continue&quot; header on the request, wait for a &quot;HTTP/1.1 100</span></span><br><span class="line">      <span class="comment">// Continue&quot; response before transmitting the request body. If we don&#x27;t get that, return what</span></span><br><span class="line">      <span class="comment">// we did get (such as a 4xx response) without ever transmitting the request body.</span></span><br><span class="line">      <span class="comment">// 如果有 Expect: 100-continue 在请求头中，那么要等服务器的响应</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="string">&quot;100-continue&quot;</span>.equalsIgnoreCase(request.header(<span class="string">&quot;Expect&quot;</span>))) &#123;</span><br><span class="line">          httpCodec.flushRequest();</span><br><span class="line">          responseBuilder = httpCodec.readResponseHeaders(<span class="literal">true</span>);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (responseBuilder == <span class="literal">null</span>) &#123;</span><br><span class="line">          <span class="comment">// Write the request body if the &quot;Expect: 100-continue&quot; expectation was met.</span></span><br><span class="line">          <span class="comment">// 写入请求体</span></span><br><span class="line">          <span class="type">Sink</span> <span class="variable">requestBodyOut</span> <span class="operator">=</span> httpCodec.createRequestBody(request, request.body().contentLength());</span><br><span class="line">          <span class="type">BufferedSink</span> <span class="variable">bufferedRequestBody</span> <span class="operator">=</span> Okio.buffer(requestBodyOut);</span><br><span class="line">          request.body().writeTo(bufferedRequestBody);</span><br><span class="line">          bufferedRequestBody.close();</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!connection.isMultiplexed()) &#123;</span><br><span class="line">          <span class="comment">// If the &quot;Expect: 100-continue&quot; expectation wasn&#x27;t met, prevent the HTTP/1 connection from</span></span><br><span class="line">          <span class="comment">// being reused. Otherwise we&#x27;re still obligated to transmit the request body to leave the</span></span><br><span class="line">          <span class="comment">// connection in a consistent state.</span></span><br><span class="line">          streamAllocation.noNewStreams();</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  httpCodec.finishRequest();</span><br><span class="line">  <span class="comment">// 得到响应头</span></span><br><span class="line">  <span class="keyword">if</span> (responseBuilder == <span class="literal">null</span>) &#123;</span><br><span class="line">      responseBuilder = httpCodec.readResponseHeaders(<span class="literal">false</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 构造 response</span></span><br><span class="line">  <span class="type">Response</span> <span class="variable">response</span> <span class="operator">=</span> responseBuilder</span><br><span class="line">          .request(request)</span><br><span class="line">          .handshake(streamAllocation.connection().handshake())</span><br><span class="line">          .sentRequestAtMillis(sentRequestMillis)</span><br><span class="line">          .receivedResponseAtMillis(System.currentTimeMillis())</span><br><span class="line">          .build();</span><br><span class="line"></span><br><span class="line">  <span class="type">int</span> <span class="variable">code</span> <span class="operator">=</span> response.code();</span><br><span class="line">  <span class="comment">// 如果为 web socket 且状态码是 101 ，那么 body 为空</span></span><br><span class="line">  <span class="keyword">if</span> (forWebSocket &amp;&amp; code == <span class="number">101</span>) &#123;</span><br><span class="line">      <span class="comment">// Connection is upgrading, but we need to ensure interceptors see a non-null response body.</span></span><br><span class="line">      response = response.newBuilder()</span><br><span class="line">              .body(Util.EMPTY_RESPONSE)</span><br><span class="line">              .build();</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 读取 body</span></span><br><span class="line">      response = response.newBuilder()</span><br><span class="line">              .body(httpCodec.openResponseBody(response))</span><br><span class="line">              .build();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 如果请求头中有 close 那么断开连接</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="string">&quot;close&quot;</span>.equalsIgnoreCase(response.request().header(<span class="string">&quot;Connection&quot;</span>))</span><br><span class="line">          || <span class="string">&quot;close&quot;</span>.equalsIgnoreCase(response.header(<span class="string">&quot;Connection&quot;</span>))) &#123;</span><br><span class="line">      streamAllocation.noNewStreams();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 抛出协议异常</span></span><br><span class="line">  <span class="keyword">if</span> ((code == <span class="number">204</span> || code == <span class="number">205</span>) &amp;&amp; response.body().contentLength() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ProtocolException</span>(</span><br><span class="line">              <span class="string">&quot;HTTP &quot;</span> + code + <span class="string">&quot; had non-zero Content-Length: &quot;</span> + response.body().contentLength());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> response;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>CallServerInterceptor</code> 中可见，关于请求和响应部分都是通过 <code>HttpCodec</code> 来实现的。而在 <code>HttpCodec</code> 内部又是通过 <code>sink</code> 和 <code>source</code> 来实现的。所以说到底还是 IO 流在起作用。</p><h2 id="异步请求"><a href="#异步请求" class="headerlink" title="异步请求"></a>异步请求</h2><p>和同步请求类似，先实例化 OkHttpClient 和 Request 对象，然后使用 OkHttpClient 对象的 newCall() 方法创建 Call 对象，只不过最后执行 enqueue() 方法，整体和网络请求的思路相似。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">OkHttpClient</span> <span class="variable">okHttpClient</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OkHttpClient</span>.Builder().build();</span><br><span class="line"><span class="type">Request</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Request</span>.Builder().url(<span class="string">&quot;https://wshunli.com&quot;</span>).build();</span><br><span class="line"><span class="type">Call</span> <span class="variable">call</span> <span class="operator">=</span> okHttpClient.newCall(request);</span><br><span class="line">call.enqueue(<span class="keyword">new</span> <span class="title class_">Callback</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onFailure</span><span class="params">(Call call, IOException e)</span> &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onResponse</span><span class="params">(Call call, Response response)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>异步请求在 <code>Callback</code> 回调中获取响应，有 <code>onResponse()</code> 、 <code>onFailure()</code> 两个方法。</p><h3 id="发送异步网络请求"><a href="#发送异步网络请求" class="headerlink" title="发送异步网络请求"></a>发送异步网络请求</h3><p>前面三个步骤完全一致，我们从发送异步网络请求开始，异步请求是调用 <code>RealCall</code> 实例的 <code>enqueue()</code> 方法。。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// RealCall#enqueue()</span></span><br><span class="line"><span class="meta">@Override</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">enqueue</span><span class="params">(Callback responseCallback)</span> &#123;</span><br><span class="line">  <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (executed) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Already Executed&quot;</span>);</span><br><span class="line">    executed = <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  captureCallStackTrace();</span><br><span class="line">  eventListener.callStart(<span class="built_in">this</span>);</span><br><span class="line">  client.dispatcher().enqueue(<span class="keyword">new</span> <span class="title class_">AsyncCall</span>(responseCallback));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里使用 <code>Dispatcher</code> 分发器我来处理请求。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Dispatcher#enqueue()</span></span><br><span class="line"><span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">enqueue</span><span class="params">(AsyncCall call)</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (runningAsyncCalls.size() &lt; maxRequests &amp;&amp; runningCallsForHost(call) &lt; maxRequestsPerHost) &#123;</span><br><span class="line">    runningAsyncCalls.add(call);</span><br><span class="line">    executorService().execute(call);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    readyAsyncCalls.add(call);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实质上异步网络请求是在 <code>Dispatcher</code> 中做到任务调度。</p><p><font font size="3" color="#FF0000">下面介绍 OkHttp 中的 任务调度</font></p><p>我们来看 <code>Dispatcher</code> 类的源代码。 </p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Dispatcher</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">int</span> <span class="variable">maxRequests</span> <span class="operator">=</span> <span class="number">64</span>;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">int</span> <span class="variable">maxRequestsPerHost</span> <span class="operator">=</span> <span class="number">5</span>;</span><br><span class="line">  <span class="keyword">private</span> <span class="meta">@Nullable</span> Runnable idleCallback;</span><br><span class="line">  <span class="comment">/** Executes calls. Created lazily. */</span></span><br><span class="line">  <span class="comment">// 线程池的实现</span></span><br><span class="line">  <span class="keyword">private</span> <span class="meta">@Nullable</span> ExecutorService executorService;</span><br><span class="line">  <span class="comment">/** Ready async calls in the order they&#x27;ll be run. */</span></span><br><span class="line">  <span class="comment">// 就绪等待网络请求的异步队列</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> Deque&lt;AsyncCall&gt; readyAsyncCalls = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">  <span class="comment">/** Running asynchronous calls. Includes canceled calls that haven&#x27;t finished yet. */</span></span><br><span class="line">  <span class="comment">// 正在执行网络请求的异步队列</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> Deque&lt;AsyncCall&gt; runningAsyncCalls = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">  <span class="comment">/** Running synchronous calls. Includes canceled calls that haven&#x27;t finished yet. */</span></span><br><span class="line">  <span class="comment">// 正在执行网络请求的同步队列</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> Deque&lt;RealCall&gt; runningSyncCalls = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">Dispatcher</span><span class="params">(ExecutorService executorService)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.executorService = executorService;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">Dispatcher</span><span class="params">()</span> &#123;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 创建线程池</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">synchronized</span> ExecutorService <span class="title function_">executorService</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (executorService == <span class="literal">null</span>) &#123;</span><br><span class="line">      executorService = <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">0</span>, Integer.MAX_VALUE, <span class="number">60</span>, TimeUnit.SECONDS,</span><br><span class="line">          <span class="keyword">new</span> <span class="title class_">SynchronousQueue</span>&lt;Runnable&gt;(), Util.threadFactory(<span class="string">&quot;OkHttp Dispatcher&quot;</span>, <span class="literal">false</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> executorService;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/* 省略部分无关代码*/</span></span><br><span class="line">  <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">enqueue</span><span class="params">(AsyncCall call)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (runningAsyncCalls.size() &lt; maxRequests &amp;&amp; runningCallsForHost(call) &lt; maxRequestsPerHost) &#123;</span><br><span class="line">      runningAsyncCalls.add(call);</span><br><span class="line">      executorService().execute(call);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      readyAsyncCalls.add(call);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/* 省略部分无关代码*/</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>异步请求是放在线程池中执行的，如果最大异步请求数小于 64 并且 单个 HOST 的异步请求数小于 5 ，将请求添加到 <code>runningAsyncCalls</code> 中，否则添加到 <code>readyAsyncCalls</code> 中。</p><p>我们来看添加进线程池的 <code>AsyncCall</code> 类，实际上 <code>AsyncCall</code> 是继承自 <code>NamedRunnable</code> 的 <code>RealCall</code> 内部类。<code>NamedRunnable</code> 是实现了 <code>Runnable</code> 接口的抽象类。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">AsyncCall</span> <span class="keyword">extends</span> <span class="title class_">NamedRunnable</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> Callback responseCallback;</span><br><span class="line"></span><br><span class="line">  AsyncCall(Callback responseCallback) &#123;</span><br><span class="line">    <span class="built_in">super</span>(<span class="string">&quot;OkHttp %s&quot;</span>, redactedUrl());</span><br><span class="line">    <span class="built_in">this</span>.responseCallback = responseCallback;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  String <span class="title function_">host</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> originalRequest.url().host();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  Request <span class="title function_">request</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> originalRequest;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  RealCall <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> RealCall.<span class="built_in">this</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span> <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">signalledCallback</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="comment">// 和同步请求相同，调用拦截器，得到响应</span></span><br><span class="line">      <span class="type">Response</span> <span class="variable">response</span> <span class="operator">=</span> getResponseWithInterceptorChain();</span><br><span class="line">      <span class="keyword">if</span> (retryAndFollowUpInterceptor.isCanceled()) &#123;</span><br><span class="line">        signalledCallback = <span class="literal">true</span>;</span><br><span class="line">        responseCallback.onFailure(RealCall.<span class="built_in">this</span>, <span class="keyword">new</span> <span class="title class_">IOException</span>(<span class="string">&quot;Canceled&quot;</span>));</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        signalledCallback = <span class="literal">true</span>;</span><br><span class="line">        responseCallback.onResponse(RealCall.<span class="built_in">this</span>, response);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">      <span class="keyword">if</span> (signalledCallback) &#123;</span><br><span class="line">        <span class="comment">// Do not signal the callback twice!</span></span><br><span class="line">        Platform.get().log(INFO, <span class="string">&quot;Callback failure for &quot;</span> + toLoggableString(), e);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        eventListener.callFailed(RealCall.<span class="built_in">this</span>, e);</span><br><span class="line">        responseCallback.onFailure(RealCall.<span class="built_in">this</span>, e);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">      <span class="comment">// 在 runningAsyncCalls 中移除</span></span><br><span class="line">      client.dispatcher().finished(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>AsyncCall</code> 的 <code>execute()</code> 方法中，也是调用了 <code>getResponseWithInterceptorChain()</code> 方法来得到 <code>Response</code> 对象。从这里开始，就和同步请求的流程是一样的，就没必要讲了。</p><p>不同的是在得到 <code>Response</code> 后，进行结果的回调。</p><p>在 <code>AsyncCall</code> 的最后调用了 <code>Dispatcher</code> 的 <code>finished()</code> 方法。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Dispatcher#finished()</span></span><br><span class="line"><span class="comment">/** Used by &#123;<span class="doctag">@code</span> AsyncCall#run&#125; to signal completion. */</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">finished</span><span class="params">(AsyncCall call)</span> &#123;</span><br><span class="line">  finished(runningAsyncCalls, call, <span class="literal">true</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Used by &#123;<span class="doctag">@code</span> Call#execute&#125; to signal completion. */</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">finished</span><span class="params">(RealCall call)</span> &#123;</span><br><span class="line">  finished(runningSyncCalls, call, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">finished</span><span class="params">(Deque&lt;T&gt; calls, T call, <span class="type">boolean</span> promoteCalls)</span> &#123;</span><br><span class="line">  <span class="type">int</span> runningCallsCount;</span><br><span class="line">  Runnable idleCallback;</span><br><span class="line">  <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!calls.remove(call)) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AssertionError</span>(<span class="string">&quot;Call wasn&#x27;t in-flight!&quot;</span>);</span><br><span class="line">    <span class="comment">// 将 readyAsyncCalls 中的 call 移动到 runningAsyncCalls 中，并加入到线程池中</span></span><br><span class="line">    <span class="keyword">if</span> (promoteCalls) promoteCalls();</span><br><span class="line">    runningCallsCount = runningCallsCount();</span><br><span class="line">    idleCallback = <span class="built_in">this</span>.idleCallback;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (runningCallsCount == <span class="number">0</span> &amp;&amp; idleCallback != <span class="literal">null</span>) &#123;</span><br><span class="line">    idleCallback.run();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里所做的工作就是把执行过的 Call 移除，然后将 <code>readyAsyncCalls</code> 中的 Call 移动到 <code>runningAsyncCalls</code> 中并加入线程池中。</p><blockquote><p>基本上 OkHttp 的请求响应的流程就介绍完了，主要是关于 OkHttp 的 <strong>拦截器链</strong> 和 <strong>任务调度</strong> 原理。</p></blockquote><p>还有很多细节没有涉及，需要花费很大的精力，才能理解分析透彻，后面有机会再介绍。</p><blockquote><p>参考资料：<br>1、拆轮子系列：拆 OkHttp - Piasy的博客 | Piasy Blog<br><a href="https://blog.piasy.com/2016/07/11/Understand-OkHttp/">https://blog.piasy.com/2016/07/11/Understand-OkHttp/</a><br>2、OkHttp源码解析 | 俞其荣的博客 | Qirong Yu’s Blog<br><a href="http://yuqirong.me/2017/07/25/OkHttp%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/">http://yuqirong.me/2017/07/25/OkHttp源码解析/</a><br>3、OkHttp源码分析 - 掘金<br><a href="https://juejin.im/post/5af4482951882567286064e6">https://juejin.im/post/5af4482951882567286064e6</a><br>4、okhttp源码分析（一）——基本流程（超详细） - 简书<br><a href="https://www.jianshu.com/p/37e26f4ea57b">https://www.jianshu.com/p/37e26f4ea57b</a><br>5、OKHttp源码解析 | Frodo’s Blog<br><a href="http://frodoking.github.io/2015/03/12/android-okhttp/">http://frodoking.github.io/2015/03/12/android-okhttp/</a><br>6、OkHttp 源码解析（一）：基本流程 - Coding - SegmentFault 思否<br><a href="https://segmentfault.com/a/1190000012656606">https://segmentfault.com/a/1190000012656606</a><br>7、【Android】OkHttp源码分析 - CSDN博客<br><a href="https://blog.csdn.net/u010983881/article/details/79175824">https://blog.csdn.net/u010983881/article/details/79175824</a><br>8、深入浅出 OkHttp 源码 - DiyCode<br><a href="https://www.diycode.cc/topics/640">https://www.diycode.cc/topics/640</a><br>9、Okhttp框架源码分析 - 简书<br><a href="https://www.jianshu.com/p/18a4861600d1">https://www.jianshu.com/p/18a4861600d1</a><br>10、OkHttp 3.7源码分析（一）——整体架构 - CSDN博客<br><a href="https://blog.csdn.net/asiaLIYAZHOU/article/details/72598320">https://blog.csdn.net/asiaLIYAZHOU/article/details/72598320</a><br>11、okhttp网络框架源码解析 - CSDN博客<br><a href="https://blog.csdn.net/fanguangjun123/article/details/78621585">https://blog.csdn.net/fanguangjun123/article/details/78621585</a><br>12、OKHttp网络框架源码解析（一）okHttp框架同步异步请求流程和源码分析 - CSDN博客<br><a href="https://blog.csdn.net/qq_24675479/article/details/79483193">https://blog.csdn.net/qq_24675479/article/details/79483193</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍 OkHttp 网络框架，包含简单的使用和源码解析。&lt;strong&gt;本文内容基于 OkHttp 3.11.0 版本&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;网上关于 OkHttp 源码解析的文章有很多，我在这里参考他们的资料，形成自己的知识体系。&lt;/p&gt;</summary>
    
    
    
    <category term="源码解析" scheme="https://www.wshunli.com/categories/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    
    <category term="Android" scheme="https://www.wshunli.com/tags/Android/"/>
    
    <category term="网络框架" scheme="https://www.wshunli.com/tags/%E7%BD%91%E7%BB%9C%E6%A1%86%E6%9E%B6/"/>
    
    <category term="源码解析" scheme="https://www.wshunli.com/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    
    <category term="OkHttp" scheme="https://www.wshunli.com/tags/OkHttp/"/>
    
  </entry>
  
  <entry>
    <title>《剑指Offer》编程题目 Java 实现（01-10）</title>
    <link href="https://www.wshunli.com/posts/25842bb5.html"/>
    <id>https://www.wshunli.com/posts/25842bb5.html</id>
    <published>2018-09-11T13:07:05.000Z</published>
    <updated>2026-06-17T23:42:59.827Z</updated>
    
    <content type="html"><![CDATA[<p>《剑指Offer》编程题目 Java 实现，老是看书学习理论知识不太行，还得动手写代码啊。</p><span id="more"></span><p>笔试中的重要性不必多说，面试官还总是喜欢让手写代码。</p><p>1、赋值运算函数</p><p>2、单例设计模式</p><p>在设计模式中有详细的介绍，这里不再赘述，请移步：</p><p><a href="https://www.wshunli.com/posts/d1c4534.html">https://www.wshunli.com/posts/d1c4534.html</a></p><p>3、二维数组中查找目标值</p><blockquote><p>在一个二维数组中，每一行都按照从左到右递增的顺序排序，每一列都按照从上到下递增的顺序排序。请完成一个函数，输入这样的一个二维数组和一个整数，判断数组中是否含有该整数。</p></blockquote><p>（1）直接暴力查找</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">Find</span><span class="params">(<span class="type">int</span> target, <span class="type">int</span>[][] array)</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span>[] anArray : array) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> anAnArray : anArray) &#123;</span><br><span class="line">            <span class="keyword">if</span> (anAnArray == target) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（2）从右上角&#x2F;左下角的元素出发</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">Find</span><span class="params">(<span class="type">int</span> target, <span class="type">int</span>[][] array)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">row</span> <span class="operator">=</span> array.length;</span><br><span class="line">    <span class="type">int</span> <span class="variable">col</span> <span class="operator">=</span> array[<span class="number">0</span>].length;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>, j = col - <span class="number">1</span>; i &lt; row &amp;&amp; j &gt;= <span class="number">0</span>; ) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> array[i][j];</span><br><span class="line">        <span class="keyword">if</span> (value == target) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (value &lt; target) i++;</span><br><span class="line">        <span class="keyword">if</span> (value &gt; target) j--;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>4、替换字符串中的空格</p><blockquote><p>请实现一个函数，将一个字符串中的每个空格替换成 “%20” 。</p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">replaceSpace</span><span class="params">(StringBuffer str)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> str.toString().replaceAll(<span class="string">&quot; &quot;</span>, <span class="string">&quot;%20&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个太偷懒了，不那么偷懒：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">replaceSpace</span><span class="params">(StringBuffer str)</span> &#123;</span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">    <span class="type">String</span> <span class="variable">string</span> <span class="operator">=</span> str.toString();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; string.length(); i++) &#123;</span><br><span class="line">        <span class="type">char</span> <span class="variable">charAt</span> <span class="operator">=</span> string.charAt(i);</span><br><span class="line">        <span class="keyword">if</span> (charAt == <span class="string">&#x27; &#x27;</span>) &#123;</span><br><span class="line">            builder.append(<span class="string">&quot;%20&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            builder.append(charAt);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> builder.toString();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>5、从尾到头打印链表</p><blockquote><p>输入一个链表，按链表值从尾到头的顺序返回一个 ArrayList 。</p></blockquote><p>（1）借助堆栈的“后进先出”实现</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">*    public class ListNode &#123;</span></span><br><span class="line"><span class="comment">*        int val;</span></span><br><span class="line"><span class="comment">*        ListNode next = null;</span></span><br><span class="line"><span class="comment">*        ListNode(int val) &#123;</span></span><br><span class="line"><span class="comment">*            this.val = val;</span></span><br><span class="line"><span class="comment">*        &#125;</span></span><br><span class="line"><span class="comment">*    &#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> ArrayList&lt;Integer&gt; <span class="title function_">printListFromTailToHead</span><span class="params">(ListNode listNode)</span> &#123;</span><br><span class="line">    Stack&lt;Integer&gt; integers = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">while</span> (listNode != <span class="literal">null</span>) &#123;</span><br><span class="line">        integers.push(listNode.val);</span><br><span class="line">        listNode = listNode.next;</span><br><span class="line">    &#125;</span><br><span class="line">    ArrayList&lt;Integer&gt; arrayList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">while</span> (!integers.isEmpty()) &#123;</span><br><span class="line">        arrayList.add(integers.pop());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> arrayList;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（2）借助递归实现</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ArrayList&lt;Integer&gt; arrayList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">public</span> ArrayList&lt;Integer&gt; <span class="title function_">printListFromTailToHead</span><span class="params">(ListNode listNode)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (listNode != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.printListFromTailToHead(listNode.next);</span><br><span class="line">        arrayList.add(listNode.val);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> arrayList;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>（3）使用 Collections 的 reverse 方法</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> ArrayList&lt;Integer&gt; <span class="title function_">printListFromTailToHead</span><span class="params">(ListNode listNode)</span> &#123;</span><br><span class="line">    ArrayList&lt;Integer&gt; arrayList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">while</span> (listNode != <span class="literal">null</span>) &#123;</span><br><span class="line">        arrayList.add(listNode.val);</span><br><span class="line">        listNode = listNode.next;</span><br><span class="line">    &#125;</span><br><span class="line">    Collections.reverse(arrayList);</span><br><span class="line">    <span class="keyword">return</span> arrayList;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>6、由前序和中序遍历重建二叉树</p><p>7、用两个栈实现队列<br>8、求旋转数组的最小数字<br>9、斐波那契数列的第n项（青蛙跳台阶）<br>10、二进制中1的个数<br>11、数值的整数次方<br>12、打印1到最大的n位数<br>13、O(1)时间删除链表节点<br>14、使数组中的奇数位于偶数前面<br>15、找链表中倒数第K个节点<br>16、输出反转后的链表<br>17、合并两个有序链表<br>18、判断二叉树A中是否包含子树B<br>19、二叉树的镜像<br>20、顺时针打印矩阵<br>21、包含min函数的栈<br>22、判断一个栈是否是另一个栈的弹出序列<br>23、层序遍历二叉树<br>24、后序遍历二叉搜索树<br>25、二叉树中和为某值的路径<br>26、复杂链表的复制<br>27、二叉搜索树转换为双向链表<br>28、打印字符串中所有字符的排列<br>29、数组中出现次数超过一半的数字<br>30、找出最小的K个数<br>31、连续子数组的最大和<br>32、从1到整数n中1出现的次数<br>33、把数组中的数排成一个最小的数<br>34、求第N个丑数<br>35、第一个出现一次的字符<br>36、数组中逆序对的个数<br>37、两个链表的第一个公共节点<br>38、数字在排序数组中出现的次数<br>39、二叉树的深度<br>40、数组中只出现一次的两个数，而其他数都出现两次。<br>41、和为s的连续整数序列<br>42、翻转字符串<br>43、n个骰子的点数及出现的概率44. 扑克牌的顺子<br>44、圆圈中最后剩下的数<br>45、1+2+3+…+n的和<br>46、不用加减乘除做加法<br>47、不能被继承的类<br>48、字符串转换为整数<br>49、树中两个节点的最低公共祖先<br>50、找出重复的数<br>51、构建乘积数组<br>52、正则表达式匹配<br>53、表示数值的字符串<br>54、字符流中第一个不重复的字符<br>55、链表中环的入口节点<br>56、删除链表中重复的节点<br>57、二叉树的下一个节点<br>58、对称的二叉树<br>59、按之字形顺序打印二叉树<br>60、把二叉树打印成多行<br>61、序列化二叉树<br>62、二叉搜索树的第K个节点<br>63、数据流中的中位数<br>64、滑动窗口的最大值<br>65、矩阵中的路径<br>66、机器人的运动范围</p><blockquote><p>参考资料<br>1、剑指Offer_编程题_牛客网<br><a href="https://www.nowcoder.com/ta/coding-interviews">https://www.nowcoder.com/ta/coding-interviews</a><br>2、【剑指offer】Java版代码（完整版） - CSDN博客<br><a href="https://blog.csdn.net/baiye_xing/article/details/78428561">https://blog.csdn.net/baiye_xing/article/details/78428561</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;《剑指Offer》编程题目 Java 实现，老是看书学习理论知识不太行，还得动手写代码啊。&lt;/p&gt;</summary>
    
    
    
    <category term="语言基础" scheme="https://www.wshunli.com/categories/%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="Java" scheme="https://www.wshunli.com/tags/Java/"/>
    
    <category term="读书笔记" scheme="https://www.wshunli.com/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    <category term="《剑指Offer》" scheme="https://www.wshunli.com/tags/%E3%80%8A%E5%89%91%E6%8C%87Offer%E3%80%8B/"/>
    
  </entry>
  
  <entry>
    <title>图解数据结构-算法部分（Java语言实现）</title>
    <link href="https://www.wshunli.com/posts/444e2c0f.html"/>
    <id>https://www.wshunli.com/posts/444e2c0f.html</id>
    <published>2018-09-04T06:48:13.000Z</published>
    <updated>2026-06-17T23:42:59.828Z</updated>
    
    <content type="html"><![CDATA[<p>数据结构与算法一直是比较薄弱的地方，不仅在面试的时候会问相关问题、手写代码，而且在笔试的时候发挥重要作用。</p><span id="more"></span><p>前面一直学习的数据结构，下面排序、查找属于算法的范畴了。</p><h1 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h1><p>所谓 “排序” (Sorting) 就是指将一组数据，按特定规则调换位置，使数据具有某种顺序关系（递增或递减）。</p><p><strong>排序分类</strong>，可分为内部（内存中）和外部（外部存储器）排序两大类。</p><p>常见的内部排序法有：冒泡排序法、选择排序法、插入排序法、合并排序法、快速排序 法、堆积排序法、希尔排序法、基数排序法等。 至于比较常见的外部排序法有：直接合并排序法、K 路合并法、多相合并法等。 </p><p><strong>排序算法分析</strong>：算法是否稳定、时间复杂度、空间复杂度。</p><p>稳定的排序是指数据在经过排序后，两个相同键值的记录仍然保待原来的次序。</p><h2 id="内部排序法"><a href="#内部排序法" class="headerlink" title="内部排序法"></a>内部排序法</h2><p>内部排序法的时间复杂度及键值整理。</p><p><img src="https://img.wshunli.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F/%E5%86%85%E9%83%A8%E6%8E%92%E5%BA%8F%E6%B3%95.png" alt="内部排序法"></p><p>1、<strong>冒泡排序法</strong></p><p>冒泡排序（Bubble Sort）是一种简单的排序算法。它重复地走访过要排序的数列，一次比较两个元素，如果他们的顺序错误就把他们交换过来。</p><p><img src="https://img.wshunli.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E6%B3%95.gif" alt="冒泡排序法"></p><p>实现算法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> i, j, tmp;</span><br><span class="line"><span class="type">int</span> data[] = &#123;<span class="number">6</span>, <span class="number">5</span>, <span class="number">9</span>, <span class="number">7</span>, <span class="number">2</span>, <span class="number">8</span>&#125;;    <span class="comment">//原始数据</span></span><br><span class="line"><span class="keyword">for</span> (i = <span class="number">5</span>; i &gt; <span class="number">0</span>; i--)             <span class="comment">//扫描次数</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">for</span> (j = <span class="number">0</span>; j &lt; i; j++)         <span class="comment">//比较、交换次数</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 比较相邻两数，如第一数较大则交换</span></span><br><span class="line">        <span class="keyword">if</span> (data[j] &gt; data[j + <span class="number">1</span>]) &#123;</span><br><span class="line">            tmp = data[j];</span><br><span class="line">            data[j] = data[j + <span class="number">1</span>];</span><br><span class="line">            data[j + <span class="number">1</span>] = tmp;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是这样如论如何都会执行 $ n(n-1)&#x2F;2 $ 次，我们可以加一个判断在没有可替换的数据时终止程序。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bubble</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> i, j, tmp, flag;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">5</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        flag = <span class="number">0</span>;           <span class="comment">//flag用来判断是否有执行交换的动作</span></span><br><span class="line">        <span class="keyword">for</span> (j = <span class="number">0</span>; j &lt; i; j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (data[j + <span class="number">1</span>] &lt; data[j]) &#123;</span><br><span class="line">                tmp = data[j];</span><br><span class="line">                data[j] = data[j + <span class="number">1</span>];</span><br><span class="line">                data[j + <span class="number">1</span>] = tmp;</span><br><span class="line">                flag++;    <span class="comment">//如果有执行过交换，则flag不为0</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//当执行完一次扫描就判断是否做过交换动作，如果没有交换过数据，</span></span><br><span class="line">        <span class="comment">//表示此时数组已完成排序，故可直接跳出循环</span></span><br><span class="line">        <span class="keyword">if</span> (flag == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>冒泡排序是最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近 n²&#x2F;2 次, 时间复杂度为 O(n²) . 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). </p><p>平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的 temp 变量需要内存空间, 因此空间复杂度为常量O(1).</p><p>Tips: 由于冒泡排序只在相邻元素大小不符合要求时才调换他们的位置, 它并不改变相同元素之间的相对顺序, 因此它是稳定的排序算法.</p><p>2、<strong>选择排序法</strong></p><p>在未排序序列中找到最小（大）元素，存放到未排序序列的起始位置。</p><p>算法描述：</p><p>(1) 从待排序序列中，找到关键字最小的元素；<br>(2) 如果最小元素不是待排序序列的第一个元素，将其和第一个元素互换；<br>(3) 从余下的 N - 1 个元素中，找出关键字最小的元素，重复 (1)、(2) 步，直到排序结束。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">select</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> i, j, tmp;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;            <span class="comment">//扫描 5 次</span></span><br><span class="line">        <span class="keyword">for</span> (j = i + <span class="number">1</span>; j &lt; <span class="number">6</span>; j++) &#123;    <span class="comment">//由 i+1 比较起，比较 5 次</span></span><br><span class="line">            <span class="keyword">if</span> (data[i] &gt; data[j]) &#123;     <span class="comment">//比较第 i 及第 j 个元素</span></span><br><span class="line">                tmp = data[i];</span><br><span class="line">                data[i] = data[j];</span><br><span class="line">                data[j] = tmp;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>选择排序的简单和直观名副其实，这也造就了它 “出了名的慢性子” ，无论是哪种情况，哪怕原数组已排序完成，它也将花费将近 n²&#x2F;2 次遍历来确认一遍。即便是这样，它的排序结果也还是不稳定的。 唯一值得高兴的是，它并不耗费额外的内存空间。</p><p>3、<strong>插入排序法</strong></p><p>将数组中的所有元素依次跟前面已经排好的元素相比较，再将数组元素插入合适的位置。</p><p><img src="https://img.wshunli.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E6%B3%95.gif" alt="插入排序法"></p><p>实现算法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">insert</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> i;     <span class="comment">// i 为扫描次数</span></span><br><span class="line">    <span class="type">int</span> j;     <span class="comment">// j 来定位比较的元素</span></span><br><span class="line">    <span class="type">int</span> tmp;   <span class="comment">// tmp 用来暂存数据</span></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">1</span>; i &lt; size; i++) &#123;  <span class="comment">// 扫描循环次数为 SIZE-1</span></span><br><span class="line">        tmp = data[i];</span><br><span class="line">        j = i - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (j &gt;= <span class="number">0</span> &amp;&amp; tmp &lt; data[j]) &#123;  <span class="comment">// 如果第二元素小于第一元素</span></span><br><span class="line">            data[j + <span class="number">1</span>] = data[j]; <span class="comment">// 就把所有元素往后推一个位置</span></span><br><span class="line">            j--;</span><br><span class="line">        &#125;</span><br><span class="line">        data[j + <span class="number">1</span>] = tmp;       <span class="comment">// 最小的元素放到第一个元素</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Tips: 由于直接插入排序每次只移动一个元素的位，并不会改变值相同的元素之间的排序，因此它是一种稳定排序。</p><p>4、<strong>希尔排序法</strong></p><p>希尔排序是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序，待整个序列中的记录 “基本有序” 时，再对全体记录进行依次直接插入排序。</p><p>实现算法：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">shell</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> i;        <span class="comment">// i 为扫描次数</span></span><br><span class="line">    <span class="type">int</span> j;        <span class="comment">// j 来定位比较的元素</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> <span class="number">1</span>;    <span class="comment">// k 打印计数</span></span><br><span class="line">    <span class="type">int</span> tmp;      <span class="comment">// tmp 用来暂存数据</span></span><br><span class="line">    <span class="type">int</span> jmp;      <span class="comment">// 设定间隔位移量</span></span><br><span class="line">    jmp = size / <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">while</span> (jmp != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (i = jmp; i &lt; size; i++) &#123;</span><br><span class="line">            tmp = data[i];</span><br><span class="line">            j = i - jmp;</span><br><span class="line">            <span class="keyword">while</span> (j &gt;= <span class="number">0</span> &amp;&amp; tmp &lt; data[j])  <span class="comment">//插入排序法</span></span><br><span class="line">            &#123;</span><br><span class="line">                data[j + jmp] = data[j];</span><br><span class="line">                j = j - jmp;</span><br><span class="line">            &#125;</span><br><span class="line">            data[jmp + j] = tmp;</span><br><span class="line">        &#125;</span><br><span class="line">        jmp = jmp / <span class="number">2</span>;    <span class="comment">//控制循环数</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>5、<strong>合并排序法</strong></p><p>合并排序算法是将两个（或两个以上）有序表合并成一个新的有序表；<br>即把待排序序列分为若干个子序列，每个子序列是有序的，然后再把有序子序列合并为整体有序序列。</p><p><img src="https://img.wshunli.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F/%E5%90%88%E5%B9%B6%E6%8E%92%E5%BA%8F%E6%B3%95.gif" alt="合并排序法"></p><p>6、<strong>快速排序法</strong></p><p>快速排序法又称分割交换排序法，是目前公认最佳的排序法。</p><p>它的原理和冒泡排序法一样都是用交换的方式，不过它会先在数据中找到一个虚拟的中间值，把小于中间值的数据放在左边，而大于中间值的数据放在右边，再以同样的方式分别处理左右两边的数据，直到完成为止。</p><p><img src="https://img.wshunli.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E6%8E%92%E5%BA%8F/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E6%B3%95.gif" alt="快速排序法"></p><p>7、<strong>堆积排序法</strong></p><p>堆排序的过程就是将待排序的序列构造成一个堆，选出堆中最大的移走，再把剩余的元素调整成堆，找出最大的再移走，重复直至有序。</p><p>8、<strong>基数排序法</strong></p><p>基数排序（Radix sort）是一种非比较型整数排序算法，其原理是将整数按位数切割成不同的数字，然后按每个位数分别比较。由于整数也可以表达字符串（比如名字或日期）和特定格式的浮点数，所以基数排序也不是只能使用于整数。</p><h2 id="外部排序法"><a href="#外部排序法" class="headerlink" title="外部排序法"></a>外部排序法</h2><p>直接合井排序法 (Direct Merge Sort) 是外部存储设备最常用的排序方法。</p><p>它可以分为两个步骤：<br>步骤1: 将要排序的文件分为几个大小可以加载到内存空间的小文件，再使用内部排序法将各文件内的数据排序。<br>步骤2: 将第一步所建立的小文件每两个合并成一个文件。两两合井后，把所有文件合并成一个文件后就可以完成排序了。</p><h1 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h1><p>所谓查找，就是从数据文件中，寻找符合某特定条件的记录。而用来查找的条件就称为 “键值” 。</p><p>一般来说，如果数据在查找前经过排序，将可大幅减少查找的时间。至于查找技巧中比 较常见的方法有顺序法、二分查找法、斐波那契法、插值法、哈希法、m 路查找树、B-tree 等。</p><p>数据结构：<a href="https://www.wshunli.com/posts/850e5c53.html">https://www.wshunli.com/posts/850e5c53.html</a><br>算法：<a href="https://www.wshunli.com/posts/444e2c0f.html">https://www.wshunli.com/posts/444e2c0f.html</a></p><blockquote><p>参考资料<br>1、八大排序算法总结与java实现 | iTimeTraveler<br><a href="https://itimetraveler.github.io/2017/07/18/%E5%85%AB%E5%A4%A7%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93%E4%B8%8Ejava%E5%AE%9E%E7%8E%B0/">https://itimetraveler.github.io/2017/07/18/八大排序算法总结与java实现/</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;数据结构与算法一直是比较薄弱的地方，不仅在面试的时候会问相关问题、手写代码，而且在笔试的时候发挥重要作用。&lt;/p&gt;</summary>
    
    
    
    <category term="数据结构与算法" scheme="https://www.wshunli.com/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="Java" scheme="https://www.wshunli.com/tags/Java/"/>
    
    <category term="算法" scheme="https://www.wshunli.com/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="数据结构与算法" scheme="https://www.wshunli.com/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>图解数据结构（Java语言实现）</title>
    <link href="https://www.wshunli.com/posts/850e5c53.html"/>
    <id>https://www.wshunli.com/posts/850e5c53.html</id>
    <published>2018-08-29T12:41:03.000Z</published>
    <updated>2026-06-17T23:42:59.829Z</updated>
    
    <content type="html"><![CDATA[<p>数据结构与算法一直是比较薄弱的地方，不仅在面试的时候会问相关问题、手写代码，而且在笔试的时候发挥重要作用。</p><span id="more"></span><p>这次选择看的书籍是 《图解数据结构-使用Java》 ，先入门，后面再深入学习。</p><p>算法的时间复杂度，用来度量算法的运行时间，记作: T(n) &#x3D; O(f(n))。它表示随着 输入大小 n 的增大，算法执行需要的时间的增长速度可以用 f(n) 来描述。</p><p>线性表是 n 个元素的有限序列（n &gt;&#x3D; 0），是计算机科学中一种相当基础的数据结构。</p><h1 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h1><p><strong>数组</strong> 其实是一排紧密相邻的可读内存，并提供一个能够 <strong>直接访问</strong> 单一数据内容的计算方法。</p><p>这样能够直接通过计算，并访问任一位置的数据，即所谓的数组的 <strong>随机读取</strong> 。</p><p>当 Java 数组声明时会在内存中分配一定的暂存空间，空间大小以数据类型和数组数量为依据。</p><p>一维数据、二维数组、三维数组、n 维数组。</p><p>数组可用于矩阵、多项式等的运算。</p><h1 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h1><p><strong>链表</strong> 是由许多相同数据类型的元素按照特定顺序排列而成的线性表，其在内存中是不连续与随机存储的。</p><p>这样就不能像数组那样随机读取数据，而要 <strong>按照顺序</strong> 找到所需数据。</p><h2 id="单向链表"><a href="#单向链表" class="headerlink" title="单向链表"></a>单向链表</h2><p>单向链表是由节点组成，指针方向相同的链表。其中节点由数据字段和链接字段组成。</p><p>在 Java 中，声明节点：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">    <span class="type">int</span> data;</span><br><span class="line">    Node next;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Node</span><span class="params">(<span class="type">int</span> data)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">        <span class="built_in">this</span>.next = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而 Java 中没有指针的概念，我们声明两个对象分别指向第一个和最后一个节点：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Node first;</span><br><span class="line">    <span class="keyword">private</span> Node last;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>1、<strong>单向链表的创建</strong></p><p>下面创建简单单向链表类：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Node first;</span><br><span class="line">    <span class="keyword">private</span> Node last;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> first == <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insert</span><span class="params">(<span class="type">int</span> data)</span> &#123;</span><br><span class="line">        <span class="type">Node</span> <span class="variable">node</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Node</span>(data);</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.isEmpty()) &#123;</span><br><span class="line">            first = node;</span><br><span class="line">            last = node;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            last.next = node;</span><br><span class="line">            last = node;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">print</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Node</span> <span class="variable">current</span> <span class="operator">=</span> first;</span><br><span class="line">        <span class="keyword">while</span> (current != <span class="literal">null</span>) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;current.data=&quot;</span> + current.data);</span><br><span class="line">            current = current.next;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后实例化链表对象即可：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">LinkedList</span> <span class="variable">linkedList</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LinkedList</span>();</span><br><span class="line">        linkedList.insert(<span class="number">99</span>);</span><br><span class="line">        linkedList.insert(<span class="number">90</span>);</span><br><span class="line">        linkedList.insert(<span class="number">95</span>);</span><br><span class="line">        linkedList.print();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样所有节点都知道下个节点在哪里，只要有首节点的存在，就可以对整个列表进行遍历、插入及删除节点等动作。</p><p>2、<strong>单向链表节点的删除</strong></p><p>将欲删除节点的前一个节点的指针指向欲删除节点的下一个节点即可。</p><p>如果删除 <strong>首节点</strong>，将首节点的下个节点设置为首节点；如果删除 <strong>末节点</strong>，将前一个节点指向 null 即可。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">delete</span><span class="params">(Node node)</span> &#123;</span><br><span class="line">    Node newNode;</span><br><span class="line">    Node temp;</span><br><span class="line">    <span class="keyword">if</span> (first.data == node.data) &#123;</span><br><span class="line">        first = first.next;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (last.data == node.data) &#123;</span><br><span class="line">        temp = first;</span><br><span class="line">        <span class="keyword">while</span> (temp.next != last) &#123;</span><br><span class="line">            temp = temp.next;</span><br><span class="line">        &#125;</span><br><span class="line">        temp.next = last.next; <span class="comment">// temp.next = null;</span></span><br><span class="line">        last = temp; <span class="comment">// 设置末节点</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        newNode = first;</span><br><span class="line">        temp = first;</span><br><span class="line">        <span class="keyword">while</span> (temp.data != node.data) &#123;</span><br><span class="line">            newNode = temp;</span><br><span class="line">            temp = temp.next;</span><br><span class="line">        &#125;</span><br><span class="line">        newNode.next = temp.next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样删除有点弊端，根据 node 节点的值判断是否是同一节点，并且没有对节点是否存在做判断。</p><p>3、<strong>单向链表节点的添加</strong></p><p>添加节点和删除节点有点类似，将前一个节点指向新添加的节点，然后将新添加节点指向下一个节点即可。</p><p>如果添加为 <strong>首节点</strong> ，将欲添加节点指向首节点；如果添加为 <strong>末节点</strong> ，将原末节点指向新节点即可。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insert</span><span class="params">(Node node)</span> &#123;</span><br><span class="line">    Node newNode;</span><br><span class="line">    Node temp;</span><br><span class="line">    <span class="keyword">if</span> (node.next == first) &#123;</span><br><span class="line">        node.next = first;</span><br><span class="line">        first = node;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (node.next == <span class="literal">null</span>) &#123;</span><br><span class="line">        last.next = node;</span><br><span class="line">        node.next = <span class="literal">null</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        newNode = first;</span><br><span class="line">        temp = first;</span><br><span class="line">        <span class="keyword">while</span> (node.next != newNode.next) &#123;</span><br><span class="line">            temp = newNode;</span><br><span class="line">            newNode = newNode.next;</span><br><span class="line">        &#125;</span><br><span class="line">        temp.next = node;</span><br><span class="line">        node.next = newNode;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样在节点位置的判断上还是有弊端的。</p><p>4、<strong>单向链表的反转</strong></p><p>面试有时候会让手写这个代码。</p><p><strong>遍历法</strong>: 从链表头部开始，逐个反转节点。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Node <span class="title function_">reverse</span><span class="params">(Node head)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (head == <span class="literal">null</span>) <span class="keyword">return</span> <span class="literal">null</span>;      <span class="comment">// 空链表</span></span><br><span class="line">    <span class="keyword">if</span> (head.next == <span class="literal">null</span>) <span class="keyword">return</span> head; <span class="comment">// 一个元素的链表</span></span><br><span class="line"></span><br><span class="line">    <span class="type">Node</span> <span class="variable">preNode</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">Node</span> <span class="variable">nowNode</span> <span class="operator">=</span> head;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (nowNode != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">Node</span> <span class="variable">nextNode</span> <span class="operator">=</span> nowNode.next;   <span class="comment">// 保存下一个结点</span></span><br><span class="line">        nowNode.next = preNode;         <span class="comment">// 当前结点指向前一个结点</span></span><br><span class="line">        preNode = nowNode;              <span class="comment">// 前任结点 到现任节点</span></span><br><span class="line">        nowNode = nextNode;             <span class="comment">// 现任节点到下一结点</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> preNode;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>递归法</strong>：从链表尾部开始，逐个反转节点。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Node <span class="title function_">reverse</span><span class="params">(Node node)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (node == <span class="literal">null</span> || node.next == <span class="literal">null</span>) <span class="keyword">return</span> node;</span><br><span class="line">    <span class="type">Node</span> <span class="variable">headNode</span> <span class="operator">=</span> reverse(node.next);</span><br><span class="line">    node.next.next = node;</span><br><span class="line">    node.next = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">return</span> headNode;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上算法都需要传入链表的头部节点，打印时需要注意头部和尾部节点引用。</p><p>5、<strong>单向链表的串联</strong></p><p>将列表的首位节点相连即可。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> LinkedList <span class="title function_">connect</span><span class="params">(LinkedList list1, LinkedList list2)</span> &#123;</span><br><span class="line">    <span class="type">LinkedList</span> <span class="variable">list</span> <span class="operator">=</span> list1;</span><br><span class="line"><span class="comment">//  while (list.last.next != null) &#123;</span></span><br><span class="line"><span class="comment">//     list.last = list.last.next;</span></span><br><span class="line"><span class="comment">//  &#125;</span></span><br><span class="line">    list.last.next = list2.first;</span><br><span class="line">    <span class="keyword">return</span> list;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="环形链表"><a href="#环形链表" class="headerlink" title="环形链表"></a>环形链表</h2><p>我们把单向链表的尾部指向头部，整个链表就成为单向环形结构。</p><p>这里创建链表、插入节点、删除节点、链表串联都很类似。</p><h2 id="双向链表"><a href="#双向链表" class="headerlink" title="双向链表"></a>双向链表</h2><p>双向链表基本结构和单项连链表类似，至少一个节点存放数据，另外它有两个字段存放指针。</p><h1 id="堆栈"><a href="#堆栈" class="headerlink" title="堆栈"></a>堆栈</h1><p>堆栈是一种抽象的数据结构：只能从堆栈的 <strong>顶端</strong> 访问数据；数据访问符合 <strong>后进先出</strong> 的原则。</p><h2 id="堆栈的数组实现"><a href="#堆栈的数组实现" class="headerlink" title="堆栈的数组实现"></a>堆栈的数组实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">StackByArray</span> &#123; <span class="comment">//以数组模拟堆栈的类声明</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span>[] stack; <span class="comment">//在类中声明数组</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> top;  <span class="comment">//指向堆栈顶端的索引</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//StackByArray类构造函数</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">StackByArray</span><span class="params">(<span class="type">int</span> stack_size)</span> &#123;</span><br><span class="line">        stack = <span class="keyword">new</span> <span class="title class_">int</span>[stack_size]; <span class="comment">//建立数组</span></span><br><span class="line">        top = -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//类方法：push</span></span><br><span class="line">    <span class="comment">//存放顶端数据，并更正新堆栈的内容</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">push</span><span class="params">(<span class="type">int</span> data)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (top &gt;= stack.length) &#123; <span class="comment">//判断堆栈顶端的索引是否大于数组大小</span></span><br><span class="line">            System.out.println(<span class="string">&quot;堆栈已满，无法再加入&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            stack[++top] = data; <span class="comment">//将数据存入堆栈</span></span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//类方法：empty</span></span><br><span class="line">    <span class="comment">//判断堆栈是否为空堆栈，是则返回true，不是则返回false</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">empty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (top == -<span class="number">1</span>) <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//类方法：pop</span></span><br><span class="line">    <span class="comment">//从堆栈取出数据</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">pop</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (empty()) <span class="comment">//判断堆栈是否为空，如果是则返回-1值</span></span><br><span class="line">            <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="keyword">return</span> stack[top--]; <span class="comment">//先将数据取出后，再将堆栈指针往下移</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="堆栈的链表实现"><a href="#堆栈的链表实现" class="headerlink" title="堆栈的链表实现"></a>堆栈的链表实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Node</span> <span class="comment">//链接节点的声明</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span> data;</span><br><span class="line">    Node next;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Node</span><span class="params">(<span class="type">int</span> data)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">        <span class="built_in">this</span>.next = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">StackByLink</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> Node front; <span class="comment">//指向堆栈底端的指针</span></span><br><span class="line">    <span class="keyword">public</span> Node rear;  <span class="comment">//指向堆栈顶端的指针</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//类方法：isEmpty()</span></span><br><span class="line">    <span class="comment">//判断堆栈如果为空堆栈,则front==null;</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> front == <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//打印堆栈内容</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">output_of_Stack</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Node</span> <span class="variable">current</span> <span class="operator">=</span> front;</span><br><span class="line">        <span class="keyword">while</span> (current != <span class="literal">null</span>) &#123;</span><br><span class="line">            System.out.print(<span class="string">&quot;[&quot;</span> + current.data + <span class="string">&quot;]&quot;</span>);</span><br><span class="line">            current = current.next;</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//在堆栈顶端加入数据</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insert</span><span class="params">(<span class="type">int</span> data)</span> &#123;</span><br><span class="line">        <span class="type">Node</span> <span class="variable">newNode</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Node</span>(data);</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.isEmpty()) &#123;</span><br><span class="line">            front = newNode;</span><br><span class="line">            rear = newNode;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            rear.next = newNode;</span><br><span class="line">            rear = newNode;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//在堆栈顶端删除数据</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">pop</span><span class="params">()</span> &#123;</span><br><span class="line">        Node newNode;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.isEmpty()) &#123;</span><br><span class="line">            System.out.print(<span class="string">&quot;===目前为空堆栈===\n&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        newNode = front;</span><br><span class="line">        <span class="keyword">if</span> (newNode == rear) &#123;</span><br><span class="line">            front = <span class="literal">null</span>;</span><br><span class="line">            rear = <span class="literal">null</span>;</span><br><span class="line">            System.out.print(<span class="string">&quot;===目前为空堆栈===\n&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (newNode.next != rear)</span><br><span class="line">                newNode = newNode.next;</span><br><span class="line">            newNode.next = rear.next;</span><br><span class="line">            rear = newNode;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="堆栈的应用"><a href="#堆栈的应用" class="headerlink" title="堆栈的应用"></a>堆栈的应用</h2><p>二叉树及森林的遍历；图形的深度优先遍历；递归程序的调用及返回等等。</p><h1 id="队列"><a href="#队列" class="headerlink" title="队列"></a>队列</h1><p>队列是一种抽象的数据结构：只能从队列的 <strong>两端</strong> 访问数据；数据访问符合 <strong>先进先出</strong> 的原则。</p><h2 id="队列的数组实现"><a href="#队列的数组实现" class="headerlink" title="队列的数组实现"></a>队列的数组实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ArrayQueue</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span>[] data;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> size;<span class="comment">//元素个数</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> front;<span class="comment">//队列中第一个对象的位置</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> rear;<span class="comment">//队列中当前对象的位置</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ArrayQueue</span><span class="params">()</span> &#123;</span><br><span class="line">        data = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">10</span>];</span><br><span class="line">        size = <span class="number">0</span>;</span><br><span class="line">        front = <span class="number">0</span>;</span><br><span class="line">        rear = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(<span class="type">int</span> t)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (isFull()) &#123;</span><br><span class="line">            resize();</span><br><span class="line">            front = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        rear = (front + size) % data.length;</span><br><span class="line">        System.out.println(rear);</span><br><span class="line">        data[rear] = t;</span><br><span class="line">        size++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">remove</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (isEmpty()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;队列为空!&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> <span class="variable">tempData</span> <span class="operator">=</span> data[front];</span><br><span class="line">        data[front] = <span class="number">0</span>;</span><br><span class="line">        front = (front + <span class="number">1</span>) % (data.length);</span><br><span class="line">        size--;</span><br><span class="line">        <span class="keyword">return</span> tempData;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">size</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> size;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> size == <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isFull</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> size == data.length;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">resize</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">/*注意重新扩容的时候并不需要去设置size</span></span><br><span class="line"><span class="comment">         * 队列的大小并不能通过数组的大小直观的显示出来。</span></span><br><span class="line"><span class="comment">         * 但是栈就可以直观的通过数组的大小显示出来*/</span></span><br><span class="line">        <span class="type">int</span>[] tmp = <span class="keyword">new</span> <span class="title class_">int</span>[data.length * <span class="number">2</span>];</span><br><span class="line">        System.arraycopy(data, <span class="number">0</span>, tmp, <span class="number">0</span>, data.length);</span><br><span class="line">        data = tmp;</span><br><span class="line">        tmp = <span class="literal">null</span>;<span class="comment">//引用置为空，便于gc处理  </span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="队列的链表实现"><a href="#队列的链表实现" class="headerlink" title="队列的链表实现"></a>队列的链表实现</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">QueueNode</span>                 <span class="comment">// 队列节点类</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span> data;                    <span class="comment">// 节点数据</span></span><br><span class="line">    QueueNode next;              <span class="comment">// 指向下一个节点</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//构造函数</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">QueueNode</span><span class="params">(<span class="type">int</span> data)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.data = data;</span><br><span class="line">        next = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Linked_List_Queue</span> &#123; <span class="comment">//队列类</span></span><br><span class="line">    <span class="keyword">public</span> QueueNode front; <span class="comment">//队列的前端指针</span></span><br><span class="line">    <span class="keyword">public</span> QueueNode rear;  <span class="comment">//队列的尾端指针</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//构造函数</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Linked_List_Queue</span><span class="params">()</span> &#123;</span><br><span class="line">        front = <span class="literal">null</span>;</span><br><span class="line">        rear = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//方法enqueue:队列数据的存入</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">enqueue</span><span class="params">(<span class="type">int</span> value)</span> &#123;</span><br><span class="line">        <span class="type">QueueNode</span> <span class="variable">node</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">QueueNode</span>(value); <span class="comment">//建立节点</span></span><br><span class="line">        <span class="comment">//检查是否为空队列</span></span><br><span class="line">        <span class="keyword">if</span> (rear == <span class="literal">null</span>)</span><br><span class="line">            front = node; <span class="comment">//新建立的节点成为第一个节点</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            rear.next = node; <span class="comment">//将节点加入到队列的尾端</span></span><br><span class="line">        rear = node; <span class="comment">//将队列的尾端指针指向新加入的节点</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//方法dequeue:队列数据的取出</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">dequeue</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> value;</span><br><span class="line">        <span class="comment">//检查队列是否为空队列</span></span><br><span class="line">        <span class="keyword">if</span> (!(front == <span class="literal">null</span>)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (front == rear) rear = <span class="literal">null</span>;</span><br><span class="line">            value = front.data; <span class="comment">//将队列数据取出</span></span><br><span class="line">            front = front.next; <span class="comment">//将队列的前端指针指向下一个</span></span><br><span class="line">            <span class="keyword">return</span> value;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125; <span class="comment">//队列类声明结束</span></span><br></pre></td></tr></table></figure><p>环形队列、优先队列、双向队列</p><h1 id="树状结构"><a href="#树状结构" class="headerlink" title="树状结构"></a>树状结构</h1><p>树是一种用来表述有 <strong>分支</strong> 的数据结构，是由一个或者一个以上的节点组成的有限集合。</p><p><strong>树的专有名词</strong>：</p><p>结点度：结点子树的个数；树的度：树中最大的结点度。</p><p>叶子节点：没有子节点的节点，即度为 0 的节点。</p><h2 id="二叉树"><a href="#二叉树" class="headerlink" title="二叉树"></a>二叉树</h2><p>二叉树最多有两个子节点，即度 &lt;&#x3D; 2 的树。</p><p><strong>特殊的二叉树</strong>：</p><p>1、满二叉树，树的高度为 h 树的节点为 $2^h-1$ 我们称为满二叉树。</p><p>2、完全二叉树，树的高度为 h 树的节点小于 $2^h-1$ ，但是其节点和满二叉树从左到右，从上到下的顺序一一对应。</p><p><img src="https://img.wshunli.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%9B%BE%E8%A7%A3%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E4%BA%8C%E5%8F%89%E6%A0%91-%E7%89%B9%E6%AE%8A%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91.png" alt="特殊的二叉树"></p><p>3、歪二叉树，当一个二叉树完全没有右节点&#x2F;左节点时。</p><p>4、严格二叉树，每个二叉树都有非空的左右子树。成为严格二叉树。</p><h2 id="二叉树的存储方式"><a href="#二叉树的存储方式" class="headerlink" title="二叉树的存储方式"></a>二叉树的存储方式</h2><p>1、<strong>数组表示法</strong></p><p>首先将二叉树想象为满二叉树，然后依次存放入数组中，空位为 null 即可。</p><blockquote><p>以数组建立二叉树，要求小于父节点的值放在左子节点，反之放在右边。</p></blockquote><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CH06_01</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String args[])</span> <span class="keyword">throws</span> IOException</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="type">int</span> i, level;</span><br><span class="line">        <span class="type">int</span> data[] = &#123;<span class="number">6</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">9</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">4</span>, <span class="number">2</span>&#125;; <span class="comment">/*原始数组*/</span></span><br><span class="line">        <span class="type">int</span> btree[] = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">16</span>];</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">16</span>; i++) btree[i] = <span class="number">0</span>;</span><br><span class="line">        System.out.print(<span class="string">&quot;原始数组内容: \n&quot;</span>);</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">8</span>; i++)</span><br><span class="line">            System.out.print(<span class="string">&quot;[&quot;</span> + data[i] + <span class="string">&quot;] &quot;</span>);</span><br><span class="line">        System.out.println();</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">8</span>; i++)                    <span class="comment">/*把原始数组中的值逐一对比*/</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (level = <span class="number">1</span>; btree[level] != <span class="number">0</span>; )   <span class="comment">/*比较树根及数组内的值*/</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (data[i] &gt; btree[level])        <span class="comment">/*如果数组内的值大于树根，则往右子树比较*/</span></span><br><span class="line">                    level = level * <span class="number">2</span> + <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">else</span>                               <span class="comment">/*如果数组内的值小于或等于树根，则往左子树比较*/</span></span><br><span class="line">                    level = level * <span class="number">2</span>;</span><br><span class="line">            &#125;                                      <span class="comment">/*如果子树节点的值不为0，则再与数组内的值比较一次*/</span></span><br><span class="line">            btree[level] = data[i];                <span class="comment">/*把数组值放入二叉树*/</span></span><br><span class="line">        &#125;</span><br><span class="line">        System.out.print(<span class="string">&quot;二叉树内容：\n&quot;</span>);</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">1</span>; i &lt; <span class="number">16</span>; i++)</span><br><span class="line">            System.out.print(<span class="string">&quot;[&quot;</span> + btree[i] + <span class="string">&quot;] &quot;</span>);</span><br><span class="line">        System.out.print(<span class="string">&quot;\n&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、链表表示法</p><p>二叉链表结构主要由一个数据域和两个分别指向左、右孩子的结点组成，其结构如下：</p><p><img src="https://img.wshunli.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%9B%BE%E8%A7%A3%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E4%BA%8C%E5%8F%89%E6%A0%91-%E9%93%BE%E8%A1%A8%E8%A1%A8%E7%A4%BA%E6%B3%95.png" alt="链表表示法"></p><p>TreeNode 及 BinaryTree 声明如下：</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TreeNode</span> &#123;</span><br><span class="line">    <span class="type">int</span> value;</span><br><span class="line">    TreeNode left_Node;</span><br><span class="line">    TreeNode right_Node;</span><br><span class="line">    <span class="comment">// TreeNode构造函数</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">TreeNode</span><span class="params">(<span class="type">int</span> value)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.value = value;</span><br><span class="line">        <span class="built_in">this</span>.left_Node = <span class="literal">null</span>;</span><br><span class="line">        <span class="built_in">this</span>.right_Node = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//二叉树类声明</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BinaryTree</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> TreeNode rootNode; <span class="comment">//二叉树的根节点</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//构造函数:利用传入一个数组的参数来建立二叉树</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">BinaryTree</span><span class="params">(<span class="type">int</span>[] data)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; data.length; i++)</span><br><span class="line">            Add_Node_To_Tree(data[i]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//将指定的值加入到二叉树中适当的节点</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">Add_Node_To_Tree</span><span class="params">(<span class="type">int</span> value)</span> &#123;</span><br><span class="line">        <span class="type">TreeNode</span> <span class="variable">currentNode</span> <span class="operator">=</span> rootNode;</span><br><span class="line">        <span class="keyword">if</span> (rootNode == <span class="literal">null</span>) &#123; <span class="comment">//建立树根</span></span><br><span class="line">            rootNode = <span class="keyword">new</span> <span class="title class_">TreeNode</span>(value);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//建立二叉树</span></span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (value &lt; currentNode.value) &#123; <span class="comment">//在左子树</span></span><br><span class="line">                <span class="keyword">if</span> (currentNode.left_Node == <span class="literal">null</span>) &#123;</span><br><span class="line">                    currentNode.left_Node = <span class="keyword">new</span> <span class="title class_">TreeNode</span>(value);</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> currentNode = currentNode.left_Node;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123; <span class="comment">//在右子树</span></span><br><span class="line">                <span class="keyword">if</span> (currentNode.right_Node == <span class="literal">null</span>) &#123;</span><br><span class="line">                    currentNode.right_Node = <span class="keyword">new</span> <span class="title class_">TreeNode</span>(value);</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> currentNode = currentNode.right_Node;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样增删很容易，但是不容易找到父节点，除非增加字段。</p><h2 id="二叉树的遍历"><a href="#二叉树的遍历" class="headerlink" title="二叉树的遍历"></a>二叉树的遍历</h2><p>二叉树的遍历：即“访问树中所有节点各一次”。按照二叉树特性，一律从左向右。</p><p>根据访问根节点的顺序，二叉树的遍历规则主要有四种，先根次序遍历，中根次序遍历，后根次序遍历以及层次遍历。</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 中序遍历</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">InOrder</span><span class="params">(TreeNode node)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (node != <span class="literal">null</span>) &#123;</span><br><span class="line">        InOrder(node.left_Node);</span><br><span class="line">        System.out.print(<span class="string">&quot;[&quot;</span> + node.value + <span class="string">&quot;] &quot;</span>);</span><br><span class="line">        InOrder(node.right_Node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 前序遍历</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">PreOrder</span><span class="params">(TreeNode node)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (node != <span class="literal">null</span>) &#123;</span><br><span class="line">        System.out.print(<span class="string">&quot;[&quot;</span> + node.value + <span class="string">&quot;] &quot;</span>);</span><br><span class="line">        PreOrder(node.left_Node);</span><br><span class="line">        PreOrder(node.right_Node);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 后序遍历</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">PostOrder</span><span class="params">(TreeNode node)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (node != <span class="literal">null</span>) &#123;</span><br><span class="line">        PostOrder(node.left_Node);</span><br><span class="line">        PostOrder(node.right_Node);</span><br><span class="line">        System.out.print(<span class="string">&quot;[&quot;</span> + node.value + <span class="string">&quot;] &quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="图形结构"><a href="#图形结构" class="headerlink" title="图形结构"></a>图形结构</h1><p>图形结构是用来探讨两个顶点间是否相连的关系图，若在边上加权值，这类图成为“网络”。</p><h2 id="图形介绍"><a href="#图形介绍" class="headerlink" title="图形介绍"></a>图形介绍</h2><p>图形有两种：有向图、无向图。</p><p>图形的专业术语：</p><p>度：一个顶点所拥有边的总数。<br>入&#x2F;出度：在有向图中，定点为箭头终点的边的个数为入度；出度为起点边的个数。</p><h2 id="图形的表示法"><a href="#图形的表示法" class="headerlink" title="图形的表示法"></a>图形的表示法</h2><p>1、邻接矩阵法&#x2F;相邻表法</p><p>2、相邻多元列表法&#x2F;索引表格法</p><h2 id="图形的遍历"><a href="#图形的遍历" class="headerlink" title="图形的遍历"></a>图形的遍历</h2><p>图形的遍历方法有两种：深度优先遍历、广度优先遍历。</p><p>1、深度优先使用递归与 <strong>堆栈</strong> 的技巧</p><p>从图形的某一顶点开始遍历，被访问过的顶点就做上已访问的记号，接着遍历此顶点的所有相邻且未访问过的顶点中的任意一个顶点，并做上已访问的记号，再以该点为新的起点继续进行先深后广的搜索。</p><p><img src="https://img.wshunli.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%9B%BE%E8%A7%A3%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%9B%BE%E5%BD%A2-%E5%9B%BE%E7%9A%84%E9%81%8D%E5%8E%86.png" alt="图形-图的遍历"></p><p>（1）从起点 1 开始，将相邻的 2 3 放入堆栈</p><p>3 2</p><p>（2）将 2 取出，并将与 2 相邻且未访问的 4 5 放入堆栈</p><p>3 5 4</p><p>（3）将 4 取出，并将与 4 相邻且未访问的 8 放入堆栈</p><p>3 5 8</p><p>（4）将 8 取出，并将与 8 相邻且未访问的 5 放入堆栈</p><p>3 5 5</p><p>（5）将 5 取出，发现与 5 相邻的节点都访问过了，这里就舍去</p><p>3</p><p>（6）将 3 取出，并将与 3 相邻且未访问的 6 7 放入堆栈</p><p>7 6</p><p>（7）最后将堆栈的节点逐个判断即可。</p><p>7 7</p><p>最终遍历顺序为：1 -&gt; 2 -&gt; 4 -&gt; 8 -&gt; 5 -&gt; 3 -&gt; 6 -&gt; 7</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">dfs</span><span class="params">(<span class="type">int</span> current)</span></span><br><span class="line">&#123;</span><br><span class="line">    run[current] = <span class="number">1</span>;</span><br><span class="line">    System.out.print(<span class="string">&quot;[&quot;</span> + current + <span class="string">&quot;]&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> ((Head[current].first) != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (run[Head[current].first.x] == <span class="number">0</span>) <span class="comment">//如果顶点尚未遍历，就进行dfs的递归调用</span></span><br><span class="line">            dfs(Head[current].first.x);</span><br><span class="line">        Head[current].first = Head[current].first.next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、广度优先使用递归与 <strong>队列</strong> 的技巧</p><p>从图形的某顶点开始遍历，被访问过的顶点就做上已访问的记号，接着遍历此顶点的所有相邻且未访问过的顶点中的任意个顶点，并做上已访问的记号，再以该点为新的起点继续进行先广后深的搜索。</p><p><img src="https://img.wshunli.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/%E5%9B%BE%E8%A7%A3%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%9B%BE%E5%BD%A2-%E5%9B%BE%E7%9A%84%E9%81%8D%E5%8E%86.png" alt="图形-图的遍历"></p><p>（1）从起点 1 开始，将相邻的 2 3 放入堆栈</p><p>2 3</p><p>（2）将 2 取出，并将与 2 相邻且未访问的 4 5 放入堆栈</p><p>3 4 5</p><p>（3）将 3 取出，并将与 3 相邻且未访问的 6 7 放入堆栈</p><p>4 5 6 7</p><p>（4）将 4 取出，并将与 4 相邻且未访问的 8 放入堆栈</p><p>5 6 7 8</p><p>（5）将 5 取出，并将与 5 相邻且未访问的 8 放入堆栈</p><p>6 7 8 8</p><p>（6）将 6 取出，并将与 6 相邻且未访问的 7 放入堆栈</p><p>7 8 8 7</p><p>（7）将 7 取出，发现与 7 相邻的节点都访问过了，这里就舍去</p><p>8 8 7</p><p>（8）最后将队列的节点逐个判断即可。</p><p>8 7</p><p>最终遍历顺序为：1 -&gt; 2 -&gt; 3 -&gt; 4 -&gt; 5 -&gt; 6 -&gt; 7 -&gt; 8</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bfs</span><span class="params">(<span class="type">int</span> current)</span> &#123;</span><br><span class="line">    Node tempnode; <span class="comment">//临时的节点指针</span></span><br><span class="line">    enqueue(current); <span class="comment">//将第一个顶点存入队列</span></span><br><span class="line">    run[current] = <span class="number">1</span>; <span class="comment">//将遍历过的顶点设定为1</span></span><br><span class="line">    System.out.print(<span class="string">&quot;[&quot;</span> + current + <span class="string">&quot;]&quot;</span>); <span class="comment">//打印该遍历过的顶点</span></span><br><span class="line">    <span class="keyword">while</span> (front != rear) &#123; <span class="comment">//判断目前是否为空队列</span></span><br><span class="line">        current = dequeue(); <span class="comment">//将顶点从队列中取出</span></span><br><span class="line">        tempnode = Head[current].first; <span class="comment">//先记录目前顶点的位置</span></span><br><span class="line">        <span class="keyword">while</span> (tempnode != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (run[tempnode.x] == <span class="number">0</span>) &#123;</span><br><span class="line">                enqueue(tempnode.x);</span><br><span class="line">                run[tempnode.x] = <span class="number">1</span>; <span class="comment">//记录已遍历过</span></span><br><span class="line">                System.out.print(<span class="string">&quot;[&quot;</span> + tempnode.x + <span class="string">&quot;]&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            tempnode = tempnode.next;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="生成树"><a href="#生成树" class="headerlink" title="生成树"></a>生成树</h2><p>一个图形的生成树以最少的边来连接图形中所有的顶点，且不造成回路(Cycle)的树状结构。</p><p>深度优先生成树、广度优先生成树。</p><p>MST 生成树，即在加权图（网络）上，计算路径成本最小的的生成树。有 Peim 算法和 Kruskal 算法等。</p><blockquote><p>前面一直学习的数据结构，下面排序、查找属于算法的范畴了。</p></blockquote><p>数据结构：<a href="https://www.wshunli.com/posts/850e5c53.html">https://www.wshunli.com/posts/850e5c53.html</a><br>算法：<a href="https://www.wshunli.com/posts/444e2c0f.html">https://www.wshunli.com/posts/444e2c0f.html</a></p><blockquote><p>参考资料<br>1、《图解数据结构-使用Java》<br>2、（数据结构）十分钟搞定时间复杂度（算法的时间复杂度） - 简书<br><a href="https://www.jianshu.com/p/f4cca5ce055a">https://www.jianshu.com/p/f4cca5ce055a</a><br>3、单链表反转的两种实现（Java） - CSDN博客<br><a href="https://blog.csdn.net/acquaintanceship/article/details/73011169">https://blog.csdn.net/acquaintanceship/article/details/73011169</a><br>4、data structures - Reversing a linked list in Java, recursively - Stack Overflow<br><a href="https://stackoverflow.com/questions/354875/reversing-a-linked-list-in-java-recursively">https://stackoverflow.com/questions/354875/reversing-a-linked-list-in-java-recursively</a><br>5、【算法】如何判断链表有环 - CSDN博客<br><a href="https://blog.csdn.net/u010983881/article/details/78896293">https://blog.csdn.net/u010983881/article/details/78896293</a><br>6、队列的实现(JAVA) - CSDN博客<br><a href="https://blog.csdn.net/lcore/article/details/8868078">https://blog.csdn.net/lcore/article/details/8868078</a><br>7、树和二叉树定义、基本术语和性质 - CSDN博客<br><a href="https://blog.csdn.net/lsh_2013/article/details/43121373">https://blog.csdn.net/lsh_2013/article/details/43121373</a><br>8、java数据结构与算法之树基本概念及二叉树（BinaryTree）的设计与实现 - CSDN博客<br><a href="https://blog.csdn.net/javazejian/article/details/53727333">https://blog.csdn.net/javazejian/article/details/53727333</a><br>9、data structures - Difference between “Complete binary tree”, “strict binary tree”,”full binary Tree”? - Stack Overflow<br><a href="https://stackoverflow.com/questions/12359660/difference-between-complete-binary-tree-strict-binary-tree-full-binary-tre">https://stackoverflow.com/questions/12359660/difference-between-complete-binary-tree-strict-binary-tree-full-binary-tre</a><br>10、数据结构 - 图的基本术语 - CSDN博客<br><a href="https://blog.csdn.net/wangzi11322/article/details/45417081">https://blog.csdn.net/wangzi11322/article/details/45417081</a><br>11、《图论》——图的存储与遍历（Java） - CSDN博客<br><a href="https://blog.csdn.net/Gamer_gyt/article/details/51498546">https://blog.csdn.net/Gamer_gyt/article/details/51498546</a><br>12、Java 与图 - 简书<br><a href="https://www.jianshu.com/p/a47a147ec92c">https://www.jianshu.com/p/a47a147ec92c</a><br>13、DFS（深度优先搜索）和BFS(广度优先搜索) - 简书<br><a href="https://www.jianshu.com/p/b086986969e6">https://www.jianshu.com/p/b086986969e6</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;数据结构与算法一直是比较薄弱的地方，不仅在面试的时候会问相关问题、手写代码，而且在笔试的时候发挥重要作用。&lt;/p&gt;</summary>
    
    
    
    <category term="数据结构与算法" scheme="https://www.wshunli.com/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="Java" scheme="https://www.wshunli.com/tags/Java/"/>
    
    <category term="数据结构与算法" scheme="https://www.wshunli.com/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"/>
    
    <category term="数据结构" scheme="https://www.wshunli.com/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
  </entry>
  
  <entry>
    <title>Jenkins及C++自动检测流水线搭建流程方法</title>
    <link href="https://www.wshunli.com/posts/22a0081c.html"/>
    <id>https://www.wshunli.com/posts/22a0081c.html</id>
    <published>2018-08-17T02:18:16.000Z</published>
    <updated>2026-06-17T23:42:59.824Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look.">  <script id="hbeData" type="hbeData" data-hmacdigest="af9a3aa4965c80893e3071cd2e3ef8ca400c6e2ac1c0e68a74958f94791c4c66">2f1f22b262c5c5f48f1931f0a262bffc5efb0785a6077000bac24df2c1c1b12e419752508b227eefa5fd07a9178e68adb60c61d5bf582f664dc7360ed18304aa4016f2826bb5b443c18695456b2cfda4ed3148ca754051077fb030edd58a6ff8851ae47ee77e32f09ca00f0f3ff0fcccb619e6fdfc0dd73792cff82b423d879219add5dc54bbf93a327b756d7b232d6ab43c79ca4c49ac675b050be0b90633af6be62751013cb149793c83ad84d10949579f6be958792e8596b6927910b5e720f52467b975d75c6f6f4f1131179091329ceb5ed4d24dc7635e922be21613073fb170952b6591db95dbe9334060787e177d3b9c476638b0b52bc037342f8ea3a6b5c3903f99ede896fb1113aa6617c0ded5281c8e80b0ab749da3edd241d754a4b11a63af57526fdeda6e46c5a980242504b11a5851899993c8c49356beee3e5271aa70fae17f37785f6dc5cefca622e2880bf17369c4544fd138230241476a557e1cd7c9cfc7b0a950a261831d4ca03f6de1b3f4d765866673e647a14944b6d1fc53255824e9ca3fc64c1702c1b279ba79ce8de3bc0e47437d0e746710a5d1a72924af0af53ff3f4ac5fd4a06118853fa590cb5468140bc0ea63f0d15a4bd0e60c04618deb7cd094ee1d3f29c04489857beae0f8988e594aab599412548d8ee09da7086d5a921087bf165e34d237f278d2c7d23188741762f8f57d5a2f23d2937419a36fe35cf9e294c2ca502b2a04143225de8d5b14db54b0b2f9a17749f3d97ddcbbcd5f51fbc9dbbf3c28dee1d0693f608e47566197f316bd0a843f7e555e0babc0964f27c4ef6221750f8e041a19db03477384f020b5ec6e1979cbccb91543b86454dbe66e38d6ca4a1b7e07db8014507ecc30370473e69c1b776bbc993a66d136bc0b955c8501e8747a4d7d8d2f1ac5b2ee679fa80316e1b02d8a8a4fda7e437a0d4f0f9cdff8fe1dc0eeab9a900384e3f0628f29ecf2fbdfba0c39fbf9b74ee8f6376ef7de8ecee4f8a8d2097b95e6358397b2f0c14a4e9ac3864ededc49d0017a51604ed4a62098898a5117f1b2030c37558bb2e9cba20982eb226d57bbb6cf6b237feaf802d729eb82045bdab6db6aca4adc8136743db0b01d79972f2e6bf6e2bba377652135f0624690255638256aa64c05876f0f5a305e82c76397426807b7535485aa95eb5d370ec61d3e803da8d1ccff7b4e0fdaabcc6d748ae998ae034b0ff4358455b5d8aab6e4305798b691abe60e69e6e4b37a79f8ae244583cc5ac3c241d980e9e400420932b3bb8f9bb26cab898407d0d1c8f749260834c00f06a0307524f3b61d0bba9f120969e904bc81a1139160176d2f07f8de054dc94ed83c9d9223db138d6152db997e58b20f71d8325e87d20285081b2cfde408f7effaadf6f37779c8ce37e96466c20319b64a5df6c0028deed05c412b62a1431833f3f00c4f032708f394d4b133644bf5bebea4011d353a4766709b4c54ff8cb01968ff998f79fa36a2b91a92ef76c84382a687913adc6bb82854a6d4e827fbec2e022d3b967d1e93e2bad8e05b6edae20d7b2508d9998d56f5c9c86fdad4ac32ea6610d9c4034a3237fbe54673c2e84b9e2d7668e4d7dbb119400488604044d6deeec39597fcc6a3ac9736b15dd38525c916d282ed9b698186f20c0a61963d1a69654978b20deaa5413f1dfd3acaa6a269d73455f875b9068fc0e307f77c03efcb36bda517eb3849e6f1e22058b52eef3d5aacfe9283dd3d70c49d222529072919c2287de38e37e31452a66442f167b93960d62ec6ef7abfe1bb73ddf95b13b7b20fe24fe3b4df56d54abdfe51569a65b2a40b3e10241e6b2da4b827dfc5629bbdef3e68aa62a971c783226f7fa1d458eb16c18b087639527f6ae227a7dc9e96c6c6879f822f61ce7f05f473191b31a7ceed313ac958b726dcd1008cb09f8369fdec36cd7e31ed89ab24d96a8e873902d8b7b166a514d0c977876200bf52e324cfa8f5fac53734492d19363154fa1f82bf92ae49dd26922dac23491e29d716037e2e1212242b3fa36b2a10b61c6cfe718ceee6c1ad676adcaeb33e75dfb0b0ac212d165d3e15adb7c6077cb1bc5d2b278bab99454b791ca721b9aa9caad34ee13e44726b227a17acf6372c492453a6510977f6bdfba7bceb02722f85e7164a6f18ddf95b8b8cca706cc19faba5bb2c2720e4cca110fd93669e2d31a40afd484347d743a519cc60e2349b8ae81b2826d8a137fcdf879f41408569df96c0f639cb7b7a3b375d84c4f1bdc35c2f52d9db9ccd5849448620fe8b0de8cf7217d21ea9a75843f1e83ef967384d038db03f2074579cc13f5cd12793307f806882b550dd65b0ada7d0b4f2d6b52a9c0ad6bfc8c0c03cbd755b1f4810732b1faaa8f8fddeec25c794a7a179479a4dcedd01af2cd2b1dc0384710ffbdefda14c8177d82e74006be9b579bb700907ce9888f9fa54a49c8500db2c119583a299afa7db56775cc5eead21ce6fcc915d5b272a92216c034045d45a6317a01ec55fd0850020215982b09cebd743f2e2f48ef33394a40e33880bb50928d15f03b9d859b550c24e7ccbed6e66e1d0db2a6ca138ead4b1e4788610413147b96d66ed3f7eff79230fe0fe2c6e34e018954b22ce990e17ddf54d3e7d8b9e37ddfb6bf174aa3561b811a2fb9f345732233d4e91879bdb4b9c2becebe32c93697977d25d4cd862ccad40642bbf99319843b208eb06535b01099f8a3103c020702782f3e51b964e85dd14d27cec694f1a5baeed7273ee88063ebf01db4e48fabcb994b50b86f6c04c3b38219479051713754ede33e650ba9bc743ed2d0ab491db9d0b4a30e66f12dae8e408d3b46d2617b92b07ecbb214549a43165c78d7703f7b189fac3d1cc0411f27258d916ce7c07cea0517ed1f01d8910c4962e1e50c327523434bb16db33eb0df2f0d0f2884a5d5e265ec73747dd0c59df3c1a229ea9965a08a850472a261790ac5b6a967b100f5c70ed2b253ed8e0fb7dd15e8fb5f0794eebd4da300d451d93e7b78f57299b86829ebc40a48c743c8827d9cfa520e411db7d6ab7362eea95da20deecf6765c4623f8fdcb519ee71ef20b0f32d08b971f3214a8652376f3a0e43670257776bf0444947cbc19b7d92faed42566fb78b270fd4c7a742ce8b45ea0f8fdf4f1b7209a1f0398498794d0ec89fd51ace8ccfcf2f49b45498774175bbe1a1428af50659472496aeceffb5787ea8f05cb35b095738b6c751afa81fc3f355aaa34a61547061964524866e1efc03353fe9662f64987e0cbe2ee3c2aeaee68c4ac4e5de413119c5af9af73b06f7e493519a7fc5a73a4b82739267bd290d09e4978365bedc87de50635ec37e940e6a673fa1c2a1a2618099569dc16fcb6218e2e276530a40b3fb8ded42a293ef303e8c7172126e37ace66c98f423bd8490cb994127d5df5ce0ed1ff8d64d108ebd249733b74f245ffca2ac318b829e516f42583632bcf66d2ab20bb42006518775d0d75c6525ae1f4f0c5f6aea3f1d6955d53518947b1a33ddc560f0b68ffaa4dac00eef7a8755b2d3329d7367044cd869e0ede2fe01171b85bd1b951c041f01afa6bd80eff1b24dfaba747e1dcd6d6f06010e6ab04b2c787fd816bb53ce2e085d3acedbde2cb34176bcf4ae0a71006352684a916ea8e7a7bdc4f87de68761e05b5f689e9aa4649eb65fe3666f618c4a413ba4e53ad40e53669379c17cca680baddea96e7509823ec481f88db0cc6c52f89390749c48c9a68268d8162540dcd0b11b83ef307219563c75ca22aae7e95e60eb6e6b60155b42f4c41a07daba5ff5229eb755ff230ef47f2430f11bf00ddc04f5fbe4c0279fd91d071dd07f2278a997a9b04d0251cbf85812584bfb7dbaf1385597273b50eb53a61040286e09a24b13b40d21e81c12dfabadcb54d36506538b440be627f04b8e038bc2c397d03780f11fa9a909cc68381c051018932bf4b0d83826308b80b1053c0cb83f20f15b3d364c381bc2b33dab2d4bdb2fd99b4931822d839257f604d01823cc29c6e66ae9a57923fc8bd0a63cca59d671320adb80e3d99b53435446c29630d8f9d488b99e8b8ab8d8044eb492aa863953bc3141520286fd264784a9b313942fbdfbed60e631da34a0ef97b446f2c253ab21b8c1eb54adc15d9a94436b86d47e5f783c020718a40884a75025467c1c9d9f5f1d451b072ea1648356cfec43c44afbb675cf5df5ef9ec3a277f7acf029d56b4ed3eef0e36f09464830d6f3f7917bcd920424bb2fefedc3031b7e7705445f1e2da430d3201e12c74d7edd9e472be49a68ca4d067ac20a727580760f3d8e3ccb3e93fd31f3104a529adb85ae942d34c156a7d659e71b8090ad486c507e16e462128472e24599b9fc436b00f8503e501c7510f937278016a9f90c186b79ff94b7ea702a76728ba04b7898bf23994080cfd29c7f49647b3de420e1191646f2ecfe647f2a2e53a1192c09724dfe2495dff8a70ba800efe3a2c5261e537733b691946865d5ecbd743d7054e5c014fd4e1759a4495a4a00a922f5861fc6e6ba33e9f1bbc499fcdb21af27bfa6f9e462a6f33989b13faeb37adb7293eb563c1b3d685815c326abd51e1c2452103513c9a88943ce6e96d7112152595682ab7c5d7404e4e5154f4f28f5f4e769da1d30b2e064ab5c95dcd35165967d3bc73439aef73ec6a9da736c0a26c0ad2421f650ccb99736beb751d96250156bd1c7d0d2c54d082bb59d494e2a0bb61e0a728a36566a1f02e39e8b0e40fd479fece2477e813cf41ba7f104fb755942ae9417d16a5aafdd4e3f404d2c86e7109ade66953cd66482fbe61014c5cbf02039cc74dafefad87c8d4021e137351ddace50f917c415ab6cb1330f8635af0e186956e162a32bc098d793367eb69bbf9a5fa3554288b7748521598be42afa657328993320f9af5368cec591b7997a180dbc90b3696cbd8e5eda546546b098452dee63f454206f89d019d61bb08f7e3ed9f21c833a761dadfa3d1f44ce14f987e4e8c3cef8e182129bb97051aa24cdbb1d46ce33abfabd92c1eb2140235768d08b0ed27f74c74a26296c6b31749f1d6ebb02ec088f65a5bbfc0005b3240b64d18df58cb42372aab784d2693c39de23df4059c8e9c3125acb7fdd33b77fb76cc24444bb4e17bdee390766ceface56b61fa1da58a643a0d89692c418899f56d2ed6562b1aada54976741e6cb541985012a2d71b5469c6bd21acd1af7a24b904d65199f6e9e27d5fe697af382a0d5893a143ed7de02d012158746da036172408bae907dc1c172f55c72d8123022e6e1ea50112108eaf7086ed7c70d9864836a3e4c4cb4f9c234eb868a786da2227736e888e23135cfd6127136832ba8bf7c58f591764e2fcd27f893da3d217daba0b96d8e25105ffcd51aef55fa76b5f21f34b389a3a94f6135369716d0b1965cddfc3e0aa6699148eb9474a0baa5b532939aace4827c4512bc22941fc12535538bd951726d6e6742b8a63b3ad9f0ad4f3944b46c2af5198ecfeec01d41c8ab58bc7b17bc610aba3796f1d6700bcd90cded8b65fe6890313a10a5358b4c52f4d091679ce4afd673f4c2b84421c801dda3dfd3b50152a9011aea96145ecc7f6e1c0fcaeffd070cfe97f348b1616a4e6980dcf00a489c7c97ee1e0889dff8d513b3f8dd70f217fe3bf841d5cd0361feb31af7e5114b4ea5b099b3d7864b9604b08b79dd1b82f00098e88b9e0bb1b815ed22a3c2d2484339a7c7e379f37f5c412fc6aa2bf659647ce74eb07867def8172852e911a361b64b9ec226fb66caeeec9fa4d2e414bf48f10aca727056ca01d234db699d2f53bd7ef8ce85118a5f4080a33a6f6255c26863665df975c7a1ed343820eac5d6ff5dfc4c87182f6c1f9b62cc6273ecc6a649e31309ef0cda24273c6c385c8adf01cba197c3de09ae7e7fe59c15dfd40983c151091fdfd86c262b99fb6cd30eb565bf1490518647023745c6261216fcf95e8ba431a478740c20d00ed9f0cf68531ede6b6d96967896c85e11e5bcd9580f74b5612a382b8e2ea0385354e9680c090dd639a7fb217bfce13844394eaefd6a5ba9020984103198aa2a33d8077af8d40077f668be63ba52489e1f13e4d09ffa31e4a53b9a78b2c71d7f6ce9b26989b0c575d55f334489c7de61b498c3fb75db5c974d4d8b59be443e094b69f0fb8ef6de5b23964041b04880795a8f7d7f52b3cc325a86840c4a18254a76b433113d6c71809a5cb57933c37912316daab792f4bd038940656d05a394ad11e81b10c05fa1e614aa307f6a5660624243ca7f8f6523a07c5ad7cea9d2d5b97334acf3c1c129fa804a12871ff33203bb41f8bb7a5d7aeae1b94bc419ff6e6ab2025054d9dfe8f133099c21e22c1f585bc72a2c4f44f58bbaf9e9482869a6c8adab20c8057679d5565c6093876c61d37fa5f0764fa5f2866529c4365625db77214b358035e634c38d4e473729ad54a3ae70a19969aa66ba5cb339cfb66575e255c2b0ef3cf1c606654ca83be8862b0390155917a2f563d577e0b7169783764b39a2a242f9f0601a2edb2fdc0f041329d8f47677858d1301618f790f38d95486a21d3c8d92fce81b9560bd9e531339061850a438b58b6918712ae5ba90835319413fd87402b9b51a718cc295dcc2979d541491644e621cf3d199d544eaec3847cb9706db65ccc45af206b295979599ed26a2dcb36a5e201b0a4fccc8267823c17bd0a9b2fd1c0e9151ed62608a12e0edb278562cc66e6b6ebae8baa2faa0250604dd52551cd6eacc8c07e6d302decb92c712b50e200564a2077f1d9368ee84b574b99657a8954bec1c221ea8068ef29d1d35efda7020caf19aecdce441387fc47b2ba8921951e5b49b04e5b946cff388f70892927c5a555555999e85e5e7436aa57943c4a3ef29524199e2bd2aa598e679e3de7aacf328e42de8072e73ccf2ec858a20dc0559a03fa44823ae9168ac8839fce7ff7fd7e3f4a1a5b3deb0a5b39339ec7702a7b2a465f801b115b9fbb6d4a83694dc9ec6379a6d011361d53065c4ced86c5a65f6c9fbc819a6db87b9ee7054fafe93251d1b780363577a700e042695bab77f4f9eaad7858b92064654edc710bc16145d6af5463e3541576ad5dc9abda1e4d639610be5ad5ddefd1a2bf07cfe7812f777331caae1a7fc2380e77fb8b18d4e90152f789c46d1d44a8026023bf433b1cd1689bf17b407cd141386a3a70e3575a362f4ede53f434cd696b3a83833980a9ca439800621adc3e9ce5fbaf631ea21e3de8556e40a182dd8e2bd5d98d6d716a482f8211012a2aee3565bd1c1cc6c8f2ae63d3793bbfd310a2882ceec7aabd1890403cdd838f898040370b30e07ddc5fa7a87d08b3b3ba2274d98d2b1bd8a2138b95afb7e28ce52ce6e09cea661ed800eeb6f8ae849cb0f25c1a25210c65f70f5c8e306511024579718b822f60d729c03db7b5e6c3f15fdaca8b19f857fe006ccab6ab4cb04314238308204de7099121f176205c3dc45f67f75fbcbe258d9493c73210e65591eadfd3d840f8757b487255bf47fca704308f916ad1f8579427b5a5e2892722661d143a10648fefa0f3c22cebc70545b5a859224303ba4968ad5abd840b4e0e984465ad71c7a8dc17b3d433c5fbdc4d302ecbb3e16e2a2388ef9252f3c111da9c7572aff2839391619d0682fec4310af2d37804f8cc1a1da04562cc4d3b2bb34c5725d3a5dbfab873ac59bdb4a6534d8ca1a4b6df2706e4f1a22e67f728ee471db04a5d5f19b4d8709eb6f094281a236ba1c1a02b5ddd7ada263dd1e70098525aed38fd21a80f5d2e3d462522627f8ff003da1ed96b9d7db2c94c68e924ebaadc96663abea59c9b0349db807194a7386a0a3ca89c908bdf58bd0821f4754ccee33bf0826a34086d35c4d66061903a7a4d7a9b4d608de3df1b662eb9bf3acc59abd6a21bae2b0f0388b46abd17f126e371442e872311275b4e7466a229a88450e7f8bd49d5f8e50563e0ccf0c3b58fbf10e922ab9fb516ac7b092c21a25bae4e607ca570a7c7f11cc28b25eda3e48e6442494be9c81a82e6899f4f85c1e82dec7bd8f320b95f4b02a94a49a992deecec22a76afb309fe3af908e17106299066e7bb615e6c9dc351296e8d31f725e91c4d5d7b5b0274c95d0815e0eef7d908e0a5ed5d4b9ea500d50df670e3ac12dd6ec6a8610c9835740044572176a53a91c267ffbad3abc63fc8340a78ea4a4eac620e03d83ee62c9dbadac047ebaee3096a52a7c9d265e8c209f47987a0de553764682da54c18864d82e0035c524fc2df688b58f2af42676d5dade578acace4b7ae7566472e73a8fba6a25eb948d33724922bc0f9f6f9ee09aadabfc5149e98aa1a6f671135036dbb4a6767266fbf56377cfe3f754c5831d023dce043e6bfc63e19303153ed1ee2a8fb99fc8bb58da9d4def9051975dc679be23a0d2db0db6c1b8ed6f0defd00c6377514dc86d384468b36194483117f48cd7bcf6839cabeaadcd20a03a5b92e5f2c7f540b20c0f764a97984f13a20754835afb3a2f2f8b9af14a807ee69512555528370e664ee58399a4a9f424df16ad22bcba45b2350cb089d2725fc1b8849fb71d9ab23ff37e5d39d6d17cc454881536f1994d6a3cb1001110c14e9effae5c93b7d91fdb3b3a01640441d39f45c43a407bd2eaeef2196b0c4007faedd4569455548f28ab5b08afad12afbf60e1ec99d917b96283ce2201a89a88c6810fd6ab6495a2225403a7440d0b6bad2360cbb784d94e515b2710a0d2516c99ebbdd75e2f2d8814d77fe5a6eeda11ebb901ababa7bc589522e93fbde50ad5d72e4bc4b5ff82eb3598bf4260edee0f9ac6f1a91da55e5f711187830a7b17024862e10e5277a90289e90caa0f286415e2499572bbf5569756a5164d660839e674afe3bb7375241a9b4433373cf621765454096a30e0a6a93b067cdeddb7dc7a8df01b18dac5fa117aa074b52ada1382b1dbf00c741766a979b0b62ab9e1210b9ba94606fb0995f26b7c689198e382d666f0ed1ee7a07e96e0279ca6b2c66e96b6bedaf1911377c9cbd1ea53f6fc83f1151aecf595b8de7024ca4947a92a7e25c275008c2464da9793419cf47a3932ed81eeea3dc538863db0e6a50af93241eae50879be54cb02c816d324bab14adab8606e705dc44d3b5e63fb3be03d720b2d5981aa9c1f21f5948285b9b2ede5ba9a5039e770efead1461bd15be596426a6bb2cb7ba8c11608a6e96f34d8f6013dcb1a80bc426e72f255b89f2e8303af71d2d28b2e3cf37b7816d7968adbd9e1152e091f285a7ee275d0dae337d7f82d68a93b48542430695932ba4a58c5ad4c7e2314be900ed948eb05806ca29ab26e30fd1e0899a242d7147e8fa47bc25aec2055455b69efc38cd27dbc9498ae7f016285df0f973de8b1887ff753914606964af502bd7a458accb20deba50fc33883b59511c1e1f287c15d3dd68d58e2265541e974e1669a74126e2a5c875b9c33b493528935968aa1f13776a20930cb7f57e020aa1f84a522c5364aea388c934d978030fa00765b08338ce80731f941d532ec37a963e33c6344edb2a56fe6450d2465415dcf0dbea24b6e851d02c8fee2598ff2694a0a4bd245ad01e8848a866e7c5f633cbb3e65fc677464d1eb8eb25eb740ebf1eeb2b4d4c4cc6d463e56419613f1226adafbec5666a9f19072ad3a23473e9122e94728ff97c6eac414e7d4047be88c04477c18cfdaaee91f3fdb62646b34d3c16d647310b99a51e6079f65fde1caaed44630b4dbfc712ed32a9d0a424d3b89fc93c60ff1bfc6f42f5c0d0997133ce5df836e9924f36f2ad624f7aff8470eebe10d24247997414b44efa6e538ad06b3a93783e4081d9cbfbc8870212e964627e67ed9c714e660766ee5c6993d0e51563fd3048e3bc6be64a2c8d72876d91bdfad820ddb7266282623469c54622d31685539895f5b5df4215ce895bf16f5f939d3ed43b3bae7496a97983864ef29fac3812ec06fe939d49b45b7aee0e236e69b81ea370aeb5e3b89e4038b7fa66943adc1489a01e16718c87c4cdeb9738f61a63b8fa57c7ae3028f5d715ca25bd328d4ff99915e8f43e0656135de25e2a536fd301c9949a8ab13b5ffc43bae4869db5cfce57fcca77301f6284b24483c6e6a77ecd363602cfead9e9679a8959a078c161ab9d5a23f4ca80d3b6b68344b58f8a78d69446e85dc5cf96649d3784c560c316e550204c2bba8c2be8172fe09dd27428facaebd4ce26323f93152d8d5ec376c93c51e84e6976eaf2b10f4d424c3200e7ad2adf97c7eeda14b931e811ac76c26125431c846c3ae047602bba63e14bf24cbc551dd462aba375350162a2190663876b5b669197650d7b2e51e4479e07ce05dafc7d18776817c2e5602eccf5fae7a53bc6999002cc3b1c54535a585f08283985e32fa295c5b9df546af4b8edef1de0bf123fd8d2e0a27db85835523b0db0403572ae331b6a119a8f2c4e96d3734b0fbe375878cfb4f599aadfc7b61bfe17f234973af75582ecb4c663405f95ae0f386fcaefcfe37a27c75e2fc49620081ead58e696f6c3a95acea0ce30323b4c9600e3771274d58802caa8bc843668f35098723cffb42d5eb9c137b4acca5d26663f151ca718964b43d300f2ea146b8e0b84113599309c1dbeac55bb4ad2ec4d55af574f2f986f4471d0c353bfe3908d5a515dd5c3acce9efd6e9da5ff08a156a23bbd5c13e77704b693f67c0ff19a3bd176e39ae53829a20d731ff4ee0ad528e3572f297d6c0ed5e7ae93c0e775b8ee111d079eb20af74fa3ec4545dd8ff70bd0759a45ed99a863808e8cacf16d92fc174f736cab351655ddf0ff9624f7ac0464cf8d39a88d1c2d57359fe132c5553f13bc648c22b1f8c1067b2c75c4e15849f41f29576e1fbd67449de038ac3586b4775516238cf533b7d9ae174aff60a1d765cbc56732626e17b69c2aff0365c0f43c27fd6b80ae58f6f843c3190085a3374fb245b963246135b960ef3bba37709f37ec7699f8f5a31b0fbc719676a69208df6bcccba684776fe2fc4bb344022f701bab55ce0a0e0c2791b93b3f6691a676ecf02f97a68432cdb82d4b5b6f848b220a1fa96628d559ec497297a2da212a581eb52a02331afc3288780e85cdc8f18d6d6cb1be144ae6c30c720e71796399374852c34d9936e0a9833560f01226b246a3970c34de49cb127010a61f0ed6461861026b5936cdf271ef694b6007cab60e19f9bcc06f9c71ad0046fbaa632b07e593c4315a77f84d21b4941ce22ba997ec510ec98adaf244a4c5d7d6a2c6d84b6656bc370516b526caa389000a98aece9aa396e4c20a2f35978de48184209a1f21e96783f132ec3106ab06cb8947fad220146fc311f037bd2fbc0827fa2c18b1347c10f98c4305bcde72b5164db2df32b165dc3a477349367671d363fea56d28503a78e84c1223886a4f23a4b1d0b8e025942355dd34193c3a21a147b8442a5ffdd31a9b9682c0ed33f1b3e3576ea36cb2b19a2541451b689d010dd6524479bc33c3ab243288e5098a99f882f806059f650f74745d4bd1426ce2eb8c5242764f9f4805a5324d7d505ce44f525efea3492737ad9a712c93c47d79059ecbda3e55fc1083f919fedbe967129d9fa967b9d0f1681915614e50be7ba191d520431d3a15ef09adb5cac15cc1b3d47c9037a400737f34078db82909d48ccf9da34ffaaa77c82bf896a80227905a207e6d16ac4a8493b6c4ef740a473261912d07e326b8e117309cdd15deb54af79bf9e5ebdc89fa05791822e8afc70d6f4d9b172d4b58a6837420293d00053f5c5f1fddd8e3295368b4d31aacc3d6f5878f54871dfcddc307cee9f770a19af749c0aca24cda44b1f5887913a0b5ad9a640d2ef1d40da8e519aaee8d4e3581135d2ed9595d2997d169224ce6f894e7d12d918729f6fdd07b52aea80edeacc08f1935050316687196b677ee09392d893512f6f71ea38e2b96e8fa30cf0b903a14c4c181acecbfc78e4f0e889395bd14c617dea76d64ac56d80ecfc928fb1a5fe903ec033f535bd83c35baa7844f3e6c4f52559efab408676a0cddae77ca3d67f441c5f64fa59fc964239a5929d5b3feaaf4869bde064c439b3000edf4f110f9e4ff21f8a372b795f2173542777166b38b63f4905a695e6c586004929dc3210c98f65767fb7fd8f4dfc7840896c6ead14c071e75d9cae236136ed8d9b0cf0b2920a092e790f7dec12d47aaa821e939352dddef6b1899dd191292240b7455249ddef288d97fa7b54f0b97b25cc6bb7a7b70d03c4cb4d5cb8abc69159971b9ef2b839118e375dc0045154f77a1f626e4c1a034d58d3b6b89c1cf8ece66bcd8789b147d4aa89d734a232c0fb4bd167b4a029ed900369ffad273c7de969e178acf7b98349959245f3b2df1b7f5fe7e1288db0570fcabfb23ec8691e2de490be17ce5393c9d333f675e7513d0a8a0954728684b67c5924b915cd70f04bce999e08fc12e9e610f1a1b44aa7797b4aa228cb1fff8d7bb3cfecf701f7f3305fbe126c88a04c36931d70c6484de2dd2a19eba650b6e25af88fb62afd1f3e183931166a6294c7a2748a2e1e97f1b5d522e3b7c643752f467394fddcdc04cdec07d1fd2ba8fcfb7ce3ca53c58cf11b161e6e0ac86d0df2d95d9a87f99733e250ce962fa70969c0e865a0977a4131a153a9a06d0ad76fba172b7e9a157525343ef3be1c021f05b6f5ec58822a9fe0bbef9d80ee4c2ccc1a548e3c092f644b0a82845f54cf0ee434267f5670422e5370f5113563ef7a8253324dedba6f75bf015c6cd38788ac76c6dbc6fdb9a7e5bae2bf8e5912db36f71632820b20dad4d3c49555b22b51c4ab340b968742cca10bad7b57013b043cda63c52a039a6f1ed831913ebec617cacb0c37049aeed66845cdef44f5611fa9d4ddc0dba44e245d7e52c8c9146f6fa3afdefcb356d2034d07100e210406bc984fae325a7f5c202cc739130a2d706f24a23b8bc0a6040d3bdeae2e5834b20f41d920b224084f439fa13056739bd2efb884abf945d1a005af17d9aebec043a7367cf9ca9df1dec02a41cbade058cf1d36d16f6c2477a141937ae36ccc4e87560b4e2f323d6b373434168055</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-default">      <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-default">欢迎您访问我的博客，请输入密码查看本文.</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">【加密文章】本文主要整理 UK 代码自动检测流水线搭建流程方法。</summary>
    
    
    
    <category term="持续集成" scheme="https://www.wshunli.com/categories/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/"/>
    
    
    <category term="Java" scheme="https://www.wshunli.com/tags/Java/"/>
    
    <category term="Jenkins" scheme="https://www.wshunli.com/tags/Jenkins/"/>
    
  </entry>
  
  <entry>
    <title>Jenkins与Atlas中间层系统对接流程方法</title>
    <link href="https://www.wshunli.com/posts/c74848c1.html"/>
    <id>https://www.wshunli.com/posts/c74848c1.html</id>
    <published>2018-08-11T01:59:05.000Z</published>
    <updated>2026-06-17T23:42:59.824Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="Oh, this is an invalid password. Check and try again, please." data-whm="OOPS, these decrypted content may changed, but you can still have a look.">  <script id="hbeData" type="hbeData" data-hmacdigest="a094b23a1e29f4bef5ce63d918b52ade5a713cb4df2c5f38e050c132bcd0e328">2f1f22b262c5c5f48f1931f0a262bffc5efb0785a6077000bac24df2c1c1b12ee922f5ca5ccc2c491b07646ec4ca696797f25b2d9021f75498340a50e73b9b37b3b191cb35011a7390aa110945b1619759fc71f55cca02d7f0d7f115397ff54fe04749cac0073ac681319915d6ab8615f1f5fe99eadcf733600ead541bfff8d62b6b103e98b324a2a81b2af02a33eeec1a275beaf6e007c5646633cbd36e0d29dd1fa0d5f5fa5c0e4803f7873e5d08dc024ef35c97a6d84d725b2d6cbb5350f127e0379d9348ded9939b5b9377544eac276625a5304d19bd3ee44c4428787bf8b699628c0c31cf5d82adb240abb88e7f37a46c3f27f43ae08c207bae229f4805717805de0e32af6b550b9376b60979891ac31bdf60cd230dfa931c75e915a2a5e4d66195b64334433b0ae1498c29a3dfc9cdf4d2b3245210bb5e4110afef775ed2b742db92932b0fc1209e82062b00d75666126d1f3df643bbc4beb979a75563a65c52f49d641c56a7013943f5da47dbcef21e42e698dc5f827545b10e3719f122bbd43571a7ad27489d6d861e04178710ac56cb8f25479f306004c2eb0c6757a713380ef6ee583ff77ff813c8a7df06bd8e49cec326765bca2f86f8fcc5e8fdb5c288f4cbdc34eb7de667d498f4de602cbf4b4bb2a6d6be7fe5bd8f2e5d989a0c086833e37bb1b58153f9394efb8e0e6dde251df5246476ad76b246ec86d6bab119aa0b4b91ce0ecbad880a64fc13a8dc89a9e1e1d3eccdd3761e002e69880b5f1d639b9a7ed02cb617e96eb16dabeb48c2caa967e3ba0482de05c4b257f013bd531445531955ff07035b71c02ff20addc0e337c996e1f4fb22f0686c39a0e83cb394f9d9c97d778acdb9b0952a7f2ef0c45e8f50a45dc587d04a9e57f93fe599648367b9b90a4aa31a9ad0c4e75e966a4b8ed69ac2f449880e2c56462c89e61128e0f6bc151ad0cf7c485d9ccf80576dcf4677699ec5595f9238b5b809d579ae09dbc2c7d7d83b22fc5068d4daf9bc2e43867a655164264ddf1844a34e54f12e4ac4b07fafa06648dd34c8c46270394b6158cc00243c8a6136f70de9e75bd62d777e6bd5b6d4b02e265c9e7ff35d425d72f9ef94c671957cb45aea32a2c87ede51c7c77301f48d305daef038556b0a62c23ca2b1d515490336be6674521361fdf3c4755f395b5bee571e1600d343daf2aaac3aebcc49851b2b89720b44d795836529818a7ba23adec68629465abb4171b1f0bb1171cad77565bf176c57b7e0f09f0bdd9ff2b2f7524f4c85b0050b7e87916d46b43b91320141bfda30b5a42213e766744aa26993eec02beabc92130b2ee2702b2c1326223597132af85e9829ac440f0a58b5662b32cfcd1e99c92350cf9bd39e94b558e42b50e20a6bc3e4ed6ea8a49af296f2349361e29fe3ddd6159ab5b82cf285c0e196bd58d149786fb96732ae211fe5dd27d7c1817c11b60d260ee2a5888a0791ef69c365adf2d9a2067487cc9db89f49ef5c0094627ec97ea93e46c67052ab7e9fad8f3da87597109cc2497afb447fdb618767e388f3fb54dd9dbb99addab7efcbd21060e57cdc8a1af07fba2038180e7c9c6541d88617bdccc490c7565c78c529784384431828f07da78dcdecd61c504147df339eb12df39e6523f179fe74ead308c8c3e8afaa7176ff781bd8e06ac3a2a5dd06ef63a25db7b14cd367c6c450654e85ee2f9a553b7f28305c06695c9bb8d5d93f88f2f5573a5673ff146d7b6f648fc55f0094dba71f0e43b138fd47a4febe91d789ddec89c7e8f7ea86b43439e3b7f6e193b52cb1a3de6488d680917d560b0317d7b08ce2a469196a01dab796b7ae378482d7e9ff21b54f6a911b8e8c4a7f3b45baf253710135c81629392300418054bd3933944c36028ceff418c5d3f24575fcec235d7e3e69b7932cb362236243f8b2e7740ada4640693c733b4ee621ac0acfefd14e2891364bb9c07d3032883f28a24d5203b537e0326a539c568b517052833e7a738536c730737dcbaa2c8f4a2c2ca43ab533d015ec4737c40b4a32ac64049d5012458e10c348ed44b240c3657ee17962197fd454211e056265ceef807e3f37a2595ff8c7e9bfa3d99acdaccc603a64b0ef58e4155702fc0796b74c18f384608422c7a3ebb5f85f008ad66407f3b2f3a174ea574e9b5268399e3dee8aae26d67c82fba42448e93ef9e84ed6f8b100aff51d10927d4f49c1c699cdd73c1081d865511fa72486f79ce963a6587311bff166b53c195e0dfd51d2aad8622e57a88149a7fe7255ec9f86995e6c652fc45fc042df40141893e061fbbd8a7cd7153291fefe9cbac3029ddaaa99fab8ad88a37fdfdb8b3a3af74dfbf260c255b3fd9aae2c0024d47ec473948a6fcec843cec8cc4915e39cba8e3170eb5dc6871dee628a564d80eba7315173af80d369187e40e079bd42899c12c81ec05d27f0c2740809c026723eea60b5ff45e7e86c339b673d482570286b34a6b68fa9f105beb90b196824f9f2b224eaf67db9b36cbd634a3906abfe6e5cb70e7620a92156d947d518690190fcd723632485fce30e19f8b7706ed34ca83296095099a19331b78ddf57df34f8bad4d1a3f839130871987f6de68899a616101a39c94d69ca342e934b67d5a34eae35bb7e20c93d11e285b01fed6a3f414c9d2c162eea34746cc225ce6c9e667d9153d95c1be0dee5b39b2dbeb93bef1a46b0b5a0470d2821cc1de8e340ecb1be08fc43b3f828beb098fec755a96ab6f1d218d472162ed971f5993b4c8b78f1bc3b4a735e249a04747f2ddcb870798a6a2f24451af6ebccc5088044be484e85250c46fb8dd1fe823aee70fe714c7d2196d5033683060011080d6bcbd58f9da95e5fb61bfe4267df4dc5d54f58ea17deeaa67eda34566b7e2bd5e26ff31ccb2a68b2d3499ff8a277a97c9384d85f6d4072639f82d95951a4241561d76de9431a348cdc095bca8d05c264bb02f9ab4aa2e6e867f3f54c420ac505f9d2344416bf6bdb8f0ad93880f7fd8ac8b158df964840cf5861e4efa52f80babf0cde64215fd2cb87ba0649c2b292db344901fd5667ccd29d293278c1ffda2c77ef1354472e1038403f069380f048a3f7be64b894cc0bbf9bd2dae7ebc0fe326b3fc96929abd98eae7c8b64cd27e1df89164840ccfec76811b0b1950938e51d53d985b31c09467e68fda0044d346fcb589039d4ab45c8f550ef95ac82e054cc09a029041ff8289808027814e67164266fe7587f27917ad9fd306eb5524befb31872e3f5cabc5e509ef4be2a64298cfd419713f608b4cf387bb23ab6ac8a1d777c1800e777348b6c237f9881f0dd38030d190a0c5ccbe20974c79a84d17c4cafdeb87d5a903a542b2c99aea2d372798fdc628493ad9330574cfa44ec0567fbbdb78122fd92259c5e48ca8ccf4a5bc34b5198b451df1342e1496665375395a6954bddf46ca7fe0dd6e66ad34a03ecdb7e73a3f61c77c81492c09bf4f9d2e8bf83be59a67566edc3a1da963653e88de5fdab897d8874de672ad4ddb0dc60cca2103773f190c9c3daf7895ea47b240fcd247d79b793395f6bdb49c4a102414ca3e46e1b2cbc259a8cbc3bfdbd2e451917ab6f5142b79b0b71ac74a341800ef8411c43ab1bf4f7358a80eb57c3ddc82d20b03cbec79ea3934818b143a4177d2b2448a9231f5a01acc8821e0bfbfc2bbdb59963e7b9d35448261f6ad9f71fb6cff8f30c899b053e7f9c02b233235ea69a3c84900296bbffb96b32de30444c7ae29ed21e47228e27a547487061e041a1d29031d3006641aa1ef5c075df9e6418569f81b59b4f59d7d94e35ac3427a1d95b083ed3bfbde49ee2cbf2a56a53a0f0f09b0322e31ecda5dd0bc7c0fadc005aa62090d130c8b445a042cc8b05ab986c92bfb4ce246c0bcef1dab0cc913a9ef8ae85ad514c4f47c3d8325d5ae7a5d1fafd416373b1cfe974b4e50d64f22d320088a91d6911464eb9aa52dc677896bc7c967eeeea2e2d34d1e7c4fd2a0288b0f80242a894433c81952475667412a5e2952f08dc9fb9638c7ae4754d9b6da9c3e5ae39dc65d1208a7387e75cf08ef617c38b9a8709f5c380f916b2c0c628d4b38a68d69b86a7c981dd243bb0da249d121a4de5a2def98f5976b3be9777de7100ce9bf815fd5ad2a593374353ee19a6479b77438c440d8cc0249058d35a7c190d5f2a64ca3d7300ec4dbe09e9259e05fe126de5ba19c89d38f3566a1150ce2dcf7ada29aa390d4deba97c29ac0093173823ac3b9b7c8585b11ac7f740b586ffddacf06f7bc42797ecf6bd366a01ed27453b96593e0ebffa4605f9f59566542dc5f8d9d0901def8dda263bdac2a96ebce4eca776de085cb7c43b483ddb6801639d8d82f3a6dad107a890cac417ff0f42286b1aa5cb02596012f0a9a1f5ae42e0f6e0bc550c9b178a0adda0dec2c55846b423baac73808de3f1cf1d1820229fde7b05b767865a2c9b3dba502a487eef9b205b1c800f8e6d2ebee24dd3ebbc0e956c7329df0dfa0cfc5b67ff8f335a689ca56673b3fb28b1f1bc019cd364ad57b0ed897c414f4591393a2c10db674856a416074f208a4e5021d75806613ba02e8326c4c291981f0cdeca377bdc66d0594f249092cc890ce10e68376f128eeb005d8652918a6cab92455365a128d46cd5b8f1c27accb3cdadc8528b5f04d25c4c116bbf4d46a608e22773e80bb432065c1846a9223cda2d48f2ed6d2b56721e38d230c5f5fabce044c803232a09fd996b8bf132fca439be43fd06b7d788f3793d6bcaaf30f858aba6ce100f15987a3ce92a64a14704879d9af00b97c4144111475df70142aa11d2304dae0296c19329b88d342cdf864a62c6f5594e3649aa0ab032d453ff2e61076a8e29ea512c06305323050e22f3590fb71517445b737182e76adfe2e6e05286008769f13c31fd4f8086c25e1aaf73bfd159f868cc31c73a90d8c87a2b8d3e5bc9331b634cc40ead2dcde5e942221f522715dc864257b5dec24cdb9632da6388a460dfa38ac2af313c8502955744af82b2f64c0e4bd52dddc4a5de8a8b24833ff1151efdf4951e060a571bc3eed783e32bff59e5b8f56d1982de0344adfd6883d85ea7c14429bd8e615424ef6fd68eb9a869b91bfe9b38023257b3f9561caf9df762d631e7ae2cc48a5d26c7ba7be2b5bc71a75bc52503dd9b72478b1a3b4edc9c8b80129d1691006eb00165d7e157ca4d17ee1301ca102ece0d068ca2310dab4a8658d96b7ed3182e90bb45476c1d160efa4f824c3acac976bebccce2f8cbefa51d3ea15f672a46806c83057df891a129373c13ec19fb19e7e1f5e7b610f8806b284d2a9aff907a5b608424ce181aae505b59b58f967700d9047089115cc77d834c246183bba709f4982a10ef77bbaf33ebfdfefd3321a3b14fee849a28fce751fabb375bee166d063ac2cef1670d1d4482f87699021b8337d7f88738621600675ec2e39f4134c9a72076924c882db1dcd6e56df92ab2f2ba5a5e1f1fae05ffa24e7a0b370a5f80b20d9906d07b892d7853604de1892e494b106c240f47ca41adc59aeeea0f41d58a8d7845688f380033f173330aef716f27c59a23c1660e57ff2ee832600575a7c04149944b3d18cc569ceefff33b9b55a3c30967f4b02fe66388420c516f2297511ead8b45fa718f179cd50e8f1b82c068f60c378050a9a0872471edbd6829263aed207f4df0006fcad7dbc7d84b5fe423e638f7829eb910d7fa5ee6b948e432b6d061f7dcf432737611e82c78c6fd9d681d891432de9bbcf30db73090e8dc1466415f5a1d96c7f747f7f1fa3b5dec6cc3d55eabc764456a4a765dd6d22171e3003599973a5a817a6fc8be729b1321d3156b6acfc9410750a044f61b4a6a9f7c7be14d4c9968cef00968e55b433738b21fea285ac72791212a5ad91b25ed5bd09030291bb4eeda615d8a95d462da2e43df75073e1452b3cc4b5b0253b115ed4f37265bbffbf3425dcd595fe07e68449d4be181298b6cf86ce305cc3bad192c4514ecb080a365b6e4e7ede82bc9d43a142fde11bb0fd6bdbe6ea52a69a3d051f46dfa88a157d5d359bf662a20bcaa3b85bba4736ffd9efe9c48ee8e18bc14174b4b7b9846d4501ea855fb52ca4080dfc942234630d5542c5f8d6bac7cc3f6ac7cd43b945ce848b93a66dec59fa9d2de25d9c2edaeb62983d9ea4d74b84f42d9558f32a96f5666fc14168593697ff170e63e2b6fde4ee093b1e71df6579d8514a0b3586b9e1de4054737eb51f0a1a64974ad486769038c58f03eee0d82435d91dd51f9ef6f2168fb76508b6aed1fe99411deff4cdafcdf9fa8992bd607843e3eac7a50027847175350a2ea7df95125978293949c1cd123e189f8d9df097d880bf93d41aa4488a262ab2d3e1c42ef17e5cbab98094abec69aef01a4fb634abdc770028dd63594db93348ae4d13f30084e814d3e2f7a26d8453222d217351e412f500d68310c900c43d3b8a51cdf28de9b2ead1894fd935893daeed8ae251977dd948ae9e292839c906e843716a8fd7ff683ea58f260926edfae581bb66a2c6b9e820e7a3e0fe90ec5d11aa55bac07cb0f83c6da10e54cd1a4a633dbbb6aa7142c27b7ea84fe78bbf674604ac488f2941c3451bda4768b6c8463bba83f133b6c2a6dc94595149162a29a55f0930791a3b20b4fd5849bdeecb546c63b788fd2df6f993b27217ce164bc11ac42ef39ea63c9684170b8398f144092a5406de0b0ca32</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-default">      <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-default">欢迎您访问我的博客，请输入密码查看本文.</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">【加密文章】本文主要整理 Jenkins 与 Atlas 对接流程方法。</summary>
    
    
    
    <category term="持续集成" scheme="https://www.wshunli.com/categories/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/"/>
    
    
    <category term="Java" scheme="https://www.wshunli.com/tags/Java/"/>
    
    <category term="Jenkins" scheme="https://www.wshunli.com/tags/Jenkins/"/>
    
  </entry>
  
  <entry>
    <title>Jenkins安装Java代码质量分析工具</title>
    <link href="https://www.wshunli.com/posts/57f40b04.html"/>
    <id>https://www.wshunli.com/posts/57f40b04.html</id>
    <published>2018-07-25T01:15:32.000Z</published>
    <updated>2026-06-17T23:42:59.824Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍 Checkstyle、PMD、FindBugs 三款主流的 Java 静态分析工具，以及 SourceMonitor 代码度量工具，Simian 代码重复检查工具等。</p><span id="more"></span><h1 id="Jenkins-安装-Java-代码质量分析工具"><a href="#Jenkins-安装-Java-代码质量分析工具" class="headerlink" title="Jenkins 安装 Java 代码质量分析工具"></a>Jenkins 安装 Java 代码质量分析工具</h1><p>在 Java 世界中，Checkstyle、PMD、FindBugs 插件是三款主流的静态分析工具。</p><p>1、Checkstyle 擅长检查编码标准和约定，编码行为以及其他的一些质量指标（如代码复杂度）。</p><p>Checkstyle（<a href="https://checkstyle.sourceforge.net/">https://checkstyle.sourceforge.net/</a> ） 是 SourceForge 下的一个项目，提供了一个帮助  JAVA 开发人员遵守某些编码规范的工具。</p><p>CheckStyle 检验的主要内容包括：Javadoc 注释、命名约定、标题、Import 语句、体积大小、空白、修饰符、块、代码问题、类设计和混合检查（包括一些有用的比如非必须的 System.out 和 printstackTrace）。</p><p>在 Jenkins 中安装 CheckStyle 插件并重启。</p><p>在项目中配置 <code>pom.xml</code> （可选）</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-checkstyle-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.16<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">configLocation</span>&gt;</span>checkstyle.xml<span class="tag">&lt;/<span class="name">configLocation</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    ....</span><br><span class="line"><span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在 Maven 构建中添加 <code>checkstyle:checkstyle</code> 参数。</p><p>构建完成后输出 XML 分析结果，文件在 <code>target</code> 目录下。</p><p>关于插件使用的更多信息可参考：<br><a href="https://wiki.jenkins.io/display/JENKINS/Checkstyle+Plugin">https://wiki.jenkins.io/display/JENKINS/Checkstyle+Plugin</a></p><p>2、PMD 类似于 Checkstyle ，它更加专注于编码和设计实践。</p><p>PMD（<a href="https://pmd.github.io/">https://pmd.github.io/</a> ） An extensible cross-language static code analyzer.</p><p>专注于潜在的编码问题，比如未使用或者次优化的代码，代码大小和复杂性，以及良好的编码行为。</p><p>PMD 也附带了 CPD ，以支持探测重复或者近似重复代码。</p><p>在 Jenkins 中安装 PMD 插件并重启。</p><p>在项目中配置 <code>pom.xml</code> （可选）</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-pmd-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.5<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span>        </span><br><span class="line">    ....</span><br><span class="line"><span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在 Maven 构建中添加 <code>pmd:pmd</code> 参数。</p><p>构建完成后输出 XML 分析结果，文件在 <code>target</code> 目录下。</p><p>关于插件使用的更多信息可参考：<br><a href="https://wiki.jenkins.io/display/JENKINS/PMD+Plugin">https://wiki.jenkins.io/display/JENKINS/PMD+Plugin</a></p><p>3、FindBugs 专注于识别潜在的危险和错误的代码。</p><p>FindBugs（<a href="https://findbugs.sourceforge.net/">https://findbugs.sourceforge.net/</a> ） 检查应用程序的字节码来找出潜在的 bug 、性能问题或者差的编码行为。</p><p>在 Jenkins 中安装 FindBugs 插件并重启。</p><p>在项目中配置 <code>pom.xml</code> （可选）</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.codehaus.mojo<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>findbugs-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.5.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">findbugsXmlOutput</span>&gt;</span>true<span class="tag">&lt;/<span class="name">findbugsXmlOutput</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">findbugsXmlWithMessages</span>&gt;</span>true<span class="tag">&lt;/<span class="name">findbugsXmlWithMessages</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">xmlOutput</span>&gt;</span>true<span class="tag">&lt;/<span class="name">xmlOutput</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    ....</span><br><span class="line"><span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在 Maven 构建中添加 <code>findbugs:findbugs</code> 参数。</p><p>构建完成后输出 XML 分析结果，文件在 <code>target</code> 目录下。</p><p>关于插件使用的更多信息可参考：<br><a href="https://wiki.jenkins.io/display/JENKINS/FindBugs+Plugin">https://wiki.jenkins.io/display/JENKINS/FindBugs+Plugin</a></p><p>4、Static Analysis Collector 插件汇总静态分析结果</p><p>插件地址：<a href="https://wiki.jenkins.io/display/JENKINS/Analysis+Collector+Plugin">https://wiki.jenkins.io/display/JENKINS/Analysis+Collector+Plugin</a></p><h1 id="Jenkins-安装-SourceMonitor-代码度量工具"><a href="#Jenkins-安装-SourceMonitor-代码度量工具" class="headerlink" title="Jenkins 安装 SourceMonitor 代码度量工具"></a>Jenkins 安装 SourceMonitor 代码度量工具</h1><p>SourceMonitor（<a href="https://www.campwoodsw.com/sourcemonitor.html">https://www.campwoodsw.com/sourcemonitor.html</a> ） 允许查看软件源代码内部，以了解项目拥有的代码量，并确定模块的相对复杂度。</p><p>在 Jenkins 中也有 SourceMonitor（<a href="https://github.com/jenkinsci/sourcemonitor-plugin">https://github.com/jenkinsci/sourcemonitor-plugin</a> ）插件，但是好久没更新了，也存在一些问题（<a href="https://issues.jenkins-ci.org/browse/JENKINS-5741">https://issues.jenkins-ci.org/browse/JENKINS-5741</a> ）。</p><p>报错如下：</p><figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">Parsing sourcemonitor results</span><br><span class="line">hudson.AbortException: Parsing file error</span><br><span class="line">at com.thalesgroup.hudson.plugins.sourcemonitor.SourceMonitorParser.invoke(SourceMonitorParser.java:70)</span><br><span class="line">at com.thalesgroup.hudson.plugins.sourcemonitor.SourceMonitorParser.invoke(SourceMonitorParser.java:45)</span><br><span class="line">at hudson.FilePath.act(FilePath.java:1047)</span><br><span class="line">at hudson.FilePath.act(FilePath.java:1025)</span><br><span class="line">at com.thalesgroup.hudson.plugins.sourcemonitor.SourceMonitorPublisher.perform(SourceMonitorPublisher.java:80)</span><br><span class="line">at hudson.tasks.BuildStepMonitor$3.perform(BuildStepMonitor.java:45)</span><br><span class="line">at hudson.model.AbstractBuild$AbstractBuildExecution.perform(AbstractBuild.java:744)</span><br><span class="line">at hudson.model.AbstractBuild$AbstractBuildExecution.performAllBuildSteps(AbstractBuild.java:690)</span><br><span class="line">at hudson.model.Build$BuildExecution.post2(Build.java:186)</span><br><span class="line">at hudson.model.AbstractBuild$AbstractBuildExecution.post(AbstractBuild.java:635)</span><br><span class="line">at hudson.model.Run.execute(Run.java:1819)</span><br><span class="line">at hudson.model.FreeStyleBuild.run(FreeStyleBuild.java:43)</span><br><span class="line">at hudson.model.ResourceController.execute(ResourceController.java:97)</span><br><span class="line">at hudson.model.Executor.run(Executor.java:429)</span><br><span class="line">Build step &#x27;Publish SourceMonitor results&#x27; changed build result to FAILURE</span><br><span class="line">Build step &#x27;Publish SourceMonitor results&#x27; marked build as failure</span><br><span class="line">Finished: FAILURE</span><br></pre></td></tr></table></figure><p>原因是在构建的时候需要添加 <code>sourcemonitor:sourcemonitor</code> 参数。</p><p>SourceMonitor 插件地址：<a href="https://plugins.jenkins.io/sourcemonitor">https://plugins.jenkins.io/sourcemonitor</a></p><p>这里可以安装公司内部的 <code>hwSourceMonitor.hpi</code> 插件</p><p>参考资料：<br>1、<a href="https://3ms.wshunli.com/km/blogs/details/2503631">https://3ms.wshunli.com/km/blogs/details/2503631</a><br>2、<a href="https://3ms.wshunli.com/hi/group/2033815/wiki_4496373.html">https://3ms.wshunli.com/hi/group/2033815/wiki_4496373.html</a></p><p><del>也可以使用 HTML Publisher 插件解析 SourceMonitor 输出的 xml 结果。</del></p><p>0、准备 SourceMonitor 并安装 Jenkins Server 上。</p><p>本文安装在 <code>C:\Program Files (x86)\SourceMonitor\SourceMonitor.exe</code> 目录。</p><p>1、安装 HTML Publisher 插件。</p><p>HTML Publisher Plugin 用来把 SourceMonitor 检测的结果可视化。</p><p>2、在构建后添加 Windows 批处理命令。</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">&quot;C:\Program Files (x86)\SourceMonitor\SourceMonitor.exe&quot; /C &quot;C:\CI_Tools\SourceMonitorCommand.xml&quot;</span><br><span class="line">&quot;C:\CI_Tools\msxsl.exe&quot; SourceMonitorReport.xml &quot;C:\CI_Tools\SourceMonitorSummaryGeneration.xsl&quot; -o SourceMonitorSummaryGeneration.xml</span><br><span class="line">&quot;C:\CI_Tools\msxsl.exe&quot; SourceMonitorSummaryGeneration.xml &quot;C:\CI_Tools\SourceMonitor.xsl&quot; -o SourceMonitorResult.html</span><br></pre></td></tr></table></figure><p>其中 <code>SourceMonitorCommand.xml</code> 来自 </p><p><code>C:\Program Files (x86)\SourceMonitor\Samples\sample_commands.xml</code></p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;UTF-8&quot;</span> standalone=<span class="string">&quot;yes&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">sourcemonitor_commands</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">write_log</span>&gt;</span>true<span class="tag">&lt;/<span class="name">write_log</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">command</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">project_file</span>&gt;</span>C:\Windows\System32\config\systemprofile\.jenkins\workspace\findbugs-demo\target/sourcemonitor\project.smp<span class="tag">&lt;/<span class="name">project_file</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">project_language</span>&gt;</span>Java<span class="tag">&lt;/<span class="name">project_language</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">source_directory</span>&gt;</span>C:\Windows\System32\config\systemprofile\.jenkins\workspace\findbugs-demo\src\main\java<span class="tag">&lt;/<span class="name">source_directory</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">parse_utf8_files</span>&gt;</span>true<span class="tag">&lt;/<span class="name">parse_utf8_files</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">file_extensions</span>&gt;</span>*.java<span class="tag">&lt;/<span class="name">file_extensions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">include_subdirectories</span>&gt;</span>true<span class="tag">&lt;/<span class="name">include_subdirectories</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">export</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">export_file</span>&gt;</span>C:\Windows\System32\config\systemprofile\.jenkins\workspace\findbugs-demo\target/sourcemonitor\sourcemonitor.xml<span class="tag">&lt;/<span class="name">export_file</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">export_type</span>&gt;</span>2 (project details as XML)<span class="tag">&lt;/<span class="name">export_type</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">export_option</span>&gt;</span>Include method metrics: option 3<span class="tag">&lt;/<span class="name">export_option</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">export</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">command</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">sourcemonitor_commands</span>&gt;</span></span><br></pre></td></tr></table></figure><p>需要下载 <code>msxsl</code> 工具（<a href="https://www.microsoft.com/en-us/download/details.aspx?id=21714">https://www.microsoft.com/en-us/download/details.aspx?id=21714</a> ）并放置到 <code>C:\CI_Tools</code> 目录下。</p><p>3、最后添加  Publish HTML reports 即可。</p><p>不过我没有配置成功，总是提示找不到 <code>SourceMonitorReport.xml</code> 文件。</p><p>如果使用 SourceMonitor 插件遇到如下 OutOfMemoryError 错误。</p><figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">FATAL: Java heap space</span><br><span class="line">java.lang.OutOfMemoryError: Java heap space</span><br></pre></td></tr></table></figure><p>解决办法：<a href="https://wiki.jenkins.io/display/JENKINS/Builds+failing+with+OutOfMemoryErrors">https://wiki.jenkins.io/display/JENKINS/Builds+failing+with+OutOfMemoryErrors</a></p><h1 id="Jenkins-安装-Simian-代码重复检查工具"><a href="#Jenkins-安装-Simian-代码重复检查工具" class="headerlink" title="Jenkins 安装 Simian 代码重复检查工具"></a>Jenkins 安装 Simian 代码重复检查工具</h1><p>Simian（<a href="https://www.harukizaemon.com/simian/">https://www.harukizaemon.com/simian/</a> ） 是一个检查重复代码的工具。</p><p>这里可以使用公司内部的插件</p><p><a href="https://3ms.wshunli.com/hi/group/2964/wiki_4145693.html">https://3ms.wshunli.com/hi/group/2964/wiki_4145693.html</a></p><h1 id="Jenkins-安装-Cobertura-代码测试覆盖率工具"><a href="#Jenkins-安装-Cobertura-代码测试覆盖率工具" class="headerlink" title="Jenkins 安装 Cobertura 代码测试覆盖率工具"></a>Jenkins 安装 Cobertura 代码测试覆盖率工具</h1><p>Cobertura（<a href="https://sourceforge.net/projects/cobertura/">https://sourceforge.net/projects/cobertura/</a> ） 一项衡量是否所有代码都被测到的工具。</p><p>在 Jenkins 中安装 Cobertura 插件并重启。</p><p>在项目中配置 <code>pom.xml</code> （可选）</p><figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.codehaus.mojo<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>cobertura-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.6<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">instrumentation</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">ignoreTrivial</span>&gt;</span>false<span class="tag">&lt;/<span class="name">ignoreTrivial</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">instrumentation</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">formats</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">format</span>&gt;</span>html<span class="tag">&lt;/<span class="name">format</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">format</span>&gt;</span>xml<span class="tag">&lt;/<span class="name">format</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">formats</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">    ....</span><br><span class="line"><span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在 Maven 构建中添加 <code>cobertura:cobertura</code> 参数。</p><p><code>compile -D cobertura.report.format=xml clean compile cobertura:cobertura</code></p><p>这里注意添加 <code>-D cobertura.report.format=xml</code> 参数。</p><p>构建完成后输出 XML 分析结果，文件在 <code>target</code> 目录下。</p><p>关于插件使用的更多信息可参考：<br><a href="https://wiki.jenkins.io/display/JENKINS/Cobertura+Plugin">https://wiki.jenkins.io/display/JENKINS/Cobertura+Plugin</a></p><blockquote><p>参考资料<br>1、jenkins+maven配置Checkstyle+FindBugs+PMD - 简书<br><a href="https://www.jianshu.com/p/03b9e38d03b2">https://www.jianshu.com/p/03b9e38d03b2</a><br>2、CheckStyle提高代码质量 - CSDN博客<br><a href="https://blog.csdn.net/lx_yoyo/article/details/73332590">https://blog.csdn.net/lx_yoyo/article/details/73332590</a><br>3、Jenkins+maven+checkstyle对java代码进行静态代码分析 - CSDN博客<br><a href="https://blog.csdn.net/hwhua1986/article/details/48339545">https://blog.csdn.net/hwhua1986/article/details/48339545</a><br>4、Jenkins+maven+pmd对java代码进行静态代码分析 - CSDN博客<br><a href="https://blog.csdn.net/hwhua1986/article/details/48342745">https://blog.csdn.net/hwhua1986/article/details/48342745</a><br>5、[Jenkins]持续集成环境下fingbug插件的安装使用与配置 - Amberly - 博客园<br><a href="https://www.cnblogs.com/amberly/p/7201041.html">https://www.cnblogs.com/amberly/p/7201041.html</a><br>6、静态检查——SourceMonitor的学习和使用 - CSDN博客<br><a href="https://blog.csdn.net/yf210yf/article/details/17535713">https://blog.csdn.net/yf210yf/article/details/17535713</a><br>7、[料理佳餚] Jenkins 增加 SourceMonitor Plugin | 軟體主廚的程式料理廚房 - 點部落<br><a href="https://dotblogs.com.tw/supershowwei/2015/10/14/153562">https://dotblogs.com.tw/supershowwei/2015/10/14/153562</a><br>8、CI Server 16 - 整合程式碼複雜度及深度報表 (Source Monitor) - iT 邦幫忙<br><a href="https://ithelp.ithome.com.tw/articles/10107051">https://ithelp.ithome.com.tw/articles/10107051</a><br>8、重复代码检查工具simian的基本用法 | 知行一<br><a href="https://purecpp.org/?p=92">https://purecpp.org/?p=92</a><br>9、Jenkins集成Simian插件_百度经验<br><a href="https://jingyan.baidu.com/article/c45ad29ccbfd3a051653e272.html">https://jingyan.baidu.com/article/c45ad29ccbfd3a051653e272.html</a><br>10、[料理佳餚] Jenkins 增加 Simian Plugin | 軟體主廚的程式料理廚房 - 點部落<br><a href="https://dotblogs.com.tw/supershowwei/2015/10/14/153561">https://dotblogs.com.tw/supershowwei/2015/10/14/153561</a><br>11、代码测试覆盖率Cobertura使用 - CSDN博客<br><a href="https://blog.csdn.net/cathy_sunshine/article/details/75258298">https://blog.csdn.net/cathy_sunshine/article/details/75258298</a><br>12、jenkins集成cobertura，调用显示cobertura的report - CSDN博客<br><a href="https://blog.csdn.net/yaominhua/article/details/40684647">https://blog.csdn.net/yaominhua/article/details/40684647</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍 Checkstyle、PMD、FindBugs 三款主流的 Java 静态分析工具，以及 SourceMonitor 代码度量工具，Simian 代码重复检查工具等。&lt;/p&gt;</summary>
    
    
    
    <category term="持续集成" scheme="https://www.wshunli.com/categories/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/"/>
    
    
    <category term="Java" scheme="https://www.wshunli.com/tags/Java/"/>
    
    <category term="Jenkins" scheme="https://www.wshunli.com/tags/Jenkins/"/>
    
  </entry>
  
</feed>
