<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on "地瓜哥"博客网</title><link>https://www.diguage.com/post/</link><description>Recent content in Posts on "地瓜哥"博客网</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Mon, 08 Sep 2025 22:04:47 +0800</lastBuildDate><atom:link href="https://www.diguage.com/post/index.xml" rel="self" type="application/rss+xml"/><item><title>Spring Boot 启动流程概述</title><link>https://www.diguage.com/post/spring-boot-startup-process-overview/</link><pubDate>Tue, 08 Jul 2025 09:23:05 +0800</pubDate><guid>https://www.diguage.com/post/spring-boot-startup-process-overview/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/spring-startup-process-overview/" target="_blank" rel="noopener">Spring 启动流程概述&lt;/a> 中，D瓜哥简要介绍了一下 Spring 的启动流程概述。前段时间查看了一下 Spring Boot 的启动流程，画了个时序图，分享一下。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/spring-boot/SpringApplication-run.svg" alt="Spring Boot 启动流程" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. Spring Boot 启动流程&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>结合这张时序图，再加上 &lt;a href="https://www.diguage.com/post/spring-startup-process-overview/" target="_blank" rel="noopener">Spring 启动流程概述&lt;/a> 中的 Spring 启动时序图，就可以看到 Spring Boot 和 Spring 启动的全貌了。&lt;/p>
&lt;/div></description></item><item><title>Redis 核心数据结构（四）</title><link>https://www.diguage.com/post/redis-core-data-structure-4/</link><pubDate>Tue, 17 Jun 2025 16:36:56 +0800</pubDate><guid>https://www.diguage.com/post/redis-core-data-structure-4/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/redis-core-data-structure-3/" target="_blank" rel="noopener">Redis 核心数据结构（三）&lt;/a> 中，重点介绍了一下 Redis 7+ 使用的底层的数据结构 listpack。本文重点看一下，Redis 是如何基于 listpack 以及其他数据结构类型来构建对外暴露的五个核心数据结构的。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_quicklist">quicklist&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>关于 quicklist 更详细的介绍，请看 &lt;a href="https://www.diguage.com/post/redis-core-data-structure-1/#quicklist" target="_blank" rel="noopener">Redis 核心数据结构（一：quicklist&lt;/a>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>与上述内容不一样的地方是，现在的 quicklist 底层是使用 listpack 来构建的，而不是上述内容介绍的 ziplist。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_list">list&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>关于 &lt;code>list-max-listpack-size&lt;/code> 的解释，在源码中找到了详细介绍：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>redis.conf&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># Lists are also encoded in a special way to save a lot of space.&lt;/span>
&lt;span class="c"># The number of entries allowed per internal list node can be specified&lt;/span>
&lt;span class="c"># as a fixed maximum size or a maximum number of elements.&lt;/span>
&lt;span class="c"># For a fixed maximum size, use -5 through -1, meaning:&lt;/span>
&lt;span class="c"># -5: max size: 64 Kb &amp;lt;-- not recommended for normal workloads&lt;/span>
&lt;span class="c"># -4: max size: 32 Kb &amp;lt;-- not recommended&lt;/span>
&lt;span class="c"># -3: max size: 16 Kb &amp;lt;-- probably not recommended&lt;/span>
&lt;span class="c"># -2: max size: 8 Kb &amp;lt;-- good&lt;/span>
&lt;span class="c"># -1: max size: 4 Kb &amp;lt;-- good&lt;/span>
&lt;span class="c"># Positive numbers mean store up to _exactly_ that number of elements&lt;/span>
&lt;span class="c"># per list node.&lt;/span>
&lt;span class="c"># The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),&lt;/span>
&lt;span class="c"># but if your use case is unique, adjust the settings as necessary.&lt;/span>
list-max-listpack-size &lt;span class="nt">-2&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Redis 核心数据结构（三）</title><link>https://www.diguage.com/post/redis-core-data-structure-3/</link><pubDate>Fri, 13 Jun 2025 17:36:31 +0800</pubDate><guid>https://www.diguage.com/post/redis-core-data-structure-3/</guid><description>&lt;div class="paragraph">
&lt;p>在五年前，D瓜哥写了 &lt;a href="https://www.diguage.com/post/redis-core-data-structure-1/" target="_blank" rel="noopener">Redis 核心数据结构（一）&lt;/a> 和 &lt;a href="https://www.diguage.com/post/redis-core-data-structure-2/" target="_blank" rel="noopener">Redis 核心数据结构（二）&lt;/a> 两篇文章，来对 Redis 内部的数据结构做了深入分析。随着时间的推移，Redis 的实现也在不断进化，现在这些内容已经跟不上最新发展了，推陈出新，现在重写文章，来介绍 Redis 的最新发展。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_listpack">listpack&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>从 Redis 7.0 开始，使用 listpack 替换原来的 ziplist。至于替换原因，在 &lt;a href="https://github.com/redis/redis/issues/8702" target="_blank" rel="noopener">[NEW] listpack migration - replace all usage of ziplist with listpack&lt;/a> 做了解释说明：&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>The reason for using listpack instead of ziplist is that ziplist may cause cascading updates when insert and delete in middle, which is the biggest problem.&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;div class="attribution">
— sundb
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>翻译过来：当在中间进行插入和删除时，ziplist 也许会产生级联更新，这是一个大问题。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_编码规范">编码规范&lt;/h3>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/redis/listpack.png" alt="listpack 编码格式" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. listpack 编码格式&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>相比 ziplist，listpack 更偏向空间换时间。淡化极致的内存使用率，向更快的方向发力。&lt;/p>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="integer">对整数编码&lt;/h4>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/redis/listpack-integer.png" alt="listpack 整数编码" width="95%"/>
&lt;/div></description></item><item><title>算法模式：子集</title><link>https://www.diguage.com/post/algorithm-pattern-subsets/</link><pubDate>Thu, 10 Apr 2025 11:38:25 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-subsets/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-backtracking/" target="_blank" rel="noopener">算法模式：回溯&lt;/a> 介绍一种“一步三回头”、“落棋有悔”的算法模式：回溯。本篇文章，介绍一种无需“一步三回头”，无需“落棋有悔”也可以解决排列组合问题的算法模式：子集。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_子集">子集&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>超级多的编程面试问题都会涉及到排列和组合问题。一般都是使用回溯来解决该类问题，回溯法属于 &lt;a href="https://www.diguage.com/post/algorithm-pattern-depth-first-search/" target="_blank" rel="noopener">深度优先搜索&lt;/a>。子集问题模式讲的是用 &lt;a href="https://www.diguage.com/post/algorithm-pattern-breadth-first-search/" target="_blank" rel="noopener">广度优先搜索&lt;/a> 来处理这些问题。子集模式适用于子集与全排列。下面分别介绍：&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="deal-with-subset">处理子集问题&lt;/h3>
&lt;div class="paragraph">
&lt;p>举例来说明一下这个模式：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给一组数字 &lt;code>[1, 5, 3]&lt;/code>&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>我们从空集开始：&lt;code>[[]]&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>把第一个数 &lt;code>1&lt;/code>，加到之前已经存在的集合中：&lt;code>[[], [1]]&lt;/code>;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>把第二个数 &lt;code>5&lt;/code>，加到之前的集合中得到：&lt;code>[[], [1], [5], [1,5]]&lt;/code>;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>再加第三个数 &lt;code>3&lt;/code>，则有：&lt;code>[[], [1], [5], [1,5], [3], [1,3], [5,3], [1,5,3]]&lt;/code>.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果原有集合中存在重复元素，那么就需要针对这种情况特殊处理一下。流程如下：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给一组数字 &lt;code>[5, 1, 5]&lt;/code>&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>先对原有集合进行排序： &lt;code>[1, 5, 3]&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>从空集开始：&lt;code>[[]]&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>把第一个数 &lt;code>1&lt;/code>，加到之前已经存在的集合中：&lt;code>[[], [1]]&lt;/code>;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>把第二个数 &lt;code>5&lt;/code>，加到之前的集合中得到：&lt;code>[[], [1], [5], [1,5]]&lt;/code>;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>处理第三个数，也是 &lt;code>5&lt;/code> 时需要注意：&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>如果还是按照上述方案处理，那么就会得到如下结果： &lt;code>[[], [1], [5], [1,5], &lt;strong>[5], [1, 5]&lt;/strong>, [5, 5], [1,5, 5]]&lt;/code>。这里出现了重复子集： &lt;code>[5], [1, 5]&lt;/code>。该方案不通过，❌&lt;/p>
&lt;/li>
&lt;li>
&lt;p>观察最后生成的所有子集与重复的子集，会发现重复的子集，在处理第二个数时，已经处理过 &lt;code>[], [1]&lt;/code>，如果再次处理 &lt;code>5&lt;/code>，那么就会出现重复。所以，只需要处理在处理上一个相同的数时新增加的子集即可。上一个相同数新增的子集是 &lt;code>[5], [1,5]&lt;/code>，只需要在这些子集后面增加当前数字即可。这样最后的子集就是：&lt;code>[[], [1], [5], [1,5], [5, 5], [1,5, 5]]&lt;/code>。方案通过 ✅&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>算法模式：回溯</title><link>https://www.diguage.com/post/algorithm-pattern-backtracking/</link><pubDate>Wed, 09 Apr 2025 17:30:51 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-backtracking/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-transform-and-conquer/" target="_blank" rel="noopener">算法模式：变治法&lt;/a> 介绍一种有魔力的，可以将复杂问题化繁为简，化腐朽为神奇的算法模式：变治法。本篇文章，介绍一种“一步三回头”、“落棋有悔”的算法模式：回溯。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_回溯">回溯&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>“回溯”算法也叫“回溯搜索”算法，主要用于在一个庞大的空间里搜索我们所需要的问题的解。我们每天使用的“搜索引擎”就是帮助我们在庞大的互联网上搜索我们需要的信息。“搜索”引擎的“搜索”和“回溯搜索”算法的“搜索”意思是一样的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“回溯”指的是“状态重置”，可以理解为“回到过去”、“恢复现场”，是在编码的过程中，是为了节约空间而使用的一种技巧。而回溯其实是“深度优先遍历”特有的一种现象。之所以是“深度优先遍历”，是因为我们要解决的问题通常是在一棵树上完成的，在这棵树上搜索需要的答案，一般使用深度优先遍历。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“全排列”就是一个非常经典的“回溯”算法的应用。我们知道，&lt;code>N&lt;/code> 个数字的全排列一共有 \$N!\$ 这么多个。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>使用编程的方法得到全排列，就是在这样的一个树形结构中进行编程，具体来说，就是&lt;strong>执行一次深度优先遍历，从树的根结点到叶子结点形成的路径就是一个全排列。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/0046-01.png" alt="0046 01" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>说明：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>每一个结点表示了“全排列”问题求解的不同阶段，这些阶段通过变量的“不同的值”体现；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>这些变量的不同的值，也称之为“状态”；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用深度优先遍历有“回头”的过程，在“回头”以后，状态变量需要设置成为和先前一样；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>因此在回到上一层结点的过程中，需要撤销上一次选择，这个操作也称之为“状态重置”；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>深度优先遍历，可以直接借助系统栈空间，为我们保存所需要的状态变量，在编码中只需要注意遍历到相应的结点的时候，状态变量的值是正确的，具体的做法是：往下走一层的时候，&lt;code>path&lt;/code> 变量在尾部追加，而往回走的时候，需要撤销上一次的选择，也是在尾部操作，因此 &lt;code>path&lt;/code> 变量是一个栈。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>深度优先遍历通过“回溯”操作，实现了全局使用一份状态变量的效果。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>解决一个回溯问题，实际上就是一个决策树的遍历过程。&lt;/strong>只需要思考 3 个问题：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>路径：也就是已经做出的选择。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>选择列表：也就是你当前可以做的选择。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>结束条件：也就是到达决策树底层，无法再做选择的条件。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这三个问题也就对应回溯三部曲：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>定义递归函数以及参数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>确定递归终止条件&lt;/p>
&lt;/li>
&lt;li>
&lt;p>思考递归单层搜索逻辑&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>代码方面，回溯算法的框架：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code>result = []
def backtrack(路径, 选择列表):
 if 满足结束条件:
 result.add(路径)
 return

 for 选择 in 选择列表:
 做选择
 backtrack(路径, 选择列表)
 撤销选择&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>其核心就是 &lt;code>for&lt;/code> 循环里面的递归，在递归调用之前「做选择」，在递归调用之后「撤销选择」&lt;/strong>，特别简单。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>必须说明的是，不管怎么优化，都符合回溯框架，而且时间复杂度都不可能低于 \$O(N!)\$，因为穷举整棵决策树是无法避免的。&lt;strong>这也是回溯算法的一个特点，不像动态规划存在重叠子问题可以优化，回溯算法就是纯暴力穷举，复杂度一般都很高。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>玩回溯，一定要画出递归调用树。这样可以帮助我们更深入地理解整个回溯的过程，方便进一步剪枝优化。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>回溯优化，重要的是，要学会剪枝！&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_46_全排列">LeetCode 46. 全排列&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/permutations/" target="_blank" rel="noopener">LeetCode - 46. 全排列 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给定一个不含重复数字的数组 &lt;code>nums&lt;/code> ，返回其 &lt;em>所有可能的全排列&lt;/em> 。你可以 &lt;strong>按任意顺序&lt;/strong> 返回答案。&lt;/p>
&lt;/div></description></item><item><title>算法模式：变治法</title><link>https://www.diguage.com/post/algorithm-pattern-transform-and-conquer/</link><pubDate>Tue, 08 Apr 2025 16:50:33 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-transform-and-conquer/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-divide-and-conquer/" target="_blank" rel="noopener">算法模式：分治法&lt;/a> 介绍一种可用于处理节点前后顺序的算法模式：拓扑排序。本篇文章，介绍一种有魔力的，可以将复杂问题化繁为简，化腐朽为神奇的算法模式：变治法。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_变治法">变治法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>D瓜哥最早知道变治法也是在 &lt;a href="https://book.douban.com/subject/26337727/" target="_blank" rel="noopener">《算法设计与分析基础》&lt;/a> 中。这里也直接引用该书的介绍。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>变治法，就是基于变换的一种思想方法，首先把问题的实例变得容易求解，然后进行求解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>变治法的工作可以分成两个阶段：首先把问题变得更容易求解，然后对实例进行求解。根据我们对问题实例的变换方式，变治思想有3种主要的类型：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>实例化简(Instance simplification) — 指将原问题变换为同样问题的一个更简单或者更方便的实例。一个典型的案例是：去重时，先排序，&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>列表预排序&lt;/p>
&lt;div class="olist lowerroman">
&lt;ol class="lowerroman" type="i">
&lt;li>
&lt;p>检验数组中元素的唯一性&lt;/p>
&lt;/li>
&lt;li>
&lt;p>模式计算&lt;/p>
&lt;/li>
&lt;li>
&lt;p>查找问题&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>高斯消元法&lt;/p>
&lt;div class="olist lowerroman">
&lt;ol class="lowerroman" type="i">
&lt;li>
&lt;p>系数矩阵的LU分解(LU decomposition)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>计算矩阵的逆&lt;/p>
&lt;/li>
&lt;li>
&lt;p>计算矩阵的行列式&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>AVL 树&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>改变表现(Representation Change) — 指将原问题变换为同样实例的不同表现。经典的栗子：霍纳法则。&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>多路平衡查找树（最简单的情况：2-3树）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>求多项式的霍纳法则&lt;/p>
&lt;/li>
&lt;li>
&lt;p>两种二进制幂算法&lt;/p>
&lt;/li>
&lt;li>
&lt;p>堆排序&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>问题化简(Problem reduction) — 指把一个给定的问题变换为另一个可以用已知算法求解的问题。（归化思想）转换的难题在于如何找到一个变换的目标算法。典型案例是背包问题，背包问题的本质是线性规划。了解了线性规划的本质后，才能更好地解决高维的背包问题。&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>求最小公倍数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>计算图中的路径数量&lt;/p>
&lt;/li>
&lt;li>
&lt;p>最优化问题（最大化问题(maximization problem)、最小化问题(minimization problem)）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>线性规划（单纯形法、0/1背包问题）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>简化为图问题&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_474_一和零">LeetCode 474. 一和零&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/ones-and-zeroes/" target="_blank" rel="noopener">LeetCode - 474. 一和零&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你一个二进制字符串数组 &lt;code>strs&lt;/code> 和两个整数 &lt;code>m&lt;/code> 和 &lt;code>n&lt;/code> 。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>请你找出并返回 &lt;code>strs&lt;/code> 的最大子集的长度，该子集中 &lt;strong>最多&lt;/strong> 有 &lt;code>m&lt;/code> 个 &lt;code>0&lt;/code> 和 &lt;code>n&lt;/code> 个 &lt;code>1&lt;/code> 。&lt;/p>
&lt;/div></description></item><item><title>算法模式：分治法</title><link>https://www.diguage.com/post/algorithm-pattern-divide-and-conquer/</link><pubDate>Mon, 07 Apr 2025 15:59:27 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-divide-and-conquer/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-decrease-and-conquer/" target="_blank" rel="noopener">算法模式：减治法&lt;/a> 介绍一种有魔力的，可以将复杂问题化繁为简，化腐朽为神奇的算法模式：减治法。本篇文章，继续介绍一种有魔力的，可以将复杂问题化繁为简，化腐朽为神奇的算法模式：分治法。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_分治法">分治法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>关于分治法的内容，这里继续参考 &lt;a href="https://book.douban.com/subject/26337727/" target="_blank" rel="noopener">《算法设计与分析基础》&lt;/a> 中的内容。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>分治法是按照以下方案工作的。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>将一个问题划分为同一类型的若干子问题，子问题最好规模相同。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对这些子问题求解（一般使用递归方法，但在问题规模足够小时，有时也会利用另一个算法）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>有必要的话，合并这些子问题的解，以得到原始问题的答案。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/divide-and-conquer-1.png" alt="分治法" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 分治法&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从字面上分析就可以看到有哪些步骤：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>分-分解-将问题分解为规模更小的子问题，子问题最好相同或相似；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>治-求解-将这些规模更小的子问题逐个击破；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>合-合并-将已解决的子问题合并，最终得出原问题的解；&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从上述步骤中我们可以看出，分治算法一般适用满足以下条件的场景：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>问题规模缩小到一定的程度就可以很容易解决；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>问题&lt;strong>可以分解&lt;/strong>为若干个规模较小的相同问题；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>问题分解出的若干子问题的解可以合并为该问题的解；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>每个子问题都是&lt;strong>独立&lt;/strong>的，相互之间没有交集。（这是区别分治法与减）&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在“分”的过程中，我们尽可能让分解出的子问题与原始问题相似，而规模更小。这刚好符合递归的特性。因此，分治法往往与递归联系在一起。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在分治法最典型的运用中，问题规模为 &lt;code>n&lt;/code> 的实例被划分为两个规模为 &lt;code>n/2&lt;/code> 的实例。更一般的情况下，一个规模为 &lt;code>n&lt;/code> 的实例可以划分为 &lt;code>b&lt;/code> 个规模为 &lt;code>n/b&lt;/code> 的实例，其中 &lt;code>a&lt;/code> 个实例需要求解（这里，&lt;code>a&lt;/code> 和 &lt;code>b&lt;/code> 是常量，&lt;code>a≥1&lt;/code>，&lt;code>b&amp;gt;1&lt;/code>）。&lt;/p>
&lt;/div>
&lt;div class="stemblock text-center">
&lt;div class="content">
\$T(n) = aT(n/b) + f(n)\$
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>其中，\$f(n)\$ 是一个函数，表示将问题分解为小问题和将结果合并起来所消耗的时间&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>分治法的典型案例如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>归并排序&lt;/p>
&lt;/li>
&lt;li>
&lt;p>快速排序&lt;/p>
&lt;/li>
&lt;li>
&lt;p>二叉树的经典遍历算法和其他类似的算法都需要递归处理左右两棵子树&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Strassen 算法&lt;/p>
&lt;/li>
&lt;li>
&lt;p>最近对问题&lt;/p>
&lt;/li>
&lt;li>
&lt;p>凸包问题&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>分治法对&lt;strong>分治出的部分需要分别处理&lt;/strong>，进行分开的单独计算，而减治法则利用了&amp;#34;一个问题给定实例的解和同样问题较小实例的解之间的关系&amp;#34;，只针对部分子问题求解，&lt;strong>减治掉的那部分就不需要了&lt;/strong>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>减常因子的减治法也可以看做是分治的变种。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_148_排序链表">LeetCode 148. 排序链表&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/sort-list/" target="_blank" rel="noopener">LeetCode - 148. 排序链表 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你链表的头结点 &lt;code>head&lt;/code> ，请将其按 &lt;strong>升序&lt;/strong> 排列并返回 &lt;strong>排序后的链表&lt;/strong> 。&lt;/p>
&lt;/div></description></item><item><title>算法模式：减治法</title><link>https://www.diguage.com/post/algorithm-pattern-decrease-and-conquer/</link><pubDate>Sun, 06 Apr 2025 23:32:03 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-decrease-and-conquer/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-topological-sort/" target="_blank" rel="noopener">算法模式：拓扑排序&lt;/a> 介绍一种可用于处理节点前后顺序的算法模式：拓扑排序。本篇文章，介绍一种有魔力的，可以将复杂问题化繁为简，化腐朽为神奇的算法模式：减治法。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_减治法">减治法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>D瓜哥最早知道减治法是在 &lt;a href="https://book.douban.com/subject/26337727/" target="_blank" rel="noopener">《算法设计与分析基础》&lt;/a> 中。这里也直接引用该书的介绍。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>减治(decrease-and-conquer)技术利用了一个问题给定实例的解和同样问题较小实例的解之间的某种关系。自底向上版本往往是迭代实现的，从求解问题的一个较小实例开始，该方法有时也称为增量法(Incremental Approach)。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>减治法有3种主要的变化形式：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>减去一个常量。&lt;/strong>在减常量(decrease-by-a-constant)变化形式中，每次算法迭代总是从实例中减去一个相同的常量。&lt;/p>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>插入排序&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>减去一个常量因子。&lt;/strong>减常因子(decrease-by-a-constant-factor)技术意味着在算法的每次迭代中，总是从实例的规模中减去一个相同的常数因子。在大多数应用中，这样的常数因子等于2，其实就是减半。&lt;/p>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>二分查找&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>减去的规模是可变的。&lt;/strong>在减治法的减可变规模(variable-size-decrease)变化形式中，算法在每次迭代时，规模减小的模式都是不同的。&lt;/p>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>计算最大公约数的欧几里得算法是这种情况的一个很好的例子。 \$gcd(m, n)=gcd(n,m mod n)\$&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_50_powx_n">LeetCode 50. Pow(x, n)&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/powx-n/" target="_blank" rel="noopener">LeetCode - 50. Pow(x, n) &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>实现 &lt;a href="https://www.cplusplus.com/reference/valarray/pow/">pow(x, n)&lt;/a> ，即计算 &lt;code>x&lt;/code> 的整数 &lt;code>n&lt;/code> 次幂函数（即，&lt;code>x&lt;sup>n&lt;/sup>&lt;/code>）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：x = 2.00000, n = 10
输出：1024.00000&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：x = 2.10000, n = 3
输出：9.26100&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 3：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：x = 2.00000, n = -2
输出：0.25000
解释：2-2 = 1/22 = 1/4 = 0.25&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>提示：&lt;/strong>&lt;/p>
&lt;/div></description></item><item><title>算法模式：拓扑排序</title><link>https://www.diguage.com/post/algorithm-pattern-topological-sort/</link><pubDate>Fri, 04 Apr 2025 09:09:09 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-topological-sort/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-union-find/" target="_blank" rel="noopener">算法模式：并查集&lt;/a> 介绍一种关于特殊的树的算法模式：并查集。本篇文章，介绍一种可用于处理节点前后顺序的算法模式：拓扑排序。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_拓扑排序">拓扑排序&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>拓扑排序模式用来寻找一种线性的顺序，这些元素之间具有依懒性。比如，如果事件 B 依赖于事件 A，那 A 在拓扑排序顺序中排在 B 的前面。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这种模式定义了一种简单方式来理解拓扑排序这种技术。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这种模式是这样奏效的：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>初始化&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>借助于 &lt;code>Map&lt;/code> 将图保存成邻接表形式。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>找到所有的起点，用 &lt;code>Map&lt;/code> 来帮助记录每个节点的入度&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>创建图，找到每个节点的入度&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>利用输入，把图建好，然后遍历一下图，将入度信息记录在 &lt;code>Map&lt;/code> 中&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>找所有的起点&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>所有入度为 &lt;code>0&lt;/code> 的节点，都是有效的起点，而且我们讲他们都加入到一个队列中&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>排序&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>对每个起点，执行以下步骤&lt;/p>
&lt;div class="olist lowerroman">
&lt;ol class="lowerroman" type="i">
&lt;li>
&lt;p>把它加到结果的顺序中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将其在图中的孩子节点取到&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将其孩子的入度减少1&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果孩子的入度变为0，则改孩子节点成为起点，将其加入队列中&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>重复上述过程，直到起点队列为空。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>用一句话概括：&lt;strong>将依赖关系转化成一张有向图，如果这张图中的节点没有循环依赖，那么则方案可行，否则方案不可行。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
这里解释的是一种广度优先搜索，还存在一种深度优先搜索的处理办法，感兴趣可以尝试一下。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="paragraph">
&lt;p>拓扑排序模式识别：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>待解决的问题需要处理无环图&lt;/p>
&lt;/li>
&lt;li>
&lt;p>你需要以一种有序的秩序更新输入元素&lt;/p>
&lt;/li>
&lt;li>
&lt;p>需要处理的输入遵循某种特定的顺序&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_207_课程表">LeetCode 207. 课程表&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/course-schedule/" target="_blank" rel="noopener">LeetCode - 207. 课程表 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>你这个学期必须选修 &lt;code>numCourses&lt;/code> 门课程，记为 &lt;code>0&lt;/code> 到 &lt;code>numCourses - 1&lt;/code> 。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在选修某些课程之前需要一些先修课程。 先修课程按数组 &lt;code>prerequisites&lt;/code> 给出，其中 &lt;code>prerequisites[i] = [a&lt;sub>i&lt;/sub>, b&lt;sub>i&lt;/sub>]&lt;/code>，表示如果要学习课程 &lt;code>a&lt;sub>i&lt;/sub>&lt;/code> 则 &lt;strong>必须&lt;/strong> 先学习课程 &lt;code>b&lt;sub>i&lt;/sub>&lt;/code>。&lt;/p>
&lt;/div></description></item><item><title>算法模式：并查集</title><link>https://www.diguage.com/post/algorithm-pattern-union-find/</link><pubDate>Thu, 03 Apr 2025 15:22:41 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-union-find/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-trie/" target="_blank" rel="noopener">算法模式：前缀树&lt;/a> 介绍一种关于特殊的树的算法模式。本篇文章，再介绍一种关于特殊的树的算法模式：并查集。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_并查集">并查集&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>并查集算法，英文是 Union-Find，是解决动态连通性（Dynamic Conectivity）问题的一种算法。动态连通性是计算机图论中的一种数据结构，动态维护图结构中相连信息。简单的说就是，图中各个节点之间是否相连、如何将两个节点连接，连接后还剩多少个连通分量。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>动态连通性其实可以抽象成给一幅图连线。假设用一个数组表示一堆节点，每个节点都是一个连通分量。初始化视图如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/union-find-1.png" alt="并查集初始化" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 并查集初始化&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>并查集的一个重要操作是 &lt;code>union(a, b)&lt;/code>，就是将节点 &lt;code>a&lt;/code> 和节点 &lt;code>b&lt;/code> 建立连接。如图所示：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/union-find-2.png" alt="并查集合并" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. 并查集合并&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;code>union(a, b)&lt;/code> 还可以将已经建立的两个“子网”进行连接：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/union-find-3.png" alt="并查集再合并" width="95%"/>
&lt;/div>
&lt;div class="title">图 3. 并查集再合并&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>并查集除了 &lt;code>union&lt;/code>，还有一个重要操作是 &lt;code>connnected(a, b)&lt;/code>。判断方法也很简单，从节点 &lt;code>a&lt;/code> 和 &lt;code>b&lt;/code> 开始，向上查找，直到两个节点的根节点，判断两个根节点是否相等即可判断两个节点是否已经连接。为了加快这个判断速度，可以对其进行“路径压缩”，直白点说，就是将所有树的节点，都直接指向根节点，这样只需要一步即可到达根节点。路径压缩如图所示：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/union-find-4.png" alt="并查集路径压缩" width="95%"/>
&lt;/div>
&lt;div class="title">图 4. 并查集路径压缩&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>简单代码实现如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="kn">package&lt;/span> &lt;span class="nn">com.diguage.labs&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="kn">import&lt;/span> &lt;span class="nn">java.util.ArrayList&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">java.util.List&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="cm">/**
 * 并查集
 *
 * PS：没想到代码竟然一次通过。
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 * @since 2025-04-03 15:22:41
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">UnionFind&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="cm">/**
 * 连通分量
 */&lt;/span>
 &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="cm">/**
 * 每个节点及对应的父节点
 */&lt;/span>
 &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">parent&lt;/span>&lt;span class="o">;&lt;/span>

 &lt;span class="kd">public&lt;/span> &lt;span class="nf">UnionFind&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="n">parent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="o">];&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">parent&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="cm">/**
 * a 和 b 建立连接
 */&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">ap&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">find&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">bp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">find&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">ap&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">bp&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="n">parent&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">ap&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">bp&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="n">size&lt;/span>&lt;span class="o">--;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="cm">/**
 * a 和 b 是否连通
 */&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kt">boolean&lt;/span> &lt;span class="nf">connected&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">ap&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">find&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">bp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">find&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">ap&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">bp&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="cm">/**
 * 连通分量
 */&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="nf">count&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="cm">/**
 * 查找节点 a 的根节点
 */&lt;/span>
 &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="nf">find&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">ap&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parent&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">];&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">ap&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Integer&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">ArrayList&lt;/span>&lt;span class="o">&amp;lt;&amp;gt;();&lt;/span>
 &lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="c1">// 向上查找根节点&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">ap&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">ap&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">ap&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">ap&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parent&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">ap&lt;/span>&lt;span class="o">];&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="c1">// 路径压缩&lt;/span>
 &lt;span class="c1">// 只有一步，无需缩短路径&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">size&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">ap&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">Integer&lt;/span> &lt;span class="n">idx&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">parent&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">idx&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ap&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">ap&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">String&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">UnionFind&lt;/span> &lt;span class="n">uf&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">UnionFind&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">6&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">9&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">union&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">count&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s">&amp;#34;, &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">connected&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">9&lt;/span>&lt;span class="o">));&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">count&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s">&amp;#34;, &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">connected&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">9&lt;/span>&lt;span class="o">));&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">count&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s">&amp;#34;, &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">connected&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">9&lt;/span>&lt;span class="o">));&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">count&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s">&amp;#34;, &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">uf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">connected&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">9&lt;/span>&lt;span class="o">));&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>算法模式：前缀树</title><link>https://www.diguage.com/post/algorithm-pattern-trie/</link><pubDate>Wed, 02 Apr 2025 19:16:24 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-trie/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-depth-first-search/" target="_blank" rel="noopener">算法模式：深度优先搜索&lt;/a> 介绍了介绍一种即适用于树，又适用于图的的算法模式。本篇文章，介绍一种关于特殊的树的算法模式：前缀树。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_前缀树">前缀树&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>前缀树，又称为字典树，还叫单词查找树，英文是 Trie，也有叫 Prefix Tree。顾名思义，就是一个像字典一样的树。如图：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/trie.svg" alt="前缀树" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 前缀树&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>前缀树是一种有序树，用于保存关联数组，其中的键通常是字符串。与二叉查找树不同，键不是直接保存在节点中，而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀，也就是这个节点对应的字符串，而根节点对应空字符串。一般情况下，不是所有的节点都有对应的值，只有叶子节点和部分内部节点所对应的键才有相关的值。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_208_实现_trie_前缀树">LeetCode 208. 实现 Trie (前缀树)&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/implement-trie-prefix-tree/" target="_blank" rel="noopener">LeetCode - 208. 实现 Trie (前缀树) &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>&lt;a href="https://baike.baidu.com/item/字典树/9825209?fr=aladdin">Trie&lt;/a>&lt;/strong>（发音类似 &amp;#34;try&amp;#34;）或者说 &lt;strong>前缀树&lt;/strong> 是一种树形数据结构，用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景，例如自动补全和拼写检查。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>请你实现 Trie 类：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>Trie()&lt;/code> 初始化前缀树对象。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>void insert(String word)&lt;/code> 向前缀树中插入字符串 &lt;code>word&lt;/code> 。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>boolean search(String word)&lt;/code> 如果字符串 &lt;code>word&lt;/code> 在前缀树中，返回 &lt;code>true&lt;/code>（即，在检索之前已经插入）；否则，返回 &lt;code>false&lt;/code> 。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>boolean startsWith(String prefix)&lt;/code> 如果之前已经插入的字符串 &lt;code>word&lt;/code> 的前缀之一为 &lt;code>prefix&lt;/code> ，返回 &lt;code>true&lt;/code> ；否则，返回 &lt;code>false&lt;/code> 。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入
[&amp;#34;Trie&amp;#34;, &amp;#34;insert&amp;#34;, &amp;#34;search&amp;#34;, &amp;#34;search&amp;#34;, &amp;#34;startsWith&amp;#34;, &amp;#34;insert&amp;#34;, &amp;#34;search&amp;#34;]
[[], [&amp;#34;apple&amp;#34;], [&amp;#34;apple&amp;#34;], [&amp;#34;app&amp;#34;], [&amp;#34;app&amp;#34;], [&amp;#34;app&amp;#34;], [&amp;#34;app&amp;#34;]]
输出
[null, null, true, false, true, null, true]

解释
Trie trie = new Trie();
trie.insert(&amp;#34;apple&amp;#34;);
trie.search(&amp;#34;apple&amp;#34;); // 返回 True
trie.search(&amp;#34;app&amp;#34;); // 返回 False
trie.startsWith(&amp;#34;app&amp;#34;); // 返回 True
trie.insert(&amp;#34;app&amp;#34;);
trie.search(&amp;#34;app&amp;#34;); // 返回 True&lt;/pre>
&lt;/div></description></item><item><title>算法模式：深度优先搜索</title><link>https://www.diguage.com/post/algorithm-pattern-depth-first-search/</link><pubDate>Tue, 01 Apr 2025 10:43:31 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-depth-first-search/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-breadth-first-search/" target="_blank" rel="noopener">算法模式：广度优先搜索&lt;/a> 介绍了介绍一种即适用于树，又适用于图的的算法模式。本篇文章，继续介绍一种即适用于树，又适用于图的的算法模式：深度优先搜索。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_深度优先搜索">深度优先搜索&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>深度优先搜索主要思路是从图中一个未访问的顶点 V 开始，沿着一条路一直走到底，然后从这条路尽头的节点回退到上一个节点，再从另一条路开始走到底…​，不断递归重复此过程，直到所有的顶点都遍历完成，它的特点是不撞南墙不回头，先走完一条路，再换一条路继续走。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>树是图的一种特例(连通无环的图就是树)，所以，深度优先搜索也适用于树。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在对树做深度优先搜索时，可以用递归（或显式栈，如果你想用迭代方式的话）来记录遍历过程中访问过的父节点。运行方式是从根节点开始，如果该节点不是叶子节点，我们需要干三件事：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>需要区别我们是先处理根节点（pre-order，前序），处理孩子节点之间处理根节点（in-order，中序），还是处理完所有孩子再处理根节点（post-order，后序）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>递归处理当前节点的左右孩子。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_124_二叉树中的最大路径和">LeetCode 124. 二叉树中的最大路径和&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/binary-tree-maximum-path-sum/" target="_blank" rel="noopener">LeetCode - 124. 二叉树中的最大路径和 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>二叉树中的 &lt;strong>路径&lt;/strong> 被定义为一条节点序列，序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 &lt;strong>至多出现一次&lt;/strong> 。该路径 &lt;strong>至少包含一个&lt;/strong> 节点，且不一定经过根节点。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>路径和&lt;/strong> 是路径中各节点值的总和。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你一个二叉树的根节点 &lt;code>root&lt;/code> ，返回其 &lt;strong>最大路径和&lt;/strong> 。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre> 1
 / \
 2 3
输入：root = [1,2,3]
输出：6
解释：最优路径是 2 -&amp;gt; 1 -&amp;gt; 3 ，路径和为 2 + 1 + 3 = 6&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre> -10
 / \
 9 20
 / \
 15 7

输入：root = [-10,9,20,null,null,15,7]
输出：42
解释：最优路径是 15 -&amp;gt; 20 -&amp;gt; 7 ，路径和为 15 + 20 + 7 = 42&lt;/pre>
&lt;/div></description></item><item><title>算法模式：广度优先搜索</title><link>https://www.diguage.com/post/algorithm-pattern-breadth-first-search/</link><pubDate>Mon, 31 Mar 2025 07:31:39 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-breadth-first-search/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-k-way-merge/" target="_blank" rel="noopener">算法模式：多路归并&lt;/a> 介绍了一种利用堆做链表合并的算法模式。本篇文章，介绍一种即适用于树，又适用于图的的算法模式：广度优先搜索。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_广度优先搜索">广度优先搜索&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>广度优先搜索既适用于树，又适用于图。除此之外，在处理一些矩阵问题时，也会用到广度优先搜索的思想。当然，也可以把矩阵按照图来理解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>树上的广度优先搜索模式是通过把根节点加到队列中，然后不断遍历直到队列为空。每一次循环中，我们都会把队头结点拿出来（remove），然后对其进行必要的操作。在删除每个节点的同时，其孩子节点，都会被加到队列中。借助于队列数据结构，从而能保证树的节点按照他们的层数打印出来。打印完当前层所有元素，才能执行到下一层。所有这种需要遍历树且需要一层一层遍历的问题，都能用这种模式高效解决。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>识别树上的广度优先搜索：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>如果你被问到去遍历树，需要按层操作的方式（也称作层序遍历）&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_102_二叉树的层序遍历">LeetCode 102. 二叉树的层序遍历&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/binary-tree-level-order-traversal/" target="_blank" rel="noopener">LeetCode - 102. 二叉树的层序遍历 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你二叉树的根节点 &lt;code>root&lt;/code> ，返回其节点值的 &lt;strong>层序遍历&lt;/strong> 。（即逐层地，从左到右访问所有节点）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/0102-00.jpg" alt="0102 00" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：root = [3,9,20,null,null,15,7]
输出：[[3],[9,20],[15,7]]&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：root = [1]
输出：[[1]]&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 3：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：root = []
输出：[]&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>提示：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>树中节点数目在范围 &lt;code>[0, 2000]&lt;/code> 内&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>-1000 &amp;lt;= Node.val &amp;lt;= 1000&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_思路分析">思路分析&lt;/h3>
&lt;div class="paragraph">
&lt;p>思路与上述描述类似，这里直接看图：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/0102-00.png" alt="广度优先搜索" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 广度优先搜索&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>代码如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 * @since 2025-03-31 07:31:39
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Integer&lt;/span>&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="nf">levelOrder&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">root&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">root&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="nc">Collections&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">emptyList&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Integer&lt;/span>&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">LinkedList&lt;/span>&lt;span class="o">&amp;lt;&amp;gt;();&lt;/span>
 &lt;span class="nc">Queue&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">TreeNode&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">queue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">LinkedList&lt;/span>&lt;span class="o">&amp;lt;&amp;gt;();&lt;/span>
 &lt;span class="n">queue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">offer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">root&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(!&lt;/span>&lt;span class="n">queue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isEmpty&lt;/span>&lt;span class="o">())&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">queue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">size&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Integer&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">level&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">ArrayList&lt;/span>&lt;span class="o">&amp;lt;&amp;gt;(&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">node&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">queue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">poll&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="n">level&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">node&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">val&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="c1">// 将下一层节点，从左到右，依次加入到队列中&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">node&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">queue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">offer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">node&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">node&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">queue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">offer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">node&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">level&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div></description></item><item><title>算法模式：多路归并</title><link>https://www.diguage.com/post/algorithm-pattern-k-way-merge/</link><pubDate>Sun, 30 Mar 2025 23:20:44 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-k-way-merge/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-two-heaps/" target="_blank" rel="noopener">算法模式：双堆&lt;/a> 介绍了一种利用两个堆选择中间数的算法模式。本篇文章，再来介绍一种关于堆的模式：多路归并。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_多路归并">多路归并&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>多路归并能帮咱们解决那些涉及到多组排好序的数组的问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>每当你的输入是 K 个排好序的数组，你就可以用堆来高效顺序遍历其中所有数组的所有元素。你可以将每个数组中最小的一个元素加入到最小堆中，从而得到全局最小值。当我们拿到这个全局最小值之后，再从该元素所在的数组里取出其后面紧挨着的元素，加入堆。如此往复直到处理完所有的元素。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>该模式是这样的运行的：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>把每个数组中的第一个元素都加入最小堆中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>取出堆顶元素（全局最小），将该元素放入排好序的结果集合里面&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将刚取出的元素所在的数组里面的下一个元素加入堆&lt;/p>
&lt;/li>
&lt;li>
&lt;p>重复步骤 2，3，直到处理完所有数字&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>识别K路归并：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>该问题的输入是排好序的数组，链表或是矩阵&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果问题让咱们合并多个排好序的集合，或是需要找这些集合中最小的元素&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_23_合并_k_个升序链表">LeetCode 23. 合并 K 个升序链表&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/merge-k-sorted-lists/" target="_blank" rel="noopener">LeetCode - 23. 合并 K 个升序链表 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你一个链表数组，每个链表都已经按升序排列。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>请你将所有链表合并到一个升序链表中，返回合并后的链表。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：lists = [[1,4,5],[1,3,4],[2,6]]
输出：[1,1,2,3,4,4,5,6]
解释：链表数组如下：
[
 1-&amp;gt;4-&amp;gt;5,
 1-&amp;gt;3-&amp;gt;4,
 2-&amp;gt;6
]
将它们合并到一个有序链表中得到。
1-&amp;gt;1-&amp;gt;2-&amp;gt;3-&amp;gt;4-&amp;gt;4-&amp;gt;5-&amp;gt;6&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：lists = []
输出：[]&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 3：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：lists = [[]]
输出：[]&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>提示：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>k == lists.length&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>0 &amp;lt;= k &amp;lt;= 10&lt;sup>4&lt;/sup>&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>0 &amp;lt;= lists[i].length &amp;lt;= 500&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>-10&lt;sup>4&lt;/sup> &amp;lt;= lists[i][j] &amp;lt;= 10&lt;sup>4&lt;/sup>&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>lists[i]&lt;/code> 按 &lt;strong>升序&lt;/strong> 排列&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>lists[i].length&lt;/code> 的总和不超过 &lt;code>10&lt;sup>4&lt;/sup>&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>算法模式：双堆</title><link>https://www.diguage.com/post/algorithm-pattern-two-heaps/</link><pubDate>Fri, 28 Mar 2025 17:00:16 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-two-heaps/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-cyclic-sort/" target="_blank" rel="noopener">算法模式：循环排序&lt;/a> 介绍了一种只需 \$O(1)\$ 时间就可以完成排序的算法模式。本篇文章，来介绍一种可以快速查出数组中位数的模式：双堆。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_双堆">双堆&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>很多问题中，我们被告知，我们拿到一大把可以分成两队的数字。为了解决这个问题，我们感兴趣的是，怎么把数字分成两半？使得：小的数字都放在一起，大的放在另外一半。双堆模式就能高效解决此类问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>正如名字所示，该模式用到了两个堆，是不是很难猜？一个最小堆用来找最小元素；一个最大堆，拿到最大元素。这种模式将一半的元素放在最大堆中，这样你可以从这一堆中秒找到最大元素。同理，把剩下一半丢到最小堆中，\$O(1)\$ 时间找到他们中的最小元素。通过这样的方式，这一大堆元素的中位数就可以从两个堆的堆顶拿到数字，从而计算出来。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>判断双堆模式的秘诀：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>这种模式在优先队列，计划安排问题（Scheduling）中有奇效&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果问题让你找一组数中的最大/最小/中位数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>有时候，这种模式在涉及到二叉树数据结构时也特别有用&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/max-min-heap.svg" alt="大堆与小堆" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 大堆与小堆&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_295_数据流的中位数">LeetCode 295. 数据流的中位数&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/find-median-from-data-stream/" target="_blank" rel="noopener">LeetCode - 295. 数据流的中位数 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>中位数&lt;/strong>是有序整数列表中的中间值。如果列表的大小是偶数，则没有中间值，中位数是两个中间值的平均值。&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>例如 &lt;code>arr = [2,3,4]&lt;/code> 的中位数是 &lt;code>3&lt;/code> 。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>例如 &lt;code>arr = [2,3]&lt;/code> 的中位数是 &lt;code>(2 + 3) / 2 = 2.5&lt;/code> 。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>实现 MedianFinder 类:&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>MedianFinder()&lt;/code> 初始化 &lt;code>MedianFinder&lt;/code> 对象。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>void addNum(int num)&lt;/code> 将数据流中的整数 &lt;code>num&lt;/code> 添加到数据结构中。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>double findMedian()&lt;/code> 返回到目前为止所有元素的中位数。与实际答案相差 &lt;code>10&lt;sup>-5&lt;/sup>&lt;/code> 以内的答案将被接受。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入
[&amp;#34;MedianFinder&amp;#34;, &amp;#34;addNum&amp;#34;, &amp;#34;addNum&amp;#34;, &amp;#34;findMedian&amp;#34;, &amp;#34;addNum&amp;#34;, &amp;#34;findMedian&amp;#34;]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]

解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0&lt;/pre>
&lt;/div></description></item><item><title>算法模式：循环排序</title><link>https://www.diguage.com/post/algorithm-pattern-cyclic-sort/</link><pubDate>Thu, 27 Mar 2025 22:16:54 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-cyclic-sort/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-quickselect/" target="_blank" rel="noopener">算法模式：快速选择&lt;/a> 介绍了如何利用快排思想快速选出第 K 个 最 X 的元素。本篇文章，介绍一种只需 \$O(1)\$ 时间就可以完成排序的算法模式：循环排序。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_循环排序">循环排序&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>循环排序讲述的是一种很好玩的模式：可以用来处理数组中的数值限定在一定的区间的问题。这种模式一个个遍历数组中的元素，如果当前这个数它不在其应该在的位置的话，咱们就把它和它应该在的那个位置上的数交换一下。你可以尝试将该数放到其正确的位置上，但这复杂度就会是 \$O(n^2)\$。这样的话，可能就不是最优解了。因此循环排序的优势就体现出来了。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/cyclic-sort.png" alt="循环排序" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 循环排序&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>循环排序适用的场景：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>包含连续数字的数组（如 1 到 n 或 0 到 n-1）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>需要找出缺失/重复数字的问题&lt;/p>
&lt;/li>
&lt;li>
&lt;p>需要原地排序且时间复杂度要求高的情况&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_41_缺失的第一个正数">LeetCode 41. 缺失的第一个正数&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/first-missing-positive/" target="_blank" rel="noopener">LeetCode - 41. 缺失的第一个正数&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你一个未排序的整数数组 &lt;code>nums&lt;/code> ，请你找出其中没有出现的最小的正整数。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>请你实现时间复杂度为 \$O(n)\$ 并且只使用常数级别额外空间的解决方案。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：nums = [1,2,0]
输出：3
解释：范围 [1,2] 中的数字都在数组中。&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：nums = [3,4,-1,1]

输出：2

解释：1 在数组中，但 2 没有。&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 3：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：nums = [7,8,9,11,12]
输出：1
解释：最小的正数 1 没有出现。&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>提示：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>1 &amp;lt;= nums.length &amp;lt;= 10&lt;sup>5&lt;/sup>&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>-2&lt;sup>31&lt;/sup> &amp;lt;= nums[i] &amp;lt;= 2&lt;sup>31&lt;/sup> - 1&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>算法模式：快速选择</title><link>https://www.diguage.com/post/algorithm-pattern-quickselect/</link><pubDate>Wed, 26 Mar 2025 16:24:19 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-quickselect/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-top-k-elements/" target="_blank" rel="noopener">算法模式：Top K 问题&lt;/a> 介绍了如何利用堆快速选出最 X 的 K 个元素。本篇文章，介绍一种可以快速选择第 K 个 最 X 元素的算法模式：快速选择。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_快速选择">快速选择&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>快速选择起源于快排算法。在快排算法中，把元素根据基准元素分成左右两部分，一边的元素小于基准元素，另外一个的元素大于等于基准元素，再对两边的元素递归处理，最终得到有序结果。受此启发，在将元素根据基准元素分成左右两部分后，这里假设，左边小于基准元素，右边大于等于基准元素，那么会有如下三种情况：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>当前基准元素所在位置正好是 K，正好是所求结果，直接返回；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>当前基准元素所在位置小于 K，那么 K 位置在当前基准元素的右边；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>当前基准元素所在位置大于 K，那么 K 位置在当前基准元素的左边；&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>所以，该模式不仅适用于求第 K 个之最元素，也适用于求“Top K 问题”。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_215_数组中的第k个最大元素">LeetCode 215. 数组中的第K个最大元素&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/kth-largest-element-in-an-array/" target="_blank" rel="noopener">LeetCode - 215. 数组中的第K个最大元素 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给定整数数组 &lt;code>nums&lt;/code> 和整数 &lt;code>k&lt;/code>，请返回数组中第 &lt;strong>&lt;code>k&lt;/code>&lt;/strong> 个最大的元素。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>请注意，你需要找的是数组排序后的第 &lt;code>k&lt;/code> 个最大的元素，而不是第 &lt;code>k&lt;/code> 个不同的元素。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>你必须设计并实现时间复杂度为 \$O(n)\$ 的算法解决此问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1:&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入: [3,2,1,5,6,4], k = 2
输出: 5&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2:&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4&lt;/pre>
&lt;/div></description></item><item><title>算法模式：Top K 问题</title><link>https://www.diguage.com/post/algorithm-pattern-top-k-elements/</link><pubDate>Tue, 25 Mar 2025 21:20:04 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-top-k-elements/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-monotonic-stack/" target="_blank" rel="noopener">算法模式：单调栈&lt;/a> 介绍了单调栈的算法模式。本篇文章，介绍一种堆相关的算法模式：Top K 问题。（英语原文是 Top K Elements，实在没有找到好的翻译，暂时翻译成 “Top K 问题”，后续有好的翻译再改。）&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_top_k_问题">Top K 问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>任何让求解最大/最小/最频繁的K个元素的题，都遵循这种模式。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>用来记录这种前 K 类型的最佳数据结构就是堆了（在Java中，对应的结构是优先队列 &lt;code>PriorityQueue&lt;/code> ）。这种模式借助堆来解决很多这种前 K 个数值的问题。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/max-min-heap.svg" alt="大堆与小堆" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 大堆与小堆&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这个模式是这样的：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>根据题目要求，将K个元素插入到最小堆或是最大堆。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>遍历剩下的还没访问的元素，如果当前出来到的这个元素比堆顶元素大或者小，那咱们把堆顶元素先删除，再加当前元素进去。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果求最大的前 K 个元素，则适合使用小堆，将待检查元素与堆顶元素相比，堆顶元素小，直接删除堆顶元素，将待检查元素添加到堆即可。反之，则用大堆。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>注意这种模式下，咱们不需要去排序数组，因为堆具有这种良好的局部有序性，这对咱们需要解决问题就够了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>识别最大 K 个元素模式：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>如果你需要求最大/最小/最频繁的前K个元素&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果你需要通过排序去找一个特定的数&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_347_前_k_个高频元素">LeetCode 347. 前 K 个高频元素&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/top-k-frequent-elements/" target="_blank" rel="noopener">LeetCode - 347. 前 K 个高频元素 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你一个整数数组 &lt;code>nums&lt;/code> 和一个整数 &lt;code>k&lt;/code> ，请你返回其中出现频率前 &lt;code>k&lt;/code> 高的元素。你可以按 &lt;strong>任意顺序&lt;/strong> 返回答案。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1:&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2:&lt;/strong>&lt;/p>
&lt;/div></description></item><item><title>算法模式：单调栈</title><link>https://www.diguage.com/post/algorithm-pattern-monotonic-stack/</link><pubDate>Mon, 24 Mar 2025 20:26:12 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-monotonic-stack/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-sliding-window/" target="_blank" rel="noopener">算法模式：滑动窗口&lt;/a> 介绍了滑动窗口的算法模式。本篇文章，介绍一种堆栈相关的算法模式：单调栈。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_单调栈">单调栈&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>所谓单调栈，就是在栈的基础上，增加了一个附加条件：栈内元素单调递增或者递减，如果不符合要求，则将元素出栈，直到符合条件为止。当需要给当前的元素，找右边/左边第一个比它大/小的位置时，就特别适合使用单调栈。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/monotonically-increasing-stack.svg" alt="单调递增栈" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 单调递增栈&lt;/div>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/monotonically-decreasing-stack.svg" alt="单调递减栈" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. 单调递减栈&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一般会用到 &lt;code>Deque&lt;/code> 的以下四个方法：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>stack.isEmpty()&lt;/code>：如果 &lt;code>deque&lt;/code> 不包含任何元素，则返回 &lt;code>true&lt;/code>，否则返回 &lt;code>false&lt;/code>。因为要栈顶元素在满足要求的时候要弹出，所以需要进行空栈判断。有些场景，可能栈一定不会空的时候，就不需要该方法进行空栈判断。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>stack.push(e)&lt;/code>：将元素 &lt;code>e&lt;/code> 入栈。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>stack.pop()&lt;/code>：将栈顶元素弹出，并返回当前弹出的栈顶元素。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>stack.peek()&lt;/code>：获取栈顶元素，不弹出。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="c1">// 定义一个单调栈&lt;/span>
&lt;span class="nc">Deque&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Integer&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">stack&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">LinkedList&lt;/span>&lt;span class="o">&amp;lt;&amp;gt;();&lt;/span>
&lt;span class="c1">// 第一个元素，直接添加&lt;/span>
&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">push&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">);&lt;/span> &lt;span class="c1">// 注意：栈内存的是数组下标&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 如果是单调递增栈，那么这里就是大于，即 nums[i] &amp;gt; nums[deque.peek()]&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">peek&lt;/span>&lt;span class="o">()])&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">push&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">peek&lt;/span>&lt;span class="o">()])&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">push&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="c1">// 此处除了入栈，在有些场景下，还有可能有其他操作&lt;/span>
 &lt;span class="c1">// ..............&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 循环比较，直到遇到当前元素小于栈顶的元素情况，跳出循环&lt;/span>
 &lt;span class="c1">// 单调递增栈，这里是小于，即nums[i] &amp;lt; nums[deque.peek()]&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(!&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isEmpty&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">peek&lt;/span>&lt;span class="o">()])&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">//主要逻辑&lt;/span>
 &lt;span class="c1">// ............&lt;/span>
 &lt;span class="c1">// ............&lt;/span>
 &lt;span class="c1">// 弹出栈顶元素&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">pop&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">push&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>记住这两句话：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>单调递增栈，利用波谷剔除栈中的波峰，留下波谷；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>单调递减栈，利用波峰剔除栈中的波谷，留下波峰。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_316_去除重复字母">LeetCode 316. 去除重复字母&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/remove-duplicate-letters/" target="_blank" rel="noopener">LeetCode - 316. 去除重复字母 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你一个字符串 &lt;code>s&lt;/code>，请你去除字符串中重复的字母，使得每个字母只出现一次。需保证 &lt;strong>返回结果的字典序最小&lt;/strong>（要求不能打乱其他字符的相对位置）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：s = &amp;#34;bcabc&amp;#34;
输出：&amp;#34;abc&amp;#34;&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：s = &amp;#34;cbacdcbc&amp;#34;
输出：&amp;#34;acdb&amp;#34;&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>提示：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>1 &amp;lt;= s.length &amp;lt;= 10&lt;sup>4&lt;/sup>&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>s&lt;/code> 由小写英文字母组成&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>算法模式：滑动窗口</title><link>https://www.diguage.com/post/algorithm-pattern-sliding-window/</link><pubDate>Fri, 21 Mar 2025 11:49:41 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-sliding-window/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-two-pointer/" target="_blank" rel="noopener">算法模式：双指针&lt;/a> 介绍了双指针的算法模式。本篇文章，介绍一种类似双指针的算法模式：滑动窗口。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_滑动窗口">滑动窗口&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>滑动窗口类型的题目经常是用来执行数组或是链表上某个区间（窗口）上的操作。比如找最长的全为1的子数组长度。滑动窗口一般从第一个元素开始，一直往右边一个一个元素挪动。当然了，根据题目要求，我们可能有固定窗口大小的情况，也有窗口的大小变化的情况。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/sliding-window.png" alt="滑动窗口" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 滑动窗口&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>滑动窗口大概思路如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="c1">// 向前滑动窗口&lt;/span>
&lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">right&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">array&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">lenght&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 扩大窗口，将元素放入窗口&lt;/span>
 &lt;span class="n">right&lt;/span>&lt;span class="o">++;&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">缩小窗口条件&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 处理窗口内的元素&lt;/span>
 &lt;span class="c1">// 缩小窗口，将元素丢出窗口&lt;/span>
 &lt;span class="n">left&lt;/span>&lt;span class="o">++;&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面是一些我们用来判断我们可能需要上滑动窗口策略的方法：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>问题的输入是一些线性结构：比如链表，数组，字符串之类的&lt;/p>
&lt;/li>
&lt;li>
&lt;p>让你去求最长/最短子字符串或是某些特定的长度要求&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_3_无重复字符的最长子串">LeetCode 3. 无重复字符的最长子串&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/longest-substring-without-repeating-characters/" target="_blank" rel="noopener">LeetCode - 3. 无重复字符的最长子串 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给定一个字符串 &lt;code>s&lt;/code> ，请你找出其中不含有重复字符的 &lt;strong>最长子串&lt;/strong> 的长度。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1:&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入: s = &amp;#34;abcabcbb&amp;#34;
输出: 3
解释: 因为无重复字符的最长子串是 &amp;#34;abc&amp;#34;，所以其长度为 3。&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2:&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入: s = &amp;#34;bbbbb&amp;#34;
输出: 1
解释: 因为无重复字符的最长子串是 &amp;#34;b&amp;#34;，所以其长度为 1。&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 3:&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入: s = &amp;#34;pwwkew&amp;#34;
输出: 3
解释: 因为无重复字符的最长子串是 &amp;#34;wke&amp;#34;，所以其长度为 3。
 请注意，你的答案必须是 子串 的长度，&amp;#34;pwke&amp;#34; 是一个子序列，不是子串。&lt;/pre>
&lt;/div></description></item><item><title>算法模式：双指针</title><link>https://www.diguage.com/post/algorithm-pattern-two-pointer/</link><pubDate>Thu, 06 Mar 2025 17:32:45 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-two-pointer/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-merge-intervals/" target="_blank" rel="noopener">算法模式：区间合并&lt;/a> 介绍了合并区间所用的算法模式。本篇文章，介绍一种即可以用在数组，又可以用在链表中的算法模式：双指针。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_双指针">双指针&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>双指针是这样的模式：两个指针朝着左右方向移动（双指针分为同向双指针和异向双指针），直到他们有一个或是两个都满足某种条件。双指针通常用在排好序的数组或是链表中寻找对子。比如，你需要去比较数组中每个元素和其他元素的关系时，你就需要用到双指针了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>需要双指针的原因是：如果你只用一个指针的话，你得来回跑才能在数组中找到你需要的答案。这一个指针来来回回的过程就很耗时和浪费空间了 — 这是考虑算法的复杂度分析的时候的重要概念。虽然 Brute F orce 一个指针的解法可能会奏效，但时间复杂度一般会是 \$O(n^2)\$。在很多情况下，双指针能帮助我们找到空间或是时间复杂度更低的解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>识别使用双指针的招数：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>一般来说，数组或是链表是排好序的，你得在里头找一些组合满足某种限制条件&lt;/p>
&lt;/li>
&lt;li>
&lt;p>这种组合可能是一对数，三个数，或是一个子数组&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_15_三数之和">LeetCode 15. 三数之和&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/3sum/" target="_blank" rel="noopener">LeetCode - 15. 三数之和 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你一个整数数组 &lt;code>nums&lt;/code> ，判断是否存在三元组 &lt;code>[nums[i], nums[j], nums[k]]&lt;/code> 满足 &lt;code>i != j&lt;/code>、&lt;code>i != k&lt;/code> 且 &lt;code>j != k&lt;/code> ，同时还满足 &lt;code>nums[i] + nums[j] + nums[k] == 0&lt;/code>。请你返回所有和为 &lt;code>0&lt;/code> 且不重复的三元组。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>注意：&lt;/strong>答案中不可以包含重复的三元组。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：nums = [-1,0,1,2,-1,-4]
输出：[[-1,-1,2],[-1,0,1]]
解释：
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意，输出的顺序和三元组的顺序并不重要。&lt;/pre>
&lt;/div></description></item><item><title>算法模式：快慢指针</title><link>https://www.diguage.com/post/algorithm-pattern-fast-slow-pointers/</link><pubDate>Wed, 05 Mar 2025 19:50:11 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-fast-slow-pointers/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-two-pointer/" target="_blank" rel="noopener">算法模式：双指针&lt;/a> 介绍了双指针模式。本篇文章，再介绍一种即可以用在数组，又可以用在链表中的算法模式：快慢指针。快慢指针，其实是双指针模式的一个变种。所以，两者在很多地方有相通之处。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_快慢指针">快慢指针&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>快慢指针模式，有一个非常出名的名字，叫龟兔赛跑。大家肯定都知道龟兔赛跑啦。但还是再解释一下快慢指针：这种算法的两个指针的在数组上（或是链表上，序列上）的移动速度不一样。还别说，这种方法在解决有环的链表和数组时特别有用。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>通过控制指针不同的移动速度（比如在环形链表上），这种算法证明了他们肯定会相遇的。快的一个指针肯定会追上慢的一个（可以想象成跑道上面跑得快的人套圈跑得慢的人）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>咋知道需要用快慢指针模式勒？&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>问题需要处理环上的问题，比如环形链表和环形数组&lt;/p>
&lt;/li>
&lt;li>
&lt;p>当你需要知道链表的长度或是某个特别位置的信息的时候&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>那啥时候用快慢指针而不是上面的双指针呢？&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>有些情形下，咱们不应该用双指针，比如我们在单链表上不能往回移动的时候。一个典型的需要用到快慢指针的模式的是当你需要去判断一个链表是否是回文的时候。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_141_环形链表">LeetCode 141. 环形链表&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/linked-list-cycle/" target="_blank" rel="noopener">LeetCode - 141. 环形链表 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你一个链表的头节点 &lt;code>head&lt;/code> ，判断链表中是否有环。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果链表中有某个节点，可以通过连续跟踪 &lt;code>next&lt;/code> 指针再次到达，则链表中存在环。为了表示给定链表中的环，评测系统内部使用整数 &lt;code>pos&lt;/code> 来表示链表尾连接到链表中的位置（索引从 0 开始）。&lt;strong>注意：&lt;code>pos&lt;/code> 不作为参数进行传递&lt;/strong>。仅仅是为了标识链表的实际情况。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;em>如果链表中存在环&lt;/em> ，则返回 &lt;code>true&lt;/code> 。 否则，返回 &lt;code>false&lt;/code> 。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/0141-00.png" alt="0141 00" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：head = [3,2,0,-4], pos = 1
输出：true
解释：链表中有一个环，其尾部连接到第二个节点。&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/0141-01.png" alt="0141 01" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：head = [1,2], pos = 0
输出：true
解释：链表中有一个环，其尾部连接到第一个节点。&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 3：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/0141-03.png" alt="0141 03" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：head = [1], pos = -1
输出：false
解释：链表中没有环。&lt;/pre>
&lt;/div></description></item><item><title>算法模式：区间合并</title><link>https://www.diguage.com/post/algorithm-pattern-merge-intervals/</link><pubDate>Wed, 05 Mar 2025 19:50:11 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-merge-intervals/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-modified-binary-search/" target="_blank" rel="noopener">算法模式：改进的二分查找&lt;/a> 介绍了二分查找以及相关变种。本篇文章，继续介绍数组相关的算法模式：区间合并。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_区间合并">区间合并&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>区间合并模式是一个用来处理有区间重叠的很高效的技术。在涉及到区间的很多问题中，通常咱们需要要么判断是否有重叠，要么合并区间，如果他们重叠的话。这个模式是这么起作用的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给两个区间，一个是 a，另外一个是 b。别小看就两个区间，他们之间的关系能跑出来6种情况。详细的就看图啦。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/0056-merge-intervals.png" alt="区间关系" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 区间关系&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>观察这六种排序，明显后三种排序是前三种排序的一个“变种”：对区间根据起点和终点进行排序，就是剩下前三种排序了。再对其进行合并就很简单了：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>没有重叠，则直接开启新区间。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>有重叠，起点和终点分别取最大值和最小值即可：由于区间已经排序，则相邻两个区间的起点是前面区间的起点，重点则是两个区间终点的最大值。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_56_合并区间">LeetCode 56. 合并区间&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/merge-intervals/" target="_blank" rel="noopener">LeetCode - 56. 合并区间 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>以数组 &lt;code>intervals&lt;/code> 表示若干个区间的集合，其中单个区间为 &lt;code>intervals[i] = [start&lt;sub>i&lt;/sub>, end&lt;sub>i&lt;/sub>]&lt;/code>。请你合并所有重叠的区间，并返回 &lt;em>一个不重叠的区间数组，该数组需恰好覆盖输入中的所有区间&lt;/em> 。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：intervals = [[1,3],[2,6],[8,10],[15,18]]
输出：[[1,6],[8,10],[15,18]]
解释：区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：intervals = [[1,4],[4,5]]
输出：[[1,5]]
解释：区间 [1,4] 和 [4,5] 可被视为重叠区间。&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>提示：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>1 &amp;lt;= intervals.length &amp;lt;= 10&lt;sup>4&lt;/sup>&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>intervals[i].length == 2&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>0 &amp;lt;= start&lt;sub>i&lt;/sub> &amp;lt;= end&lt;sub>i&lt;/sub> &amp;lt;= 10&lt;sup>4&lt;/sup>&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>算法模式：改进的二分查找</title><link>https://www.diguage.com/post/algorithm-pattern-modified-binary-search/</link><pubDate>Tue, 04 Mar 2025 19:47:34 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-modified-binary-search/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-prefix-sum/" target="_blank" rel="noopener">算法模式：前缀和&lt;/a> 介绍了前缀和的算法模式。本篇文章，继续介绍数组相关的算法模式：改进的二分查找。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_二分查找">二分查找&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>二分查找相比每一个学过计算机算法的小伙伴都了解，时间复杂度是： \$\log_2N\$，是一个非常高效的数组查找算法。当然，前提是数组必须有序。过程如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/binary-search.jpg" alt="二分查找" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 二分查找&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/binary-search/description/" target="_blank" rel="noopener">LeetCode 704. 二分查找&lt;/a> 就是一个标准的二分查找的算法题。代码如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 * @since 2024-09-14 19:52:26
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="nf">search&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">target&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">left&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">right&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">left&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="n">right&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">mid&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">left&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">right&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">left&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">mid&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">target&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">mid&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">mid&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">target&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">left&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mid&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">right&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mid&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>除了在排序数组中查找特定的值，二分查找还可以用于找边界和在旋转数组中查值。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_找边界leetcode_34_在排序数组中查找元素的第一个和最后一个位置">找边界：LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/" target="_blank" rel="noopener">LeetCode - 34. 在排序数组中查找元素的第一个和最后一个位置&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给你一个按照非递减顺序排列的整数数组 &lt;code>nums&lt;/code>，和一个目标值 &lt;code>target&lt;/code>。请你找出给定目标值在数组中的开始位置和结束位置。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果数组中不存在目标值 &lt;code>target&lt;/code>，返回 &lt;code>[-1, -1]&lt;/code>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>你必须设计并实现时间复杂度为 \$log_2n\$ 的算法解决此问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：nums = [5,7,7,8,8,10], target = 8
输出：[3,4]&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：nums = [5,7,7,8,8,10], target = 6
输出：[-1,-1]&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>示例 3：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="literalblock">
&lt;div class="content">
&lt;pre>输入：nums = [], target = 0
输出：[-1,-1]&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>提示：&lt;/strong>&lt;/p>
&lt;/div></description></item><item><title>算法模式：前缀和</title><link>https://www.diguage.com/post/algorithm-pattern-prefix-sum/</link><pubDate>Thu, 27 Feb 2025 19:47:34 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-prefix-sum/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/algorithm-pattern-difference-array/" target="_blank" rel="noopener">算法模式：差分数组&lt;/a>，本篇文章，继续介绍数组相关的算法模式：前缀和。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_前缀和">前缀和&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>前缀和可以简单理解为「数列的前 n 项的和」。具体过程如图所示：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/prefix-sum.svg" alt="前缀和" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 前缀和&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这是一种重要的预处理方式，也就是需要额外的空间并且提前计算好这些值。如果使用得当，能大大降低查询的时间复杂度。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_303_区域和检索_数组不可变">LeetCode 303. 区域和检索 - 数组不可变&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/range-sum-query-immutable/" target="_blank" rel="noopener">LeetCode - 303. 区域和检索 - 数组不可变 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给定一个整数数组 &lt;code>nums&lt;/code>，处理以下类型的多个查询:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>计算索引 &lt;code>left&lt;/code> 和 &lt;code>right&lt;/code> （包含 &lt;code>left&lt;/code> 和 &lt;code>right&lt;/code>）之间的
&lt;code>nums&lt;/code> 元素的 &lt;strong>和&lt;/strong> ，其中 &lt;code>left &amp;lt;= right&lt;/code>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>实现 &lt;code>NumArray&lt;/code> 类：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>NumArray(int[] nums)&lt;/code> 使用数组 &lt;code>nums&lt;/code> 初始化对象&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>int sumRange(int left, int right)&lt;/code> 返回数组 &lt;code>nums&lt;/code> 中索引 &lt;code>left&lt;/code> 和 &lt;code>right&lt;/code> 之间的元素的 &lt;strong>总和&lt;/strong>，包含 &lt;code>left&lt;/code> 和 &lt;code>right&lt;/code> 两点（也就是 &lt;code>nums[left] + nums[left + 1] + …​ + nums[right]&lt;/code> )&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>算法模式：差分数组</title><link>https://www.diguage.com/post/algorithm-pattern-difference-array/</link><pubDate>Wed, 26 Feb 2025 17:01:52 +0800</pubDate><guid>https://www.diguage.com/post/algorithm-pattern-difference-array/</guid><description>&lt;div class="paragraph">
&lt;p>Christopher Alexander 在 &lt;a href="https://book.douban.com/subject/1177968/" target="_blank" rel="noopener">《建筑的永恒之道》&lt;/a> 中说：“每一个模式描述了一个在我们周围不断重复发生的问题，以及该问题的解决方案的核心。这样，你就能一次又一次地使用该方案而不必做重复劳动。”受此影响，GoF 总结经验，写出了著名的 &lt;a href="https://book.douban.com/subject/34262305/" target="_blank" rel="noopener">《设计模式》&lt;/a>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在算法中，也有很多类似设计模式这样的解决方案。D瓜哥称其为“算法模式”。后面，慢慢写文章一一介绍一下。由浅及深，今天先来介绍最简单的一个模式：差分数组。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_差分数组">差分数组&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>差分数组：差分数组就是原始数组相邻元素之间的差。举例如下：&lt;/p>
&lt;/div>
&lt;table class="tableblock frame-all grid-all stretch">
&lt;colgroup>
&lt;col style="width: 14.2857%;"/>
&lt;col style="width: 14.2857%;"/>
&lt;col style="width: 14.2857%;"/>
&lt;col style="width: 14.2857%;"/>
&lt;col style="width: 14.2857%;"/>
&lt;col style="width: 14.2857%;"/>
&lt;col style="width: 14.2858%;"/>
&lt;/colgroup>
&lt;thead>
&lt;tr>
&lt;th class="tableblock halign-left valign-top">下标&lt;/th>
&lt;th class="tableblock halign-left valign-top">0&lt;/th>
&lt;th class="tableblock halign-left valign-top">1&lt;/th>
&lt;th class="tableblock halign-left valign-top">2&lt;/th>
&lt;th class="tableblock halign-left valign-top">3&lt;/th>
&lt;th class="tableblock halign-left valign-top">4&lt;/th>
&lt;th class="tableblock halign-left valign-top">5&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">原始数组&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">5&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">9&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">2&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">6&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">5&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">3&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">差分数组&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">5&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">4&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">-7&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">4&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">-1&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">-2&lt;/p>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="paragraph">
&lt;p>差分数组是从原始数组构造出来的一个辅助数组，表示相邻元素直接的差值。可用于解决需要对数组一个区间内同时做加减的操作。比如：随着公交站各个站台上下车，判断公交车是否超载。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_leetcode_370_区间加法">LeetCode 370. 区间加法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://leetcode.cn/problems/range-addition/" target="_blank" rel="noopener">LeetCode - 370. 区间加法 &lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>假设你有一个长度为 &lt;code>n&lt;/code> 的数组，初始情况下所有的数字均为 &lt;code>0&lt;/code>，你将会被给出 &lt;code>k&lt;/code> 个更新的操作。&lt;/p>
&lt;/div></description></item><item><title>玩转 Kubernetes（一）：离线安装 Kubernetes 2</title><link>https://www.diguage.com/post/play-with-kubernetes-01-install-kubernetes-offline-2/</link><pubDate>Mon, 10 Feb 2025 21:46:32 +0800</pubDate><guid>https://www.diguage.com/post/play-with-kubernetes-01-install-kubernetes-offline-2/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/play-with-kubernetes-01-install-kubernetes-offline/" target="_blank" rel="noopener">玩转 Kubernetes（一）：离线安装 Kubernetes 1&lt;/a> 中，D瓜哥基于 Kubespray 进行魔改的脚本搭建起来容器镜像仓库。但是，每次都魔改着实麻烦，所以，探索 Kubespray 原生支持才是更为委托的长久之计。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>经过多次探索，终于，可以几乎无需魔改就可以利用 Kubespray 原生支持进行 Kubernetes 的离线安装。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
以下是在 Mac 上的操作，在 Linux 等系统上操作类似。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_按照_python_依赖">按照 Python 依赖&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在 Mac 的虚拟机离线安装 Kubernetes，使用 Mac 当做容器镜像服务器和二进制安装文件下载服务器是一个非常好的选择。为此，需要在完成一些基本的操作。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>由于运行 Kubespray，需要一个 Python 环境以及相关依赖，所以，就需要先安装相关依赖。&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># 配置 Python 镜像&lt;/span>
pip config &lt;span class="nb">set &lt;/span>global.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple

&lt;span class="c"># 进入 Kubespray 的上层目录&lt;/span>
&lt;span class="nb">cd&lt;/span> /PATH/TO/kubespray/..

&lt;span class="c"># 按照 Python 相关依赖&lt;/span>
&lt;span class="nv">VENVDIR&lt;/span>&lt;span class="o">=&lt;/span>kubespray-venv
&lt;span class="nv">KUBESPRAYDIR&lt;/span>&lt;span class="o">=&lt;/span>kubespray
python3 &lt;span class="nt">-m&lt;/span> venv &lt;span class="nv">$VENVDIR&lt;/span>
&lt;span class="nb">source&lt;/span> &lt;span class="nv">$VENVDIR&lt;/span>/bin/activate
&lt;span class="nb">cd&lt;/span> &lt;span class="nv">$KUBESPRAYDIR&lt;/span>
pip &lt;span class="nb">install&lt;/span> &lt;span class="nt">-U&lt;/span> &lt;span class="nt">-r&lt;/span> requirements.txt&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_生成镜像列表及二进制文件列表">生成镜像列表及二进制文件列表&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>安装完相关依赖，就需要生成相关文件列表：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># 生成镜像列表以及相关二进制文件列表&lt;/span>
&lt;span class="nb">cd&lt;/span> /PATH/TO/kubespray/contrib/offline

./generate_list.sh&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
注意：大多数情况下，我们的安装目标是 Linux。所以，建议这步操作在 Linux 上完成，这样得到的下载文件列表是 Linux 格式的。在 Mac 上完成，那么部分文件的格式就是 Mac 的，不能用于 Linux 的安装。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div></description></item><item><title>玩转 Kubernetes（一）：离线安装 Kubernetes 1</title><link>https://www.diguage.com/post/play-with-kubernetes-01-install-kubernetes-offline/</link><pubDate>Tue, 14 Jan 2025 16:13:19 +0800</pubDate><guid>https://www.diguage.com/post/play-with-kubernetes-01-install-kubernetes-offline/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-3/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（三）：链路追踪&lt;/a> 等几篇文章中，D瓜哥分享了如何使用 Docker Compose 在本地搭建起来一套应用可观测性环境。感觉还不够好玩，毕竟正在在企业中，Kubernetes 已经是绝对的主流。要玩就玩最具挑战性的东西，玩最符合企业所需的技能和工具。所以，打算将上面那套简易玩具，按照企业级的要求，搬到 Kubernetes 上去。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果想玩 Kubernetes，首先面临的一个问题就是 Kubernetes 集群的搭建。本来是一个非常简单的事情，但是由于众所周知的原因，变得非常具有挑战性。经过各种探索和多次试验，发现一种“离线”安装方式，感觉是一个不错的方式。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>本方法是基于 &lt;a href="https://kubespray.io/#/" target="_blank" rel="noopener">Kubespray&lt;/a> 的一种安装办法，Kubespray 是由 Kubernetes SIG 小组来负责维护的一整套安装方式。既可以支持在裸机环境上安装，也支持云上环境安装。而且，只需要简单几行可以复制粘贴的命令，即可完成安装工作。非常适合入门玩耍使用。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>本安装方法所需的软件，D瓜哥都已经上传到 GitHub，如果需要下载，请移步： &lt;a href="https://github.com/diguage/k8s-packages/releases/tag/2.26.0" target="_blank" rel="noopener">Kubespray-2.26.0 安装包大全&lt;/a>。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_搭建服务器集群">搭建服务器集群&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>这里推荐使用 &lt;a href="https://www.vagrantup.com/" target="_blank" rel="noopener">Vagrant&lt;/a> 搭建集群。搭配 &lt;a href="https://www.virtualbox.org/" target="_blank" rel="noopener">VirtualBox&lt;/a>，只需要一个配置文件，就可以轻轻松松搭建一个 Linux 服务器集群。搭建集群的配置文件 &lt;code>Vagrantfile&lt;/code> 如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="ruby">&lt;span class="c1"># -*- mode: ruby -*-&lt;/span>
&lt;span class="c1"># vi: set ft=ruby :&lt;/span>
&lt;span class="c1"># @author D瓜哥 · &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>&lt;/span>

&lt;span class="c1"># All Vagrant configuration is done below. The &amp;#34;2&amp;#34; in Vagrant.configure&lt;/span>
&lt;span class="c1"># configures the configuration version (we support older styles for&lt;/span>
&lt;span class="c1"># backwards compatibility). Please don&amp;#39;t change it unless you know what&lt;/span>
&lt;span class="c1"># you&amp;#39;re doing.&lt;/span>

&lt;span class="no">Vagrant&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">configure&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">|&lt;/span>
 &lt;span class="c1"># The most common configuration options are documented and commented below.&lt;/span>
 &lt;span class="c1"># For a complete reference, please see the online documentation at&lt;/span>
 &lt;span class="c1"># &lt;a href="https://docs.vagrantup.com" target="_blank">https://docs.vagrantup.com&lt;/a>&lt;/span>

 &lt;span class="c1"># 三节点集群&lt;/span>
 &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">..&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">each&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">|&lt;/span>
 &lt;span class="n">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">define&lt;/span> &lt;span class="s2">&amp;#34;node&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">node&lt;/span>&lt;span class="o">|&lt;/span>

 &lt;span class="c1"># Every Vagrant development environment requires a box. You can search for&lt;/span>
 &lt;span class="c1"># boxes at &lt;a href="https://vagrantcloud.com/search" target="_blank">https://vagrantcloud.com/search&lt;/a>&lt;/span>
 &lt;span class="c1"># &lt;a href="https://portal.cloud.hashicorp.com/vagrant/discover/bento/ubuntu-24.04" target="_blank">https://portal.cloud.hashicorp.com/vagrant/discover/bento/ubuntu-24.04&lt;/a>&lt;/span>
 &lt;span class="c1"># &lt;a href="https://portal.cloud.hashicorp.com/vagrant/discover/alvistack/ubuntu-24.04" target="_blank">https://portal.cloud.hashicorp.com/vagrant/discover/alvistack/ubuntu-24.04&lt;/a>&lt;/span>
 &lt;span class="n">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">box&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;alvistack/ubuntu-24.04&amp;#34;&lt;/span>
 &lt;span class="n">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">box_version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;20250210.0.0&amp;#34;&lt;/span>

 &lt;span class="c1"># 设置虚拟机的主机名&lt;/span>
 &lt;span class="n">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">hostname&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;node&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>

 &lt;span class="n">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">boot_timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">600&lt;/span>

 &lt;span class="c1"># Disable automatic box update checking. If you disable this, then&lt;/span>
 &lt;span class="c1"># boxes will only be checked for updates when the user runs&lt;/span>
 &lt;span class="c1"># `vagrant box outdated`. This is not recommended.&lt;/span>
 &lt;span class="c1"># config.vm.box_check_update = false&lt;/span>

 &lt;span class="c1"># Create a forwarded port mapping which allows access to a specific port&lt;/span>
 &lt;span class="c1"># within the machine from a port on the host machine. In the example below,&lt;/span>
 &lt;span class="c1"># accessing &amp;#34;localhost:8080&amp;#34; will access port 80 on the guest machine.&lt;/span>
 &lt;span class="c1"># NOTE: This will enable public access to the opened port&lt;/span>
 &lt;span class="c1"># config.vm.network &amp;#34;forwarded_port&amp;#34;, guest: 80, host: 8080&lt;/span>

 &lt;span class="c1"># Create a forwarded port mapping which allows access to a specific port&lt;/span>
 &lt;span class="c1"># within the machine from a port on the host machine and only allow access&lt;/span>
 &lt;span class="c1"># via 127.0.0.1 to disable public access&lt;/span>
 &lt;span class="c1"># config.vm.network &amp;#34;forwarded_port&amp;#34;, guest: 80, host: 8080, host_ip: &amp;#34;127.0.0.1&amp;#34;&lt;/span>

 &lt;span class="c1"># Create a private network, which allows host-only access to the machine&lt;/span>
 &lt;span class="c1"># using a specific IP.&lt;/span>
 &lt;span class="c1"># 设置虚拟机的IP&lt;/span>
 &lt;span class="n">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">network&lt;/span> &lt;span class="s2">&amp;#34;private_network&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">ip: &lt;/span>&lt;span class="s2">&amp;#34;10.0.2.&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">auto_config: &lt;/span>&lt;span class="kp">true&lt;/span>

 &lt;span class="c1"># Create a public network, which generally matched to bridged network.&lt;/span>
 &lt;span class="c1"># Bridged networks make the machine appear as another physical device on&lt;/span>
 &lt;span class="c1"># your network.&lt;/span>
 &lt;span class="c1"># config.vm.network &amp;#34;public_network&amp;#34;&lt;/span>

 &lt;span class="c1"># Share an additional folder to the guest VM. The first argument is&lt;/span>
 &lt;span class="c1"># the path on the host to the actual folder. The second argument is&lt;/span>
 &lt;span class="c1"># the path on the guest to mount the folder. And the optional third&lt;/span>
 &lt;span class="c1"># argument is a set of non-required options.&lt;/span>
 &lt;span class="c1"># 设置主机与虚拟机的共享目录，根据需要开启&lt;/span>
 &lt;span class="n">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">synced_folder&lt;/span> &lt;span class="s2">&amp;#34;/path/to/&lt;/span>&lt;span class="si">#{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;/data&amp;#34;&lt;/span>


 &lt;span class="c1"># Disable the default share of the current code directory. Doing this&lt;/span>
 &lt;span class="c1"># provides improved isolation between the vagrant box and your host&lt;/span>
 &lt;span class="c1"># by making sure your Vagrantfile isn&amp;#39;t accessible to the vagrant box.&lt;/span>
 &lt;span class="c1"># If you use this you may want to enable additional shared subfolders as&lt;/span>
 &lt;span class="c1"># shown above.&lt;/span>
 &lt;span class="c1"># config.vm.synced_folder &amp;#34;.&amp;#34;, &amp;#34;/vagrant&amp;#34;, disabled: true&lt;/span>

 &lt;span class="c1"># Provider-specific configuration so you can fine-tune various&lt;/span>
 &lt;span class="c1"># backing providers for Vagrant. These expose provider-specific options.&lt;/span>
 &lt;span class="c1"># Example for VirtualBox:&lt;/span>

 &lt;span class="n">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">vm&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">provider&lt;/span> &lt;span class="s2">&amp;#34;virtualbox&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span> &lt;span class="o">|&lt;/span>&lt;span class="n">vb&lt;/span>&lt;span class="o">|&lt;/span>
 &lt;span class="c1"># 设置虚拟机的名称&lt;/span>
 &lt;span class="c1"># vb.name = &amp;#34;node#{i}&amp;#34;&lt;/span>

 &lt;span class="c1"># if node.vm.hostname == &amp;#34;node1&amp;#34;&lt;/span>
 &lt;span class="c1"># # Display the VirtualBox GUI when booting the machine&lt;/span>
 &lt;span class="c1"># vb.gui = true&lt;/span>
 &lt;span class="c1"># end&lt;/span>

 &lt;span class="c1"># Customize the amount of memory on the VM:&lt;/span>
 &lt;span class="n">vb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">memory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;6144&amp;#34;&lt;/span>

 &lt;span class="c1"># 设置虚拟机的CPU个数&lt;/span>
 &lt;span class="n">vb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">cpus&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>
 &lt;span class="k">end&lt;/span>

 &lt;span class="c1"># View the documentation for the provider you are using for more&lt;/span>
 &lt;span class="c1"># information on available options.&lt;/span>

 &lt;span class="c1"># Enable provisioning with a shell script. Additional provisioners such as&lt;/span>
 &lt;span class="c1"># Ansible, Chef, Docker, Puppet and Salt are also available. Please see the&lt;/span>
 &lt;span class="c1"># documentation for more information about their specific syntax and use.&lt;/span>
 &lt;span class="c1"># config.vm.provision &amp;#34;shell&amp;#34;, inline: &amp;lt;&amp;lt;-SHELL&lt;/span>
 &lt;span class="c1"># sudo yum makecache --refresh&lt;/span>
 &lt;span class="c1"># sudo yum install -y tcpdump&lt;/span>
 &lt;span class="c1"># sudo yum install -y nc&lt;/span>
 &lt;span class="c1"># sudo yum install -y net-tools&lt;/span>
 &lt;span class="c1"># SHELL&lt;/span>
 &lt;span class="k">end&lt;/span>
 &lt;span class="k">end&lt;/span>
&lt;span class="k">end&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>killercoda CKA：Troubleshooting - 3</title><link>https://www.diguage.com/post/killercoda-cka-troubleshooting-3/</link><pubDate>Thu, 26 Dec 2024 19:54:28 +0800</pubDate><guid>https://www.diguage.com/post/killercoda-cka-troubleshooting-3/</guid><description>&lt;div class="sect1">
&lt;h2 id="_troubleshooting_service_account_role_role_binding_issue">1. Troubleshooting - Service account, role, role binding Issue&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://killercoda.com/sachin/course/CKA/sa-cr-crb-issue" target="_blank" rel="noopener">Troubleshooting - Service account, role, role binding Issue&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>You have a service account named &lt;code>dev-sa&lt;/code>, a Role named &lt;code>dev-role-cka&lt;/code>, and a RoleBinding named &lt;code>dev-role-binding-cka&lt;/code>. we are trying to &lt;code>create&lt;/code> &lt;code>list&lt;/code> and &lt;code>get&lt;/code> the &lt;code>pods&lt;/code> and &lt;code>services&lt;/code>. However, using &lt;code>dev-sa&lt;/code> service account is not able to perform these operations. fix this issue.&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl get serviceaccounts dev-sa &lt;span class="nt">-o&lt;/span> yaml
apiVersion: v1
kind: ServiceAccount
metadata:
 creationTimestamp: &lt;span class="s2">&amp;#34;2025-01-22T09:48:06Z&amp;#34;&lt;/span>
 name: dev-sa
 namespace: default
 resourceVersion: &lt;span class="s2">&amp;#34;2270&amp;#34;&lt;/span>
 uid: 48b68f34-8c19-4477-9631-4f368f6ecc66

&lt;span class="nv">$ &lt;/span>kubectl get role dev-role-cka
NAME CREATED AT
dev-role-cka 2025-01-22T09:48:06Z

&lt;span class="nv">$ &lt;/span>kubectl get role dev-role-cka &lt;span class="nt">-o&lt;/span> yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
 creationTimestamp: &lt;span class="s2">&amp;#34;2025-01-22T09:48:06Z&amp;#34;&lt;/span>
 name: dev-role-cka
 namespace: default
 resourceVersion: &lt;span class="s2">&amp;#34;2271&amp;#34;&lt;/span>
 uid: 7a011481-8edd-4417-a1b8-8d15290d3e9f
rules:
- apiGroups:
 - &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
 resources:
 - secrets
 verbs:
 - get

&lt;span class="nv">$ &lt;/span>kubectl get rolebindings dev-role-binding-cka &lt;span class="nt">-o&lt;/span> yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
 creationTimestamp: &lt;span class="s2">&amp;#34;2025-01-22T09:48:07Z&amp;#34;&lt;/span>
 name: dev-role-binding-cka
 namespace: default
 resourceVersion: &lt;span class="s2">&amp;#34;2272&amp;#34;&lt;/span>
 uid: 888af489-86b6-4d38-a723-a8ff13656d2b
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: Role
 name: dev-role-cka
subjects:
- kind: ServiceAccount
 name: dev-sa
 namespace: default

&lt;span class="c"># 将 Role 删掉，重建即可&lt;/span>
&lt;span class="nv">$ &lt;/span>kubectl delete role dev-role-cka &lt;span class="nt">--force&lt;/span> &lt;span class="nt">--grace-period&lt;/span> 0
Warning: Immediate deletion does not &lt;span class="nb">wait &lt;/span>&lt;span class="k">for &lt;/span>confirmation that the running resource has been terminated. The resource may &lt;span class="k">continue &lt;/span>to run on the cluster indefinitely.
role.rbac.authorization.k8s.io &lt;span class="s2">&amp;#34;dev-role-cka&amp;#34;&lt;/span> force deleted

&lt;span class="nv">$ &lt;/span>kubectl create role dev-role-cka &lt;span class="nt">--resource&lt;/span>&lt;span class="o">=&lt;/span>pods,services &lt;span class="nt">--verb&lt;/span>&lt;span class="o">=&lt;/span>create,list,get
role.rbac.authorization.k8s.io/dev-role-cka created

&lt;span class="nv">$ &lt;/span>kubectl get role dev-role-cka &lt;span class="nt">-o&lt;/span> yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
 creationTimestamp: &lt;span class="s2">&amp;#34;2025-01-22T09:49:46Z&amp;#34;&lt;/span>
 name: dev-role-cka
 namespace: default
 resourceVersion: &lt;span class="s2">&amp;#34;2414&amp;#34;&lt;/span>
 uid: b3d7fc62-f029-4f4b-88a5-99ee9840af05
rules:
- apiGroups:
 - &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
 resources:
 - pods
 - services
 verbs:
 - create
 - list
 - get&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>killercoda CKA：Troubleshooting - 2</title><link>https://www.diguage.com/post/killercoda-cka-troubleshooting-2/</link><pubDate>Tue, 24 Dec 2024 19:54:28 +0800</pubDate><guid>https://www.diguage.com/post/killercoda-cka-troubleshooting-2/</guid><description>&lt;div class="sect1">
&lt;h2 id="_troubleshooting_persistent_volume_persistent_volume_claim_issue">1. Troubleshooting - Persistent Volume, Persistent Volume Claim - Issue&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://killercoda.com/sachin/course/CKA/pv-pvc-issue" target="_blank" rel="noopener">Troubleshooting - Persistent Volume, Persistent Volume Claim - Issue&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>&lt;code>my-pvc&lt;/code> Persistent Volume Claim is stuck in a Pending state, fix this issue, make sure it is in Bound state&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl get pvc my-pvc &lt;span class="nt">-o&lt;/span> wide
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE VOLUMEMODE
my-pvc Pending standard &amp;lt;&lt;span class="nb">unset&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> 38s Filesystem

&lt;span class="nv">$ &lt;/span>kubectl get pv my-pv &lt;span class="nt">-o&lt;/span> wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE VOLUMEMODE
my-pv 100Mi RWO Retain Available standard &amp;lt;&lt;span class="nb">unset&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> 51s Filesystem

&lt;span class="nv">$ &lt;/span>kubectl get pvc my-pvc &lt;span class="nt">-o&lt;/span> yaml | &lt;span class="nb">tee &lt;/span>pv.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 annotations:
 kubectl.kubernetes.io/last-applied-configuration: |
 &lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;apiVersion&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;v1&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;kind&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;PersistentVolumeClaim&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;metadata&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;annotations&amp;#34;&lt;/span>:&lt;span class="o">{}&lt;/span>,&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;my-pvc&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;namespace&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>,&lt;span class="s2">&amp;#34;spec&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;accessModes&amp;#34;&lt;/span>:[&lt;span class="s2">&amp;#34;ReadWriteMany&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>,&lt;span class="s2">&amp;#34;resources&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;requests&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;storage&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;150Mi&amp;#34;&lt;/span>&lt;span class="o">}}&lt;/span>,&lt;span class="s2">&amp;#34;storageClassName&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;standard&amp;#34;&lt;/span>&lt;span class="o">}}&lt;/span>
 creationTimestamp: &lt;span class="s2">&amp;#34;2025-01-20T13:08:41Z&amp;#34;&lt;/span>
 finalizers:
 - kubernetes.io/pvc-protection
 name: my-pvc
 namespace: default
 resourceVersion: &lt;span class="s2">&amp;#34;2002&amp;#34;&lt;/span>
 uid: a4c6c044-4118-47a4-97b9-ceb69fac3bc2
spec:
 accessModes:
 - ReadWriteMany
 resources:
 requests:
 storage: 150Mi
 storageClassName: standard
 volumeMode: Filesystem
status:
 phase: Pending

&lt;span class="nv">$ &lt;/span>kubectl get pv my-pv &lt;span class="nt">-o&lt;/span> yaml
apiVersion: v1
kind: PersistentVolume
metadata:
 annotations:
 kubectl.kubernetes.io/last-applied-configuration: |
 &lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;apiVersion&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;v1&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;kind&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;PersistentVolume&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;metadata&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;annotations&amp;#34;&lt;/span>:&lt;span class="o">{}&lt;/span>,&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;my-pv&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>,&lt;span class="s2">&amp;#34;spec&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;accessModes&amp;#34;&lt;/span>:[&lt;span class="s2">&amp;#34;ReadWriteOnce&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>,&lt;span class="s2">&amp;#34;capacity&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;storage&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;100Mi&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>,&lt;span class="s2">&amp;#34;hostPath&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;/mnt/data&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>,&lt;span class="s2">&amp;#34;persistentVolumeReclaimPolicy&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;Retain&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;storageClassName&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;standard&amp;#34;&lt;/span>&lt;span class="o">}}&lt;/span>
 creationTimestamp: &lt;span class="s2">&amp;#34;2025-01-20T13:08:41Z&amp;#34;&lt;/span>
 finalizers:
 - kubernetes.io/pv-protection
 name: my-pv
 resourceVersion: &lt;span class="s2">&amp;#34;2003&amp;#34;&lt;/span>
 uid: 85a371c4-0931-4b57-87ea-fc1fceb674c1
spec:
 accessModes:
 - ReadWriteOnce
 capacity:
 storage: 100Mi
 hostPath:
 path: /mnt/data
 &lt;span class="nb">type&lt;/span>: &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
 persistentVolumeReclaimPolicy: Retain
 storageClassName: standard
 volumeMode: Filesystem

&lt;span class="nv">$ &lt;/span>vim pv.yaml
&lt;span class="c"># 两个问题：&lt;/span>
&lt;span class="c"># 1、 PVC 和 PV 的 accessModes 不一致，改为 ReadWriteOnce即可&lt;/span>
&lt;span class="c"># 2、 PVC 的存储是 150Mi，而 PV 只有 100Mi，也改为 100Mi 即可。&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl delete &lt;span class="nt">-f&lt;/span> pv.yaml &lt;span class="nt">--force&lt;/span> &lt;span class="nt">--grace-period&lt;/span> 0
Warning: Immediate deletion does not &lt;span class="nb">wait &lt;/span>&lt;span class="k">for &lt;/span>confirmation that the running resource has been terminated. The resource may &lt;span class="k">continue &lt;/span>to run on the cluster indefinitely.
persistentvolumeclaim &lt;span class="s2">&amp;#34;my-pvc&amp;#34;&lt;/span> force deleted

&lt;span class="nv">$ &lt;/span>kubectl apply &lt;span class="nt">-f&lt;/span> pv.yaml
persistentvolumeclaim/my-pvc created

&lt;span class="nv">$ &lt;/span>kubectl get pvc my-pvc &lt;span class="nt">-o&lt;/span> wide
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE VOLUMEMODE
my-pvc Bound my-pv 100Mi RWO standard &amp;lt;&lt;span class="nb">unset&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> 10s Filesystem&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>killercoda CKA：Troubleshooting - 1</title><link>https://www.diguage.com/post/killercoda-cka-troubleshooting-1/</link><pubDate>Sun, 22 Dec 2024 19:54:28 +0800</pubDate><guid>https://www.diguage.com/post/killercoda-cka-troubleshooting-1/</guid><description>&lt;div class="sect1">
&lt;h2 id="_troubleshooting_pod_issue">1. Troubleshooting - Pod Issue&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://killercoda.com/sachin/course/CKA/pod-issue" target="_blank" rel="noopener">Troubleshooting - Pod Issue&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>&lt;code>hello-kubernetes&lt;/code> pod not running, fix that issue&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl get pod
NAME READY STATUS RESTARTS AGE
hello-kubernetes 0/1 RunContainerError 2 &lt;span class="o">(&lt;/span>6s ago&lt;span class="o">)&lt;/span> 29s

&lt;span class="nv">$ &lt;/span>kubectl describe pod hello-kubernetes
Name: hello-kubernetes
Namespace: default
Priority: 0
Service Account: default
Node: node01/172.30.2.2
Start Time: Mon, 20 Jan 2025 07:21:57 +0000
Labels: &amp;lt;none&amp;gt;
Annotations: cni.projectcalico.org/containerID: 2e010161283b56bfd70d604c31ece3dc3189882f1e24c2ea57647dbaec3b2bdb
 cni.projectcalico.org/podIP: 192.168.1.4/32
 cni.projectcalico.org/podIPs: 192.168.1.4/32
Status: Running
IP: 192.168.1.4
IPs:
 IP: 192.168.1.4
Containers:
 echo-container:
 Container ID: containerd://4f01851fcb908cd7bd1031a1726b8b75873d69fb246a5eebdd5c3dc003be7c19
 Image: redis
 Image ID: docker.io/library/redis@sha256:ca65ea36ae16e709b0f1c7534bc7e5b5ac2e5bb3c97236e4fec00e3625eb678d
 Port: &amp;lt;none&amp;gt;
 Host Port: &amp;lt;none&amp;gt;
 Command:
 shell
 &lt;span class="nt">-c&lt;/span>
 &lt;span class="k">while &lt;/span>&lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">do &lt;/span>&lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;Hello Kubernetes&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nb">sleep &lt;/span>5&lt;span class="p">;&lt;/span> &lt;span class="k">done
 &lt;/span>State: Waiting
 Reason: CrashLoopBackOff
 Last State: Terminated
 Reason: StartError
 Message: failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: &lt;span class="nb">exec&lt;/span>: &lt;span class="s2">&amp;#34;shell&amp;#34;&lt;/span>: executable file not found &lt;span class="k">in&lt;/span> &lt;span class="nv">$PATH&lt;/span>: unknown
 Exit Code: 128
 Started: Thu, 01 Jan 1970 00:00:00 +0000
 Finished: Mon, 20 Jan 2025 07:22:20 +0000
 Ready: False
 Restart Count: 2
 Environment: &amp;lt;none&amp;gt;
 Mounts:
 /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-xk5qj &lt;span class="o">(&lt;/span>ro&lt;span class="o">)&lt;/span>
Conditions:
 Type Status
 PodReadyToStartContainers True
 Initialized True
 Ready False
 ContainersReady False
 PodScheduled True
Volumes:
 kube-api-access-xk5qj:
 Type: Projected &lt;span class="o">(&lt;/span>a volume that contains injected data from multiple sources&lt;span class="o">)&lt;/span>
 TokenExpirationSeconds: 3607
 ConfigMapName: kube-root-ca.crt
 ConfigMapOptional: &amp;lt;nil&amp;gt;
 DownwardAPI: &lt;span class="nb">true
&lt;/span>QoS Class: BestEffort
Node-Selectors: &amp;lt;none&amp;gt;
Tolerations: node.kubernetes.io/not-ready:NoExecute &lt;span class="nv">op&lt;/span>&lt;span class="o">=&lt;/span>Exists &lt;span class="k">for &lt;/span>300s
 node.kubernetes.io/unreachable:NoExecute &lt;span class="nv">op&lt;/span>&lt;span class="o">=&lt;/span>Exists &lt;span class="k">for &lt;/span>300s
Events:
 Type Reason Age From Message
 &lt;span class="nt">----&lt;/span> &lt;span class="nt">------&lt;/span> &lt;span class="nt">----&lt;/span> &lt;span class="nt">----&lt;/span> &lt;span class="nt">-------&lt;/span>
 Normal Scheduled 41s default-scheduler Successfully assigned default/hello-kubernetes to node01
 Normal Pulled 35s kubelet Successfully pulled image &lt;span class="s2">&amp;#34;redis&amp;#34;&lt;/span> &lt;span class="k">in &lt;/span>5.57s &lt;span class="o">(&lt;/span>5.57s including waiting&lt;span class="o">)&lt;/span>&lt;span class="nb">.&lt;/span> Image size: 45006722 bytes.
 Normal Pulled 33s kubelet Successfully pulled image &lt;span class="s2">&amp;#34;redis&amp;#34;&lt;/span> &lt;span class="k">in &lt;/span>422ms &lt;span class="o">(&lt;/span>422ms including waiting&lt;span class="o">)&lt;/span>&lt;span class="nb">.&lt;/span> Image size: 45006722 bytes.
 Normal Pulling 19s &lt;span class="o">(&lt;/span>x3 over 40s&lt;span class="o">)&lt;/span> kubelet Pulling image &lt;span class="s2">&amp;#34;redis&amp;#34;&lt;/span>
 Normal Created 18s &lt;span class="o">(&lt;/span>x3 over 35s&lt;span class="o">)&lt;/span> kubelet Created container echo-container
 Warning Failed 18s &lt;span class="o">(&lt;/span>x3 over 34s&lt;span class="o">)&lt;/span> kubelet Error: failed to create containerd task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: &lt;span class="nb">exec&lt;/span>: &lt;span class="s2">&amp;#34;shell&amp;#34;&lt;/span>: executable file not found &lt;span class="k">in&lt;/span> &lt;span class="nv">$PATH&lt;/span>: unknown
 Normal Pulled 18s kubelet Successfully pulled image &lt;span class="s2">&amp;#34;redis&amp;#34;&lt;/span> &lt;span class="k">in &lt;/span>467ms &lt;span class="o">(&lt;/span>467ms including waiting&lt;span class="o">)&lt;/span>&lt;span class="nb">.&lt;/span> Image size: 45006722 bytes.
 Warning BackOff 6s &lt;span class="o">(&lt;/span>x4 over 32s&lt;span class="o">)&lt;/span> kubelet Back-off restarting failed container echo-container &lt;span class="k">in &lt;/span>pod hello-kubernetes_default&lt;span class="o">(&lt;/span>5a459cd4-866a-4e57-8d44-ae83156e1e0b&lt;span class="o">)&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl get pod hello-kubernetes &lt;span class="nt">-o&lt;/span> yaml | &lt;span class="nb">tee &lt;/span>pod.yaml
apiVersion: v1
kind: Pod
metadata:
 annotations:
 cni.projectcalico.org/containerID: 2e010161283b56bfd70d604c31ece3dc3189882f1e24c2ea57647dbaec3b2bdb
 cni.projectcalico.org/podIP: 192.168.1.4/32
 cni.projectcalico.org/podIPs: 192.168.1.4/32
 kubectl.kubernetes.io/last-applied-configuration: |
 &lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;apiVersion&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;v1&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;kind&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;Pod&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;metadata&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;annotations&amp;#34;&lt;/span>:&lt;span class="o">{}&lt;/span>,&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;hello-kubernetes&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;namespace&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>,&lt;span class="s2">&amp;#34;spec&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;containers&amp;#34;&lt;/span>:[&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;command&amp;#34;&lt;/span>:[&lt;span class="s2">&amp;#34;shell&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;-c&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;while true; do echo &amp;#39;Hello Kubernetes&amp;#39;; sleep 5; done&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>,&lt;span class="s2">&amp;#34;image&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;redis&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;echo-container&amp;#34;&lt;/span>&lt;span class="o">}]}}&lt;/span>
 creationTimestamp: &lt;span class="s2">&amp;#34;2025-01-20T07:21:57Z&amp;#34;&lt;/span>
 name: hello-kubernetes
 namespace: default
 resourceVersion: &lt;span class="s2">&amp;#34;2157&amp;#34;&lt;/span>
 uid: 5a459cd4-866a-4e57-8d44-ae83156e1e0b
spec:
 containers:
 - &lt;span class="nb">command&lt;/span>:
 - shell
 - &lt;span class="nt">-c&lt;/span>
 - &lt;span class="k">while &lt;/span>&lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">do &lt;/span>&lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;Hello Kubernetes&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nb">sleep &lt;/span>5&lt;span class="p">;&lt;/span> &lt;span class="k">done
 &lt;/span>image: redis
 imagePullPolicy: Always
 name: echo-container
 resources: &lt;span class="o">{}&lt;/span>
 terminationMessagePath: /dev/termination-log
 terminationMessagePolicy: File
 volumeMounts:
 - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
 name: kube-api-access-xk5qj
 readOnly: &lt;span class="nb">true
 &lt;/span>dnsPolicy: ClusterFirst
 enableServiceLinks: &lt;span class="nb">true
 &lt;/span>nodeName: node01
 preemptionPolicy: PreemptLowerPriority
 priority: 0
 restartPolicy: Always
 schedulerName: default-scheduler
 securityContext: &lt;span class="o">{}&lt;/span>
 serviceAccount: default
 serviceAccountName: default
 terminationGracePeriodSeconds: 30
 tolerations:
 - effect: NoExecute
 key: node.kubernetes.io/not-ready
 operator: Exists
 tolerationSeconds: 300
 - effect: NoExecute
 key: node.kubernetes.io/unreachable
 operator: Exists
 tolerationSeconds: 300
 volumes:
 - name: kube-api-access-xk5qj
 projected:
 defaultMode: 420
 sources:
 - serviceAccountToken:
 expirationSeconds: 3607
 path: token
 - configMap:
 items:
 - key: ca.crt
 path: ca.crt
 name: kube-root-ca.crt
 - downwardAPI:
 items:
 - fieldRef:
 apiVersion: v1
 fieldPath: metadata.namespace
 path: namespace
&lt;span class="c"># 省略了 status 字段&lt;/span>

&lt;span class="nv">$ &lt;/span>vim pod.yaml
&lt;span class="c"># 根据提示，没有 shell，将 shell 修改为 sh 即可。&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl replace &lt;span class="nt">-f&lt;/span> pod.yaml
Error from server &lt;span class="o">(&lt;/span>Conflict&lt;span class="o">)&lt;/span>: error when replacing &lt;span class="s2">&amp;#34;pod.yaml&amp;#34;&lt;/span>: Operation cannot be fulfilled on pods &lt;span class="s2">&amp;#34;hello-kubernetes&amp;#34;&lt;/span>: the object has been modified&lt;span class="p">;&lt;/span> please apply your changes to the latest version and try again

&lt;span class="c"># 不能替换，就直接删除，再重建&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl delete &lt;span class="nt">-f&lt;/span> pod.yaml &lt;span class="nt">--force&lt;/span> &lt;span class="nt">--grace-period&lt;/span> 0
Warning: Immediate deletion does not &lt;span class="nb">wait &lt;/span>&lt;span class="k">for &lt;/span>confirmation that the running resource has been terminated. The resource may &lt;span class="k">continue &lt;/span>to run on the cluster indefinitely.
pod &lt;span class="s2">&amp;#34;hello-kubernetes&amp;#34;&lt;/span> force deleted

&lt;span class="nv">$ &lt;/span>kubectl apply &lt;span class="nt">-f&lt;/span> pod.yaml
pod/hello-kubernetes created

&lt;span class="nv">$ &lt;/span>kubectl get pod
NAME READY STATUS RESTARTS AGE
hello-kubernetes 1/1 Running 0 5s&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>killercoda CKA：Workloads &amp; Scheduling</title><link>https://www.diguage.com/post/killercoda-cka-workloads-scheduling/</link><pubDate>Fri, 20 Dec 2024 19:54:28 +0800</pubDate><guid>https://www.diguage.com/post/killercoda-cka-workloads-scheduling/</guid><description>&lt;div class="sect1">
&lt;h2 id="_workloads_scheduling_pod">1. Workloads &amp;amp; Scheduling - Pod&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://killercoda.com/sachin/course/CKA/pod" target="_blank" rel="noopener">Workloads &amp;amp; Scheduling - Pod&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>Fresher deployed a pod named &lt;code>my-pod&lt;/code>. However, while specifying the resource limits, they mistakenly given &lt;code>100Mi&lt;/code> storage limit instead of &lt;code>50Mi&lt;/code>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>node doesn’t have sufficient resources, So change it to &lt;code>50Mi&lt;/code> only.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl get pod my-pod &lt;span class="nt">-o&lt;/span> yaml | &lt;span class="nb">tee &lt;/span>pod.yaml
apiVersion: v1
kind: Pod
metadata:
 annotations:
 cni.projectcalico.org/containerID: 8414bfefda21fa6ca74ef8d499c92a22ae6cc0dbb6d0bc4d82eb0129a795d75d
 cni.projectcalico.org/podIP: 192.168.1.4/32
 cni.projectcalico.org/podIPs: 192.168.1.4/32
 kubectl.kubernetes.io/last-applied-configuration: |
 &lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;apiVersion&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;v1&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;kind&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;Pod&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;metadata&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;annotations&amp;#34;&lt;/span>:&lt;span class="o">{}&lt;/span>,&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;my-pod&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;namespace&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>,&lt;span class="s2">&amp;#34;spec&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;containers&amp;#34;&lt;/span>:[&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;image&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;nginx:latest&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;my-container&amp;#34;&lt;/span>,&lt;span class="s2">&amp;#34;resources&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;limits&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;memory&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;100Mi&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>,&lt;span class="s2">&amp;#34;requests&amp;#34;&lt;/span>:&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;memory&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;50Mi&amp;#34;&lt;/span>&lt;span class="o">}}}]}}&lt;/span>
 creationTimestamp: &lt;span class="s2">&amp;#34;2025-01-14T07:53:50Z&amp;#34;&lt;/span>
 name: my-pod
 namespace: default
 resourceVersion: &lt;span class="s2">&amp;#34;2026&amp;#34;&lt;/span>
 uid: fcf1e97e-cec0-45b0-b82d-766ad0c51823
spec:
 containers:
 - image: nginx:latest
 imagePullPolicy: Always
 name: my-container
 resources:
 limits:
 memory: 100Mi
 requests:
 memory: 50Mi
 terminationMessagePath: /dev/termination-log
 terminationMessagePolicy: File
 volumeMounts:
 - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
 name: kube-api-access-thchj
 readOnly: &lt;span class="nb">true
 &lt;/span>dnsPolicy: ClusterFirst
 enableServiceLinks: &lt;span class="nb">true
 &lt;/span>nodeName: node01
 preemptionPolicy: PreemptLowerPriority
 priority: 0
 restartPolicy: Always
 schedulerName: default-scheduler
 securityContext: &lt;span class="o">{}&lt;/span>
 serviceAccount: default
 serviceAccountName: default
 terminationGracePeriodSeconds: 30
 tolerations:
 - effect: NoExecute
 key: node.kubernetes.io/not-ready
 operator: Exists
 tolerationSeconds: 300
 - effect: NoExecute
 key: node.kubernetes.io/unreachable
 operator: Exists
 tolerationSeconds: 300
 volumes:
 - name: kube-api-access-thchj
 projected:
 defaultMode: 420
 sources:
 - serviceAccountToken:
 expirationSeconds: 3607
 path: token
 - configMap:
 items:
 - key: ca.crt
 path: ca.crt
 name: kube-root-ca.crt
 - downwardAPI:
 items:
 - fieldRef:
 apiVersion: v1
 fieldPath: metadata.namespace
 path: namespace
&lt;span class="c"># 省略没用的 status 字段&lt;/span>

&lt;span class="nv">$ &lt;/span>vim pod.yaml
&lt;span class="c"># 将 limit 中，100Mi 改为 50Mi&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl delete &lt;span class="nt">-f&lt;/span> pod.yaml &lt;span class="nt">--force&lt;/span> &lt;span class="nt">--grace-period&lt;/span> 0
Warning: Immediate deletion does not &lt;span class="nb">wait &lt;/span>&lt;span class="k">for &lt;/span>confirmation that the running resource has been terminated. The resource may &lt;span class="k">continue &lt;/span>to run on the cluster indefinitely.
pod &lt;span class="s2">&amp;#34;my-pod&amp;#34;&lt;/span> force deleted

&lt;span class="nv">$ &lt;/span>kubectl apply &lt;span class="nt">-f&lt;/span> pod.yaml
pod/my-pod created&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>killercoda CKA：Storage</title><link>https://www.diguage.com/post/killercoda-cka-storage/</link><pubDate>Thu, 19 Dec 2024 19:54:28 +0800</pubDate><guid>https://www.diguage.com/post/killercoda-cka-storage/</guid><description>&lt;div class="sect1">
&lt;h2 id="_storage_persistent_volume">1. Storage - Persistent Volume&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://killercoda.com/sachin/course/CKA/pv" target="_blank" rel="noopener">Storage - Persistent Volume&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>Create a PersistentVolume (PV) named &lt;code>black-pv-cka&lt;/code> with the following specifications:&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>Volume Type: &lt;code>hostPath&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Path: &lt;code>/opt/black-pv-cka&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Capacity: &lt;code>50Mi&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>&lt;/span>

&lt;span class="nv">$ &lt;/span>vim pv.yaml
&lt;span class="c"># 编写 YAML 文件&lt;/span>

&lt;span class="nv">$ &lt;/span>&lt;span class="nb">cat &lt;/span>pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
 name: black-pv-cka
spec:
 capacity:
 storage: 50Mi
 accessModes:
 - ReadWriteOnce
 persistentVolumeReclaimPolicy: Retain
 hostPath:
 path: /opt/black-pv-cka

&lt;span class="nv">$ &lt;/span>kubectl apply &lt;span class="nt">-f&lt;/span> pv.yaml
persistentvolume/black-pv-cka created&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_storage_persistent_volume_claim">2. Storage - Persistent Volume Claim&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://killercoda.com/sachin/course/CKA/pvc" target="_blank" rel="noopener">Storage - Persistent Volume Claim&lt;/a>&lt;/p>
&lt;/div></description></item><item><title>killercoda CKA：Services &amp; Networking</title><link>https://www.diguage.com/post/killercoda-cka-services-networking/</link><pubDate>Wed, 18 Dec 2024 19:54:28 +0800</pubDate><guid>https://www.diguage.com/post/killercoda-cka-services-networking/</guid><description>&lt;div class="sect1">
&lt;h2 id="_services_networking_services">1. Services &amp;amp; Networking - Services&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://killercoda.com/sachin/course/CKA/svc" target="_blank" rel="noopener">Services &amp;amp; Networking - Services&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>You have an existing Nginx pod named &lt;code>nginx-pod&lt;/code>. Perform the following steps:&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>Expose the &lt;code>nginx-pod&lt;/code> internally within the cluster using a Service named &lt;code>nginx-service&lt;/code> .&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Use &lt;code>port forwarding&lt;/code> to service to access the Welcome content of &lt;code>nginx-pod&lt;/code> using the &lt;code>curl&lt;/code> command.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl get pod &lt;span class="nt">--show-labels&lt;/span>
NAME READY STATUS RESTARTS AGE LABELS
nginx-pod 1/1 Running 0 8m48s &lt;span class="nv">app&lt;/span>&lt;span class="o">=&lt;/span>nginx

&lt;span class="nv">$ &lt;/span>&lt;span class="nb">cat &lt;/span>svc.yaml
apiVersion: v1
kind: Service
metadata:
 name: nginx-service
spec:
 selector:
 app: nginx
 ports:
 - name: http
 protocol: TCP
 port: 80
 targetPort: 80

&lt;span class="nv">$ &lt;/span>kubectl apply &lt;span class="nt">-f&lt;/span> svc.yaml
service/nginx-service created

&lt;span class="nv">$ &lt;/span>kubectl port-forward service/nginx-service 8081:80
Forwarding from 127.0.0.1:8081 -&amp;gt; 80
Forwarding from &lt;span class="o">[&lt;/span>::1]:8081 -&amp;gt; 80
Handling connection &lt;span class="k">for &lt;/span>8081


&lt;span class="c"># 打开另外一个终端&lt;/span>
&lt;span class="nv">$ &lt;/span>curl localhost:8081
&amp;lt;&lt;span class="o">!&lt;/span>DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;&lt;span class="nb">head&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
html &lt;span class="o">{&lt;/span> color-scheme: light dark&lt;span class="p">;&lt;/span> &lt;span class="o">}&lt;/span>
body &lt;span class="o">{&lt;/span> width: 35em&lt;span class="p">;&lt;/span> margin: 0 auto&lt;span class="p">;&lt;/span>
font-family: Tahoma, Verdana, Arial, sans-serif&lt;span class="p">;&lt;/span> &lt;span class="o">}&lt;/span>
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Welcome to nginx!&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;For online documentation and support please refer to
&amp;lt;a &lt;span class="nv">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http://nginx.org/&amp;#34;&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>nginx.org&amp;lt;/a&amp;gt;.&amp;lt;br/&amp;gt;
Commercial support is available at
&amp;lt;a &lt;span class="nv">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http://nginx.com/&amp;#34;&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>nginx.com&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Thank you &lt;span class="k">for &lt;/span>using nginx.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>killercoda CKA：Architecture, Installation &amp; Maintenance</title><link>https://www.diguage.com/post/killercoda-cka-architecture-installation-maintenance/</link><pubDate>Mon, 16 Dec 2024 19:54:28 +0800</pubDate><guid>https://www.diguage.com/post/killercoda-cka-architecture-installation-maintenance/</guid><description>&lt;div class="sect1">
&lt;h2 id="_architecture_installation_maintenance_create_pod">1. Architecture, Installation &amp;amp; Maintenance - Create Pod&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://killercoda.com/sachin/course/CKA/pod-create" target="_blank" rel="noopener">Architecture, Installation &amp;amp; Maintenance - Create Pod&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>Create a pod called &lt;code>sleep-pod&lt;/code> using the &lt;code>nginx&lt;/code> image and also &lt;code>sleep&lt;/code> (using &lt;code>command&lt;/code> ) for give any value for seconds.&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>&lt;/span>

&lt;span class="nv">$ &lt;/span>&lt;span class="nb">cat &lt;/span>nginx.yaml
apiVersion: v1
kind: Pod
metadata:
 name: sleep-pod
spec:
 containers:
 - name: nginx
 image: nginx
 &lt;span class="nb">command&lt;/span>:
 - &lt;span class="nb">sleep&lt;/span>
 - &lt;span class="s2">&amp;#34;3600&amp;#34;&lt;/span>

&lt;span class="nv">$ &lt;/span>kubectl apply &lt;span class="nt">-f&lt;/span> nginx.yaml
pod/sleep-pod created

&lt;span class="nv">$ &lt;/span>kubectl get pod
NAME READY STATUS RESTARTS AGE
sleep-pod 1/1 Running 0 5s&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>理解数据库分片</title><link>https://www.diguage.com/post/understanding-database-sharding/</link><pubDate>Sun, 01 Dec 2024 15:42:01 +0800</pubDate><guid>https://www.diguage.com/post/understanding-database-sharding/</guid><description>&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>最近在 DigitalOcean 社区看到一篇文章，讲解数据库分片架构的，感觉非常不错，图文并茂，翻译过来，分享给需要的朋友。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_介绍">介绍&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>任何应用程序或网站，如果出现大幅增长，最终都需要进行扩展，以适应流量的增加。对于数据驱动型应用程序和网站来说，在进行扩展时必须确保数据的安全性和完整性。很难预测一个网站或应用程序会变得多受欢迎，或者它的受欢迎程度会维持多久，这就是为什么一些组织会选择一种允许他们动态扩展数据库的数据库架构。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在这篇概念性文章中，我们将讨论这样一种数据库架构：分片数据库。近年来，分片数据库受到了广泛关注，但很多人并不清楚什么是分片数据库，也不知道在哪些情况下分片数据库才有意义。我们将介绍什么是分片、分片的一些主要优点和缺点，以及几种常见的分片方法。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_什么是分片">什么是分片？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>分片是一种与水平分区相关的数据库架构模式，即把一个表的行分成多个不同的表，称为分区。每个分区都有相同的模式和列，但也有完全不同的行。同样，每个分区中的数据都是唯一的，与其他分区中的数据无关。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从水平分区与垂直分区的关系角度来思考水平分区可能会有所帮助。在垂直分区表中，整个列都被分离出来并放入新的、不同的表中。一个垂直分区中的数据独立于所有其他分区中的数据，每个分区都有不同的行和列。下图说明了如何对表格进行水平和垂直分区：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/databases/sharding-1.png" alt="水平分区与垂直分区" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 水平分区与垂直分区&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>分片是指将数据分割成两个或多个较小的块，称为逻辑分片。然后，逻辑分片分布在不同的数据库节点上，称为物理分片，物理分片可容纳多个逻辑分片。尽管如此，所有分片中保存的数据共同代表了一个完整的逻辑数据集。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>数据库分片是无共享架构的典范。这意味着分片是独立的，它们不共享任何相同的数据或计算资源。不过，在某些情况下，将某些表复制到每个分片中作为参考表是有意义的。例如，假设有一个应用程序的数据库依赖于重量测量的固定转换率。通过将包含必要转换率数据的表复制到每个分片中，有助于确保每个分片中都包含查询所需的所有数据。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>通常，分片是在应用程序级实现的，这意味着应用程序包含定义向哪个分片传输读写的代码。不过，有些数据库管理系统内置了分片功能，允许你直接在数据库级实施分片。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>鉴于以上对分片的概述，让我们来看看这种数据库架构的一些优点和缺点。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_分片的优点">分片的优点&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>对数据库进行分片的主要吸引力在于，它有助于促进水平扩展，也称为向外扩展，横向扩展。水平扩展是指在现有堆栈中添加更多机器，以分散负载，允许更多流量和更快处理。这通常与垂直扩展（也称向上扩展）形成对比，后者涉及升级现有服务器的硬件，通常是增加更多内存或 CPU。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在一台机器上运行一个关系数据库，并根据需要通过升级其计算资源来扩大其规模相对简单。但归根结底，任何非分布式数据库在存储和计算能力方面都是有限的，因此可以自由横向扩展，会让你的设置更加灵活。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一些人选择分片数据库架构的另一个原因是为了加快查询响应速度。在未分片的数据库上提交查询时，数据库可能需要搜索查询表中的每一行，然后才能找到所需的结果集。对于使用大型单体数据库的应用程序来说，查询速度会慢得令人望而却步。不过，通过将一个表分片成多个表后，查询需要处理的行数就会减少，返回结果集的速度也会快得多。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>分片还可以减轻中断造成的影响，从而提高应用程序的可靠性。如果您的应用程序或网站依赖的是未分片的数据库，中断有可能导致整个应用程序不可用。 而使用分片数据库时，故障可能只影响单个分片。尽管这可能会导致部分用户无法使用应用程序或网站的某些部分，但总体影响仍小于整个数据库崩溃的影响。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_分片的缺点">分片的缺点&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>虽然分片可以使数据库的扩展更容易并提高性能，但它也会带来一些限制。在此，我们将讨论其中的一些限制，以及为什么要避免使用分片。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>人们在使用分片时遇到的第一个困难是正确实施分片数据库架构的复杂性。如果操作不当，分片过程很有可能导致数据丢失或表损坏。即使操作正确，分片也可能对团队的工作流程产生重大影响。用户必须跨多个分片位置管理数据，而不是从一个入口点访问和管理数据，这可能会对某些团队造成干扰。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>用户在对数据库进行分片后有时会遇到一个问题，那就是分片最终会变得不平衡。举例来说，假设你的数据库有两个独立的分片，一个用于存储姓氏以字母 A 至 M 开头的客户，另一个用于存储姓氏以字母 N 至 Z 开头的客户。然而，你的应用程序为大量姓氏以字母 G 开头的人提供服务。 A-M 分区已成为所谓的数据库热点。在这种情况下，分片给数据库带来的任何好处都会被速度变慢和崩溃所抵消。数据库很可能需要修复和重新分片，以使数据分布更均匀。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>另一个主要缺点是，一旦数据库被分片，就很难将其恢复到未分片的架构。数据库分片前的任何备份都不包括分片后写入的数据。 因此，要重建未分片的原始架构，就需要将新的分片数据与旧的备份合并，或者将分片后的数据库变回单一数据库，这两种方法都会耗费大量成本和时间。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最后一个需要考虑的缺点是，并非每个数据库引擎都支持分片。例如，PostgreSQL 不包括自动分片功能，但可以手动分片 PostgreSQL 数据库。 有一些 Postgres 变种确实包含自动分片功能，但它们往往落后于最新的 PostgreSQL 版本，而且缺乏某些其他功能。一些专门的数据库技术（如 MySQL Cluster 或某些数据库即服务产品（如 MongoDB Atlas））确实包含自动分片功能，但这些数据库管理系统的普通版本并不包含。因此，分片通常需要“自己开发”。这意味着通常很难找到分片文档或故障排除技巧。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当然，这些只是分片前需要考虑的一些一般性问题。根据其用例，对数据库进行分片可能会有更多潜在的缺点。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>现在，我们已经介绍了分片的一些缺点和优点，下面将介绍几种不同的分片数据库架构。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_分片架构">分片架构&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>一旦决定对数据库进行分片，接下来需要考虑的就是如何分片。在运行查询或将输入数据分发到分片表或数据库时，将数据分发到正确的分片至关重要。否则，可能会导致数据丢失或查询缓慢。在本节中，我们将介绍几种常见的分片架构，每种架构都使用略有不同的流程在分片间分发数据。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_基于键的分片">基于键的分片&lt;/h3>
&lt;div class="paragraph">
&lt;p>基于密钥的分片，也称为基于散列的分片，涉及使用从新写入的数据中提取的值，例如客户的 ID 编号、客户端应用程序的 IP 地址、邮政编码等并将其输入散列函数，以确定数据应进入哪个分片。散列函数是一种输入数据（如客户电子邮件）并输出离散值（即散列值）的函数。在分片的情况下，散列值是一个分片 ID，用于确定输入的数据将存储在哪个分片上。整个过程如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/databases/sharding-2.png" alt="基于键的分片" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. 基于键的分片&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>为确保条目以一致的方式放置于正确的分片，输入散列函数的值都应来自同一列。此列被称为分片键。简单来说，分片键与主键类似，都是用于为单个行建立唯一标识符的列。从广义上讲，分片键应该是静态的，也就是说，它不应该包含可能会随时间变化的值。否则，会增加更新操作的工作量，并可能降低性能。&lt;/p>
&lt;/div></description></item><item><title>基于 Docker 搭建开发环境（三）：链路追踪</title><link>https://www.diguage.com/post/building-a-develop-environment-based-on-docker-3/</link><pubDate>Sun, 20 Oct 2024 16:50:11 +0800</pubDate><guid>https://www.diguage.com/post/building-a-develop-environment-based-on-docker-3/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;strong>基于 Docker 搭建开发环境系列&lt;/strong>:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-1/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（一）：数据库+监控&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-2/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（二）：EFK 日志套件&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-3/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（三）：链路追踪&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-1/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（一）：数据库+监控&lt;/a> 和 &lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-2/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（二）：EFK 日志套件&lt;/a> 两篇文章中，分别介绍了“数据库+监控”和“EFK 日志套件”。这篇文章给大家分享一下如何在本地搭建起一套简单的分布式链路追踪。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 AI 的帮助下，如同砍瓜切菜一样，非常迅速地就完成了 &lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-2/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（二）：EFK 日志套件&lt;/a> 的搭建。原以为搞这个也会分分钟的问题，结果应用的追踪数据一致无法正常发送到 Jaeger 中，各种改端口号都不行。后来，无意间看了 OpenTelemetry 的配置文档，增加了一个协议配置，全部流程竟然通了，非常神奇！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>站在更高的视角去看，链路追踪其实是可观测性的一部分，包括上篇文章的日志，也是可观测性的一部分。日志、追踪、度量，三者是相辅相成的。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/it/observability.png" alt="可观测性" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 可观测性&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 OpenTelemetry 出现之前，日志、追踪、度量是分离的，三者各各自为战。而 OpenTelemetry 的出现，则是试图将三者统一。目前 OpenTelemetry 是云原生架构中，最炙手可热的分布式链路追踪解决方案，它提供了一套相关标准，各个厂商可以在这套标准之上进行各种各样的组件开发，大家可以根据自己的需要，选择不同的组件，进行可插拔式的安装。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/it/opentelemetry-collection.webp" alt="OpenTelemetry 的野心" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. OpenTelemetry 的野心&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在这篇文章中，链路追踪的解决方案选择的是 OpenTelemetry + OpenTelemetry Collector + Jaeger。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_opentelemetry">OpenTelemetry&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>OpenTelemetry 并不需要在 Docker 中启动或者配置什么。在目前的架构中，Jaeger 是作为 OpenTelemetry 的一个实现来出现的。 OpenTelemetry 需要做的就是下载一个 Java Agent，执行 &lt;code>docker/config/opentelemetry/download-opentelemetry-agent.sh&lt;/code> 脚本即可下载最新版的 Java Agent。在业务应用启动时，增加如下 JVM 参数：&lt;/p>
&lt;/div></description></item><item><title>基于 Docker 搭建开发环境（二）：EFK 日志套件</title><link>https://www.diguage.com/post/building-a-develop-environment-based-on-docker-2/</link><pubDate>Thu, 17 Oct 2024 22:42:11 +0800</pubDate><guid>https://www.diguage.com/post/building-a-develop-environment-based-on-docker-2/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;strong>基于 Docker 搭建开发环境系列&lt;/strong>:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-1/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（一）：数据库+监控&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-2/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（二）：EFK 日志套件&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-3/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（三）：链路追踪&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-1/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（一）：数据库+监控&lt;/a> 中，介绍了一下如何使用 Docker 搭建起 MySQL + NACOS + Prometheus + Grafana 集成数据库、注册中心+配置管理、监控的开发环境。这篇文章来介绍一下如何在原来的基础上接入 Elasticsearch + Fluentd + Kibana 套件，并且将 NACOS 的日志接入到 Elasticsearch 里。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_elasticsearch">Elasticsearch&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>由于 Elasticsearch 8+ 的版本修改了安全策略，不允许 Kibana 使用超级管理员 &lt;code>elastic&lt;/code> 连接 Elasticsearch，这里选用 7.x 版本做演示。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>还有一点需要提醒，在设置 Elasticsearch 的超级管理员 &lt;code>elastic&lt;/code> 的账户密码时，如果密码是全部的阿拉伯数字，那么需要用双引号或者单引号括起来。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在测试中，还遇到一个磁盘过载导致的只读问题。解决方式如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;code>curl -X GET &amp;#34;localhost:9200/_cat/allocation?v&amp;amp;pretty&amp;#34;&lt;/code> 查看磁盘使用情况&lt;/p>
&lt;/li>
&lt;li>
&lt;p>解除只读状态&lt;/p>
&lt;div class="openblock">
&lt;div class="content">
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="nv">$ &lt;/span>curl &lt;span class="nt">-X&lt;/span> PUT &lt;span class="s2">&amp;#34;localhost:9200/test/_settings&amp;#34;&lt;/span> &lt;span class="nt">-H&lt;/span> &lt;span class="s1">&amp;#39;Content-Type: application/json&amp;#39;&lt;/span> &lt;span class="nt">-d&lt;/span>&lt;span class="s1">&amp;#39;
{
 &amp;#34;index.blocks.read_only_allow_delete&amp;#34;: null
}
&amp;#39;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>基于 Docker 搭建开发环境（一）：数据库+监控</title><link>https://www.diguage.com/post/building-a-develop-environment-based-on-docker-1/</link><pubDate>Wed, 16 Oct 2024 21:48:11 +0800</pubDate><guid>https://www.diguage.com/post/building-a-develop-environment-based-on-docker-1/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;strong>基于 Docker 搭建开发环境系列&lt;/strong>:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-1/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（一）：数据库+监控&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-2/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（二）：EFK 日志套件&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/building-a-develop-environment-based-on-docker-3/" target="_blank" rel="noopener">基于 Docker 搭建开发环境（三）：链路追踪&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>去年，很多同事要换 Mac 本，所以，写了 &lt;a href="https://www.diguage.com/post/install-software-for-new-mac/" target="_blank" rel="noopener">新 Mac 安装软件脚本&lt;/a>，方便大家一键换机。最近想玩一下 Spring Cloud 以及相关周边的部署、监控等开源解决方案。由于组件众多及为了便于迁移和共享，计划基于 Docker 及 Docker Compose 搭建一套相关的开发环境。记录一下，方便有相同需求的朋友借鉴。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
最新版的 Docker 在下载镜像时，会先访问一下 Docker 的官方站点。由于国内众所周知的网络情况，访问 Docker 官方站点总失败。所以，即使配置了国内 Docker 镜像站点也会失败。只需要将 Docker 软件回滚到 4.30.0 即可。（Mac 下验证有效，其他操作系统待进一步验证。）
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_mysql">MySQL&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>开发中，最常用的应该就是数据库了。所以，先来搞 MySQL 数据库。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>创建如下目录结构，并添加相关相关文件：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="nv">$ &lt;/span>tree
&lt;span class="nb">.&lt;/span>
├── README.adoc
├── clean.sh
├── data
│ └── mysql
│ └── .gitkeep
├── docker
│ ├── config
│ │ └── mysql
│ │ └── init.sql
│ ├── &lt;span class="nb">env&lt;/span>
│ │ └── mysql.env
│ └── images
│ └── mysql.dockerfile
└── docker-compose.yml&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>再谈 DDD 是银弹吗？</title><link>https://www.diguage.com/post/is-ddd-a-silver-bullet-2/</link><pubDate>Sat, 07 Sep 2024 22:04:19 +0800</pubDate><guid>https://www.diguage.com/post/is-ddd-a-silver-bullet-2/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/is-ddd-a-silver-bullet/" target="_blank" rel="noopener">DDD 是银弹吗？&lt;/a> 中，D瓜哥分享了关于领域驱动设计的三个问题。最近在读一本书 &lt;a href="https://book.douban.com/subject/35731315/" target="_blank" rel="noopener">《架构设计2.0：大型分布式系统架构方法论与实践》&lt;/a>。（这本书还不错，推荐）这本书中，花了两个章节的篇幅，重点谈论了领域驱动设计。引用书中的观点，结合个人开发经验，再来谈一谈 DDD 是否是银弹？&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_软件建模的困难">软件建模的困难&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>首先，必须面对的一个事实是：软件建模，困难重重；尤其是对于复杂业务的建模，更是难上加难。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于复杂业务的软件开发，其生命周期大概分为如下五个阶段：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>确定业务目标和业务价值。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>比如某消费信贷业务。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>目标被拆解成一系列核心功能点。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>比如消费信贷下的授信、交易、账务等。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>围绕这些功能点定义业务流程、业务规则，以及整个过程设计什么样的业务数据或业务对象。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>比如账单分期金额必须大于 100 元。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>领域建模。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>比如对账务系统进行建模。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>基于领域模型做技术架构的设计。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>比如是否要做读写分离？是否要做分库分表等？&lt;/p>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>软件建模的本质是找出现实世界中的“不变形”。但是，现实世界中，唯一不变的就是这个世界在不断变化！所以，建模的过程也是一个反复的过程。如下图：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/develop/develop-lifecycle.svg" alt="复杂业务软件开发的生命周期" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 复杂业务软件开发的生命周期&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_几乎不存在稳定的领域模型">几乎不存在稳定的领域模型&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>我们追求一个稳定的领域模型，但是，现实却给了我们重重一击：稳定的模型几乎不可能做到。原因如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>意识问题。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>在消费、业务及产品等关注的是业务流程。唯独开发人员要将业务流程转化成业务模型。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>现实世界的复杂性。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>现实业务是复杂的，建模只是抽取了一个现实业务某一时刻的业务形态。但是，业务形态会有变化的，比如取现前期不可分期，后期业务迭代可能就会运行进行分期。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>迭代速度。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>互联网公司要求“小步快跑，快速迭代”。这与模型的稳定其实是矛盾的。为了业务的迭代速度，只能牺牲模型的稳定性，为了赶工期，只能在模型上不断打补丁。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>火候的掌握。&lt;/strong>&lt;/p>
&lt;div class="openblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>开发人员的设计能力无法一蹴而就。既需要思考，又需要反复练习。在快速的业务迭代和人员流动下，开发人员根本没有时间锤炼自己的设计能力。那么，对于设计火候的掌握，也就很难达到理想中的水平。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_领域驱动设计的困难">领域驱动设计的困难&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>书中总结了实施领域驱动设计的五个困难，D瓜哥逐一谈谈自己的看法：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>领域驱动设计本身只是一套思维方法，而不是要严格执行的标准，所以其本身弹性很大。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>这个问题，D瓜哥在上一篇文章中已经讨论过了。弹性太大，就有太多值得商榷的地方，也许初次开发，还可以按照某个人的想法一以贯之。但是，随着参与维护的人员增多，每个人都会不由自主地会带入个人的一些想法，各种想法的碰撞，必然就会引入代码结构的混乱。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>思维方式的转换很难。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>绝大多数面向业务的开发人员，尤其是 Java 开发人员，对三层架构已经有根深蒂固的认识。思维方式已经被打上深深的烙印，想要改变，坦白讲，极其困难。尤其是，没有一个统一的标准和广泛认可的实现范例，完全靠摸着石头过河，必然会“一千个读者，就有一千个哈姆雷特。”&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>领域驱动设计的实施需要强大的技术基础实施来保证。&lt;/strong>&lt;/p>
&lt;div class="paragraph">
&lt;p>D瓜哥私以为这个倒不是什么问题。针对技术问题，尤其是一些共性问题，都有成熟的解决方案。只要能合理搭积木，就可以解决相应的问题。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>大量存量的老系统，重构成本大于收益，没有重构动力。&lt;/strong>&lt;/p>
&lt;div class="openblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>编程第一准则：代码能跑就不要动。重构引入的问题谁来解决？重构带来的事故谁来负责？这个时候必须祭出这张图了：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/common/bug-code-run.jpeg" alt="代码能跑就不要动" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. 代码能跑就不要动&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当然，私以为不是程序员反感重构代码，更多是因为下面这个因素。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>在互联网的快速开发迭代面前，很少有人可以静下心来在软件方法论层面去精雕细琢，更多的是快速堆砌功能，完成业务需求开发。&lt;/strong>&lt;/p>
&lt;div class="openblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>业务的快速迭代，导致根本没有时间让开发人员去优化代码。可口的饭菜需要恰当的火候和足够的时间，优雅的软件建模也需要恰当的火候和足够的时间。精心地软件建模需要三个月，业务让你一个月上线，而且还是加班加点才能干完。结合实际来看，绝大多数情况都会想业务妥协吧？！&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_领域驱动设计的出路">领域驱动设计的出路&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>书中的观点是做个折中：&lt;strong>在宏观层面，遵循领域驱动设计的方法论；在微观层面，不严格遵循领域驱动设计的方法论。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥是这样理解的：可以利用领域驱动设计里面的限界上下文的思想，把领域做个分割，划分成业务更聚合的子域。在子域内部，提炼出统一语言，来规范业务、产品和开发沟通的业务术语。在子域交互的接口层面，进行精心设计，精雕细琢。至于子域及接口的内部实现，就交给开发团队自己决策，只要满足对应的技术指标（比如每秒要支撑多大的访问量）即可。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在部门内部讨论时，D瓜哥还给出了一个更具操作性和落地性的方案：现实面临的问题是代码冗余，技术欠债，不容易维护。&lt;strong>先放下关于领域驱动设计的无谓讨论，利用每一次开发的机会，把冗余代码删除，把代码重构和优化，一步一步地精炼代码，即使不谈领域驱动设计，相信在逐步重构和优化下，技术欠债会逐渐弥补，可维护性也会逐步提高。&lt;/strong>&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div></description></item><item><title>DDD 是银弹吗？</title><link>https://www.diguage.com/post/is-ddd-a-silver-bullet/</link><pubDate>Wed, 28 Aug 2024 19:27:44 +0800</pubDate><guid>https://www.diguage.com/post/is-ddd-a-silver-bullet/</guid><description>&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>史前时期最骇人的景象，莫过于一群巨兽在焦油坑里做垂死前的挣扎。不妨闭上眼睛想像一下，你看到了一群恐龙、长毛象、剑齿虎正在奋力挣脱焦油的束缚，但越挣扎，焦油就缠得越紧，就算他再强壮、再厉害，最后，都难逃灭顶的命运。过去十年间，大型系统的软件开发工作就像是掉进了焦油坑里……&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;div class="attribution">
— 佛瑞德·布鲁克斯（Frederick P. Brooks）&lt;br/>
&lt;cite>《人月神话》&lt;/cite>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>应该早在 2019 年，在 &lt;a href="https://coolshell.cn/" target="_blank" rel="noopener">左耳朵耗子哥&lt;/a> 的推荐下阅读了 &lt;a href="https://book.douban.com/subject/5344973/" target="_blank" rel="noopener">《领域驱动设计》&lt;/a>，并将读书摘要整理成几篇文章：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/domain-driven-design-1/" target="_blank" rel="noopener">《领域驱动设计》读书笔记（一）：运用领域模型&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/domain-driven-design-2/" target="_blank" rel="noopener">《领域驱动设计》读书笔记（二）：模型驱动设计的构造块&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/domain-driven-design-3/" target="_blank" rel="noopener">《领域驱动设计》读书笔记（三）：通过重构来加深理解&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/domain-driven-design-4/" target="_blank" rel="noopener">《领域驱动设计》读书笔记（四）：战略设计&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>部门要搞 DDD 和体系化建设，正好有一个核心项目要做重构，领导让实践一下领域驱动设计，苦于没有范例可以参考，感觉无处下手，所以又读了 &lt;a href="https://book.douban.com/subject/35235992/" target="_blank" rel="noopener">《中台架构与实现·基于DDD和微服务》&lt;/a>（最早读的是极客时间专栏，后专栏编撰成该书）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>后来，又陆陆续续看了好多领域驱动设计的相关文章。对于领域驱动设计，即了解过，也实践过。所以，结合自身的经历和体会，谈一谈我的感受。不吹不黑，重点谈三个问题。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_如何快速上手">1. 如何快速上手？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>上面介绍了一下D瓜哥的个人经历，是付出了一点的时间和精力的，由此引出了第一个问题：&lt;strong>如何快速上手？&lt;/strong>对于一个工作多年，经验丰富，也算勤奋好学的高级码农，上手还如此困难重重，那么对于一个刚刚参加工作的职场新人，上手是否会更加困难？又该如何克服这个困难？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>任何一家公司，尤其是大型技术公司，都是由初中高级工程师组成的，而且成员人数也是由多到少，参与实际开发工作，大概率也会由多到少，初级开发工程师干了大量的实际编码工作。如果无法吸引大多数的初级工程师参与进来，只有个别的高级工程师去落地，那么，所谓的领域驱动设计，只能成为空中楼阁，海市蜃楼。华而不实，无法落地。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>但是，由于经验少，这对于初级工程师来说，也许是一个优势。毕竟，一张白纸，可以画出各种美丽的画卷。中高级工程师已经习惯于传统的开发模式，思维已经定格。但是，初级工程师，反倒是嗷嗷待哺，更容易塑性。可惜的是，现在没有好的示例可以学习。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_哪里有可以参考的示例">2. 哪里有可以参考的示例？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>快速上手的最好办法，就是给一个完整的示例，拿来直接抄作业。对于入门的程序员，学东西上手最快的办法就是抄代码。把示例代码，拿过来改吧改吧就能跑起来，无形中就学会怎么写代码了。对于传统的三层架构，有太多的示例可以来学习了，比如 &lt;a href="https://github.com/springside/springside4" target="_blank" rel="noopener">SpringSide&lt;/a>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从 &lt;a href="https://book.douban.com/subject/1418618/" target="_blank" rel="noopener">《Domain-Driven Design》&lt;/a> 这本书在 2003 年出版到现在，已经有 21 年了。到现在为止，也没有见到一个开源的、能运行起来的基于领域驱动设计的项目。也可能是鄙人孤陋寡闻，坐井观天，没有发现。如果谁发现了，欢迎向我反馈。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>作为对比，我们来看一下 Spring 的发展过程。Spring 的思想最早是在 &lt;a href="https://book.douban.com/subject/1426848/" target="_blank" rel="noopener">《J2EE Development without EJB》&lt;/a> 这本书里出现的，这本书是在 2004 年 6 月出版的。这本书出版后，开源社区根据这本书里面的思想及代码片段，开发出了 Spring 框架。在两年后，Spring 之父 Rod Johnson 接着出版了 &lt;a href="https://book.douban.com/subject/1440851/" target="_blank" rel="noopener">《Professional Java Development with the Spring Framework》&lt;/a>，系统介绍了一下 Spring 框架的各种使用案例。到 2008 年我上大学的时候，在国内的培训行业，已经开始重点讲解 Spring 了。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
其实，D瓜哥想拿传统的三层架构的发展来做对比，可惜没有找到更确切的时间线。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="paragraph">
&lt;p>期待一个完整的、基于领域驱动设计的、能正常运行起来的开源项目尽早出现！&lt;/p>
&lt;/div></description></item><item><title>关于高中求学的一些问答及提醒</title><link>https://www.diguage.com/post/some-tips-for-high-school/</link><pubDate>Sat, 17 Aug 2024 15:30:12 +0800</pubDate><guid>https://www.diguage.com/post/some-tips-for-high-school/</guid><description>&lt;div class="paragraph">
&lt;p>家里一个亲戚今年参加中招考高中，由于成绩不是很理想，所以，就面临一个问题：&lt;strong>选择哪所高中去求学？&lt;/strong>由此引发的一系列思考和讨论，D瓜哥觉得非常有共性，分享出来，希望给需要的家长一个参考。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_去重点高中怕跟不上选择去上私立高中">1. 去重点高中怕跟不上，选择去上私立高中&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>一些家长可能会有这样的想法：孩子中考成绩不理想，如果去重点高中，担心孩子跟不上课程，所以，选择去上私立高中。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>关于这个问题，是一个伪命题。&lt;strong>高中最重要的考核就是三年以后的高考。&lt;/strong>高考对所有参加的考生都是一视同仁的，不会因为私立高中和公立高中而有什么差别。（同一个省的高考生是一样的，跨省则不一定。这里不予讨论。）所以，&lt;strong>只要上高中，就必须努力跟上，尽力向前冲！&lt;/strong>否则，就会掉队，考不上大学，考不上大学，就失去了上高中的主要意义。所以，根本无需考虑能不能跟上的问题。这个问题只有一个选择：&lt;strong>只要求学，就只能加油往前冲！&lt;/strong>&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_选择高中的关注点">2. 选择高中的关注点&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>由于孩子成绩不理想，那么可能无法进入理想的中学。接下来的一个问题就是：&lt;strong>如何选择高中？&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于这个问题，私以为&lt;strong>对于高中的的考察标准只有一条：就是高考录取率，各个层次本科的录取率。&lt;/strong>但是在选择高中时，有一些值得关注的点，下面一一说明。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_生源质量">2.1. 生源质量&lt;/h3>
&lt;div class="paragraph">
&lt;p>聪明的学生在任何高中都有好的成绩。脑瓜子不灵的学生，即使送到人大附中这样全国最好的高中也难有好的成绩。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>另外，如果身边有一些成绩好的同学，那么当自己学习有问题时，可以更方便地找同学帮忙解答。如果身边事一群瓜娃子，出现学习问题，也无法及时解决，日积月累，成绩自然难以提高！&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_学校的历史成绩">2.2. 学校的历史成绩&lt;/h3>
&lt;div class="paragraph">
&lt;p>成绩好的学校大概率会一如既往地好下去。而差的学校想变好，却需要付出非常巨大的努力和相当长的时间来改善。它还需要时间，逐步向社会来证实它的实力，以求获取更好的生源。对于学校来说，这个时间是可以耗得起的。但是，作为学生，上学的时间段时是卡死的，耗不起，也等不起。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_老师资历">2.3. 老师资历&lt;/h3>
&lt;div class="paragraph">
&lt;p>好的老师有更好的教学方法，对学生学习更有帮助。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>但是，好的师资也需要好的学生来衬托。&lt;strong>老师和学生是相互成就的。&lt;/strong>只有伯乐，没有千里马，伯乐只能悲叹“英雄无用武之地”！&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_学习氛围">2.4. 学习氛围&lt;/h3>
&lt;div class="paragraph">
&lt;p>大多数人都从众。所以，学习氛围好的地方，大部分人会被带动起来学习。但是，如果学习氛围一般，大多数人就会随波逐流，逐步落伍。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_私立高中的困境">2.5. 私立高中的困境&lt;/h3>
&lt;div class="paragraph">
&lt;p>目前，国内大多数人更愿意相信公立高中。认为公立高中有政府托底，更有保障。而对私立高中，大多数人缺乏足够的信任。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当然，还有一个原因是私立高中普遍学费高昂，这对于很多家长来说，也是一个不小的负担。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_小结">2.6. 小结&lt;/h3>
&lt;div class="paragraph">
&lt;p>所以，综合上述情况，导致的结果是，大多数情况下私立高中的生源质量普遍一般，甚至不好。由于生源质量问题，导致无论是学校的历史成绩，还是学校氛围，可能都会差强人意。所以，如果家长希望通过上私立高中来提高孩子成绩。综合来看，个人觉得很难实现，甚至基本不可能（可能比买彩票中大奖的概率要高一点）。最后的结果，可能钱也花了，时间也耽误了，孩子也没有太大的起色，最后高考成绩也不理想。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>还有一个种情况，从小就一直上私立学校的情况，这种大多数是双语教学，面向的也是国外的大学，大部分是不把高考作为第一选项的。这种情况，不在此讨论范围。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_转学">3. 转学&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>如果一个学校不行，在条件允许的情况下，尽快转学，转到更好的学校。这个时候，由于入学晚的原因，可能不容易合群，家长要多鼓励和开导，提供足够的情绪价值，帮助孩子早日完成过渡。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>另外，学生也不要太在意，一般情况下，等到上高二会进行二次分班，大家又都从新开始了。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_培优班">4. 培优班&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>如果一个学生没有考进更好的中学，去了一所一般的高中，而且还进培优班了。这里有三点需要注意：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>如果不是托关系进培优班，那么只能说明这个高中生源质量确实一般。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>成绩一般的学生在培优班里，有可能每次考试都是班里垫底。这就要多去关注一下学生在整个高中的整体成绩排名。好的情况是，在班级垫底，但在年级总体排名靠前，这种情况家长要多鼓励学生，培养学生有一颗强大的心脏。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>另外一种情况是，由于可能长时间在班级成绩排名垫底，这对学生来说，无形中会带来很大的压力，导致有些学生可能会自暴自弃。所以，家长要做好安慰和支持，多发现学生的优点和长处，多鼓励孩子。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_相比私立高中的更优解">5. 相比私立高中的更优解&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>如果有能力支付私立高中的钱，相对来说，另外一条路可操作性更大一些：&lt;strong>尽力去层次更好的公立高中，把省下来的学费给孩子请一对一的私教辅导。&lt;/strong>&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_私教辅导">6. 私教辅导&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>学生有各种各样的性格，老师也有跟种各样的教法，所以，孔子提出要“因材施教”。如果学生和私教老师不是很契合，那么请私教老师不一定就能提高成绩。如果已经请私教的情况下，家长一定要多关注学生的反馈，了解学生的学习情况，判断是否合拍，&lt;strong>如果不合适，要尽早更换私教老师。&lt;/strong>&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_高中成绩可以突飞猛进">7. 高中成绩可以突飞猛进&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>无需太过担心孩子的高中入学成绩排名。以D瓜哥的上学经历来看，在高一，学生的成绩排名会有剧烈的波动，有人入学成绩很差，后来迎头赶上的；也有人入学成绩很好，但后来却跌落谷底。所以，只要学生初中不掉队，&lt;strong>上了高中肯下功夫学习，成绩就会逐步提高。&lt;/strong>（掉队的学生可能就不会想着去上高中了）&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_不要选择职高或32大专">8. 不要选择职高或“3+2”大专&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>还有一个选项：选择职高或者是“3+2”的大专。私以为，这个选项可以直接忽略，除非迫不得已。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>大多数成绩还可以的学生都去上高中了，所以，能选择去上职高或者大专的，大部分成绩都一般，甚至很差。结果，大部分人可能就会不学无术，聚众打架斗殴。最后，孩子不仅可能没学好，甚至沾染了一堆坏毛病。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>另外，现在学历贬值严重，满大街都是本科生，大专生更没出路（可以看看现在公务员招聘都要求什么学历）。上了大专，如果想有出路，还要考专升本，相当于一次高考，那为何不直接上高中呢？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当然，在职高或者大专里面，也不乏一些学有所成的人才，但这样的概率太低了。这个问题，不再争论，争论就是你对。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_总结">总结&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>综合上述的讨论，&lt;strong>对于孩子上学，最优解就是上尽可能好的高中。如果上不了最好的高中，那就选次一点的公立高中。如果家庭条件允许，可以给孩子请一些私教辅导，来帮助孩子提高成绩。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最后，送给正在求学的学子们一句话：&lt;strong>你充满了潜能，但你的努力还远远不够！&lt;/strong>祝福每一个学子学有所成！&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div></description></item><item><title>单调栈实践（二）：应用</title><link>https://www.diguage.com/post/monotonic-stack-in-practice-2/</link><pubDate>Fri, 02 Aug 2024 22:57:10 +0800</pubDate><guid>https://www.diguage.com/post/monotonic-stack-in-practice-2/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/monotonic-stack-in-practice-1/" target="_blank" rel="noopener">单调栈实践（一）：入门&lt;/a> 中对单调栈做了一个初步介绍，同时使用一个类似单调栈的题目做了入门的尝试。在本文中，将分析正式单调栈的使用案例。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_实践_leetcode_503_下一个更大元素_ii">实践： LeetCode 503. 下一个更大元素 II&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>单调栈主要就是为了解决选择下一个更大或者更小元素的相关问题。来看一下 &lt;a href="https://leetcode.com/problems/next-greater-element-ii/" target="_blank" rel="noopener">LeetCode 503. 下一个更大元素 II&lt;/a>。&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>给定一个循环数组 &lt;code>nums&lt;/code> （ &lt;code>nums[nums.length - 1]&lt;/code> 的下一个元素是 &lt;code>nums[0]&lt;/code> ），返回 &lt;code>nums&lt;/code> 中每个元素的下一个更大元素。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>数字 &lt;code>x&lt;/code> 的下一个更大的元素是按数组遍历顺序，这个数字之后的第一个比它更大的数，这意味着你应该循环地搜索它的下一个更大的数。如果不存在，则输出 &lt;code>-1&lt;/code> 。&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果熟悉单调栈，这道题的解法就一目了然：&lt;strong>将数组从后向前遍历，如果单调栈栈顶元素比当前元素小，就将栈顶元素弹出；重复上述操作，直到栈顶元素大于当前元素，或者栈为空。如果栈不为空，则栈顶元素就是当前元素的后继更大元素。&lt;/strong>代码如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * LeetCode 503. 下一个更大元素 II
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 * @since 2024-07-05 23:08:39
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="nf">nextGreaterElements&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">nums&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="kt">int&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">nums&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">];&lt;/span>
 &lt;span class="nc">Deque&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Integer&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">stack&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">LinkedList&lt;/span>&lt;span class="o">&amp;lt;&amp;gt;();&lt;/span>
 &lt;span class="c1">// 只需要将数组“拼接”，遍历两遍数组，就可以解决所有元素后继更大元素的问题&lt;/span>
 &lt;span class="c1">// 从后向前遍历，再加上单调递增栈，就是时间复杂度为 O(n) 的解决方案&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">--)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 取余即可获取当前需要处理的元素&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="c1">// 在单调栈不为空的情况下，将栈中小于等于当前元素的值都弹出&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(!&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isEmpty&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">peek&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="o">])&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">pop&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="c1">// 剩下元素既是比当前元素大的后继元素。为空则是没有更大元素&lt;/span>
 &lt;span class="c1">// 这里还有一个隐含变量：&lt;/span>
 &lt;span class="c1">// 由于栈是从后向前添加，则栈顶元素距离当前元素更近。&lt;/span>
 &lt;span class="c1">// 如果栈不为空，则栈顶元素就是符合条件的元素。&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isEmpty&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">peek&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">push&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="o">]);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
使用单调栈，一个关键点是确定使用的是单调递增栈，还是单调递减栈。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre>这里给大家留一个思考题：本文提供的答案是从后向前遍历数组。尝试一下从前向后遍历数组的解决方案。&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_实践_leetcode_42_接雨水">实践： LeetCode 42. 接雨水&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>下面再来看一下： &lt;a href="https://leetcode.com/problems/trapping-rain-water/description/" target="_blank" rel="noopener">LeetCode 42. 接雨水&lt;/a>。&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>给定 n 个非负整数表示每个宽度为 1 的柱子的高度图，计算按此排列的柱子，下雨之后能接多少雨水。&lt;/p>
&lt;/div></description></item><item><title>单调栈实践（一）：入门</title><link>https://www.diguage.com/post/monotonic-stack-in-practice-1/</link><pubDate>Thu, 01 Aug 2024 11:32:47 +0800</pubDate><guid>https://www.diguage.com/post/monotonic-stack-in-practice-1/</guid><description>&lt;div class="paragraph">
&lt;p>最近刷 LeetCode 算法题中，遇到了一些需要单调栈的题目，就顺便学习了一下单调栈。分享出来，以备后续深入学习。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>学习单调栈之前，先了解一些栈。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_栈_stack">栈 Stack&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>栈是一个众所周知的线性数据结构，它遵循先入后出（First In Last Out，简称 FILO）或后入先出（Last In First Out，简称 LIFO）的访问顺序。操作示意图如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/stack.gif" alt="入栈与出栈" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 入栈与出栈&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_单调栈_monotonic_stack">单调栈 Monotonic Stack&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>单调栈是一种特殊的栈，添加了一些限制条件：内部元素只能是递增或递减的顺序存储；添加元素时，如果新元素不符合单调性，则将其内部元素弹出，直到符合添加时，才添加元素。根据元素顺序，又可分为单调递增栈和单调递减栈。操作示意图如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/monotonically-increasing-stack.svg" alt="单调递增栈" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. 单调递增栈&lt;/div>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/monotonically-decreasing-stack.svg" alt="单调递减栈" width="95%"/>
&lt;/div>
&lt;div class="title">图 3. 单调递减栈&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="monotonic-stack-pseudocode">代码示例&lt;/h3>
&lt;div class="paragraph">
&lt;p>在写代码时，一般基于 &lt;code>Deque&lt;/code> 来实现，通常用到以下四个方法：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>deque.isEmpty()&lt;/code>：如果 &lt;code>deque&lt;/code> 不包含任何元素，则返回 &lt;code>true&lt;/code>，否则返回 &lt;code>false&lt;/code>。因为要栈顶元素在满足要求的时候要弹出，所以需要进行空栈判断。有些场景，可能栈一定不会空的时候，就不需要该方法进行空栈判断。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>deque.push(e)&lt;/code>：将元素 &lt;code>e&lt;/code> 入栈。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>deque.pop()&lt;/code>：将栈顶元素弹出，并返回当前弹出的栈顶元素。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>deque.peek()&lt;/code>：获取栈顶元素，不弹出。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="c1">// 定义一个单调栈&lt;/span>
&lt;span class="nc">Deque&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Integer&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">stack&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">LinkedList&lt;/span>&lt;span class="o">&amp;lt;&amp;gt;();&lt;/span>
&lt;span class="c1">// 第一个元素，直接添加&lt;/span>
&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">push&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">);&lt;/span> &lt;span class="c1">// 这里存的是数组下标&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 单调递增栈这里就是大于，即 nums[i] &amp;gt; nums[deque.peek()]&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">peek&lt;/span>&lt;span class="o">()])&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">push&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">peek&lt;/span>&lt;span class="o">()])&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">push&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="c1">// 此处除了入栈，在有些场景下，还有可能有其他操作&lt;/span>
 &lt;span class="c1">// ..............&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 循环比较，直到遇到当前元素小于栈顶的元素情况，跳出循环&lt;/span>
 &lt;span class="c1">// 单调递增栈，这里是小于，即nums[i] &amp;lt; nums[deque.peek()]&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(!&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isEmpty&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">nums&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">peek&lt;/span>&lt;span class="o">()])&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">//主要逻辑&lt;/span>
 &lt;span class="c1">// ............&lt;/span>
 &lt;span class="c1">// ............&lt;/span>
 &lt;span class="c1">// 弹出栈顶元素&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">pop&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="n">stack&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">push&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_应用_leetcode_155_最小栈">应用： LeetCode 155. 最小栈&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>来看一个 LeetCode 算法提： &lt;a href="https://leetcode.com/problems/min-stack/description/" target="_blank" rel="noopener">LeetCode 155. 最小栈&lt;/a>，D瓜哥愿意称之为单调栈入门最佳试题。&lt;/p>
&lt;/div></description></item><item><title>深入研究 BeanFactoryPostProcessor</title><link>https://www.diguage.com/post/dive-into-beanfactorypostprocessor/</link><pubDate>Sat, 01 Jun 2024 16:18:55 +0800</pubDate><guid>https://www.diguage.com/post/dive-into-beanfactorypostprocessor/</guid><description>&lt;div class="paragraph">
&lt;p>D瓜哥在 &lt;a href="https://www.diguage.com/post/spring-extensions-overview/" target="_blank" rel="noopener">Spring 扩展点概览及实践&lt;/a> 中概要性地介绍了一下 Spring 的核心扩展点。里面也提到了 &lt;code>BeanFactoryPostProcessor&lt;/code> 和 &lt;code>BeanDefinitionRegistryPostProcessor&lt;/code>，但仅仅提了一句，没有深入研究。在 &lt;a href="https://www.diguage.com/post/spring-extensions-and-mybatis/" target="_blank" rel="noopener">Spring 扩展点实践：整合 MyBATIS&lt;/a> 中，由于 &lt;code>MapperScannerConfigurer&lt;/code> 实现了 &lt;code>BeanDefinitionRegistryPostProcessor&lt;/code> 接口，也只是简单介绍了一些作用，又一次没有深入研究。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最近，在开发一个插件时，遇到了一个问题：&lt;strong>利用 &lt;code>BeanFactoryPostProcessor&lt;/code> 对一些特定 &lt;code>BeanDefinition&lt;/code> 设置属性，但生成的 Bean 却没有相关的属性值。&lt;/strong>由此，对 &lt;code>BeanFactoryPostProcessor&lt;/code> 做了一些研究。记录一下，以备不时之需。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_spring_启动流程简介">Spring 启动流程简介&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/spring-startup-process-overview/" target="_blank" rel="noopener">Spring 启动流程概述&lt;/a> 中，D瓜哥对 Spring 的启动流程做了比较详细的介绍。同时画了一张启动流程图，如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/spring-framework/AbstractApplicationContext-refresh.svg" alt="AbstractApplicationContext.refresh — 重塑容器" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. AbstractApplicationContext.refresh — 重塑容器&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从该图中可以明显看到，如果需要对 Spring 的 &lt;code>BeanDefinition&lt;/code> 做些修改，那么，就需要通过实现 &lt;code>BeanFactoryPostProcessor&lt;/code> 接口，来对 Spring 做些扩展。坦白讲，为了上述流程图只展示了一个非常概要性的流程。如果深入一下 &lt;code>invokeBeanFactoryPostProcessors&lt;/code> 方法的细节，会发现这又是一番天地。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_beanfactorypostprocessor_调用详解">&lt;code>BeanFactoryPostProcessor&lt;/code> 调用详解&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>D瓜哥把 &lt;code>invokeBeanFactoryPostProcessors&lt;/code> 方法的流程图也画了出来，细节如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/spring-framework/AbstractApplicationContext-invokeBeanFactoryPostProcessors.svg" alt="BeanDefinitionRegistryPostProcessor &amp;amp; BeanFactoryPostProcessor 调用过程" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. BeanDefinitionRegistryPostProcessor &amp;amp; BeanFactoryPostProcessor 调用过程&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从这张流程图上可以看出 &lt;code>BeanFactoryPostProcessor&lt;/code> 的调用过程，比在 &lt;a href="https://www.diguage.com/post/spring-startup-process-overview/" target="_blank" rel="noopener">Spring 启动流程概述&lt;/a> 中介绍的要复杂很多：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>首先，执行 &lt;code>BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry&lt;/code> 方法，顺序如下：&lt;/p>
&lt;div class="paragraph">
&lt;p>关于 &lt;code>BeanDefinitionRegistryPostProcessor&lt;/code> 的处理流程，D瓜哥在 &lt;a href="https://www.diguage.com/post/spring-extensions-overview/#bean-factory-post-processor" target="_blank" rel="noopener">Spring 扩展点概览及实践：BeanDefinitionRegistryPostProcessor&lt;/a> 中有更详细的描述，不了解的朋友请参考那篇文章的介绍。&lt;/p>
&lt;/div></description></item><item><title>生产环境中 Java 21 启动参数</title><link>https://www.diguage.com/post/java-21-boot-parameters-in-the-production/</link><pubDate>Mon, 13 May 2024 17:04:11 +0800</pubDate><guid>https://www.diguage.com/post/java-21-boot-parameters-in-the-production/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/upgrade-to-openjdk21/" target="_blank" rel="noopener">OpenJDK 21 升级指南&lt;/a> 中，给大家分享了一下升级到 OpenJDK 21 中遇到的一些问题。文末留了一个小问题：生产环境的 Java 21 启动参数怎么配置？这篇文章将给出 D瓜哥的答案。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>先说明一下生产环境的机器配置：4C8G，四个内核，8G 内存。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_启动参数">启动参数&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>鉴于 &lt;a href="https://www.diguage.com/post/gc-performance-incremental-qps/" target="_blank" rel="noopener">JVM GC 性能测试（二）：递增流量&lt;/a> 和 &lt;a href="https://www.diguage.com/post/gc-performance-real-qps/" target="_blank" rel="noopener">JVM GC 性能测试（三）：真实流量&lt;/a> 中，G1 GC 的惊艳表现，这里分别提供 Gen ZGC 和 G1 GC 两个配置。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
两个配置差距级小，为了方便复制粘贴，还是分两个来展示。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_gen_zgc_配置">Gen ZGC 配置&lt;/h3>
&lt;div class="paragraph">
&lt;p>追求极致低延迟，就上 GenZGC，它通过牺牲大约 10% 的吞吐量，换来无与伦比的低延时。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
注意：使用时，请修改日志目录！
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c">## 变量配置 #######################################################################&lt;/span>
&lt;span class="c"># java -XshowSettings:all --展示所有配置项（测试发现也不全）&lt;/span>
&lt;span class="nt">-Dfile&lt;/span>.encoding&lt;span class="o">=&lt;/span>UTF-8

&lt;span class="c"># &lt;a href="https://zhuanlan.zhihu.com/p/455313866" target="_blank">https://zhuanlan.zhihu.com/p/455313866&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://zhuanlan.zhihu.com/p/455746995" target="_blank">https://zhuanlan.zhihu.com/p/455746995&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://blog.csdn.net/u014149685/article/details/83002405" target="_blank">https://blog.csdn.net/u014149685/article/details/83002405&lt;/a>&lt;/span>
&lt;span class="c"># 随机数来源&lt;/span>
&lt;span class="nt">-Djava&lt;/span>.security.egd&lt;span class="o">=&lt;/span>file:/dev/./urandom
&lt;span class="nt">-Djava&lt;/span>.security&lt;span class="o">=&lt;/span>file:/dev/./urandom

&lt;span class="c"># &lt;a href="https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html" target="_blank">https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/net/doc-files/net-properties.html" target="_blank">https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/net/doc-files/net-properties.html&lt;/a>&lt;/span>
&lt;span class="c"># DNS 过期时间&lt;/span>
&lt;span class="nt">-Dnetworkaddress&lt;/span>.cache.ttl&lt;span class="o">=&lt;/span>10
&lt;span class="c">#&lt;/span>
&lt;span class="nt">-Dsun&lt;/span>.net.client.defaultConnectTimeout&lt;span class="o">=&lt;/span>60000
&lt;span class="nt">-Dsun&lt;/span>.net.client.defaultReadTimeout&lt;span class="o">=&lt;/span>60000
&lt;span class="c">#-Dsun.net.inetaddr.ttl=300&lt;/span>

&lt;span class="c"># &lt;a href="https://mdnice.com/writing/47e729bbf8e44431a396a481ed173dae" target="_blank">https://mdnice.com/writing/47e729bbf8e44431a396a481ed173dae&lt;/a>&lt;/span>
&lt;span class="nt">-Djava&lt;/span>.awt.headless&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>
&lt;span class="c"># &lt;a href="https://blog.csdn.net/maverick0/article/details/8282472" target="_blank">https://blog.csdn.net/maverick0/article/details/8282472&lt;/a>&lt;/span>
&lt;span class="nt">-Djmagick&lt;/span>.systemclassloader&lt;span class="o">=&lt;/span>no

&lt;span class="c"># From Cassandra&lt;/span>
&lt;span class="c"># On Java &amp;gt;= 9 Netty requires the io.netty.tryReflectionSetAccessible system property&lt;/span>
&lt;span class="c"># to be set to true to enable creation of direct buffers using Unsafe. Without it,&lt;/span>
&lt;span class="c"># this falls back to ByteBuffer.allocateDirect which has inferior performance and&lt;/span>
&lt;span class="c"># risks exceeding MaxDirectMemory&lt;/span>
&lt;span class="c"># &lt;a href="https://blog.csdn.net/jdcdev_/article/details/132843927" target="_blank">https://blog.csdn.net/jdcdev_/article/details/132843927&lt;/a>&lt;/span>
&lt;span class="nt">-Dio&lt;/span>.netty.tryReflectionSetAccessible&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>
&lt;span class="c"># 内部中间件&lt;/span>
&lt;span class="c"># 注意：一些中间件会内嵌 Netty，这里建议同步修改其相关参数配置。&lt;/span>
&lt;span class="nt">-Dump&lt;/span>.profiler.shade.io.netty.tryReflectionSetAccessible&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>
&lt;span class="nt">-Dtitan&lt;/span>.profiler.shade.io.netty.tryReflectionSetAccessible&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>

&lt;span class="c"># Revert changes in defaults introduced in &lt;a href="https://netty.io/news/2022/03/10/4-1-75-Final.html" target="_blank">https://netty.io/news/2022/03/10/4-1-75-Final.html&lt;/a>&lt;/span>
&lt;span class="nt">-Dio&lt;/span>.netty.allocator.useCacheForAllThreads&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>
&lt;span class="nt">-Dio&lt;/span>.netty.allocator.maxOrder&lt;span class="o">=&lt;/span>11

&lt;span class="c"># 内部中间件&lt;/span>
&lt;span class="c"># 理由上面已讲&lt;/span>
&lt;span class="nt">-Dump&lt;/span>.profiler.shade.io.netty.allocator.useCacheForAllThreads&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>
&lt;span class="nt">-Dump&lt;/span>.profiler.shade.io.netty.allocator.maxOrder&lt;span class="o">=&lt;/span>11

&lt;span class="c"># Byte Buddy 支持21&lt;/span>
&lt;span class="nt">-Dnet&lt;/span>.bytebuddy.experimental&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>
&lt;span class="nt">-Dpfinder&lt;/span>.shade.net.bytebuddy.experimental&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>



&lt;span class="c">## 参数配置 #####################################################################&lt;/span>

&lt;span class="c"># &lt;a href="https://jacoline.dev/inspect" target="_blank">https://jacoline.dev/inspect&lt;/a> -- JVM 参数诊断&lt;/span>
&lt;span class="c"># &lt;a href="https://chriswhocodes.com/corretto_jdk21_options.html" target="_blank">https://chriswhocodes.com/corretto_jdk21_options.html&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html" target="_blank">https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html&lt;/a>&lt;/span>

&lt;span class="c"># &lt;a href="https://blog.csdn.net/wxb880114/article/details/119888587" target="_blank">https://blog.csdn.net/wxb880114/article/details/119888587&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://www.cnblogs.com/three-fighter/p/14644152.html" target="_blank">https://www.cnblogs.com/three-fighter/p/14644152.html&lt;/a>&lt;/span>
&lt;span class="c">#- &lt;a href="https://www.skjava.com/article/2134434173" target="_blank">https://www.skjava.com/article/2134434173&lt;/a>&lt;/span>

&lt;span class="c"># 解锁诊断参数&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+UnlockDiagnosticVMOptions

&lt;span class="c"># 解锁试验参数&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+UnlockExperimentalVMOptions

&lt;span class="c"># 启用 ZGC&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+UseZGC
&lt;span class="c"># 启用分代ZGC&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+ZGenerational

&lt;span class="c"># &lt;a href="https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html" target="_blank">https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html&lt;/a>&lt;/span>
&lt;span class="c"># 加快 GC 的时间和能力&lt;/span>
&lt;span class="nt">-XX&lt;/span>:ZAllocationSpikeTolerance&lt;span class="o">=&lt;/span>5
&lt;span class="nt">-XX&lt;/span>:ConcGCThreads&lt;span class="o">=&lt;/span>2
&lt;span class="nt">-XX&lt;/span>:ParallelGCThreads&lt;span class="o">=&lt;/span>4

&lt;span class="c"># G1 GC&lt;/span>
&lt;span class="c">#-XX:+UseG1GC&lt;/span>
&lt;span class="c">#-XX:MaxGCPauseMillis=50&lt;/span>

&lt;span class="c"># 初始堆大小，等价于 -XX:InitialHeapSize&lt;/span>
&lt;span class="nt">-Xms4608m&lt;/span>

&lt;span class="c"># 弱最大堆，尽量保持，但是可以突破&lt;/span>
&lt;span class="c">#-XX:SoftMaxHeapSize=3g&lt;/span>

&lt;span class="c"># 最大堆大小，等价于 -XX:MaxHeapSize&lt;/span>
&lt;span class="nt">-Xmx4608m&lt;/span>

&lt;span class="c"># 归还未使用的内存&lt;/span>
&lt;span class="c">#-XX:+ZUncommit&lt;/span>

&lt;span class="c"># 设置每个线程的堆栈大小，等价于 -XX:ThreadStackSize=512k&lt;/span>
&lt;span class="nt">-Xss512k&lt;/span>

&lt;span class="c"># &lt;a href="https://cloud.tencent.com/developer/article/1408384" target="_blank">https://cloud.tencent.com/developer/article/1408384&lt;/a>&lt;/span>
&lt;span class="c"># 本地内存大小&lt;/span>
&lt;span class="nt">-XX&lt;/span>:MaxDirectMemorySize&lt;span class="o">=&lt;/span>512m

&lt;span class="c"># &lt;a href="https://cloud.tencent.com/developer/article/2277327" target="_blank">https://cloud.tencent.com/developer/article/2277327&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://cloud.tencent.com/developer/article/2277328" target="_blank">https://cloud.tencent.com/developer/article/2277328&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://cloud.tencent.com/developer/article/2277329" target="_blank">https://cloud.tencent.com/developer/article/2277329&lt;/a>&lt;/span>
&lt;span class="c"># 元空间&lt;/span>
&lt;span class="c"># 设置为 256m 时，发生过一次频繁 GC 导致应用无法相应的问题&lt;/span>
&lt;span class="nt">-XX&lt;/span>:MetaspaceSize&lt;span class="o">=&lt;/span>512m
&lt;span class="c"># 最大元空间&lt;/span>
&lt;span class="nt">-XX&lt;/span>:MaxMetaspaceSize&lt;span class="o">=&lt;/span>512m

&lt;span class="c"># &lt;a href="https://cloud.tencent.com/developer/article/1408773" target="_blank">https://cloud.tencent.com/developer/article/1408773&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://blog.csdn.net/lidf1992/article/details/75050219" target="_blank">https://blog.csdn.net/lidf1992/article/details/75050219&lt;/a>&lt;/span>
&lt;span class="c"># 编译代码缓存空间&lt;/span>
&lt;span class="nt">-XX&lt;/span>:ReservedCodeCacheSize&lt;span class="o">=&lt;/span>256m

&lt;span class="c"># &lt;a href="https://cloud.tencent.com/developer/article/1408827" target="_blank">https://cloud.tencent.com/developer/article/1408827&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://malloc.se/blog/zgc-jdk15" target="_blank">https://malloc.se/blog/zgc-jdk15&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://tinyzzh.github.io/java/jvm/2022/04/24/JVM_CompressedOops.html" target="_blank">https://tinyzzh.github.io/java/jvm/2022/04/24/JVM_CompressedOops.html&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://www.cnblogs.com/star95/p/17512212.html" target="_blank">https://www.cnblogs.com/star95/p/17512212.html&lt;/a> -- 由于从 JDK15 开始，&lt;/span>
&lt;span class="c"># -XX:+UseCompressedClassPointers 与 -XX:-UseCompressedOops 之间的强&lt;/span>
&lt;span class="c"># 关联被打破，文章里关于上述这种搭配是不正确的。 TODO 可以从新测试验证一线。&lt;/span>
&lt;span class="c"># TODO 如果开启 -XX:+UseCompressedClassPointers，不确定 32M 是否够用？&lt;/span>
&lt;span class="c"># &lt;a href="https://www.zhihu.com/question/268392125" target="_blank">https://www.zhihu.com/question/268392125&lt;/a>&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+UseCompressedClassPointers
&lt;span class="nt">-XX&lt;/span>:CompressedClassSpaceSize&lt;span class="o">=&lt;/span>48M

&lt;span class="c"># 关闭热度衰减&lt;/span>
&lt;span class="nt">-XX&lt;/span>:-UseCounterDecay

&lt;span class="c"># 内存占座&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+AlwaysPreTouch

&lt;span class="c"># 禁止代码中显示调用GC&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+DisableExplicitGC

&lt;span class="c"># 关闭安全点间隔&lt;/span>
&lt;span class="nt">-XX&lt;/span>:GuaranteedSafepointInterval&lt;span class="o">=&lt;/span>0

&lt;span class="c"># 避免循环无法进入安全点的问题&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+UseCountedLoopSafepoints
&lt;span class="c"># &lt;a href="https://blog.csdn.net/m0_46596655/article/details/123606813" target="_blank">https://blog.csdn.net/m0_46596655/article/details/123606813&lt;/a>&lt;/span>
&lt;span class="nt">-XX&lt;/span>:LoopStripMiningIter&lt;span class="o">=&lt;/span>1000

&lt;span class="c"># 打印命令行参数&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+PrintCommandLineFlags

&lt;span class="c"># 显式地并发处理 GC 调用&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+ExplicitGCInvokesConcurrent

&lt;span class="c"># &lt;a href="https://panlw.github.io/15320998566522.html" target="_blank">https://panlw.github.io/15320998566522.html&lt;/a>&lt;/span>
&lt;span class="nt">-XX&lt;/span>:AutoBoxCacheMax&lt;span class="o">=&lt;/span>20000

&lt;span class="c"># &lt;a href="https://blog.csdn.net/zshake/article/details/88796414" target="_blank">https://blog.csdn.net/zshake/article/details/88796414&lt;/a>&lt;/span>
&lt;span class="c"># 省略异常栈信息从而快速抛出&lt;/span>
&lt;span class="nt">-XX&lt;/span>:-OmitStackTraceInFastThrow

&lt;span class="c"># &lt;a href="https://www.jianshu.com/p/c9259953ca38" target="_blank">https://www.jianshu.com/p/c9259953ca38&lt;/a>&lt;/span>
&lt;span class="c"># 致命错误日志文件&lt;/span>
&lt;span class="nt">-XX&lt;/span>:ErrorFile&lt;span class="o">=&lt;/span>/path/to/log/jvm/hs_err_%p.log

&lt;span class="c"># &lt;a href="https://blog.csdn.net/lusa1314/article/details/84134458" target="_blank">https://blog.csdn.net/lusa1314/article/details/84134458&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://juejin.cn/post/7127557371932442632" target="_blank">https://juejin.cn/post/7127557371932442632&lt;/a>&lt;/span>
&lt;span class="c"># 当JVM发生OOM时，自动生成DUMP文件。&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+HeapDumpOnOutOfMemoryError
&lt;span class="c"># 设置上述DUMP文件路径&lt;/span>
&lt;span class="nt">-XX&lt;/span>:HeapDumpPath&lt;span class="o">=&lt;/span>/path/to/log/jvm/

&lt;span class="c"># &lt;a href="https://juejin.cn/post/6959405798556434440" target="_blank">https://juejin.cn/post/6959405798556434440&lt;/a>&lt;/span>
&lt;span class="c"># 设置 JFR 相关参数&lt;/span>
&lt;span class="c"># TODO 感觉这里不全乎，似乎需要 -XX:+FlightRecorder 来启用&lt;/span>
&lt;span class="c"># TODO 似乎可以设置文件，例如： -XX:StartFlightRecording=duration=200s,filename=flight.jfr&lt;/span>
&lt;span class="c"># 不确定文件名是否可以这样配置，测试一下_%p-%t&lt;/span>
&lt;span class="c"># Amazon Corretto JDK OK；Eclipse Temurin 不识别，并且监控报错&lt;/span>
&lt;span class="c">#-XX:StartFlightRecording=delay=5s,disk=true,dumponexit=true,duration=24h,maxage=5d,maxsize=2g,filename=/path/to/log/jvm/jfr_%p-%t.jfr.log&lt;/span>
&lt;span class="c">#-XX:FlightRecorderOptions=maxchunksize=128m&lt;/span>

&lt;span class="c">#-XX:StringDeduplicationAgeThreshold=threshold? TODO 测试之后才可以定&lt;/span>

&lt;span class="c"># &lt;a href="https://zhuanlan.zhihu.com/p/111886882" target="_blank">https://zhuanlan.zhihu.com/p/111886882&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://github.com/apache/cassandra/tree/trunk/conf" target="_blank">https://github.com/apache/cassandra/tree/trunk/conf&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://github.com/elastic/elasticsearch/blob/main/distribution/src/config/jvm.options" target="_blank">https://github.com/elastic/elasticsearch/blob/main/distribution/src/config/jvm.options&lt;/a>&lt;/span>
&lt;span class="c"># java -Xlog:help&lt;/span>
&lt;span class="c"># 日志配置&lt;/span>
&lt;span class="nt">-Xlog&lt;/span>:gc&lt;span class="k">*&lt;/span>&lt;span class="o">=&lt;/span>debug,stringdedup&lt;span class="k">*&lt;/span>&lt;span class="o">=&lt;/span>debug,heap&lt;span class="k">*&lt;/span>&lt;span class="o">=&lt;/span>trace,age&lt;span class="k">*&lt;/span>&lt;span class="o">=&lt;/span>debug,promotion&lt;span class="k">*&lt;/span>&lt;span class="o">=&lt;/span>trace,jit&lt;span class="k">*&lt;/span>&lt;span class="o">=&lt;/span>info,safepoint&lt;span class="k">*&lt;/span>&lt;span class="o">=&lt;/span>debug:file&lt;span class="o">=&lt;/span>/path/to/log/jvm/gc_%p-%t.log:time,pid,tid,level,tags:filecount&lt;span class="o">=&lt;/span>10,filesize&lt;span class="o">=&lt;/span>500M
&lt;span class="c"># 分开设置可用，使用分开的配置&lt;/span>
&lt;span class="c">#-Xlog:gc*=debug,stringdedup*=debug,heap*=trace,age*=debug,promotion*=trace:file=/path/to/log/jvm/gc-%t.log:utctime,level,tags:filecount=10,filesize=200M&lt;/span>
&lt;span class="c">#-Xlog:jit*=info:file=/path/to/log/jvm/jit_compile-%t.log:utctime,level,tags:filecount=10,filesize=50M&lt;/span>
&lt;span class="c">#-Xlog:safepoint*=debug:file=/path/to/log/jvm/safepoint-%t.log:utctime,level,tags:filecount=10,filesize=50M&lt;/span>


&lt;span class="c"># &lt;a href="https://stackoverflow.com/a/44059335" target="_blank">https://stackoverflow.com/a/44059335&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://openjdk.org/jeps/261" target="_blank">https://openjdk.org/jeps/261&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://www.diguage.com/post/upgrade-to-openjdk21/" target="_blank">https://www.diguage.com/post/upgrade-to-openjdk21/&lt;/a> -- 内有详细介绍&lt;/span>
&lt;span class="c"># 开启模块权限：下面是D瓜哥需要的模块，请根据自己实际需求来调整。&lt;/span>
&lt;span class="nt">--add-exports&lt;/span> java.base/sun.security.action&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.io&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.lang.reflect&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.lang&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.math&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.net&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.security&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.time&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.util.concurrent.locks&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.util.concurrent&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/java.util&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/jdk.internal.loader&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="c"># Netty 内部需要 &lt;a href="https://stackoverflow.com/a/57892679" target="_blank">https://stackoverflow.com/a/57892679&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://github.com/netty/netty/issues/7769" target="_blank">https://github.com/netty/netty/issues/7769&lt;/a>&lt;/span>
&lt;span class="c"># &lt;a href="https://blog.csdn.net/thewindkee/article/details/123618476" target="_blank">https://blog.csdn.net/thewindkee/article/details/123618476&lt;/a>&lt;/span>
&lt;span class="nt">--add-opens&lt;/span> java.base/jdk.internal.misc&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/sun.net.util&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="c"># 设置 -Dio.netty.tryReflectionSetAccessible=true 后，不设置该值也会报错&lt;/span>
&lt;span class="nt">--add-opens&lt;/span> java.base/java.nio&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/sun.nio.ch&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/sun.reflect.generics.reflectiveObjects&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.base/sun.util.calendar&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.management/com.sun.jmx.mbeanserver&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.management/java.lang.management&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> java.management/sun.management&lt;span class="o">=&lt;/span>ALL-UNNAMED
&lt;span class="nt">--add-opens&lt;/span> jdk.management/com.sun.management.internal&lt;span class="o">=&lt;/span>ALL-UNNAMED&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>使用 OpenRewrite 优化代码</title><link>https://www.diguage.com/post/optimize-code-using-openrewrite/</link><pubDate>Tue, 07 May 2024 17:48:49 +0800</pubDate><guid>https://www.diguage.com/post/optimize-code-using-openrewrite/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/upgrade-to-openjdk21/" target="_blank" rel="noopener">OpenJDK 21 升级指南&lt;/a> 中提到， OpenRewrite 可以帮忙解决一些升级 OpenJDK 中发现的问题。随着不断的探索，D瓜哥发现，OpenRewrite 的功能远远不止这些。下面就挑选一些重要的功能来给大家做一些讲解。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
为了方便查看改动点，建议将代码交给版本管理工具，比如 Git，来管理。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_快速入门">快速入门&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>OpenRewrite 是一套对源码做重构的大型生态系统，可以帮助开发人员减少技术债。所以，它提供了一套的相关工具。对于大多数开发人员来说，最方便的也许就是基于 Maven 插件的相关工具。这里以对 Java 的 &lt;code>import&lt;/code> 语句排序来为示例展示一下 OpenRewrite 的使用方法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在项目的 &lt;code>pom.xml&lt;/code> 中增加如下配置：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="xml">&lt;span class="c">&amp;lt;!-- @author: D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a> --&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;plugin&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.openrewrite.maven&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>rewrite-maven-plugin&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>5.30.0&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;configuration&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;activeRecipes&amp;gt;&lt;/span>
 &lt;span class="c">&amp;lt;!-- import 排序 --&amp;gt;&lt;/span>
 &lt;span class="c">&amp;lt;!-- &lt;a href="https://docs.openrewrite.org/recipes/java/orderimports" target="_blank">https://docs.openrewrite.org/recipes/java/orderimports&lt;/a> --&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;recipe&amp;gt;&lt;/span>org.openrewrite.java.OrderImports&lt;span class="nt">&amp;lt;/recipe&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/activeRecipes&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/plugin&amp;gt;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>然后执行如下命令：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">mvn rewrite:run&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>执行会输出一大堆东西，这里就不再展示，执行完成后，使用 Git 查看一下改动点。如下图：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/open-rewrite/order-imports.png" alt="使用 OpenRewrite 排序 import 的改动点" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 使用 OpenRewrite 排序 import 的改动点&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>将这些修改点提交，就完成了一次优化， OpenRewrite 的基本使用，你学废了吗？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这里再多说一句： 由于 OpenRewrite 精巧的设计，可以通过使用不同的处方，进行各种各样的优化。所以，最重要的一点就是了解 OpenRewrite 各种不同的处方及使用办法。下面就介绍一下常用的处方及使用办法。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_常用处方">常用处方&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="upgrade-java21">升级到 Java 21&lt;/h3>
&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/upgrade-to-openjdk21/" target="_blank" rel="noopener">OpenJDK 21 升级指南&lt;/a> 中提到，可以使用“科技与狠活”来解决很多升级中遇到的问题。这里就来实操一把。&lt;/p>
&lt;/div></description></item><item><title>OpenJDK 21 升级指南</title><link>https://www.diguage.com/post/upgrade-to-openjdk21/</link><pubDate>Mon, 06 May 2024 19:50:22 +0800</pubDate><guid>https://www.diguage.com/post/upgrade-to-openjdk21/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;a href="https://openjdk.org/projects/jdk/21/" target="_blank" rel="noopener">OpenJDK 21&lt;/a> 已经发布半年有余，在这个版本中， &lt;a href="https://openjdk.org/jeps/439" target="_blank" rel="noopener">Generational ZGC&lt;/a> 也一起发布了。在 &lt;a href="https://malloc.se/blog/zgc-jdk16" target="_blank" rel="noopener">ZGC | What’s new in JDK 16&lt;/a> 中， &lt;a href="https://malloc.se/" target="_blank" rel="noopener">Per Lidén&lt;/a> 宣称，将 ZGC 的最大停顿时间从 10ms 降低到了 1ms。再加上 &lt;a href="https://www.diguage.com/post/gc-performance-incremental-qps/" target="_blank" rel="noopener">JVM GC 性能测试（二）：递增流量&lt;/a> 和 &lt;a href="https://www.diguage.com/post/gc-performance-real-qps/" target="_blank" rel="noopener">JVM GC 性能测试（三）：真实流量&lt;/a> 文中，GenZGC 的惊艳表现，这些种种先进技术，着实充满诱惑，忍不住想吃口螃蟹 🦀。这篇文章，D瓜哥就来分享一下，自己在升级 OpenJDK 21 中的一些经验。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
本文仅介绍升级 OpenJDK 的相关内容，ZGC 原理等会专门撰文介绍。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_升级依赖">升级依赖&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>依赖升级不是 KPI，也不涉及需求交付。所以，大多数项目的依赖自从项目创建后，就很少升级。如果想比较顺利地将项目升级到 OpenJDK 21，那么，先将项目所用依赖做一个整体升级是一个事半功倍的操作。可以直接使用 Maven 命令来检查依赖可以升级的情况：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">mvn versions:display-dependency-updates&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>执行该命令后，会有如下类似输出：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># 检查依赖升级情况&lt;/span>
&lt;span class="nv">$ &lt;/span>mvn versions:display-dependency-updates

&lt;span class="c"># 此处省略一万个字&lt;/span>
&lt;span class="c"># @author: D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>&lt;/span>

&lt;span class="o">[&lt;/span>INFO] org.springframework:spring-aop ......... 5.3.33 -&amp;gt; 6.1.6
&lt;span class="o">[&lt;/span>INFO] org.springframework:spring-aspects ..... 5.3.33 -&amp;gt; 6.1.6
&lt;span class="o">[&lt;/span>INFO] org.springframework:spring-beans ....... 5.3.33 -&amp;gt; 6.1.6
&lt;span class="o">[&lt;/span>INFO] org.springframework:spring-context ..... 5.3.33 -&amp;gt; 6.1.6
&lt;span class="o">[&lt;/span>INFO] org.springframework:spring-core ........ 5.3.33 -&amp;gt; 6.1.6
&lt;span class="o">[&lt;/span>INFO] org.springframework:spring-jdbc ........ 5.3.33 -&amp;gt; 6.1.6
&lt;span class="o">[&lt;/span>INFO] org.springframework:spring-web ......... 5.3.33 -&amp;gt; 6.1.6

&lt;span class="o">[&lt;/span>INFO] org.mybatis:mybatis-2-spring ............ 1.1.0 -&amp;gt; 1.2.0
&lt;span class="o">[&lt;/span>INFO] org.mybatis:mybatis-spring .............. 2.1.1 -&amp;gt; 2.1.2

&lt;span class="o">[&lt;/span>INFO] org.junit.jupiter:junit-jupiter ........ 5.9.3 -&amp;gt; 5.10.2
&lt;span class="o">[&lt;/span>INFO] org.junit.jupiter:junit-jupiter-api .... 5.9.3 -&amp;gt; 5.10.2&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>JVM GC 性能测试（三）：真实流量</title><link>https://www.diguage.com/post/gc-performance-real-qps/</link><pubDate>Wed, 01 May 2024 12:12:12 +0800</pubDate><guid>https://www.diguage.com/post/gc-performance-real-qps/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;strong>JVM GC 性能测试系列&lt;/strong>:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-comparison-method/" target="_blank" rel="noopener">JVM GC 性能对比方法&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-same-qps/" target="_blank" rel="noopener">JVM GC 性能测试（一）：相同流量&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-incremental-qps/" target="_blank" rel="noopener">JVM GC 性能测试（二）：递增流量&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-real-qps/" target="_blank" rel="noopener">JVM GC 性能测试（三）：真实流量&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>书接上文，在 &lt;a href="https://www.diguage.com/post/gc-performance-incremental-qps/" target="_blank" rel="noopener">JVM GC 性能测试（二）：递增流量&lt;/a> 的最后，D瓜哥提到了一个问题，对于在 &lt;a href="https://www.diguage.com/post/gc-performance-same-qps/" target="_blank" rel="noopener">JVM GC 性能测试（一）：相同流量&lt;/a> 和 &lt;a href="https://www.diguage.com/post/gc-performance-incremental-qps/" target="_blank" rel="noopener">JVM GC 性能测试（二）：递增流量&lt;/a> 中存在的巨大 QPS 差异疑惑不解。所以，D瓜哥决定将测试机器接入到线上环境，在真实访问中，观察各个 GC 的表现。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_一言以蔽之">一言以蔽之&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>J21-Gen-ZGC 和 J21-G1 无论在稳定性，吞吐量以及响应时效性上都非常优秀。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>再极端峰值情况，J21-G1 是更好的选择，更加稳定，不容易出凸点。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>日常使用，J21-Gen-ZGC 响应性更好，接口耗时更低。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>鉴于 OpenJDK 21 G1 GC 一如既往的惊艳表现，D瓜哥准备整理一下 G1 GC 的主要优化，敬请关注： &lt;a href="https://www.diguage.com/post/main-optimization-of-garbage-first-collector/" target="_blank" rel="noopener">Java G1 垃圾收集器主要优化&lt;/a>。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_服务调用监控数据">1. 服务调用监控数据&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>监控服务调用的相关数据，这是对于用户来说，感知最强烈的相关数据，也是直接关系到服务质量的数据。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_服务调用次数">1.1. 服务调用次数&lt;/h3>
&lt;div class="paragraph">
&lt;p>从调用次数上来看，五个分组没有大的变化，可以说根本没有达到系统的极限峰值。当然，这才是正常现象，如果日常运行都爆峰值，那说明系统早该扩容了。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/gc-performance-3/api-qps-second.jpg" alt="服务调用次数（秒级）" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 服务调用次数（秒级）&lt;/div>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/gc-performance-3/api-qps-minute.jpg" alt="服务调用次数（分钟级）" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. 服务调用次数（分钟级）&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="api-qps-time">1.2. 服务调用耗时&lt;/h3>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>整体上讲，J21-Gen-ZGC 的耗时更短，从数据上来看，TP999 能比 J21-G1 的少 10~20ms；TP99 更加夸张，J21-Gen-ZGC 的耗时只有 J21-G1 的一半。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>J21-Gen-ZGC 和 J21-G1 还是一如既往的稳。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>这次测试中，J17-ZGC 也很稳，有些出乎意料。但是，结合下面 &lt;a href="#jvm-cpu">JVM CPU 使用率&lt;/a> 和 &lt;a href="#os-cpu">系统 CPU 使用率&lt;/a> 来看，J17-ZGC 和 J21-ZGC 的 CPU 使用率早早就达到 90%+，再结合上面两个测试，从稳定性来看，J17-ZGC 和 J21-ZGC 只能被排除掉。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>JVM GC 性能测试（二）：递增流量</title><link>https://www.diguage.com/post/gc-performance-incremental-qps/</link><pubDate>Tue, 30 Apr 2024 00:29:00 +0800</pubDate><guid>https://www.diguage.com/post/gc-performance-incremental-qps/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;strong>JVM GC 性能测试系列&lt;/strong>:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-comparison-method/" target="_blank" rel="noopener">JVM GC 性能对比方法&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-same-qps/" target="_blank" rel="noopener">JVM GC 性能测试（一）：相同流量&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-incremental-qps/" target="_blank" rel="noopener">JVM GC 性能测试（二）：递增流量&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-real-qps/" target="_blank" rel="noopener">JVM GC 性能测试（三）：真实流量&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/gc-performance-same-qps/" target="_blank" rel="noopener">JVM GC 性能测试（一）：相同流量&lt;/a> 中，D瓜哥使用一个总量请求对所有分组的所有机器进行性能测试。但是，经过测试发现了一个问题，同时产生了另外一个问题，有两个问题没有得到很好的解答：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>由于服务响应时长直接关系到服务调用次数，当某一台机器出现问题时，整体调用次数就会急剧下降，调用次数加不上去。一个机器出问题，所有机器的访问量就上不去了。这是测试中发现的一个问题。当然，这属于测试工具的问题，别不是 GC 的问题。但是，也影响到我们的压测，也需要解决。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>上次测试，这是针对某一个指定服务调用量进行性能测试，那么，无法确定每个 GC 能支撑的极限调用峰值。另外，在极限峰值和超极限峰值的情况下，各个 GC 的表现如何？这个也有待验证。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>针对上述两个问题，设计了本次测试。测试方法如下：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>各个分组使用一套相同的流量策略：&lt;/p>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>各个分组几乎同时开始执行测试任务；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>调用量从低到高，以此同时使用相关的调用量进行测试；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>除最开始预热阶段的调用量外，后续每个调用量都持续进行十分钟的测试。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>针对每个 GC 分组单独设定一套调用发量程序，这个保证各个 GC 分组直接不相互影响。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>最后，再分析调用量相同时段的各个 GC 表现，就可以看到各个 GC 的极限峰值。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
为了保留更多细节，本文所有截图都是在 34 吋带鱼屏下，使用全屏模式展示并截图的。如果看不清楚，可以右击在新页面打开图片来查看。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="paragraph">
&lt;p>具体流量及时间段：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>750， 23:14:30 ~ 23:19:30&lt;/p>
&lt;/li>
&lt;li>
&lt;p>800， 23:19:30 ~ 23:29:30&lt;/p>
&lt;/li>
&lt;li>
&lt;p>850， 23:29:30 ~ 23:39:30&lt;/p>
&lt;/li>
&lt;li>
&lt;p>900， 23:39:30 ~ 23:49:30&lt;/p>
&lt;/li>
&lt;li>
&lt;p>950， 23:49:30 ~ 23:59:30&lt;/p>
&lt;/li>
&lt;li>
&lt;p>1000，23:59:30 ~ 00:09:30&lt;/p>
&lt;/li>
&lt;li>
&lt;p>1050，00:09:30 ~ 00:19:30&lt;/p>
&lt;/li>
&lt;li>
&lt;p>1100，00:19:30 ~ 00:29:30&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>JVM GC 性能测试（一）：相同流量</title><link>https://www.diguage.com/post/gc-performance-same-qps/</link><pubDate>Thu, 25 Apr 2024 22:02:47 +0800</pubDate><guid>https://www.diguage.com/post/gc-performance-same-qps/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;strong>JVM GC 性能测试系列&lt;/strong>:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-comparison-method/" target="_blank" rel="noopener">JVM GC 性能对比方法&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-same-qps/" target="_blank" rel="noopener">JVM GC 性能测试（一）：相同流量&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-incremental-qps/" target="_blank" rel="noopener">JVM GC 性能测试（二）：递增流量&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-real-qps/" target="_blank" rel="noopener">JVM GC 性能测试（三）：真实流量&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/gc-performance-comparison-method/" target="_blank" rel="noopener">JVM GC 性能对比方法&lt;/a> 介绍了性能对比的方法，这篇文章就根据该方法对上述提到的5种 JVM GC 进行性能测试。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在正式测试之前，D瓜哥进行了多次小流量试探性测试，来探索一个合适的量。找到一个比较平稳的量后，乘以机器数量，获得一个每秒总计请求量，最后使用该总量数据去做压测。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>根据多次测试的数据来看，最后选择的是每台每秒 500 QPS，5 个分组，每个分组 5 台机器，所以，每秒的请求总量是： 500 * 5 * 5 = 12500 QPS；每个分组每分钟的总量是：500 * 5 * 60 = 150000 QPS。使用每台机器以此使用 100 QPS，200 QPS，300 QPS，400 QPS 各运行一分钟来对系统进行预热。最后以每台每秒 500 QPS 的访问量来对测试机器进行持续十分钟的性能测试，最后分析这十分钟的相关数据。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_一言以蔽之">一言以蔽之&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>服务稳定性：J21-Gen-ZGC、J21-G1、J8-G1 稳定性最好；J17-ZGC 有轻微波动；J21-ZGC 有剧烈波动；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>服务耗时 TP999：J21-Gen-ZGC &amp;lt; J17-ZGC &amp;lt; J21-G1 &amp;lt; J8-G1 &amp;lt; J21-ZGC；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>CPU 消耗：J21-G1 &amp;lt; J8-G1 &amp;lt; J17-ZGC &amp;lt; J21-Gen-ZGC &amp;lt; J21-ZGC；&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>JVM GC 性能对比方法</title><link>https://www.diguage.com/post/gc-performance-comparison-method/</link><pubDate>Wed, 24 Apr 2024 19:52:47 +0800</pubDate><guid>https://www.diguage.com/post/gc-performance-comparison-method/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;strong>JVM GC 性能测试系列&lt;/strong>:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-comparison-method/" target="_blank" rel="noopener">JVM GC 性能对比方法&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-same-qps/" target="_blank" rel="noopener">JVM GC 性能测试（一）：相同流量&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-incremental-qps/" target="_blank" rel="noopener">JVM GC 性能测试（二）：递增流量&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/gc-performance-real-qps/" target="_blank" rel="noopener">JVM GC 性能测试（三）：真实流量&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>现在部门内部绝大部分应用都还在使用 OpenJDK 8，计划推进部门升级 JDK 到 OpenJDK21。本着实事求是，用数据说话的原则，准备对如下 GC 做性能测试：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>OpenJDK 8 G1 GC（以下称 J8-G1。具体版本号：1.8.0_321-b07。）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>OpenJDK 17 ZGC（以下称 J17-ZGC。具体版本号：17.0.9+9。）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>OpenJDK 21 G1（以下称 J21-G1。具体版本号：21.0.2+13-LTS。）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>OpenJDK 21 ZGC（以下称 J21-ZGC。具体版本号：21.0.2+13-LTS。）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>OpenJDK 21 Gen ZGC（以下称 J21-Gen-ZGC。具体版本号：21.0.2+13-LTS。）&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="admonitionblock note">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-note" title="Note">&lt;/i>
&lt;/td>
&lt;td class="content">
所有 OpenJDK 版本都是选用相同大版本号里的最高的版本。所有的机器都是 4C8G 的配置，JVM 堆栈内存设置为 4608M 。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="paragraph">
&lt;p>为了减少不必要的干扰，JVM 相关参数也尽可能做到了一致或者接近。（等测试完，D瓜哥会把相关参数也分享出来。）&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_测试对象">测试对象&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>由于D瓜哥所处的部门是一个直接面向用户的线上业务部门，所以，大部分系统是直接面对用户，接受用户访问的在线业务系统。所以，为了服务线上业务系统的需求，测试对象的选择就限定在了类似的场景中。测试对象是线上接受用户访问的一个服务。结构如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/gc-performance/api.svg" alt="压测接口依赖关系图" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 压测接口依赖关系图&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>该接口有外部依赖服务，也有数据库查询，是一个微服务架构下典型的在线服务接口。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_测试方法">测试方法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>原本计划是想直接通过上线，将线上不同分组的机器使用不同的 GC 来做测试，但是，这样面临好几个问题：&lt;/p>
&lt;/div></description></item><item><title>JVM 剖析花园：2 - 透明大页</title><link>https://www.diguage.com/post/jvm-anatomy-quarks-02-transparent-huge-pages/</link><pubDate>Sat, 13 Jan 2024 19:24:37 +0800</pubDate><guid>https://www.diguage.com/post/jvm-anatomy-quarks-02-transparent-huge-pages/</guid><description>&lt;div class="sect1">
&lt;h2 id="_问题">问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>什么是大页（Large Page）？什么是透明大页（Transparent Huge Page）？它对我有什么帮助？&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_理论">理论&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>虚拟内存现在已被视为理所当然。现在只有少数人还记得，更不用说做一些“真实模式”编程了，在这种情况下，你会接触到实际的物理内存。相反，每个进程都有自己的虚拟内存空间，该空间被映射到实际内存上。例如，两个进程在相同的虚拟地址 &lt;code>0x42424242&lt;/code> 上拥有不同的数据，而这些数据将由不同的物理内存支持。现在，当程序访问该地址时，应将虚拟地址转换为物理地址。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/os/virtual-address-space-and-physical-address-space-relationship.svg" alt="虚拟内存地址与物理内存地址之间的关系" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 虚拟内存地址与物理内存地址之间的关系&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这通常由操作系统维护 &lt;a href="https://en.wikipedia.org/wiki/Page_table" target="_blank" rel="noopener">“页表”&lt;/a>，硬件通过“页表遍历”来实现地址转换。如果在页面粒度上维护翻译，整个过程就会变得简单。但这样做的成本并不低，而且每次内存访问都需要这样做！因此，还需要对最新的翻译进行小型缓存，即 &lt;a href="https://en.wikipedia.org/wiki/Translation_lookaside_buffer" target="_blank" rel="noopener">转译后备缓冲区（Translation Lookaside Buffer (TLB)）&lt;/a>。TLB 通常很小，只有不到 100 个条目，因为它的速度至少要与 L1 缓存相当，甚至更快。对于许多工作负载来说，TLB 未命中和相关的页表遍历需要大量时间。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>既然我们无法将 TLB 做得更大，那么我们可以做其他事情：制作更大的页面！大多数硬件有 4K 基本页和 2M/4M/1G “大页”。用更大的页来覆盖相同的区域，还能使页表本身更小，从而降低页表遍历的成本。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 Linux 世界中，至少有两种不同的方法可以在应用程序中实现这一点：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt" target="_blank" rel="noopener">&lt;strong>hugetlbfs&lt;/strong>&lt;/a>。切出系统内存的一部分，将其作为虚拟文件系统公开，让应用程序通过 &lt;code>mmap(2)&lt;/code> 从其中获取。这是一个特殊的接口，需要操作系统配置和应用程序更改才能使用。这也是一种“要么全有，要么全无”的交易：分配给 &lt;code>hugetlbfs&lt;/code>（持久部分）的空间不能被普通进程使用。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.kernel.org/doc/Documentation/vm/transhuge.txt" target="_blank" rel="noopener">&lt;strong>透明大页（Transparent Huge Pages (THP)）&lt;/strong>&lt;/a>。让应用程序像往常一样分配内存，但尽量以透明方式为应用程序提供大容量页面支持的存储空间。理想情况下，不需要更改应用程序，但我们会看到应用程序如何从了解 THP 的可用性中获益。但在实际应用中，会产生内存开销（因为会为小文件分配整个大页面）或时间开销（因为 THP 有时需要对内存进行碎片整理以分配页面）。好在有一个中间方案：通过 &lt;code>madvise(2)&lt;/code> 可以让应用程序告诉 Linux 在哪里使用 THP。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>不明白为什么术语中会交替使用 &amp;#34;large &amp;#34;和 &amp;#34;huge&amp;#34;。总之，OpenJDK 支持这两种模式：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="nv">$ &lt;/span>java &lt;span class="nt">-XX&lt;/span>:+PrintFlagsFinal 2&amp;gt;&amp;amp;1 | &lt;span class="nb">grep &lt;/span>Huge
 bool UseHugeTLBFS &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> &lt;span class="o">{&lt;/span>product&lt;span class="o">}&lt;/span> &lt;span class="o">{&lt;/span>default&lt;span class="o">}&lt;/span>
 bool UseTransparentHugePages &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> &lt;span class="o">{&lt;/span>product&lt;span class="o">}&lt;/span> &lt;span class="o">{&lt;/span>default&lt;span class="o">}&lt;/span>
&lt;span class="nv">$ &lt;/span>java &lt;span class="nt">-XX&lt;/span>:+PrintFlagsFinal 2&amp;gt;&amp;amp;1 | &lt;span class="nb">grep &lt;/span>LargePage
 bool UseLargePages &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span> &lt;span class="o">{&lt;/span>pd product&lt;span class="o">}&lt;/span> &lt;span class="o">{&lt;/span>default&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>JVM 剖析花园：1 - 锁粗化及循环</title><link>https://www.diguage.com/post/jvm-anatomy-quarks-01-lock-coarsening-for-loops/</link><pubDate>Sat, 13 Jan 2024 16:08:36 +0800</pubDate><guid>https://www.diguage.com/post/jvm-anatomy-quarks-01-lock-coarsening-for-loops/</guid><description>&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>“JVM 剖析花园”是由 JVM 研发专家及性能极客 &lt;a href="https://shipilev.net/" target="_blank" rel="noopener">Aleksey Shipilëv&lt;/a> 撰写的一个系列文章，专门介绍一些有关 JVM 的基本知识。笔者也是前几年无意间发现的一片宝藏文章。早就有翻译过来，介绍给大家的想法，可惜一直未能付诸实践。最近在查资料时，无意间又翻到了这个系列，遂下定决心，完成这个萌发已久的小想法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>为了便于理解，对该系列的名字做了微调，原文是“JVM Anatomy Quarks”，将原文的“Quarks”（夸克）翻译为了“花园”。&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“JVM 解剖花园”是一个正在进行中的小型系列文章，每篇文章都会介绍一些有关 JVM 的基本知识。这个名字强调了一个事实，即单篇文章不能孤立地看待，这里描述的大部分内容都会很容易地相互影响。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>阅读这篇文章大约需要 5-10 分钟。因此，它只针对单一主题、单一测试、单一基准和单一观察进行深入探讨。这里的证据和讨论可能是轶事，并没有对错误、一致性、写作风格、语法和语义错误、重复或一致性进行实际审查。请自行承担使用和/或信任的风险。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock note">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-note" title="Note">&lt;/i>
&lt;/td>
&lt;td class="content">
以上是该系列介绍。这里介绍一次，后续文章不再赘述。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_问题">问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>众所周知，Hotspot 可以进行 &lt;a href="https://en.wikipedia.org/wiki/Java_performance#Escape_analysis_and_lock_coarsening" target="_blank" rel="noopener">锁粗化优化&lt;/a>，有效合并多个相邻的锁定块，从而减少锁定开销。它能有效地对如下代码做优化：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="kd">synchronized&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// statements 1&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;span class="kd">synchronized&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// statements 2&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>优化后：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="kd">synchronized&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// statements 1&lt;/span>
 &lt;span class="c1">// statements 2&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>现在，今天提出的一个有趣问题是：Hotspot 是否会对循环进行这种优化？例如：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="k">for&lt;/span> &lt;span class="o">(...)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kd">synchronized&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// something&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>是否会被优化成如下这样：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="kd">synchronized&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(...)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// something&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>理论上，没有什么能阻止我们这样做。我们甚至可以把这种优化看作是 &lt;a href="https://en.wikipedia.org/wiki/Loop_unswitching" target="_blank" rel="noopener">循环判断外提&lt;/a>，只不过这里是针对锁而已。然而，这样做的缺点是有可能使锁变得过于粗糙，从而导致特定线程在执行大循环时占用锁。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_实验">实验&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>要回答这个问题，最简单的方法就是找到当前 Hotspot 优化的正面证据。幸运的是，有了 &lt;a href="https://github.com/openjdk/jmh" target="_blank" rel="noopener">JMH&lt;/a>，这一切都变得非常简单。它不仅有助于建立基准，还有助于工程中最重要的部分—​基准分析。让我们从一个简单的基准检查程序开始：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="nd">@Fork&lt;/span>&lt;span class="o">(...,&lt;/span> &lt;span class="n">jvmArgsPrepend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">{&lt;/span>&lt;span class="s">&amp;#34;-XX:-UseBiasedLocking&amp;#34;&lt;/span>&lt;span class="o">})&lt;/span>
&lt;span class="nd">@State&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">Scope&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">Benchmark&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">LockRoach&lt;/span> &lt;span class="o">{&lt;/span> &lt;i class="conum" data-value="1">&lt;/i>&lt;b>(1)&lt;/b>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="o">;&lt;/span>

 &lt;span class="nd">@Benchmark&lt;/span>
 &lt;span class="nd">@CompilerControl&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">CompilerControl&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">Mode&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">DONT_INLINE&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">test&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">1000&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="o">++)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kd">synchronized&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">x&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mh">0x42&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="colist arabic">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td>&lt;i class="conum" data-value="1">&lt;/i>&lt;b>1&lt;/b>&lt;/td>
&lt;td>完整代码在 &lt;a href="https://shipilev.net/jvm/anatomy-quarks/1-lock-coarsening-for-loops/LockRoach.java" target="_blank" rel="noopener">这里&lt;/a>。&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这里有几个重要的技巧：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>使用 &lt;code>-XX:-UseBiasedLocking&lt;/code> 禁用偏向锁可以避免更长的预热时间，因为偏向锁不会立即启动，而是会在初始化阶段等待 5 秒（参见 &lt;code>BiasedLockingStartupDelay&lt;/code> 选项）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>禁用 &lt;code>@Benchmark&lt;/code> 的方法内联有助于在反汇编时将其分离。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>增加一个神奇的数字 &lt;code>0x42&lt;/code>，有助于在反汇编中快速找到增量。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Spring Boot 应用程序浪费的内存</title><link>https://www.diguage.com/post/memory-wasted-by-spring-boot-application/</link><pubDate>Mon, 08 Jan 2024 23:12:14 +0800</pubDate><guid>https://www.diguage.com/post/memory-wasted-by-spring-boot-application/</guid><description>&lt;div class="paragraph">
&lt;p>当今世界被广泛浪费的资源之一是：内存。由于编程效率低下，内存浪费量惊人（有时 &amp;#34;令人震惊&amp;#34;）。我们在多个企业应用程序中都看到了这种情况。为了证明这一点，我们进行了一项小型研究。我们分析了著名的 Spring Boot Pet Clinic 应用程序，看看它浪费了多少内存。该应用程序由社区设计，旨在展示如何使用 Spring 应用程序框架构建简单但功能强大的面向数据库的应用程序。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_环境">环境&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>Spring Boot 2.1.4.RELEASE&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Java SDK 1.8&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Tomcat 8.5.20&lt;/p>
&lt;/li>
&lt;li>
&lt;p>MySQL 5.7.26 with MySQL Connector/J 8.0.15&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_压力测试">压力测试&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>我们使用流行的开源压力测试工具 &lt;a href="https://jmeter.apache.org/" target="_blank" rel="noopener">Apache JMeter&lt;/a> 进行压力测试。我们使用以下设置执行了 30 分钟的压力测试：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>线程数（用户&lt;/strong>）- 1000（连接到目标的用户数量）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>上升周期（秒）&lt;/strong> - 10。所有请求开始的时间范围。根据我们的配置，每 0.01 秒将启动 1 个新线程，即 100 个线程/秒。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>循环次数&lt;/strong> - 永久。这 1000 个线程将背靠背执行测试迭代。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>持续时间（秒）&lt;/strong> - 1800。启动后，1000 个线程持续运行 1800 秒。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/gc-tuning/jmeter-setting.png" alt="JMeter 设置" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. JMeter 设置&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我们在负载测试中使用了以下场景：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>在系统中添加新的宠物主人。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>查看宠物主人的相关信息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>向系统中添加一只新宠物。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>查看宠物相关信息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在宠物探视历史中添加探视信息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>更新宠物相关信息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>更新宠物主人的相关信息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>通过搜索主人姓名查看主人信息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>查看所有主人的信息。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_如何测量内存浪费">如何测量内存浪费？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>业界有数百种工具可以显示&lt;strong>内存使用量&lt;/strong>。但是，我们很少遇到能测量因低效编程而浪费的内存量的工具。 &lt;a href="https://heaphero.io/" target="_blank" rel="noopener">HeapHero&lt;/a> 是一款简单的工具，它可以分析堆转储，并告诉我们由于编程效率低下而浪费了多少内存。&lt;/p>
&lt;/div></description></item><item><title>GC 调优成功案例：减少新生代的大小</title><link>https://www.diguage.com/post/gc-tuning-success-story-reducing-young-gen-size/</link><pubDate>Sat, 06 Jan 2024 16:51:18 +0800</pubDate><guid>https://www.diguage.com/post/gc-tuning-success-story-reducing-young-gen-size/</guid><description>&lt;div class="paragraph">
&lt;p>当对垃圾回收性能做调优时，不仅能改善垃圾回收暂停时间，还能改善整个应用程序的响应时间并降低云计算成本。最近，我们帮助调整了一个流行应用程序的垃圾回收行为。仅仅是一个微小的改动，就带来了巨大的改善。让我们在这篇文章中讨论一下这个垃圾回收调整的成功案例。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_垃圾收集关键绩效指标">垃圾收集关键绩效指标&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>有句名言叫“无法衡量的东西就无法优化”。说到垃圾回收的调整，您只需关注 3 个主要关键绩效指标 (KPI)：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>GC 暂停时间&lt;/p>
&lt;/li>
&lt;li>
&lt;p>GC 吞吐量&lt;/p>
&lt;/li>
&lt;li>
&lt;p>CPU 消耗量&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>垃圾回收运行时，会暂停应用程序。“GC 停顿时间”表示应用程序在垃圾回收事件中停顿的时间。该指标以秒或毫秒为单位。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“GC 吞吐量”表示应用程序处理客户事务的总时间与处理垃圾回收活动的总时间之比。该指标以百分比为单位。例如，如果有人说他的应用程序的 GC 吞吐量是 98%，这表明该应用程序有 98% 的时间用于处理客户活动，其余 2% 的时间用于处理垃圾回收活动。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>即使是处理一个简单的请求，现代应用程序也会创建成千上万个对象。因此，垃圾收集器必须在后台不断运行，以释放为每个请求创建的成千上万个对象。因此，垃圾回收往往会消耗大量的 CPU。因此，在调整垃圾回收性能时，还应研究 CPU 消耗。要了解有关这些 KPI 的更多信息，请参阅： 内存调整： &lt;a href="https://www.diguage.com/post/garbage-collection-kpi/" target="_blank" rel="noopener">关键性能指标&lt;/a>。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_如何获取这些_kpi">如何获取这些 KPI？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在调优垃圾回收性能时，垃圾回收日志是您最好的朋友。您可以通过 &lt;a href="https://www.diguage.com/post/how-to-do-gc-log-analysis/" target="_blank" rel="noopener">这篇文章&lt;/a> 给出的 JVM 参数在应用程序中启用垃圾回收日志。建议始终开启垃圾回收日志，因为它能提供丰富的信息，有助于预测中断、排除生产问题并帮助进行容量规划。此外，启用垃圾收集不会给应用程序增加任何明显的开销。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>启用垃圾收集日志后，您可以使用免费的垃圾收集日志分析工具，如 &lt;a href="https://gceasy.io/" target="_blank" rel="noopener">GCeasy&lt;/a>、 &lt;a href="https://www.ibm.com/support/pages/java-sdk" target="_blank" rel="noopener">IBM GC &amp;amp; Memory visualizer&lt;/a> 和 &lt;a href="https://github.com/mgm3746/garbagecat" target="_blank" rel="noopener">Google Garbage cat&lt;/a> 等，查看上述关键绩效指标。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在下面这篇文章，教你 &lt;a href="https://www.diguage.com/post/how-to-do-gc-log-analysis/" target="_blank" rel="noopener">如何进行 GC 日志分析&lt;/a>。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_垃圾回收行为基线">垃圾回收行为基线&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>介绍到此为止。让我们回到本文最初的主题。我们在这个流行的应用程序上启用了垃圾回收日志。我们让应用程序运行了 24 小时。然后，我们将生成的 GC 日志文件上传到 GCeasy 工具。该工具提供了具有洞察力的图表和 GC KPI。该应用程序的 GC 吞吐量为 96.176%，平均暂停时间为 12.429 秒。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/gc-tuning/baselin-KPI.png" alt="基线 GC KPI（由 GCeasy 生成）" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 基线 GC KPI（由 GCeasy 生成）&lt;/div></description></item><item><title>应用程序的内存是大还是小？</title><link>https://www.diguage.com/post/large-or-small-memory-size-for-my-app/</link><pubDate>Sat, 06 Jan 2024 12:17:15 +0800</pubDate><guid>https://www.diguage.com/post/large-or-small-memory-size-for-my-app/</guid><description>&lt;div class="paragraph">
&lt;p>应该在内存容量大的少量实例（即机器）上运行应用程序，还是在内存容量小的大量实例上运行应用程序？哪种策略是最佳的？这个问题可能会经常遇到。在开发应用程序长达 20 年，且构建了 JVM 性能工程/故障排除工具（ &lt;a href="https://gceasy.io/" target="_blank" rel="noopener">GCeasy&lt;/a>、 &lt;a href="https://fastthread.io/" target="_blank" rel="noopener">FastThread,&lt;/a>、 &lt;a href="https://heaphero.io/" target="_blank" rel="noopener">HeapHero&lt;/a>）之后，我仍然不知道这个问题的正确答案。同时，我相信这个问题也没有非黑即白的答案。在本文中，我想与大家分享一下我对这个问题的看法和经验。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_两个数十亿美元企业的故事">两个数十亿美元企业的故事&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>由于我们的 JVM 性能工程/故障排除工具已广泛应用于各大企业，因此我有机会看到世界级企业应用的实际实施情况。最近，我有机会参观了两家高速成长的科技公司（如果我说出他们的名字，读这篇文章的人都会知道）。这两家公司的总部都在硅谷。它们的业务是技术，因此在工程设计方面很有一套。它们是华尔街的宠儿，享有极高的估值。它们的市值高达数十亿美元。它们是现代企业蓬勃发展的典型代表。在我们的对话中，让我们称这两家企业为公司 A 和公司 B。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在内存大小方面，两家企业都采用了&lt;strong>两个极端&lt;/strong>，这让我感到非常惊讶。公司 A 将堆大小（即 &lt;code>-Xmx&lt;/code>）设置为 250GB，而公司 B 则将堆大小设置为 2GB：公司 A 的堆大小是公司 B 的 125 倍。两家公司都对自己的内存大小设置很自信。俗话说：&amp;#34;事实胜于雄辩&amp;#34;，两家企业都在扩大规模，处理数十亿的关键业务交易。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>两家公司都从事相同的业务，收入/市值大致相同，位于同一地理区域，在同一时间点采用两种极端的内存大小，这真是一次绝佳的体验。鉴于这种现实生活中的真实经验，正确的答案是什么？大内存还是小内存？我的结论是：如果你有一支优秀的团队，采用这两种策略都能取得成功。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_大内存容量可能很昂贵">大内存容量可能很昂贵&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>与内存容量小、实例数量多的情况相比，内存容量大、实例（即机器）数量少的情况往往成本较高。以下是基于美国东部（弗吉尼亚州北部）地区 AWS EC2 实例成本的简单计算：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>m4.16xlarge - 256GB 内存 - Linux 按实例收费：3.2 美元/小时&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>T3a small - 2GB 内存 - Linux 按实例收费：0.0188 美元/小时&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>因此，要获得 256GB 内存容量，我们必须获得 128 个 “T3a small” 实例（即 128 个实例 x 2GB = 256GB）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>128 x T3a small - 2GB 内存 - Linux 按实例收费：2.4064 美元/小时（即 128 x 0.0188 美元/小时）&lt;/p>
&lt;/div></description></item><item><title>如何实现 GC 的高吞吐量？</title><link>https://www.diguage.com/post/how-to-aim-for-high-gc-throughput/</link><pubDate>Fri, 05 Jan 2024 09:24:33 +0800</pubDate><guid>https://www.diguage.com/post/how-to-aim-for-high-gc-throughput/</guid><description>&lt;div class="paragraph">
&lt;p>三四十年前，开发人员负责释放在应用程序中创建的对象。业务应用程序相当复杂，有不同的工作流、用例和场景。即使开发人员在某个场景中少释放一个对象，对象也会在内存中累积，造成内存泄漏。Java 于 1995 年推出时，承诺自动进行垃圾回收。它将删除对象的责任从开发人员转移到了 Java 虚拟机（JVM），从而彻底改变了内存管理。整个行业都积极拥抱了这一创新理念，因为开发人员不再需要操心手动内存管理。从那时起，自动垃圾回收已成为所有现代编程语言的默认功能。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在本篇文章中，我们将探讨垃圾回收过程中的一个关键性能指标：&amp;#34;GC 吞吐量&amp;#34;。我们将了解它的含义、在 Java 应用程序中的重要性以及它对整体性能的影响。此外，我们还将深入探讨提高 GC 吞吐量的可行策略，为现代软件开发释放其优势。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_什么是垃圾回收吞吐量">什么是垃圾回收吞吐量？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>每当运行自动垃圾回收事件时，应用程序都会停顿，以识别内存中未引用的对象并将其释放。在停顿期间，不会处理任何客户请求。垃圾回收吞吐量请求应用程序处理客户请求的时间占多大比例，垃圾回收活动的时间占多大比例。例如，如果有人说他的应用程序的 GC 吞吐量是 98%，这意味着他的应用程序有 98% 的时间用于处理客户请求，其余 2% 的时间用于处理垃圾回收活动。 高 GC 吞吐量是可取的，因为它表明应用程序有效地利用了系统资源，从而减少了停顿，提高了整体性能。相反，GC 吞吐量低会导致垃圾回收停顿时间增加，影响应用程序的响应速度，造成性能瓶颈。监控和优化 GC 吞吐量对于确保应用程序的顺利执行和响应速度至关重要。在下一节中，我们将探讨查找应用程序 GC 吞吐量的方法，并了解如何解释结果以优化 Java 应用程序性能。继续…&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_如何找到应用程序的_gc_吞吐量">如何找到应用程序的 GC 吞吐量？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>垃圾回收日志是研究 GC 性能的最佳来源。如果你的应用程序运行在 JVM 上，你可以通过 &lt;a href="https://www.diguage.com/post/how-to-do-gc-log-analysis/" target="_blank" rel="noopener">如何进行 GC 日志分析&lt;/a> 文章中提到的 JVM 参数启用 GC 日志。启用 GC 日志后，让应用程序处理流量至少一天，以观察高流量和低流量时段各自的运行情况。之后，可以将生成的 GC 日志文件上传到 GC 日志分析工具，以获得有价值的分析结果。一些常用的 GC 日志分析工具包括 &lt;a href="https://gceasy.io/" target="_blank" rel="noopener">GCeasy&lt;/a>、 &lt;a href="https://www.ibm.com/support/pages/java-sdk" target="_blank" rel="noopener">IBM GC &amp;amp; Memory visualizer&lt;/a> 和 &lt;a href="https://github.com/mgm3746/garbagecat" target="_blank" rel="noopener">Google Garbage cat&lt;/a> 等。这些工具将报告 GC 吞吐量以及其他重要的 GC 指标。下面是 GCeasy 工具的摘录，展示了包括 GC 吞吐量在内的各种 GC 关键性能指标 (KPI) 报告。&lt;/p>
&lt;/div></description></item><item><title>Java ZGC 调优</title><link>https://www.diguage.com/post/java-zgc-algorithm-tuning/</link><pubDate>Wed, 03 Jan 2024 22:19:48 +0800</pubDate><guid>https://www.diguage.com/post/java-zgc-algorithm-tuning/</guid><description>&lt;div class="paragraph">
&lt;p>ZGC 是一种专门的垃圾回收器，主要用于管理大型堆和尽量减少 Java 应用程序中的停顿。它能应对在内存密集型工作负载和一致的响应时间至关重要的情况下的垃圾回收的挑战。ZGC 利用并发处理能力和先进的算法，为优化现代 Java 应用程序的性能提供了有效的解决方案。在本篇文章中，将专门探讨调整 ZGC 以提高性能的技术。不过，如果想了解更多基础知识，可以观看在 JAX 伦敦会议上发表的 &lt;a href="https://www.youtube.com/watch?v=6G0E4O5yxks" target="_blank" rel="noopener">垃圾回收调优&lt;/a> 讲座。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_如何启用_zgc">如何启用 ZGC？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>确保使用的 Java 版本支持 ZGC。OpenJDK 从 JDK11 开始支持 ZGC。在启动应用程序时添加以下 JVM 参数，这样就可以在 Java 应用程序中启用 ZGC 垃圾收集器：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># D瓜哥 · &lt;a href="https://www.digauge.com" target="_blank">https://www.digauge.com&lt;/a>&lt;/span>
&lt;span class="nt">-XX&lt;/span>:+UseZGC &lt;span class="nt">-XX&lt;/span>:+ZGenerational &lt;i class="conum" data-value="1">&lt;/i>&lt;b>(1)&lt;/b>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="colist arabic">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td>&lt;i class="conum" data-value="1">&lt;/i>&lt;b>1&lt;/b>&lt;/td>
&lt;td>D瓜哥注：分代 ZGC 从 OpenJDK 21+ 开始支持。&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_何时使用_zgc">何时使用 ZGC？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>如果应用符合其中任何一项要求，就可以考虑使用 ZGC：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>堆大小较大&lt;/strong>：ZGC 特别适合堆容量较大的应用程序，堆容量通常在数十 GB 或更大。如果应用需要大量内存，ZGC 的低延迟特性将使其成为一个令人信服的选择。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>低延迟要求&lt;/strong>：当应用需要一致的响应时间和低延迟性能时，ZGC 将大显身手。在需要最大限度缩短垃圾回收暂停时间的情况下，ZGC 表现出色，特别适合交互式应用和实时性应用。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>具有不同工作负载的应用&lt;/strong>：ZGC 专为处理不同的工作负载而设计，因此适用于内存使用模式不可预测的应用。无论应用程序经历的是周期性的，突发性的，还是富有变化性的负载，ZGC 都能有效地适应这些调整。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_zgc_调优参数">ZGC 调优参数&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>ZGC 是 Java 中的一种垃圾收集器，它采用了一种不同的调优方法：将暴露的 JVM 参数数量降至最低。与需要细粒度调整的传统垃圾收集器不同，ZGC 专注于优化大型堆的管理，同时以最小的配置开销提供高效的垃圾收集。这种精简的方法允许开发人员主要关注一个关键的 JVM 调整参数：堆大小。&lt;/p>
&lt;/div></description></item><item><title>Spring 应用合并之路</title><link>https://www.diguage.com/post/the-merging-spring-applications-road/</link><pubDate>Sat, 23 Dec 2023 20:38:47 +0800</pubDate><guid>https://www.diguage.com/post/the-merging-spring-applications-road/</guid><description>&lt;div class="paragraph">
&lt;p>公司最近一年在推进降本增效，在用尽各种手段之后，发现应用太多，每个应用都做跨机房容灾部署，则最少需要 4 台机器（称为容器更合适）。那么，将相近应用做一个合并，减少维护项目，提高机器利用率就是一个可选方案。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>经过前后三次不同的折腾，最后探索出来一个可行方案。记录一下，分享出来，希望对有相关需求的研发童鞋有所帮助。下面按照四种可能的方案，分别做介绍。另外，为了方便做演示，专门整了两个演示项目：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://github.com/diguage/merge-demo-boot" target="_blank" rel="noopener">diguage/merge-demo-boot&lt;/a> — 合并项目，下面简称为 &lt;code>boot&lt;/code>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://github.com/diguage/merge-demo-web" target="_blank" rel="noopener">diguage/merge-demo-web&lt;/a> — 被合并项目，下面简称为 &lt;code>web&lt;/code>。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_jar_包引用">Jar 包引用&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>这个方式，可能是给人印象最容易的方式。仔细思考一下，从维护性的角度来看，这个方式反而是最麻烦的方式，理由如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;code>web&lt;/code> 项目每次更新，都需要重新打包发布新版； &lt;code>boot&lt;/code> 项目也需要跟着更新发布。拉一次屎，脱两次裤子。属实麻烦。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>还需要考虑 &lt;code>web&lt;/code> 项目的加载问题，类似下面要描述的，是否共用容器：&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>共用容器 — 这是最容器想到的方式。但是这种方式，需要解决 Bean 冲突的问题。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不共用容器 — 这种方式需要处理 &lt;code>web&lt;/code> 容器如何加载的问题。默认应该是无法识别。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>基于这些考虑，这种方式直接被抛弃了。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_仓库合并公用一套容器">仓库合并，公用一套容器&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>这是第一次尝试使用的方案。也是遇到问题最多的方案。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>将两个仓库做合并。&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>将 &lt;code>web&lt;/code> 仓库的地址配置到 &lt;code>boot&lt;/code> 项目里： &lt;code>git remote add web &lt;a href="mailto:git@github.com">git@github.com&lt;/a>:diguage/merge-demo-web.git&lt;/code>；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在 &lt;code>boot&lt;/code> 项目里，切出来一个分支： &lt;code>git switch -c web&lt;/code>；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将 &lt;code>web&lt;/code> 分支的提交清空： &lt;code>git update-ref -d HEAD&lt;/code>，然后做一次提交；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将 &lt;code>web&lt;/code> 项目的代码克隆到 &lt;code>web&lt;/code> 分支上： &lt;code>git pull --rebase --allow-unrelated-histories web master&lt;/code>；注意，这里需要加 &lt;code>--allow-unrelated-histories&lt;/code> 参数，以允许不相干的仓库进行合并。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>从 &lt;code>boot&lt;/code> 项目的 &lt;code>master&lt;/code> 分支上，切出来一个合并分支： &lt;code>git switch -c merge&lt;/code>；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将 &lt;code>web&lt;/code> 项目向 &lt;code>boot&lt;/code> 项目合并： &lt;code>git merge --allow-unrelated-histories web&lt;/code>；注意，这里需要加 &lt;code>--allow-unrelated-histories&lt;/code> 参数，以允许不相干的仓库进行合并。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>处理代码冲突，完成合并即可。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>新 Mac 安装软件脚本</title><link>https://www.diguage.com/post/install-software-for-new-mac/</link><pubDate>Tue, 24 Oct 2023 16:39:37 +0800</pubDate><guid>https://www.diguage.com/post/install-software-for-new-mac/</guid><description>&lt;div class="paragraph">
&lt;p>最近公司可以申请零净值 MacBook 笔记本，就随手申请了一个。由于有很多软件需要安装，就搜集了一下以前安装软件的命令，整理成一个安装脚本，分享出来，方便后续再次装机。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_xcode_select">1. &lt;code>xcode-select&lt;/code>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>作为开发人员，这是基础工具包，必须安装：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">xcode-select &lt;span class="nt">--install&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_安装脚本">2. 安装脚本&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>安装脚本主要构成如下：&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_oh_my_zsh">2.1. oh my zsh&lt;/h3>
&lt;div class="paragraph">
&lt;p>安装脚本先安装了 oh-my-zsh。最近帮同事搞 MacBook，没有 oh-my-zsh 的加持，写命令行浑身难受。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/logos/oh-my-zsh.png" alt="oh-my-zsh" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. oh-my-zsh&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>由于安装 oh my zsh 会导致脚本退出，所以，单独安装：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c">#!/usr/bin/env bash&lt;/span>
&lt;span class="c">#&lt;/span>
&lt;span class="c"># Author: D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>&lt;/span>
&lt;span class="c">#&lt;/span>

&lt;span class="c"># 安装 oh-my-zsh&lt;/span>
sh &lt;span class="nt">-c&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">$(&lt;/span>curl &lt;span class="nt">-fsSL&lt;/span> https://cdn.jsdelivr.net/gh/ohmyzsh/ohmyzsh/tools/install.sh&lt;span class="si">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_homebrew">2.2. Homebrew&lt;/h3>
&lt;div class="paragraph">
&lt;p>脚本里面主要使用了 Homebrew 来安装软件。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/logos/homebrew.svg" alt="Homebrew" width="50%"/>
&lt;/div>
&lt;div class="title">图 2. Homebrew&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_sdkman">2.3. sdkman&lt;/h3>
&lt;div class="paragraph">
&lt;p>Java JDK + Maven 等相关安装，主要使用了 Sdkman，方便多个版本相互切换。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/logos/sdkman.png" alt="sdkman" width="95%"/>
&lt;/div>
&lt;div class="title">图 3. sdkman&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_感谢_jsdelivr">2.4. 感谢 jsDelivr&lt;/h3>
&lt;div class="paragraph">
&lt;p>为了解决安装 oh-my-zsh 和 Homebrew 时，GitHub 访问不畅，使用 jsDelivr 将它们的安装链接进行改写，可以利用 CDN 加速，让安装过程更加顺利。&lt;/p>
&lt;/div></description></item><item><title>Spring 对占位符的处理（一）：XML 中的 Bean</title><link>https://www.diguage.com/post/placeholder-in-spring-1/</link><pubDate>Mon, 01 May 2023 22:15:55 +0800</pubDate><guid>https://www.diguage.com/post/placeholder-in-spring-1/</guid><description>&lt;div class="paragraph">
&lt;p>最近有小伙伴在开发时，遇到了一个 Spring 占位符，例如 &lt;code>${token}&lt;/code>， 在不同环境下处理不一致的问题，正好对 Spring 对占位符的处理也有一些不清楚的地方，趁此机会，把 Spring 对占位符的处理机制深入了解一下，方便后续排查问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>经常阅读D瓜哥博客的朋友可能知道，D瓜哥在 &lt;a href="https://www.diguage.com/post/spring-extensions-and-dubbo-1/#spring-plugin" target="_blank" rel="noopener">Spring 扩展点实践：整合 Apache Dubbo（一）： Spring 插件机制简介&lt;/a> 中已经介绍了 Spring 的插件机制。在阅读以下内容之前，建议大家先去阅读一下这篇文章中“Spring 插件机制简介”章节的内容，以便于无缝衔接。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在分析的过程中发现， Spring 对占位符有两种截然不同的出来阶段：① XML 配置文件中的占位符；② Java 源代码中 &lt;code>@Value&lt;/code> 注解中的占位符。由于内容较多，一篇讲解完有些过长，所以分三篇文章来分别介绍这两种处理过程。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>本篇首先来介绍一下对 XML 配置文件中的占位符的处理。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_示例代码">示例代码&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在正式开始之前，先来看一下示例代码：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>UserRpc.java&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 * @since 2023-05-02 10:23:49
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">UserRpc&lt;/span> &lt;span class="o">{&lt;/span>

 &lt;span class="nd">@Value&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;${user.appId}&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="kd">private&lt;/span> &lt;span class="nc">String&lt;/span> &lt;span class="n">appId&lt;/span>&lt;span class="o">;&lt;/span>

 &lt;span class="c1">// 这里不使用注解，而是使用 XML 配置&lt;/span>
 &lt;span class="c1">// @Value(&amp;#34;${user.token}&amp;#34;)&lt;/span>
 &lt;span class="kd">private&lt;/span> &lt;span class="nc">String&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>token.properties&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">user.appId&lt;span class="o">=&lt;/span>dummyAppId
user.token&lt;span class="o">=&lt;/span>dummyToken&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>spring.xml&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="xml">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;beans&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://www.springframework.org/schema/beans&amp;#34;&lt;/span>
 &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
 &lt;span class="na">xmlns:context=&lt;/span>&lt;span class="s">&amp;#34;http://www.springframework.org/schema/context&amp;#34;&lt;/span>
 &lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 https://www.springframework.org/schema/context/spring-context.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>

 &lt;span class="c">&amp;lt;!-- @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a> --&amp;gt;&lt;/span>

 &lt;span class="nt">&amp;lt;context:annotation-config/&amp;gt;&lt;/span>

 &lt;span class="nt">&amp;lt;bean&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;userRpc&amp;#34;&lt;/span>
 &lt;span class="na">class=&lt;/span>&lt;span class="s">&amp;#34;com.diguage.truman.context.UserRpc&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
 &lt;span class="c">&amp;lt;!-- XML 配置的占位符实例在此 --&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;property&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;token&amp;#34;&lt;/span> &lt;span class="na">value=&lt;/span>&lt;span class="s">&amp;#34;${user.token}&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/bean&amp;gt;&lt;/span>

 &lt;span class="nt">&amp;lt;context:property-placeholder&lt;/span> &lt;span class="na">location=&lt;/span>&lt;span class="s">&amp;#34;classpath:token.properties&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>

&lt;span class="nt">&amp;lt;/beans&amp;gt;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_bean_标签处理">&lt;code>&amp;lt;bean&amp;gt;&lt;/code> 标签处理&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/spring-startup-process-overview/" target="_blank" rel="noopener">Spring 启动流程概述&lt;/a> 中，已经介绍过，Spring 的启动过程几乎都被封装在 &lt;code>AbstractApplicationContext#refresh&lt;/code> 方法中。在 &lt;code>refresh&lt;/code> 方法中调用了 &lt;code>refreshBeanFactory&lt;/code> 方法；在 &lt;code>refreshBeanFactory&lt;/code> 方法执行过程中，调用了 &lt;code>loadBeanDefinitions&lt;/code> 方法。而 &lt;code>BeanDefinition&lt;/code> 的加载是由 &lt;code>org.springframework.context.support.AbstractRefreshableApplicationContext#loadBeanDefinitions&lt;/code> 来完成的。通过 XML 文件配置的 Bean 是由 &lt;code>org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)&lt;/code> （&lt;code>AbstractRefreshableApplicationContext&lt;/code> 的子类）处理完成的。处理过程的时序图如下：&lt;/p>
&lt;/div></description></item><item><title>关于接口可维护性的一些建议</title><link>https://www.diguage.com/post/tips-for-interface-maintainability/</link><pubDate>Fri, 28 Apr 2023 17:16:24 +0800</pubDate><guid>https://www.diguage.com/post/tips-for-interface-maintainability/</guid><description>&lt;div class="paragraph">
&lt;p>在做新需求开发或者相关系统的维护更新时，尤其是涉及到不同系统的接口调用时，在可维护性方面，总感觉有很多地方差强人意。一些零星思考，抛砖引玉，希望引发更多的思考和讨论。总结了大概有如下几条建议：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>在接口注释中加入接口文档链接&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将调用接口处写上被调用接口文档链接&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将接口源代码发布到私服仓库&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对于状态值常量，优先在接口参数类或者返回值类中定义&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果使用 &lt;code>Map&lt;/code> 对象作为传输载体，要提供 Key 值定义常量&lt;/p>
&lt;/li>
&lt;li>
&lt;p>针对 &lt;code>Map&lt;/code> 返回值，可以考虑使用将 &lt;code>Map&lt;/code> 转化成对象&lt;/p>
&lt;/li>
&lt;li>
&lt;p>尽可能简化接口依赖&lt;/p>
&lt;/li>
&lt;li>
&lt;p>只传递必要字段，尽量避免大而全的接口&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将接口的参数和返回值原始数据打印到日志中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将 RPC 接口的类名及方法打印到日志中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>核心思想：以人为本，就近原则，触手可及&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面，D瓜哥对每一条建议做一个详细说明。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_在接口注释中加入接口文档链接">1. 在接口注释中加入接口文档链接&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在做接口开发时，无论是对自有接口的升级改造，还是针对外部接口的从头接入，都涉及到接口文档。不同之处是，前者的工作重点是书写或者更新接口文档；而后者是根据接口文档开发合适的接入代码。但是，经常遇到的一个麻烦是，找不到接口文档。在组内需要找老同事询问；如果是跨部门，还需要两层甚至三层的进行转接，非常麻烦。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥认为，在这种情况下，为了方便大家维护，最好的办法就是将接口文档链接直接放在代码注释中，这样后续维护的人员，直接就可以点击链接直达接口文档，简单方便高效。如果是新建的接口，就可以先创建一个空文档，把链接放在注释中，后续再书写文档内容。如果是维护已有接口，可以在维护时，将缺失的链接加入到注释中，自己方便，也方便其他人进行后续的维护更新。这样，在循序渐进的过程中，逐步就可以把文档链接补充到代码中，方便维护代码，也同步更新文档。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_将调用接口处写上被调用接口文档链接">2. 将调用接口处写上被调用接口文档链接&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在调用其他系统的接口时，没有接口文档，几乎寸步难行。在第一次接入接口时，绝大多数情况下，都是参考着接口文档做接入工作。但是，目前的情况时，接入时参考文档，参考完就随手把文档给“扔了”。后续如果还需要做进一步升级维护，还需要到处找接口文档；另外，交互的系统难免有一些 Bug，在和其他系统维护人员对接处理 Bug 时，只有接口没有文档，对方可能也需要去找文档链接。无形中，很多时间都浪费在了找文档的过程中。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥最近尝试了一个实践，就是在接口调用的地方，把接口文档链接当做注释加入到代码中。这样，无论是后续维护升级，还是沟通协调处理问题，都非常方便。别人问接口是什么，连接口+文档都可以一把复制就搞定。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>经过最近一段时间的实践情况来看，这个处理非常方便，是一个非常值得推广的实践。再插一句，也可以像一条建议一样，可以在维护代码时，不断把已接入的接口文档加入到调用接口的地方，循序渐进，方便后续人维护升级。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_将接口源代码发布到私服仓库">3. 将接口源代码发布到私服仓库&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>接口文档链接在注释中，在构建结果中就不复存在了。所以，为了方便接口使用方可以在接口中查询到对应的接口文档，就需要把源码也发布到私服仓库中。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这里只说明一下 Java 的相关处理办法。如果使用 Maven 作为构建工具的话，默认是不会将源代码发布到私服仓库中的。关于如何将源代码发布到，在 &lt;a href="https://www.diguage.com/post/upgrade-maven-plugins/#src-to-repo" target="_blank" rel="noopener">升级 Maven 插件：将源码发布到私服仓库&lt;/a> 中已经做过相关介绍，这里就不再赘述。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>除了将源码发布到私服仓库，另外，还建议编译构建时，保持方法的原始参数命名。这个也可以通过配置 Maven 插件来完成，具体配置见： &lt;a href="https://www.diguage.com/post/upgrade-maven-plugins/#compile-with-parameters" target="_blank" rel="noopener">升级 Maven 插件：字节码文件包含原始参数名称&lt;/a>。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="constant-definition">4. 对于状态值常量，优先在接口参数类或者返回值类中定义&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在做接口开发时，很多数据都有一个状态值，比如订单状态，再比如接口状态等等。目前的一个情况时，这些状态值大部分书写在文档中，在接入接口时，需要接入方自定义这些状态值。这就有些繁琐了，而且状态定义也不明确，甚至有可能遗漏一些重要的状态值。有些懒省事，直接在代码中硬编码一个魔法值，后续维护的跟还需要根据上下文反推这个值的含义，非常不利于维护。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥个人觉得，有两个处理办法：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>如果状态值不是很多，优先在接口参数类或者返回值类中定义。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果状态值很多，可以考虑单独抽取成一个常量类或者枚举类。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这样使用的时候，触手可及。不需要到处去找。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="key-for-map">5. 如果使用 &lt;code>Map&lt;/code> 对象作为传输载体，要提供 Key 值定义常量&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>有些系统可能考虑方便增加字段，选择使用 &lt;code>Map&lt;/code> 作为数据载体。自己开发的时候很爽，但是给接口接入却非常不友好。接入方从 &lt;code>Map&lt;/code> 中获取数据时，要么自己定义 Key 值；要么直接使用魔法值硬编码在代码中。使用前者方案，就需要在各个接入方都需要自定义一套；使用后者，初期是省事了，后来维护的人员就懵逼了。这都无形中增加了很多维护成本。&lt;/p>
&lt;/div></description></item><item><title>Versions Maven 插件简介</title><link>https://www.diguage.com/post/intro-to-versions-maven-plugin/</link><pubDate>Fri, 21 Apr 2023 22:07:37 +0800</pubDate><guid>https://www.diguage.com/post/intro-to-versions-maven-plugin/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/specification-for-maven-bom/" target="_blank" rel="noopener">制定组织内 Maven BOM 的一些规范&lt;/a> 中，D瓜哥 介绍了一些组织内指定 Maven BOM 的一些规范。根据这些规范，D瓜哥 创建并维护了部门内部的 Maven BOM。今年，要求对部门内的陈旧依赖做一些升级工作。所以，在 &lt;a href="https://www.diguage.com/post/experience-about-upgrading-java-dependencies/" target="_blank" rel="noopener">关于升级 Spring 等依赖的一些经验&lt;/a> 中介绍了一些升级开源依赖的经验；在上一篇文章 &lt;a href="https://www.diguage.com/post/upgrade-maven-plugins/" target="_blank" rel="noopener">升级 Maven 插件&lt;/a> 中介绍了升级 Maven 插件的一些注意事项。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥一直坚持“机器可以干的事情，就应该交给机器干”。对于依赖管理，Maven Enforcer 插件就可以对依赖做必要的检查，所以，在 &lt;a href="https://www.diguage.com/post/use-maven-enforcer-plugin-to-check-dependencies/" target="_blank" rel="noopener">使用 Maven Enforcer 插件检查依赖&lt;/a> 中，介绍了如何使用 Apache Maven Enforcer 来管理依赖。由于要维护部门内部的 Maven BOM，同时由于版本控的特质，所以，需要时长检查依赖升级情况。原来都是手动检查，需要一个一个去搜索各个依赖，不仅费时费力，而且还低效。最近，Maven 有一个插件可以胜任这个工作，它就是： &lt;a href="https://www.mojohaus.org/versions/versions-maven-plugin/index.html" target="_blank" rel="noopener">Versions Maven Plugin&lt;/a>。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_依赖检查">依赖检查&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Versions Maven Plugin 支持两种配置方式：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>外置配置文件 &lt;code>maven-version-rules.xml&lt;/code>；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>内置在 POM 文件中，直接写在插件的 &lt;code>&amp;lt;configuration&amp;gt;&lt;/code> 标签中。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第一种方案不方便迁移。还要额外管理一个配置文件。推荐使用第二种方式。另外，直接将这些配置放在 Maven BOM 中，使用继承的方式使用 Maven BOM，那么子项目就自动继承了这些配置。后续也只需要一个地方的配置即可。示例配置如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="xml">&lt;span class="c">&amp;lt;!-- @author: D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a> --&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;plugin&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.codehaus.mojo&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>versions-maven-plugin&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>2.15.0&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;configuration&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;ruleSet&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;ignoreVersions&amp;gt;&lt;/span>
 &lt;span class="c">&amp;lt;!-- 可以使用 ignoreVersion 配置忽略 SNAPSHOT、alpha、beta 版等 --&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;ignoreVersion&amp;gt;&lt;/span>
 &lt;span class="c">&amp;lt;!-- &amp;#39;exact&amp;#39; (默认) 或 &amp;#39;regex&amp;#39; --&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;type&amp;gt;&lt;/span>regex&lt;span class="nt">&amp;lt;/type&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>(.+-SNAPSHOT|.+-M\d)&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/ignoreVersion&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;ignoreVersion&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;type&amp;gt;&lt;/span>regex&lt;span class="nt">&amp;lt;/type&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>.+-(alpha|beta)&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/ignoreVersion&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/ignoreVersions&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/ruleSet&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/plugin&amp;gt;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>升级 Maven 插件</title><link>https://www.diguage.com/post/upgrade-maven-plugins/</link><pubDate>Tue, 21 Mar 2023 17:36:48 +0800</pubDate><guid>https://www.diguage.com/post/upgrade-maven-plugins/</guid><description>&lt;div class="paragraph">
&lt;p>D瓜哥在 &lt;a href="https://www.diguage.com/post/experience-about-upgrading-java-dependencies/" target="_blank" rel="noopener">关于升级 Spring 等依赖的一些经验&lt;/a> 中，介绍了一些升级 Spring 等依赖的一些经验。在 &lt;a href="https://www.diguage.com/post/upgrade-ibatis-mybatis-about-duplicate-key-exception/" target="_blank" rel="noopener">升级 iBATIS/MyBATIS 对处理 DuplicateKeyException 的影响&lt;/a> 中，分析了升级 iBATIS/MyBATIS 对处理 DuplicateKeyException 异常的影响。在升级中，还遇到一些 Maven 插件相关的问题。这里也分享出来，希望对大家有所帮助。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_properties_文件编码错误">Properties 文件编码错误&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在升级过程中，遇到过 Properties 文件编码错误的问题。可以通过配置对应的编码来解决这个问题。配置如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="xml">&lt;span class="c">&amp;lt;!-- D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a> --&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;plugin&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.apache.maven.plugins&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>maven-resources-plugin&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>3.3.0&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;configuration&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;propertiesEncoding&amp;gt;&lt;/span>ISO-8859-1&lt;span class="nt">&amp;lt;/propertiesEncoding&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/plugin&amp;gt;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_参考资料">参考资料&lt;/h3>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/plugins/maven-resources-plugin/examples/filtering-properties-files.html" target="_blank" rel="noopener">Apache Maven Resources Plugin – Filtering Properties Files&lt;/a>。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_使用_maven_enforcer_插件检查依赖">使用 Maven Enforcer 插件检查依赖&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>私以为“机器可以干的事情，就应该交给机器干”。对于依赖管理，Maven Enforcer 插件就可以对依赖做必要的检查。所以，推荐使用 Maven Enforcer 插件来检查低版本及有安全漏洞的依赖。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>详细介绍请参考： &lt;a href="https://www.diguage.com/post/use-maven-enforcer-plugin-to-check-dependencies/" target="_blank" rel="noopener">使用 Maven Enforcer 插件检查依赖&lt;/a>&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="compile-with-parameters">字节码文件包含原始参数名称&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>一些对外发布的依赖，建议将原始参数名称编译到构建结果里。可以通过指定构建参数来完成。&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="xml">&lt;span class="c">&amp;lt;!-- D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a> --&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;plugin&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.apache.maven.plugins&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>maven-compiler-plugin&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>3.11.0&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;configuration&amp;gt;&lt;/span>
 &lt;span class="c">&amp;lt;!-- &lt;a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html" target="_blank">https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html&lt;/a> --&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;compilerArgs&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;arg&amp;gt;&lt;/span>-parameters&lt;span class="nt">&amp;lt;/arg&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/compilerArgs&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;parameters&amp;gt;&lt;/span>true&lt;span class="nt">&amp;lt;/parameters&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/configuration&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/plugin&amp;gt;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_参考资料_2">参考资料&lt;/h3>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/plugins/maven-compiler-plugin/examples/pass-compiler-arguments.html" target="_blank" rel="noopener">Apache Maven Compiler Plugin – Pass Compiler Arguments&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_解决测试依赖问题">解决测试依赖问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>部分项目可能已经使用了 JUnit 5，但是执行测试代码时，可能报错。可以使用如下配置来解决这个问题：&lt;/p>
&lt;/div></description></item><item><title>升级 iBATIS/MyBATIS 对处理 DuplicateKeyException 的影响</title><link>https://www.diguage.com/post/upgrade-ibatis-mybatis-about-duplicate-key-exception/</link><pubDate>Thu, 23 Feb 2023 11:21:23 +0800</pubDate><guid>https://www.diguage.com/post/upgrade-ibatis-mybatis-about-duplicate-key-exception/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/experience-about-upgrading-java-dependencies/" target="_blank" rel="noopener">关于升级 Spring 等依赖的一些经验&lt;/a> 中，分享了一些开源依赖的升级经验。部分小伙伴质疑升级 iBATIS/MyBATIS 会影响对 &lt;code>DuplicateKeyException&lt;/code> 异常的处理。这篇文章就从源码分析/代码更新的就角度来分析一下升级相关依赖是否会对 &lt;code>DuplicateKeyException&lt;/code> 异常的处理带来实质性的影响。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>由于主要的技术栈涉及 MySQL 驱动、iBATIS、MyBATIS、Spring 周边等。所以，本文仅分析涉及的这些依赖。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥使用 &lt;a href="https://dev.mysql.com/doc/employee/en/" target="_blank" rel="noopener">MySQL: Employees Sample Database&lt;/a> 搭建了一个 Spring + MyBATIS + MySQL Connector/J 的测试环境。连续插入两条一样的数据，单步调试，在 &lt;code>com.mysql.jdbc.MysqlIO#sendCommand&lt;/code> 方法中，就可以观察到如下异常：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/upgrade-spring/mysql-error1062.png" alt="MySQL Error 1062" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. MySQL Error 1062&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从这里可以明显看出，MySQL 驱动返回的异常中， &lt;code>venderCode&lt;/code> 编码是 &lt;code>1062&lt;/code>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>顺着这个线，往上走，到 &lt;code>org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String, java.lang.Object)&lt;/code> 方法中，可以看到，&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/upgrade-spring/mybatis-wrap-exception.png" alt="MyBATIS wrap Exception" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. MyBATIS wrap Exception&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在这里，会将 &lt;code>SQLException&lt;/code> 包装成 &lt;code>PersistenceException&lt;/code>，这也是 MyBATIS 对外暴露的统一的异常类。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>继续往上走，就到了 &lt;code>org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke&lt;/code> 方法：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/upgrade-spring/mybatis-translateException.png" alt="MyBATIS translateException" width="95%"/>
&lt;/div>
&lt;div class="title">图 3. MyBATIS translateException&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 &lt;code>SqlSessionInterceptor#invoke&lt;/code> 方法的异常处理中，将 &lt;code>PersistenceException&lt;/code> 异常通过 &lt;code>org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible&lt;/code> 方法，将异常转换成 &lt;code>DataAccessException&lt;/code> 对象。 &lt;code>DataAccessException&lt;/code> 类是 Spring 数据访问的异常类基类。&lt;/p>
&lt;/div></description></item><item><title>细说编码与字符集</title><link>https://www.diguage.com/post/dive-into-encoding-and-character-set/</link><pubDate>Sat, 29 Oct 2022 09:09:12 +0800</pubDate><guid>https://www.diguage.com/post/dive-into-encoding-and-character-set/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;strong>文章还没写完，提前放出防止出现 404。稍后慢慢更新，敬请期待： &lt;a href="https://www.diguage.com/post/dive-into-encoding-and-character-set/" target="_blank" rel="noopener">细说编码与字符集 - &amp;#34;地瓜哥&amp;#34;博客网&lt;/a>&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>文章还没写完，提前放出防止出现 404。稍后慢慢更新，敬请期待： &lt;a href="https://www.diguage.com/post/dive-into-encoding-and-character-set/" target="_blank" rel="noopener">细说编码与字符集 - &amp;#34;地瓜哥&amp;#34;博客网&lt;/a>&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>文章还没写完，提前放出防止出现 404。稍后慢慢更新，敬请期待： &lt;a href="https://www.diguage.com/post/dive-into-encoding-and-character-set/" target="_blank" rel="noopener">细说编码与字符集 - &amp;#34;地瓜哥&amp;#34;博客网&lt;/a>&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>前段时间要研究 Hessian 编码格式，为了搞清楚 Hessian 对字符串的编码，就顺路查了好多编码和字符集的工作，理清了很多以前模糊的知识点。下面整理一下笔记，也梳理一下自己的思路和理解。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_ascii_码">ASCII 码&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>计算机起源于美国，他们对英语字符与二进制位之间的对应关系做了统一规定，并制定了一套字符编码规则，这套编码规则被称为 American Standard Code for Information Interchange，简称为 ASCII 编码&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>其实，ASCII 最早起源于电报码。最早的商业应用是贝尔公司的七位电传打字机。后来于 1963 年发布了该标准的第一版。在网络交换中使用的 ASCII 格式是在 1969 年发布的，该格式在 2015 年发展成为互联网标准。点击 &lt;a href="https://www.rfc-editor.org/rfc/rfc20.pdf" target="_blank" rel="noopener">RFC 20: ASCII format for network interchange&lt;/a>，感受一下 1969 年的古香古色。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>ASCII 编码一共定义了128个字符的编码规则，用七位二进制表示（&lt;code>0x00&lt;/code> - &lt;code>0x7F&lt;/code>）, 这些字符组成的集合就叫做 ASCII 字符集。完整列表如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/encoding/ascii-table.svg" alt="ASCII Table" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>ASCII 码可以说是现在所有编码的鼻祖。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_编码乱战及_unicode_应运而生">编码乱战及 Unicode 应运而生&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>ASCII 编码是为专门英语指定的编码标准，但是却不能编码英语外来词。比如 &lt;code>résumé&lt;/code>，其中 &lt;code>é&lt;/code> 就不在 ASCII 编码范围内。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>随着计算机的发展，各个国家或地区，甚至不同公司都推出了不同的编码标准，比如中国推出了 GB2312、GBK 以及 GB18030；微软推出了 Windows character sets 。&lt;/p>
&lt;/div></description></item><item><title>深入理解 Java 代码块</title><link>https://www.diguage.com/post/dive-into-java-code-block/</link><pubDate>Sun, 04 Sep 2022 19:17:06 +0800</pubDate><guid>https://www.diguage.com/post/dive-into-java-code-block/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/jvm-bytecode-constant/" target="_blank" rel="noopener">Java 虚拟机操作码探秘：常量指令&lt;/a> 中对 Java 虚拟机操作码中关于常量操作的指令(操作码)做了初步介绍。估计会有人疑问：文中的“栈”、“栈顶”等是什么？接下来就准备解答这些疑问。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在答疑解惑之前，先来了解一下 Java 编译器对 Java 代码中的代码块是如何处理的？常见的代码块有普通代码块和静态代码块，下面对其做分别介绍。由于涉及到构造函数，所以，先对构造函数做一个介绍。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_构造函数">构造函数&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="no-constructor">无构造函数&lt;/h3>
&lt;div class="paragraph">
&lt;p>先来看看当没有声明构造函数时，编译结果是什么样的：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * 无构造函数示例
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">Example&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>编译后，使用 &lt;code>javap -c&lt;/code> 查看一下编译结果：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="nv">$ &lt;/span>javap &lt;span class="nt">-c&lt;/span> Example
Compiled from &lt;span class="s2">&amp;#34;Example.java&amp;#34;&lt;/span>
public class Example &lt;span class="o">{&lt;/span>
 public Example&lt;span class="o">()&lt;/span>&lt;span class="p">;&lt;/span>
 Code:
 0: aload_0
 1: invokespecial &lt;span class="c">#1 // Method java/lang/Object.&amp;#34;&amp;lt;init&amp;gt;&amp;#34;:()V&lt;/span>
 4: &lt;span class="k">return&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从结果上来看：编译器自动给没有声明构造函数的类，生成了一个无参构造函数，并且在其中调用了父类（这里是 &lt;code>Object&lt;/code>）的无参构造函数。这是大家都熟知的基础知识。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_有参构造函数">有参构造函数&lt;/h3>
&lt;div class="paragraph">
&lt;p>再来看看当有声明参数的构造函数时，编译结果是什么样的：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * 有参构造函数示例
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">Example&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="nf">Example&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>编译后，使用 &lt;code>javap -c&lt;/code> 查看一下编译结果：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="nv">$ &lt;/span>javap &lt;span class="nt">-c&lt;/span> Example
Compiled from &lt;span class="s2">&amp;#34;Example.java&amp;#34;&lt;/span>
public class Example &lt;span class="o">{&lt;/span>
 public Example&lt;span class="o">(&lt;/span>int&lt;span class="o">)&lt;/span>&lt;span class="p">;&lt;/span> &lt;i class="conum" data-value="1">&lt;/i>&lt;b>(1)&lt;/b>
 Code:
 0: aload_0
 1: invokespecial &lt;span class="c">#1 // Method java/lang/Object.&amp;#34;&amp;lt;init&amp;gt;&amp;#34;:()V&lt;/span>
 4: &lt;span class="k">return&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Java 虚拟机操作码探秘：常量指令</title><link>https://www.diguage.com/post/jvm-bytecode-constant/</link><pubDate>Sat, 03 Sep 2022 16:17:34 +0800</pubDate><guid>https://www.diguage.com/post/jvm-bytecode-constant/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/jvm-instruction-set/" target="_blank" rel="noopener">Java 虚拟机指令(操作码)集&lt;/a> 中给出了一个操作码的列表。针对所有的指令，仅仅给出了一个大概介绍，对理解来说可以说毫无助力。为了弥补这个短板，这里也学习 &lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">“Hessian 协议解释与实战”系列&lt;/a> 那样，来一个详细解释和实战，配合实例来做个深入分析和讲解。这是这个系列的第一篇文章，就以列表中第一部分“常量”指令开始。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从 &lt;a href="https://www.diguage.com/post/jvm-instruction-set/" target="_blank" rel="noopener">Java 虚拟机指令(操作码)集&lt;/a> 列表上来看，一共 21 个指令；按照处理数据的类型，合并同类项后，剩下有 &lt;code>nop&lt;/code>、 &lt;a href="#aconst_null">&lt;code>aconst_null&lt;/code>&lt;/a>、 &lt;a href="#iconst">&lt;code>iconst_&amp;lt;i&amp;gt;&lt;/code>&lt;/a>、 &lt;a href="#lconst">&lt;code>lconst_&amp;lt;l&amp;gt;&lt;/code>&lt;/a>、 &lt;a href="#fconst">&lt;code>fconst_&amp;lt;f&amp;gt;&lt;/code>&lt;/a>、 &lt;a href="#dconst">&lt;code>dconst_&amp;lt;d&amp;gt;&lt;/code>&lt;/a>、 &lt;a href="#bipush">&lt;code>bipush&lt;/code>&lt;/a>、 &lt;a href="#sipush">&lt;code>sipush&lt;/code>&lt;/a>、 &lt;a href="#ldc">&lt;code>ldc&lt;/code>&lt;/a> 和 &lt;a href="#ldc2_w">&lt;code>ldc2_w&lt;/code>&lt;/a> 等几个指令。下面，按照顺序，对其进行一一讲解。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock note">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-note" title="Note">&lt;/i>
&lt;/td>
&lt;td class="content">
&lt;div class="paragraph">
&lt;p>操作码助记符的首字母一般是有特殊含义的，表示操作码所作用的数据类型： &lt;code>i&lt;/code> 代表对 &lt;code>int&lt;/code> 类型的数据操作； &lt;code>l&lt;/code> 代表 &lt;code>long&lt;/code>； &lt;code>s&lt;/code> 代表 &lt;code>short&lt;/code>； &lt;code>b&lt;/code> 代表 &lt;code>byte&lt;/code>；&lt;code>c&lt;/code> 代表 &lt;code>char&lt;/code>；&lt;code>f&lt;/code> 代表 &lt;code>float&lt;/code>， &lt;code>d&lt;/code> 代表 &lt;code>double&lt;/code>； &lt;code>a&lt;/code> 代表 reference。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>尖括号之间的字母指定了指令隐含操作数的数据类型，&lt;code>&amp;lt;n&amp;gt;&lt;/code> 代表非负的整数； &lt;code>&amp;lt;i&amp;gt;&lt;/code> 代表是 &lt;code>int&lt;/code> 类型数据； &lt;code>&amp;lt;l&amp;gt;&lt;/code> 代表 &lt;code>long&lt;/code> 类型； &lt;code>&amp;lt;f&amp;gt;&lt;/code> 代表 &lt;code>float&lt;/code> 类型； &lt;code>&amp;lt;d&amp;gt;&lt;/code> 代表 &lt;code>double&lt;/code> 类型。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>另外还需要指出一点：这种指令表示法在整个 Java 虚拟机规范之中都是通用的。&lt;/p>
&lt;/div>
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_nop">&lt;code>nop&lt;/code>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>根据 &lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.nop" target="_blank" rel="noopener">Chapter 6. The Java Virtual Machine Instruction Set：nop&lt;/a> 来看，就是“Do nothing”，暂时没有找到使用方法。就不做多介绍，后续看到相关资料，再做补充。&lt;/p>
&lt;/div></description></item><item><title>制定组织内 Maven BOM 的一些规范</title><link>https://www.diguage.com/post/specification-for-maven-bom/</link><pubDate>Sat, 03 Sep 2022 09:19:59 +0800</pubDate><guid>https://www.diguage.com/post/specification-for-maven-bom/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/experience-about-upgrading-java-dependencies/" target="_blank" rel="noopener">关于升级 Spring 等依赖的一些经验&lt;/a> 中介绍了 D瓜哥在升级项目依赖时，遇到的一些问题以及一些需要注意的地方。但是，这里还存在一个问题：&lt;strong>各个依赖的版本依然散落在各个项目中；升级依赖，需要在所有项目中，把所有相关项目的依赖都巴拉一下，费时费力。&lt;/strong>解决这个问题的一个比较好的办法是&lt;strong>制定一个组织内部的 Maven BOM，集中管理相关依赖的版本。&lt;/strong>这样升级的时候，还需要修改 BOM 的版本号即可。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_maven_bom_介绍">Maven BOM 介绍&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>BOM（Bill of Materials）是由 Maven 提供的功能,它通过定义一整套相互兼容的 jar 包版本集合，使用时只需要依赖该BOM文件，即可放心的使用需要的依赖 jar 包，且无需再指定版本号。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_一些基本原则">一些基本原则&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>Spring &amp;amp; Spring Boot 是 Java 生态中，全世界广泛使用的开发框架，在各种场景中都经受过考验。所以，Spring &amp;amp; Spring Boot 选择的 Jar 在稳定性和兼容性方面都有保证。另外，Spring Boot 本身就集成了非常非常多的依赖，并为此创建了一个网页 &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/dependency-versions.html" target="_blank" rel="noopener">Spring Boot Dependency versions&lt;/a> 来说明它集成的依赖及版本。故而，可以选择以 Spring Boot 为底本，来制作自己的 BOM。&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>如果不需要 Spring 相关依赖，可以将 Spring 相关依赖删除掉，然后在其之上增加组织内部依赖而创建自己的 BOM。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果需要 Spring 相关依赖，那么直接继承&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>在稳定性方面，经过更多人检验的版本，则稳定性更有保障。所以，选择最近两年下载次数比较多的版本。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>更新的版本，更容易获得技术升级带来的红利。所以，在可能的情况下，优先选择高版本。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>优先考虑目标 JDK 的支持情况。例如，一些依赖的高版本或低版本不支持 Java 8，但是 Java 8 是生产环境部署的主要版本，那么太高的版本和低版本都不适合。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_外部_jar_包选择标准">外部 Jar 包选择标准&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>尽量将外部中间件统一到同一种依赖的同一个版本上。例如：数据库连接池全部使用 HikariCP；JSON 处理统一使用 Jackson。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>选择最近两年发布的版本中，下载次数最多的版本为准。如果有发布的小版本升级，则在该版本基础上，该版本的最新修订版。例如，1.2.3 是最近两年下载最多的版本，但是 1.2.4 已经发布，则优先选择使用 1.2.4。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果有两个大版本，高版本符合条件的情况下，优先选择高版本。低版本大概率是先淘汰的，高版本相对来说维护时间更长，另外高版本的代码优化得更佳。例如，Ehcache 的选择。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果传递依赖造成依赖 Jar 包版本冲突，则尽可能选择高版本的 Jar。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>持续演进的项目的依赖优先级更高；相反，临近淘汰的项目优先级降低，甚至不予考虑。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>两年以上未更新的依赖，在 API 兼容的情况下，直接升级到最新版。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>没有显示使用而是间接引入的依赖，不再单独声明，由直接依赖来引入。如果需要解决冲突，则按照上面的原则来处理。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Java 虚拟机指令(操作码)集</title><link>https://www.diguage.com/post/jvm-instruction-set/</link><pubDate>Wed, 31 Aug 2022 13:00:44 +0800</pubDate><guid>https://www.diguage.com/post/jvm-instruction-set/</guid><description>&lt;div class="paragraph">
&lt;p>最近在研究 Java 虚拟机字节码。在 &lt;a href="https://book.douban.com/subject/26418340/" target="_blank" rel="noopener">《Java虚拟机规范》&lt;/a> 看到一个整理完整的 Java 虚拟机指令集（也叫操作码）列表。转载过来，方便查阅。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>关于 Java 虚拟机指令(操作码)，准备写一个“探秘”系列：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/jvm-bytecode-constant/" target="_blank" rel="noopener">Java 虚拟机操作码探秘：常量指令&lt;/a> — 重点介绍一下关于“常量”指令。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;table class="tableblock frame-all grid-all stripes-even stretch">
&lt;colgroup>
&lt;col style="width: 7%;"/>
&lt;col style="width: 7%;"/>
&lt;col style="width: 7%;"/>
&lt;col style="width: 12%;"/>
&lt;col style="width: 67%;"/>
&lt;/colgroup>
&lt;thead>
&lt;tr>
&lt;th class="tableblock halign-center valign-middle">分类&lt;/th>
&lt;th class="tableblock halign-center valign-middle" colspan="2">操作码&lt;/th>
&lt;th class="tableblock halign-center valign-middle">助记符&lt;/th>
&lt;th class="tableblock halign-center valign-middle">指令含义&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="21">&lt;p class="tableblock">常量&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">0&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x00&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.nop" target="_blank" rel="noopener">nop&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">什么都不做&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">1&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x01&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.aconst_null" target="_blank" rel="noopener">aconst_null&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 null 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">2&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x02&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iconst_i" target="_blank" rel="noopener">iconst_m1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型 -1 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">3&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x03&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iconst_i" target="_blank" rel="noopener">iconst_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型 0 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">4&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x04&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iconst_i" target="_blank" rel="noopener">iconst_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型 1 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">5&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x05&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iconst_i" target="_blank" rel="noopener">iconst_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型 2 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">6&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x06&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iconst_i" target="_blank" rel="noopener">iconst_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型 3 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">7&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x07&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iconst_i" target="_blank" rel="noopener">iconst_4&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型 4 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">8&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x08&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iconst_i" target="_blank" rel="noopener">iconst_5&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型 5 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">9&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x09&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lconst_l" target="_blank" rel="noopener">lconst_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 long 类型 0 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">10&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x0a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lconst_l" target="_blank" rel="noopener">lconst_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 long 类型 1 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">11&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x0b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fconst_f" target="_blank" rel="noopener">fconst_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 float 类型 0 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">12&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x0c&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fconst_f" target="_blank" rel="noopener">fconst_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 float 类型 1 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">13&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x0d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fconst_f" target="_blank" rel="noopener">fconst_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 float 类型 2 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">14&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x0e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dconst_d" target="_blank" rel="noopener">dconst_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 double 类型 0 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">15&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x0f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dconst_d" target="_blank" rel="noopener">dconst_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 double 类型 1 推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">16&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x10&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.bipush" target="_blank" rel="noopener">bipush&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将单字节的常量值(-128 ~ 127)推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">17&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x11&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.sipush" target="_blank" rel="noopener">sipush&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将一个短整类型常量值(-32,768 ~ 32,767)推送栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">18&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x12&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ldc" target="_blank" rel="noopener">ldc&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int、 float 或 String 类型常量值从常量池中推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">19&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x13&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ldc_w" target="_blank" rel="noopener">ldc_w&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将int、 float 或 String 类型常量值从常量池中推送栈顶(宽索引)&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">20&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x14&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ldc2_w" target="_blank" rel="noopener">ldc2_w&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 long 或 double 类型常量值从常量池中推送至栈(宽索引)&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="33">&lt;p class="tableblock">加载&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">21&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>0x15&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iload" target="_blank" rel="noopener">iload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将指定的 int 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">22&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x16&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lload" target="_blank" rel="noopener">lload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将指定的 long 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">23&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x17&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fload" target="_blank" rel="noopener">fload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将指定的 float 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">24&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x18&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dload" target="_blank" rel="noopener">dload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将指定的 double 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">25&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x19&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.aload" target="_blank" rel="noopener">aload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将指定的引用类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">26&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x1a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iload_n" target="_blank" rel="noopener">iload_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 1 个 int 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">27&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x1b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iload_n" target="_blank" rel="noopener">iload_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 2 个 int 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">28&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x1c&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iload_n" target="_blank" rel="noopener">iload_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 3 个 int 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">29&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x1d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iload_n" target="_blank" rel="noopener">iload_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 4 个 int 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">30&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x1e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lload_n" target="_blank" rel="noopener">lload_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 1 个 long 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">31&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x1f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lload_n" target="_blank" rel="noopener">lload_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 2 个 long 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">32&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x20&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lload_n" target="_blank" rel="noopener">lload_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 3 个 long 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">33&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x21&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lload_n" target="_blank" rel="noopener">lload_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 4 个 long 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">34&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x22&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fload_n" target="_blank" rel="noopener">fload_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 1 个 float 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">35&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x23&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fload_n" target="_blank" rel="noopener">fload_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 2 个 float 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">36&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x24&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fload_n" target="_blank" rel="noopener">fload_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 3 个 float 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">37&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x25&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fload_n" target="_blank" rel="noopener">fload_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 4 个 float 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">38&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x26&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dload_n" target="_blank" rel="noopener">dload_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 1 个 double 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">39&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x27&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dload_n" target="_blank" rel="noopener">dload_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 2 个 double 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">40&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x28&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dload_n" target="_blank" rel="noopener">dload_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 3 个 double 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">41&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x29&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dload_n" target="_blank" rel="noopener">dload_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 4 个 double 类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">42&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x2a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.aload_n" target="_blank" rel="noopener">aload_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 1 个引用类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">43&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x2b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.aload_n" target="_blank" rel="noopener">aload_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 2 个引用类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">44&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x2c&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.aload_n" target="_blank" rel="noopener">aload_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 3 个引用类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">45&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x2d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.aload_n" target="_blank" rel="noopener">aload_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将第 4 个引用类型本地变量推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">46&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x2e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iaload" target="_blank" rel="noopener">iaload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型数组的指定元素推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">47&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x2f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.laload" target="_blank" rel="noopener">laload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 long 类型数组的指定元素推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">48&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x30&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.faload" target="_blank" rel="noopener">faload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 float 类型数组的指定元素推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">49&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x31&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.daload" target="_blank" rel="noopener">daload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 double 类型数组的指定元素推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">50&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x32&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.aaload" target="_blank" rel="noopener">aaload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将引用类型数组的指定元素推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">51&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x33&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.baload" target="_blank" rel="noopener">baload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 boolean 或 byte 类型数组的指定元素推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">52&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x34&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.caload" target="_blank" rel="noopener">caload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 char 类型数组的指定元素推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">53&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x35&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.saload" target="_blank" rel="noopener">saload&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 short 类型数组的指定元素推送至栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="33">&lt;p class="tableblock">存储&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">54&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>0x36&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.istore" target="_blank" rel="noopener">istore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值存入指定本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">55&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x37&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lstore" target="_blank" rel="noopener">lstore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值存入指定本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">56&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x38&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fstore" target="_blank" rel="noopener">fstore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值存入指定本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">57&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x39&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dstore" target="_blank" rel="noopener">dstore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值存入指定本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">58&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x3a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.astore" target="_blank" rel="noopener">astore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶引用类型数值存入指定本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">59&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x3b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.istore_n" target="_blank" rel="noopener">istore_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值存入第 1 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">60&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x3c&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.istore_n" target="_blank" rel="noopener">istore_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值存入第 2 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">61&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x3d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.istore_n" target="_blank" rel="noopener">istore_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值存入第 3 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">62&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x3e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.istore_n" target="_blank" rel="noopener">istore_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值存入第 4 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">63&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x3f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lstore_n" target="_blank" rel="noopener">lstore_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值存入第 1 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">64&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x40&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lstore_n" target="_blank" rel="noopener">lstore_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值存入第 2 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">65&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x41&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lstore_n" target="_blank" rel="noopener">lstore_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值存入第 3 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">66&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x42&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lstore_n" target="_blank" rel="noopener">lstore_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值存入第 4 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">67&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x43&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fstore_n" target="_blank" rel="noopener">fstore_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值存入第 1 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">68&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x44&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fstore_n" target="_blank" rel="noopener">fstore_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值存入第 2 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">69&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x45&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fstore_n" target="_blank" rel="noopener">fstore_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值存入第 3 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">70&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x46&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fstore_n" target="_blank" rel="noopener">fstore_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值存入第 4 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">71&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x47&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dstore_n" target="_blank" rel="noopener">dstore_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值存入第 1 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">72&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x48&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dstore_n" target="_blank" rel="noopener">dstore_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值存入第 2 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">73&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x49&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dstore_n" target="_blank" rel="noopener">dstore_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值存入第 3 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">74&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x4a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dstore_n" target="_blank" rel="noopener">dstore_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值存入第 4 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">75&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x4b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.astore_n" target="_blank" rel="noopener">astore_0&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶引用类型数值存入第 1 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">76&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x4c&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.astore_n" target="_blank" rel="noopener">astore_1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶引用类型数值存入第 2 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">77&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x4d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.astore_n" target="_blank" rel="noopener">astore_2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶引用类型数值存入第 3 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">78&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x4e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.astore_n" target="_blank" rel="noopener">astore_3&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶引用类型数值存入第 4 个本地变量&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">79&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x4f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iastore" target="_blank" rel="noopener">iastore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值存入指定数组的指定索引位置&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">80&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x50&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lastore" target="_blank" rel="noopener">lastore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值存入指定数组的指定索引位置&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">81&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x51&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fastore" target="_blank" rel="noopener">fastore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值存入指定数组的指定索引位置&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">82&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x52&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dastore" target="_blank" rel="noopener">dastore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值存入指定数组的指定索引位置&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">83&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x53&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.aastore" target="_blank" rel="noopener">aastore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶引用类型数值存入指定数组的指定索引位置&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">84&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x54&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.bastore" target="_blank" rel="noopener">bastore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 boolean 或 byte 类型数值存入指定数组的指定索引位置&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">85&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x55&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.castore" target="_blank" rel="noopener">castore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 char 类型数值存入指定数组的指定索引位置&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">86&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x56&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.sastore" target="_blank" rel="noopener">sastore&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 short 类型数值存入指定数组的指定索引位置&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="9">&lt;p class="tableblock">栈&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">87&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>0x57&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.pop" target="_blank" rel="noopener">pop&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶数值弹出(数值不能是 long 或 double 类型的)&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">88&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x58&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.pop2" target="_blank" rel="noopener">pop2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶的一个 long 或 double 类型的数值或两个其他类型的数值弹出&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">89&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x59&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dup" target="_blank" rel="noopener">dup&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">复制栈顶数值并将复制值压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">90&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x5a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dup_x1" target="_blank" rel="noopener">dup_x1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">复制栈顶值并将其插入栈顶那两个值的下面&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">91&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x5b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dup_x2" target="_blank" rel="noopener">dup_x2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">复制栈顶值并将其插入栈顶那两个或三个值的下面&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">92&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x5c&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dup2" target="_blank" rel="noopener">dup2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">复制栈顶的一个 long 或 double 类型的值，或两个其他类型的值，并将其压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">93&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x5d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dup2_x1" target="_blank" rel="noopener">dup2_x1&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">复制栈顶的一个或两个值，并将其插入栈顶那两个或三个值的下面&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">94&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x5e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dup2_x2" target="_blank" rel="noopener">dup2_x2&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">复制栈顶的一个或两个值，并将其插入栈顶那两个、三个或四个值的下面&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">95&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x5f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.swap" target="_blank" rel="noopener">swap&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶的两个数值互换(数值不能是 long 或 double 类型的)&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="37">&lt;p class="tableblock">数学&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">96&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>0x60&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iadd" target="_blank" rel="noopener">iadd&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 int 类型数值相加并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">97&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x61&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ladd" target="_blank" rel="noopener">ladd&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 1ong 类型数值相加并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">98&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x62&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fadd" target="_blank" rel="noopener">fadd&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 float 类型数值相加并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">99&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x63&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dadd" target="_blank" rel="noopener">dadd&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 double 类型数值相加并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">100&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x64&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.isub" target="_blank" rel="noopener">isub&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 int 类型数值相减并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">101&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x65&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lsub" target="_blank" rel="noopener">lsub&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 long 类型数值相减并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">102&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x66&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fsub" target="_blank" rel="noopener">fsub&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 float 类型数值相减并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">103&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x67&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dsub" target="_blank" rel="noopener">dsub&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 double 类型数值相减并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">104&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x68&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.imul" target="_blank" rel="noopener">imul&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 int 类型数值相乘并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">105&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x69&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lmul" target="_blank" rel="noopener">lmul&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 long 类型数值相乘并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">106&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x6a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fmul" target="_blank" rel="noopener">fmul&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 float 类型数值相乘并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">107&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x6b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dmul" target="_blank" rel="noopener">dmul&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 double 类型数值相乘并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">108&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x6с&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.idiv" target="_blank" rel="noopener">idiv&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 int 类型数值相除并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">109&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x6d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ldiv" target="_blank" rel="noopener">ldiv&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 long 类型数值相除并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">110&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x6e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fdiv" target="_blank" rel="noopener">fdiv&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 float 类型数值相除并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">111&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x6f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ddiv" target="_blank" rel="noopener">ddiv&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 double 类型数值相除并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">112&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x70&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.irem" target="_blank" rel="noopener">irem&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 int 类型数值作取模运算并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">113&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x71&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lrem" target="_blank" rel="noopener">lrem&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 long 类型数值作取模运算并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">114&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x72&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.frem" target="_blank" rel="noopener">frem&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 float 类型数值作取模运算并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">115&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x73&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.drem" target="_blank" rel="noopener">drem&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 double 类型数值作取模运算并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">116&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x74&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ineg" target="_blank" rel="noopener">ineg&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值取负并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">117&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x75&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lneg" target="_blank" rel="noopener">lneg&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值取负并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">118&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x76&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fneg" target="_blank" rel="noopener">fneg&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值取负并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">119&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x77&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dneg" target="_blank" rel="noopener">dneg&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值取负并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">120&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x78&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ishl" target="_blank" rel="noopener">ishl&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型数值左移位指定位数并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">121&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x79&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lshl" target="_blank" rel="noopener">lshl&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 long 类型数值左移位指定位数并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">122&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x7a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ishr" target="_blank" rel="noopener">ishr&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型数值(有符号)右移位指定位数并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">123&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x7b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lshr" target="_blank" rel="noopener">lshr&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 long 类型数值(有符号)右移位指定位数并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">124&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">0x7c&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iushr" target="_blank" rel="noopener">iushr&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 int 类型数值(无符号)右移位指定位数并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">125&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x7d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lushr" target="_blank" rel="noopener">lushr&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将 long 类型数值(无符号)右移位指定位数并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">126&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x7e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iand" target="_blank" rel="noopener">iand&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 int 类型数值作“按位与”并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">127&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x7f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.land" target="_blank" rel="noopener">land&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 long 类型数值作“按位与”并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">128&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x80&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ior" target="_blank" rel="noopener">ior&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 int 类型数值作“按位或”并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">129&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">0x81&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lor" target="_blank" rel="noopener">lor&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 long 类型数值作“按位或”并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">130&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x82&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ixor" target="_blank" rel="noopener">ixor&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 int 类型数值作“按位异或”并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">131&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x83&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lxor" target="_blank" rel="noopener">lxor&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶两 long 类型数值作“按位异或”并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">132&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x84&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.iinc" target="_blank" rel="noopener">iinc&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将指定 int 类型变量增加指定值(&lt;code>i++&lt;/code>，&lt;code>i--&lt;/code>，&lt;code>i += 2&lt;/code>)&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="15">&lt;p class="tableblock">转换&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">133&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>0x85&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.i2l" target="_blank" rel="noopener">i2l&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值强制转换成 long 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">134&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x86&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.i2f" target="_blank" rel="noopener">i2f&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值强制转换成 float 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">135&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x87&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.i2d" target="_blank" rel="noopener">i2d&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值强制转换成 double 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">136&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x88&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.l2i" target="_blank" rel="noopener">l2i&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值强制转换成 int 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">137&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x89&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.l2f" target="_blank" rel="noopener">l2f&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值强制转换成 float 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">138&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x8a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.l2d" target="_blank" rel="noopener">l2d&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 long 类型数值强制转换成 double 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">139&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x8b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.f2i" target="_blank" rel="noopener">f2i&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值强制转换成 int 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">140&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x8c&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.f2l" target="_blank" rel="noopener">f2l&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值强制转换成 long 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">141&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x8d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.f2d" target="_blank" rel="noopener">f2d&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 float 类型数值强制转换成 double 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">142&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x8e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.d2i" target="_blank" rel="noopener">d2i&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值强制转换成 int 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">143&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x8f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.d2l" target="_blank" rel="noopener">d2l&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值强制转换成 long 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">144&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x90&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.d2f" target="_blank" rel="noopener">d2f&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 double 类型数值强制转换成 float 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">145&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x91&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.i2b" target="_blank" rel="noopener">i2b&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值强制转换成 byte 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">146&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x92&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.i2c" target="_blank" rel="noopener">i2c&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值强制转换成 char 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">147&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x93&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.i2s" target="_blank" rel="noopener">i2s&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶 int 类型数值强制转换成 short 类型数值并将结果压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="19">&lt;p class="tableblock">比较&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">148&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>0x94&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lcmp" target="_blank" rel="noopener">lcmp&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 long 类型数值大小，并将结果(1，0，-1)压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">149&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x95&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fcmp_op" target="_blank" rel="noopener">fcmpl&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 float 类型数值大小，并将结果(1，0，-1)压入栈顶；当其中一个数值为“NaN”时，将 -1 压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">150&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x96&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.fcmp_op" target="_blank" rel="noopener">fcmpg&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 float 类型数值大小，并将结果(1，0，-1)压入栈顶；当其中一个数值为“NaN”时，将1压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">151&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x97&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dcmp_op" target="_blank" rel="noopener">dcmpl&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 double 类型数值大小，并将结果(1，0，-1)压入栈顶；当其中一个数值为“NaN”时，将-1压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">152&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x98&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dcmp_op" target="_blank" rel="noopener">dcmpg&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 double 类型数值大小，并将结果(1，0，-1)压入栈顶；当其中一个数值为“NaN”时，将1压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">153&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x99&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_cond" target="_blank" rel="noopener">ifeq&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">当栈顶 int 类型数值等于 0 时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">154&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x9a&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_cond" target="_blank" rel="noopener">ifne&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">当栈顶 int 类型数值不等于 0 时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">155&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x9b&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_cond" target="_blank" rel="noopener">iflt&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">当栈顶 int 类型数值小于 0 时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">156&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x9c&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_cond" target="_blank" rel="noopener">ifge&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">当栈顶 int 类型数值大于等于 0 时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">157&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x9d&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_cond" target="_blank" rel="noopener">ifgt&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">当栈顶 int 类型数值大于 0 时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">158&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x9e&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_cond" target="_blank" rel="noopener">ifle&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">当栈顶 int 类型数值小于等于 0 时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">159&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0x9f&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_icmp_cond" target="_blank" rel="noopener">if_icmpeq&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 int 类型数值大小，当前者等于后者时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">160&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xa0&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_icmp_cond" target="_blank" rel="noopener">if_icmpne&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 int 类型数值大小，当前者不等于后者时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">161&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xa1&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_icmp_cond" target="_blank" rel="noopener">if_icmplt&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 int 类型数值大小，当前者小于后者时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">162&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xa2&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_icmp_cond" target="_blank" rel="noopener">if_icmpge&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 int 类型数值大小，当前者大于等于后者时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">163&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xa3&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_icmp_cond" target="_blank" rel="noopener">if_icmpgt&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 int 类型数值大小，当前者大于后者时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">164&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xa4&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_icmp_cond" target="_blank" rel="noopener">if_icmple&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两 int 类型数值大小，当前者小于等于后者时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">165&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xa5&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_acmp_cond" target="_blank" rel="noopener">if_acmpeq&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两引用类型数值，当结果相等时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">166&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xa6&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.if_acmp_cond" target="_blank" rel="noopener">ifacmpne&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">比较栈顶两引用类型数值，当结果不相等时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="11">&lt;p class="tableblock">控制&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">167&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>0xa7&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.goto" target="_blank" rel="noopener">goto&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">无条件跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">168&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xa8&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.jsr" target="_blank" rel="noopener">jsr&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">跳转至指定 16 位 offset 位置，并将 jsr 下一条指令地址压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">169&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xa9&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ret" target="_blank" rel="noopener">ret&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">返回至由指定的局部变量所给出的指令位置(一般与 jsr、jsr_w 联合使用)&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">170&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xaa&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.tableswitch" target="_blank" rel="noopener">tableswitch&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">用于 switch 条件跳转，case 值连续(变长指令)&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">171&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xab&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lookupswitch" target="_blank" rel="noopener">lookupswitch&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">用于 switch 条件跳转，case 值不连续(变长指令)&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">172&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xac&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ireturn" target="_blank" rel="noopener">ireturn&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">从当前方法返回 int&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">173&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>Oxad&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.lreturn" target="_blank" rel="noopener">lreturn&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">从当前方法返回 long&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">174&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xae&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.freturn" target="_blank" rel="noopener">freturn&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">从当前方法返回 float&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">175&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xaf&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.dreturn" target="_blank" rel="noopener">dreturn&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">从当前方法返回 double&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">176&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xb0&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.areturn" target="_blank" rel="noopener">areturn&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">从当前方法返回对象引用&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">177&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xb1&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.return" target="_blank" rel="noopener">return&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">从当前方法返回void&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="18">&lt;p class="tableblock">引用&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">178&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>0xb2&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.getstatic" target="_blank" rel="noopener">getstatic&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">获取指定类的静态字段，并将其值压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">179&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xb3&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.putstatic" target="_blank" rel="noopener">putstatic&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">为指定类的静态字段赋值&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">180&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xb4&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.getfield" target="_blank" rel="noopener">getfield&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">获取指定类的实例字段，并将其值压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">181&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xb5&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.putfield" target="_blank" rel="noopener">putfield&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">为指定类的实例字段赋值&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">182&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xb6&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.invokevirtual" target="_blank" rel="noopener">invokevirtual&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">调用实例方法&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">183&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xb7&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.invokespecial" target="_blank" rel="noopener">invokespecial&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">调用父类方法、实例初始化方法、私有方法&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">184&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xb8&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.invokestatic" target="_blank" rel="noopener">invokestatic&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">调用静态方法&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">185&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xb9&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.invokeinterface" target="_blank" rel="noopener">invokeinterface&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">调用接口方法&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">186&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xba&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.invokedynamic" target="_blank" rel="noopener">invokedynamic&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">调用动态链接方法&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">187&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xbb&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.new" target="_blank" rel="noopener">new&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">创建一个对象，并将其引用值压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">188&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xbc&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.newarray" target="_blank" rel="noopener">newarray&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">创建一个指定原始类型(如int、float 、char等)的数组，并将其引用值压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">189&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xbd&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.anewarray" target="_blank" rel="noopener">anewarray&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">创建一个引用型(如类、接口、数组)的数组，并将其引用值压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">190&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xbe&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.arraylength" target="_blank" rel="noopener">arraylength&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">获得数组的长度值并压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">191&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xbf&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.athrow" target="_blank" rel="noopener">athrow&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">将栈顶的异常抛出&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">192&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">0xcO&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.checkcast" target="_blank" rel="noopener">checkcast&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">检验类型转换，检验未通过将抛出 ClassCastException&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">193&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">0xc1&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.instanceof" target="_blank" rel="noopener">instanceof&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">检验对象是否是指定类的实例。如果是，就将 1 压入栈顶，否则将 0 压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">194&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xc2&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.monitorenter" target="_blank" rel="noopener">monitorenter&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">获得对象的锁，用于实现同步块&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">195&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xc3&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.monitorexit" target="_blank" rel="noopener">monitorexit&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">释放对象的锁，用于实现同步块&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="6">&lt;p class="tableblock">扩展&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">196&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>0xc4&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.wide" target="_blank" rel="noopener">wide&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">扩展本地变量索引的宽度&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">197&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xс5&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.multianewarray" target="_blank" rel="noopener">multianewarray&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">创建指定类型和指定维度的多维数组(执行该指令时，操作栈中必须包含各维度的长度值)，并将其引用值压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">198&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xc6&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ifnull" target="_blank" rel="noopener">ifnull&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">为nu11时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">199&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xc7&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.ifnonnull" target="_blank" rel="noopener">ifnonnull&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">不为nu11时跳转&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">200&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xc8&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.goto_w" target="_blank" rel="noopener">goto_w&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">无条件跳转(宽索引)&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">201&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xc9&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.jsr_w" target="_blank" rel="noopener">jsr_w&lt;/a>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">跳转至指定 32 位 offset 位置，并将 jsr_w 下一条指令地址压入栈顶&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle" rowspan="3">&lt;p class="tableblock">保留指令&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">202&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">&lt;code>Оxca&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">breakpoint&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">调试时的断点标记&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">254&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>Oxfe&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">impdep1&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">为特定软件面预留的语言后门&lt;/p>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">255&lt;/p>&lt;/td>
&lt;td class="tableblock halign-center valign-middle">&lt;p class="tableblock">&lt;code>0xff&lt;/code>&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">impdep2&lt;/p>&lt;/td>
&lt;td class="tableblock halign-left valign-top">&lt;p class="tableblock">为特定硬件面预留的语言后门&lt;/p>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="sect1">
&lt;h2 id="_参考资料">参考资料&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html" target="_blank" rel="noopener">Chapter 6. The Java Virtual Machine Instruction Set&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/Java_bytecode" target="_blank" rel="noopener">Java bytecode&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions" target="_blank" rel="noopener">List of Java bytecode instructions&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>关于升级 Spring 等依赖的一些经验</title><link>https://www.diguage.com/post/experience-about-upgrading-java-dependencies/</link><pubDate>Sun, 28 Aug 2022 09:19:59 +0800</pubDate><guid>https://www.diguage.com/post/experience-about-upgrading-java-dependencies/</guid><description>&lt;div class="paragraph">
&lt;p>到公司后，熟悉了一些项目后，发现大部分项目的依赖都比较陈旧，比如某些项目还在使用 Spring 3.x 的版本。所以，在进行需求开发时，也顺手把一些项目的依赖给升级了一下。周五，一个小伙伴问我关于升级 Spring 的经验。正好趁此机会，把一些经验总结一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面的描述以 Java 8 为准，没有在其他版本 Java 上试验过。参考时，请慎重。描述的原则如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>尽量选择还在维护中的版本，而不是已经 End of Life 的过时版。这样有问题可以及时反馈并得到修复。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Java 8 是目标版本，所以，一定要兼容 Java 8。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_spring_framework_升级">Spring Framework 升级&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Spring Framework 从 3.2.6.RELEASE 开始提供 BOM。可以利用 BOM 简化 Spring 依赖声明：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="xml">&lt;span class="c">&amp;lt;!-- D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a> --&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;dependencyManagement&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;dependencies&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-framework-bom&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>5.3.25&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;type&amp;gt;&lt;/span>pom&lt;span class="nt">&amp;lt;/type&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>import&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;/dependencies&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/dependencyManagement&amp;gt;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这样，就不需要重复声明 Spring 依赖的版本，直接使用即可。 Spring Framework Bom 保证了 Spring 自身依赖的版本统一。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这里，关于 Spring 的升级，还有几点需要说明：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>从 Spring 3.X 升级到 Spring 4.X+ 后，原来的 &lt;code>MappingJacksonHttpMessageConverter&lt;/code> 已经被删除了；直接使用 &lt;code>MappingJackson2HttpMessageConverter&lt;/code> 即可。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>从 Spring 3.0.0.RELEASE 到 Spring 3.1.4.RELEASE，Spring 有一个 &lt;code>spring-asm&lt;/code>，如果不再使用这个区间的 Spring，请把这个依赖删掉。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果使用了 Apache Velocity 1.X 作为前端模板，由于 Spring 5+ 将相关集成代码删除，所以，只能将 Spring 升级到 4.3.30.RELEASE。相关 BOM 如下：&lt;/p>
&lt;div class="openblock">
&lt;div class="content">
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="xml">&lt;span class="c">&amp;lt;!-- D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a> --&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-framework-bom&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>4.3.30.RELEASE&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;type&amp;gt;&lt;/span>pom&lt;span class="nt">&amp;lt;/type&amp;gt;&lt;/span>
 &lt;span class="nt">&amp;lt;scope&amp;gt;&lt;/span>import&lt;span class="nt">&amp;lt;/scope&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Avro、ProtoBuf、Thrift 的模式演进之法【翻译】</title><link>https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift/</link><pubDate>Sat, 27 Aug 2022 08:30:43 +0800</pubDate><guid>https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift/</guid><description>&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>前面系统研究了 Hessian 序列化协议。并以此为契机，顺带实例对比了 Hessian、MessagePack 和 JSON 的序列化。早在 2012 年，Martin Kleppmann 就写了一篇文章 &lt;a href="https://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html" target="_blank" rel="noopener">《Schema evolution in Avro, Protocol Buffers and Thrift》&lt;/a>，也是基于实例，对比了 Avro、ProtoBuf、Thrift 的差别。现在翻译出来，方便做系列研究。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>整个“序列化系列”目录如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a> — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章，增加了协议内容错误提示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数&lt;/a> — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> — 介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 等三种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/" target="_blank" rel="noopener">Hessian 协议解释与实战（三）：字符串&lt;/a> — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多，处理细节也有繁琐，所以单独成篇来介绍。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> — 开始第四篇分析之前，先来介绍一下 Hessian 的源码实现。方便后续展开说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> — 铺垫了一些关于实例对象的处理，重点介绍关于数组和集合的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/" target="_blank" rel="noopener">Hessian 协议解释与实战（五）：对象与映射&lt;/a> — 重点介绍关于对象与映射的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-vs-msgpack-vs-json/" target="_blank" rel="noopener">Hessian、Msgpack 和 JSON 实例对比&lt;/a> — 用实例对比 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift" target="_blank" rel="noopener">Avro、ProtoBuf、Thrift 的模式演进之路&lt;/a> — 翻译的 Martin Kleppmann 的文章，重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>用自己的小故事致敬白衣天使</title><link>https://www.diguage.com/post/show-my-respect-to-medical-workers-by-my-story-on-medical-workers-day/</link><pubDate>Fri, 19 Aug 2022 08:55:06 +0800</pubDate><guid>https://www.diguage.com/post/show-my-respect-to-medical-workers-by-my-story-on-medical-workers-day/</guid><description>&lt;div class="paragraph">
&lt;p>8·19中国医师节&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>致敬守护健康的白衣天使&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>虽然我一直挺健康，但我与医生的故事有好多。按时间顺序，挑选几个来说一说。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>早在 2018 年，由于智齿斜长，顶住最后大牙，担心影响到大牙的健康，就到北京大学口腔医院魏公村总院，做智齿拔除手术。由于去排队时间较晚，就被分配了一个急诊号，不知道医生是何许人也。拍片做完检查，医生说我上下有两个智齿，可以考虑一起拔除。麻药敲击拔牙，一气呵成。医生说，你可以走啦。我疑问，不是拔两颗吗？怎么只拔了一颗？医生说，两颗都拔啦。没想到这么迅速。实在是太专业啦！这名医生叫郭华秋。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 2019 年，在盆友圈看到一个纪录片《医者·脊梁》，认识了一个医生，在业余时间，利用自己的专业知识，深入偏远地区，为这些偏远地区免费义诊，并且建立了一个基金会，为需要手术的脊柱侧弯患者承担必要的费用。经过短暂思考后就决定，要为这个基金会捐款，让他们去救助更多的人。初步定在每年六一儿童节这天，每年捐一千块钱，连续捐十年。这家基金会叫北京海鹰脊柱健康公益基金会。这名医生叫刘海鹰。（他是我的安阳老乡，也是我的大学师兄。真是荣幸之至！）放两张盆友圈截图：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/medical-workers-day/donation-2018.jpg" alt="2018年捐款" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 2018年捐款&lt;/div>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/medical-workers-day/donation-2022.jpg" alt="2022年捐款" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. 2022年捐款&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>2020年，一场突如其来的疫情，让很多很多医护工作者舍小家为大家，用自己的臂膀组建了守护大家健康的卫生长城。这些医生和护士叫中国医护人员。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>感谢他们的付出，感谢他们的辛劳，也感谢他们的专业。有感于此，我在2021年初，就请我认识的每一位医护朋友吃一顿饭。可惜到现在为止，还没有成行。以后有机会一起吃饭的话，希望医护朋友不要可以客气，给我一个表达感激的机会。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/medical-workers-day/medical-1.jpg" alt="向医护人员致敬" width="95%"/>
&lt;/div>
&lt;div class="title">图 3. 向医护人员致敬&lt;/div>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>这是公众号的一篇文章。昨天早晨出门吃早餐，刷朋友圈才知道昨天是医师节。在地铁上写了这篇文章。本来是昨天群发的。由于没有想尝试一次群发多篇文章，就在手机APP上先发表了附加的文章，以为只是发布而不群发。结果，搞了个乌龙，直接群发了，也没办法撤销群发。所以，这篇文章只能今天发表了。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div></description></item><item><title>Hessian、Msgpack 和 JSON 实例对比</title><link>https://www.diguage.com/post/hessian-vs-msgpack-vs-json/</link><pubDate>Fri, 05 Aug 2022 11:28:03 +0800</pubDate><guid>https://www.diguage.com/post/hessian-vs-msgpack-vs-json/</guid><description>&lt;div class="paragraph">
&lt;p>前段时间，翻译了 Hessian 2.0 的序列化协议，发布在了 &lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a>。但是，其中有很多言语不详之处。所以，接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议，以求做到知其然知其所以然。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>目录如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a> — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章，增加了协议内容错误提示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数&lt;/a> — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> — 介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 等三种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/" target="_blank" rel="noopener">Hessian 协议解释与实战（三）：字符串&lt;/a> — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多，处理细节也有繁琐，所以单独成篇来介绍。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> — 开始第四篇分析之前，先来介绍一下 Hessian 的源码实现。方便后续展开说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> — 铺垫了一些关于实例对象的处理，重点介绍关于数组和集合的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/" target="_blank" rel="noopener">Hessian 协议解释与实战（五）：对象与映射&lt;/a> — 重点介绍关于对象与映射的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-vs-msgpack-vs-json/" target="_blank" rel="noopener">Hessian、Msgpack 和 JSON 实例对比&lt;/a> — 用实例对比 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift" target="_blank" rel="noopener">Avro、ProtoBuf、Thrift 的模式演进之路&lt;/a> — 翻译的 Martin Kleppmann 的文章，重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>本文用实际来对比一下 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/div></description></item><item><title>Hessian 协议解释与实战（五）：对象与映射</title><link>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/</link><pubDate>Tue, 31 May 2022 16:02:13 +0800</pubDate><guid>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/</guid><description>&lt;div class="paragraph">
&lt;p>前段时间，翻译了 Hessian 2.0 的序列化协议，发布在了 &lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a>。但是，其中有很多言语不详之处。所以，接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议，以求做到知其然知其所以然。目录如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a> — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章，增加了协议内容错误提示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数&lt;/a> — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> — 介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 等三种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/" target="_blank" rel="noopener">Hessian 协议解释与实战（三）：字符串&lt;/a> — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多，处理细节也有繁琐，所以单独成篇来介绍。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> — 开始第四篇分析之前，先来介绍一下 Hessian 的源码实现。方便后续展开说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> — 铺垫了一些关于实例对象的处理，重点介绍关于数组和集合的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/" target="_blank" rel="noopener">Hessian 协议解释与实战（五）：对象与映射&lt;/a> — 重点介绍关于对象与映射的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-vs-msgpack-vs-json/" target="_blank" rel="noopener">Hessian、Msgpack 和 JSON 实例对比&lt;/a> — 用实例对比 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift" target="_blank" rel="noopener">Avro、ProtoBuf、Thrift 的模式演进之路&lt;/a> — 翻译的 Martin Kleppmann 的文章，重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> 中研究了数组和集合的处理方式。接下来介绍对象和映射的处理。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_基础工具方法">基础工具方法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>基础工具方法就不再赘述，请直接参考 &lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/#helper-methods" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：基础工具方法&lt;/a> 中提到的几个方法。&lt;/p>
&lt;/div></description></item><item><title>Hessian 协议解释与实战（四）：数组与集合</title><link>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/</link><pubDate>Thu, 26 May 2022 18:01:43 +0800</pubDate><guid>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/</guid><description>&lt;div class="paragraph">
&lt;p>前段时间，翻译了 Hessian 2.0 的序列化协议，发布在了 &lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a>。但是，其中有很多言语不详之处。所以，接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议，以求做到知其然知其所以然。目录如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a> — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章，增加了协议内容错误提示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数&lt;/a> — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> — 介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 等三种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/" target="_blank" rel="noopener">Hessian 协议解释与实战（三）：字符串&lt;/a> — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多，处理细节也有繁琐，所以单独成篇来介绍。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> — 开始第四篇分析之前，先来介绍一下 Hessian 的源码实现。方便后续展开说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> — 铺垫了一些关于实例对象的处理，重点介绍关于数组和集合的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/" target="_blank" rel="noopener">Hessian 协议解释与实战（五）：对象与映射&lt;/a> — 重点介绍关于对象与映射的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-vs-msgpack-vs-json/" target="_blank" rel="noopener">Hessian、Msgpack 和 JSON 实例对比&lt;/a> — 用实例对比 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift" target="_blank" rel="noopener">Avro、ProtoBuf、Thrift 的模式演进之路&lt;/a> — 翻译的 Martin Kleppmann 的文章，重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> 对 Hessian 的 Java 实现做了一个概要的分析，对处理流程以及整体架构做了一个简单的分析。接下来，回到主题，继续来解释 Hessian 序列化协议。这篇文章，我们来重点分析一下数组与集合相关的操作。&lt;/p>
&lt;/div></description></item><item><title>Hessian 源码分析（Java）</title><link>https://www.diguage.com/post/hessian-source-analysis-for-java/</link><pubDate>Sat, 21 May 2022 09:39:44 +0800</pubDate><guid>https://www.diguage.com/post/hessian-source-analysis-for-java/</guid><description>&lt;div class="paragraph">
&lt;p>前面通过几篇文章，解释并实践了一下 Hessian 的序列化协议。文章目录如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a> — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章，增加了协议内容错误提示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数&lt;/a> — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> — 介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 等三种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/" target="_blank" rel="noopener">Hessian 协议解释与实战（三）：字符串&lt;/a> — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多，处理细节也有繁琐，所以单独成篇来介绍。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> — 开始第四篇分析之前，先来介绍一下 Hessian 的源码实现。方便后续展开说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> — 铺垫了一些关于实例对象的处理，重点介绍关于数组和集合的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/" target="_blank" rel="noopener">Hessian 协议解释与实战（五）：对象与映射&lt;/a> — 重点介绍关于对象与映射的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-vs-msgpack-vs-json/" target="_blank" rel="noopener">Hessian、Msgpack 和 JSON 实例对比&lt;/a> — 用实例对比 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift" target="_blank" rel="noopener">Avro、ProtoBuf、Thrift 的模式演进之路&lt;/a> — 翻译的 Martin Kleppmann 的文章，重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>该系列第四篇文章准备详细介绍一下 Hessian 对对象、链表以及 &lt;code>Map&lt;/code> 等处理。但是，越调试代码，越发觉得应该先对 Hessian 的实现做一个源码分析。于是，就有了本文。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这里有几点需要声明一下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>在上面“解释与实战”系列文章中提到的代码就不再重复说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>通过“解释与实战”系列文章，大家应该可以领略到，处理序列化有大量的细节。但是，本文并不打算涉及。本文重点是介绍 Hessian 的 Java 实现的架构蓝图。相当于给指明一条路，沿着这条路，大家就可以探索 Hessian 的各种细节。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>本文的介绍，全部基于 Hessian 4.0.60 的源码。由于没有找到 Hessian 的仓库，D瓜哥从 Hessian 的网站下，下载了源码包，解压后发布在了 GitHub 上： &lt;a href="https://github.com/diguage/hessian" target="_blank" rel="noopener">Hessian — The source code of Hessian Library.&lt;/a>。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Hessian 协议解释与实战（三）：字符串</title><link>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/</link><pubDate>Wed, 04 May 2022 16:09:05 +0000</pubDate><guid>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/</guid><description>&lt;div class="paragraph">
&lt;p>前段时间，翻译了 Hessian 2.0 的序列化协议，发布在了 &lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a>。但是，其中有很多言语不详之处。所以，接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议，以求做到知其然知其所以然。目录如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a> — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章，增加了协议内容错误提示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数&lt;/a> — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> — 介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 等三种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/" target="_blank" rel="noopener">Hessian 协议解释与实战（三）：字符串&lt;/a> — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多，处理细节也有繁琐，所以单独成篇来介绍。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> — 开始第四篇分析之前，先来介绍一下 Hessian 的源码实现。方便后续展开说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> — 铺垫了一些关于实例对象的处理，重点介绍关于数组和集合的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/" target="_blank" rel="noopener">Hessian 协议解释与实战（五）：对象与映射&lt;/a> — 重点介绍关于对象与映射的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-vs-msgpack-vs-json/" target="_blank" rel="noopener">Hessian、Msgpack 和 JSON 实例对比&lt;/a> — 用实例对比 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift" target="_blank" rel="noopener">Avro、ProtoBuf、Thrift 的模式演进之路&lt;/a> — 翻译的 Martin Kleppmann 的文章，重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> 中研究了长整型、二进制数据与 &lt;code>null&lt;/code> 等三种数据类型的处理方式。接下来，我们再来介绍字符串的处理情况。&lt;/p>
&lt;/div></description></item><item><title>Hessian 协议解释与实战（二）：长整型、二进制数据与 Null</title><link>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/</link><pubDate>Tue, 03 May 2022 16:09:15 +0800</pubDate><guid>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/</guid><description>&lt;div class="paragraph">
&lt;p>前段时间，翻译了 Hessian 2.0 的序列化协议，发布在了 &lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a>。但是，其中有很多言语不详之处。所以，接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议，以求做到知其然知其所以然。目录如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a> — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章，增加了协议内容错误提示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数&lt;/a> — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> — 介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 等三种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/" target="_blank" rel="noopener">Hessian 协议解释与实战（三）：字符串&lt;/a> — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多，处理细节也有繁琐，所以单独成篇来介绍。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> — 开始第四篇分析之前，先来介绍一下 Hessian 的源码实现。方便后续展开说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> — 铺垫了一些关于实例对象的处理，重点介绍关于数组和集合的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/" target="_blank" rel="noopener">Hessian 协议解释与实战（五）：对象与映射&lt;/a> — 重点介绍关于对象与映射的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-vs-msgpack-vs-json/" target="_blank" rel="noopener">Hessian、Msgpack 和 JSON 实例对比&lt;/a> — 用实例对比 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift" target="_blank" rel="noopener">Avro、ProtoBuf、Thrift 的模式演进之路&lt;/a> — 翻译的 Martin Kleppmann 的文章，重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）&lt;/a> 中研究了布尔型数据、日期类型、浮点类型数据、整数类型数据等四种数据类型的处理方式。接下来，我们再来介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 的处理情况。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_基础工具方法">基础工具方法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>基础工具方法就不再赘述，请直接参考 &lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/#helper-methods" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数：基础工具方法&lt;/a> 中提到的几个方法。&lt;/p>
&lt;/div></description></item><item><title>Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数</title><link>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/</link><pubDate>Mon, 02 May 2022 17:22:01 +0800</pubDate><guid>https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/</guid><description>&lt;div class="paragraph">
&lt;p>前段时间，翻译了 Hessian 2.0 的序列化协议，发布在了 &lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a>。但是，其中有很多言语不详之处。所以，接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议，以求做到知其然知其所以然。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>目录如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a> — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章，增加了协议内容错误提示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数&lt;/a> — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> — 介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 等三种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/" target="_blank" rel="noopener">Hessian 协议解释与实战（三）：字符串&lt;/a> — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多，处理细节也有繁琐，所以单独成篇来介绍。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> — 开始第四篇分析之前，先来介绍一下 Hessian 的源码实现。方便后续展开说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> — 铺垫了一些关于实例对象的处理，重点介绍关于数组和集合的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/" target="_blank" rel="noopener">Hessian 协议解释与实战（五）：对象与映射&lt;/a> — 重点介绍关于对象与映射的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-vs-msgpack-vs-json/" target="_blank" rel="noopener">Hessian、Msgpack 和 JSON 实例对比&lt;/a> — 用实例对比 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift" target="_blank" rel="noopener">Avro、ProtoBuf、Thrift 的模式演进之路&lt;/a> — 翻译的 Martin Kleppmann 的文章，重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;hr/>
&lt;div class="sect1">
&lt;h2 id="helper-methods">基础工具方法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Hessian 序列化之后的数据，都是字节数组，为了方便查看字节数组的二进制形式和十六进制形式，在正式开始之前，先介绍一下期间用到的辅助工具方法。闲言少叙，直接上代码：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * 创建 Hessian2Output 对象，以便用于序列化
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>
 */&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="nc">Hessian2Output&lt;/span> &lt;span class="nf">getHessian2Output&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">OutputStream&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">SerializerFactory&lt;/span> &lt;span class="n">serializerFactory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">SerializerFactory&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="n">serializerFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setAllowNonSerializable&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="nc">Hessian2Output&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">Hessian2Output&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setSerializerFactory&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">serializerFactory&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>

&lt;span class="cm">/**
 * 打印字节数组
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>
 */&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">printBytes&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">byte&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">bitx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nc">Integer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">toBinaryString&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">Byte&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">toUnsignedInt&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="o">));&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">zbits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nc">String&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">format&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;%8s&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">bitx&lt;/span>&lt;span class="o">).&lt;/span>&lt;span class="na">replace&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="sc">&amp;#39; &amp;#39;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="sc">&amp;#39;0&amp;#39;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="mi">0&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">printf&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;%4d 0x%02X %8s %c %n&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">zbits&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">printf&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;%4d 0x%02X %8s %n&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">zbits&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>

&lt;span class="cm">/**
 * 将 long 转化成二进制字符串（前面补0）
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>
 */&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="nc">String&lt;/span> &lt;span class="nf">getBinaryString&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">bits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nc">Long&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">toBinaryString&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="kt">char&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">chars&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nc">String&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">format&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;%64s&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">bits&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="o">.&lt;/span>&lt;span class="na">replace&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="sc">&amp;#39; &amp;#39;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="sc">&amp;#39;0&amp;#39;&lt;/span>&lt;span class="o">).&lt;/span>&lt;span class="na">toCharArray&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="nc">StringBuilder&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">StringBuilder&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">64&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">chars&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">append&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">chars&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">]);&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">chars&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">append&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;,&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">toString&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="o">}&lt;/span>

&lt;span class="cm">/**
 * 将 int 转化成二进制字符串（前面补0）
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>
 */&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="nc">String&lt;/span> &lt;span class="nf">getBinaryString&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">bits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nc">Integer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">toBinaryString&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="kt">char&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">chars&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nc">String&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">format&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;%32s&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">bits&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="o">.&lt;/span>&lt;span class="na">replace&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="sc">&amp;#39; &amp;#39;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="sc">&amp;#39;0&amp;#39;&lt;/span>&lt;span class="o">).&lt;/span>&lt;span class="na">toCharArray&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="nc">StringBuilder&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">StringBuilder&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">64&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">chars&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">append&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">chars&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">]);&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">chars&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">append&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;,&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">toString&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Hessian 2.0 序列化协议（中文版）</title><link>https://www.diguage.com/post/hessian-serialization-protocol/</link><pubDate>Wed, 20 Apr 2022 17:30:08 +0800</pubDate><guid>https://www.diguage.com/post/hessian-serialization-protocol/</guid><description>&lt;div class="paragraph">
&lt;p>公司在微服务系统中，序列化协议大多数使用 &lt;a href="https://msgpack.org/" target="_blank" rel="noopener">MessagePack&lt;/a>。但是，由于 MessagePack 设计限制，导致微服务接口在增减参数时，只能在最后操作。但是，由于个人操作，难免失误，结果造成因为增减字段导致的事故层出不穷。最近，一些条件成熟，准备推动部门将序列化协议切换到 Hessian。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>原以为，切换到 Hessian 就可以万事大吉。但是，在和同事的沟通中发现，同事反馈，Hessian 本身也有一些限制。为了对 Hessian 有一个更深入的了解，干脆就把 Hessian 序列化协议读一遍。看协议，文字不多，干脆就把协议完整翻译一遍。闲言少叙，正文开始。&lt;/p>
&lt;/div>
&lt;hr/>
&lt;div class="sect1 text-center">
&lt;h2 id="_hessian_2_0_序列化协议">&lt;span class="big">&lt;strong>Hessian 2.0 序列化协议&lt;/strong>&lt;/span>&lt;/h2>
&lt;div class="sectionbody">

&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_协议解释">协议解释&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>针对该协议有很多言语不详，甚至模糊不清之处，专门做了一些解释和实践，叙述系列文章，用于辅助消化理解。目录如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-serialization-protocol/" target="_blank" rel="noopener">Hessian 2.0 序列化协议（中文版）&lt;/a> — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章，增加了协议内容错误提示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-1/" target="_blank" rel="noopener">Hessian 协议解释与实战（一）：布尔、日期、浮点数与整数&lt;/a> — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/" target="_blank" rel="noopener">Hessian 协议解释与实战（二）：长整型、二进制数据与 Null&lt;/a> — 介绍长整数类型数据、二进制数据和 &lt;code>null&lt;/code> 等三种类型的数据的处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-3/" target="_blank" rel="noopener">Hessian 协议解释与实战（三）：字符串&lt;/a> — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多，处理细节也有繁琐，所以单独成篇来介绍。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-source-analysis-for-java/" target="_blank" rel="noopener">Hessian 源码分析（Java）&lt;/a> — 开始第四篇分析之前，先来介绍一下 Hessian 的源码实现。方便后续展开说明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-4/" target="_blank" rel="noopener">Hessian 协议解释与实战（四）：数组与集合&lt;/a> — 铺垫了一些关于实例对象的处理，重点介绍关于数组和集合的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-5/" target="_blank" rel="noopener">Hessian 协议解释与实战（五）：对象与映射&lt;/a> — 重点介绍关于对象与映射的相关处理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/hessian-vs-msgpack-vs-json/" target="_blank" rel="noopener">Hessian、Msgpack 和 JSON 实例对比&lt;/a> — 用实例对比 JSON、Hessian 和 MessagePack 的区别。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/schema-evolution-in-avro-protobuf-thrift" target="_blank" rel="noopener">Avro、ProtoBuf、Thrift 的模式演进之路&lt;/a> — 翻译的 Martin Kleppmann 的文章，重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>在 Spring Boot 中 Jackson 日期格式化技巧</title><link>https://www.diguage.com/post/jackson-date-format-on-spring-boot/</link><pubDate>Tue, 12 Apr 2022 16:04:23 +0800</pubDate><guid>https://www.diguage.com/post/jackson-date-format-on-spring-boot/</guid><description>&lt;div class="paragraph">
&lt;p>使用 Spring Boot 时，需要使用 Jackson 处理一些 Java Time API 类型的 JSON 序列化问题，在处理一些类的字段时，可以通过直接在属性上加注解的方式来指定其格式化样式。但是，昨天同事遇到一个格式化 &lt;code>Map&lt;/code> 数据的问题，这样就不能通过加注解来解决格式化样式的问题了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在网上各种搜索，各种尝试后，终于解决了这个问题，记录一下，以备不时之需。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>闲言少叙，直接上代码：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="kn">package&lt;/span> &lt;span class="nn">com.diguage.demo.config&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="kn">import&lt;/span> &lt;span class="nn">com.fasterxml.jackson.databind.ObjectMapper&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">com.fasterxml.jackson.databind.util.StdDateFormat&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">com.fasterxml.jackson.datatype.jsr310.JavaTimeModule&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.context.annotation.Bean&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.context.annotation.Configuration&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.context.annotation.Primary&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="kn">import&lt;/span> &lt;span class="nn">java.text.DateFormat&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">java.text.SimpleDateFormat&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">java.time.LocalDate&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">java.time.LocalDateTime&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="kn">import&lt;/span> &lt;span class="nn">static&lt;/span> &lt;span class="n">com&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">fasterxml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">jackson&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">databind&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">SerializationFeature&lt;/span>&lt;span class="o">.*;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">static&lt;/span> &lt;span class="n">java&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">format&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">DateTimeFormatter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">ofPattern&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="cm">/**
 * 配置类
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 */&lt;/span>
&lt;span class="nd">@Configuration&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">Config&lt;/span> &lt;span class="o">{&lt;/span>

 &lt;span class="cm">/**
 * 创建 ObjectMapper 对象，配置日期格式化
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 */&lt;/span>
 &lt;span class="nd">@Bean&lt;/span>
 &lt;span class="nd">@Primary&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="nc">ObjectMapper&lt;/span> &lt;span class="nf">objectMapper&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">ObjectMapper&lt;/span> &lt;span class="n">mapper&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">ObjectMapper&lt;/span>&lt;span class="o">();&lt;/span>

 &lt;span class="nc">String&lt;/span> &lt;span class="n">dateTimepattern&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;yyyy-MM-dd HH:mm:ss&amp;#34;&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">datePattern&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;yyyy-MM-dd&amp;#34;&lt;/span>&lt;span class="o">;&lt;/span>

 &lt;span class="nc">DateFormat&lt;/span> &lt;span class="n">dateFormat&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">SimpleDateFormat&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">dateTimepattern&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">mapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setDateFormat&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">dateFormat&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="n">mapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">configure&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="no">WRITE_DATES_AS_TIMESTAMPS&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">mapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setDateFormat&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nc">StdDateFormat&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">withColonInTimeZone&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="o">));&lt;/span>

 &lt;span class="nc">JavaTimeModule&lt;/span> &lt;span class="n">javaTimeModule&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">JavaTimeModule&lt;/span>&lt;span class="o">();&lt;/span>

 &lt;span class="n">javaTimeModule&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">addDeserializer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">LocalDate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">,&lt;/span>
 &lt;span class="k">new&lt;/span> &lt;span class="nf">LocalDateDeserializer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">ofPattern&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">datePattern&lt;/span>&lt;span class="o">)));&lt;/span>
 &lt;span class="n">javaTimeModule&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">addSerializer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">LocalDate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">,&lt;/span>
 &lt;span class="k">new&lt;/span> &lt;span class="nf">LocalDateSerializer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">ofPattern&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">datePattern&lt;/span>&lt;span class="o">)));&lt;/span>

 &lt;span class="n">javaTimeModule&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">addDeserializer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">LocalDateTime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">,&lt;/span>
 &lt;span class="k">new&lt;/span> &lt;span class="nf">LocalDateTimeDeserializer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">ofPattern&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">dateTimepattern&lt;/span>&lt;span class="o">)));&lt;/span>
 &lt;span class="n">javaTimeModule&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">addSerializer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">LocalDateTime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">,&lt;/span>
 &lt;span class="k">new&lt;/span> &lt;span class="nf">LocalDateTimeSerializer&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">ofPattern&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">dateTimepattern&lt;/span>&lt;span class="o">)));&lt;/span>

 &lt;span class="n">mapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">registerModule&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">javaTimeModule&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="k">return&lt;/span> &lt;span class="n">mapper&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_后续问题">后续问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>不知道通过这种方式指定日期格式化样式后，在处理一些打格式化样式注解的字段时，会有什么样的表现？有机会测试一下。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_参考资料">参考资料&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://www.baeldung.com/jackson-serialize-dates" target="_blank" rel="noopener">Jackson Date&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://stackoverflow.com/a/56777701/951836" target="_blank" rel="noopener">json - Java 8 LocalDate Jackson format&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div></description></item><item><title>使用 Maven Enforcer 插件检查依赖</title><link>https://www.diguage.com/post/use-maven-enforcer-plugin-to-check-dependencies/</link><pubDate>Tue, 29 Mar 2022 20:04:08 +0800</pubDate><guid>https://www.diguage.com/post/use-maven-enforcer-plugin-to-check-dependencies/</guid><description>&lt;div class="paragraph">
&lt;p>最近公司项目要对一些内部依赖做集中升级。为此，D瓜哥发布了一个 BOM（BOM stands for Bill Of Materials），用于规范项目依赖及版本。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>但是升级后，效果不理想，检查发现还是有不少依赖的版本依然不符合要求。经同事提醒，可以使用 &lt;a href="https://maven.apache.org/enforcer/index.html#" target="_blank" rel="noopener">Apache Maven Enforcer&lt;/a> 来做规范检查，测试一下效果确实不错。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>将 Apache Maven Enforcer 和 &lt;a href="https://www.mojohaus.org/extra-enforcer-rules/" target="_blank" rel="noopener">Extra Enforcer Rules&lt;/a> 的文档大致巴拉了一遍之后，根据项目的实际情况，挑选出来可用规则如下：&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_比较有用的几个规则">比较有用的几个规则&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/enforcer/enforcer-rules/bannedDependencies.html" target="_blank" rel="noopener">bannedDependencies&lt;/a> – 排除不需要的依赖，引入需要的依赖。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/enforcer/enforcer-rules/banDuplicatePomDependencyVersions.html" target="_blank" rel="noopener">banDuplicatePomDependencyVersions&lt;/a> – 防止依赖重复声明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/enforcer/enforcer-rules/dependencyConvergence.html" target="_blank" rel="noopener">dependencyConvergence&lt;/a> – 确保所有依赖收敛到相同的版本。也可以考虑加入。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/enforcer/enforcer-rules/reactorModuleConvergence.html" target="_blank" rel="noopener">reactorModuleConvergence&lt;/a> – 多模块开发时，确保父子模块的版本是一致的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/enforcer/enforcer-rules/requireJavaVersion.html" target="_blank" rel="noopener">requireJavaVersion&lt;/a> – 检查 JDK 的版本&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/enforcer/enforcer-rules/requireMavenVersion.html" target="_blank" rel="noopener">requireMavenVersion&lt;/a> – 检查 Maven 的版本&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/enforcer/enforcer-rules/requireReleaseVersion.html" target="_blank" rel="noopener">requireReleaseVersion&lt;/a> – 这个可以通过激活生产环境的 profile 来启用该规则，保证发布的不是快照版。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://maven.apache.org/enforcer/enforcer-rules/requireUpperBoundDeps.html" target="_blank" rel="noopener">requireUpperBoundDeps&lt;/a> – 确保直接引用的依赖不比间接解析出来的依赖版本低。感觉这个也挺有用，但是使用方式还没搞清楚。实例有些模糊。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.mojohaus.org/extra-enforcer-rules/banDuplicateClasses.html" target="_blank" rel="noopener">banDuplicateClasses&lt;/a> – 检查重复类定义。可以避免一些特殊情况。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.mojohaus.org/extra-enforcer-rules/requirePropertyDiverges.html" target="_blank" rel="noopener">requirePropertyDiverges&lt;/a> – 确保项目定义的属性与依赖中包含的属性不重复。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.mojohaus.org/extra-enforcer-rules/enforceBytecodeVersion.html" target="_blank" rel="noopener">enforceBytecodeVersion&lt;/a> – 确保使用的字节码版本不高于指定版本。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.mojohaus.org/extra-enforcer-rules/banCircularDependencies.html" target="_blank" rel="noopener">banCircularDependencies&lt;/a> – 确保没有循环依赖。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.mojohaus.org/extra-enforcer-rules/requireEncoding.html" target="_blank" rel="noopener">requireEncoding&lt;/a> – 指定项目字符集。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_实践总结">实践总结&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>D瓜哥把上面的规则几乎全部试用了一遍，把发现的一些需要特别注意的地方标注记录一下吧：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;code>banDuplicateClasses&lt;/code> — 这个插件还是很棒的。使用的时候，成功检查出废弃不用的依赖（废弃依赖被收入到另外一个依赖中了。）。不过，也发现一些问题，项目中使用了 &lt;code>netty-all&lt;/code> 及 Netty 的其他模块依赖。但是，并没有检查出来，感觉是项目代码有直接依赖的重复类才会被检测出来。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>requireUpperBoundDeps&lt;/code> — 开启这个检查时，发现间接引用了 &lt;code>commons-lang:commons-lang:2.6&lt;/code>，但是项目直接声明的依赖是 &lt;code>commons-lang:commons-lang:2.5&lt;/code>，就直接报错了。私以为这个检查规则还是很赞的。但是，因为我们的项目中有有依赖 Gson 1.X，也有 Gson 2.X 的，而且这两个版本在处理父子类有相同字段时的存在抛异常的差异，所以无法启用，实在可惜。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>reactorModuleConvergence&lt;/code> –- 多模块开发时，确保父子模块的版本是一致的。这个规则还是很赞的。但是，因为我测试的模块不存在这个问题，所以，没有触发报警。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>requirePropertyDiverges&lt;/code> — 本想启用这个规则，看了一下配置，着实麻烦，而且不是全局检查，似乎是检查指定配置项，感觉不是很满意。没有启用。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>enforceBytecodeVersion&lt;/code> — 检查字节码版本。这个是不超过上限，我是想检查下限，所以没有启用。反思：在写这个文章时，又思考了一下，检查下限是有问题的，一些陈旧的依赖就不能使用了。但是这些依赖是没有问题的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>banCircularDependencies&lt;/code> — 这个规则似乎 Maven 已经内置了，以前遇到过这样的场景，Maven 直接报错了。所以，就没有启用这个规则。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>requireEncoding&lt;/code> — 这个规则非常棒。在试用过程中发现，它会把存 ASCII 字符的 &lt;code>UTF-8&lt;/code> 文件判定为 &lt;code>US-ASCII&lt;/code> 编码。没有找到好的办法来解决这个问题。所以，可惜没有启用。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>《投资中最简单的事》之读书笔记（四）：投资心理学</title><link>https://www.diguage.com/post/the-simple-things-in-investment-4/</link><pubDate>Sat, 26 Feb 2022 09:24:35 +0800</pubDate><guid>https://www.diguage.com/post/the-simple-things-in-investment-4/</guid><description>&lt;div class="paragraph">
&lt;p>最近一位同事离职，在践行宴会上，大家聊到投资理财，D瓜哥推荐了 &lt;a href="https://book.douban.com/subject/35000951/" target="_blank" rel="noopener">《投资中最简单的事》&lt;/a>。去年已经把这本书读完了，下班后把这本书的摘要笔记整理一下，按照章节，分四篇来分享：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-1/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（一）：投资理念&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-2/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（二）：投资方法&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-3/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（三）：投资风险&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-4/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（四）：投资心理学&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第四部分投资心理学">第四部分　投资心理学&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_11人性的弱点投资者常见的心理误区">11　人性的弱点：投资者常见的心理误区&lt;/h3>
&lt;div class="paragraph">
&lt;p>一个股票便宜与否，看估值比看近期涨跌更可靠：基本面大幅超预期时会越涨越便宜，反之会越跌越贵。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>美国消费股多，适合价值投资；加拿大资源股多，适合趋势投资。互联网赢家通吃，买龙头；休闲服装百花齐放，买成长。差异化产品，买品牌；同质化产品，买成本低的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>俾斯麦说，每个笨蛋都会从自己的教训中吸取经验，聪明人则会从别人的经验中获益。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>同样是错过航班，错过 3分钟的比错过 30分钟的更沮丧；同样是彩票没中，号码与头奖号码只差一点的是最痛苦的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>与成功擦肩而过比从未接近成功更令人难以接受，更令人想再试一次，所以，许多赌博形式正是包含大量“差点就赢”的设计，才吸引了这么多赌徒不惜倾家荡产屡败屡战。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_12后视镜">12　后视镜&lt;/h3>
&lt;div class="paragraph">
&lt;p>应该跑向球将要去的地方，而不是球现在所在的地方。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_13傻瓜定价说">13　傻瓜定价说&lt;/h3>
&lt;div class="paragraph">
&lt;p>对市场敬畏但不屈服，逆市场而动但懂得自我保护。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_14这次不同了">14　这次不同了&lt;/h3>
&lt;div class="paragraph">
&lt;p>人们从历史中学到的唯一教训就是人们从来不吸取任何教训。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>每一次危机或泡沫都感觉像是史无前例，其实不过是历史长河中的一朵浪花。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当人们在为“黄金十年”找论据时，市场离见顶就不远了；当人们在为长期的悲观寻找理由时，市场已经在底部区域了。投资者的悲哀，永远是轻易地放弃和错误地坚持。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_15树动风动心动">15　树动风动心动&lt;/h3>
&lt;div class="paragraph">
&lt;p>短期来说，股价波动是人心与人心博弈的结果，是心在动，难以预测。中期来说，股价更多是由政策面决定：吹的是政策宽松的暖风，股价就上涨；刮的是政策紧缩的寒风，股价就下跌。所以说，中期是风在动。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>长期来说，股价是由基本面决定的。那些根基不稳的病树，难以避免在风中被连根拔起的命运；而那些有稳固根基的好树，不管人心冷暖，风向东西，终将成长为参天大树。所以说，长期是树在动。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“心在动”常对“风在动”推波助澜，把五级风放大为十级，此时人们已不关心“树动”与否，反正玩的是击鼓传花的游戏。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_附录投资访谈顺应规律投资才能事半功倍">附录　投资访谈：顺应规律投资，才能事半功倍&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>过去 10年，人家问我看好什么行业，我在任何时候都是讲三大类行业：金融地产、品牌消费、先进制造。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>有“霸王条款”的行业都不会太差。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>便宜是硬道理，股票的回报并不取决于它未来的增长快还是慢，而是取决于未来增长比当前股价反映的增长预期更快还是更慢。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我们找白酒的顺序是先找高端，再找次高端，再到平价酒，因为中国经济的复苏通常是先重点扶持基建，商务应酬增加，对高端酒的需求会增加，之后经济恢复，老百姓有钱了，中端酒开始起来，然后再到低端酒。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>市场是一个拍卖系统，最后的顶点总是最疯狂的人在定价，就像在拍卖会上面，最后的买主肯定是那个最狂热的、愿意出最高价的人。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一个投资人说过，当一家公司的创始人和管理者，主要是做好两点：一个是算命，即想清楚一些大的战略性的问题；第二是相面，就是选人。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_后记">后记&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>大佬为什么对科技股长期持谨慎的态度？为什么热衷科技股的基金经理鲜有长时间业绩突出者？&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>第一，科技进步内在的突变性决定了科技股投资人业绩内在的不可延续性。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第二，科学技术是不断变化的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第三，虽然新技术的前景广阔，初期的行业格局却非常散乱，绝大多数投资人很难在事前判断出谁是最终赢家。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>临渊羡鱼不如退而结网，投资必须把握你能把握的东西。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>知道自己的能力边界，发挥自己的优势，买便宜的好公司，注意安全边际，注重定价权，人弃我取，在胜负已分的行业里找赢家，这些貌似投资中最简单的事，其实也是投资中最本质的东西。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div></description></item><item><title>《投资中最简单的事》之读书笔记（三）：投资风险</title><link>https://www.diguage.com/post/the-simple-things-in-investment-3/</link><pubDate>Sat, 26 Feb 2022 08:38:00 +0800</pubDate><guid>https://www.diguage.com/post/the-simple-things-in-investment-3/</guid><description>&lt;div class="paragraph">
&lt;p>最近一位同事离职，在践行宴会上，大家聊到投资理财，D瓜哥推荐了 &lt;a href="https://book.douban.com/subject/35000951/" target="_blank" rel="noopener">《投资中最简单的事》&lt;/a>。去年已经把这本书读完了，下班后把这本书的摘要笔记整理一下，按照章节，分四篇来分享：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-1/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（一）：投资理念&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-2/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（二）：投资方法&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-3/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（三）：投资风险&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-4/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（四）：投资心理学&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第三部分投资风险">第三部分　投资风险&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_07价值陷阱与成长陷阱">07　价值陷阱与成长陷阱&lt;/h3>
&lt;div class="sect3">
&lt;h4 id="_价值陷阱">价值陷阱&lt;/h4>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>第一类是被技术进步淘汰的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第二类是赢家通吃行业里的小公司。&lt;/p>
&lt;div class="paragraph">
&lt;p>集中度越来越高说明行业里的龙头企业比其他企业增长更快，不然就是越来越分散。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>第三类是分散的、重资产的夕阳行业。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第四类是景气顶点的周期股。&lt;/p>
&lt;div class="openblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>对周期股价值的评估可以借助两个概念，一个是常态化的盈利（ Normalized Earnings），计算剔除了经济周期波动后的企业盈利；另一个是盈利能力（ Earnings Power），而不是某一时间的盈利数额，评估企业内在的可持续的盈利水平。&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>怎么操作?&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>第五类是有会计欺诈的公司。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>最后一类是人不行、捧着“金饭碗”讨饭的公司。“&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>金玉之堂，莫之能守&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_成长陷阱">成长陷阱&lt;/h4>
&lt;div class="paragraph">
&lt;p>成长陷阱比价值陷阱更常见。&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>估值过高&lt;/p>
&lt;/li>
&lt;li>
&lt;p>技术路径踏空&lt;/p>
&lt;/li>
&lt;li>
&lt;p>无利润增长&lt;/p>
&lt;/li>
&lt;li>
&lt;p>成长性破产&lt;/p>
&lt;/li>
&lt;li>
&lt;p>盲目多元化&lt;/p>
&lt;/li>
&lt;li>
&lt;p>树大招风&lt;/p>
&lt;div class="paragraph">
&lt;p>追随者挑战巨头的时候要小心，不要太早显露目的，以免遭到巨头的封杀和围剿。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>新产品风险&lt;/p>
&lt;/li>
&lt;li>
&lt;p>寄生式增长&lt;/p>
&lt;/li>
&lt;li>
&lt;p>强弩之末&lt;/p>
&lt;/li>
&lt;li>
&lt;p>会计造假&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>各类价值陷阱的共性是利润的不可持续性，各类成长陷阱的共性是成长的不可持续性。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>成长本身并不是陷阱，但人性的弱点中对未来成长习惯性地过高预期和过高估值却是不折不扣的陷阱。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>管子说：“不为不可成，不求不可得，不处不可久，不行不可复。”&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_黄金坑还是融化中的冰棒">“黄金坑”还是“融化中的冰棒”&lt;/h4>
&lt;div class="paragraph">
&lt;p>这有点像一根正在融化的冰棒，你拿在手中它就不断地融化。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>价值陷阱大部分的问题主要出现在内在逻辑被破坏，这样整个公司的护城河、核心竞争力逐步丧失，整个公司要么被时代淘汰，要么被对手打败，这种大多数是价值陷阱。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我们需要理解什么是永久性的下跌，什么是暂时性的调整。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果找到了每一个公司便宜的原因，就能判断相应的股票是价值陷阱还是“黄金坑”。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>趋势投资是可以不断止损的，每一次错误最大的损失是有限的。所以，从这个意义上看，价值投资的风险比趋势投资更大。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_08真假风险与安全边际">08　真假风险与安全边际&lt;/h3>
&lt;div class="paragraph">
&lt;p>股市如围城，城内的人在往外逃，城外的人在往里冲。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在互联网时代，品牌的优势比渠道的优势更重要。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>股票暴涨后，真实的风险上升，感受到的风险却在下降，在 6 000点股市最危险的时候大家感受到的都是歌舞升平；股票暴跌后，真实的风险下降，感受到的风险上升，在 2 000点股市相对低谷时人们感受到的却都是凄风苦雨。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>黑色星期一之后暴露的风险很大，但是隐藏的风险不大；感受到的风险很大，但是真实的风险不大。能区分并利用这两种风险的不同，是成功投资的必要条件。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于逆向投资者来说，最痛的时候，往往是最不该放手的时候。正如索罗斯所说，如果你承受不了失败的痛苦，就不要入市，因为没有人能够百战百胜。&lt;/p>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_安全边际">安全边际&lt;/h4>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>东方不亮西方亮，给点阳光就灿烂&lt;/p>
&lt;/li>
&lt;li>
&lt;p>估值低到足以反映大多数可能的坏情况&lt;/p>
&lt;/li>
&lt;li>
&lt;p>有“冗余设计”，有“备用系统”来限制下跌空间&lt;/p>
&lt;/li>
&lt;li>
&lt;p>价值易估，不具反身性，可越跌越买&lt;/p>
&lt;div class="openblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>索罗斯所说的反身性是指股价下跌本身对公司基本面有负面作用，易形成自我强化的恶性循环。&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>贝尔斯登和可口可乐对比!多思考!!!&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>鼻莫如大，目莫如小&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_止损">止损&lt;/h4>
&lt;div class="paragraph">
&lt;p>对于趋势投资者而言，止损不止赢是短线交易的第一法则，自不必多说。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>忘掉你的成本，是成功投资的第一步。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>不止损是有很严格的前提条件的：必须避开各种价值陷阱；所买的股票有足够安全边际；所承担的只是价格波动的风险而非本金永久性丧失的风险。&lt;/p>
&lt;/div></description></item><item><title>《投资中最简单的事》之读书笔记（二）：投资方法</title><link>https://www.diguage.com/post/the-simple-things-in-investment-2/</link><pubDate>Fri, 25 Feb 2022 22:34:00 +0800</pubDate><guid>https://www.diguage.com/post/the-simple-things-in-investment-2/</guid><description>&lt;div class="paragraph">
&lt;p>最近一位同事离职，在践行宴会上，大家聊到投资理财，D瓜哥推荐了 &lt;a href="https://book.douban.com/subject/35000951/" target="_blank" rel="noopener">《投资中最简单的事》&lt;/a>。去年已经把这本书读完了，下班后把这本书的摘要笔记整理一下，按照章节，分四篇来分享：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-1/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（一）：投资理念&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-2/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（二）：投资方法&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-3/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（三）：投资风险&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-4/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（四）：投资心理学&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第二部分投资方法">第二部分　投资方法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_04投资的三个基本问题">04　投资的三个基本问题&lt;/h3>
&lt;div class="paragraph">
&lt;p>如果把我过去十几年的投资分析方法做一个简单的概括，最根本的就是要回答三个问题：为什么认为一家公司便宜，为什么认为一家公司好，以及为什么要现在买。这三个问题中，第一个是估值的问题，第二个是公司品质的问题，第三个是买卖时机的问题。&lt;/p>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_问题1估值">问题1：估值&lt;/h4>
&lt;div class="paragraph">
&lt;p>世界上不存在每年都有效的投资方法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在好行业中挑选好公司，然后等待好价格出现时买入。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>三板斧：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>波特五力分析。不要孤立地看待一只股票，而要把一个公司放到行业的上下游产业链和行业竞争格局的大背景中分析，重点搞清三个问题：公司对上下游的议价权、与竞争对手的比较优势、行业对潜在进入者的门槛。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>杜邦分析。弄清公司过去 5年究竟是靠什么模式赚钱的（高利润、高周转还是高杠杆），然后看公司战略规划、团队背景和管理执行力等是否与其商业模式一致。例如，高利润模式的看其广告投入、研发投入、产品定位、差异化营销是否合理有效，高周转模式的看其运营管理能力、渠道管控能力、成本控制能力等是否具备，高杠杆模式的看其风险控制能力、融资成本高低等。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>估值分析。通过同业横比和历史纵比，加上市值与未来成长空间比，在显著低估时买入。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这“三板斧”分别解决的是好行业、好公司和好价格的问题，挑出来的“三好学生”就是值得长期持有的好股票了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>不要被上市公司讲的“美好未来”忽悠了，一定要看现在看得见摸得着的利润、现金流和资产。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_问题2品质">问题2：品质&lt;/h4>
&lt;div class="paragraph">
&lt;p>便宜不便宜大多数人都能够判断，因此关键的区别在于搞清楚公司的品质。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>三个要素中，投资者真正需要下大力气搞清楚的就是品质。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>马和骑师，选马比选骑师重要。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我认为选一个好行业是成功投资的基本条件。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>公司的品质好坏，关键是看能不能具有定价权。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>你对你的竞争对手怎么看？最近竞争对手有没有什么做法让你特别难受？你最近有没有什么做法让你的竞争对手感到难受？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我为什么不太喜欢政府支持的新兴行业？因为政府的支持其实是增加了供应，增加了行业的竞争对手。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>如何判断公司的品质？&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>是不是一个好行业？首先是看行业的格局，不要有太多人做，做的人多了，竞争自然就激烈了。&lt;/p>
&lt;div class="paragraph">
&lt;p>扶持其实是鼓励更多的人进入行业而加剧了竞争，打压却限制了新增供给，反而改善了竞争格局并且让行业龙头做大。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>第二个要注意的是差异化竞争和同质化竞争的区别。&lt;/p>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>差异化的第一个标志是品牌。差异化、定价权的一个来源是品牌，而且最好是请客送礼的品牌。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>差异化的第二个标志是有回头客，即用户黏度高。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>差异化的第三个标志是单价不要太高。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>差异化的第四个标志是转换成本。&lt;/p>
&lt;div class="paragraph">
&lt;p>差异化的关键在于能不能提价，提价之后是不是影响销售。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>差异化的第五个标志是服务网络。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>差异化的第六个标志是先发优势。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对行业格局的分析和对公司核心竞争力的理解是“道”，财务分析只是“术”，但是后者对前者起着验证和把关的作用。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>判断一个公司所在的行业好不好，首先是看行业竞争格局是不是清晰，领先者有没有品牌的美誉度，领先者产品的售价是不是显著高于其他竞争者，领先者有没有网络服务的优势，有没有规模效应，产品的销售半径是不是相对比较小（不用参与全球竞争），是不是有回头客，是不是低单价（下游对价格不敏感），是不是转换成本高，领先者是不是有先发优势，技术变化是不是没有那么快。对品质的评判有很多指标，核心是“这是不是一门好生意，有没有定价权，是不是一门容易赚钱的生意”。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我一直强调胜而后求战，愿意买已经把竞争对手打趴下的公司，而不是战而后求胜，在百舸争流中猜赢家。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我认为行业研究员能够给基金经理提供的附加值在于讲出这个行业的特点。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我分析公司、分析行业，更看重的是行业的内在特质和公司的长期经济特征这一类静态的信息，这些是规律性的东西，只有对行业真正理解了才能够说得出来。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一个成功的投资者应该能够把行业到底竞争的是什么说清楚，把这个行业是得什么东西得天下弄明白。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在不同的细分子行业中竞争的东西也是不一样的，投资之前必须把每个细分子行业中决定胜负的因素研究清楚。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>即使是同一个行业，投资者也得明白在不同阶段，到底什么因素是决定胜负的关键。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要认识一个行业，不妨做一道填空题：得___者得天下，用一个词来概括这个行业竞争的是什么。例如，基金业是得人才者得天下，高端消费品是得品牌者得天下，低端消费品是得渠道者得天下，无差异中间品是得成本者得天下，制造业是得规模者得天下，大宗品是得资源者得天下。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_问题3时机">问题3：时机&lt;/h4>
&lt;div class="paragraph">
&lt;p>投资的第三个大问题是时机。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>投资是科学加艺术，在投资的三个基本问题中，估值是最接近科学的，有一整套的方法和规律可以学习；而选时是最接近艺术的，只可悟、不可学，只可意会、不可言传。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于多数人而言，对待选时的正确态度应该是避免把大量的时间花在试图“抄底”或者“逃顶”上。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>彼得·林奇说的“如果你每年花 10分钟在宏观分析上，你就浪费了 10分钟”&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>如何在合适的时候加仓？&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>第一种办法是看估值。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第二种办法，是根据各种指标之间的领先和滞后关系进行分析（具体请参读 &lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-3/#chapter-10" target="_blank" rel="noopener">本书第 10章：四种周期、三种杠杆，行业轮动时机的把握&lt;/a>）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第三种办法，是根据对市场情绪的把握和逆向思考进行分析。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“表弟指标”&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于多数人而言，只要把估值掌握好，把基本面分析好，淡化选时，长期来看投资回报就不会差。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>历史上的股市见底信号 1．市场估值在历史低位； 2. M1见底回升； 3．降存准或降息； 4．成交量极度萎缩； 5．社保汇金入市； 6．大股东和高管增持； 7．机构大幅超配非周期类股票； 8．强周期股在跌时抗跌，涨时领涨； 9．机构仓位在历史低点； 10．新股停发或降印花税。&lt;/p>
&lt;/div></description></item><item><title>《投资中最简单的事》之读书笔记（一）：投资理念</title><link>https://www.diguage.com/post/the-simple-things-in-investment-1/</link><pubDate>Thu, 24 Feb 2022 23:38:00 +0800</pubDate><guid>https://www.diguage.com/post/the-simple-things-in-investment-1/</guid><description>&lt;div class="paragraph">
&lt;p>最近一位同事离职，在践行宴会上，大家聊到投资理财，D瓜哥推荐了 &lt;a href="https://book.douban.com/subject/35000951/" target="_blank" rel="noopener">《投资中最简单的事》&lt;/a>。去年已经把这本书读完了，下班后把这本书的摘要笔记整理一下，按照章节，分四篇来分享：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-1/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（一）：投资理念&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-2/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（二）：投资方法&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-3/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（三）：投资风险&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/the-simple-things-in-investment-4/" target="_blank" rel="noopener">《投资中最简单的事》之读书笔记（四）：投资心理学&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_推荐序">推荐序&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>天下武林，林林总总。名门正宗如少林武当，诚然名扬天下，而武林之大，但凡修得暗镖神剑者，亦可独步江湖。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>即使最成功的投资人，也要心胸坦荡，认识到自我局限，不可以名门正宗自居，须认识到获得真理是一个学无止境、永远追求的过程。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在多数人都醉心于“即时满足”（ instant gratification）的世界里，懂得用“延迟满足”（ delayed gratification）去做交易的人，已先胜一筹了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>健康的环境和心情是长期修行的结果。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_初版序">初版序&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>成长投资的价值支撑主要来自企业未来收入和利润的高增长，更强调公司未来的价值 V&lt;sub>n&lt;/sub> 要远大于公司现在的价值 V&lt;sub>0&lt;/sub>，但这种投资需要预测未来的远见，只有极少数的人能够做到。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我努力奉行一些简单的原则，例如：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>第一，便宜是硬道理。&lt;/strong>即使是普通公司，只要足够便宜，也会有丰厚的回报。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>第二，定价权是核心竞争力。&lt;/strong>有核心竞争力的公司有两个标准：一是做的是自己可以不断复制的事情，比如麦当劳和星巴克可以不断地跨区域开新店，在全世界成功复制；二是做的是别人不可能复制的事情，具备独占资源、品牌美誉度、专利、技术、寡头垄断地位、牌照准入限制等特征，最终体现为企业的定价权。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>第三，胜而后求战，不要战而后求胜。&lt;/strong>百舸争流的行业，增长再快也很难找投资标的，不妨等待行业“内战”结束、赢家产生后再做投资。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>第四，人弃我取，逆向投资。&lt;/strong>无论是巴菲特、索罗斯，还是邓普顿、卡尔·伊坎，投资领域的集大成者大多数都具有超强的逆向思维能力。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这些规律性的东西虽然看起来是投资中最简单的事，却也是投资中最本质的东西，并不会因为时空的不同而产生大的差异。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第一部分投资理念">第一部分　投资理念&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_01以实业的眼光做投资">01　以实业的眼光做投资&lt;/h3>
&lt;div class="paragraph">
&lt;p>下跌后，悲痛欲绝；上涨后，欢欣雀跃。两者均无必要。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>以买企业的心态做投资，不因急跌而失措，也不因急涨而忘形。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在一个先发优势不断被颠覆、没有永远的赢家的行业里，只有勇于自我否定、因时而变才能生存。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>成长是未来的，难预测；门槛是既成的，易把握。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第一，把客户的钱当作自己的钱来珍惜；第二，把二级市场的股票投资当作一级市场的实业投资来分析。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果用自己的钱做实业投资，首先要考虑的问题就是：这是不是一门好生意。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>人们只看到成功的“一将”，选择性地忽视了“枯了的万骨”。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>什么行业易出长期牛股？行业集中度持续提高的行业。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>用自己的钱做实业投资要考虑的第二个问题，就是这门生意的现金流状况如何，毕竟做生意的最终目的是赚取现金流。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>好公司的两个标准：一是它做的事情别人做不了；二是它做的事情自己可以重复做。前者是门槛，决定利润率的高低和趋势；后者是成长的可复制性，决定销售增速。如果二者不可兼得，宁要有门槛的低增长（可持续），也不要没门槛的高增长（不可持续）。门槛是现有的，好把握；成长是将来的，难预测。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>用自己的钱做实业投资要考虑的第三个问题就是行业的竞争格局以及公司是否具有比较优势。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>作为一个后来者，想颠覆既有的龙头老大的地位，就得看自己能够为客户提供哪些不可比拟的价值，以及相对于竞争对手的比较竞争优势在哪里。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>互联网的本质是“人生人”，优势在于能以极低成本服务无数客户，规模效应体现在“人多”，“二八”现象不明显，是典型的散户经济，得散户者得天下。银行业的本质是“钱生钱”，规模效应体现在“钱多”， 80%的业务来自 20%的客户，“二八”现象显著，得大户者得天下，而且那 20%的大客户是需要线下的高端服务的，这就是网上银行至今在欧美日韩都没有很成功的案例的重要原因。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>互联网“人生人”主要靠两条：一是网络效应；二靠人多提升用户体验。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>中国线下零售的分散和低效是中国线上电商迅速崛起的重要原因。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>分析技术变化快的行业时不必看市场占有率，而要看是否适应最新的杀手级应用的潮流。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>做投资要研究的就是这些不以人的意志为转移的规律，而不是整天去猜测市场的情绪变化。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>宝石被人扔进泥里再踩上几只脚也仍是宝石，砖头被请进庙堂受人膜拜也仍是砖头。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>市场的主题炒作往往是短期的，长期起决定性作用的仍然是行业竞争格局、公司竞争优势和定价权。只有坚持以实业的眼光去评估企业的价值，不受市场的极端情绪波动所影响，时间才会是我们的朋友。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_02人弃我取逆向投资的关键">02　人弃我取，逆向投资的关键&lt;/h3>
&lt;div class="paragraph">
&lt;p>众人夺路而逃时，不挡路、不跟随。不挡路是因为不想被踩死，不跟随是因为乌合之众往往跑错方向。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>新兴行业讲的是需求快速成长的事，不必纠结于供给。而传统行业则只有控制供给，企业利润才能快速增长。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>长期看表现好的传统行业要么是寡头行业，要么是淘汰落后产能的行业，二者的供给增长相对于需求而言都受到了有效控制。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>很多人认为小股票的成长性普遍好于大股票。如果这是事实的话，那么大多数行业的集中度就会越来越低。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>以高估值买新兴行业而落入成长陷阱的是沉迷于“未得到”，以低估值买夕阳行业而落入价值陷阱的是沉迷于“已失去”。投资中风险收益比最高的还是那些容易被低估的“正拥有”。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>人弃我取、逆向投资是超额收益的重要来源。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一只下跌的股票是否值得逆向投资的关键在于以下三点：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>首先，看估值是否够低、是否已经过度反映了可能的坏消息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>其次，看遭遇的问题是否是短期问题、是否是可解决的问题。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>最后，看股价暴跌本身是否会导致公司的基本面进一步恶化，即是否有索罗斯所说的反身性。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>买股票之前问问自己，下跌后敢加仓吗？如果不敢，最好一开始就别买，因为价格的波动是不可避免的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>食品饮料是个适合逆向投资的领域。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>大家都挤在树上摘葡萄时，也许就是该在地上捡苹果的时候了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>A股的情绪波动容易走极端，因此“人多的地方不去”是至理名言。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>牢记管子所说的“不处不可久，不行不可复”，不去“击鼓传花”，不接最后一棒，把选股范围基本限制在低估值的大盘蓝筹里，以此躲过中小盘中的许多“地雷”。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一般说来，趋势的初期和末期，就是真理在少数人手里的时候。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>买早了还得熬得住，这是逆向投资者的必备素质。&lt;/p>
&lt;/div></description></item><item><title>文档技术方案选型：AsciiDoc vs Markdown</title><link>https://www.diguage.com/post/asciidoc-vs-markdown/</link><pubDate>Sun, 13 Feb 2022 19:56:09 +0800</pubDate><guid>https://www.diguage.com/post/asciidoc-vs-markdown/</guid><description>&lt;div class="paragraph">
&lt;p>D瓜哥在前面的文章 &lt;a href="https://www.diguage.com/post/building-blog-with-hugo/" target="_blank" rel="noopener">使用 Hugo 搭建博客&lt;/a> 中介绍了如何用 Hugo 搭建个人博客。部门准备系统地整理一下各个小组的文档。恰好 D瓜哥 对写文档非常感兴趣，正好写个材料分享一下血泪经验。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_编辑进化之路">编辑进化之路&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_第一代wordpress">第一代：WordPress&lt;/h3>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/website/wordpress.png" alt="wordpress" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>缺点：写作和排版割裂，排版耗时且繁琐&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_第二代markdown">第二代：MarkDown&lt;/h3>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/website/markdown.png" alt="markdown" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>缺点：方言众多，工具链不够完整。&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>现在已经改观很多。&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_第三代asciidoc">第三代：AsciiDoc&lt;/h3>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/website/asciidoc.png" alt="asciidoc" width="95%"/>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_轻量级标记语言的优点">轻量级标记语言的优点&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_思路与格式融为一体">思路与格式融为一体&lt;/h3>
&lt;div class="paragraph">
&lt;p>在整理文档时，随手加入格式管理，不需要为格式分心，也无须浪费时间调整排版。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_代码高亮">代码高亮&lt;/h3>
&lt;div class="paragraph">
&lt;p>AsciiDoc 与 MarkDown 都支持&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="cm">/**
 * @author D瓜哥 · &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">Main&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">String&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;Hello, D瓜哥！&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_文本格式">文本格式&lt;/h3>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>文本格式，天然跨平台，支持性好，方便编辑与管理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>结合 Git，支持版本管理。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_生态完善">生态完善&lt;/h3>
&lt;div class="sect3">
&lt;h4 id="_markdown">Markdown&lt;/h4>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo: The world’s fastest framework for building websites&lt;/a> — Hugo 使用 &lt;a href="https://github.com/yuin/goldmark" target="_blank" rel="noopener">yuin/goldmark: A markdown parser written in Go.&lt;/a> 来做转换工作。也支持 AsciiDoc，不过需要挑选比较合适的主题： &lt;a href="https://themes.gohugo.io/" target="_blank" rel="noopener">Hugo Themes&lt;/a>。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_asciidoc">AsciiDoc&lt;/h4>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;a href="https://antora.org/" target="_blank" rel="noopener">Antora&lt;/a> — The multi-repository documentation site generator for tech writers who writing in AsciiDoc.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://docs.asciidoctor.org/diagram-extension/latest/" target="_blank" rel="noopener">Asciidoctor Diagram&lt;/a> — 支持多种文本画图工具。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://docs.asciidoctor.org/epub3-converter/latest/" target="_blank" rel="noopener">Asciidoctor EPUB3 Documentation&lt;/a> — 可以直接将 AsciiDoc 文档转化成 EPUB 电子书。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://asciidoctor.org/docs/asciidoctor-pdf/" target="_blank" rel="noopener">Asciidoctor PDF&lt;/a> — 可以直接将 AsciiDoc 文档转化成 PDF 文档。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>常见插件的支持：Maven、IntelliJ IDEA、VS Code 等。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>优化网站</title><link>https://www.diguage.com/post/optimize-websites/</link><pubDate>Mon, 07 Feb 2022 11:44:17 +0800</pubDate><guid>https://www.diguage.com/post/optimize-websites/</guid><description>&lt;div class="paragraph">
&lt;p>前几年应大势所趋，使用 Let’s Encrypt 给所有网站都上了 HTTPS。因为去年年中把博客托管到 GitHub 上了，导致一起申请 HTTPS 证书的站点无法按时更新证书。所以，所有证书都过期了。前几天有朋友发消息问我，Byte Buddy 的中文文档是不是我搞的？正好借机把证书更新了一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>此后不久，无意间查看了一下网站服务器的操作系统和 Nginx 版本，发现竟然是 Ubuntu 16.04 + Nginx 1.12。Ubuntu 16.04 都”过期“了，正好得空升级一下。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_升级操作系统">升级操作系统&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>以前没有升级过操作系统大版本，正好借此机会练手：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="bash">&lt;span class="c"># 升级操作系统版本执行，先做一下常规升级&lt;/span>
&lt;span class="nb">sudo &lt;/span>apt-get update
&lt;span class="nb">sudo &lt;/span>apt-get upgrade
&lt;span class="nb">sudo &lt;/span>reboot

&lt;span class="c"># 检查可以升级的版本&lt;/span>
&lt;span class="nb">sudo &lt;/span>&lt;span class="k">do&lt;/span>&lt;span class="nt">-release-upgrade&lt;/span> &lt;span class="nt">-c&lt;/span>

&lt;span class="c"># 开始升级&lt;/span>
&lt;span class="nb">sudo &lt;/span>&lt;span class="k">do&lt;/span>&lt;span class="nt">-release-upgrade&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>升级完成后，检查操作系统版本：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="bash">&lt;span class="nv">$ &lt;/span>&lt;span class="nb">cat&lt;/span> /etc/os-release
&lt;span class="nv">NAME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Ubuntu&amp;#34;&lt;/span>
&lt;span class="nv">VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;20.04.3 LTS (Focal Fossa)&amp;#34;&lt;/span>
&lt;span class="nv">ID&lt;/span>&lt;span class="o">=&lt;/span>ubuntu
&lt;span class="nv">ID_LIKE&lt;/span>&lt;span class="o">=&lt;/span>debian
&lt;span class="nv">PRETTY_NAME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Ubuntu 20.04.3 LTS&amp;#34;&lt;/span>
&lt;span class="nv">VERSION_ID&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;20.04&amp;#34;&lt;/span>
&lt;span class="nv">HOME_URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://www.ubuntu.com/&amp;#34;&lt;/span>
&lt;span class="nv">SUPPORT_URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://help.ubuntu.com/&amp;#34;&lt;/span>
&lt;span class="nv">BUG_REPORT_URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://bugs.launchpad.net/ubuntu/&amp;#34;&lt;/span>
&lt;span class="nv">PRIVACY_POLICY_URL&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy&amp;#34;&lt;/span>
&lt;span class="nv">VERSION_CODENAME&lt;/span>&lt;span class="o">=&lt;/span>focal
&lt;span class="nv">UBUNTU_CODENAME&lt;/span>&lt;span class="o">=&lt;/span>focal&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>有几点需要注意：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>LTS 版本升级，只能一步一步升级，从 16.04 升级到 18.04，再从 18.04 升级到 20.04。不能跳级。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>升级过程不能中断，需要逐步确认。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果升级中断，系统就会处在一个中间阶段，不能升级，不能重启（我遇到的情况）。需要人工介入处理，继续升级完成才行：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="bash">&lt;span class="nb">sudo &lt;/span>dpkg &lt;span class="nt">--configure&lt;/span> &lt;span class="nt">-a&lt;/span>

&lt;span class="c"># 执行上述命令失败是，删除两个锁文件，再次执行即可&lt;/span>
&lt;span class="nb">sudo rm&lt;/span> &lt;span class="nt">-rf&lt;/span> /var/lib/dpkg/lock
&lt;span class="nb">sudo rm&lt;/span> /var/lib/dpkg/lock-frontend

&lt;span class="nb">sudo &lt;/span>dpkg &lt;span class="nt">--configure&lt;/span> &lt;span class="nt">-a&lt;/span>

&lt;span class="nb">sudo &lt;/span>apt-get update
&lt;span class="nb">sudo &lt;/span>apt-get dist-upgrade

&lt;span class="nb">sudo &lt;/span>&lt;span class="k">do&lt;/span>&lt;span class="nt">-release-upgrade&lt;/span>

&lt;span class="nb">sudo &lt;/span>reboot&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>《事实》之读书笔记（二）</title><link>https://www.diguage.com/post/factfulness-2/</link><pubDate>Sun, 24 Oct 2021 20:38:29 +0800</pubDate><guid>https://www.diguage.com/post/factfulness-2/</guid><description>&lt;div class="paragraph">
&lt;p>在去年世界读书日时，D瓜哥在 &lt;a href="https://www.diguage.com/post/books-on-book-day/" target="_blank" rel="noopener">在世界读书日，推荐书单&lt;/a> 中推荐了 &lt;a href="https://book.douban.com/subject/33385402/" target="_blank" rel="noopener">《事实》&lt;/a> 这本书。这周五，上班路上在地铁上把这本书看完了。受益匪浅，获益良多。做个笔记，摘录学习一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>整本书主要分享了下面这十条事实的经验法则：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/factfulness-0.jpeg" alt="事实的经验法则" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从这本书中，让D瓜哥感受最大的是：现在，不止中国，全世界范围内，所有人的生活都在整体地变好。这不仅仅是一种感觉，而是有很多相关国际机构的统计数据做支撑的。以前也有这种感觉，但那只是一种猜测，一种从自身生活感觉上的推断。虽然，新闻媒体中，全球各地各种灾难、冲突，甚至战争不断，但那些都是小范围内的小概率事件。为全世界人民，尤其是中国人民的生活提高，感到深深的自豪。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>除了自豪以外，在做读书笔记时，还有一点趣闻，值得分享一二。这本书D瓜哥看的是纸版书。下文中的摘录也都是随手在书上划线出来的。但是，D瓜哥却可以利用一下午的时间，快速整理出来。之所以这么快速，完全得益于微信的“文字识别”功能：拍照，通过微信发送，然后在微信中打开图片，就会“文字识别”功能。这样就可以快速提取文字，整理出读书摘要。使用过程中，D瓜哥也发现，识别率依赖于图片和文字的清洗程度，偶尔也有识别出错的地方。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当然，这种方法也并非D瓜哥首创，而是在坐地铁上班时，看到一个人用同样的方法做笔记，原以为效率不高，试了一下竟然效率还挺高。感兴趣的小伙伴，不妨试试。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这本书与 &lt;a href="https://www.diguage.com/post/the-art-of-thinking-clearly-1/" target="_blank" rel="noopener">《清醒思考的艺术》&lt;/a> 有一种异曲同工之妙。《事实》侧重数据分析，从上往下从世界上更广阔的现象去分析人本身的一些偏见或错觉；而《清醒思考的艺术》则是侧重自我反思，从下往上从自身去透视普罗大众的一些思维偏误。两本书一起阅读，相信会相得益彰，妙趣横生的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥把读书笔记整理一下。按照章节，分二篇来分享：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/factfulness-1/" target="_blank" rel="noopener">《事实》之读书笔记（一）&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/factfulness-2/" target="_blank" rel="noopener">《事实》之读书笔记（二）&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_6_以偏概全">6. 以偏概全&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>一分为二的本能促使我们把世界分为“我们”和“他们”，而以偏概全的本能使得“我们”认为“他们”是完全一样的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>真正有效的商业策略，需要建立在实事求交是的世界观的基础上，才能够发现未来的客户在哪里。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>真正的挑战是我们要做正确的归纳和分类，而不是错误的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要想做到正确的归纳和分类，最好的办法就是行万里路。没有什么比切身经历更重要。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>失去意识的士兵在仰面躺着的时候，很容易被自己的呕吐物呛到，导致窒息。而俯卧的情况下，他门的呕吐可以自然排出，他们白呼吸道可以保持畅通。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我们也应该尽可能地发现我们逻辑思维中隐藏的以偏概全的错误。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当我们面对新的证据的时候，我们必须有勇气质疑我们之前的假设，并且重新做出评估。如果我们错了，我们应当勇于承认。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/factfulness-6.jpeg" alt="以偏概全" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要做到实事求是，就是要意识到当我们讨论一个群体的时候，我们的分类可能是错误的。我们一定会继续做分类和类比的工作，但是我们要尽可能地提醒自己，不要做错。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>要想控制住我们的以偏概全的本能，我们要经常质疑自己的分类方法。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>在同一类别中寻找不同。&lt;/strong>特别是当一个类别非常巨大的时候，我们应该试图找到有效的办法来将其分得更小、更准确。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>在不同类别中寻找相同。&lt;/strong>如果你发现不同的类别之间存在着巨大的相似性，那么要考虑，你的分类方法有可能是不正确的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>在不同类别中寻找不同。&lt;/strong>不要假设在一个类别中适用的规则可以在其他类别中同样适用。比如收入水平第四级的人不要假设其他级别的人也适用同样的生活规则。再比如失去意识的士兵和沉睡中的婴儿是不同的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>注意大多数。&lt;/strong>大多数仅仅意味着超过一半，我们应当具体区分，大多数究竟意味着51%还是99%。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>注意极端案例。&lt;/strong>活灵活现的图片往往会给我们留下深刻的印象，但是它们有可能只代表着极端案例，而不是普遍现象。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>不要假设别人是傻瓜。&lt;/strong>当你发现一些奇怪现象的时候，请保持好奇心和谦卑之心，去探究这现象背后的道理。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_7_命中注定">7. 命中注定&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>为你所处的特定群体宣称一种所谓的命运，也将有利于将这个群体团结在一起，并且产生一种优越感。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>社会和文化并不像岩石一样不可改变。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>文化、国家、宗教和人民都不是岩石，它们都处在持续的变化中。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我认为，最后脱离贫困的人将是那些生活在偏远、极其贫瘠的土地上，同时又受到战争困扰白的农民。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>事实上宗教和妇女人均生育人数之间并没有必然的联系。反而是收入水平和妇女人均生育人数有非常强 的相关性。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要想控制命中注定的本能 就要注意不要忽视微小的改变。不要轻易忽略每年的变化，尽管这个变化可能仅有百分之一。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>为了控制命中注定的本能， 我们应该持续关注新的数据，并且随时更新我们的认识。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/factfulness-7.jpeg" alt="命中注定" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要做到实事求是，就是要认识到很多事情（比如人民、国家、宗教和文化）看起来似乎保持不变，仅仅是因为改变发生得非常缓慢，并且要记住聚沙成塔。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>要想控制命中注定的本能，就要记住缓慢的改变也仍然是改变。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>注意追踪持续的提高。&lt;/strong>每年小的改变可以在几十年后积累成巨大的改变。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>更新你的知识。&lt;/strong>有些知识很快就会变得过时，技术、国家、社会文化和宗教都在持续的改变当中。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>与老年人对话。&lt;/strong>如果你想弄清楚价值观是如何改变的，请想一想你的祖父母们的价值观和你的价值观有什么不同。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>收集文化改变的案例。&lt;/strong>找到反面的案例来挑战那种认为文化一成不变的说法。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_8_单一视角">8. 单一视角&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>人们总是倾向于认为所有的问题都有单一的原因和单一的解决方案，我把这称为人类的单一视角本能。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>应该持续地测试自己的想法的不足之处。对你自己有限的经验采取谦卑的态度，积极获取最新的信息。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>政治的意识形态；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>专业局限性。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>拥有高智商，数学很好 受过高等教育，甚至得过诺贝尔奖 这些都不能确保你能更正确地认识世界。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>知识有时候会成为专家自的障碍，使他们看不到真正的解决方案。最好的方法就是以多视角来观察这个世界。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>与其试图去根治一种或者几种特别的疾病，还不如致力于提高整个社会的医疗健康水平。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果这些孕妇不能及时到达医院，医院就起不到作用。如果没有救护车，或者没有能够让救护车通行的道路的话，光有医院又有什么用呢?同样地，教育工作者们非常清楚，电灯的作用要远远大于更多的教科书和更多的老师，因为电灯使得学生们可以在太阳落山之后做家庭作业。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果让我选择在哪里生活的话，我不会根据意识形态进行行选择， 而会基于这个国家究竟给它的人民带来了什么而做选择。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在大家讨论自由市场或是政府于预的话题的时候，答案不应该是绝对的是或者否，而应当具体问题具体分析。真正的答案不应该是非黑即白，而是应该在监管和自由之间取得一种平衡。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>任何人如果想强调民主是经济增长和健康水平提高的必要条件的话，都无法在现实中找到足够的依据。与其把民主当作一种取得进步的手段，还不如把民主当作国家奋斗的目标。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>没有任何一种单一的指标可以衡量一个国家的进步。现实永远是复杂的。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/factfulness-8.jpeg" alt="单一视角" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要做到实事求是，就是要认识到单一视角会限制你的想象力。并且时刻牢记获取最佳解决问题的方法就是从多个不同的角度来观察问题，得到一个更全面的了解，从而制订切实可行的解决方案。&lt;/p>
&lt;/div></description></item><item><title>《事实》之读书笔记（一）</title><link>https://www.diguage.com/post/factfulness-1/</link><pubDate>Sun, 24 Oct 2021 12:38:38 +0800</pubDate><guid>https://www.diguage.com/post/factfulness-1/</guid><description>&lt;div class="paragraph">
&lt;p>在去年世界读书日时，D瓜哥在 &lt;a href="https://www.diguage.com/post/books-on-book-day/" target="_blank" rel="noopener">在世界读书日，推荐书单&lt;/a> 中推荐了 &lt;a href="https://book.douban.com/subject/33385402/" target="_blank" rel="noopener">《事实》&lt;/a> 这本书。这周五，上班路上在地铁上把这本书看完了。受益匪浅，获益良多。做个笔记，摘录学习一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>整本书主要分享了下面这十条事实的经验法则：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/factfulness-0.jpeg" alt="事实的经验法则" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从这本书中，让D瓜哥感受最大的是：现在，不止中国，全世界范围内，所有人的生活都在整体地变好。这不仅仅是一种感觉，而是有很多相关国际机构的统计数据做支撑的。以前也有这种感觉，但那只是一种猜测，一种从自身生活感觉上的推断。虽然，新闻媒体中，全球各地各种灾难、冲突，甚至战争不断，但那些都是小范围内的小概率事件。为全世界人民，尤其是中国人民的生活提高，感到深深的自豪。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>除了自豪以外，在做读书笔记时，还有一点趣闻，值得分享一二。这本书D瓜哥看的是纸版书。下文中的摘录也都是随手在书上划线出来的。但是，D瓜哥却可以利用一下午的时间，快速整理出来。之所以这么快速，完全得益于微信的“文字识别”功能：拍照，通过微信发送，然后在微信中打开图片，就会“文字识别”功能。这样就可以快速提取文字，整理出读书摘要。使用过程中，D瓜哥也发现，识别率依赖于图片和文字的清洗程度，偶尔也有识别出错的地方。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当然，这种方法也并非D瓜哥首创，而是在坐地铁上班时，看到一个人用同样的方法做笔记，原以为效率不高，试了一下竟然效率还挺高。感兴趣的小伙伴，不妨试试。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这本书与 &lt;a href="https://www.diguage.com/post/the-art-of-thinking-clearly-1/" target="_blank" rel="noopener">《清醒思考的艺术》&lt;/a> 有一种异曲同工之妙。《事实》侧重数据分析，从上往下从世界上更广阔的现象去分析人本身的一些偏见或错觉；而《清醒思考的艺术》则是侧重自我反思，从下往上从自身去透视普罗大众的一些思维偏误。两本书一起阅读，相信会相得益彰，妙趣横生的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥把读书笔记整理一下。按照章节，分二篇来分享：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/factfulness-1/" target="_blank" rel="noopener">《事实》之读书笔记（一）&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/factfulness-2/" target="_blank" rel="noopener">《事实》之读书笔记（二）&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_引言">引言&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>貌以绝大多数人对这个世界的理解都是错误的。并且不仅仅是错误， 而是系统性的错误。所谓系统性的错误，我指的是这些错误答案并不是随机选择所得。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>人类的答案却是毫无例外地偏向一个方向。人们想象中的世界比真实的世界更加夸张。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>人们对世界的认识是如此错误 ，这一点使我陷入了深深的沮丧和焦虑。这就好比当你使用 GPS 系统的时候，你希望你使用的是正确的信息。你绝不希望你在使用导航系统的时候，这个系统的基础信息全部是另外一个城市的。因为你很清楚，用错误的信息来导航，你最终只能去到一个错误的终点。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>因为缺乏知识只会导致人们和大猩猩一样随机选择答案，而不是犯下系统性错误，回答得比随机选择更差。只有错误的知识，才能够使我们犯下系统性的错误，才能使我们得到如此差的结果。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我所面对的问题，其实是一个有关知识升级的问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我得到了一个结论，要想根除无知，我必须帮助人们升级他们的知识系统。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我也总是非常乐于告诉所有人，他们就是《皇帝的新衣》里一丝不挂的皇帝，他们对自己所生活的世界毫无了解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>无知的问题，并不仅仅是知识升级的问题，也并不可能仅仅通过提供更好的教学工具以及更清晰的数据来解决。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当人们思考的时候人们会持续地并且本能地通过他们的世界观来猜测和理解这个世界。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>根源在于人们错误的过分情绪化的世界观。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当我们知道，我们对世界的误解是来源于系统性的为误判时，我们就大可不必为我们答错题而感到尴尬了。取而代之的应该是感到无比好奇这种错觉是如何产生的呢？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>以数据作为根治无知的良方，以理性作为心灵平静的源泉。因为这个世界并没有你所感觉到的那么糟糕。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>实事求是的思维方式 应该就像健康饮食和持续锻炼一样，成为你日常生活的一部分。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这本书将会告诉你一个真实的世界，也会告诉你为什么你自己不能看到这个世界的真相，你应当如何做才能够看到世界的真相以及如何在现实的生活中变得更加乐观，少点紧张，多点希望。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_一分为二">1. 一分为二&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>人类十大情绪化本能中的一个，一分为二的本能。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>人们似乎总是喜欢把事物一分为二为两种类别，而这两种类别又是互相对立、互相矛盾的，并且这两个类别之间存在着一道巨大的鸿沟。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果你想真正地首付某人他的观念是错误的，那么最有效的办法就是能够用数据来测试他们的观念。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>今年你会用一张 1965 年的地图来为你导航吗？今天你会愿意你的医生用 1965 年的顶尖科技来为你进行诊断和治疗吗？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>那种把世界一分为二的观念，那种认为全世界大多数人口都过着贫穷而悲催的生活的观念，是一种错觉。是彻头彻尾的误解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>人类有一种很强烈的、情绪化的本能。这种本能促使人们习惯于把事情一分为二：好的和坏的，英雄和恶棍，我所在的国家和其他国家。把时间一分为二是简单、直观而且情绪化的方法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这种一分为二的本能会误导我们，把平滑过渡当做两极分化，把和而不同当做分道扬镳，把求同存异当做矛盾对立。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>三种情况激发一分为二的错误本能：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>只比较平均数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>只比较极端情况&lt;/p>
&lt;/li>
&lt;li>
&lt;p>只俯视不仰视&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>平均数最大的问题就是它用了一个单独的数字取代了大量数据的分布规律。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>可能只看到了两个平均数直接的鸿沟，而错过了两组数据之间重合的部分。所谓的鸿沟在事实上并不存在。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在更多的时候，一分为二的的思维方式只能带给我们误导性的和过度情绪化的结论。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>事实上，即便在全世界最不平等的国家，人们的收入之间也并不存在一条鸿沟，绝大多数人都生活在中间阶层。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对你而言，遥想建立一种实事求是的世界观，最大的挑战就是认识到你自己绝大多数的第一手经验是来自第四级以上的生活，而几乎所有的二手经验则经过了大众媒体过滤，大众媒体基本上只会关心那些极端的案例，却并不真正反应现实。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>任何一个从高层建筑从上向下俯视的人都很难辨别出接近地面的矮层建筑的真实高度，因为它们看起来都一样矮。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/factfulness-1.jpeg" alt="一分为二" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要做到实事求是，就要做到当你听到一分为二的说法时，你就能迅速认识到这种说法描述的是一种两极分化的图画，而两极之间存在一道巨大的鸿沟。而在现实中，这道鸿沟往往并不存在，绝大多数人都生活于中间状态。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>要想有效地控制我们一分为二的错误本能，我们就要坚持寻找绝大多数。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>我们要注意只比较平均数的做法。&lt;/strong>平均数之外，我们还要注意数据的实际分布。如果两组数据的分布出现了重叠，那么有可能两组之间的鸿沟并不存在。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>我们要注意只比较极端情况的做法。&lt;/strong>在所有的群体、国家或者国民中，总会有极端情况的存在，总会有顶层和底层。而顶层和底层之间的差别，有时候是极端不公平的。即便如此，大多数仍然分布在中间状态，而在中间并不存在鸿沟。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>我们要注意只俯视不仰视的做法。&lt;/strong>记住俯视会带来错觉，一切看起来都一样矮，但是事实并非如此。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_负面思维">2. 负面思维&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>负面思维：我们队坏事总会比好事更加关注。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>国际合作的基础就是我们拥有对这个世界同样的、实事求是的理解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在统计学里面，有一个很好的基本原则：当差别小于 10% 时，不要轻易做任何结论。&lt;/p>
&lt;/div></description></item><item><title>《领域驱动设计》读书笔记（三）：通过重构来加深理解</title><link>https://www.diguage.com/post/domain-driven-design-3/</link><pubDate>Wed, 18 Aug 2021 14:13:58 +0800</pubDate><guid>https://www.diguage.com/post/domain-driven-design-3/</guid><description>&lt;div class="paragraph">
&lt;p>我们面临的真正挑战是找到深层次的模型，这个模型不但能够捕捉到领域专家的微妙的关注点，还可驱动切实可行的设计。我们的最终目的是开发出能够捕捉到领域深层含义的模型。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要想成功地开发出实用的模型，需要注意以下 3 点：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>复杂巧妙的领域模型是可以实现的，也是值得我们去花费力气实现的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>这样的模型离开不断的重构是很难开发出来的，重构需要领域专家和热爱学习领域知识的开发人员密切参与进来。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>要实现并有效地运用模型，需要精通设计技巧。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>重构就是在不改变软件功能的前提下重新设计它。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>自动化的单元测试套件能够保证对代码进行相对安全的试验。&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>设计模式重构 — 为实现更深层模型而进行的重构。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>代码细节重构&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>简称为“领域模型重构”。 学习以更高维度去看待问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>《重构》一书中所列出的重构分类涵盖了大部分常用的代码细节重构。这些重构主要是为了解决一些可以从代码中观察到的问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>领域模型会随着新认识的出现而不断变化，由于其变化如此多样，以至于根本无法整理出一个完整的目录。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>建模和设计都需要你发挥创造力。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对象分析的传统方法是先在需求文档中确定名词和动词，并将其作为系统的初始对象和方法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>事实上，初始模型通常都是基于对领域的浅显认知而构建的，既不够成熟也不够深入。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>深层模型能够穿过领域表象，清楚地表达出领域专家们的主要关注点以及最相关的知识。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>戴久了的手套在手指关节处会变得柔软；而其他部分则依然硬实，可起到保护的作用。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>柔性设计除了便于修改，还有助于改进模型本身。 Model-Driven Design 需要以下两个方面的支持：深层模型使设计更具表现力；同时，当设计的灵活性可以让开发人员进行试验，而设计又能清晰地表达出领域含义时，那么这个设计实际上就能够将开发人员的深层理解反馈到整个模型发现的过程中。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>由于模型和设计之间具有紧密的关系，因此如果代码难于重构，建模过程也会停滞不前。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>你需要富有创造力，不断地尝试，不断地发现问题才能找到合适的方法为你所发现的领域概念建模，但有时你也可以借用别人已建好的模式。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第_8_章_突破">第 8 章 突破&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/domain-driver-design/refactor-breakthrough.png" alt="重构/突破" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 重构/突破&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>小改进可防止系统退化，成为避免模型变得陈腐的第一道防线。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>重构的原则是始终小步前进，始终保持系统正常运转。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>过渡到真正的深层模型需要从根本上调整思路，并且对设计做大幅修改。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>不要试图去制造突破，那只会使项目陷入困境。通常，只有在实现了许多适度的重构后才有可能出现突破。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要为突破做好准备，应专注于知识消化过程，同时也要逐渐建立健壮的 Ubiquitous Language 。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第_9_章_将隐式概念转变为显式概念">第 9 章 将隐式概念转变为显式概念&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>深层建模的第一步就是要设法在模型中表达出领域的基本概念。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>若开发人员识别出设计中隐含的某个概念或是在讨论中受到启发而发现一个概念时，就会对领域模型和相应的代码进行许多转换，在模型中加入一个或多个对象或关系，从而将此概念显式地表达出来。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_概念挖掘">概念挖掘&lt;/h3>
&lt;div class="paragraph">
&lt;p>倾听领域专家使用的语言。有没有一些术语能够简洁地表达出复杂的概念？他们有没有纠正过你的用词（也许是很委婉的提醒）？当你使用某个特定词语时，他们脸上是否已经不再流露出迷惑的表情？这些都暗示了某个概念也许可以改进模型。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>有些概念可能需要你自己去挖掘和创造。要挖掘的地方就是设计中最不足的地方，也就是操作复杂且难于解释的地方。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>看书与咨询领域专家并不冲突。即便能够从领域专家那里得到充分的支持，花点时间从文献资料中大致了解领域理论也是值得的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>开发人员还有另一个选择，就是阅读在此领域中有过开发经验的软件专业人员编写的资料。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>阅读书籍并不能提供现成的解决方案，但可以为她提供一些全新的实验起点，以及在这个领域中探索过的人总结出来的经验。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_如何为那些不太明显的概念建模">如何为那些不太明显的概念建模&lt;/h3>
&lt;div class="sect3">
&lt;h4 id="_显式的约束">显式的约束&lt;/h4>
&lt;div class="paragraph">
&lt;p>约束是模型概念中非常重要的类别。它们通常是隐含的，将它们显式地表现出来可以极大地提高设计质量。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>将约束条件提取到其自己的方法中，这样就可以通过方法名来表达约束的含义，从而在设计中显式地表现出这条约束。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面是一些警告信号，表明约束的存在正在扰乱其“宿主对象”（Host Object）的设计：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>计算约束所需的数据从定义上看并不属于这个对象。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>相关规则在多个对象中出现，造成了代码重复或导致不属于同一族的对象之间产生了继承关系。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>很多设计和需求讨论是围绕这些约束进行的，而在代码实现中，它们却隐藏在过程代码中。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果约束的存在掩盖了对象的基本职责，或者如果约束在领域中非常突出但在模型中却不明显，那么就可以将其提取到一个显式的对象中，甚至可以把它建模为一个对象和关系的集合。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_将过程建模为领域对象">将过程建模为领域对象&lt;/h4>
&lt;div class="paragraph">
&lt;p>对象是用来封装过程的，这样我们只需考虑对象的业务目的或意图就可以了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>过程是应该被显式表达出来，还是应该被隐藏起来呢？区分的方法很简单：它是经常被领域专家提起呢，还是仅仅被当作计算机程序机制的一部分？&lt;/p>
&lt;/div></description></item><item><title>《卡片笔记写作法》之读书笔记（三）：成功写作的六个步骤</title><link>https://www.diguage.com/post/how-to-take-smart-notes-3/</link><pubDate>Wed, 11 Aug 2021 06:48:10 +0800</pubDate><guid>https://www.diguage.com/post/how-to-take-smart-notes-3/</guid><description>&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>不写，就无法思考。&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;div class="attribution">
— 卢曼 1992，53
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最近把 &lt;a href="https://book.douban.com/subject/35503571/" target="_blank" rel="noopener">《卡片笔记写作法》&lt;/a> 读完了，深受启发，相见恨晚。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>先分享一个小趣闻：看完这本书，想实践一下，就去购物网站搜合适的活页卡片。考虑到便利性，就选择了 A6 大小的活页。上网找了一圈，也没有找到标准尺寸的 A6 活页。考虑以后直接拿 A4 纸进行裁剪。现在的问题变成了如何把 A4 纸裁剪得标准一些。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>看完全书，纵观卡曼的发展过程，特别叹服的一点是，他会不断反思自己的行为和学习方法，并且反复尝试改进自己的学习方法，以求达到最好的效率。反思自己，虽然总是耕读不断，虽然过程中也许捡了不少好砖，但是就像那个捡玉米的猴子，随捡随丢，终究没能垒起我的阅读城堡！可惜可惜！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>闲言少叙，书归正传。读这本书，有几点印象特别深刻：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>书中将记忆区分为两种机智：&lt;strong>存储强度（storage strength）与提取强度（retrieval strength）。并且“存储与提取负相关”，也就是说，存入记忆越容易，提取出来越困难；反之，如果你有些吃力地存入，知识提取会更方便。&lt;/strong>这点有点意思。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>书中一个比喻特别形象：&lt;strong>卢曼卡片盒就像是复利投资，而特罗洛普的技术就像是储蓄罐。&lt;/strong>复利投资可以滚雪球，越滚越大，而且是指数增长。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>书中还有一个概念很有意思：“思维网格”，&lt;strong>如果知识既不是被孤立地保存，也不是被孤立地学习，而是聚集在一个思想网络中，或者说是“思维模型的网格”中，那么理解新的信息就变得更容易了。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>卡片笔记学习法自然而然产生的自下而上写作法拥有一个巨大优势：&lt;strong>我们所使用的想法都嵌入了丰富的上下文中，并且附带了可以使用的素材，而不是凭空产生的。这样做会带来另一个意想不到的收获，那就是我们会对新的想法持更加开放的态度。&lt;/strong>可以轻松避免由于预设结论导致的认知偏误。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>卡片盒笔记的精华所在：&lt;strong>以新的和意想不到的方式，将不同的想法汇集在一起，从而产生新的想法。&lt;/strong>卢曼的卡片不是简简单单地摘抄，而是自己组织语言的转述，需要加工再输出。不仅仅是摘录原文，更是思想的录像机与连接器，记录下当下的思想，建立与已有思想的链接。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>一个巨大的误解是：如果不制订计划，就只能是漫无目的地乱写一气。其实我们真正该做的是构建适合自己的工作流程，让洞见和新想法成为推动我们前进的驱动力。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>记笔记也不是最关键的工作，思考、阅读、理解和提出想法才是，笔记只是它们的具体成果。写，无疑是我们思考、阅读、学习、理解和产生想法的最佳催化剂。&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>书中的真知灼见数不胜数，可以通过D瓜哥的这几篇读书笔记“管中窥豹”。当然，更推荐大家去阅读原文。D瓜哥是在“微信读书”中阅读的免费版，礼尚往来，推荐一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥把读书笔记整理一下。按照章节，分三篇来分享：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/how-to-take-smart-notes-1/" target="_blank" rel="noopener">《卡片笔记写作法》之读书笔记（一）：绪论&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/how-to-take-smart-notes-2/" target="_blank" rel="noopener">《卡片笔记写作法》之读书笔记（二）：有效写作的四个基本原则&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/how-to-take-smart-notes-3/" target="_blank" rel="noopener">《卡片笔记写作法》之读书笔记（三）：成功写作的六个步骤&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第三章_成功写作的六个步骤">第三章 成功写作的六个步骤&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_明确区分独立而又相关联的任务">明确区分独立而又相关联的任务&lt;/h3>
&lt;div class="sect3">
&lt;h4 id="_全神贯注于每一项任务">全神贯注于每一项任务&lt;/h4>
&lt;div class="paragraph">
&lt;p>根据一项引用量很大的研究，频繁出现的电子邮件和短信所造成的不断干扰，会使我们的工作效率降低40%，并使我们的智商至少降低10%。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我们正被越来越多的干扰因素包围着，并且提升注意力的机会也更少了。&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>考虑把微信和工作通讯录的提醒给关闭掉。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_不要一心多用">不要一心多用&lt;/h4>
&lt;div class="paragraph">
&lt;p>虽然那些进行多任务处理的人感觉自己更有效率，但实际上他们的效率却低了很多（Wang and Tchernev,2012;Rosen,2008;Ophir,Nass and Wagner,2009），而且在数量和质量方面的表现都明显落后于一次只做一件事的人。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当我们认为自己在同时处理多项任务时，我们真正做的其实是在两件甚至多件事情之间快速转移注意力，每一次转移注意力都会消耗我们的转移能力，并会延长我们再次集中注意力所需要的时间。总的来说，一心多用会让人疲惫不堪，也会降低我们处理多项任务的能力。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>写论文不仅仅是在键盘上打字，它还包括阅读、理解、思索、产生想法、建立联系、区分术语、寻找合适的词语、结构化、组织、编辑、修正和重写等。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>自从20世纪70年代米哈里·契克森米哈赖（Mihaly Csikszentmihalyi）描述了“心流”状态——一种毫不费力就能高度集中注意力的状态（Csikszentmihalyi, 1975）[2&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>集中注意力不同于“持续注意”，后者指的是我们需要在较长的时间内保持对一项任务的关注，这也是学习、理解或完成某件事情所必需的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>好消息是，如果我们能避免同时处理多项任务，尽可能将不同类型的任务分开，使它们不会相互干扰，并排除可能的其他干扰，我们就能训练自己在更长时间内专注于一件事的能力。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_不同的任务需要不同的关注度">不同的任务需要不同的关注度&lt;/h4>
&lt;div class="paragraph">
&lt;p>校对则需要重点关注写作时用什么词表达更准确，因而校对需要的是更专注的注意力，而写作过程中寻找合适的词语则需要更多的漂浮注意力。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果我们不用同时考虑文章的结构，就更容易集中精力琢磨正确的词语。这就是我们为什么应该把稿件的大纲打印出来，一直放在眼前的原因。我们必须搞清楚眼下哪些内容不必写，因为知道这些内容要写在其他章节里。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>专业地阅读，不是指掌握一种方法，并将其应用于一切情况，而是根据不同的文本灵活地调整阅读的速度和方法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“具体来说，杰出科学家解决问题的行为可以在‘对特定概念的超常专注程度’和‘对想法的有趣探索’之间交替进行。这表明，要想成功解决问题，可能需要根据任务和需求灵活地选择应对策略。”（Vartanian,2009）&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>真正的答案是有创造力的人需要以上两种能力，创造力的关键是能够在开放、活泼的思维和传统的分析框架之间灵活地切换。”（Dean,2013）&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_做专家不做计划制订者">做专家，不做计划制订者&lt;/h4>
&lt;div class="paragraph">
&lt;p>我们停止制订计划之时，就是开始学习之时。根据具体情况在最重要和最有前景的任务之间灵活地选择和切换，变得擅于产生洞见和写出好文章——这需要通过练习来实现。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>学术写作是一个高度依赖认知和思考的过程，但人们误以为仅仅依靠有意识的决策就可以完成，这就是制订计划广受推崇的原因。如果真的想在学术写作方面取得进步，可以通过经验和刻意练习来掌握这门艺术。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>不要害怕犯错，要在错误中成长！
&amp;gt;要想成为专家，我们需要自由地、独立地做出决定，还要经历能够帮助我们学习的必要错误。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要想成为专家，我们需要自由地、独立地做出决定，还要经历能够帮助我们学习的必要错误。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>根据德雷福斯的模型理论[4]，正确运用学习到的规则，可以使你成为一个称职的“胜任者”（位于5层级模型中的第3层级），但它不会使你成为“精通者”（第4层级），当然也不会使你变成“专家”（第5层级）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>根据德雷福斯的模型理论[4]，正确运用学习到的规则，可以使你成为一个称职的“胜任者”（位于5层级模型中的第3层级），但它不会使你成为“精通者”（第4层级），当然也不会使你变成“专家”（第5层级）。&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>经常只是一味地循规蹈矩，只会在“别人的规则”里打转！&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这里所说的直觉不是指某种神秘的力量，而是一种融入了过去的经验，通过无数次成功或失败的反馈循环，深入学习、反复实践的沉淀。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_写下来为大脑减负">写下来为大脑减负&lt;/h4>
&lt;div class="paragraph">
&lt;p>心理学家过去在谈到短期记忆容量时的看法是一致的：我们最多可以同时在脑海中保存7±2件事（Miller,1956）。&lt;/p>
&lt;/div></description></item><item><title>《卡片笔记写作法》之读书笔记（二）：有效写作的四个基本原则</title><link>https://www.diguage.com/post/how-to-take-smart-notes-2/</link><pubDate>Tue, 10 Aug 2021 06:17:30 +0800</pubDate><guid>https://www.diguage.com/post/how-to-take-smart-notes-2/</guid><description>&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>不写，就无法思考。&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;div class="attribution">
— 卢曼 1992，53
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最近把 &lt;a href="https://book.douban.com/subject/35503571/" target="_blank" rel="noopener">《卡片笔记写作法》&lt;/a> 读完了，深受启发，相见恨晚。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>先分享一个小趣闻：看完这本书，想实践一下，就去购物网站搜合适的活页卡片。考虑到便利性，就选择了 A6 大小的活页。上网找了一圈，也没有找到标准尺寸的 A6 活页。考虑以后直接拿 A4 纸进行裁剪。现在的问题变成了如何把 A4 纸裁剪得标准一些。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>看完全书，纵观卡曼的发展过程，特别叹服的一点是，他会不断反思自己的行为和学习方法，并且反复尝试改进自己的学习方法，以求达到最好的效率。反思自己，虽然总是耕读不断，虽然过程中也许捡了不少好砖，但是就像那个捡玉米的猴子，随捡随丢，终究没能垒起我的阅读城堡！可惜可惜！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>闲言少叙，书归正传。读这本书，有几点印象特别深刻：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>书中将记忆区分为两种机智：&lt;strong>存储强度（storage strength）与提取强度（retrieval strength）。并且“存储与提取负相关”，也就是说，存入记忆越容易，提取出来越困难；反之，如果你有些吃力地存入，知识提取会更方便。&lt;/strong>这点有点意思。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>书中一个比喻特别形象：&lt;strong>卢曼卡片盒就像是复利投资，而特罗洛普的技术就像是储蓄罐。&lt;/strong>复利投资可以滚雪球，越滚越大，而且是指数增长。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>书中还有一个概念很有意思：“思维网格”，&lt;strong>如果知识既不是被孤立地保存，也不是被孤立地学习，而是聚集在一个思想网络中，或者说是“思维模型的网格”中，那么理解新的信息就变得更容易了。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>卡片笔记学习法自然而然产生的自下而上写作法拥有一个巨大优势：&lt;strong>我们所使用的想法都嵌入了丰富的上下文中，并且附带了可以使用的素材，而不是凭空产生的。这样做会带来另一个意想不到的收获，那就是我们会对新的想法持更加开放的态度。&lt;/strong>可以轻松避免由于预设结论导致的认知偏误。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>卡片盒笔记的精华所在：&lt;strong>以新的和意想不到的方式，将不同的想法汇集在一起，从而产生新的想法。&lt;/strong>卢曼的卡片不是简简单单地摘抄，而是自己组织语言的转述，需要加工再输出。不仅仅是摘录原文，更是思想的录像机与连接器，记录下当下的思想，建立与已有思想的链接。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>一个巨大的误解是：如果不制订计划，就只能是漫无目的地乱写一气。其实我们真正该做的是构建适合自己的工作流程，让洞见和新想法成为推动我们前进的驱动力。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>记笔记也不是最关键的工作，思考、阅读、理解和提出想法才是，笔记只是它们的具体成果。写，无疑是我们思考、阅读、学习、理解和产生想法的最佳催化剂。&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>书中的真知灼见数不胜数，可以通过D瓜哥的这几篇读书笔记“管中窥豹”。当然，更推荐大家去阅读原文。D瓜哥是在“微信读书”中阅读的免费版，礼尚往来，推荐一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥把读书笔记整理一下。按照章节，分三篇来分享：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/how-to-take-smart-notes-1/" target="_blank" rel="noopener">《卡片笔记写作法》之读书笔记（一）：绪论&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/how-to-take-smart-notes-2/" target="_blank" rel="noopener">《卡片笔记写作法》之读书笔记（二）：有效写作的四个基本原则&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/how-to-take-smart-notes-3/" target="_blank" rel="noopener">《卡片笔记写作法》之读书笔记（三）：成功写作的六个步骤&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第二章_有效写作的四个基本原则">第二章 有效写作的四个基本原则&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_写作是唯一重要的事情">写作是唯一重要的事情&lt;/h3>
&lt;div class="paragraph">
&lt;p>有了想法只有自己知道，就等于没想法，而无法重现的论据也根本算不上论据。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>教授不是为了学生而存在的，学生也不是为了教授而存在的，两者都只为真理而存在，而真理永远属于公共事务。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>专注于写作，并不意味着要减少阅读量，因为它是写作材料的主要来源；专注于写作，并不意味着要少参加讲座或研讨会，因为它们不但能为你提供写作的思路和值得解答的问题，而且是了解研究现状的最佳途径之一，更不用说它为提出和讨论问题提供了平台；专注于写作也并不意味着要停止做演讲或寻找其他方式将自己的想法公之于众——没有什么比这更好的途径为你的想法提供反馈了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>努力找寻你“应该”做的事情并不是在浪费时间，相反，借助于此，你会努力以尽可能高的效率去学习，从而迅速找到那个开放性的问题，也是唯一值得写作的问题。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_简洁是最重要的">简洁是最重要的&lt;/h3>
&lt;div class="paragraph">
&lt;p>越简单的想法往往具有越强大的效果（并且在一开始总被忽视）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>马尔科姆·麦克莱恩（Malcom McLean）的集装箱思想和实践，改变了世界贸易格局。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>1956年4月26日，麦克莱恩把“理想X号（Ideal X）”邮轮改装成可以装载58个集装箱的邮轮起航的时候，仅仅是因为这种直接装卸集装箱的分装方式比整船装卸货物的方式便捷，能免于在港口漫长的等待几天。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>麦克莱恩比别人更明白，重要的不是船东的观点，而是整个贸易的目的，即把货物从生产者那里带到最终目的地。只有将运输链的每一个环节——从包装到运送，从设计船舶到设计港口——都统一起来之后，集装箱的潜力才能够得到充分释放。&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>从这个地方也可以看出，不要只谋求一城的得失，要从战略的高度看全局的胜负。做笔记也一样，不是写完就完事了，整理输出再加工更加重要。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当他们读到一个有趣的句子，就在下面画线；当他们想要评论时，就写到空白处；当他们有一个想法时，就写进笔记本；当他们觉得一篇文章足够重要，就努力摘抄。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>传统的笔记系统中，主要问题是人们把某条笔记存放在了哪个主题下；而卢曼卡片盒笔记系统中，主要问题则是考虑想在哪个主题下再见到它。&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>有点举一反三的灵动，也有反其道而行之的逆向思维。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这里还有一个问题：一些潜在关联不能及时发现，需要后续逐步探索才能慢慢展示出来。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>所有的努力只为一个目的：发表有价值的观点。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>传统的笔记系统是为了找到你意图搜索的东西，让你的大脑疲于回忆，而卢曼卡片盒笔记系统则是为了给你呈现你已经忘记的想法，让你的大脑专注于思考。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要达到群聚效应的最佳点，关键是要分清楚三类笔记。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>闪念笔记（Fleeting Notes）：这类笔记只是对收集信息起提醒作用，可以用任何一种方式来写，最后会在一两天内被扔进垃圾桶。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>永久笔记（Permanent Notes）：这种笔记以一种永久可以理解的方式包含了必要的信息，并且永远不会被扔掉。它们总是以同样的方式存放在同一个地方，或者是在文献管理系统中，或者按出版标准写好放在卡片盒中。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>项目笔记（Project Notes）：它们只与某一特定项目有关，保存在特定项目的文件夹内，项目结束后即可丢弃或存档。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第一个典型的错误是很多勤奋的学生都会遵循“要像写科学杂志一样记笔记”的建议。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>由于他把每一条笔记都划分到“永久笔记”的范畴，导致优秀的笔记被其他或许只与特定项目有关、或许实际上不那么好的笔记淹没了，因此永远也无法实现群聚效应。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第二个典型的错误是只收集与特定项目有关的笔记。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第三个典型的错误当然是把所有的笔记都当作闪念笔记。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这三种类别的混乱有一个共同点，那就是记笔记的好处会随着你保存笔记数量的增加而减少，笔记记得越多，就越难找到恰当的那个，越难以自如地将相关的笔记整合到一起。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>闪念笔记只有在你能在一两天内回顾它们，并将它们变成以后可以使用的适当的笔记时才有价值。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>永久笔记不只是思想或观点的备忘录，而是以书面形式包含实际思想或观点的文本，这是一个至关重要的区别。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>与项目有关的笔记可以是以下内容：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>手稿中的评论；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>与项目有关的文献收集；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>大纲；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>草稿片段；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>备忘录；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>待办事项；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>草稿本身。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>《卡片笔记写作法》之读书笔记（一）：绪论</title><link>https://www.diguage.com/post/how-to-take-smart-notes-1/</link><pubDate>Mon, 09 Aug 2021 06:48:10 +0800</pubDate><guid>https://www.diguage.com/post/how-to-take-smart-notes-1/</guid><description>&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>不写，就无法思考。&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;div class="attribution">
— 卢曼 1992，53
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最近把 &lt;a href="https://book.douban.com/subject/35503571/" target="_blank" rel="noopener">《卡片笔记写作法》&lt;/a> 读完了，深受启发，相见恨晚。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>先分享一个小趣闻：看完这本书，想实践一下，就去购物网站搜合适的活页卡片。考虑到便利性，就选择了 A6 大小的活页。上网找了一圈，也没有找到标准尺寸的 A6 活页。考虑以后直接拿 A4 纸进行裁剪。现在的问题变成了如何把 A4 纸裁剪得标准一些。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>看完全书，纵观卡曼的发展过程，特别叹服的一点是，他会不断反思自己的行为和学习方法，并且反复尝试改进自己的学习方法，以求达到最好的效率。反思自己，虽然总是耕读不断，虽然过程中也许捡了不少好砖，但是就像那个捡玉米的猴子，随捡随丢，终究没能垒起我的阅读城堡！可惜可惜！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>闲言少叙，书归正传。读这本书，有几点印象特别深刻：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>书中将记忆区分为两种机智：&lt;strong>存储强度（storage strength）与提取强度（retrieval strength）。并且“存储与提取负相关”，也就是说，存入记忆越容易，提取出来越困难；反之，如果你有些吃力地存入，知识提取会更方便。&lt;/strong>这点有点意思。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>书中一个比喻特别形象：&lt;strong>卢曼卡片盒就像是复利投资，而特罗洛普的技术就像是储蓄罐。&lt;/strong>复利投资可以滚雪球，越滚越大，而且是指数增长。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>书中还有一个概念很有意思：“思维网格”，&lt;strong>如果知识既不是被孤立地保存，也不是被孤立地学习，而是聚集在一个思想网络中，或者说是“思维模型的网格”中，那么理解新的信息就变得更容易了。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>卡片笔记学习法自然而然产生的自下而上写作法拥有一个巨大优势：&lt;strong>我们所使用的想法都嵌入了丰富的上下文中，并且附带了可以使用的素材，而不是凭空产生的。这样做会带来另一个意想不到的收获，那就是我们会对新的想法持更加开放的态度。&lt;/strong>可以轻松避免由于预设结论导致的认知偏误。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>卡片盒笔记的精华所在：&lt;strong>以新的和意想不到的方式，将不同的想法汇集在一起，从而产生新的想法。&lt;/strong>卢曼的卡片不是简简单单地摘抄，而是自己组织语言的转述，需要加工再输出。不仅仅是摘录原文，更是思想的录像机与连接器，记录下当下的思想，建立与已有思想的链接。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>一个巨大的误解是：如果不制订计划，就只能是漫无目的地乱写一气。其实我们真正该做的是构建适合自己的工作流程，让洞见和新想法成为推动我们前进的驱动力。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>记笔记也不是最关键的工作，思考、阅读、理解和提出想法才是，笔记只是它们的具体成果。写，无疑是我们思考、阅读、学习、理解和产生想法的最佳催化剂。&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>书中的真知灼见数不胜数，可以通过D瓜哥的这几篇读书笔记“管中窥豹”。当然，更推荐大家去阅读原文。D瓜哥是在“微信读书”中阅读的免费版，礼尚往来，推荐一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥把读书笔记整理一下。按照章节，分三篇来分享：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/how-to-take-smart-notes-1/" target="_blank" rel="noopener">《卡片笔记写作法》之读书笔记（一）：绪论&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/how-to-take-smart-notes-2/" target="_blank" rel="noopener">《卡片笔记写作法》之读书笔记（二）：有效写作的四个基本原则&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/how-to-take-smart-notes-3/" target="_blank" rel="noopener">《卡片笔记写作法》之读书笔记（三）：成功写作的六个步骤&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_中文版序">中文版序&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>卡片盒笔记依赖一种非线性的方式，即缓慢而稳定的改进，促进思维产生持续、微妙的变化，并且事物间相生相依。朱利安发现这些主题在中国哲学中被讨论得更为广泛。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我欣喜地发现，并非我一人有此体验：卡片盒笔记法——关联的、分散的、自下而上的方法不仅比一些教育者们所推崇的线性的、自上而下的方法更为自然，而且衍生出了诸多写作及笔记记录工具。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>卡片盒笔记的精华所在：以新的和意想不到的方式，将不同的想法汇集在一起，从而产生新的想法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>随着可使用工具的快速变化，我决定让这本书尽可能地与工具无关。书籍有着不同的生命周期，因此应该更多地关注不受时间影响的方面。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_推荐序一_像卢曼一样写卡片">推荐序一 像卢曼一样写卡片&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>我写作时，手边会放着纸质的卡片。我一般会先在卡片上写下一些灵感，然后再用卡片写作软件将它们正式整合成文章。同样，在阅读时，我也会不断地使用纸质卡片盒与卡片写作软件写下读书心得。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>日本知名学者梅棹忠夫在《智识的生产技术》中提到的“京大卡”，他认为卡片要大一些，大概是B6开本大小，更容易保存写作灵感。而我带队研发的“写匠”，更多地参考了美国知名作家纳博科夫的卡片写作法——使用索引卡，通过任意打乱卡片次序完成自己的写作。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>尼克拉斯·卢曼是20世纪德国重要的社会学家，而且其影响力已远远超出了社会学领域。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>传统的卡片写作法，像纳博科夫，是只使用一个盒子，用一个盒子来保存自己写作的内容。而卢曼多了一个盒子，对自己卡片写作的内容进行索引和整理。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>比约克率先区分了记忆竞争的两种不同类型：存储强度（storage strength）与提取强度（retrieval strength）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“存储与提取负相关”，也就是说，存入记忆越容易，提取出来越困难；反之，如果你有些吃力地存入，知识提取会更方便。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第一类是主题索引。当某个主题的内容积累得足够丰富，卢曼就会做一张主题索引卡，对这个主题进行概览。主题索引卡上会汇集所有相关笔记的编码或链接，每条笔记会用一两个词或一个短句简要说明核心内容。这类索引，相当于给了你一个进入某一主题的入口。第二类与主题索引类似，只不过不是对某一主题的概览，而是针对盒子里相近位置的卡片所涉及的所有不同主题进行概览。第三类是在当前卡片上做索引，标明这条笔记逻辑上的前一条是什么、后一条是什么（这些卡片在盒子里的位置可能并不挨着）。第四类，也是最常用的索引形式，就是简单的“笔记-笔记”连接。两条笔记可能完全没有关系，把它们关联在一起，往往会产生出乎意料的新思路。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我经常推荐的是德国科学家丹尼尔·卢德克（Daniel Lüdecke）开发的开源软件——卡片盒笔记（Zettelkasten）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一是将文本的颗粒度拆分得更细。组块（block）是认知科学上的一个常用概念，文本组块正是这个概念的具象化。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>以大的单位很难记住，拆分成小的单位就更容易记住。写作时，用大脑直接记住的东西越多，写作就越不容易被打断，节省的脑力就越多。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>二是建立了一个“双向引用”关系。什么是“双向引用”？&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_推荐序二_外脑思考突破思维局限的写作法">推荐序二 外脑思考，突破思维局限的写作法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>我和作者的兴趣一样，也是外脑思考（Thinking Outside the Brain）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>但工具功能很难改变人的思维习惯，而需要配合课程、书籍或教练服务。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>千里之行始于跬步的道理中国人都懂，但没人教过我们在一步一步地、枯燥地踏在探索未知的路上时如何保持热情，如何坚持，如何充满信心，如何时刻和自己对话，以及如何走好每一步。书中介绍的工作流和原则，是卢曼等人利用卡片盒实践过的，他们完全无须坚持就充满乐趣地达成了非凡成就。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>深知全世界的人仅凭意志力和坚持，绝无可能完成那种极限挑战。只有完全换另一种思维方式，才能心情愉悦地、安全地达成目标。创业如此，做产品如此，写论文亦如此。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>把诸如拖延、坚持、压力、焦虑、知识管理、时间管理等热词从脑海里划掉。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>使用卡片笔记写作法，我们可以不定期地提取先前的想法和事实，并将它们与其他信息联系起来，这正是专家们推荐的学习方式。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_推荐序三_用卡片笔记积累你的知识复利">推荐序三 用卡片笔记积累你的知识复利&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>书中主要介绍的是德国社会学家尼克拉斯·卢曼自创的一套笔记方法，利用这套方法，他一生积累了90000个知识卡片，写了58本书和其他大量出版物。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>就是把你感兴趣或者将来可能会用到的知识收集起来，然后用一种像集装箱一样标准化的方式，去处理这些笔记，建立笔记之间的联系，供你使用。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>重要的不是记录，而是更好地思考。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>比如拒绝做知识的搬运工，必须用自己的话写下来；比如需要记录的是知识，而非信息；比如不需要进行机械分类，而是让关系慢慢地自动生长出来等。&lt;/p>
&lt;/div></description></item><item><title>日志最佳实践探究</title><link>https://www.diguage.com/post/logging-best-practices/</link><pubDate>Wed, 14 Jul 2021 10:52:58 +0800</pubDate><guid>https://www.diguage.com/post/logging-best-practices/</guid><description>&lt;div class="paragraph">
&lt;p>加入公司以来，参与了很多个项目的开发维护；也排查处理过很多线上问题；为了写 Mock 测试，也专门去日志系统上扒拉过不少日志等等。在整个过程中，对日志的认识有了不少更深刻的认识和体会。也发现不少问题。这里先从存在的问题展开论述。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_日志存在的问题">日志存在的问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>从个人的眼光上来看，当前的系统存在如下问题：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>必要日志没有打印出来，导致在追踪问题或测试代码时，带来不必要的麻烦。比如查看一个接口的返回值用于 Mock 测试；再比如 RPC 调用报错，返回值以及错误信息没有打印到日志中，不知道具体错误原因是什么。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>日志抽取中日志路径配置错误，导致日志重复收集，带来不必要的处理和存储成本。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>日志代码不规范，导致不必要的性能消耗；或者大促时，日志降级不生效。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>日志框架繁多，造成造成冲突，遗漏部分日志。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>日志配置不规范，不利于日志的采集和清洗。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>日志和调用链路物理隔离，查看一个请求的整个调用链路上的日志非常不方便，不利于问题的快速排查和定位。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;em>大家的系统中，存在什么样的日志问题？欢迎留言交流讨论。&lt;/em>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>针对这些问题，我觉得有些地方值得发力一下。然后，做了一些探索，总结一下，以备后续使用。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_日志最佳实践探索">日志最佳实践探索&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>对于日志的使用，相信所有的开发人员都比较清楚，网上也有大量资料，相关日志框架的官方文档，也写的非常详尽，这里就不再赘述。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>本文从一个角度对日志规范进行探究：&lt;strong>在排查问题时，能否通过日志来尽快地了解系统运行状态，定位问题原因？&lt;/strong>另外，由于 Java 的日志框架特别多，有一些比较容易迷惑的问题，尝试做出一点总结。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>系统运行后，不严格地说，再去观察系统运行状态，就类似于在黑夜中行走。此时，向你扔过来一块板砖🧱，那么，事后如何追责呢？&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/logging/throw-bug.jpg" alt="throw bug" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>请问：你能否成功躲开这块叫做 Bug 的板砖🧱？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>日志用来记录用户操作、系统运行状态等，是一个系统的重要组成部分。然而，由于日志通常不属于系统的核心功能，但是在日志对于排查问题，有无可替代的作用，理应得到所有开发人员的重视（不重视，怎么甩锅？！）！&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>If dog is a man’s best friend, &lt;br/>
logs are software engineer’s best friend.&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;div class="attribution">
— Geshan Manandhar&lt;br/>
&lt;cite>Logging best practices&lt;/cite>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>好的日志可以帮助系统的开发和运维人员：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>了解线上系统的运行状态&lt;/p>
&lt;/li>
&lt;li>
&lt;p>快速准确定位线上问题&lt;/p>
&lt;/li>
&lt;li>
&lt;p>发现系统瓶颈&lt;/p>
&lt;/li>
&lt;li>
&lt;p>预警系统潜在风险&lt;/p>
&lt;/li>
&lt;li>
&lt;p>挖掘产品最大价值&lt;/p>
&lt;/li>
&lt;li>
&lt;p>可以将一个流程完整串起来（比如orderId）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>……&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/logging/bug-goto-land.jpg" alt="bug goto land" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>不好的日志导致：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>对系统的运行状态一知半解，甚至一无所知&lt;/p>
&lt;/li>
&lt;li>
&lt;p>系统出现问题无法定位，或者需要花费巨大的时间和精力&lt;/p>
&lt;/li>
&lt;li>
&lt;p>无法发现系统瓶颈，不知优化从何做起&lt;/p>
&lt;/li>
&lt;li>
&lt;p>无法基于日志对系统运行过程中的错误和潜在风险进行监控和报警&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对挖掘用户行为和提升产品价值毫无帮助&lt;/p>
&lt;/li>
&lt;li>
&lt;p>……&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>《领域驱动设计》读书笔记（二）：模型驱动设计的构造块</title><link>https://www.diguage.com/post/domain-driven-design-2/</link><pubDate>Sun, 11 Jul 2021 23:44:22 +0800</pubDate><guid>https://www.diguage.com/post/domain-driven-design-2/</guid><description>&lt;div class="paragraph">
&lt;p>遵循“职责驱动设计”的原则，&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“契约式设计”思想。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>开发一个好的领域模型是一门艺术。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/domain-driver-design/model-driven-design-language.png" alt="模型驱动设计语言" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. 模型驱动设计语言&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第_4_章_分离领域">第 4 章 分离领域&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_模式layered_architecture">模式：Layered Architecture&lt;/h3>
&lt;div class="paragraph">
&lt;p>在面向对象的程序中，常常会在业务对象中直接写入用户界面、数据库访问等支持代码。而一些业务逻辑则会被嵌入到用户界面组件和数据库脚本中。这么做是为了以最简单的方式在短期内完成开发工作。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果与领域有关的代码分散在大量的其他代码之中，那么查看和分析领域代码就会变得异常困难。对用户界面的简单修改实际上很可能会改变业务逻辑，而要想调整业务规则也很可能需要对用户界面代码、数据库操作代码或者其他的程序元素进行仔细的筛查。这样就不太可能实现一致的、模型驱动的对象了，同时也会给自动化测试带来困难。考虑到程序中各个活动所涉及的大量逻辑和技术，程序本身必须简单明了，否则就会让人无法理解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要想创建出能够处理复杂任务的程序，需要做到关注点分离——使设计中的每个部分都得到单独的关注。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Layered Architecture 的基本原则是层中的任何元素都仅依赖于本层的其他元素或其下层的元素。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/domain-driver-design/layer.png" alt="应用分层" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. 应用分层&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给复杂的应用程序划分层次。在每一层内分别进行设计，使其具有内聚性并且只依赖于它的下层。采用标准的架构模式，只与上层进行松散的耦合。将所有与领域模型相关的代码放在一个层中，并把它与用户界面层、应用层以及基础设施层的代码分开。领域对象应该将重点放在如何表达领域模型上，而不需要考虑自己的显示和存储问题，也无需管理应用任务等内容。这使得模型的含义足够丰富，结构足够清晰，可以捕捉到基本的业务知识，并有效地使用这些知识。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>关注点的清晰分离可以使每一层的设计更易理解和维护。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在连接各层的同时不影响分离带来的好处，这是很多模式的目的所在。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>各层之间是松散连接的，层与层的依赖关系只能是单向的。上层可以直接使用或操作下层元素，方法是通过调用下层元素的公共接口，保持对下层元素的引用（至少是暂时的），以及采用常规的交互手段。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果下层元素需要与上层元素进行通信（不只是回应直接查询），则需要采用另一种通信机制，使用架构模式来连接上下层，如回调模式或 Observers 模式。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最早将用户界面层与应用层和领域层相连的模式是 Model-View-Controller（MVC，模型—视图—控制器）框架。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>只要连接方式能够维持领域层的独立性，保证在设计领域对象时不需要同时考虑可能与其交互的用户界面，那么这些连接方式就都是可用的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最好的架构框架既能解决复杂技术问题，也能让领域开发人员集中精力去表达模型，而不考虑其他问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>不妄求万全之策，只要有选择性地运用框架来解决难点问题，就可以避开框架的很多不足之处。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_领域层是模型的精髓">领域层是模型的精髓&lt;/h3>
&lt;div class="paragraph">
&lt;p>“领域层”则是领域模型以及所有与其直接相关的设计元素的表现，它由业务逻辑的设计和实现组成。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果领域逻辑与程序中的其他关注点混在一起，就不可能实现这种一致性。将领域实现独立出来是领域驱动设计的前提。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_模式the_smart_ui反模式">模式：The Smart UI“反模式”&lt;/h3>
&lt;div class="paragraph">
&lt;p>Smart UI是另一种设计方法，与领域驱动设计方法迥然不同且互不兼容。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果一个经验并不丰富的项目团队要完成一个简单的项目，却决定使用 Model-Driven Design 以及 Layered Architecture，那么这个项目组将会经历一个艰难的学习过程。团队成员不得不去掌握复杂的新技术，艰难地学习对象建模。（即使有这本书的帮助，这也依然是一个具有挑战性的任务！）对基础设施和各层的管理工作使得原本简单的任务却要花费很长的时间来完成。简单项目的开发周期较短，期望值也不是很高。所以，早在项目团队完成任务之前，该项目就会被取消，更谈不上去论证有关这种方法的许多种令人激动的可行性了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>即使项目有更充裕的时间，如果没有专家的帮助，团队成员也不太可能掌握这些技术。最后，假如他们确实能够克服这些困难，恐怕也只会开发出一套简单的系统。因为这个项目本来就不需要丰富的功能。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在用户界面中实现所有的业务逻辑。将应用程序分成小的功能模块，分别将它们实现成用户界面，并在其中嵌入业务规则。用关系数据库作为共享的数据存储库。使用自动化程度最高的用户界面创建工具和可用的可视化编程工具。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果一个架构能够把那些与领域相关的代码隔离出来，得到一个内聚的领域设计，同时又使领域与系统其他部分保持松散耦合，那么这种架构也许可以支持领域驱动设计。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如何让一个有效的领域模型和一个富有表达力的实现同时演进。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第_5_章_软件中所表示的模型">第 5 章 软件中所表示的模型&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>一个对象是用来表示某种具有连续性和标识的事物的呢（可以跟踪它所经历的不同状态，甚至可以跨不同的实现跟踪它），还是用于描述某种状态的属性呢？这是 Entity 与 Value Object 之间的根本区别。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>领域中还有一些方面适合用动作或操作来表示，这比用对象表示更加清楚。这些方面最好用 Service 来表示，而不应把操作的责任强加到 Entity 或 Value Object 上，尽管这样做稍微违背了面向对象的建模传统。 Service 是应客户端请求来完成某事。&lt;/p>
&lt;/div></description></item><item><title>AWK 简介</title><link>https://www.diguage.com/post/introducing-awk/</link><pubDate>Sat, 10 Jul 2021 10:14:47 +0800</pubDate><guid>https://www.diguage.com/post/introducing-awk/</guid><description>&lt;div class="paragraph">
&lt;p>这周需要处理一个日志文件，有一次体会到 AWK 强大和方便，但也认识到自己对 AWK 了解的粗浅。所以，写篇文章再深入学习一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>根据维基百科显示，AWK 于二十世纪七十年代在 Bell Labs 创建；其名字来源于三位创始人： Alfred Aho、Peter Weinberger and Brian Kernighan。AWK 是一个现在几乎每台 Linux 机器上都会有这个命令。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>AWK 是一种领域专用语言，专用设计用于文本处理，常用于提取文本或者生成报告。 AWK 也像 Shell 一样，方言和实现众多。D瓜哥这里选择最常用的 GNU AWK 实现。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>AWK 是以行为单位来处理文本的。它不仅仅是一个命令行，而且是一门语言。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>先展示一下我们的实例程序：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="bash">&lt;span class="nv">$ &lt;/span>&lt;span class="nb">cat &lt;/span>employee.txt
ajay manager account 45000
sunil clerk account 25000
varun manager sales 50000
amit manager account 47000
tarun peon sales 15000
deepak clerk sales 23000
sunil peon sales 13000
satvik director purchase 80000&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>AWK 的基本用法如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="bash">&lt;span class="c"># ① 基本格式&lt;/span>
&lt;span class="nv">$ &lt;/span>&lt;span class="nb">awk &lt;/span>动作 文件名

&lt;span class="c"># ② 标准 I/O 格式&lt;/span>
&lt;span class="nv">$ &lt;/span>&lt;span class="nb">cat &lt;/span>文件名 | &lt;span class="nb">awk &lt;/span>动作&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>《领域驱动设计》读书笔记（一）：运用领域模型</title><link>https://www.diguage.com/post/domain-driven-design-1/</link><pubDate>Thu, 08 Jul 2021 20:40:05 +0800</pubDate><guid>https://www.diguage.com/post/domain-driven-design-1/</guid><description>&lt;div class="sect1">
&lt;h2 id="_序">序&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>控制复杂性的关键是有一个好的领域模型，这个模型不应该仅仅停留在领域的表面，而是要透过表象抓住领域的实质结构，从而为软件开发人员提供他们所需的支持。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在领域建模过程中不应将概念与实现割裂开来。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>概念与实现密不可分的最主要原因在于，领域模型的最大价值是它提供了一种通用语言，这种语言是将领域专家和技术人员联系在一起的纽带。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>领域模型并不是按照“先建模，后实现”这个次序来工作的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>真正强大的领域模型是随着时间演进的，即使是最有经验的建模人员也往往发现他们是在系统的初始版本完成之后才有了最好的想法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>既品尝过成功的美酒，也体验过失败的沮丧。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_前言">前言&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>真正决定软件复杂性的是设计方法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>很多应用程序最主要的复杂性并不在技术上，而是来自领域本身、用户的活动或业务。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>领域驱动设计是一种思维方式，也是一组优先任务，它旨在加速那些必须处理复杂领域的软件项目的开发。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>领域驱动设计的实质就是消化吸收大量知识，最后产生一个反映深层次领域知识并聚焦于关键概念的模型。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>极端的简约主义是解救那些过度追求设计的执迷者的良方。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>实际上， XP最适合那些对设计的感觉很敏锐的开发人员。 XP过程假定人们可以通过重构来改进设计，而且可以经常、快速地完成重构。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>首先需要深入研究模型，然后基于最初的（可能是不成熟的）模型实现一个初始设计，再反复改进这个设计。每次团队对领域有了新的理解之后，都需要对模型进行改进，使模型反映出更丰富的知识，而且必须对代码进行重构，以便反映出更深刻的模型，并使应用程序可以充分利用模型的潜力。
第一部分 运用领域模型&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>模型是一种简化。它是对现实的解释——把与解决问题密切相关的方面抽象出来，而忽略无关的细节。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>模型正是解决此类信息超载问题的工具。模型这种知识形式对知识进行了选择性的简化和有意的结构化。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>领域模型并非某种特殊的图，而是这种图所要传达的思想。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对这类知识严格的组织且有选择的抽象。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>领域建模并不是要尽可能建立一个符合“现实”的模型。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>建模更像是制作电影——出于某种目的而概括地反映现实。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在领域驱动的设计中，3个基本用途决定了模型的选择。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>模型和设计的核心互相影响。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>模型是团队所有成员使用的通用语言的中枢。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>模型是浓缩的知识。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>软件的核心是其为用户解决领域相关的问题的能力。所有其他特性，不管有多么重要，都要服务于这个基本目的。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第_1_章_消化知识">第 1 章 消化知识&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;strong>有效建模的要素：&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>模型和实现的绑定。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>建立了一种基于模型的语言。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>开发一个蕴含丰富知识的模型。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>提炼模型。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>头脑风暴和实验。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>语言和草图，再加上头脑风暴活动，将我们的讨论变成“模型实验室”，在这些讨论中可以演示、尝试和判断上百种变化。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>高效的领域建模人员是知识的消化者。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>领域模型的不断精化迫使开发人员学习重要的业务原理，而不是机械地进行功能开发。领域专家被迫提炼自己已知道的重要知识的过程往往也是完善其自身理解的过程，而且他们会渐渐理解软件项目所必需的概念严谨性。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>模型永远都不会是完美的，因为它是一个不断演化完善的过程。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>高效率的团队需要有意识地积累知识，并持续学习。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>业务活动和规则如同所涉及的实体一样，都是领域的核心，任何领域都有各种类别的概念。知识消化所产生的模型能够反映出对知识的深层理解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当我们的建模不再局限于寻找实体和值对象时，我们才能充分吸取知识，因为业务规则之间可能会存在不一致。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>知识消化是一种探索，它永无止境。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_第_2_章_交流与语言的使用">第 2 章 交流与语言的使用&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>领域模型可成为软件项目通用语言的核心。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_模式ubiquitous_language">模式：Ubiquitous Language&lt;/h3>
&lt;div class="paragraph">
&lt;p>如果语言支离破碎，项目必将遭遇严重问题。领域专家使用他们自己的术语，而技术团队所使用的语言则经过调整，以便从设计角度讨论领域。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>日常讨论所使用的术语与代码（软件项目的最重要产品）中使用的术语不一致。甚至同一个人在讲话和写东西时使用的语言也不一致，这导致的后果是，对领域的深刻表述常常稍纵即逝，根本无法记录到代码或文档中。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>翻译使得沟通不畅，并削弱了知识消化。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>然而任何一方的语言都不能成为公共语言，因为它们无法满足所有的需求。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Ubiquitous Language（通用语言）的词汇包括类和主要操作的名称。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>将模型作为语言的支柱。确保团队在内部的所有交流中以及代码中坚持使用这种语言。在画图、写东西，特别是讲话时也要使用这种语言。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>通过尝试不同的表示方法（它们反映了备选模型）来消除难点。然后重构代码，重新命名类、方法和模块，以便与新模型保持一致。解决交谈中的术语混淆问题，就像我们对普通词汇形成一致的理解一样。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要认识到， Ubiquitous Language 的更改就是对模型的更改。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>领域专家应该抵制不合适或无法充分表达领域理解的术语或结构，开发人员应该密切关注那些将会妨碍设计的有歧义和不一致的地方。&lt;/p>
&lt;/div></description></item><item><title>关于 MySQL 新版连接驱动时区对齐问题的研究</title><link>https://www.diguage.com/post/research-on-timezone-of-mysql-new-connection-driver/</link><pubDate>Wed, 07 Jul 2021 10:23:41 +0800</pubDate><guid>https://www.diguage.com/post/research-on-timezone-of-mysql-new-connection-driver/</guid><description>&lt;div class="paragraph">
&lt;p>在一个项目开量验证过程中，发现 createDate 字段不正确，比正确时间晚了十四个小时。调研发现，这是一个非常典型的问题。现在把定位问题的思路和解决办法给大家做个分享。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>首先，检查数据库配置，查询线上生产环境配置，结果如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/mysql/mysql-variable.png" alt="MySQL 变量" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. MySQL 变量&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>同时，检查线上生产环境 MySQL 版本，为问题复现做准备：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/mysql/mysql-version.png" alt="MySQL 版本" width="95%"/>
&lt;/div>
&lt;div class="title">图 2. MySQL 版本&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从数据库配置上来说，基本正常，没有发现什么问题。（持续运行了这么长时间，有问题应该早就发现了。）&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>其次，检查数据库连接配置，正式环境的链接配置如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code>jdbc:mysql://&amp;lt;host&amp;gt;:3306/&amp;lt;schema&amp;gt;?createDatabaseIfNotExist=true
 &amp;amp;characterEncoding=utf-8&amp;amp;useUnicode=true&amp;amp;connectTimeout=2000
 &amp;amp;socketTimeout=2000&amp;amp;autoReconnect=true&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>数据库连接也没有问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第三，询问 SA 线上服务器时区配置，回复上是 CST，这个和数据库对应，没有问题。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/mysql/talk-to-sa.png" alt="与 SA 沟通" width="95%"/>
&lt;/div>
&lt;div class="title">图 3. 与 SA 沟通&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>配置检查正常，那么只好在本地搭建环境，重现问题，再寻求解决方案。由于项目是基于 Spring Boot 2.3.7.RELEASE 开发的，相关依赖也尽量使用 Spring Boot 指定版本的，所以，很快把开发环境搭好了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在配置服务器环境时，遇到一点小小的问题：我一直以为有个时区名称叫 CST，就在网上去查怎么设置，结果徒劳半天也没有找到。后来上开发机检查开发机时区配置，发现是 &lt;code>Asia/Shanghai&lt;/code>。将测试服务器设置为该时区，数据库内部查询时区，显示和服务器一直。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>调试代码中，发现 MySQL 连接驱动的代码中，有配置时区的相关代码，如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>com.mysql.cj.protocol.a.NativeProtocol#configureTimezone&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * Configures the client&amp;#39;s timezone if required.
 *
 * @throws CJException
 * if the timezone the server is configured to use can&amp;#39;t be
 * mapped to a Java timezone.
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">configureTimezone&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 获取服务器时区&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">configuredTimeZoneOnServer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">serverSession&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getServerVariable&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;time_zone&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="c1">// 如果服务器时区是 SYSTEM，则使用服务器的 system_time_zone 时区设置&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;SYSTEM&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">equalsIgnoreCase&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">configuredTimeZoneOnServer&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">configuredTimeZoneOnServer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">serverSession&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getServerVariable&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;system_time_zone&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="c1">// 获取客户端时区配置&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">canonicalTimezone&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">getPropertySet&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">getStringProperty&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">PropertyKey&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">serverTimezone&lt;/span>&lt;span class="o">).&lt;/span>&lt;span class="na">getValue&lt;/span>&lt;span class="o">();&lt;/span>

 &lt;span class="c1">// 如果服务器时区不为空，切客户端时区配置不可用，则使用服务器的时区配置&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">configuredTimeZoneOnServer&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// user can override this with driver properties, so don&amp;#39;t detect if that&amp;#39;s the case&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">canonicalTimezone&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nc">StringUtils&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isEmptyOrWhitespaceOnly&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">canonicalTimezone&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">try&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">canonicalTimezone&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nc">TimeUtil&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getCanonicalTimezone&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">configuredTimeZoneOnServer&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">getExceptionInterceptor&lt;/span>&lt;span class="o">());&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">IllegalArgumentException&lt;/span> &lt;span class="n">iae&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">throw&lt;/span> &lt;span class="nc">ExceptionFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">createException&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">WrongArgumentException&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">iae&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getMessage&lt;/span>&lt;span class="o">(),&lt;/span> &lt;span class="n">getExceptionInterceptor&lt;/span>&lt;span class="o">());&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">canonicalTimezone&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">canonicalTimezone&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 为该会话设置时区&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">serverSession&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setServerTimeZone&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">TimeZone&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getTimeZone&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">canonicalTimezone&lt;/span>&lt;span class="o">));&lt;/span>

 &lt;span class="c1">//&lt;/span>
 &lt;span class="c1">// The Calendar class has the behavior of mapping unknown timezones to &amp;#39;GMT&amp;#39; instead of throwing an exception, so we must check for this...&lt;/span>
 &lt;span class="c1">//&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(!&lt;/span>&lt;span class="n">canonicalTimezone&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">equalsIgnoreCase&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;GMT&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">serverSession&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getServerTimeZone&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">getID&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">equals&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;GMT&amp;#34;&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">throw&lt;/span> &lt;span class="nc">ExceptionFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">createException&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">WrongArgumentException&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nc">Messages&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getString&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;Connection.9&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">Object&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="o">{&lt;/span> &lt;span class="n">canonicalTimezone&lt;/span> &lt;span class="o">}),&lt;/span>
 &lt;span class="n">getExceptionInterceptor&lt;/span>&lt;span class="o">());&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>

&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Raft 论文摘要（二）</title><link>https://www.diguage.com/post/summary-of-the-raft-paper-2/</link><pubDate>Mon, 05 Jul 2021 19:58:32 +0800</pubDate><guid>https://www.diguage.com/post/summary-of-the-raft-paper-2/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章中，通过阅读 &lt;a href="https://raft.github.io/raft.pdf">《In Search of an Understandable Consensus Algorithm》&lt;/a> 前三节的内容，对论文的大致内容做了简介，简单说明了一下 Replicated state machines 的用途以及 Paxos 本身存在的问题。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_4_designing_for_understandability">4. Designing for understandability&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>several goals in designing Raft:&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>it must providea complete and practical foundation for system building;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>it must be safe under all conditions and available under typical operating conditions;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>it must be efficient for common operations.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Our most important goal — and most difficult challenge — was &lt;strong>understandability&lt;/strong>.&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从这里可以看出，Raft 设计的初衷就是为了易于理解和便于构建。&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
There were numerous points in the design of Raft where we had to choose among alternative approaches. In these situations we evaluated the alternatives based on understandability.
&lt;/blockquote>
&lt;/div></description></item><item><title>Raft 论文摘要（一）</title><link>https://www.diguage.com/post/summary-of-the-raft-paper-1/</link><pubDate>Fri, 02 Jul 2021 11:42:26 +0800</pubDate><guid>https://www.diguage.com/post/summary-of-the-raft-paper-1/</guid><description>&lt;div class="paragraph">
&lt;p>前一段时间，在一次开组会的时候，给小组成员简单介绍了一下 Raft 协议。大概四年前读过 Raft 的论文，这次分享的时候，好多好多细节都忘了。所以，再次把 &lt;a href="https://raft.github.io/raft.pdf">《In Search of an Understandable Consensus Algorithm》&lt;/a> 这篇论文找出来，重读一遍，做个笔记和摘要，方便后续学习和复习。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_abstract">Abstract&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="quoteblock">
&lt;blockquote>
Raft is a consensus algorithm for managing a replicated log.
&lt;/blockquote>
&lt;/div>
&lt;div class="paragraph">
&lt;p>开篇摘要就点出了 Raft 的特点： Raft 是一种管理复制日志的共识算法。&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
In order to enhance understandability, Raft separates the key elements of consensus, such as leader election, log replication, and safety, and it enforcesa stronger degree of coherency to reduce the number of states that must be considered.
&lt;/blockquote>
&lt;/div>
&lt;div class="paragraph">
&lt;p>为了增强可理解性，Raft 将共识分解成几个关键元素，例如 Leader 选举，日志复制，以及安全性等；同时，为了降低需要考虑的状态的数量，还强制实施了更强的一致性。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_1_introduction">1. Introduction&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="quoteblock">
&lt;blockquote>
Consensus algorithms allow a collection of machines to work as a coherent group that can survive the failures of some of its members.
&lt;/blockquote>
&lt;/div></description></item><item><title>从 Spring PR 中学习代码技巧</title><link>https://www.diguage.com/post/learn-code-tips-from-spring-pull-requests/</link><pubDate>Sun, 27 Jun 2021 18:20:28 +0800</pubDate><guid>https://www.diguage.com/post/learn-code-tips-from-spring-pull-requests/</guid><description>&lt;div class="paragraph">
&lt;p>D瓜哥经常关注 Spring 的 PR 与 Issue。在众多 Contributor 中，除了 Spring 团队成员之外，我对 &lt;a href="https://github.com/stsypanov" target="_blank" rel="noopener">stsypanov (Сергей Цыпанов)&lt;/a> 印象很深刻。这哥们给 Spring 提了非常多的 PR，请看列表 &lt;a href="https://github.com/spring-projects/spring-framework/pulls?page=1&amp;amp;q=author%3Astsypanov+is%3Aclosed" target="_blank" rel="noopener">Pull requests · spring-projects/spring-framework&lt;/a>，而且这个哥们的 PR 都非常有特点，绝大部分是性能提升方面的 PR，而且还会给出 JMH 的测试结果。不愧是毛熊人，做事细致严谨。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这周心血来潮，把这哥们的 PR 翻一翻，希望可以学习一些编码技巧。简单记录一下，以备以后回顾学习。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_提高_map_的遍历性能">提高 &lt;code>Map&lt;/code> 的遍历性能&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>请看： &lt;a href="https://github.com/spring-projects/spring-framework/pull/1891/files" target="_blank" rel="noopener">SPR-17074 Replace iteration over Map::keySet with Map::entrySet by stsypanov · Pull Request #1891&lt;/a>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>摘取一个示例如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="c1">// --before update------------------------------------------------------&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">String&lt;/span> &lt;span class="n">attributeName&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">attributes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">keySet&lt;/span>&lt;span class="o">())&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">Object&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">attributes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">attributeName&lt;/span>&lt;span class="o">);&lt;/span>

&lt;span class="c1">// --after update-------------------------------------------------------&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">Map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">Entry&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">String&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nc">Object&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">attributeEntry&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">attributes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">entrySet&lt;/span>&lt;span class="o">())&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">attributeName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">attributeEntry&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getKey&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="nc">Object&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">attributeEntry&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getValue&lt;/span>&lt;span class="o">();&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这个改动很小，但是对性能的改善还是比较显著的。翻看自己项目的代码，还是有不少是改动前的写法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>针对这点，D瓜哥也给 Spring 发了一个 PR： &lt;a href="https://github.com/spring-projects/spring-framework/pull/27100" target="_blank" rel="noopener">Improve performance of iteration in GroovyBeanDefinitionReader by diguage · Pull Request #27100&lt;/a>。相信不久就会合并到 &lt;code>main&lt;/code> 分支的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>所以，给 Spring 以及其他开源项目提 PR，其实一点也不难。只要，你花心思去研究，肯定有机会的。不过，也反思一点：我这个 PR 有点东施效颦的感觉，有点刷 KPI 的样子。还是应该脚踏实地去好好研究，提更多更有建设性意见的 PR。&lt;/p>
&lt;/div></description></item><item><title>在七夕“摘”情诗</title><link>https://www.diguage.com/post/love-poems-digests-at-qixi/</link><pubDate>Tue, 25 Aug 2020 09:33:51 +0800</pubDate><guid>https://www.diguage.com/post/love-poems-digests-at-qixi/</guid><description>&lt;div class="paragraph">
&lt;p>今天七夕情人节，选摘几首情诗做个纪念。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_邶风_静女">邶风 · 静女&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>静女其姝，俟我于城隅。爱而不见，搔首踟蹰。&lt;br/>
静女其娈，贻我彤管。彤管有炜，说怿女美。&lt;br/>
自牧归荑，洵美且异。匪女之为美，美人之贻。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_郑风_子衿">郑风 · 子衿&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>青青子衿，悠悠我心。纵我不往，子宁不嗣音？&lt;br/>
青青子佩，悠悠我思。纵我不往，子宁不来？&lt;br/>
挑兮达兮，在城阙兮。一日不见，如三月兮！&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="zhixiangshu">舒婷 · 致橡树&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>我如果爱你——&lt;br/>
绝不像攀援的凌霄花，&lt;br/>
借你的高枝炫耀自己；&lt;br/>
我如果爱你——&lt;br/>
绝不学痴情的鸟儿，&lt;br/>
为绿荫重复单调的歌曲；&lt;br/>
也不止像泉源，&lt;br/>
常年送来清凉的慰藉；&lt;br/>
也不止像险峰，&lt;br/>
增加你的高度，衬托你的威仪。&lt;br/>
甚至日光。&lt;br/>
甚至春雨。&lt;br/>
不，这些都还不够！&lt;br/>
我必须是你近旁的一株木棉，&lt;br/>
作为树的形像和你站在一起。&lt;br/>
根，紧握在地下，&lt;br/>
叶，相触在云里。&lt;br/>
每一阵风过，&lt;br/>
我们都互相致意，&lt;br/>
但没有人，&lt;br/>
听懂我们的言语。&lt;br/>
你有你的铜枝铁干，&lt;br/>
像刀，像剑，&lt;br/>
也像戟；&lt;br/>
我有我红硕的花朵，&lt;br/>
像沉重的叹息，&lt;br/>
又像英勇的火炬。&lt;br/>
我们分担寒潮、风雷、霹雳；&lt;br/>
我们共享雾霭、流岚、虹霓。&lt;br/>
仿佛永远分离，&lt;br/>
却又终身相依。&lt;br/>
这才是伟大的爱情，&lt;br/>
坚贞就在这里：&lt;br/>
爱——&lt;br/>
不仅爱你伟岸的身躯，&lt;br/>
也爱你坚持的位置，足下的土地。&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/goodbye-2019-hello-2020/xiangshu.jpg" alt="xiangshu" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/goodbye-2019-hello-2020/#duanwu-travel">告别 2019，迎接 2020&lt;/a> 中已经展示过这张照片了。但是，没有多做说明。这里就简要介绍一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>2019 年端午自驾游时，在内蒙古的一座山上，无意间看到了上面照片中的这两棵树，形象神似《致橡树》中“站在一起的两棵树”。所以，就专门拍下了这张照片留作纪念。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_席慕蓉_一棵开花的树">席慕蓉 · 一棵开花的树&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>如何让你遇见我&lt;br/>
在我最美丽的时刻为这&lt;br/>
我已在佛前求了五百年&lt;br/>
求它让我们结一段尘缘&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>佛于是把我化作一棵树&lt;br/>
长在你必经的路旁&lt;br/>
阳光下慎重地开满了花&lt;br/>
朵朵都是我前世的盼望&lt;/p>
&lt;/div></description></item><item><title>《远见》之读书笔记</title><link>https://www.diguage.com/post/the-long-view/</link><pubDate>Fri, 14 Aug 2020 20:58:23 +0800</pubDate><guid>https://www.diguage.com/post/the-long-view/</guid><description>&lt;div class="paragraph">
&lt;p>最近向一个朋友了解一家公司情况时，聊到职业发展的问题。就随手推荐了《远见：如何规划职业生涯3大阶段》这本书。正好利用这个机会，把以前的读书摘要发布出来。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这本书，D瓜哥在 &lt;a href="https://www.diguage.com/post/goodbye-2019-hello-2020/#the-long-view">告别 2019，迎接 2020：《远见》&lt;/a> 中也提到过。这里把当时写的读书笔记直接拷贝过来：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>本书讲人生的职业生涯分为三个阶段：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第一阶段是强势开局的时候。你在职业上的努力必须着重于为前方的漫长道路挖掘和装备自己。你的学习曲线要比职位、职称更加重要。在这一阶段，要为职业生涯打好基础并建立起良好的早期习惯。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第二阶段是聚焦长板的时候。该阶段的首要目标是寻找自己的甜蜜区，即你所擅长的、所热爱的和这个世界所需要的这三者之间的交集。这个时候你要展现自我，让自己鹤立鸡群，想方设法平稳地走在那条收获最大的职场路径上。你要专注于自己的长板，且大可忽略自己的短板。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第三阶段致力于实现持续的影响力，以及寻找一条可以稳定延续到60多岁甚至70多岁的新的可持续职业道路。你要在第三阶段完成三个关键任务：完成继任计划、保持关联性，以及为自己点燃一团新的职业之火。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>用一句话来总结：第一阶段：加添燃料，强势开局； 第二阶段：聚焦长板，达到高点； 第三阶段：优化长尾，持续发挥影响力。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>三大职场燃料来源：可迁移技能、有意义的经验和持久的关系。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>5个数字，树立正确的职场思维&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>职业生涯的长度：用62减去你目前的年龄。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>精通一项技能所需的时间：要花多少小时才能在某一方面达到“精通”？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>40岁之后能赚到的个人财富百分比：在40岁之后，你赚到的钱会占你一生个人财富的百分之多少？大部分人的估计是60%。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>社交货币：你有多少社交网络好友？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>职场支持者的人数：你认为能在“职业生涯的天堂”里遇到多少人，也就是说有多少人能对你的职业生涯和人生带来真正的变化？&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>这本书强烈推荐给对未来职业有追求的小伙伴！&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>好戏开始，下面👇是读书摘要：&lt;/p>
&lt;/div>
&lt;hr/>
&lt;hr/>
&lt;div class="sect1">
&lt;h2 id="_part_1远见思维与工具箱">PART 1　远见思维与工具箱&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_远见思维多行动少忧虑">远见思维：多行动，少忧虑&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>职业生涯的持续时间长得惊人，包括了三个截然不同的阶段。&lt;/p>
&lt;div class="paragraph">
&lt;p>第一阶段是强势开局的时候。你在职业上的努力必须着重于为前方的漫长道路挖掘和装备自己。你的学习曲线要比职位、职称更加重要。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第二阶段是聚焦长板的时候。该阶段的首要目标是寻找自己的甜蜜区，即你所擅长的、所热爱的和这个世界所需要的这三者之间的交集。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第三阶段致力于实现持续的影响力，以及寻找一条可以稳定延续到 60多岁甚至 70多岁的新的可持续职业道路。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>职场燃料很重要，因为职业生涯的基础决定结果。&lt;/p>
&lt;div class="paragraph">
&lt;p>这种燃料有三个主要来源：可迁移技能、有意义的经验和持久的关系。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>职业生涯需要通过对时间的巧妙投资来构建。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>职业生涯并不是以线性或者可预测的方式发展的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>职业生涯远不止于一份工作，而是生活的一大部分。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>实现职业规划要做到的5件事&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>学习职场数学，树立正确的长期思维方式。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>盘点职场清单，梳理你最有用处的技能、经验和关系。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>进行“ 100小时测试”并完成一份“个人时间档案”，从而了解你目前的时间投资状况。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在尝试建立新的职场路径或者在多个选项中抉择时，运用“职场路径向导”（ Career Path Navigator）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>时刻更新你的职业生涯&lt;/p>
&lt;div class="openblock">
&lt;div class="content">
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>我如何避免被机器取代？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>我能在哪里以什么方式找到工作？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>未来我将如何分配时间？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>我会把钱花光吗？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>工作如何能让我更幸福？&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_三大阶段聚焦_45_年职业生涯">三大阶段，聚焦 45 年职业生涯&lt;/h3>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>第一阶段：加添燃料，强势开局；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第二阶段：聚焦长板，达到高点；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第三阶段：优化长尾，持续发挥影响力。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>第一阶段：加添燃料，强势开局&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>职业生涯前 15年的唯一目标就是为接下来的两个阶段打好基础。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第一阶段是探索和弥补自身短板的时候。如果你是个糟糕的演讲者，那就去参加相关的培训课程。如果你对待团队成员过于强势或弱势，那就去参加领导力培训。学习要比纯粹的成功更重要。有时跌倒并不可怕，只要你能吸取教训，并将这些经验加以利用就行。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>第二阶段：锚点甜蜜区，聚焦长板&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第二阶段是在你的长板、你的爱好以及这个世界的需求之间寻找交集的时候。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>坦率地承认自己的短板，针对它们招募盟友，你就可以把大多数时间放在核心长板上了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>第三阶段：优化长尾，持续发挥影响力&lt;/strong>&lt;/p>
&lt;/div></description></item><item><title>神奇的 Morris 树遍历</title><link>https://www.diguage.com/post/morris-tree-traversal/</link><pubDate>Fri, 14 Aug 2020 18:27:03 +0800</pubDate><guid>https://www.diguage.com/post/morris-tree-traversal/</guid><description>&lt;div class="paragraph">
&lt;p>无论是在计算机课上，还是在网上，对数据结构有一定了解的童鞋，应该都对树的遍历不陌生。常见的遍历方式有如下两种：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>基于栈的遍历：需要额外的空间复杂度。实现略复杂。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>基于递归的遍历：实现简单。空间复杂度上，与栈类似，只是这里的栈维护是由系统自动完成的。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在看左程云的《程序员代码面试指南》时，里面介绍了一种只需 O(1) 的额外空间复杂度的遍历方法：Morris 树遍历。感觉非常神奇。这里给大家介绍一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Morris 树遍历的神奇之处在于，它充分利用了树里面大量的空闲节点（比如叶子节点的左右子树节点就为空，可以利用起来）来建立起必要的连接，推动遍历的进行。核心思想非常简单：找出当前节点左子树的最右节点，此时最右节点的右子树为空，将最右节点的右子树指向当前节点。然后左移，递归完成所有相关连接。没有左子树时，则向右移动，依次完成上述操作。我们来结合图来说明。如图：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/data-structure/morris-tree-traversal.svg" alt="Morris 树遍历" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. Morris 树遍历&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如上图所示，当访问根节点 &lt;code>4&lt;/code> 时，它的左子树的最右节点就是 &lt;code>3&lt;/code>，将 &lt;code>3&lt;/code> 的右子树指向当前节点 &lt;code>4&lt;/code>，如线条 &lt;code>⑥&lt;/code> 所示。向左启动，建立起 &lt;code>1&lt;/code> 到 &lt;code>2&lt;/code> 的连接 &lt;code>④&lt;/code>。再向左移动到 &lt;code>1&lt;/code>，&lt;code>1&lt;/code> 没有左子树，则向右移动，此时就利用上了刚刚建立起的连接 &lt;code>④&lt;/code>。依次类推，即可完成遍历。在遍历过程中，也需要把建立的临时连接给取消掉。&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * Morris 树遍历
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">morris&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">head&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">curr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">head&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">mostRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">curr&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 当前节点左子树的最右节点，当然是从左子树开始了&lt;/span>
 &lt;span class="n">mostRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostRight&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 左子树不为空，则找出左子树的最右节点&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>
 &lt;span class="c1">// 由于需要建立最右节点到当前节点的连接，&lt;/span>
 &lt;span class="c1">// 所以，需要判断是否已建立连接来打破死循环&lt;/span>
 &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">mostRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 最右节点的右子树为空，则第一次访问，那么建立起连接&lt;/span>
 &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="n">curr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">continue&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 最右节点的右子树不为空，则第二次访问，打破连接，恢复原貌&lt;/span>
 &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="c1">// 左子树为空，则向右移动&lt;/span>
 &lt;span class="n">curr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>根据代码，结合图示，很容易得到 Morris 遍历的结果： &lt;code>4&lt;/code>、&lt;code>2&lt;/code>、&lt;code>1&lt;/code>、&lt;code>2&lt;sub>2&lt;/sub>&lt;/code>、&lt;code>3&lt;/code>、&lt;code>4&lt;sub>2&lt;/sub>&lt;/code>、&lt;code>6&lt;/code>、&lt;code>5&lt;/code>、&lt;code>6&lt;sub>2&lt;/sub>&lt;/code>、&lt;code>7&lt;/code>。分析这个结果可以发现：&lt;strong>有左子树的节点都会被访问两次。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_前根遍历">前根遍历&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>那么该树的前根遍历是什么呢？这个也很容易得出：&lt;code>4&lt;/code>、&lt;code>2&lt;/code>、&lt;code>1&lt;/code>、&lt;code>3&lt;/code>、&lt;code>6&lt;/code>、&lt;code>5&lt;/code>、&lt;code>7&lt;/code>。如何从 Morris 遍历中，得到前根遍历的结果呢？对比两边的结果，可以很容易发现：将访问两次的元素，只在第一次访问时输出；只访问一次的原始直接输出即可。代码如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * Morris 树前根遍历
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">morrisPre&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">head&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">curr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">head&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">mostRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">curr&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 当前节点左子树的最右节点，当然是从左子树开始了&lt;/span>
 &lt;span class="n">mostRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostRight&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 左子树不为空，则找出左子树的最右节点&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>
 &lt;span class="c1">// 由于需要建立最右节点到当前节点的连接，&lt;/span>
 &lt;span class="c1">// 所以，需要判断是否已建立连接来打破死循环&lt;/span>
 &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">mostRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 最右节点的右子树为空，则第一次访问，那么建立起连接&lt;/span>
 &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="c1">// 第一次访问时，即输出&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">print&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">val&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">curr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">continue&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 最右节点的右子树不为空，则第二次访问，打破连接，恢复原貌&lt;/span>
 &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 前面内容已经分析过：有左子树的节点就会被访问两次&lt;/span>
 &lt;span class="c1">// 那么没有左子树的节点，就自会访问一次，访问到时直接输出即可。&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">print&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">val&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="c1">// 左子树为空，则向右移动&lt;/span>
 &lt;span class="n">curr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Spring AOP 源码分析：创建代理（二）</title><link>https://www.diguage.com/post/spring-aop-create-proxy-cglib/</link><pubDate>Tue, 11 Aug 2020 15:00:46 +0800</pubDate><guid>https://www.diguage.com/post/spring-aop-create-proxy-cglib/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;a href="https://www.diguage.com/post/spring-aop-bootstrap/">Spring AOP 源码分析：入门&lt;/a> 中，梳理出来了 Spring AOP 的入口。 &lt;a href="https://www.diguage.com/post/spring-aop-get-advices/">Spring AOP 源码分析：获得通知&lt;/a> 中着重介绍了如何获取通知。上一篇文章 &lt;a href="https://www.diguage.com/post/spring-aop-create-proxy-jdk/">Spring AOP 源码分析：创建代理（一）&lt;/a> 重点介绍了一下切面链的组装和基于 JDK 动态代理的 AOP 的实现，这篇文章介绍一下基于 cglib 的代理类是生成。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_cglib_简介">cglib 简介&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/spring-framework/cglib.png" alt="CGLIB" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>CGLIB（Code Generator Library）是一个高性能的代码生成库，被广泛应用于 AOP 框架（Spring）中以提供方法拦截功能，主要以继承目标类的方式来进行拦截实现，因此 CGLIB 可以对无接口的类进行代理。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>CGLIB代理主要通过操作字节码的方式为对象引入方法调用时访问操作，底层使用了ASM来操作字节码生成新的类，ASM是一个短小精悍的字节码操作框架。CGLIB的应用栈如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/spring-framework/cglib-architecture.jpg" alt="CGLIB" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>最新版的 Hibernate 已经把字节码库从 cglib 切换为 Byte Buddy。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>JDK 动态代理是通过实现 &lt;code>InvocationHandler&lt;/code> 接口，在其 &lt;code>invoke&lt;/code> 方法中添加切面逻辑。而 cglib 则是通过实现 &lt;code>MethodInterceptor&lt;/code> 接口，在其 &lt;code>invoke&lt;/code> 方法中添加切面逻辑。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面看一下在 Spring 中，是如何实现利用 cglib 来实现 AOP 编程的？&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_cglibaopproxy">&lt;code>CglibAopProxy&lt;/code>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>先看一下创建代理对象的方法：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>CglibAopProxy#getProxy(ClassLoader)&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="nd">@Override&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="nc">Object&lt;/span> &lt;span class="nf">getProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nd">@Nullable&lt;/span> &lt;span class="nc">ClassLoader&lt;/span> &lt;span class="n">classLoader&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">logger&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isTraceEnabled&lt;/span>&lt;span class="o">())&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">logger&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">trace&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;Creating CGLIB proxy: &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">advised&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getTargetSource&lt;/span>&lt;span class="o">());&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="k">try&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">rootClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">advised&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getTargetClass&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="nc">Assert&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">state&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">rootClass&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="s">&amp;#34;Target class must be available for creating a CGLIB proxy&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">proxySuperClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rootClass&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">rootClass&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">contains&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">ClassUtils&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">CGLIB_CLASS_SEPARATOR&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">proxySuperClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rootClass&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getSuperclass&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;[]&lt;/span> &lt;span class="n">additionalInterfaces&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rootClass&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getInterfaces&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">additionalInterface&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">additionalInterfaces&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">advised&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">addInterface&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">additionalInterface&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="c1">// Validate the class, writing log messages as necessary.&lt;/span>
 &lt;span class="c1">// 验证 Class&lt;/span>
 &lt;span class="n">validateClassIfNecessary&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">proxySuperClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">classLoader&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="c1">// Configure CGLIB Enhancer...&lt;/span>
 &lt;span class="nc">Enhancer&lt;/span> &lt;span class="n">enhancer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">createEnhancer&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">classLoader&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">enhancer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setClassLoader&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">classLoader&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">classLoader&lt;/span> &lt;span class="k">instanceof&lt;/span> &lt;span class="nc">SmartClassLoader&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span>
 &lt;span class="o">((&lt;/span>&lt;span class="nc">SmartClassLoader&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="n">classLoader&lt;/span>&lt;span class="o">).&lt;/span>&lt;span class="na">isClassReloadable&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">proxySuperClass&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">enhancer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setUseCache&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="n">enhancer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setSuperclass&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">proxySuperClass&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">enhancer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setInterfaces&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">AopProxyUtils&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">completeProxiedInterfaces&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">advised&lt;/span>&lt;span class="o">));&lt;/span>
 &lt;span class="n">enhancer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setNamingPolicy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">SpringNamingPolicy&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">INSTANCE&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">enhancer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setStrategy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nc">ClassLoaderAwareGeneratorStrategy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">classLoader&lt;/span>&lt;span class="o">));&lt;/span>

 &lt;span class="c1">// 设置拦截器&lt;/span>
&lt;span class="hll"> &lt;span class="nc">Callback&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">callbacks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">getCallbacks&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">rootClass&lt;/span>&lt;span class="o">);&lt;/span>
&lt;/span> &lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;[]&lt;/span> &lt;span class="n">types&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;[&lt;/span>&lt;span class="n">callbacks&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">];&lt;/span>
 &lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">types&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">length&lt;/span>&lt;span class="o">;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="o">++)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">types&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">callbacks&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">].&lt;/span>&lt;span class="na">getClass&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="c1">// fixedInterceptorMap only populated at this point, after getCallbacks call above&lt;/span>
 &lt;span class="n">enhancer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setCallbackFilter&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nc">ProxyCallbackFilter&lt;/span>&lt;span class="o">(&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">advised&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getConfigurationOnlyCopy&lt;/span>&lt;span class="o">(),&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">fixedInterceptorMap&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">fixedInterceptorOffset&lt;/span>&lt;span class="o">));&lt;/span>
 &lt;span class="n">enhancer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setCallbackTypes&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">types&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="c1">// Generate the proxy class and create a proxy instance.&lt;/span>
 &lt;span class="c1">// 生成代理类以及创建代理&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="nf">createProxyClassAndInstance&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">enhancer&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">callbacks&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">catch&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">CodeGenerationException&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="nc">IllegalArgumentException&lt;/span> &lt;span class="n">ex&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nf">AopConfigException&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;Could not generate CGLIB subclass of &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">advised&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getTargetClass&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">+&lt;/span>
 &lt;span class="s">&amp;#34;: Common causes of this problem include using a final class or a non-visible class&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span>
 &lt;span class="n">ex&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">catch&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">Throwable&lt;/span> &lt;span class="n">ex&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// TargetSource.getTarget() failed&lt;/span>
 &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nf">AopConfigException&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;Unexpected AOP exception&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">ex&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Spring AOP 源码分析：创建代理（一）</title><link>https://www.diguage.com/post/spring-aop-create-proxy-jdk/</link><pubDate>Mon, 10 Aug 2020 14:10:00 +0800</pubDate><guid>https://www.diguage.com/post/spring-aop-create-proxy-jdk/</guid><description>&lt;div class="paragraph">
&lt;p>&lt;a href="https://www.diguage.com/post/spring-aop-bootstrap/">Spring AOP 源码分析：入门&lt;/a> 中，梳理出来了 Spring AOP 的入口。上一篇文章 &lt;a href="https://www.diguage.com/post/spring-aop-get-advices/">Spring AOP 源码分析：获得通知&lt;/a> 中着重介绍了如何获取通知。接着上一篇文章，这篇文章介绍一下如何创建代理。&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>AbstractAutoProxyCreator#createProxy&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="kd">protected&lt;/span> &lt;span class="nc">Object&lt;/span> &lt;span class="nf">createProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">beanClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nd">@Nullable&lt;/span> &lt;span class="nc">String&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">,&lt;/span>
 &lt;span class="nd">@Nullable&lt;/span> &lt;span class="nc">Object&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">specificInterceptors&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nc">TargetSource&lt;/span> &lt;span class="n">targetSource&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>

 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">beanFactory&lt;/span> &lt;span class="k">instanceof&lt;/span> &lt;span class="nc">ConfigurableListableBeanFactory&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">AutoProxyUtils&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">exposeTargetClass&lt;/span>&lt;span class="o">((&lt;/span>&lt;span class="nc">ConfigurableListableBeanFactory&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">beanFactory&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">beanClass&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>


 &lt;span class="c1">// 创建代理工厂对象&lt;/span>
 &lt;span class="nc">ProxyFactory&lt;/span> &lt;span class="n">proxyFactory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">ProxyFactory&lt;/span>&lt;span class="o">();&lt;/span>

 &lt;span class="c1">// 获取当前类的属性&lt;/span>
 &lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">copyFrom&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="c1">//如果没有使用CGLib代理&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(!&lt;/span>&lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isProxyTargetClass&lt;/span>&lt;span class="o">())&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 是否可能使用CGLib代理&lt;/span>
 &lt;span class="c1">// 决定对于给定的 Bean 是否应该使用 targetClass 而不是他的接口代理&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">shouldProxyTargetClass&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setProxyTargetClass&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 查看beanClass对应的类是否含有InitializingBean.class/DisposableBean.class/Aware.class接口&lt;/span>
 &lt;span class="c1">// 无则采用JDK动态代理，有则采用CGLib动态代理&lt;/span>
 &lt;span class="n">evaluateProxyInterfaces&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="c1">// 获得所有关联的Advisor集合(该分支待补充)&lt;/span>
 &lt;span class="nc">Advisor&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">advisors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">buildAdvisors&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">specificInterceptors&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">addAdvisors&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">advisors&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="c1">// 此处的targetSource一般为SingletonTargetSource&lt;/span>
 &lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setTargetSource&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">targetSource&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="c1">// 定制代理，扩展点，空实现&lt;/span>
 &lt;span class="n">customizeProxyFactory&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="c1">// 用来控制代理工厂被配置后，是否还允许修改通知&lt;/span>
 &lt;span class="c1">// 缺省为 false&lt;/span>
 &lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setFrozen&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">freezeProxy&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="c1">// 是否设置预过滤模式，此处针对本文为true&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">advisorsPreFiltered&lt;/span>&lt;span class="o">())&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setPreFiltered&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="c1">// 获取使用JDK动态代理或者cglib动态代理产生的对象&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">proxyFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">getProxyClassLoader&lt;/span>&lt;span class="o">());&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>ProxyFactory#getProxy(ClassLoader)&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="kd">public&lt;/span> &lt;span class="nc">Object&lt;/span> &lt;span class="nf">getProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nd">@Nullable&lt;/span> &lt;span class="nc">ClassLoader&lt;/span> &lt;span class="n">classLoader&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 1、创建JDK方式的AOP代理或者CGLib方式的AOP代理&lt;/span>
 &lt;span class="c1">// 2、调用具体的AopProxy来创建Proxy代理对象&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="nf">createAopProxy&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">getProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">classLoader&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 &lt;code>createAopProxy()&lt;/code> 方法中就不再列出，因为 &lt;code>AopProxyFactory&lt;/code> 接口只有一个实现类 &lt;code>DefaultAopProxyFactory&lt;/code>。所以，直接来看看 &lt;code>getProxy(classLoader)&lt;/code> 方法：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>DefaultAopProxyFactory#createAopProxy&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="nd">@Override&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="nc">AopProxy&lt;/span> &lt;span class="nf">createAopProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">AdvisedSupport&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="kd">throws&lt;/span> &lt;span class="nc">AopConfigException&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 如果实现接口，默认采用Java动态代理&lt;/span>
 &lt;span class="c1">// 如果没有接口，或者有接口却强制使用 cglib&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(!&lt;/span>&lt;span class="no">IN_NATIVE_IMAGE&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span>
 &lt;span class="c1">// optimize 是否实用激进的优化策略&lt;/span>
 &lt;span class="c1">// proxyTargetClass 为 true，则代理类本身而不是接口&lt;/span>
 &lt;span class="c1">// 是否存在代理接口&lt;/span>
 &lt;span class="o">(&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isOptimize&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isProxyTargetClass&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">hasNoUserSuppliedProxyInterfaces&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">)))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">targetClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getTargetClass&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">targetClass&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nf">AopConfigException&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;TargetSource cannot determine target class: &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span>
 &lt;span class="s">&amp;#34;Either an interface or a target is required for proxy creation.&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">targetClass&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isInterface&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nc">Proxy&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">isProxyClass&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">targetClass&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nf">JdkDynamicAopProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nf">ObjenesisCglibAopProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nf">JdkDynamicAopProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Spring AOP 源码分析：获得通知</title><link>https://www.diguage.com/post/spring-aop-get-advices/</link><pubDate>Sun, 09 Aug 2020 15:46:50 +0800</pubDate><guid>https://www.diguage.com/post/spring-aop-get-advices/</guid><description>&lt;div class="paragraph">
&lt;p>在文章 &lt;a href="https://www.diguage.com/post/spring-aop-process-overview/" target="_blank" rel="noopener">Spring AOP 处理流程概述&lt;/a> 中，对 Spring AOP 有了一个整体认识。在文章 &lt;a href="https://www.diguage.com/post/spring-aop-bootstrap/">Spring AOP 源码分析：入门&lt;/a> 中，对 Spring AOP 的相关入口做了分析。这篇文章就带大家看一看，Spring AOP 是如何获取通知的？&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="example-code">示例代码&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/how-to-read-spring-sources/#example-code" target="_blank" rel="noopener">如何阅读 Spring 源码？: 示例代码&lt;/a> 中，已经给出了一个完整的 AOP 示例代码。为了节省篇幅，请直接参考那篇文章的示例代码，这里就不在赘述。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_注册_advice通知增强">注册 Advice(通知/增强)&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>请根据 &lt;a href="https://www.diguage.com/post/spring-aop-bootstrap/">Spring AOP 源码分析：入门&lt;/a> 中提到的关键方法入口处，打上断点，开始调试。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>首先，需要明确一点的是：对于切面（使用 &lt;code>@Aspect&lt;/code> 注解标注过的类）在 Spring 容器中，也是被统一f封装为 &lt;code>BeanDefinition&lt;/code> 实例的，也需要通过一个方式，将其注册到 Spring 容器中。比如，就像 &lt;a href="#example-code">示例代码&lt;/a> 那样，通过 &lt;code>ImportSelector&lt;/code> 方式，使用类名，将其注册到容器中。这样，就可以利用 Spring 容器对 Bean 的 API 来统一处理了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Advice(通知/增强)几乎是在意想不到的地方完成注册的：在第一次调用 &lt;code>AbstractAutoProxyCreator#postProcessBeforeInstantiation&lt;/code> 方法时，通过 &lt;code>AspectJAwareAdvisorAutoProxyCreator#shouldSkip&lt;/code> 方法，完成了切面的注册。下面，我们对这个过程抽丝剥茧，逐步分析。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>先来看看 &lt;code>findCandidateAdvisors&lt;/code> 方法：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="nd">@Override&lt;/span>
&lt;span class="kd">protected&lt;/span> &lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Advisor&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nf">findCandidateAdvisors&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// Add all the Spring advisors found according to superclass rules.&lt;/span>
 &lt;span class="c1">//当使用注解方式配置AOP的时候并不是丢弃了对XML配置的支持&lt;/span>
 &lt;span class="c1">//在这里调用父类方法加载配置文件中的AOP声明&lt;/span>
 &lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Advisor&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">advisors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kd">super&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">findCandidateAdvisors&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="c1">// Build Advisors for all AspectJ aspects in the bean factory.&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">aspectJAdvisorsBuilder&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">advisors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">addAll&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">aspectJAdvisorsBuilder&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">buildAspectJAdvisors&lt;/span>&lt;span class="o">());&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">advisors&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>如何阅读 Spring 源码？</title><link>https://www.diguage.com/post/how-to-read-spring-sources/</link><pubDate>Sat, 08 Aug 2020 07:53:32 +0800</pubDate><guid>https://www.diguage.com/post/how-to-read-spring-sources/</guid><description>&lt;div class="paragraph">
&lt;p>昨晚原计划给几个朋友简单介绍一下阅读 Spring 源码的方法。结果，大家因为各种原因没能及时参加。后来，就取消分享了。干脆写一篇文章出来，感兴趣欢迎自取。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_代码准备">代码准备&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Spring Framework 是开源的，代码托管在 GitHub 上： &lt;a href="https://github.com/spring-projects/spring-framework" target="_blank" rel="noopener">Spring Framework&lt;/a>。任何人都可以方便地获得它的源代码。所以，如果想阅读 Spring 的源代码，当然是直接把代码克隆到本地，然后直接在 IDE（推荐 IDEA）中进行调试了。另外，还需要存放自己写一些测试和文档。所以，最好把代码 fork 到自己的账户下，从 &lt;code>master&lt;/code> 上切出一个新分支并 push 到自己的 Repo 中，这样自己就可以随意更新了。具体步骤如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>克隆代码&lt;/p>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># 直接克隆原始仓库为 origin&lt;/span>
git clone git@github.com:spring-projects/spring-framework.git&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>fork 代码，D瓜哥直接 fork 到自己账户下了： &lt;a href="https://github.com/diguage/spring-framework" target="_blank" rel="noopener">diguage/spring-framework&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>添加原创仓库地址：&lt;/p>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># 添加自己仓库为 diguage&lt;/span>
&lt;span class="c"># 这样就能在所有项目中保持命名的一致性，方便标识&lt;/span>
git remote add diguage git@github.com:diguage/spring-framework.git&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>创建新分支&lt;/p>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># 创建新分支&lt;/span>
git switch &lt;span class="nt">-c&lt;/span> analysis

&lt;span class="c"># 将新分支 push 到自己的 Repo 中&lt;/span>
git push diguage analysis&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这样，在这个新分支上，就可以随意折腾了。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>下载依赖&lt;/p>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="bash">&lt;span class="c"># Mac or Linux&lt;/span>
./gradlew clean &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> ./gradlew :spring-oxm:compileTestJava &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> ./gradlew &lt;span class="nb">test&lt;/span>

&lt;span class="c"># Windows&lt;/span>
gradlew.bat clean &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> gradlew.bat :spring-oxm:compileTestJava &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> gradlew.bat &lt;span class="nb">test&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Spring AOP 源码分析：入门</title><link>https://www.diguage.com/post/spring-aop-bootstrap/</link><pubDate>Fri, 07 Aug 2020 00:00:00 +0800</pubDate><guid>https://www.diguage.com/post/spring-aop-bootstrap/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/spring-aop-process-overview/" target="_blank" rel="noopener">Spring AOP 处理流程概述&lt;/a> 中，对 Spring AOP 有了一个整体认识。这篇文章就带大家做一个细致的源码分析。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_登堂入室">登堂入室&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>使用 Spring AOP 也很简单，只需要在配置类上加上 &lt;code>@EnableAspectJAutoProxy&lt;/code> 注解即可。这个注解处理过程与 &lt;a href="https://www.diguage.com/post/spring-extensions-and-mybatis/#mapper-scan" target="_blank" rel="noopener">Spring 扩展点实践：整合 MyBATIS&lt;/a> 中 “&lt;code>@MapperScan&lt;/code> 处理” 类似，不同的是，Spring AOP 注册了 &lt;code>AnnotationAwareAspectJAutoProxyCreator&lt;/code>，它是一个 &lt;code>InstantiationAwareBeanPostProcessor&lt;/code>。具体的类图如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/spring-framework/AnnotationAwareAspectJAutoProxyCreator.svg" alt="AnnotationAwareAspectJAutoProxyCreator 的继承体系" width="98%"/>
&lt;/div>
&lt;div class="title">图 1. AnnotationAwareAspectJAutoProxyCreator 的继承体系&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在正式开始源码分析之前，有一点必须强调一下：&lt;strong>Spring AOP 只是借用了 AspectJ 的一些注解和个别关键 API，而整体实现是 Spring 自己完成的，并不是基于 AspectJ 实现的。&lt;/strong>这一点跟很多人的认识是不一样的，需要特别指出。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥在 &lt;a href="https://www.diguage.com/post/spring-bean-lifecycle-overview/" target="_blank" rel="noopener">Spring Bean 生命周期概述&lt;/a> 中指出：创建 AOP 代理对象，有两个时机：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>调用 &lt;code>InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation&lt;/code> 时，通过调用 &lt;code>AnnotationAwareAspectJAutoProxyCreator&lt;/code> 对象的 &lt;code>postProcessBeforeInstantiation&lt;/code> 方法来创建对象；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>调用 &lt;code>BeanPostProcessor#postProcessAfterInitialization&lt;/code> 时，通过调用 &lt;code>AnnotationAwareAspectJAutoProxyCreator&lt;/code> 对象的 &lt;code>postProcessAfterInitialization&lt;/code> 方法来创建对象；&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面分别对这两个方法做更详细的介绍。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_annotationawareaspectjautoproxycreatorpostprocessbeforeinstantiation">&lt;code>AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiation&lt;/code>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>&lt;code>AnnotationAwareAspectJAutoProxyCreator&lt;/code> 的 &lt;code>postProcessBeforeInstantiation&lt;/code> 方法是从 &lt;code>AbstractAutoProxyCreator&lt;/code> 继承过来的。代码如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="nd">@Override&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="nc">Object&lt;/span> &lt;span class="nf">postProcessBeforeInstantiation&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">beanClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nc">String&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 1、得到一个缓存的唯一key（根据beanClass和beanName生成唯一key）&lt;/span>
 &lt;span class="nc">Object&lt;/span> &lt;span class="n">cacheKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">getCacheKey&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="c1">// 2、如果当前targetSourcedBeans（通过自定义TargetSourceCreator创建的TargetSource）不包含cacheKey&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(!&lt;/span>&lt;span class="nc">StringUtils&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">hasLength&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">targetSourcedBeans&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">contains&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 2.1、advisedBeans（已经被增强的Bean，即AOP代理对象）中包含当前cacheKey，返回null，即走Spring默认流程&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">advisedBeans&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">containsKey&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">cacheKey&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="c1">// 2.2、如果是基础设施类（如Advisor、Advice、AopInfrastructureBean的实现）不进行处理&lt;/span>
 &lt;span class="c1">// 2.2、shouldSkip 默认false，可以生成子类覆盖，如AspectJAwareAdvisorAutoProxyCreator覆盖（if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) return true; 即如果是自己就跳过）&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">isInfrastructureClass&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanClass&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">shouldSkip&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">advisedBeans&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">cacheKey&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nc">Boolean&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">FALSE&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="c1">// Create proxy here if we have a custom TargetSource.&lt;/span>
 &lt;span class="c1">// Suppresses unnecessary default instantiation of the target bean:&lt;/span>
 &lt;span class="c1">// The TargetSource will handle target instances in a custom fashion.&lt;/span>
 &lt;span class="c1">// 3、开始创建AOP代理对象&lt;/span>
 &lt;span class="c1">// 3.1、配置自定义的TargetSourceCreator进行TargetSource创建&lt;/span>
 &lt;span class="nc">TargetSource&lt;/span> &lt;span class="n">targetSource&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">getCustomTargetSource&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="c1">// 3.2、如果targetSource不为null 添加到targetSourcedBeans缓存，并创建AOP代理对象&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">targetSource&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="nc">StringUtils&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">hasLength&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">targetSourcedBeans&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="c1">// specificInterceptors即增强（包括前置增强、后置增强等等）&lt;/span>
&lt;span class="hll"> &lt;span class="nc">Object&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">specificInterceptors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">getAdvicesAndAdvisorsForBean&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">targetSource&lt;/span>&lt;span class="o">);&lt;/span>
&lt;/span> &lt;span class="c1">//3.3、创建代理对象&lt;/span>
&lt;span class="hll"> &lt;span class="nc">Object&lt;/span> &lt;span class="n">proxy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">createProxy&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanClass&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">specificInterceptors&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">targetSource&lt;/span>&lt;span class="o">);&lt;/span>
&lt;/span> &lt;span class="c1">//3.4、将代理类型放入proxyTypes从而允许后续的predictBeanType()调用获取&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">proxyTypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">cacheKey&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">proxy&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getClass&lt;/span>&lt;span class="o">());&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">proxy&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="k">return&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>TCP 三次握手和四次挥手</title><link>https://www.diguage.com/post/tcp-3-way-handshake-and-4-way-handshake/</link><pubDate>Mon, 03 Aug 2020 00:57:26 +0800</pubDate><guid>https://www.diguage.com/post/tcp-3-way-handshake-and-4-way-handshake/</guid><description>&lt;div class="paragraph">
&lt;p>传输控制协议（英语：Transmission Control Protocol，缩写：TCP）是一种面向连接的、可靠的、基于字节流的传输层通信协议，由 IETF 的 RFC 793 定义。在简化的计算机网络 OSI 模型中，它完成第四层传输层所指定的功能。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>毫不夸张地说，TCP 协议是目前整个互联网的基础。它解决了一系列的网络问题。带来的结果，就是协议本身非常复杂。考虑到文章篇幅问题，本文着重说明 TCP 建立连接时的三次握手过程和关闭连接时的四次挥手过程。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_三次握手">三次握手&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/tcp/tcp-connection-made-three-way-handshake.png" alt="TCP 三次握手" width="95%"/>
&lt;/div>
&lt;div class="title">图 1. TCP 三次握手&lt;/div>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>第一次握手(&lt;code>SYN=1&lt;/code>, &lt;code>seq=x&lt;/code>):&lt;/p>
&lt;div class="paragraph">
&lt;p>客户端发送一个 TCP 的 &lt;code>SYN&lt;/code> 标志位置 &lt;code>1&lt;/code> 的包，指明客户端打算连接的服务器的端口，以及初始序号 &lt;code>x&lt;/code>，保存在包头的序列号(&lt;code>Sequence Number&lt;/code>)字段里。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>发送完毕后，客户端进入 &lt;code>SYN_SEND&lt;/code> 状态。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>第二次握手(&lt;code>SYN=1&lt;/code>、&lt;code>seq=y&lt;/code>；&lt;code>ACK=1&lt;/code>、&lt;code>ACKnum=x+1&lt;/code>):&lt;/p>
&lt;div class="paragraph">
&lt;p>服务器发回确认包(&lt;code>ACK&lt;/code>)应答。即 &lt;code>SYN&lt;/code> 标志位和 &lt;code>ACK&lt;/code> 标志位均为 &lt;code>1&lt;/code>。服务器端选择自己 &lt;code>ISN&lt;/code> 序列号，放到包头的序列号(&lt;code>Sequence Number&lt;/code>)字段里，同时将确认序号(&lt;code>Acknowledgement Number&lt;/code>)设置为客户的 &lt;code>ISN&lt;/code> 加 &lt;code>1&lt;/code>，即 &lt;code>x+1&lt;/code>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>发送完毕后，服务器端进入 &lt;code>SYN_RCVD&lt;/code> 状态。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>第三次握手(&lt;code>ACK=1&lt;/code>，&lt;code>ACKnum=y+1&lt;/code>)&lt;/p>
&lt;div class="paragraph">
&lt;p>客户端再次发送确认包(&lt;code>ACK&lt;/code>)，SYN 标志位为 &lt;code>0&lt;/code>，&lt;code>ACK&lt;/code> 标志位为 &lt;code>1&lt;/code>，并且把服务器发来 &lt;code>ISN&lt;/code> 的序号字段+1，放在确定字段中发送给对方，即数据段放写 &lt;code>y+1&lt;/code>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>发送完毕后，客户端进入 &lt;code>ESTABLISHED&lt;/code> 状态，当服务器端接收到这个包时，也进入 &lt;code>ESTABLISHED&lt;/code> 状态，TCP 握手结束。&lt;/p>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_syn_flood_攻击">SYN Flood 攻击&lt;/h3>
&lt;div class="paragraph">
&lt;p>在三次握手过程中，服务器发送 SYN-ACK 之后，收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 &lt;code>SYN_RCVD&lt;/code> 状态。当收到 ACK 后，服务器才能转入 &lt;code>ESTABLISHED&lt;/code> 状态.&lt;/p>
&lt;/div></description></item><item><title>HikariCP 源码分析 -- FastList</title><link>https://www.diguage.com/post/hikari-cp-source-analysis-fast-list/</link><pubDate>Sun, 02 Aug 2020 15:06:02 +0800</pubDate><guid>https://www.diguage.com/post/hikari-cp-source-analysis-fast-list/</guid><description>&lt;div class="paragraph">
&lt;p>在前面的文章 &lt;a href="https://www.diguage.com/post/hikari-cp-source-analysis-concurrent-bag/">HikariCP 源码分析 — ConcurrentBag&lt;/a> 中，D瓜哥分析了一下 HikariCP 中一个非常重要的数据结构 &lt;code>ConcurrentBag&lt;/code>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>今天，继续再介绍 HikariCP 中另一个很关键的数据结构： &lt;code>FastList&lt;/code>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;code>FastList&lt;/code> 本身的实现非常简单，要理解它的奥秘，就需要结合 Java 原生集合类的 &lt;code>ArrayList&lt;/code> 来比较性地看。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_构造函数">构造函数&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>先来对比一下两者的构造函数。先来看看 &lt;code>FastList&lt;/code>：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>FastList&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="kd">public&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">FastList&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="no">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="kd">implements&lt;/span> &lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="no">T&lt;/span>&lt;span class="o">&amp;gt;,&lt;/span> &lt;span class="nc">RandomAccess&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nc">Serializable&lt;/span>
&lt;span class="o">{&lt;/span>
 &lt;span class="kd">private&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">serialVersionUID&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">4598088075242913858L&lt;/span>&lt;span class="o">;&lt;/span>

 &lt;span class="kd">private&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">clazz&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="kd">private&lt;/span> &lt;span class="no">T&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">elementData&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="o">;&lt;/span>

 &lt;span class="cm">/**
 * Construct a FastList with a default size of 32.
 * @param clazz the Class stored in the collection
 */&lt;/span>
 &lt;span class="nd">@SuppressWarnings&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;unchecked&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="nf">FastList&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">clazz&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="o">{&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">elementData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="no">T&lt;/span>&lt;span class="o">[])&lt;/span> &lt;span class="nc">Array&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">newInstance&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">clazz&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="mi">32&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">clazz&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">clazz&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="cm">/**
 * Construct a FastList with a specified size.
 * @param clazz the Class stored in the collection
 * @param capacity the initial size of the FastList
 */&lt;/span>
 &lt;span class="nd">@SuppressWarnings&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;unchecked&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="nf">FastList&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">clazz&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">capacity&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="o">{&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">elementData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="no">T&lt;/span>&lt;span class="o">[])&lt;/span> &lt;span class="nc">Array&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">newInstance&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">clazz&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">capacity&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">clazz&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">clazz&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>使用 Hugo 搭建博客</title><link>https://www.diguage.com/post/building-blog-with-hugo/</link><pubDate>Sat, 01 Aug 2020 16:09:43 +0800</pubDate><guid>https://www.diguage.com/post/building-blog-with-hugo/</guid><description>&lt;div class="paragraph">
&lt;p>一个朋友对D瓜哥的博客感兴趣，觉得很好玩。问我怎么玩，D瓜哥也懒得手把手教了，干脆写篇文章来做个说明吧。也许对其他朋友也有所帮助。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_潮起潮落">潮起潮落&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>D瓜哥早在 2012年就使用 WordPress 搭建了一个博客： &lt;a href="https://wordpress.diguage.com/" target="_blank" rel="noopener">&amp;#34;地瓜哥&amp;#34;博客网—分享技术带来的喜悦&lt;/a>，WordPress 不亏是全世界最流行的开源 CMS 系统，各种插件可以满足非常对的需求。当年D瓜哥还想研究一下它的源代码，可惜对 PHP（对，就是那个拍黄片的）无感，没有坚持下去。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>但是，在熟悉了 MarkDown、 &lt;a href="https://asciidoctor.org/" target="_blank" rel="noopener">Asciidoctor&lt;/a>（D瓜哥是 AsciiDoctor 的死忠粉，坚决支持它的发展） 等轻量级标记语言后，愈发觉得 WordPress 太繁琐：写作一个小时，排版发布一小时。实在是太浪费时间了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>先尝试了一下 &lt;a href="https://antora.org/" target="_blank" rel="noopener">Antora&lt;/a>，之所以选它，是因为它是 AsciiDoctor 的作者发起的项目，对 AsciiDoctor 的支持性非常好。尝试了一段时间后，发现它更适合写产品说明书，更适合像阿里云等这种对外提供产品，需要提供在线产品说明书的情况。不适合做个人博客。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>去年，经过多次测试后（主要测试对 AsciiDoctor 的支持性），发现现在D瓜哥用的这个主题对 AsciiDoctor 支持得不错，随后下定决心切换到了 &lt;a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo&lt;/a>。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_hugo_简介">Hugo 简介&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>关于 Hugo 的介绍，直接转摘维基百科的介绍了：&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>Hugo 是一个用 Go 编写的静态网站生成器，2013由 Steve Francia 原创，自 v0.14 (2015年) 由 Bjørn Erik Pedersen 主力开发，并由全球各地的开发者和用户提交贡献。Hugo 以 Apache License 2.0 许可的开放源代码项目。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Hugo 一般只需几秒钟就能生成一个网站（每页少于 1 毫秒），被称为“世界上最快的网站构建框架”，也使 Hugo 大受欢迎，成为最热门的静态网站生成器之一，被广泛采用。例如，2015年7月，Netlify 推出专为 Hugo 而设的网站托管服务，而2017年，Smashing Magazine 推出重新设计的官方网站，从原来的 WordPress 迁移到基于 Hugo 的 JAMstack 解决方案。&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;div class="attribution">
— https://zh.wikipedia.org/wiki/Hugo_(軟件)
&lt;/div></description></item><item><title>源码剖析 Spring 循环依赖</title><link>https://www.diguage.com/post/spring-circular-dependence/</link><pubDate>Fri, 31 Jul 2020 18:33:30 +0800</pubDate><guid>https://www.diguage.com/post/spring-circular-dependence/</guid><description>&lt;div class="paragraph">
&lt;p>循环依赖在编程中是一个常见问题（当然，这并不是最佳实践）。并且，Spring 如何解决循环依赖这个问题在面试中也经常见。下面，D瓜哥就从源码的层面深入剖析一下这个问题。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_示例程序">示例程序&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>先展示一下示例程序：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="kn">package&lt;/span> &lt;span class="nn">com.diguage.truman.context&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.apache.commons.logging.Log&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.apache.commons.logging.LogFactory&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.junit.jupiter.api.Test&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.beans.factory.annotation.Autowired&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.context.annotation.AnnotationConfigApplicationContext&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.context.annotation.Configuration&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.context.annotation.Import&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.context.annotation.ImportSelector&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.core.type.AnnotationMetadata&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.stereotype.Component&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="cm">/**
 * @author D瓜哥, &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>
 * @since 2020-05-24 13:02
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">CircularDependenceSingletonTest&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="nc">Log&lt;/span> &lt;span class="n">log&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nc">LogFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getLog&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">CircularDependenceSingletonTest&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="nd">@Test&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">test&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">AnnotationConfigApplicationContext&lt;/span> &lt;span class="n">applicationContext&lt;/span>
 &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">AnnotationConfigApplicationContext&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="n">applicationContext&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">register&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">Config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">applicationContext&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">refresh&lt;/span>&lt;span class="o">();&lt;/span>

 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">applicationContext&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getBean&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="no">A&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">));&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">applicationContext&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getBean&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="no">B&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">));&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">applicationContext&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getBean&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="no">C&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">));&lt;/span>

 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;-A--------&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="no">A&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">applicationContext&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getBean&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="no">A&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">b&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;-B--------&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="no">B&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">applicationContext&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getBean&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="no">B&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">c&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;-C--------&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="no">C&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">applicationContext&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getBean&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="no">C&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">a&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="nd">@Configuration&lt;/span>
 &lt;span class="nd">@Import&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">AbcImportSelector&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">Config&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">AbcImportSelector&lt;/span> &lt;span class="kd">implements&lt;/span> &lt;span class="nc">ImportSelector&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nd">@Override&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="nc">String&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="nf">selectImports&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">AnnotationMetadata&lt;/span> &lt;span class="n">importingClassMetadata&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">String&lt;/span>&lt;span class="o">[]{&lt;/span>
 &lt;span class="no">A&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="o">(),&lt;/span>
 &lt;span class="no">B&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="o">(),&lt;/span>
 &lt;span class="no">C&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="o">()};&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>


 &lt;span class="nd">@Component&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">A&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nd">@Autowired&lt;/span>
 &lt;span class="no">B&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="nd">@Component&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">B&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nd">@Autowired&lt;/span>
 &lt;span class="no">C&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="nd">@Component&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">C&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nd">@Autowired&lt;/span>
 &lt;span class="no">A&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>上述示例代码中的循环依赖情况如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/spring-framework/circular-dependence.jpg" alt="循环依赖" width="98%"/>
&lt;/div>
&lt;div class="title">图 1. 循环依赖&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_源码剖析">源码剖析&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_三级缓存">三级缓存&lt;/h3>
&lt;div class="paragraph">
&lt;p>D瓜哥在 &lt;a href="https://www.diguage.com/post/dive-into-spring-core-data-structure-bean-factory/#registry">深入剖析 Spring 核心数据结构：BeanFactory&lt;/a> 中，概要性地对 &lt;code>BeanFactory&lt;/code> 的属性做了一一说明。
而其中的“三级缓存”属性，则是解决循环依赖问题的关键所在：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;code>Map&amp;lt;String, Object&amp;gt; singletonObjects = new ConcurrentHashMap&amp;lt;&amp;gt;(256)&lt;/code>：Bean 名称到单例 Bean 的映射，用于存放完全初始化好的 Bean。可以理解成，这就是所谓的容器。这是一级缓存。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>Map&amp;lt;String, Object&amp;gt; earlySingletonObjects = new HashMap&amp;lt;&amp;gt;(16)&lt;/code>：Bean 到“未成熟”单例 Bean 的映射。该 Bean 对象只是被创建出来，但是还没有注入依赖。在容器解决循环依赖时，用于存储中间状态。这是二级缓存。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>Map&amp;lt;String, ObjectFactory&amp;lt;?&amp;gt;&amp;gt; singletonFactories = new HashMap&amp;lt;&amp;gt;(16)&lt;/code>：Bean 名称到 Bean 的 ObjectFactory 对象的映射，存放 Bean 工厂对象。在容器解决循环依赖时，用于存储中间状态。这是三级缓存。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Bean 的获取过程就类似计算机缓存的作用过程：先从一级获取，失败再从二级、三级里面获取。在 &lt;code>org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)&lt;/code> 方法中，可以明确看到整个过程：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(beanName, allowEarlyReference)&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="cm">/**
 * Return the (raw) singleton object registered under the given name.
 * &amp;lt;p&amp;gt;Checks already instantiated singletons and also allows for an early
 * reference to a currently created singleton (resolving a circular reference).
 * @param beanName the name of the bean to look for
 * @param allowEarlyReference whether early references should be created or not
 * @return the registered singleton object, or {@code null} if none found
 */&lt;/span>
&lt;span class="nd">@Nullable&lt;/span>
&lt;span class="kd">protected&lt;/span> &lt;span class="nc">Object&lt;/span> &lt;span class="nf">getSingleton&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">String&lt;/span> &lt;span class="n">beanName&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="kt">boolean&lt;/span> &lt;span class="n">allowEarlyReference&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">Object&lt;/span> &lt;span class="n">singletonObject&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">singletonObjects&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">singletonObject&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">isSingletonCurrentlyInCreation&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">))&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kd">synchronized&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">singletonObjects&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">singletonObject&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">earlySingletonObjects&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">singletonObject&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">allowEarlyReference&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">ObjectFactory&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;&lt;/span> &lt;span class="n">singletonFactory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">singletonFactories&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">singletonFactory&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">singletonObject&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">singletonFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getObject&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">earlySingletonObjects&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">put&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">singletonObject&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">this&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">singletonFactories&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">remove&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">beanName&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">singletonObject&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>分布式锁之 Apache Curator InterProcessReadWriteLock</title><link>https://www.diguage.com/post/distributed-lock-apache-curator-interprocessreadwritelock/</link><pubDate>Wed, 22 Jul 2020 10:45:44 +0800</pubDate><guid>https://www.diguage.com/post/distributed-lock-apache-curator-interprocessreadwritelock/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/distributed-lock-apache-curator-interprocessmutex/">分布式锁之 Apache Curator InterProcessMutex&lt;/a> 中介绍了基于 ZooKeeper 实现的互斥锁。除此之外，还可以实现读写锁。这篇文章就来简要介绍一下 &lt;code>InterProcessReadWriteLock&lt;/code> 的实现原理。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>老规矩，先看看类的注释：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="cm">/**
 * &amp;lt;p&amp;gt;
 * A re-entrant read/write mutex that works across JVMs. Uses Zookeeper to hold the lock. All processes
 * in all JVMs that use the same lock path will achieve an inter-process critical section. Further, this mutex is
 * &amp;#34;fair&amp;#34; - each user will get the mutex in the order requested (from ZK&amp;#39;s point of view).
 * &amp;lt;/p&amp;gt;
 *
 * &amp;lt;p&amp;gt;
 * A read write lock maintains a pair of associated locks, one for read-only operations and one
 * for writing. The read lock may be held simultaneously by multiple reader processes, so long as
 * there are no writers. The write lock is exclusive.
 * &amp;lt;/p&amp;gt;
 *
 * &amp;lt;p&amp;gt;
 * &amp;lt;b&amp;gt;Reentrancy&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;
 * This lock allows both readers and writers to reacquire read or write locks in the style of a
 * re-entrant lock. Non-re-entrant readers are not allowed until all write locks held by the
 * writing thread/process have been released. Additionally, a writer can acquire the read lock, but not
 * vice-versa. If a reader tries to acquire the write lock it will never succeed.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;
 *
 * &amp;lt;b&amp;gt;Lock downgrading&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;
 * Re-entrancy also allows downgrading from the write lock to a read lock, by acquiring the write
 * lock, then the read lock and then releasing the write lock. However, upgrading from a read
 * lock to the write lock is not possible.
 * &amp;lt;/p&amp;gt;
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">InterProcessReadWriteLock&lt;/span>
&lt;span class="o">{&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>分布式锁之 Apache Curator InterProcessMutex</title><link>https://www.diguage.com/post/distributed-lock-apache-curator-interprocessmutex/</link><pubDate>Tue, 21 Jul 2020 10:13:21 +0800</pubDate><guid>https://www.diguage.com/post/distributed-lock-apache-curator-interprocessmutex/</guid><description>&lt;div class="paragraph">
&lt;p>对分布式锁耳熟能详。不过，一直关注的是基于 Redis 实现的分布式锁。知道 ZooKeeper 也可以实现分布式锁。但是，原来的想法是把 Redis 那个思路切换到 ZooKeeper 上来实现就好。今天了解到 Apache Curator 内置了分布式锁的实现： &lt;code>InterProcessMutex&lt;/code>。查看了一下源码实现，发现跟基于 Redis 实现的源码相比，在思路上还是有很大不同的。所以，特别作文记录一下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>先来看一下，整体流程：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;span class="image">&lt;img src="https://www.diguage.com/images/distributed-system/InterProcessMutex-process.png" alt="InterProcessMutex process" width="95%"/>&lt;/span>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>结合流程图和源码，加锁的过程是这样的：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>先判断本地是否有锁数据，如果有则对锁定次数自增一下，然后返回 &lt;code>true&lt;/code>；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果没有锁数据，则尝试获取锁：&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>在指定路径下创建临时顺序节点&lt;/p>
&lt;/li>
&lt;li>
&lt;p>获取指定路径下，所有节点，检查自身是否是序号最小的节点：&lt;/p>
&lt;div class="olist lowerroman">
&lt;ol class="lowerroman" type="i">
&lt;li>
&lt;p>如果自身序号最小，则获得锁；否则&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果自身不是序号最小的节点，则通过 &lt;code>while&lt;/code> 自旋 + &lt;code>wait(times)&lt;/code> 不断尝试获取锁，直到成功。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>获得锁后，把锁信息缓存在本地 &lt;code>ConcurrentMap&amp;lt;Thread, LockData&amp;gt; threadData&lt;/code> 变量中，方便计算重入。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 ZooKeeper 中的结构大致如下：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;span class="image">&lt;img src="https://www.diguage.com/images/distributed-system/InterProcessMutex-structure.png" alt="InterProcessMutex structure" width="95%"/>&lt;/span>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面我们逐个方法进行分析说明。先来看一下 &lt;code>InterProcessMutex&lt;/code> 的注释：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="cm">/**
 * A re-entrant mutex that works across JVMs. Uses Zookeeper to hold the lock. All processes in all JVMs that
 * use the same lock path will achieve an inter-process critical section. Further, this mutex is
 * &amp;#34;fair&amp;#34; - each user will get the mutex in the order requested (from ZK&amp;#39;s point of view)
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">InterProcessMutex&lt;/span> &lt;span class="kd">implements&lt;/span> &lt;span class="nc">InterProcessLock&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nc">Revocable&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">InterProcessMutex&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>Spring 扩展点实践：整合 Apache Dubbo（二）</title><link>https://www.diguage.com/post/spring-extensions-and-dubbo-2/</link><pubDate>Sat, 11 Jul 2020 16:20:00 +0800</pubDate><guid>https://www.diguage.com/post/spring-extensions-and-dubbo-2/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/spring-extensions-and-dubbo-1/" target="_blank" rel="noopener">Spring 扩展点实践：整合 Apache Dubbo（一）&lt;/a> 中，D瓜哥介绍了 Dubbo 如何使用 Spring 的插件机制与 Spring 整合。限于篇幅原因，上一篇文章只介绍到了服务提供者的注册。本篇文章继续上一篇文章的主题，继续介绍 Spring 与 Dubbo 的整合过程。先来讲解一下服务消费者的生成过程。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_dubbo_生成服务消费者的过程">Dubbo 生成服务消费者的过程&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>先来看看 XML 配置文件：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/resources/spring/dubbo-consumer.xml&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="xml">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;beans&lt;/span> &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
 &lt;span class="na">xmlns:dubbo=&lt;/span>&lt;span class="s">&amp;#34;http://dubbo.apache.org/schema/dubbo&amp;#34;&lt;/span>
 &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://www.springframework.org/schema/beans&amp;#34;&lt;/span>
 &lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd
 http://dubbo.apache.org/schema/dubbo
 http://dubbo.apache.org/schema/dubbo/dubbo.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>

 &lt;span class="nt">&amp;lt;dubbo:application&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;demo-consumer&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>

 &lt;span class="nt">&amp;lt;dubbo:registry&lt;/span> &lt;span class="na">address=&lt;/span>&lt;span class="s">&amp;#34;zookeeper://127.0.0.1:2181&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>

 &lt;span class="nt">&amp;lt;dubbo:reference&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;demoService&amp;#34;&lt;/span> &lt;span class="na">check=&lt;/span>&lt;span class="s">&amp;#34;false&amp;#34;&lt;/span> &lt;span class="na">interface=&lt;/span>&lt;span class="s">&amp;#34;org.apache.dubbo.demo.DemoService&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>

&lt;span class="nt">&amp;lt;/beans&amp;gt;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我们先看一下 &lt;code>ReferenceBean&lt;/code> 类的声明：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">&lt;code>org.apache.dubbo.config.spring.ReferenceBean&lt;/code>&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">ReferenceBean&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="no">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="kd">extends&lt;/span> &lt;span class="nc">ReferenceConfig&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="no">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="kd">implements&lt;/span> &lt;span class="nc">FactoryBean&lt;/span>&lt;span class="o">,&lt;/span>
 &lt;span class="nc">ApplicationContextAware&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nc">InitializingBean&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nc">DisposableBean&lt;/span> &lt;span class="o">{&lt;/span>

 &lt;span class="c1">// 此处省略 N 行代码&lt;/span>

 &lt;span class="nd">@Override&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="nc">Object&lt;/span> &lt;span class="nf">getObject&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="nf">get&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="c1">// 此处省略 N 行代码&lt;/span>

 &lt;span class="nd">@Override&lt;/span>
 &lt;span class="nd">@SuppressWarnings&lt;/span>&lt;span class="o">({&lt;/span>&lt;span class="s">&amp;#34;unchecked&amp;#34;&lt;/span>&lt;span class="o">})&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">afterPropertiesSet&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="kd">throws&lt;/span> &lt;span class="nc">Exception&lt;/span> &lt;span class="o">{&lt;/span>

 &lt;span class="c1">// Initializes Dubbo&amp;#39;s Config Beans before @Reference bean autowiring&lt;/span>
 &lt;span class="n">prepareDubboConfigBeans&lt;/span>&lt;span class="o">();&lt;/span>

 &lt;span class="c1">// lazy init by default.&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">init&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">init&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="c1">// eager init if necessary.&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">shouldInit&lt;/span>&lt;span class="o">())&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">getObject&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="c1">// 此处省略 N 行代码&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这个类实现了 &lt;code>FactoryBean&lt;/code> 接口，D瓜哥在 &lt;a href="https://www.diguage.com/post/spring-extensions-overview/#factory-bean">Spring 扩展点概览及实践：FactoryBean&lt;/a> 中对 &lt;code>FactoryBean&lt;/code> 介绍。所以，请在上面的 &lt;code>getObject()&lt;/code> 打个断点。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>另外，这个类还实现了 &lt;code>InitializingBean&lt;/code>，D瓜哥在 &lt;a href="https://www.diguage.com/post/spring-bean-lifecycle-overview/">Spring Bean 生命周期概述&lt;/a> 中介绍了这个接口的用途。不了解的，请移步。&lt;/p>
&lt;/div></description></item><item><title>Spring 扩展点实践：整合 Apache Dubbo（一）</title><link>https://www.diguage.com/post/spring-extensions-and-dubbo-1/</link><pubDate>Thu, 09 Jul 2020 16:28:35 +0800</pubDate><guid>https://www.diguage.com/post/spring-extensions-and-dubbo-1/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/spring-extensions-overview/" target="_blank" rel="noopener">Spring 扩展点概览及实践&lt;/a> 中介绍了 Spring 内部存在的扩展点。 &lt;a href="https://www.diguage.com/post/spring-extensions-and-mybatis/" target="_blank" rel="noopener">Spring 扩展点实践：整合 MyBATIS&lt;/a> 中，D瓜哥带大家了解了一下 MyBATIS 如何利用 Spring 的扩展点实现了与 Spring 的完美整合。现在，学以致用，我们继续来分析一下 Spring 与 Apache Dubbo 的整合流程。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_示例程序">示例程序&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Apache Dubbo 仓库中就有很完整的示例。D瓜哥直接拿来使用就不再搭建示例程序了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>首先，需要启动一个 ZooKeeper 实例。查看 Dubbo 的依赖可以看出，最新版代码依赖的 ZooKeeper 是 3.4.13 版。所以，为了最好的兼容性，就要选用 3.4.X 版的 ZooKeeper 服务器。D瓜哥直接使用 Docker 启动 ZooKeeper 了。命令如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="bash">docker run &lt;span class="nt">--rm&lt;/span> &lt;span class="nt">--name&lt;/span> zookeeper &lt;span class="nt">-d&lt;/span> &lt;span class="nt">-p&lt;/span> 2181:2181 zookeeper:3.4.14&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这次我们使用 &lt;a href="https://github.com/apache/dubbo" target="_blank" rel="noopener">Apache Dubbo&lt;/a> 的 &lt;code>dubbo-demo/dubbo-demo-xml&lt;/code> 示例。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第二步，启动服务提供者程序，找到 &lt;code>DUBBO/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/java/org/apache/dubbo/demo/provider/Application.java&lt;/code>，运行该类。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第三步，运行服务消费者程序，找到 &lt;code>DUBBO/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/java/org/apache/dubbo/demo/consumer/Application.java&lt;/code>，运行该类。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果没有任何错误，则在终端可以看到 &lt;code>result: async result&lt;/code> 输出。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在开始正餐之前，D瓜哥先给大家来个开胃菜。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="spring-plugin">Spring 插件机制简介&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>不知道大家有没有想过一个问题：Spring 框架是如何支持越来越多的功能的？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在D瓜哥了解到 Spring 的插件机制后，非常叹服 Spring 精巧的设计和灵活的扩展性。闲言少叙，好戏上演。&lt;/p>
&lt;/div></description></item><item><title>题解：538.把二叉搜索树转换为累加树</title><link>https://www.diguage.com/post/convert-bst-to-greater-tree/</link><pubDate>Fri, 03 Jul 2020 14:56:24 +0800</pubDate><guid>https://www.diguage.com/post/convert-bst-to-greater-tree/</guid><description>&lt;div class="sect1">
&lt;h2 id="_题目描述">题目描述&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>题目是 LeetCode 的第 538 题： &lt;a href="https://leetcode.com/problems/convert-bst-to-greater-tree/description/" target="_blank" rel="noopener">把二叉搜索树转换为累加树&lt;/a>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>给出二叉搜索树的根节点，该树的节点值各不相同，请你将其转换为累加树（Greater Sum Tree），使每个节点 &lt;code>node&lt;/code> 的新值等于原树中大于或等于 &lt;code>node.val&lt;/code> 的值之和。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>提醒一下，二叉搜索树满足下列约束条件：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>节点的左子树仅包含键 小于 节点键的节点。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>节点的右子树仅包含键 大于 节点键的节点。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>左右子树也必须是二叉搜索树。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_思路分析">思路分析&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>一棵二叉搜索树，如果是中根遍历，那么，其序列正好是一个升序序列。但，题目要求的是大于等于自身的节点值之和。正常的中根遍历是“左中右”，反过来“右中左”，就是降序序列，累计求和就符合题目要求了。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_解题代码">解题代码&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>中序遍历，可以用栈，也可以使用 Morris 遍历。在 &lt;a href="https://www.diguage.com/post/find-two-error-node-in-binary-search-tree/" target="_blank" rel="noopener">题解：找到二叉搜索树中两个错误的节点&lt;/a> 中就是用了 Morris 遍历。网上也很少有反向的 Morris 中根遍历。正好练习一下。&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * 基于 Morris 的倒序中根遍历
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="nc">TreeNode&lt;/span> &lt;span class="nf">convertBST&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">root&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">sum&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="c1">// 反向 Morris&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">cur&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">root&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">mostLeft&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">cur&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 向右转&lt;/span>
 &lt;span class="n">mostLeft&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cur&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostLeft&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 寻找最左边的节点&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostLeft&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">mostLeft&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">cur&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">mostLeft&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mostLeft&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostLeft&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 第一次访问，将最左节点的左子树指向当前节点&lt;/span>
 &lt;span class="n">mostLeft&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cur&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="n">cur&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cur&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">continue&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="c1">// 第二次访问，掐断中间建立的连接&lt;/span>
 &lt;span class="n">mostLeft&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="c1">// 计算累加和&lt;/span>
 &lt;span class="n">sum&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">cur&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">val&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="n">cur&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sum&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="n">cur&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cur&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">root&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>得益于 Morris 的优秀本质，这个解法的时间复杂度是： O(n)，空间复杂度是： O(1)。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>关于 Morris 树遍历的更多介绍，请看： &lt;a href="https://www.diguage.com/post/morris-tree-traversal/" target="_blank" rel="noopener">神奇的 Morris 树遍历&lt;/a>。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div></description></item><item><title>Redis 核心数据结构（二）</title><link>https://www.diguage.com/post/redis-core-data-structure-2/</link><pubDate>Fri, 03 Jul 2020 00:39:43 +0800</pubDate><guid>https://www.diguage.com/post/redis-core-data-structure-2/</guid><description>&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>本文内容对于 Redis 7+ 来说已经过时，最新实现请看下面两篇文章：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/redis-core-data-structure-3/">Redis 核心数据结构（3）&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/redis-core-data-structure-4/">Redis 核心数据结构（4）&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在上一篇文章： &lt;a href="https://www.diguage.com/post/redis-core-data-structure-1/">Redis 核心数据结构（1）&lt;/a> 中，介绍了链表、ziplist、quicklist 数据结构。这篇文章，来介绍一下 skiplist、dict。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="skiplist">skiplist&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>跳跃表是一种有序数据结构，支持平均 O(logN)、最坏 O(N) 复杂度的节点查找；大部分情况效率可以和平衡树相媲美，实现却比平衡树简单。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>跳跃表就是 Redis 中有序集合键的底层实现之一。&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">server.h&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="c">&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">zskiplistNode&lt;/span> &lt;span class="p">{&lt;/span>
 &lt;span class="n">sds&lt;/span> &lt;span class="n">ele&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="kt">double&lt;/span> &lt;span class="n">score&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="k">struct&lt;/span> &lt;span class="n">zskiplistNode&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">backward&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="k">struct&lt;/span> &lt;span class="n">zskiplistLevel&lt;/span> &lt;span class="p">{&lt;/span>
 &lt;span class="k">struct&lt;/span> &lt;span class="n">zskiplistNode&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">forward&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">span&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="p">}&lt;/span> &lt;span class="n">level&lt;/span>&lt;span class="p">[];&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">zskiplistNode&lt;/span>&lt;span class="p">;&lt;/span>

&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">zskiplist&lt;/span> &lt;span class="p">{&lt;/span>
 &lt;span class="k">struct&lt;/span> &lt;span class="n">zskiplistNode&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">header&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">tail&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">length&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">level&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">zskiplist&lt;/span>&lt;span class="p">;&lt;/span>

&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">zset&lt;/span> &lt;span class="p">{&lt;/span>
 &lt;span class="n">dict&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">dict&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="n">zskiplist&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">zsl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">zset&lt;/span>&lt;span class="p">;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>skiplist，顾名思义，首先它是一个list。实际上，它是在有序链表的基础上发展起来的。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/redis/skiplist.png" alt="skiplist" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>当我们想查找数据的时候，可以先沿着跨度大的链进行查找。当碰到比待查数据大的节点时，再回到跨度小的链表中进行查找。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>skiplist正是受这种多层链表的想法的启发而设计出来的。按照上面生成链表的方式，上面每一层链表的节点个数，是下面一层的节点个数的一半，这样查找过程就非常类似于一个二分查找，使得查找的时间复杂度可以降低到 O(logN)。但是，存在的一个问题是：如果插入新节点后就会打乱上下相邻两层节点是 2:1 的对应关系。如果要维持，则需要调整后面所有的节点。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>skiplist为了避免这一问题，它不要求上下相邻两层链表之间的节点个数有严格的对应关系，而是为每个节点随机出一个层数(level)。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/redis/redis-skiplist-insertions.png" alt="redis skiplist insertions" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>插入操作只需要修改插入节点前后的指针，而不需要对很多节点都进行调整。这就降低了插入操作的复杂度。实际上，这是 skiplist 的一个很重要的特性，这让它在插入性能上明显优于平衡树的方案。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>skiplist，翻译成中文，可以翻译成“跳表”或“跳跃表”，指的就是除了最下面第1层链表之外，它会产生若干层稀疏的链表，这些链表里面的指针故意跳过了一些节点（而且越高层的链表跳过的节点越多）。这就使得我们在查找数据的时候能够先在高层的链表中进行查找，然后逐层降低，最终降到第1层链表来精确地确定数据位置。在这个过程中，我们跳过了一些节点，从而也就加快了查找速度。&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>在中间插入一个有比较高 Level 的节点，如何维护前面节点到这个节点的这些链接？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在平衡树种，如何做范围查找？先确定边界，然后其他节点怎么查找？&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/redis/redis_skiplist_example.png" alt="redis skiplist example" width="95%"/>
&lt;/div>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>skiplist 中 key 允许重复。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在比较时，不仅比较分数（即key），还要比较数据自身。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第一层链表是双向链表，并且反向指针只有一个。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在 skiplist 中可以很方便计算每个元素的排名。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Redis 中的有序集合（sorted set），是在 skiplist, dict 和 ziplist 基础上构建起来的:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>当数据较少时，sorted set是由一个 ziplist 来实现的。其中集合元素按照分值从小到大排序。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>当数据多的时候，sorted set 是由一个叫 zset 的数据结构来实现的，这个 zset 包含一个 dict + 一个 skiplist。dict 用来查询数据到分数(score)的对应关系，而 skiplist 用来根据分数查询数据（可能是范围查找）。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Redis 核心数据结构（一）</title><link>https://www.diguage.com/post/redis-core-data-structure-1/</link><pubDate>Thu, 02 Jul 2020 10:13:16 +0800</pubDate><guid>https://www.diguage.com/post/redis-core-data-structure-1/</guid><description>&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>本文内容对于 Redis 7+ 来说已经过时，最新实现请看下面两篇文章：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/redis-core-data-structure-3/">Redis 核心数据结构（3）&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://www.diguage.com/post/redis-core-data-structure-4/">Redis 核心数据结构（4）&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Redis 目前是使用最广泛的缓存中间件。其突出特点就是支持多种常见的数据结构。对比 JDK 集合类的实现，Redis 的实现表现出很多独到之处，很多地方设计得别具匠心。下面就来简要介绍一下。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_linkedlist">linkedlist&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Redis 底层也有很多地方使用到 linkedlist，并且也是双向链表。&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">adlist.h&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="c">&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">listNode&lt;/span> &lt;span class="p">{&lt;/span>
 &lt;span class="k">struct&lt;/span> &lt;span class="n">listNode&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">prev&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="k">struct&lt;/span> &lt;span class="n">listNode&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">next&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">listNode&lt;/span>&lt;span class="p">;&lt;/span>

&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">listIter&lt;/span> &lt;span class="p">{&lt;/span>
 &lt;span class="n">listNode&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">next&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="n">direction&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">listIter&lt;/span>&lt;span class="p">;&lt;/span>

&lt;span class="k">typedef&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="n">list&lt;/span> &lt;span class="p">{&lt;/span>
 &lt;span class="n">listNode&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">head&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="n">listNode&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">tail&lt;/span>&lt;span class="p">;&lt;/span>
 &lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">dup&lt;/span>&lt;span class="p">)(&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">ptr&lt;/span>&lt;span class="p">);&lt;/span>
 &lt;span class="kt">void&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">free&lt;/span>&lt;span class="p">)(&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">ptr&lt;/span>&lt;span class="p">);&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">match&lt;/span>&lt;span class="p">)(&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">ptr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">);&lt;/span>
 &lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">len&lt;/span>&lt;span class="p">;&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="n">list&lt;/span>&lt;span class="p">;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Redis 的 linkedlist 实现特点是：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>双向：节点带有前后指针；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>无环：首尾没有相连，所以没有构成环状；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>链表保存了首尾指针；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>多态：可以保存不同类型的值，这里成为泛型也许更符合 Java 中的语义。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Redis 在 2014 年实现了 &lt;a href="#quicklist">quicklist&lt;/a>，并使用 quicklist 代替了 linkedlist。所以，现在 linkedlist 几乎已经是废弃状态。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_ziplist">ziplist&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Redis 官方在 ziplist.c 文件的注释中对 ziplist 进行了定义：&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>The ziplist is a specially encoded dually linked list that is designed
to be very memory efficient. It stores both strings and integer values,
where integers are encoded as actual integers instead of a series of
characters. It allows push and pop operations on either side of the list
in O(1) time. However, because every operation requires a reallocation of
the memory used by the ziplist, the actual complexity is related to the
amount of memory used by the ziplist.&lt;/p>
&lt;/div></description></item><item><title>Kafka 常见面试题</title><link>https://www.diguage.com/post/kafka-interview-questions/</link><pubDate>Wed, 01 Jul 2020 18:08:51 +0800</pubDate><guid>https://www.diguage.com/post/kafka-interview-questions/</guid><description>&lt;div class="paragraph">
&lt;p>Kafka 是由 LinkedIn 开发的一个分布式的消息系统，使用 Scala 编写，它以可水平扩展和高吞吐率而被广泛使用。Kafka 本身设计也非常精巧，有很多关键的知识点需要注意。在面试中，也常常被问到。整理篇文章，梳理一下自己的知识点。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_架构设计问题">架构设计问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Kafka 整体架构如下：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/kafka/kafka-architecture.png" alt="Kafka 架构" width="98%"/>
&lt;/div>
&lt;div class="title">图 1. Kafka 架构&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Kafka 架构分为以下几个部分&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Producer&lt;/strong>：消息生产者，就是向 Kafka Broker 发消息的客户端。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Consumer&lt;/strong>：消息消费者，向 Kafka Broker 取消息的客户端。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Topic&lt;/strong>：可以理解为一个队列，一个 Topic 又分为一个或多个分区。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Consumer Group&lt;/strong>：这是 Kafka 用来实现一个 Topic 消息的广播（发给所有的 Consumer）和单播（发给任意一个 Consumer）的手段。一个 Topic 可以有多个 Consumer Group。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Broker&lt;/strong>：一台 Kafka 服务器就是一个 Broker。一个集群由多个 Broker 组成。一个 Broker 可以容纳多个 Topic。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Partition&lt;/strong>：为了实现扩展性，一个非常大的 Topic 可以分布到多个 Broker上，每个 Partition 是一个有序的队列。Partition 中的每条消息都会被分配一个有序的id（offset）。将消息发给 Consumer，Kafka 只保证按一个 Partition 中的消息的顺序，不保证一个 Topic 的整体（多个 Partition 间）的顺序。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Offset&lt;/strong>：Kafka 的存储文件都是按照 offset.Kafka 来命名，用 offset 做名字的好处是方便查找。例如你想找位于 2049 的位置，只要找到 2048.Kafka 的文件即可。当然 the first offset 就是 00000000000.Kafka。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>深入剖析 Spring 核心数据结构：BeanFactory</title><link>https://www.diguage.com/post/dive-into-spring-core-data-structure-bean-factory/</link><pubDate>Fri, 26 Jun 2020 15:30:08 +0800</pubDate><guid>https://www.diguage.com/post/dive-into-spring-core-data-structure-bean-factory/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/dive-into-spring-core-data-structure-bean-definition/" target="_blank" rel="noopener">深入剖析 Spring 核心数据结构：BeanDefinition&lt;/a> 中，介绍了 &lt;code>BeanDefinition&lt;/code>。网上很多文章介绍 &lt;code>BeanDefinition&lt;/code> 的 API，D瓜哥却要反其道而行之，从内部属性来分析一下。下面我们开始。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_继承体系">继承体系&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Spring 非常好地遵循了面向对象的设计原则：面向接口编程。不放过任何可以提取出成接口的机会。虽然感觉似乎增加了类的继承关系，增加了一点的复杂度。但是，却带来了非常好的可扩展性。而 &lt;code>BeanFactory&lt;/code> 的继承体系就是一个非常典型的例子。我们来看一下它的继承体系：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/spring-framework/BeanFactory.svg" alt="BeanFactory 继承体系" width="98%"/>
&lt;/div>
&lt;div class="title">图 1. BeanFactory 继承体系&lt;/div>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;code>AliasRegistry&lt;/code>：别名注册器。Spring 中，别名注册相关的功能就是从这里实现的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>SimpleAliasRegistry&lt;/code>：别名注册器的一个简单实现，从内部属性可以看出，它是把别名映射信息存到一个 &lt;code>Map&lt;/code> 中了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>DefaultSingletonBeanRegistry&lt;/code>：默认的单例 Bean 注册器，从内部属性来说，也是基于 &lt;code>Map&lt;/code> 实现的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>FactoryBeanRegistrySupport&lt;/code>： &lt;code>FactoryBean&lt;/code> 注册器。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>SingletonBeanRegistry&lt;/code>：单例 Bean 注册器。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>BeanDefinitionRegistry&lt;/code>： &lt;code>BeanDefinition&lt;/code> 注册器。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>BeanFactory&lt;/code>：容器的基类。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>ListableBeanFactory&lt;/code>：在基本容器基础上，增加了遍历相关功能。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>HierarchicalBeanFactory&lt;/code>：在基本容器基础上，增加了父子上下级容器关联。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>AutowireCapableBeanFactory&lt;/code>：在基本容器基础上，增加了自动注入功能。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>ConfigurableBeanFactory&lt;/code>：对容器增加可配置性，比如父级容器、&lt;code>ClassLoader&lt;/code>、&lt;code>TypeConverter&lt;/code> 等。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>ConfigurableListableBeanFactory&lt;/code>：可配置可遍历容器。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>AbstractBeanFactory&lt;/code>：容器的抽象实现类，实现了容器的基础功能。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>AbstractAutowireCapableBeanFactory&lt;/code>：带自动装配功能的抽象容器类。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>DefaultListableBeanFactory&lt;/code>：这是 Spring 内部使用的默认容器实现。也是 Spring 中最重要的一个类。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_核心属性">核心属性&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="registry">Registry&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;code>Map&amp;lt;String, String&amp;gt; aliasMap = new ConcurrentHashMap&amp;lt;&amp;gt;(16)&lt;/code>：别名到 Bean 名称的映射。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>Map&amp;lt;String, Object&amp;gt; singletonObjects = new ConcurrentHashMap&amp;lt;&amp;gt;(256)&lt;/code>&lt;/strong>：Bean 名称到单例 Bean 的映射。可以理解成，这就是所谓的容器。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>Map&amp;lt;String, Object&amp;gt; earlySingletonObjects = new HashMap&amp;lt;&amp;gt;(16)&lt;/code>：Bean 到“未成熟”单例 Bean 的映射。该 Bean 对象只是被创建出来，但是还没有注入依赖。在容器解决循环依赖时，用于存储中间状态。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>Map&amp;lt;String, ObjectFactory&amp;lt;?&amp;gt;&amp;gt; singletonFactories = new HashMap&amp;lt;&amp;gt;(16)&lt;/code>：Bean 名称到 Bean 的 &lt;code>ObjectFactory&lt;/code> 对象的映射，在容器解决循环依赖时，用于存储中间状态。&lt;/p>
&lt;div class="paragraph">
&lt;p>关于这三个属性的进一步说明，请移步： &lt;a href="https://www.diguage.com//post/spring-circular-dependence/">源码剖析 Spring 循环依赖&lt;/a>。&lt;/p>
&lt;/div></description></item><item><title>深入剖析 Spring 核心数据结构：BeanDefinition</title><link>https://www.diguage.com/post/dive-into-spring-core-data-structure-bean-definition/</link><pubDate>Sun, 21 Jun 2020 21:18:57 +0800</pubDate><guid>https://www.diguage.com/post/dive-into-spring-core-data-structure-bean-definition/</guid><description>&lt;div class="paragraph">
&lt;p>林纳斯·托瓦兹（Linus Torvalds）说：“我从心底认为，优秀的程序员与平庸的程序员之间的区别，是在于认为自己的代码重要还是数据结构更加重要。平庸的程序员眼里只有代码，优秀的程序员则关注数据结构及之前的关系。” 也许很多人觉得 Spring 神秘莫测，但是如果了解了它的核心数据结构，很多问题迎刃而解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Spring 中两个数据结构最核心：① &lt;code>BeanDefinition&lt;/code>，用于表示 Bean 的定义；② &lt;code>BeanFactory&lt;/code>，用于表示整个 IoC 容器。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在前面文章 &lt;a href="https://www.diguage.com/post/spring-bean-lifecycle-overview/" target="_blank" rel="noopener">Spring Bean 生命周期概述&lt;/a>中，介绍了 Spring Bean 的生命周期。不知道大家有没有思考过 Spring 在内部是如何表示一个 Bean 的？本篇文章，就来聊一聊 &lt;code>BeanDefinition&lt;/code>&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_问题">问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>使用 Spring 时，尤其是使用 XML 配置的时候，也许我们会这样的问题：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>Bean 怎么表示？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Bean 的依赖怎么表示？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>init-method&lt;/code> 方法怎么存储？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Bean 的一些属性，比如 &lt;code>lazy-init&lt;/code> 等，怎么表示？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Bean 构造函数的参数怎么存储？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>…​&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>Java 也有类似的问题，比如怎么表示一个类？Java 通过反射 API 来解决这个问题：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;code>Class&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>Method&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>Field&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>Constructor&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>Annotation&lt;/code>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>但是，为什么 Spring 还要自己定义一套呢？主要原因是 Java 反射 API 不满足 Spring 的需求，比如，它没办法表示哪些类是 &lt;code>SCOPE_SINGLETON&lt;/code>，哪些类是 &lt;code>SCOPE_PROTOTYPE&lt;/code>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>另外，Spring 的 Bean 抽象也并不是完全自定义的，它是基于 Java 反射 API 又增加了自定义功能，其核心 API 就是 &lt;code>BeanDefinition&lt;/code>。下面，我们来仔细看一下它的继承体系以及内部核心属性。&lt;/p>
&lt;/div></description></item><item><title>题解：找到二叉搜索树中两个错误的节点</title><link>https://www.diguage.com/post/find-two-error-node-in-binary-search-tree/</link><pubDate>Sun, 21 Jun 2020 17:57:19 +0800</pubDate><guid>https://www.diguage.com/post/find-two-error-node-in-binary-search-tree/</guid><description>&lt;div class="sect1">
&lt;h2 id="_题目描述">题目描述&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>一棵二叉树原本是二叉搜索树，但是其中有两个节点调换了位置，使得这棵二叉树不再是二叉搜索树，请按升序输出这两个错误节点的值。(每个节点的值各不相同)&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_思路分析">思路分析&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>一棵二叉搜索树，如果是中序遍历，那么，其序列正好是一个升序序列。如果序列中间出现了降序，那么就是树中错误的节点。这里有两种情况需要考虑：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>如果两个节点正好相邻，那么降序的两个节点就是题目要求的节点。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果两个节点不相邻，就会出现两次降序。很容易想到，“大”的元素跑前面，“小”的元素跑后面。在进行比较的时候，第一个降序时，前面大的元素是错误节点；第二次降序时，则是后面小的元素是错误节点。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>有了上面的分析，就可以写代码了。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_解题代码">解题代码&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>中序遍历，可以用栈，也可以使用 Morris 遍历。前面正好学习了一下 Morris（详情请看： &lt;a href="https://www.diguage.com/post/morris-tree-traversal/" target="_blank" rel="noopener">神奇的 Morris 树遍历&lt;/a>），借此机会练练手：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight nowrap">&lt;code data-lang="java">&lt;span class="cm">/**
 * 基于 Morris 的中根遍历
 *
 * @author D瓜哥 · &lt;a href="https://www.diguage.com" target="_blank">https://www.diguage.com&lt;/a>
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="nc">TreeNode&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="nf">getErrs&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">head&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">TreeNode&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">];&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">curr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">head&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">mostRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">TreeNode&lt;/span> &lt;span class="n">prior&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">curr&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">mostRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostRight&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">while&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">mostRight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="n">curr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">left&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="k">continue&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">mostRight&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">prior&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">prior&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">val&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">val&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">if&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prior&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="n">result&lt;/span>&lt;span class="o">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="o">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="n">prior&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="n">curr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">curr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">right&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>得益于 Morris 的优秀本质，这个解法的时间复杂度是： O(n)，空间复杂度是： O(1)。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div></description></item><item><title>Spring AOP 处理流程概述</title><link>https://www.diguage.com/post/spring-aop-process-overview/</link><pubDate>Sat, 20 Jun 2020 19:16:35 +0800</pubDate><guid>https://www.diguage.com/post/spring-aop-process-overview/</guid><description>&lt;div class="paragraph">
&lt;p>AOP 是 Spring 框架的最核心的两个功能之一，在前面的 &lt;a href="https://www.diguage.com/post/spring-startup-process-overview/" target="_blank" rel="noopener">Spring 启动流程概述&lt;/a> 和 &lt;a href="https://www.diguage.com/post/spring-bean-lifecycle-overview/" target="_blank" rel="noopener">Spring Bean 生命周期概述&lt;/a> 两篇文章中，分别介绍了 Spring 启动过程和 Spring Bean 的生命周期，对 IoC 有了一个细致介绍。这里来细致分析一下 Spring AOP 的实现原理和处理流程。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_基本概念">基本概念&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>先来了解几个基本概念，D瓜哥私以为这些概念是 AOP 中最核心的内容，了解了基本概念，可以说基本上掌握了一半的 AOP 内容。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>学习概念最权威的地方，当然就是官方文档。所以，这些概念可以在 &lt;a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-introduction-defn" target="_blank" rel="noopener">Spring Framework Documentation: AOP Concepts&lt;/a> 中看到最权威的介绍。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>Join point(连接点)&lt;/strong>: 所谓的连接点是指那些被拦截到的点。在 Spring 中，连接点指的是方法，因为 Spring 只支持方法类型的连接点。在 Spring 中，使用&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Pointcut(切入点)&lt;/strong>: 所谓的切入点，是指要对哪些 &lt;strong>Join point(连接点)&lt;/strong> 进行拦截的定义。如果 Join point(连接点) 是全集，那么 Pointcut(切入点) 就是被选中的子集。写 AOP 代码的时候，一般是用 Pointcut(切入点) 表达式进行对 Join point(连接点) 进行选择。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Advice(通知/增强)&lt;/strong>: 所谓的通知就是指拦截到 Join point(连接点) 之后所要做的事情。通知根据作用位置不同，又细分为：&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>&lt;strong>Before advice(前置通知)&lt;/strong>: 在 Join point(连接点) 之前运行的通知。这种通知，不能阻止执行流程继续到 Join point(连接点)。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>After returning advice(后置通知)&lt;/strong>: 在 Join point(连接点) 之后运行的通知。当然，如果在 Join point(连接点) 执行过程中，抛出异常，则可能就不执行了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>After throwing advice(异常通知)&lt;/strong>: 方法抛出异常后，将会执行的通知。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>After (finally) advice(最终通知)&lt;/strong>: 无论如何都会执行的通知，即使抛出异常。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Around advice(环绕通知)&lt;/strong>: 围绕在 Join point(连接点) 的通知，方法执行前和执行后，都可以执行自定义行为。同时，也可以决定是返回 Join point(连接点) 的返回值，还是返回自定义的返回值。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Spring Bean 生命周期概述</title><link>https://www.diguage.com/post/spring-bean-lifecycle-overview/</link><pubDate>Sat, 20 Jun 2020 17:33:07 +0800</pubDate><guid>https://www.diguage.com/post/spring-bean-lifecycle-overview/</guid><description>&lt;div class="paragraph">
&lt;p>在 &lt;a href="https://www.diguage.com/post/spring-startup-process-overview/" target="_blank" rel="noopener">Spring 启动流程概述&lt;/a> 中，分析了 Spring 的启动流程。本文就来说明一下 Spring Bean 整个生命周期。如果有不清楚的地方，可以参考上文的“附录：启动日志”。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>直接上图：Spring Bean 生命周期流程图。内容较多，图片文字偏小，请放大看（矢量图，可以任意放大）：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/spring-framework/spring-bean-lifecycle.svg" alt="Spring Bean 生命周期流程图" width="98%"/>
&lt;/div>
&lt;div class="title">图 1. Spring Bean 生命周期流程图&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面是文字说明。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_bean_生命周期简述">Bean 生命周期简述&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>调用 &lt;code>InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation&lt;/code>，主要是判断 &lt;code>AnnotationAwareAspectJAutoProxyCreator&lt;/code> 是否可以生成代理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>调用构造函数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>调用 &lt;code>MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition&lt;/code>，主要是通过 &lt;code>CommonAnnotationBeanPostProcessor&lt;/code>、 &lt;code>AutowiredAnnotationBeanPostProcessor&lt;/code> 收集依赖信息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;span class="line-through">&lt;code>InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation&lt;/code>，这步什么也没做。&lt;/span>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>调用 &lt;code>InstantiationAwareBeanPostProcessor#postProcessProperties&lt;/code>，主要是完成依赖注入。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>调用 &lt;code>AutowiredAnnotationBeanPostProcessor#setBeanFactory&lt;/code>，注入 &lt;code>BeanFactory&lt;/code> 等相关信息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>调用 &lt;code>BeanPostProcessor#postProcessBeforeInitialization&lt;/code>，主要是注入 &lt;code>ApplicationContext&lt;/code> 等相关信息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>调用 &lt;code>InitializingBean#afterPropertiesSet&lt;/code>、 &lt;code>init-method&lt;/code> 方法&lt;/p>
&lt;/li>
&lt;li>
&lt;p>调用 &lt;code>BeanPostProcessor#postProcessAfterInitialization&lt;/code>，主要是生成 AOP 代理类。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_bean_生命周期详解">Bean 生命周期详解&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>从 &lt;code>getBean()&lt;/code> 方法获取 Bean 时，如果缓存中没有对应的 Bean，则会创建 Bean，整个流程如下：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;code>InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation&lt;/code> — 目前有如下四个：&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>&lt;code>ImportAwareBeanPostProcessor&lt;/code> — 继承父类实现，无所事事。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>AnnotationAwareAspectJAutoProxyCreator&lt;/code> — 继承父类实现，判断是否属于基础切面类，如果有指定的 Target 则生成代理。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>CommonAnnotationBeanPostProcessor&lt;/code> — 无所事事。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>AutowiredAnnotationBeanPostProcessor&lt;/code> — 继承父类实现，无所事事。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Spring 启动流程概述</title><link>https://www.diguage.com/post/spring-startup-process-overview/</link><pubDate>Sat, 13 Jun 2020 00:31:25 +0800</pubDate><guid>https://www.diguage.com/post/spring-startup-process-overview/</guid><description>&lt;div class="paragraph">
&lt;p>对于 Spring 启动流程和 Bean 的生命周期，总有一些小地方搞的不是很清楚，干脆直接通过修改代码增加日志输出，使用断点单步调试，把整个流程捋顺了一点点的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>除了加载配置文件或者基础配置类外，Spring 的启动过程几乎都被封装在 &lt;code>AbstractApplicationContext#refresh&lt;/code> 方法中，可以说弄清楚了这个方法的执行过程，就摸清楚了 Spring 启动全流程，下面的流程分析也是以这个方法为骨架来展开的。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_流程概要">流程概要&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>下面完整流程有些太复杂，所以，提炼一个简要的过程，方便糊弄面试官，哈哈哈😆&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>创建容器，读取 &lt;code>applicationContext.register(Config.class)&lt;/code> 指定的配置。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>准备 &lt;code>BeanFactory&lt;/code>，注册容器本身和 &lt;code>BeanFactory&lt;/code> 实例，以及注册环境配置信息等。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>执行 &lt;code>BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry&lt;/code> 注册 &lt;code>BeanDefinition&lt;/code>。有三点需要注意：&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>目前只有一个 &lt;code>ConfigurationClassPostProcessor&lt;/code> 实现类，Spring 中大量的 Bean 都是在这一步被该类注册到容器中的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>执行顺序是 ① &lt;code>PriorityOrdered&lt;/code> ② &lt;code>Ordered&lt;/code> ③ 普通的顺序来执行&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在执行上一步时，如果发现注册了 &lt;code>BeanDefinitionRegistryPostProcessor&lt;/code> 类型的 Bean，就会在循环里继续调用 &lt;code>postProcessBeanDefinitionRegistry&lt;/code> 方法。MyBATIS 和 Spring 整合的 &lt;code>MapperScannerConfigurer&lt;/code> 类就是在这一步执行的。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>执行 &lt;code>BeanFactoryPostProcessor#postProcessBeanFactory&lt;/code> 方法。目前只有一个 &lt;code>ConfigurationClassPostProcessor&lt;/code> 实现类。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>注册 &lt;code>CommonAnnotationBeanPostProcessor&lt;/code> 和 &lt;code>AutowiredAnnotationBeanPostProcessor&lt;/code> 为 &lt;code>BeanPostProcessor&lt;/code>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>注册 &lt;code>ApplicationEventMulticaster&lt;/code>，用于广播事件的。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>注册 &lt;code>ApplicationListener&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>预加载以及注册所有非懒加载的 Bean&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_启动时序图">启动时序图&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Spring 启动流程的时序图如下：&lt;/p>
&lt;/div></description></item><item><title>Spring 扩展点实践：整合 MyBATIS</title><link>https://www.diguage.com/post/spring-extensions-and-mybatis/</link><pubDate>Thu, 28 May 2020 20:06:46 +0800</pubDate><guid>https://www.diguage.com/post/spring-extensions-and-mybatis/</guid><description>&lt;div class="paragraph">
&lt;p>在上一篇文章 &lt;a href="https://www.diguage.com/post/spring-extensions-overview/" target="_blank" rel="noopener">Spring 扩展点概览及实践&lt;/a> 中介绍了 Spring 内部存在的扩展点。学以致用，现在来分析一下 Spring 与 MyBATIS 的整合流程。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_示例程序">示例程序&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>为了方便分析源码，先根据官方文档 &lt;a href="https://mybatis.org/spring/getting-started.html" target="_blank" rel="noopener">mybatis-spring – MyBatis-Spring | Getting Started&lt;/a> 搭建起一个简单实例。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>数据库方面，直接使用功能了 MySQL 示例数据库： &lt;a href="https://dev.mysql.com/doc/employee/en/" target="_blank" rel="noopener">MySQL : Employees Sample Database&lt;/a>，需要的话，自行下载。&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="kn">package&lt;/span> &lt;span class="nn">com.diguage.truman.mybatis&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="kn">import&lt;/span> &lt;span class="nn">com.mysql.cj.jdbc.Driver&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">com.zaxxer.hikari.HikariDataSource&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.apache.ibatis.session.Configuration&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.junit.jupiter.api.Test&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.mybatis.spring.SqlSessionFactoryBean&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.mybatis.spring.annotation.MapperScan&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.beans.factory.annotation.Autowired&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.context.annotation.AnnotationConfigApplicationContext&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.springframework.context.annotation.Bean&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="kn">import&lt;/span> &lt;span class="nn">javax.sql.DataSource&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="cm">/**
 * @author D瓜哥, &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>
 * @since 2020-05-29 17:11
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">MybatisTest&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nd">@Test&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">test&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">AnnotationConfigApplicationContext&lt;/span> &lt;span class="n">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">AnnotationConfigApplicationContext&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">register&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">Config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">refresh&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="nc">EmployeesMapper&lt;/span> &lt;span class="n">employeesMapper&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getBean&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">EmployeesMapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="nc">Employees&lt;/span> &lt;span class="n">employees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">employeesMapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getById&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">10001&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="nc">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">employees&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="nd">@org&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">springframework&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">annotation&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">Configuration&lt;/span>
 &lt;span class="nd">@MapperScan&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">basePackages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;com.diguage.truman.mybatis&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">Config&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nd">@Bean&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="nc">DataSource&lt;/span> &lt;span class="nf">dataSource&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">HikariDataSource&lt;/span> &lt;span class="n">dataSource&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">HikariDataSource&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="n">dataSource&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setUsername&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;root&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">dataSource&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setPassword&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;123456&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">dataSource&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setDriverClassName&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">Driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="o">());&lt;/span>
 &lt;span class="n">dataSource&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setJdbcUrl&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;jdbc:mysql://localhost:3306/employees?useUnicode=true&amp;amp;characterEncoding=utf-8&amp;amp;autoReconnectForPools=true&amp;amp;autoReconnect=true&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">dataSource&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

 &lt;span class="nd">@Bean&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="nc">SqlSessionFactoryBean&lt;/span> &lt;span class="nf">sqlSessionFactory&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nd">@Autowired&lt;/span> &lt;span class="nc">DataSource&lt;/span> &lt;span class="n">dataSource&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">SqlSessionFactoryBean&lt;/span> &lt;span class="n">factoryBean&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">SqlSessionFactoryBean&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="n">factoryBean&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setDataSource&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">dataSource&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="nc">Configuration&lt;/span> &lt;span class="n">configuration&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nc">Configuration&lt;/span>&lt;span class="o">();&lt;/span>
 &lt;span class="n">configuration&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setMapUnderscoreToCamelCase&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="n">factoryBean&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">setConfiguration&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">configuration&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="n">factoryBean&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">EmployeesMapper&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="kn">package&lt;/span> &lt;span class="nn">com.diguage.truman.mybatis&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.apache.ibatis.annotations.Param&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kn">import&lt;/span> &lt;span class="nn">org.apache.ibatis.annotations.Select&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="cm">/**
 * @author D瓜哥, &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>
 * @since 2020-05-29 17:23
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">interface&lt;/span> &lt;span class="nc">EmployeesMapper&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nd">@Select&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;SELECT * FROM employees WHERE emp_no = #{id}&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
 &lt;span class="nc">Employees&lt;/span> &lt;span class="nf">getById&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nd">@Param&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="nc">Integer&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="title">Employees&lt;/div>
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="kn">package&lt;/span> &lt;span class="nn">com.diguage.truman.mybatis&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="kn">import&lt;/span> &lt;span class="nn">java.util.Date&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="cm">/**
 * @author D瓜哥, &lt;a href="https://www.diguage.com/" target="_blank">https://www.diguage.com/&lt;/a>
 * @since 2020-05-29 17:24
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">Employees&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="nc">Integer&lt;/span> &lt;span class="n">empNo&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">Date&lt;/span> &lt;span class="n">birthDate&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">firstName&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">lastName&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">String&lt;/span> &lt;span class="n">gender&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="nc">Date&lt;/span> &lt;span class="n">hireDate&lt;/span>&lt;span class="o">;&lt;/span>

 &lt;span class="nd">@Override&lt;/span>
 &lt;span class="kd">public&lt;/span> &lt;span class="nc">String&lt;/span> &lt;span class="nf">toString&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="s">&amp;#34;Employees{&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span>
 &lt;span class="s">&amp;#34;empNo=&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">empNo&lt;/span> &lt;span class="o">+&lt;/span>
 &lt;span class="s">&amp;#34;, birthDate=&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">birthDate&lt;/span> &lt;span class="o">+&lt;/span>
 &lt;span class="s">&amp;#34;, firstName=&amp;#39;&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">firstName&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="sc">&amp;#39;\&amp;#39;&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span>
 &lt;span class="s">&amp;#34;, lastName=&amp;#39;&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">lastName&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="sc">&amp;#39;\&amp;#39;&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span>
 &lt;span class="s">&amp;#34;, gender=&amp;#39;&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">gender&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="sc">&amp;#39;\&amp;#39;&amp;#39;&lt;/span> &lt;span class="o">+&lt;/span>
 &lt;span class="s">&amp;#34;, hireDate=&amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">hireDate&lt;/span> &lt;span class="o">+&lt;/span>
 &lt;span class="sc">&amp;#39;}&amp;#39;&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>整个实例代码中，只有 &lt;code>@MapperScan(basePackages = &amp;#34;com.diguage.truman.mybatis&amp;#34;)&lt;/code> 这个注解和 MyBATIS 的配置相关，我们就从这里开始吧。&lt;/p>
&lt;/div></description></item><item><title>Spring 扩展点概览及实践</title><link>https://www.diguage.com/post/spring-extensions-overview/</link><pubDate>Tue, 26 May 2020 17:14:20 +0800</pubDate><guid>https://www.diguage.com/post/spring-extensions-overview/</guid><description>&lt;div class="paragraph">
&lt;p>学习 Spring 代码，最重要的是掌握 Spring 有哪些扩展点，可以利用这些扩展点对 Spring 做什么扩展操作。说得更具体一点，如果自己开发一个框架，如何与 Spring 进行整合，如果对 Spring 的扩展点有一个比较清晰的认识，势必会事半功倍。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_import">&lt;code>@Import&lt;/code>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>先来看一下 &lt;code>@Import&lt;/code> 注解的定义：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="nd">@Target&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">ElementType&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">TYPE&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="nd">@Retention&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">RetentionPolicy&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">RUNTIME&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="nd">@Documented&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="nd">@interface&lt;/span> &lt;span class="nc">Import&lt;/span> &lt;span class="o">{&lt;/span>

 &lt;span class="cm">/**
 * {@link Configuration @Configuration}, {@link ImportSelector},
 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
 */&lt;/span>
 &lt;span class="nc">Class&lt;/span>&lt;span class="o">&amp;lt;?&amp;gt;[]&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">();&lt;/span>

&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从声明可以看出，使用时，只需要指定 &lt;code>Class&lt;/code> 实例即可；从方法的文档中可以看出，&lt;code>Class&lt;/code> 实例可以分为三种：&lt;code>ImportSelector&lt;/code>、&lt;code>ImportBeanDefinitionRegistrar&lt;/code> 和常规组件类。示例如下：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="nd">@Configuration&lt;/span>
&lt;span class="nd">@Import&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">LogImportSelector&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">Config&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 &lt;code>org.springframework.context.annotation.ConfigurationClassParser#processImports&lt;/code> 方法中，集中了对 &lt;code>@Import&lt;/code> 注解的处理。从代码可以非常清晰地看出，分了三种情况进行处理：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;code>ImportSelector&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>ImportBeanDefinitionRegistrar&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>常规组件 &lt;code>Class&lt;/code>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面分别对其进行介绍。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_importselector">&lt;code>ImportSelector&lt;/code>&lt;/h3>
&lt;div class="paragraph">
&lt;p>先来看一下 &lt;code>ImportSelector&lt;/code> 接口的定义：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="cm">/**
 * Interface to be implemented by types that determine which @{@link Configuration}
 * class(es) should be imported based on a given selection criteria, usually one or
 * more annotation attributes.
 *
 * &amp;lt;p&amp;gt;{@code ImportSelector} implementations are usually processed in the same way
 * as regular {@code @Import} annotations, however, it is also possible to defer
 * selection of imports until all {@code @Configuration} classes have been processed
 * (see {@link DeferredImportSelector} for details).
 *
 * @since 3.1
 * @see DeferredImportSelector
 * @see Import
 * @see ImportBeanDefinitionRegistrar
 * @see Configuration
 */&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">interface&lt;/span> &lt;span class="nc">ImportSelector&lt;/span> &lt;span class="o">{&lt;/span>

 &lt;span class="cm">/**
 * Select and return the names of which class(es) should be imported based on
 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
 * @return the class names, or an empty array if none
 */&lt;/span>
 &lt;span class="nc">String&lt;/span>&lt;span class="o">[]&lt;/span> &lt;span class="nf">selectImports&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nc">AnnotationMetadata&lt;/span> &lt;span class="n">importingClassMetadata&lt;/span>&lt;span class="o">);&lt;/span>

 &lt;span class="cm">/**
 * Return a predicate for excluding classes from the import candidates, to be
 * transitively applied to all classes found through this selector&amp;#39;s imports.
 * &amp;lt;p&amp;gt;If this predicate returns {@code true} for a given fully-qualified
 * class name, said class will not be considered as an imported configuration
 * class, bypassing class file loading as well as metadata introspection.
 * @return the filter predicate for fully-qualified candidate class names
 * of transitively imported configuration classes, or {@code null} if none
 * @since 5.2.4
 */&lt;/span>
 &lt;span class="nd">@Nullable&lt;/span>
 &lt;span class="k">default&lt;/span> &lt;span class="nc">Predicate&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">String&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nf">getExclusionFilter&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
 &lt;span class="k">return&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="o">}&lt;/span>

&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div></description></item><item><title>负载均衡算法及实践</title><link>https://www.diguage.com/post/load-balancing-algorithm/</link><pubDate>Fri, 15 May 2020 11:37:25 +0800</pubDate><guid>https://www.diguage.com/post/load-balancing-algorithm/</guid><description>&lt;div class="paragraph">
&lt;p>前几天在看一个资料时，看到关于负载均衡算法的介绍。最近也在研究 Spring Cloud 和 Apache Dubbo 等微服务框架。正好负载均衡是微服务框架中一个很重要的知识点。就动手做个整理和总结。方便后续学习。&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>听朋友建议，这篇文章还可以在算法对比，客户端负载均衡与服务端负载均衡区分等两方面做些补充。这些内容后续再补充加入进来。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_常见的负载均衡算法">常见的负载均衡算法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="round-robin">轮询(Round Robin)法&lt;/h3>
&lt;div class="paragraph">
&lt;p>轮询选择指的是从已有的后端节点列表中按顺序依次选择一个节点出来提供服务。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/load-balancing-algorithm/round-robin.png" alt="round robin"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>优点：试图做到请求转移的绝对均衡。实现简单，使用广泛。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="weighted-round-robin">加权轮询(Weighted Round Robin)法&lt;/h3>
&lt;div class="paragraph">
&lt;p>实际使用中各个节点往往都带有不同的权重，所以一般都需要实现带权重的轮询选择。 权重高的被选中的次数多，权重低的被选中的次数少。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/load-balancing-algorithm/weighted-round_robin.jpg" alt="weighted round robin"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>优点：是 &lt;a href="#round-robin">轮询(Round Robin)法&lt;/a> 改良版。适用于服务器配置不一致时，可以将配置好的服务器多干活，配置差的服务器少干活以使机器的负载达到相同的水平。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="static-round-robin">静态轮询(Static Round Robin)法&lt;/h3>
&lt;div class="paragraph">
&lt;p>HAProxy 中实现的一个负载均衡算法。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>没有后台服务器的限制，服务器启动时，修改权重也不会生效。增删服务器时，服务器准备就绪后，会立即加入到服务队列中。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="random">随机(Random)法&lt;/h3>
&lt;div class="paragraph">
&lt;p>通过随机函数，根据后端服务器列表的大小值来随机选择其中一台进行访问。由概率统计理论可以得知，随着调用量的增大，其实际效果越来越接近于平均分配流量到每一台后端服务器，也就是轮询的效果。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="weighted-random">加权随机(Weighted Random)法&lt;/h3>
&lt;div class="paragraph">
&lt;p>与加权轮询法类似，加权随机法也是根据后端服务器不同的配置和负载情况来配置不同的权重。不同的是，它是按照权重来随机选择服务器的，而不是顺序。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="ip-hash">原地址哈希(IP Hashing)法&lt;/h3>
&lt;div class="paragraph">
&lt;p>源地址哈希的思想是获取客户端访问的IP地址值，通过哈希函数计算得到一个数值，用该数值对服务器列表的大小进行取模运算，得到的结果便是要访问的服务器的序号。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>优点：保证了相同客户端 IP 地址将会被哈希到同一台后端服务器，直到后端服务器列表变更。根据此特性可以在服务消费者与服务提供者之间建立有状态的 Session 会话。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="uri-hash">URI 哈希(URI Hashing)法&lt;/h3>
&lt;div class="paragraph">
&lt;p>HAProxy 中实现的一个负载均衡算法。支持部分 URI（问号之前）和完整 URI 两种模式。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这个算法可以把同一个 URI 的访问发送到同一台服务器上，以最大程度提高缓存命中率。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>该算法支持两个可选参数 &lt;code>len&lt;/code> 和 &lt;code>depth&lt;/code>，后跟一个正整数。仅在需要基于URI的开头来平衡服务器时，这些选项可能会很有用。 &lt;code>len&lt;/code> 参数指示算法仅应考虑URI开头的许多字符来计算哈希。请注意，将 &lt;code>len&lt;/code> 设置为 &lt;code>1&lt;/code> 几乎没有意义，因为大多数URI都以前导 &lt;code>/&lt;/code> 开头。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;code>depth&lt;/code> 参数指示用于计算哈希的最大目录深度。请求中的每个斜杠都计为一个级别。如果同时指定了两个参数，则在达到任意一个参数时都将停止评估。&lt;/p>
&lt;/div></description></item><item><title>HikariCP 源码分析 -- ConcurrentBag</title><link>https://www.diguage.com/post/hikari-cp-source-analysis-concurrent-bag/</link><pubDate>Wed, 06 May 2020 17:12:55 +0800</pubDate><guid>https://www.diguage.com/post/hikari-cp-source-analysis-concurrent-bag/</guid><description>&lt;div class="paragraph">
&lt;p>以前无意间搜资料了解到 HikariCP，一下子就被它的简洁代码和卓越性能吸引住了。以前也有翻过它的代码，但是不是很系统，最近再次翻阅，正好做些笔记，方便以后学习。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥最近在学习 Java 并发知识。那就从 HikariCP 自定义的并发集合 &lt;code>ConcurrentBag&lt;/code> 开始学习。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 HikariCP 的 Wiki 中，有 &lt;a href="https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole#concurrentbag" target="_blank" rel="noopener">Down the Rabbit Hole · ConcurrentBag&lt;/a> 的章节来专门介绍 &lt;code>ConcurrentBag&lt;/code>：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;code>ConcurrentBag&lt;/code> 的灵感借鉴自 C# .NET 的 &lt;code>ConcurrentBag&lt;/code> 类。但是实现却是完全不同的。这里的 &lt;code>ConcurrentBag&lt;/code> 有如下特性：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>A lock-free design&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ThreadLocal caching&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Queue-stealing&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Direct hand-off optimizations&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>下面，通过代码来对此做个说明。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 &lt;code>ConcurrentBag&lt;/code> 类的定义中，声明了集合元素必须是 &lt;code>IConcurrentBagEntry&lt;/code> 的子类。先来看看这个接口的定义：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="kd">public&lt;/span> &lt;span class="kd">interface&lt;/span> &lt;span class="nc">IConcurrentBagEntry&lt;/span>
&lt;span class="o">{&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="no">STATE_NOT_IN_USE&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="no">STATE_IN_USE&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="no">STATE_REMOVED&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">;&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="no">STATE_RESERVED&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">;&lt;/span>

 &lt;span class="kt">boolean&lt;/span> &lt;span class="nf">compareAndSet&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">expectState&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">newState&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="kt">void&lt;/span> &lt;span class="nf">setState&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">newState&lt;/span>&lt;span class="o">);&lt;/span>
 &lt;span class="kt">int&lt;/span> &lt;span class="nf">getState&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="o">}&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>接下来，看一下成员变量：&lt;/p>
&lt;/div>
&lt;div class="listingblock">
&lt;div class="content">
&lt;pre class="rouge highlight">&lt;code data-lang="java">&lt;span class="c1">// 存放共享元素&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="nc">CopyOnWriteArrayList&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="no">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">sharedList&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="kt">boolean&lt;/span> &lt;span class="n">weakThreadLocals&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="c1">// 在 ThreadLocal 缓存线程本地元素，避免线程争用&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="nc">ThreadLocal&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nc">Object&lt;/span>&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">threadList&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="nc">IBagStateListener&lt;/span> &lt;span class="n">listener&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="c1">//&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="nc">AtomicInteger&lt;/span> &lt;span class="n">waiters&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kd">volatile&lt;/span> &lt;span class="kt">boolean&lt;/span> &lt;span class="n">closed&lt;/span>&lt;span class="o">;&lt;/span>

&lt;span class="c1">// 接力队列&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="nc">SynchronousQueue&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="no">T&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">handoffQueue&lt;/span>&lt;span class="o">;&lt;/span>&lt;/code>&lt;/pre>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在 &lt;code>ConcurrentBag&lt;/code> 开头的 JavaDoc 中就做了明确说明：&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>Note that items that are &amp;#34;borrowed&amp;#34; from the bag are not actually removed from any collection, so garbage collection will not occur even if the reference is abandoned. Thus care must be taken to &amp;#34;requite&amp;#34; borrowed objects otherwise a memory leak will result. Only the &amp;#34;remove&amp;#34; method can completely remove an object from the bag.&lt;/p>
&lt;/div></description></item><item><title>在世界读书日，推荐书单</title><link>https://www.diguage.com/post/books-on-book-day/</link><pubDate>Thu, 23 Apr 2020 20:15:14 +0800</pubDate><guid>https://www.diguage.com/post/books-on-book-day/</guid><description>&lt;div class="paragraph">
&lt;p>今天是世界读书日，各种人都在推荐书单。D瓜哥也凑个热闹，水一篇文章，推荐一些书籍。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在前一段时间，D瓜哥已经写了一个书单： &lt;a href="https://www.diguage.com/post/java-concurrent-books/">推荐几本 Java 并发编程的书&lt;/a>。为了避免重复，上一个书单中推荐过的书籍，这次就不再重复推荐了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>每年十二个月，D瓜哥就推荐 12 本书，每个月读一本想必压力也不算大。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_如何阅读一本书">&lt;a href="https://book.douban.com/subject/1013208/" target="_blank" rel="noopener">如何阅读一本书？&lt;/a>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/how-to-read-a-book.jpg" alt="how to read a book" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥在年初的时候，刚刚再次重读了这本书。而且，还写了一篇读书笔记： &lt;a href="https://www.diguage.com/post/how-to-read-a-book/">《如何阅读一本书？》之读书笔记&lt;/a>。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果喜欢读书，那么这本书绝对应该是首先阅读的第一本书。一句话总结一下：&lt;strong>用检视阅读的方法来快速筛选出你关注主题的书籍；用分析阅读的方法来吸收一本书的精华；用主题阅读的办法来对多本同一主题的书去伪存真，加工再输出。&lt;/strong>&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_远见">&lt;a href="https://book.douban.com/subject/27609489/" target="_blank" rel="noopener">远见&lt;/a>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/the-long-view.jpg" alt="the long view" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥在去年年末写的年终总结 &lt;a href="https://www.diguage.com/post/goodbye-2019-hello-2020/">“告别 2019，迎接 2020”&lt;/a> 中提到了这本书。考虑这本书的实用性和对自身发展的指导意义，所以决定再次推荐这本书。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在这本书中，作者将职业生涯分为：强势开局、聚焦长板和实现持续的影响力三个阶段。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在强势开局阶段，就像要开始一个汽车拉力赛，要努力加添燃料。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在聚焦长板阶段，要努力提高自己的核心竞争力，创造自己的制高点。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在实现持续的影响力阶段，则要优化长尾效应，让自己持续保持领先。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于职业生涯有追求的小伙伴，尤其是在读大学生，一定要去尽早认真读一读这本书。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_思考快与慢">&lt;a href="https://book.douban.com/subject/10785583/" target="_blank" rel="noopener">思考，快与慢&lt;/a>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/thinking-fast-and-slow.jpg" alt="thinking fast and slow" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这是一本有关心理学方面的书籍。作者丹尼尔•卡尼曼因其与阿莫斯•特沃斯基在决策制定上的研究而荣获了 2002 年度的诺贝尔经济学奖。所以，这本书质量上肯定是有保证的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这本书主要是介绍认知心理学的。作者在书中，把人的认知分为系统一和系统二。系统一是那种不需要思考的，已经固化在我们基因中的反应，比如看见危险会跑路等；而系统二，则是需要深入思考才能有所收获的事情，比如在新税法下，计算个人应该缴纳的个人所得税。两个系统相辅相成，时刻影响着我们的生活，但我们却有些熟视无睹。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_穷查理宝典">&lt;a href="https://book.douban.com/subject/26831789/" target="_blank" rel="noopener">穷查理宝典&lt;/a>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/poor-charlies-almanack.jpg" alt="poor charlies almanack" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>提起查理·芒格，也许有些人不知道是谁。（看这篇文章的读者估计都了解）但是，他的搭档估计是人尽皆知，那就是世界股神沃伦·巴菲特。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>虽然这本书不是查理·芒格书写的，里面的精华部分，却都是查理的演讲稿。通过这些演讲，你可以看到一个睿智的老人，如何在循循善诱地向你传授他的思维方法。查理给我们介绍了他的思维模型：逆向思维，多元思维模型，打造自己的核心圈，避免嫉妒效应，内部积分卡（用我们古人的话说就是反求诸己）等等。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_社会性动物">&lt;a href="https://book.douban.com/subject/2328458/" target="_blank" rel="noopener">社会性动物&lt;/a>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/the-social-animal.jpg" alt="the social animal" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥是去年开始读这本书的，非常抱歉目前还没有读完。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这本书是讲述社会心理学的，讲述在这个社会中，人与人之间是如何相互影响的。举一个典型的例子：你思考过吗，什么样的广告最能打动你吗？&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_事实">&lt;a href="https://book.douban.com/subject/33385402/" target="_blank" rel="noopener">事实&lt;/a>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/factfulness.jpg" alt="factfulness" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>比尔·盖茨也推荐了这本书。我也是最近刚刚开始读这本书。还没有读完。就不做过多评价了。用一个问题，勾引一下你的兴趣：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>问题：在全世界所有的低收入国家里面，有多少百分比的女孩能够上完小学？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>选项：A. 20% B. 40% C. 60%&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>想知道答案，就快点去读这本书吧。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最近更新：D瓜哥终于把这本书读完了：https://www.diguage.com/post/factfulness/[《事实》之读书笔记^]。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_人类简史">&lt;a href="https://book.douban.com/subject/26953606/" target="_blank" rel="noopener">人类简史&lt;/a>&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/books/a-brief-history-of-humankind.jpg" alt="a brief history of humankind" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>坦白讲，这本书D瓜哥才读了一半。但是，作者最近发表的一篇文章： &lt;a href="http://www.ruanyifeng.com/blog/2020/03/the-world-after-coronavirus.html">尤瓦尔·赫拉利《冠状病毒之后的世界》&lt;/a>，一个史学家站在历史发展的角度去看待疫情对世界发展的影响。由此可对赫拉利的思想窥得一斑。那么，如果感兴趣，他的成名大作《人类简史》就不得不读了。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最近因为疫情影响，在网上看到各种五毛的无脑言论，怼天怼地，仿佛中国要征服世界，征服宇宙一样，真是让人呵呵…&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>未来几十年时间里，中国未来寻求自身发展，还需要融入到整个世界经济中，在全世界产业链中，力争上游，占领高附加值的产业，比如芯片，5G，大飞机等等。怼这个，怼那个，只能让自己像二战时期的纳粹德国和日本，让自己四面树敌，最后被全世界群殴。&lt;/p>
&lt;/div></description></item><item><title>《程序员修炼之道》之提示全集</title><link>https://www.diguage.com/post/the-pragmatic-programmer/</link><pubDate>Sat, 18 Apr 2020 12:39:22 +0800</pubDate><guid>https://www.diguage.com/post/the-pragmatic-programmer/</guid><description>&lt;div class="paragraph">
&lt;p>计算机行业是一个发展非常迅速的行业，技术可谓是日新月异。同样，书籍也是更迭不断。书龄在二十年以上的书，少之又少。如果还能一直保持畅销，那绝对是凤毛麟角。而 &lt;a href="https://book.douban.com/subject/35006892/" target="_blank" rel="noopener">《程序员修炼之道》&lt;/a> 绝对算是这些神品中不可或缺的一本。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最近当当在搞读书节，又成功骗我入坑，买了几本心仪已久的书。《程序员修炼之道·第2版》被我成功收入囊中。打开书本，只看目录就感觉可以值回书价了。99 条提示，字字珠玑，金科玉律。忍不住发篇水文，推波助澜，让其发扬光大。&lt;/p>
&lt;/div>
&lt;div class="dlist">
&lt;dl>
&lt;dt class="hdlist1">务实的哲学&lt;/dt>
&lt;dd>
&lt;div class="olist arabic">
&lt;ol class="arabic" start="1">
&lt;li>
&lt;p>关注你的技艺&lt;/p>
&lt;/li>
&lt;li>
&lt;p>思考！思考你的工作&lt;/p>
&lt;/li>
&lt;li>
&lt;p>你有选择权&lt;/p>
&lt;/li>
&lt;li>
&lt;p>提供选择，别找借口&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不要放任破窗&lt;/p>
&lt;/li>
&lt;li>
&lt;p>做推动变革的催化剂&lt;/p>
&lt;/li>
&lt;li>
&lt;p>牢记全景&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将质量要求视为需求问题&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对知识组合做定期投资&lt;/p>
&lt;/li>
&lt;li>
&lt;p>批判性地分析你读到和听到的东西&lt;/p>
&lt;/li>
&lt;li>
&lt;p>英语就是另一门编程语言&lt;/p>
&lt;/li>
&lt;li>
&lt;p>说什么和怎么说同样重要&lt;/p>
&lt;/li>
&lt;li>
&lt;p>把文档嵌进去，而不是拴在表面&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/dd>
&lt;/dl>
&lt;/div>
&lt;div class="dlist">
&lt;dl>
&lt;dt class="hdlist1">务实的方法&lt;/dt>
&lt;dd>
&lt;div class="olist arabic">
&lt;ol class="arabic" start="14">
&lt;li>
&lt;p>优秀的设计比糟糕的设计更容易变更&lt;/p>
&lt;/li>
&lt;li>
&lt;p>DRY—​不要重复自己&lt;/p>
&lt;/li>
&lt;li>
&lt;p>让复用变得更容易&lt;/p>
&lt;/li>
&lt;li>
&lt;p>消除不想搞事物之间的影响&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不设最终决定&lt;/p>
&lt;/li>
&lt;li>
&lt;p>放弃追逐时尚&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用曳光弹找到目标&lt;/p>
&lt;/li>
&lt;li>
&lt;p>用原型学习&lt;/p>
&lt;/li>
&lt;li>
&lt;p>靠近问题域编程&lt;/p>
&lt;/li>
&lt;li>
&lt;p>通过估算来避免意外&lt;/p>
&lt;/li>
&lt;li>
&lt;p>根据代码不断迭代进度表&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/dd>
&lt;/dl>
&lt;/div>
&lt;div class="dlist">
&lt;dl>
&lt;dt class="hdlist1">基础工具&lt;/dt>
&lt;dd>
&lt;div class="olist arabic">
&lt;ol class="arabic" start="25">
&lt;li>
&lt;p>将知识用纯文本保存&lt;/p>
&lt;/li>
&lt;li>
&lt;p>发挥 Shell 命令的威力&lt;/p>
&lt;/li>
&lt;li>
&lt;p>游刃有余地使用编辑器&lt;/p>
&lt;/li>
&lt;li>
&lt;p>永远使用版本控制&lt;/p>
&lt;/li>
&lt;li>
&lt;p>去解决问题，而不是责备&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不要恐慌&lt;/p>
&lt;/li>
&lt;li>
&lt;p>修代码前先让代码在测试中失败&lt;/p>
&lt;/li>
&lt;li>
&lt;p>读一下那些该死的出错信息&lt;/p>
&lt;/li>
&lt;li>
&lt;p>“select”没出问题&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不要假设，要证明&lt;/p>
&lt;/li>
&lt;li>
&lt;p>学习一门文本处理语言&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/dd>
&lt;/dl>
&lt;/div>
&lt;div class="dlist">
&lt;dl>
&lt;dt class="hdlist1">务实的偏执&lt;/dt>
&lt;dd>
&lt;div class="olist arabic">
&lt;ol class="arabic" start="36">
&lt;li>
&lt;p>你无法写出完美的软件&lt;/p>
&lt;/li>
&lt;li>
&lt;p>通过契约进行设计&lt;/p>
&lt;/li>
&lt;li>
&lt;p>尽早崩溃&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用断言去预防不可能的事情&lt;/p>
&lt;/li>
&lt;li>
&lt;p>有始有终&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在局部行动&lt;/p>
&lt;/li>
&lt;li>
&lt;p>小步前进——由始至终&lt;/p>
&lt;/li>
&lt;li>
&lt;p>避免占卜&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/dd>
&lt;/dl>
&lt;/div>
&lt;div class="dlist">
&lt;dl>
&lt;dt class="hdlist1">宁折不弯&lt;/dt>
&lt;dd>
&lt;div class="olist arabic">
&lt;ol class="arabic" start="44">
&lt;li>
&lt;p>解耦代码让改变更容易&lt;/p>
&lt;/li>
&lt;li>
&lt;p>只管命令不要询问&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不要链式调用方法&lt;/p>
&lt;/li>
&lt;li>
&lt;p>避免全局数据&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果全局唯一非常重要，那么将它包装到API 中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>编程讲的是代码，而程序谈的是数据&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不要囤积状态，传递下去&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不要付继承税&lt;/p>
&lt;/li>
&lt;li>
&lt;p>尽量用接口来表达多态&lt;/p>
&lt;/li>
&lt;li>
&lt;p>用委托提供服务：“有一个”胜过“是一个”&lt;/p>
&lt;/li>
&lt;li>
&lt;p>利用 mixin 共享功能&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用外部配置参数化应用程序&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/dd>
&lt;/dl>
&lt;/div>
&lt;div class="dlist">
&lt;dl>
&lt;dt class="hdlist1">并发&lt;/dt>
&lt;dd>
&lt;div class="olist arabic">
&lt;ol class="arabic" start="56">
&lt;li>
&lt;p>通过分析工作流来提高并发性&lt;/p>
&lt;/li>
&lt;li>
&lt;p>共享状态是不正确的状态&lt;/p>
&lt;/li>
&lt;li>
&lt;p>随机故障通常是并发问题&lt;/p>
&lt;/li>
&lt;li>
&lt;p>用角色实现并发性时不必共享状态&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用黑板来协调工作流&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>推荐几本 Java 并发编程的书</title><link>https://www.diguage.com/post/java-concurrent-books/</link><pubDate>Wed, 25 Mar 2020 16:32:12 +0800</pubDate><guid>https://www.diguage.com/post/java-concurrent-books/</guid><description>&lt;div class="paragraph">
&lt;p>最近，D瓜哥的一个小伙伴向我抱怨，Java 并发是个大坑，问我怎么看？我回答，当然是用眼睛看啊…&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥觉得，想学好 Java 并发，最重要的还是啃书。幸运的是，Java 中还是有不少关于并发的优秀书籍可以看。正好利用这个机会，把看过的、个人认为还不错的书推荐一波。没有看过的就不多言了。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_java并发编程实战">Java并发编程实战&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>如果只选一本书来深入研究并发，那肯定是这本书。&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/java-concurrent-books/java-concurrency-in-practice.jpg" alt="java concurrency in practice" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;a href="https://book.douban.com/subject/10484692/" target="_blank" rel="noopener">Java并发编程实战 (豆瓣)&lt;/a> — 这本书是必看的。JDK 中 JUC 就是这本书的作者们写的。虽然书名含有 &lt;code>Java&lt;/code> 一次，但是，里面更多是原理性的东西，各种语言都适用。只是例子少了一些。这本书需要多读几遍。（据说翻译不行，推荐看英文版）&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>放个英文版图片镇楼：&lt;/p>
&lt;/div>
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/java-concurrent-books/java-concurrency-in-practice-en.jpg" alt="java concurrency in practice en" width="60%"/>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_java并发编程的艺术">Java并发编程的艺术&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/java-concurrent-books/art-of-java-concurrency-programming.jpg" alt="art of java concurrency programming" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;a href="https://book.douban.com/subject/26591326/" target="_blank" rel="noopener">Java并发编程的艺术 (豆瓣)&lt;/a> — 这本书也不错，讲了很多源码方面的内容，非常棒。另外，在讲解 Double Lock 方面的知识时，涉及了很多 Java Memory Model 方面的知识，可以先看看 &lt;a href="https://book.douban.com/subject/34907497/" target="_blank" rel="noopener">深入理解Java虚拟机（第3版）(豆瓣)&lt;/a> 最后两章的内容，来提前补充一下这么方面的知识。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_实战java高并发程序设计">实战Java高并发程序设计&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/java-concurrent-books/java-concurrency-in-action.jpg" alt="java concurrency in action" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;a href="https://book.douban.com/subject/30358019/" target="_blank" rel="noopener">实战Java高并发程序设计（第2版） (豆瓣)&lt;/a> — 这本书也不错，针对 Java 8 写的，Java 8 中的很多新知识都有涉猎，例子也很全面。广度和深度，得到了兼顾，非常棒。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_java编程思想">Java编程思想&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="imageblock text-center">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/java-concurrent-books/thinking-in-java.jpg" alt="thinking in java" width="60%"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;a href="https://book.douban.com/subject/2130190/" target="_blank" rel="noopener">Java编程思想（第4版）(豆瓣)&lt;/a> — 虽然这本书已经出来十余年了，但是依然经典。第 21 章 并发，用大量的例子和陈述来介绍并发。非常棒。美中不足，是针对 Java 5 编写的，现在已经 Java 8 了。不过，作者又出了一本书，可以理解成升级版。&lt;/p>
&lt;/div></description></item><item><title>分布式事务概述</title><link>https://www.diguage.com/post/overview-of-distributed-transaction/</link><pubDate>Mon, 23 Mar 2020 12:36:58 +0800</pubDate><guid>https://www.diguage.com/post/overview-of-distributed-transaction/</guid><description>&lt;div class="paragraph">
&lt;p>现在手机银行转账已经司空见惯。但是，D瓜哥一直在思考，银卡跨行转账是如何保证事务一致性的？借机就对分布式事务，做了简单地了解。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_2pc">2PC&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>两阶段提交（2pc， two-phase commit protocol），2pc是非常经典的强一致性、中心化的原子提交协议。中心化是指协议中有两类节点：一个中心化协调者节点（coordinator）和N个参与者节点（participant、cohort）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>顾名思义，两阶段提交协议的每一次事务提交分为两个阶段：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在第一阶段，协调者询问所有的参与者是否可以提交事务（请参与者投票），所有参与者向协调者投票。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在第二阶段，协调者根据所有参与者的投票结果做出是否事务可以全局提交的决定，并通知所有的参与者执行该决定。在一个两阶段提交流程中，参与者不能改变自己的投票结果。两阶段提交协议的可以全局提交的前提是所有的参与者都同意提交事务，只要有一个参与者投票选择放弃(abort)事务，则事务必须被放弃。&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/overview-of-distributed-transaction/two-phase-commit-process.png" alt="two phase commit process"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>两阶段提交协议也依赖与日志，只要存储介质不出问题，两阶段协议就能最终达到一致的状态（成功或者回滚）&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/overview-of-distributed-transaction/2pc-example.png" alt="2pc example"/>
&lt;/div>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/overview-of-distributed-transaction/two-phase-commit-diagram.jpg" alt="two phase commit diagram"/>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_优点">优点&lt;/h3>
&lt;div class="paragraph">
&lt;p>强一致性，只要节点或者网络最终恢复正常，协议就能保证顺利结束；部分关系型数据库（Oracle）、框架直接支持&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_缺点">缺点&lt;/h3>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>网络抖动导致的数据不一致&lt;/strong>： 第二阶段中协调者向参与者发送commit命令之后，一旦此时发生网络抖动，导致一部分参与者接收到了commit请求并执行，可其他未接到commit请求的参与者无法执行事务提交。进而导致整个分布式系统出现了数据不一致。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>超时导致的同步阻塞问题&lt;/strong>： 2PC中的所有的参与者节点都为事务阻塞型，当某一个参与者节点出现通信超时，其余参与者都会被动阻塞占用资源不能释放。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>单点故障的风险&lt;/strong>： 由于严重的依赖协调者，一旦协调者发生故障，而此时参与者还都处于锁定资源的状态，无法完成事务commit操作。虽然协调者出现故障后，会重新选举一个协调者，可无法解决因前一个协调者宕机导致的参与者处于阻塞状态的问题。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>基于两阶段提交的分布式事务在提交事务时需要在多个节点之间进行协调,最大限度地推后了提交事务的时间点，客观上延长了事务的执行时间，这会导致事务在访问共享资源时发生冲突和死锁的概率增高，随着数据库节点的增多，这种趋势会越来越严重，从而成为系统在数据库层面上水平伸缩的&amp;#34;枷锁&amp;#34;， 这是很多Sharding系统不采用分布式事务的主要原因。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_3pc">3PC&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>三阶段提交协议（3pc Three-phase_commit_protocol）主要是为了解决两阶段提交协议的阻塞问题，从原来的两个阶段扩展为三个阶段，并且增加了超时机制。&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/overview-of-distributed-transaction/three-phase-commit-protocol.png" alt="three phase commit protocol"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>3PC 的三个阶段分别是 &lt;code>CanCommit&lt;/code>、&lt;code>PreCommit&lt;/code>、&lt;code>DoCommit&lt;/code>&lt;/p>
&lt;/div>
&lt;div class="dlist">
&lt;dl>
&lt;dt class="hdlist1">CanCommit&lt;/dt>
&lt;dd>
&lt;p>协调者向所有参与者发送CanCommit命令，询问是否可以执行事务提交操作。如果全部响应YES则进入下一个阶段。&lt;/p>
&lt;/dd>
&lt;dt class="hdlist1">PreCommit&lt;/dt>
&lt;dd>
&lt;p>协调者向所有参与者发送PreCommit命令，询问是否可以进行事务的预提交操作，参与者接收到PreCommit请求后，如参与者成功的执行了事务操作，则返回Yes响应，进入最终commit阶段。一旦参与者中有向协调者发送了No响应，或因网络造成超时，协调者没有接到参与者的响应，协调者向所有参与者发送abort请求，参与者接受abort命令执行事务的中断。&lt;/p>
&lt;/dd>
&lt;dt class="hdlist1">DoCommit&lt;/dt>
&lt;dd>
&lt;p>在前两个阶段中所有参与者的响应反馈均是YES后，协调者向参与者发送DoCommit命令正式提交事务，如协调者没有接收到参与者发送的ACK响应，会向所有参与者发送abort请求命令，执行事务的中断。&lt;/p>
&lt;/dd>
&lt;/dl>
&lt;/div>
&lt;div class="paragraph">
&lt;p>3PC只是解决了在异常情况下2PC的阻塞问题，但导致一次提交要传递6条消息，延时很大。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_tcc">TCC&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>TCC是Try、Commit、Cancel的缩写，TCC在保证强一致性的同时，最大限度提高系统的可伸缩性与可用性。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>TCC（Try-Confirm-Cancel）又被称&lt;strong>补偿事务&lt;/strong>，TCC 与 2PC 的思想很相似，事务处理流程也很相似，但 2PC 是应用于在 DB 层面，TCC 则可以理解为在应用层面的 2PC，是需要我们编写业务逻辑来实现。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>TCC 的核心思想是：&amp;#34;针对每个操作都要注册一个与其对应的确认（Try）和补偿（Cancel）&amp;#34;。&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/overview-of-distributed-transaction/tcc.jpg" alt="tcc"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一个完整的业务包含一组子业务，Try操作完成所有的子业务检查，预留必要的业务资源，实现与其他事务的隔离；Confirm使用Try阶段预留的业务资源真正执行业务，而且Confirm操作满足幂等性，以遍支持重试；Cancel操作释放Try阶段预留的业务资源，同样也满足幂等性。“一次完整的交易由一系列微交易的Try 操作组成，如果所有的Try 操作都成功，最终由微交易框架来统一Confirm，否则统一Cancel，从而实现了类似经典两阶段提交协议（2PC）的强一致性。”&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/overview-of-distributed-transaction/tcc-process.jpeg" alt="tcc process"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>再来一个例子：&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/overview-of-distributed-transaction/tcc-example.png" alt="tcc example"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>与2PC协议比较 ，TCC拥有以下特点：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>位于业务服务层而非资源层 ，由业务层保证原子性&lt;/p>
&lt;/li>
&lt;li>
&lt;p>没有单独的准备(Prepare)阶段，降低了提交协议的成本&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Try操作 兼备资源操作与准备能力&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Try操作可以灵活选择业务资源的锁定粒度，而不是锁住整个资源，提高了并发度&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_缺点_2">缺点&lt;/h3>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>应用侵入性强&lt;/strong>：TCC由于基于在业务层面，至使每个操作都需要有 try、confirm、cancel三个接口。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>开发难度大&lt;/strong>：代码开发量很大，要保证数据一致性 confirm 和 cancel 接口还必须实现幂等性。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div></description></item><item><title>Google 三驾马车：MapReduce、GFS、Bigtable</title><link>https://www.diguage.com/post/map-reduce-gfs-bigtable/</link><pubDate>Mon, 23 Mar 2020 10:13:57 +0800</pubDate><guid>https://www.diguage.com/post/map-reduce-gfs-bigtable/</guid><description>&lt;div class="sect1">
&lt;h2 id="_mapreduce">MapReduce&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>MapReduce编程模型来自函数式编程，包含两个最基本的算子：map，reduce&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>将一个运算任务分解成大量独立正交的子任务，每个子任务通过map算子计算，得到中间结果，然后用reduce算子进行聚合，得到最终结果。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这两个算子有一个很重要的特征：确定性的纯过程调用（pure function），函数既不会修改输入，也不存在中间状态，也没有共享的内存。因此，输入一致的情况下，输出也是一致的，这大大方便了容错性设计。&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/map-reduce-gfs-bigtable/map-reduce-framework.png" alt="map reduce framework"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>系统中有两类主要的进程节点：master（单点），worker（多个）。其中，worker根据不同的计算任务，又分为map worker（对应上图中的Map phase）、reduce worker（对应上图中的Reduce phase）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>master是系统的中心节点，负责计算任务到worker节点的分配，同时监控worker节点的状态。如果某个worker计算太慢，或者宕机，master会将该worker进程负责的计算任务转移到其他进程。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>map worker从GFS（google file system）中读取输入数据，然后将中间结果写到本地文件；reduce worker从master处得知中间结果的问题，通过rpc读取中间文件，计算之后将最终结果写入到可靠存储GFS。生产环境中，一个MapReduce过程的输出通常是另一个MapReduce计算的输入，类似Unix 的 pipeline，只不过unix pipeline通过stdin、stdout连接两个程序，而MapReduce使用GFS连接两个计算过程。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_scalability">Scalability&lt;/h3>
&lt;div class="paragraph">
&lt;p>由于计算任务的正交性，很容易通过增加map worker、reduce worker来处理计算任务的增长。Input file 到 Map phase这个阶段，使用了基于范围（range based）的分片方法，master作为元数据服务器会记录split到worker的映射关系。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_availability">Availability&lt;/h3>
&lt;div class="paragraph">
&lt;p>系统对worker的容错性较好，但对master的容错性较差。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于map worker，计算结果是写到本地文件，本地文件的位置需要通知到master，即使同一个task被多个map worker执行，单点的master只会采纳一份中间结果。而且上面提到了map function是pure function，所以计算结果也是一样的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于reduce worker，reduce task的计算结果会先写到临时文件（temporary file），task完成之后再重命名写入gfs，那么如果一个reduce task再多个reduce worker上计算，那么会不会有问题呢，答案是不会的&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_performance">Performance&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>data locality — 将任务调度到数据所在的节点进行计算，减少网络传输；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>backup task — master在发现某个worker上的task进展异常缓慢的时候，会将这个task调度到其他worker，以缩短这个任务（Job）的完成时间。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_gfs">GFS&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>GFS（Google File System）是Google研发的可伸缩、高可用、高可靠的分布式文件系统，提供了类似POSIX的API，按层级目录来组织文件。&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/map-reduce-gfs-bigtable/gfs-architecture.png" alt="gfs architecture"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>GFS master、GFS Client、GFS chunkserver。其中，GFS master任意时刻只有一个，而chunkserver和gfs client可能有多个。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一份文件被分为多个固定大小的chunk（默认64M），每个chunk有全局唯一的文件句柄 －－ 一个64位的chunk ID，每一份chunk会被复制到多个chunkserver（默认值是3)，以此保证可用性与可靠性。chunkserver将chunk当做普通的Linux文件存储在本地磁盘上。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>GFS master是系统的元数据服务器，维护的元数据包括：命令空间（GFS按层级目录管理文件）、文件到chunk的映射，chunk的位置。其中，前两者是会持久化的，而chunk的位置信息来自于Chunkserver的汇报。&lt;/p>
&lt;/div></description></item><item><title>HTTP 123</title><link>https://www.diguage.com/post/http-123/</link><pubDate>Thu, 05 Mar 2020 08:08:44 +0800</pubDate><guid>https://www.diguage.com/post/http-123/</guid><description>&lt;div class="sect1">
&lt;h2 id="_http1_0">HTTP1.0&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>根据谷歌的调查， 现在请求一个网页，平均涉及到 80 个资源，30 多个域名。考虑最原始的情况，每请求一个资源都需要建立一次 TCP 请求，显然不可接受。HTTP 协议规定了一个字段 &lt;code>Connection&lt;/code>，不过默认的值是 &lt;code>close&lt;/code>，也就是不开启。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_http1_1">HTTP1.1&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Pipeline 是为了减少不必要的 TCP 连接，但依然存在队头阻塞(HOC)的缺点，一种解决思路是利用并发连接减少某一个 HOC 的影响，另一个是共享(注意与复用的区别) TCP 连接，直接避免 HOC 问题的发生。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>HTTP1.1 的缺陷&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>高延迟 — 队头阻塞(Head-Of-Line Blocking)&lt;/p>
&lt;div class="paragraph">
&lt;p>当有多个串行请求执行时，如果第一个请求不执行完，后续的请求也无法执行。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>支持并发请求是解决解决 HOC 问题的一种方案，并发请求并非是直接解决了 HOC 的问题，而是尽可能减少 HOC 造成的影响。&lt;/p>
&lt;/div>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>将同一页面的资源分散到不同域名下，提升连接上限。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>减少请求数量&lt;/p>
&lt;/li>
&lt;li>
&lt;p>内联一些资源：css、base64 图片等&lt;/p>
&lt;/li>
&lt;li>
&lt;p>合并小文件减少资源数&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>无状态特性 — 阻碍交互&lt;/p>
&lt;/li>
&lt;li>
&lt;p>明文传输 — 不安全性&lt;/p>
&lt;div class="paragraph">
&lt;p>HTTP 1.x 也可以配合 TLS 进行安全传输，只是不是强制的。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>不支持服务端推送&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_spdy">SPDY&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>SPDY 是由 Google 推行的改进版本的 HTTP1.1。&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/http123/spdy.webp" alt="SPDY"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>针对 HTTP1.1 的缺陷，SPDY 提供了如下特性：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>多路复用 — 解决队头阻塞&lt;/p>
&lt;div class="paragraph">
&lt;p>SPDY 允许在一个连接上无限制并发流。因为请求在一个通道上，TCP 效率更高。&lt;/p>
&lt;/div></description></item><item><title>暗想</title><link>https://www.diguage.com/post/anxiang/</link><pubDate>Fri, 14 Feb 2020 20:39:36 +0800</pubDate><guid>https://www.diguage.com/post/anxiang/</guid><description>&lt;div class="paragraph">
&lt;p>理智告诉我该走了，&lt;br/>
但情感却恋恋不舍；&lt;br/>
大脑告诉我放手吧，&lt;br/>
但心依旧在沦陷；&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>认识你让生活诗情画意，&lt;br/>
想起你忍不住嘴角上扬，&lt;br/>
思念你梦都是甜的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>想做你的指南针，&lt;br/>
带你周游世界；&lt;br/>
带你翱翔太空；&lt;br/>
带你去梦想的远方。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>想做你的眼镜，&lt;br/>
带你观历史的沧海桑田；&lt;br/>
带你赏自然的鬼斧神工；&lt;br/>
带你看艺术的侠骨柔情。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>想做你的厨师，&lt;br/>
可口的早餐等你醒来；&lt;br/>
丰盛的晚餐等你回家。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>好想用心爱你一次，&lt;br/>
爱你就像爱生命；&lt;br/>
我挣钱养家，你貌美如花；&lt;br/>
我用坚强的臂膀，&lt;br/>
构建温馨的家。&lt;br/>
即使外面狂风暴雨，&lt;br/>
家中依然四季如春。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>好想和你站在一起，&lt;br/>
勇往直前，彼此成就。&lt;br/>
你做园丁，&lt;br/>
浇灌祖国的花朵；&lt;br/>
我做篝火，&lt;br/>
温暖家庭，照亮社会。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>想说的话，千言万语，&lt;br/>
却不能说出口。&lt;br/>
想见的人，魂牵梦绕，&lt;br/>
却咫尺天涯。&lt;br/>
远远祝福你，&lt;br/>
事事顺心，桃李天下。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我渴望爱的垂涎，&lt;br/>
但不要怜悯的爱。&lt;br/>
我将继续加油，&lt;br/>
做生活的愚公，&lt;br/>
逢山开路，遇水搭桥。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock note">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-note" title="Note">&lt;/i>
&lt;/td>
&lt;td class="content">
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>遥知不是雪，为有暗香来。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>知音难觅，弦断有谁听？&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div></description></item><item><title>《解读基金》之读书笔记</title><link>https://www.diguage.com/post/jie-du-ji-jin/</link><pubDate>Fri, 31 Jan 2020 12:23:28 +0800</pubDate><guid>https://www.diguage.com/post/jie-du-ji-jin/</guid><description>&lt;div class="paragraph">
&lt;p>这个月断断续续把季凯帆的 &lt;a href="https://book.douban.com/subject/30784282/" target="_blank" rel="noopener">《解读基金——我的投资观与实践（修订版）》&lt;/a>读完了，秉持读完一本，消化一本的读书理念，做一下读书笔记，整理消化一下书中的内容。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>目前房地产不景气，银行利息跑不赢 CPI 的情况下，证券市场是不错的可选性。首先，有政府在背后支撑、监督，不会像 P2P 那样随时暴雷跑路；其次，多方资料显示，长期来看，美国指数基金的收益率在 10% 左右，妥妥跑赢 CPI。第三，流动性好，随手都可以出手（当然有可能会亏损），最长一周时间就可以到账。（节假日除外）第四，起始资金低，几乎没有限制（支付宝买基金十元起步），相比买房动辄几十万，这几乎可以认为没有门槛。&lt;strong>实际上，关键的问题是如何投资证券市场。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>学习借鉴别人的成功经验是迅速提高自己能力的最佳办法之一。阅读这本书，也是学习别人的成功经验，提高自己的投资能力。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥在 &lt;a href="https://www.diguage.com/post/how-to-read-a-book/" target="_blank" rel="noopener">“《如何阅读一本书？》之读书笔记”&lt;/a> 中介绍了一种读书方法，这次也按照这个读书方法把书的内容梳理一遍。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_四个基本问题">四个基本问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>一个阅读者要提出的四个基本问题:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>整体来说，这本书到底在谈些什么？&lt;/strong>你一定要想办法找出这本书的主题，作者如何依次发展这个主题，如何逐步从核心主题分解出从属的关键议题来。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>作者细部说了什么，怎么说的？&lt;/strong>你一定要想办法找出主要的想法、声明与论点。这些组合成作者想要传达的特殊讯息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>这本书说得有道理吗？是全部有道理，还是部分有道理？&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>这本书跟你有什么关系？&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_问题">问题&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>针对这本书，D瓜哥有如下一下问题：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>股市都有哪些风险？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>常说的系统性风险都具体指哪些？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>配置什么样的投资产品？什么比例比较合适？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>基金是怎么运作的？怎么收费的？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如何选择基金？需要关注哪些方面？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>何时卖出基金？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>场内 ETF 指数基金和场外 ETF 增强基金有啥区别？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>我的操作中，有什么不合理的地方？&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_投资准备">投资准备&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_总结">总结&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>投资，说穿了就是让钱生钱。让钱生钱，这其实是所有富人的生财之道。-- D瓜哥觉得把“富人”换成“投资人”更合适。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>“富爸爸”和“穷爸爸”最核心的区别是理念，“富爸爸”在投资，让钱生钱；而“穷爸爸”就盯着那点工资，他一生的追求就是寻找一个好的工作，得到一份好的薪水。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>投机是瞄准一个“机会”，希望大赚一笔，一劳永逸，而投资是要你扎扎实实、一点一滴地走向你的最终梦想。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>基金就是把大家的钱收集到一起，由专门的人（基金经理）帮我们去投资证券市场。他提取他的佣金，我们拿我们的收益，他们帮我们理财投资。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>开放式基金，一般分为股票基金、债券基金、货币基金，以及介于股票和债券基金中间的配置型基金。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Charmin综合征&lt;/p>
&lt;/li>
&lt;li>
&lt;p>投资不是比智商，而是比谁更理性，而理性的前提就是理解，要理解就得花点时间吧。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_摘要">摘要&lt;/h3>
&lt;div class="paragraph">
&lt;p>学习借鉴别人的成功经验是迅速提高自己能力的最佳办法之一。&lt;/p>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_梳理正确的投资观念">梳理正确的投资观念&lt;/h4>
&lt;div class="paragraph">
&lt;p>投资，说穿了就是让钱生钱。让钱生钱，这其实是所有富人的生财之道。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>“富爸爸”和“穷爸爸”最核心的区别是理念，“富爸爸”在投资，让钱生钱；而“穷爸爸”就盯着那点工资，他一生的追求就是寻找一个好的工作，得到一份好的薪水。&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="paragraph">
&lt;p>&lt;a href="https://book.douban.com/subject/25816940/" target="_blank" rel="noopener">《富爸爸穷爸爸》&lt;/a> 是一本很赞的书，推荐对投资理财感兴趣的童鞋去看一看。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>投机是瞄准一个“机会”，希望大赚一笔，一劳永逸，而投资是要你扎扎实实、一点一滴地走向你的最终梦想。投机实际上更多的是收获风险和灾难，而投资则是一种理性的产物。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_基金简介">基金简介&lt;/h4>
&lt;div class="paragraph">
&lt;p>简单地说，基金就是把大家的钱收集到一起，由专门的人（基金经理）帮我们去投资证券市场。他提取他的佣金，我们拿我们的收益，他们帮我们理财投资。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>基金资产的管理与保管是分开的，基金公司管理我们的钱的投资运作，而钱一般是托管在银行的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一种是在你购买基金的时候缴纳，叫前端收费；也可以选择在卖（赎回）基金的时候缴纳，叫后端收费。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>每种基金的净值都是在当天股票交易市场收盘以后再根据其所持有的证券的市值来计算的，一天就一个值，这个值在北京时间下午 3点以后才知道。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在看一个基金的净值时会有两个，一个叫“净值”，一个叫“累计净值”。“累计净值”不考虑分红给基金净值带来的降低因素，反映了基金成立以来总体的发展情况，而“净值”则是你交易时候的真正价格。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>目前国家对个人的基金收益是不收税的。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>开放式基金，一般分为股票基金、债券基金、货币基金，以及介于股票和债券基金中间的配置型基金。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>有 80% 的基友在决定拿出十万八万元购买基金之前考虑的时间不到半个小时，比买件衣服的时间都少。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>美国非常著名的一个基金经理林奇把这种现象称为“Charmin综合征”。Charmin 是美国宝洁公司出的一种卫生纸，几乎在美国任何一个超市里面都可以看见。林奇说：“很多人在买宝洁公司股票前花的时间还不如花在挑 Charmin 卫生纸上的时间多。”这就是“Charmin综合征”。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>稀里糊涂、懵懵懂懂地就把大把的钞票投入到一个未知的领域，而且还不想花点时间去研究一下。&lt;/p>
&lt;/div></description></item><item><title>动态规划入门</title><link>https://www.diguage.com/post/dynamic-programming/</link><pubDate>Mon, 20 Jan 2020 15:20:28 +0800</pubDate><guid>https://www.diguage.com/post/dynamic-programming/</guid><description>&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
&lt;div class="paragraph">
&lt;p>本篇文章是 D瓜哥 读《算法导论》的读书笔记。记录下来是为了方便整理思路，以便啃下“动态规划”这块骨头。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>目前侧重记录书中关于“动态规划原理”的介绍。接下来会把书中的例子结合 Java 代码演绎一遍。后续会根据D瓜哥的学习和理解，逐步完善。最终希望达到通过这一篇文章，就能学会、理解动态规划。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>山高水远，道阻且长，愿一起努力！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p> — 2020年01月23日&lt;/p>
&lt;/div>
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;div class="paragraph">
&lt;p>动态规划（dynamic programming）与分治方法相似，都是通过组合子问题的解来求解原问题（在这里，“programming”指的是一种表格法，并非编写计算机程序）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>分治方法将问题划分为互不相交的子问题，递归地求解子问题，再将它们的解组合起来，求出原问题的解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>动态规划应用于子问题重叠的情况，即不同的子问题具有公共的子子问题（子问题的求解是递归进行的，将其划分为更小的子子问题）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在这种情况下，分治算法会做许多不必要的工作，它会反复地求解那些公共子问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>动态规划算法对每个子子问题只求解一次，将其解保存在一个表格中，从而无需每次求解一个子子问题时都重新计算，避免了不必要的计算工作。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>动态规划方法通常用来求解最优化问题（optimization problem）。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>设计一个动态规划算法的步骤：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>刻画一个最优解的结构特征。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>递归地定义最优解的值。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>计算最优解的值，通常采用自底向上的方法。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>利用计算出的信息构造一个最优解。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_算法原理">算法原理&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>适合应用动态规划方法求解的最优化问题应该具备的两个要素：最优子结构和子问题重叠。&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/dynamic-programming/dynamic-programming-methodology.png" alt="dynamic programming methodology"/>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_最优子结构">最优子结构&lt;/h3>
&lt;div class="paragraph">
&lt;p>用动态规划方法求解最优化问题的第一步就是刻画最优解的结构。如果一个问题的最优解包含其子问题的最优解，则称此问题具有&lt;strong>最优子结构&lt;/strong>性质。因此，某个问题是否适合应用动态规划算法，它是否具有最优子结构性质是一个好线索。&lt;/p>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_发掘最优子结构性质的通过模式">发掘最优子结构性质的通过模式&lt;/h4>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>证明问题最优解的第一个组成部分是做出一个选择。做出这次选择会产生一个或多个待解的子问题。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对于一个给定问题，在其可能的第一步选择中，你假定已经知道哪种选择才会得到最优解。你现在并不关心这种选择具体是如何得到的，只是假定已经知道了这种选择。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>给定可获得最优解的选择后，你确定这次选择会产生哪些子问题，以及如何最好地刻画子问题空间。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>利用“剪切-粘贴”（cut-and-paste）技术证明：作为构造原问题最优解的组成部分，每个子问题的解就是它本身的最优解。证明这一点是利用反证法：假定子问题的解不是其自身的最优解，那么我们就可以从原问题的解中“剪切”掉这些非最优解，将最优解“粘贴”进去，从而得到原问题一个更优的解，这与最初的解是原问题最优解的前提假设锚段。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一个刻画子问题空间的好经验是：保持子问题空间尽可能简单，只在必要时才扩展它。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于不同问题领域，最优子结构的不同体现在两个方面：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>原问题的最优解中涉及多少个子问题，以及&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在确定最优解使用哪些子问题时，我们需要考察多少种选择。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>可以用子问题的总数和每个子问题需要考察多少种选择这两个因素的乘积来粗略分析动态规划算法的运行时间。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>在动态规划方法中，通常自底向上地使用最优子结构。也就是说，首先求得子问题的最优解，然后求原问题的最优解。在求解原问题过程中，我们需要在涉及的子问题中做出选择，选出能得到原问题最优解的子问题。原问题最优解的代价通常就是子问题最优解的代价加上由此次选择直接产生的代价。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>能够使用贪心算法的问题也必须具有最优子结构性质。贪心算法和动态规划最大的不同在于，它并不是首先寻找子问题的最优解，然后在其中进行选择，而是首先做出一次“贪心”选择—​在当时（局部）看来最优的选择—​然后求解选出的子问题，从而不必费心求解所有可能相关的子问题。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock note">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-note" title="Note">&lt;/i>
&lt;/td>
&lt;td class="content">
问题：使用贪心算法和动态规划的界线是什么？什么时候使用贪心？什么时候使用动态规划？
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_重叠子问题">重叠子问题&lt;/h3>
&lt;div class="paragraph">
&lt;p>适合用动态规划方法求解的最优化问题应该具备的第二个性质是子问题空间必须足够“小”，即问题的递归算法会反复地求解相同的子问题，而不是一直生成新的子问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果递归算法反复求解相关的子问题，则就称为最优化问题具有&lt;strong>重叠子问题（overlapping subproblems）&lt;/strong>性质。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>与之相对的，适合用分治算法求解的问题通常在递归的每一步都生成全新的子问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>动态规划算法通常这样利用重叠子问题性质：对每个子问题求解一次，将解存入一个表中，当再次需要这个子问题时直接查表，每次查表的代价为常量时间。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一个问题是否适合用动态规划求解同事依赖于子问题的无关性和重叠性。两个子问题如果不共享资源，它们就是独立的。而重叠是指两个子问题实际上是同一个子问题，只是作为不同问题的子问题出现而已。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>将自顶向下的递归算法（无备忘录）与自底向上的动态规划算法进行比较，后者要高效得多，因为它利用了重叠子问题性质。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_重构最优解">重构最优解&lt;/h3>
&lt;div class="paragraph">
&lt;p>从实际考虑，通常将每个子问题所做的选择存在一个表中，这样就不必根据代价来重构这些信息。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_备忘">备忘&lt;/h3>
&lt;div class="paragraph">
&lt;p>可以保持自顶向下策略，同时达到与自底向上动态规划方法相似的效率。思路就是对自然但低效的递归算法加入备忘机制。维护一个表记录子问题的解，但仍然保持递归算法的控制流程。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>带备忘的递归算法为每个子问题维护一个表项来保存它的解。每个表项的初值设为一个特殊值，表示尚未填入子问题的解。当递归调用过程中第一次额遇到子问题时，计算其解，并存入对应表项。随后每次遇到同一个问题，只是简单地查表，返回其解。&lt;/p>
&lt;/div>
&lt;div class="admonitionblock tip">
&lt;table>
&lt;tbody>&lt;tr>
&lt;td class="icon">
&lt;i class="fa icon-tip" title="Tip">&lt;/i>
&lt;/td>
&lt;td class="content">
一个更通用的备忘方法是使用散列技术，以子问题参数为关键字。
&lt;/td>
&lt;/tr>
&lt;/tbody>&lt;/table>
&lt;/div></description></item><item><title>《如何阅读一本书？》之读书笔记</title><link>https://www.diguage.com/post/how-to-read-a-book/</link><pubDate>Wed, 08 Jan 2020 22:23:51 +0800</pubDate><guid>https://www.diguage.com/post/how-to-read-a-book/</guid><description>&lt;div class="paragraph">
&lt;p>D瓜哥在 &lt;a href="https://www.diguage.com/post/goodbye-2019-hello-2020/#how-to-read-a-book" target="_blank" rel="noopener">“告别 2019，迎接 2020”&lt;/a> 中已经提过，今年的一个重要改进就是把书读透，学以致用，不求速度但求质量。并且，还提到要重读《如何阅读一本书》和《如何有效阅读一本书》，以求提高读书技能，把书读透。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最近在读《如何阅读一本书》，大概读了五分之一。最近有个想法，何不用《如何阅读一本书》中介绍的方法来阅读《如何阅读一本书》呢？熟悉编程（尤其是 Ruby）的朋友估计该会心一笑，有点元编程的感觉。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥这里先大概介绍一下书中的内容，然后再根据读书方法来剖析这本书。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>作者在全书第一章就开宗明义地写道：这本书为那些想把读书的主要目的当作是增进理解能力的人而写。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>我们的目标就是：第一提醒读者，阅读可以是一件多少主动的事。第二要指出的是，阅读越主动，效果越好。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>阅读艺术是一个凭借着头脑运作，除了玩味读物中的一些字句之外，不假任何外助，以一己之力来提升自我的过程。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>蒙田说：“初学者的无知在于未学，而学者的无知在于学后。”&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>阅读的艺术包括了所有非辅助型自我发现学习的技巧：敏锐的观察、灵敏可靠的记忆、想象的空间，再者当然就是训练有素的分析、省思能力。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>要避免这样的错误 —— 以为读得多就是读得好的错误。&lt;/p>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_做一个自我要求的读者">做一个自我要求的读者&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>主动阅读的核心就是：你在阅读时要提出问题来 — 在阅读的过程中，你自己必须尝试去回答的问题。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_四个基本问题">四个基本问题&lt;/h3>
&lt;div class="paragraph">
&lt;p>一个阅读者要提出的四个基本问题:&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>整体来说，这本书到底在谈些什么？你一定要想办法找出这本书的主题，作者如何依次发展这个主题，如何逐步从核心主题分解出从属的关键议题来。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>作者细部说了什么，怎么说的？你一定要想办法找出主要的想法、声明与论点。这些组合成作者想要传达的特殊讯息。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>这本书说得有道理吗？是全部有道理，还是部分有道理？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>这本书跟你有什么关系？&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_做笔记的方法">做笔记的方法&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>画底线 —— 在主要的重点，或重要又有力量的句子下画线。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在画底线处的栏外再加画一道线。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在空白处做星号或其他符号 —— 要慎用。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在空白处编号 —— 作者的某个论点发展出一连串的重要陈述时，可以做顺序编号。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在空白处记下其他的页码 —— 强调作者在书中其他部分也有过同样的论点，或相关的要点，或是与此处观点不同的地方。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>将关键字或句子圈出来 —— 这跟画底线是同样的功能。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>在书页的空白处做笔记 —— 在阅读某一章节时，你可能会有些问题 （ 或答案 ），在空白处记下来，这样可以帮你回想起你的问题或答案。
.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_三种笔记">三种笔记&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>笔记主要的重点是全书的架构，而不是内容 —— 至少不是细节。因此我们称这样的笔记为结构笔记（Structural Note-making）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>概念笔记（Conceptual Note-making）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>针对一场讨论情境的笔记 —— 辩证笔记 （ Dialectical Note-making）。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>你在做这件事时，每一个分开来的步骤都需要你全神贯注地去做。在你分别练习过这些分开来的步骤后，你不但能放下你的注意力，很有效地将每个步骤做好，还能将所有的动作结合起来，表现出一个整体的顺畅行动。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一个人只要学习过一种复杂的技巧，就会知道要学习一项新技巧，一开始的复杂过程是不足为惧的。等所有分开的动作不再分离，渐渐融为一体时，学习者便能将注意力转移到目标上，而他也具备了要达成目标的能力了。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_阅读的四个层次">阅读的四个层次&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>第一层次的阅读为基础阅读（Elementary Reading）。这个层次的任务就是识文认字。&lt;/p>
&lt;/div></description></item><item><title>告别 2019，迎接 2020</title><link>https://www.diguage.com/post/goodbye-2019-hello-2020/</link><pubDate>Tue, 31 Dec 2019 19:20:00 +0800</pubDate><guid>https://www.diguage.com/post/goodbye-2019-hello-2020/</guid><description>&lt;div class="sect1">
&lt;h2 id="_我的_2019">我的 2019&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>在 2018 年底，我写了 &lt;a href="https://www.diguage.com/post/2019-plan/" target="_blank" rel="noopener">“2019 年度计划”&lt;/a> 对 2019 年做了一个简单的规划。2019 年如白驹过隙，稍纵即逝。又到一年年底，现在对这一年做一个总结。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>虽然这一年，我一直在意有这么一个计划，但是从来没有认真总结反思过。为了回顾一下，刚刚又打开计划去看，生活才发现过得如此苍白无力。抛开计划，按时间顺序，从头到尾，随意来说吧。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_读书">读书&lt;/h3>
&lt;div class="paragraph">
&lt;p>今年读了几本书：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>《小岛经济学》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《小王子（英文版）》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《未来世界的幸存者》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《白夜行》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《教父》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《远见》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《如何阅读一本书？》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《文明之光（第一册）》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《基督山伯爵》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《Kubernetes in Action》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《富爸爸穷爸爸》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《巴菲特与索罗斯的投资习惯》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《幸福的方法》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《魔鬼聊天术》&lt;/p>
&lt;/li>
&lt;li>
&lt;p>《拆掉思维里的墙》&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>挑选几本来简单说说吧。&lt;/p>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_小岛经济学">《小岛经济学》&lt;/h4>
&lt;div class="paragraph">
&lt;p>《小岛经济学》是今年读完的第一本书，也算是去年留下的一个尾巴。奥地利经济学派主张政府不干预的观点。这本书简单描述了经济发展的过程，从莽荒时代，手工作业开始，逐步引入工具，提高生产力；随着生产力的提升，人口的增多，经济活动增多，开始物物交换；从物物交换过渡到货币交易，然后银行业兴起；生产力再次提高，以及货币的增加，投资也成为必要，资本市场应运而生；随着地区优势的凸显，资源整合也势在必行，接着发展起了跨国贸易，换汇成为必要，汇率也就被引入进来；随着资本市场的繁荣，埋藏的地雷被引爆，通货膨胀，货币贬值，资本市场崩溃，社会进入大萧条，然后重置继续发展……&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>整个过程完整而清晰，犹如亲历过去发展的波澜壮阔。作为一本经济学入门教程还是很赞的。最近开始看曼昆的《经济学原理》，再来看这本书，真的只能是入门。也许智商不够用，也许对经济学的知识了解得过于浅薄，当经济发展到引入汇率之后，后面的发展过程理解的就不是很透彻了。等看一下金融的书，结合起来，希望能更好地理解。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_未来世界的幸存者">《未来世界的幸存者》&lt;/h4>
&lt;div class="paragraph">
&lt;p>在年初看完的这本书，看完后震撼很大。把当时的读后感直接贴到这里作为总结吧。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>作者站在一个技术人员的角度，去思考技术如何改变世界？未来会是什么样子？&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>作者的思考和我的思考结果是一样的：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>技术的快速发展，机器必定完成大部分重复性劳动，使得大部分劳动者被取代。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>大部分人必定只能找到低端工作，工资仅够温饱。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>贫富分化将越来越严重，而且会世袭，穷人毫无翻身的可能。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>科技发展的结果，我自己感觉是喜忧参半：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>作为一名IT从业者，真正在用技术改变这个世界，带给大家更多的便利，这是百年难得一遇的机遇，生在这个伟大的时代，能参与这个伟大的浪潮之中，可以用自己的双手去推动世界的改变，何其幸哉！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>同时，技术带给人们的便利真是难以想象，足不出户点餐、购物、约车、娱乐…&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>但是，也有很多人是不幸的，他们没有一技之长，或者人到中年，却惨遭失业；还有一部分人，或者说大部分生活在底层的人们，因为没有一技之长（仅指不容易被技术取代的技能），也许永无翻身之日，无所事事，惶惶终日。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>技术的发展，造成人类事业，伴随而来的可能会是“黑镜第一季第二集”的样子：每人一张床，四周都是显示器，想看什么看什么，什么活也不用干，平时运动挣积分，拿积分再消费…&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>进一步发展，也许就是《西部世界》，尤其是娱乐业。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>人类本身很可能会再进化一步：进化成碳基+硅基的组合体。碳基就是目前的人（也许仅仅只需要保留大脑），硅基就是各种芯片+维持大脑活动的必要设备和机械躯体…&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>基因改造将带来彻底的不平衡：人类被分为被改造人和原始人。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="the-long-view">《远见》&lt;/h4>
&lt;div class="paragraph">
&lt;p>这本书是在刷知乎时，别人推荐的书。就找来看了看。真是相见恨晚！如果在大学毕业时就看到这本书，也许……&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>简单做个摘录作为总结吧。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>本书讲人生的职业生涯分为三个阶段：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第一阶段是强势开局的时候。你在职业上的努力必须着重于为前方的漫长道路挖掘和装备自己。你的学习曲线要比职位、职称更加重要。在这一阶段，要为职业生涯打好基础并建立起良好的早期习惯。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第二阶段是聚焦长板的时候。该阶段的首要目标是寻找自己的甜蜜区，即你所擅长的、所热爱的和这个世界所需要的这三者之间的交集。这个时候你要展现自我，让自己鹤立鸡群，想方设法平稳地走在那条收获最大的职场路径上。你要专注于自己的长板，且大可忽略自己的短板。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>第三阶段致力于实现持续的影响力，以及寻找一条可以稳定延续到60多岁甚至70多岁的新的可持续职业道路。你要在第三阶段完成三个关键任务：完成继任计划、保持关联性，以及为自己点燃一团新的职业之火。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>用一句话来总结：第一阶段：加添燃料，强势开局； 第二阶段：聚焦长板，达到高点； 第三阶段：优化长尾，持续发挥影响力。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>三大职场燃料来源：可迁移技能、有意义的经验和持久的关系。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>5个数字，树立正确的职场思维&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>职业生涯的长度：用62减去你目前的年龄。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>精通一项技能所需的时间：要花多少小时才能在某一方面达到“精通”？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>40岁之后能赚到的个人财富百分比：在40岁之后，你赚到的钱会占你一生个人财富的百分之多少？大部分人的估计是60%。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>社交货币：你有多少社交网络好友？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>职场支持者的人数：你认为能在“职业生涯的天堂”里遇到多少人，也就是说有多少人能对你的职业生涯和人生带来真正的变化？&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>这本书的摘要已经发布出来，感兴趣的小伙伴请移步： &lt;a href="https://www.diguage.com/post/the-long-view/">《远见》之读书笔记&lt;/a>。&lt;/p>
&lt;/div></description></item><item><title>OKR 学习笔记 - 未来展望</title><link>https://www.diguage.com/post/okr-notes-future-vision/</link><pubDate>Tue, 22 Oct 2019 00:00:00 +0800</pubDate><guid>https://www.diguage.com/post/okr-notes-future-vision/</guid><description>&lt;div class="sect1">
&lt;h2 id="_29_他们落地_okr_都失败了原因出在哪儿">29 | 他们落地 OKR 都失败了，原因出在哪儿？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_为何这家初创公司落地_okr_失败了">为何这家初创公司落地 OKR 失败了？&lt;/h3>
&lt;div class="paragraph">
&lt;p>拿 OKR 当 KPI 用，只是通过 OKR 看结果，而忽略了 OKR 执行过程中的跟踪与复盘，最终导致 OKR 没用对，KPI 也没用好。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于初创公司而言，生存固然重要，但成长也不可忽视，OKR 既能实现目标，又能关注成长。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_为何这家传统企业落地_okr_失败了">为何这家传统企业落地 OKR 失败了？&lt;/h3>

&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_为何这家互联网公司落地_okr_失败了">为何这家互联网公司落地 OKR 失败了？&lt;/h3>

&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_总结">总结&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>没有充分理解 OKR 的核心概念，更没有真正体会到 OKR 的精髓，容易将 OKR 当 KPI 用。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>当正确理解 OKR 后，就应该快速落地，并及时跟踪落地效果，持续收集员工反馈。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>OKR 的本质就是简单，所以没必要让 OKR 落地变得复杂，无需与流程和制度相绑定。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_30_用什么工具能加强okr落地效果">30 | 用什么工具，能加强OKR落地效果？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_okr_初步落地用什么工具">OKR 初步落地，用什么工具？&lt;/h3>
&lt;div class="paragraph">
&lt;p>与“表格”相比，我更倾向于“看板”，不是电子看板，而是实体看板，需要将它固定在墙上，而且办公区域内，越多的同事看到它越好。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>看板使得 OKR 的价值发挥到了极致。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>第一，OKR 看板让文化变得公开透明。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第二，OKR 看板能激励自己努力执行。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>第三，OKR 看板能影响他人改变行为。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>OKR 看板才是初步落地 OKR 的必备神器。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_okr_初见成效用什么工具">OKR 初见成效，用什么工具？&lt;/h3>
&lt;div class="paragraph">
&lt;p>每周工作结束后，在公司范围内发布“OKR 周报”，在周报中列出本周在 OKR 中要完成进度的人和事，写得越具体越好。&lt;/p>
&lt;/div></description></item><item><title>OKR 学习笔记 - 管理心经</title><link>https://www.diguage.com/post/okr-notes-management-scripture/</link><pubDate>Mon, 21 Oct 2019 18:44:00 +0800</pubDate><guid>https://www.diguage.com/post/okr-notes-management-scripture/</guid><description>&lt;div class="sect1">
&lt;h2 id="_15_技术团队真的是成本中心吗如何改变这一现状">15 | 技术团队真的是“成本中心”吗？如何改变这一现状？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_如何体现技术团队的价值">如何体现技术团队的价值？&lt;/h3>
&lt;div class="paragraph">
&lt;p>要回答技术团队如何产生价值的问题，首先要让同事们知道工程师们每天到底在做什么。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>除了在技术团队核心工作方面来体现价值以外，你还需要在日常的项目中体现技术团队的价值。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_如何使用_okr_体现项目价值">如何使用 OKR 体现项目价值？&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>Why&lt;/strong>：为何我们要做这个项目，这个项目主要是为了解决什么问题？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>What&lt;/strong>：对于项目所解决的问题而言，它所能产生的价值到底有多大？&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>How&lt;/strong>：项目上线后，是否能够有效地去验证项目的价值？如何验证？&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>技术团队所交付项目的价值。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_如何持续地体现技术团队价值">如何持续地体现技术团队价值？&lt;/h3>
&lt;div class="paragraph">
&lt;p>不要告诉工程师们应该做些什么，更不要告诉他们应该怎么去做，而要告诉他们为什么要做。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>O 的完成率是其下每个 KR 完成率的平均值。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于曾经做过的项目所产生的价值，你还需要阶段性地向你的上级领导汇报，从而建立领导和你之间的信任，这同样也能体现技术团队的价值。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_总结">总结&lt;/h3>
&lt;div class="paragraph">
&lt;p>一些技巧：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>项目 OKR 无需你一个人来制定，你需要与协作伙伴们共同来完成。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>通过“引导式”提问方法，让你的伙伴们认可通过 OKR 来验证项目价值的方法。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>持续体现技术团队价值，通过定期向大家同步项目 OKR 完成率，这个方法值得尝试。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_16_大家都说向上管理很重要你想学一些套路吗">16 | 大家都说“向上管理”很重要，你想学一些“套路”吗？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>向下管理指的是管理自己的团队等；
向上管理指的是与自己的领导有效沟通；
横向管理指的是与自己的跨部门同事进行工作协同。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_向上管理到底管理的是什么">向上管理，到底管理的是什么？&lt;/h3>
&lt;div class="sect3">
&lt;h4 id="_问题">问题&lt;/h4>
&lt;div class="paragraph">
&lt;p>为何与自己的领导一言不合，就会选择离职呢？其根本原因还是在于自己和领导之间的沟通出现了问题。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_实质">实质&lt;/h4>
&lt;div class="paragraph">
&lt;p>向上管理，管理的其实是领导对你的期望。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_难点">难点&lt;/h4>
&lt;div class="paragraph">
&lt;p>员工想要的是更深层次理解领导的想法和要求，领导想要的是让员工能尽可能地按照自己的想法和要求去执行。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_如何让双方的沟通变得更加有效">如何让双方的沟通变得更加有效？&lt;/h3>
&lt;div class="sect3">
&lt;h4 id="_案例展示">案例展示&lt;/h4>
&lt;div class="paragraph">
&lt;p>老板参加阿里回来，要求996。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_案例分析">案例分析&lt;/h4>
&lt;div class="paragraph">
&lt;p>如果你现在已经掌握了 OKR 使用方法，那么就能将其上升到“OKR 思维”层面了，建立与领导之间的有效沟通，从而实现向上管理。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_如何使用okr_思维进行向上管理">如何使用“OKR 思维”进行向上管理？&lt;/h3>
&lt;div class="paragraph">
&lt;p>OKR 应该是一种思维，当你需要进行向上管理时，OKR 是你和领导的“共同语言”。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_总结_2">总结&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>做一名“被领导者”，需要发挥自己的智慧，充分挖掘出领导对你的期望。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>产品经理管理的是用户对产品的期望，你需要管理的是领导对你的期望。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>OKR 是一种管理思维，只要双方认可 OKR 的价值，就能快速达成共识。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_17_跨部门协同费劲沟通效率低如何粉碎部门墙">17 | 跨部门协同费劲，沟通效率低，如何粉碎“部门墙”？&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>企业内部之间存在一堵无形的“墙”，它阻碍着跨部门协同，阻碍着各部门之间信息传递和工作交流，我们称这堵墙为“部门墙”，正是由于“部门墙”的产生，才导致公司缺乏执行效率，战略无法迅速落地。&lt;/p>
&lt;/div></description></item><item><title>OKR 学习笔记 - 操作指南</title><link>https://www.diguage.com/post/okr-notes-operation-guide/</link><pubDate>Sun, 20 Oct 2019 18:44:00 +0800</pubDate><guid>https://www.diguage.com/post/okr-notes-operation-guide/</guid><description>&lt;div class="sect1">
&lt;h2 id="_08_如何轻松制定okr方法原来如此简单">08 | 如何轻松制定OKR？方法原来如此简单&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_你制定的_o_对不对">你制定的 O 对不对？&lt;/h3>
&lt;div class="paragraph">
&lt;p>O 是 OKR 中“What”的层面，它解决了“做什么”的问题。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>O 最核心的特性就一定在于它对组织、团队、个人所能提供的价值上：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>对于组织目标而言，当一位 CEO 制定组织目标时，他需要结合企业战略规划来制定 OKR，他所制定的 O 就必须具备商业价值。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果你是团队负责人，那么你一定要考虑：怎样的 O 能帮助你的团队产生价值。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>综上所述，在制定 O 时，你需要注意以下三条核心原则：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>O 要有实际价值，你要深度思考。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>O 要能激励人心，你要善于表达。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>O 要短期可实现，你要脚踏实地。&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>分享三条注意事项：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>O 尽可能要以动词形式开头。&lt;/strong>例如：打造……、建设……、实现……。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>O 用通俗易懂的语言来表达。&lt;/strong>一定不要写一些自认为“高大上”的词汇，除非大家理解上都可以达成一致。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>O 最好能附上一句目标描述。&lt;/strong>建议为 O 增加一句话描述，就“为何我要写这个目标？”这一问题，来向大家讲解。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_你制定的_kr_好不好">你制定的 KR 好不好？&lt;/h3>
&lt;div class="paragraph">
&lt;p>KR 是针对 OKR 中“How”的层面，它解决了具体“怎么做”的问题。&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>具体数字&lt;/p>
&lt;/li>
&lt;li>
&lt;p>数字范围&lt;/p>
&lt;/li>
&lt;li>
&lt;p>“里程碑”事件&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>不得不承认一个事实：恰到好处制定 KR 这件事本身就有一定挑战性。我的建议是，制定 KR 的挑战，你跳一跳就能够得着，后面再不断给自己提更高的要求。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>关于制定 KR，你需要遵循的三条核心原则，我按照重要程度依次列出来：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>KR 要支撑目标，需要与 O 直接关联。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>KR 用数据说话，需要通过数据去度量。&lt;/strong>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>KR 具备挑战性，难易度设置需要恰到好处。&lt;/strong>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>除了 KR 制定的三条核心原则以外，我也想告诉你关于 KR 的三点注意事项：&lt;/p>
&lt;/div></description></item><item><title>OKR 学习笔记 - 快速入门</title><link>https://www.diguage.com/post/okr-notes-quickstart/</link><pubDate>Sat, 19 Oct 2019 18:44:00 +0800</pubDate><guid>https://www.diguage.com/post/okr-notes-quickstart/</guid><description>&lt;div class="sect1">
&lt;h2 id="_01_你想要快速理解okr吗看这篇文章就够了">01 | 你想要快速理解OKR吗？看这篇文章就够了&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_越来越火的_okr_究竟是什么">越来越火的 OKR 究竟是什么？&lt;/h3>
&lt;div class="paragraph">
&lt;p>OKR 是一款目标管理工具，它由 Objectives（目标）和 Key Results（关键结果）两部分构成。同时，OKR 也被称为“目标与关键结果工作法”，简称为“OKR 工作法”。OKR 是融合了一系列框架、方法和哲学后的产物，它起源于 Intel，发展于 Google。&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/okr/okr-intro.png" alt="okr intro"/>
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>可以把 OKR 中的 O 想象成一个你想要去的最终目的地，它时刻为你指明前进的方向，此外，可以把 KR 想象成一个带有距离标记的路标，它时刻告诉你距离目的地还有多远。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>OKR 是一种融入了人性的科学管理框架。OKR 绝不是一款简单的目标管理工具，它充满了人性和智慧。&lt;/p>
&lt;/div>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/okr/okr-history.png" alt="okr history"/>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_你为什么需要_okr">你为什么需要 OKR？&lt;/h3>
&lt;div class="sect3">
&lt;h4 id="_okr_使方向更加聚焦">OKR 使方向更加聚焦&lt;/h4>
&lt;div class="paragraph">
&lt;p>OKR 恰恰能够解决短期目标的实现问题，让目标更加聚焦，让行动脚踏实地，通过完成 KR 从而实现 O，这也正是 OKR 的基本原理。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>为了让目标更加聚焦，因此在制定 O 的时候就需要考虑“做减法”，不要贪多求广，在资源有限的前提下，我们只有集中优势“兵力”，才能逐个歼灭“敌人”。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_okr_使沟通更加透明">OKR 使沟通更加透明&lt;/h4>
&lt;div class="paragraph">
&lt;p>《孙子兵法》云“上下同欲者胜”。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>CEO 首先制定“公司 OKR”，即组织目标，并将组织目标与部门负责人沟通。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>部门负责人再去制定“部门 OKR”，即团队目标，并将团队目标与组织目标对齐，与 CEO 确认后再与员工沟通。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>各个员工充分理解了团队目标后，再去制定自己的“员工 OKR”，即个人目标，并将个人目标与团队目标对齐，并与团队负责人确认。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_okr_使激励更加有效">OKR 使激励更加有效&lt;/h4>
&lt;div class="paragraph">
&lt;p>除了金钱等物质激励以外，最有效的激励方式莫过于“成长”和“认可”，而且精神激励往往比物质激励产生的激励效果更持久。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_如何更有效地使用_okr">如何更有效地使用 OKR？&lt;/h3>
&lt;div class="paragraph">
&lt;p>对于 O，你需要做到以下三点：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>方向明确&lt;/strong>：目标不能太抽象，也不能含混不清，要让每个人都能看得懂。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>目标对齐&lt;/strong>：下级要对齐上级的目标，即向上对齐，平级还要相互对齐，即水平对齐。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>时间限制&lt;/strong>：目标需要有时间边界，不能遥遥无期，而且时间边界要恰到好处。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>对于 KR，你同样需要做到三点：&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>&lt;strong>有挑战性&lt;/strong>：关键结果要有挑战性，拒绝平庸，拒绝不切实际，要做到“跳一跳，就能够得着”。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>容易度量&lt;/strong>：无法度量的关键结果是没有任何意义的，宁可抛弃。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>不是任务&lt;/strong>：关键结果需要对目标产生直接影响，它不是日常任务，也不是行动计划。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>沟通方法总结</title><link>https://www.diguage.com/post/to-talk/</link><pubDate>Thu, 17 Oct 2019 21:47:00 +0800</pubDate><guid>https://www.diguage.com/post/to-talk/</guid><description>&lt;div class="sect1">
&lt;h2 id="_talk和code同等重要">Talk和Code同等重要&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>Talk is cheap，show me the code.&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>有效的沟通是事业成功的必要条件。&lt;/strong>不管你的目标是成为一名卓越的管理者，还是成为某个领域的技术牛人，你都应该提高自己的沟通能力。&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_沟通的原理和问题">沟通的原理和问题&lt;/h3>
&lt;div class="paragraph">
&lt;p>沟通是指运用语言、文字或一些特定的非语言行为（面部表情、肢体动作等），把自己的想法、要求、信息等内容传递给对方。&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>约定术语&lt;/p>
&lt;/li>
&lt;li>
&lt;p>反馈确认&lt;/p>
&lt;/li>
&lt;li>
&lt;p>达成共识&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_沟通阻碍和应对方法">沟通阻碍和应对方法&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_信息不准确">信息不准确&lt;/h3>
&lt;div class="paragraph">
&lt;p>在沟通之前，首先要想清楚沟通的目的是什么，然后整理自己的措辞。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>沟通就是要来来回回的确认。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>沟通效率的关键不在于快，而是准确！&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_信息太多">信息太多&lt;/h3>
&lt;div class="paragraph">
&lt;p>不要绕弯子，有话直说，这是最高效的沟通方式。这既是对对方的一种信任，也是一种对自己的尊重。这样沟通，事情往往能得到更好的解决。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_没有交互">没有交互&lt;/h3>
&lt;div class="paragraph">
&lt;p>找到对方的兴趣点，降低表达自己真实想法的门槛，培养让大家畅所欲言的自由环境，把自己的答案变成问题，让其它人有参与感，这样才可能有好的沟通，也能够有好的结果。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_表达方式">表达方式&lt;/h3>
&lt;div class="paragraph">
&lt;p>沟通中有两个非常重要的因素，一是沟通的内容，二就是表达方式和态度。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_二手信息">二手信息&lt;/h3>
&lt;div class="paragraph">
&lt;p>到信息的源头，向当事人去求证，会让这个世界更加和谐，也会让你变得更有智慧。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_信道被黑">信道被黑&lt;/h3>
&lt;div class="paragraph">
&lt;p>在面对负面信息的时候，最好的方式不是隐瞒，而是公开以及给出解决方案。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_沟通方式及技巧">沟通方式及技巧&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_尊重">尊重&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>我可以不同意你，但是会捍卫你说话的权利。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>赢得对方的尊重需要先尊重对方。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>你一定要和对方有观点上的交互，甚至是碰撞。沟通的目的不是为了附和对方，而是产生一种更完整更全面的认知。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_倾听">倾听&lt;/h3>
&lt;div class="paragraph">
&lt;p>一定要学会倾听，掌握到更多的信息，因为掌握不了足够的信息就会信息不对称，信息不对称就会做出错误的假设。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>倾听其实不仅仅只是听，还要思考，要思考更深层的原因，不要被表象所迷惑，才会有更高效率的沟通，这才有助于你做出正确的决定。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_情绪控制">情绪控制&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>不要过早或者过度打岔和反驳。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>求同存异，冷静客观。&lt;/p>
&lt;div class="paragraph">
&lt;p>切莫在冲动之下，说出很多一些过分或过激的话，因为言语的力量是巨大的，杀伤力有时难以预估。&lt;/p>
&lt;/div>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>有时候，我会自己告诫自己，情绪是自己的，不是别人的，不应该被别人 hack 了。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_沟通技巧">沟通技巧&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>引起对方的兴趣&lt;/p>
&lt;/li>
&lt;li>
&lt;p>直达主题，强化观点&lt;/p>
&lt;div class="paragraph">
&lt;p>确定自己的目标，学会抓重点，知道自己要什么和不要什么，这样你要的才会更鲜明。当一些事情变得简明和鲜明起来时，你才会表现出有力量的观点和话语。而这些被强化过的观点和话语，只需要一句，就会在对方脑子里形成一个小爆点，要么击中了对方的软处（扎心），要么会让对方产生深度思考。&lt;/p>
&lt;/div>
&lt;/li>
&lt;li>
&lt;p>基于数据和事实&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_沟通技术">沟通技术&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_逻辑">逻辑&lt;/h3>
&lt;div class="paragraph">
&lt;p>在逻辑层面说服对方，是一种非常高级的手段。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_信息">信息&lt;/h3>
&lt;div class="paragraph">
&lt;p>信息要全面、准确。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>重点提一下 X/Y 问题。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_维度">维度&lt;/h3>
&lt;div class="paragraph">
&lt;p>能够站在更高的维度来沟通是我们需要努力的目标。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>如果站在客户的角度，最好用高维度。但如果站在技术细节的角度，这是低维度。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>高维度容易拉拢对方，而在低维度更容易说服对方。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_共同">共同&lt;/h3>
&lt;div class="paragraph">
&lt;p>共情，共享，共利，共识以及换位思考。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>寻找共同的过程就是化“敌”为“友”的过程，帮助大家在共赢的大思路和环境下，共同思考问题的解，从而实现高效沟通。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_三本书">三本书&lt;/h3>
&lt;div class="paragraph">
&lt;p>无论干什么，你一定要有一个非常犀利的观点，也就是金句。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>推荐你看三本书《清醒思考的艺术》、《简单逻辑学》和《重来》。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_好老板要善于提问">好老板要善于提问&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_引导">引导&lt;/h3>
&lt;div class="paragraph">
&lt;p>管理者要学会问问题，问员工怎样做。&lt;/p>
&lt;/div></description></item><item><title>学习方法总结</title><link>https://www.diguage.com/post/to-learn/</link><pubDate>Thu, 17 Oct 2019 20:37:00 +0800</pubDate><guid>https://www.diguage.com/post/to-learn/</guid><description>&lt;div class="sect1">
&lt;h2 id="_端正学习态度">端正学习态度&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="paragraph">
&lt;p>学习是一件“逆人性”的事，就像锻炼身体一样，&lt;strong>需要人持续付出，会让人感到痛苦，并随时想找理由放弃。&lt;/strong>&lt;/p>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_主动学习和被动学习">主动学习和被动学习&lt;/h3>
&lt;div class="imageblock">
&lt;div class="content">
&lt;img src="https://www.diguage.com/images/learn-the-pyramid-learning.png" alt="learn the pyramid learning"/>
&lt;/div>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>被动学习&lt;/strong>：如听讲、阅读、视听、演示，学习内容的平均留存率为 5%、10%、20% 和 30%。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>主动学习&lt;/strong>：如通过讨论、实践、教授给他人，会将原来被动学习的内容留存率从 5% 提升到 50%、75% 和 90%。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;div class="paragraph">
&lt;p>只有你开始自己思考，开始自己总结和归纳，开始找人交流讨论，开始践行，并开始对外输出，你才会掌握到真正的学习能力。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>所以，学习不是努力读更多的书，盲目追求阅读的速度和数量，这会让人产生低层次的勤奋和成长的感觉，这只是在使蛮力。要思辨，要践行，要总结和归纳，否则，你只是在机械地重复某件事，而不会有质的成长的。&lt;/strong>&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_浅度学习和深度学习">浅度学习和深度学习&lt;/h3>
&lt;div class="dlist">
&lt;dl>
&lt;dt class="hdlist1">深度学习&lt;/dt>
&lt;dd>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>高质量的信息源和第一手的知识。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>把知识连成地图，将自己的理解反述出来。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不断地反思和思辨，与不同年龄段的人讨论。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>举一反三，并践行之，把知识转换成技能。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/dd>
&lt;/dl>
&lt;/div>
&lt;div class="paragraph">
&lt;p>学习有三个步骤：&lt;/p>
&lt;/div>
&lt;div class="ulist">
&lt;ul>
&lt;li>
&lt;p>&lt;strong>知识采集。&lt;/strong>信息源是非常重要的，&lt;strong>获取信息源头、破解表面信息的内在本质、多方数据印证&lt;/strong>，是这个步骤的关键。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>知识缝合。&lt;/strong>所谓缝合就是把信息组织起来，成为结构体的知识。这里，&lt;strong>连接记忆，逻辑推理，知识梳理&lt;/strong>是很重要的三部分。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>技能转换。&lt;/strong>通过&lt;strong>举一反三、实践和练习，以及传授教导&lt;/strong>，把知识转化成自己的技能。这种技能可以让你进入更高的阶层。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_学习是为了找到方法">学习是为了找到方法&lt;/h3>
&lt;div class="sect3">
&lt;h4 id="_学习不仅仅是为了找到答案而更是为了找到方法">学习不仅仅是为了找到答案，而更是为了找到方法。&lt;/h4>
&lt;div class="paragraph">
&lt;p>学习是为了找到通往答案的路径和方法，是为了拥有无师自通的能力。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_学习是为了找到原理">学习是为了找到原理&lt;/h4>
&lt;div class="paragraph">
&lt;p>学习不仅仅是为了知道，而更是为了思考和理解。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>一旦理解和掌握了这些本质的东西，你就会发现，整个复杂多变的世界在变得越来越简单。你就好像找到了所有问题的最终答案似的，一通百通了。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_学习是为了了解自己">学习是为了了解自己&lt;/h4>
&lt;div class="paragraph">
&lt;p>学习不仅仅是为了开拓眼界，而更是为了找到自己的未知，为了了解自己。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>开拓眼界的目的就是发现自己的不足和上升空间，从而才能让自己成长。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect3">
&lt;h4 id="_学习是为了改变自己">学习是为了改变自己&lt;/h4>
&lt;div class="paragraph">
&lt;p>学习不仅仅是为了成长，而更是为了改变自己。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>学习是为了改变自己的思考方式，改变自己的思维方式，改变自己与生俱来的那些垃圾和低效的算法。总之，学习让我们改变自己，行动和践行，反思和改善，从而获得成长。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_源头原理和知识地图">源头、原理和知识地图&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_挑选知识和信息源">挑选知识和信息源&lt;/h3>

&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_注重基础和原理">注重基础和原理&lt;/h3>
&lt;div class="paragraph">
&lt;p>最最关键的是，这些基础知识和原理性的东西和技术，都是经历过长时间的考验的，所以，这些基础技术也有很多人类历史上的智慧结晶，会给你很多启示和帮助。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>学习就像拉弓蓄力一样，学习基础知识感觉很枯燥很不实用，工作上用不到，然而学习这些知识是为了未来可以学得更快。&lt;/p>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_使用知识图">使用知识图&lt;/h3>
&lt;div class="paragraph">
&lt;p>最重要的是，当出现一些我不知道的知识点时，我就会往这棵知识树上挂，而这样一来，也使得我的学习更为系统和全面。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>学习并不是为了要记忆那些知识点，而是为了要找到一个知识的地图，你在这个地图上能通过关键路径找到你想要的答案。&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;/div>
&lt;div class="sect1">
&lt;h2 id="_深度归纳和坚持实践">深度，归纳和坚持实践&lt;/h2>
&lt;div class="sectionbody">
&lt;div class="sect2">
&lt;h3 id="_系统地学习">系统地学习&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>这个技术出现的背景、初衷和要达到什么样的目标或是要解决什么样的问题。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>这个技术的优势和劣势分别是什么，或者说，这个技术的 trade-off 是什么。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>这个技术适用的场景。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>技术的组成部分和关键点。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>技术的底层原理和关键实现。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>已有的实现和它之间的对比。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_举一反三">举一反三&lt;/h3>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>联想能力。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>抽象能力。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>自省能力。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="dlist">
&lt;dl>
&lt;dt class="hdlist1">训练方法&lt;/dt>
&lt;dd>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>对于一个场景，制造出各种不同的问题或难题。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对于一个问题，努力寻找尽可能多的解，并比较这些解的优劣。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>对于一个解，努力寻找各种不同的测试案例，以图让其健壮。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;/dd>
&lt;/dl>
&lt;/div>
&lt;/div>
&lt;div class="sect2">
&lt;h3 id="_总结和归纳">总结和归纳&lt;/h3>
&lt;div class="paragraph">
&lt;p>我们积累的知识越多，在知识间进行联系和区辨的能力就越强，对知识进行总结和归纳也就越轻松。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>学习的开始阶段，可以不急于总结归纳，不急于下判断，做结论，而应该保留部分知识的不确定性，保持对知识的开放状态。&lt;/p>
&lt;/div></description></item><item><title>2019 年度计划</title><link>https://www.diguage.com/post/2019-plan/</link><pubDate>Sun, 09 Dec 2018 00:00:00 +0800</pubDate><guid>https://www.diguage.com/post/2019-plan/</guid><description>&lt;div class="paragraph">
&lt;p>不知不觉，2019 年元旦马上就要到来。2018 年的元旦仿佛还在昨日。人生天地之间，若白驹之过隙，忽然而已。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>2018 年，要走的已经走了；2019年，希望可以留下想留的…&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>毕业后这几年来，过得浑浑噩噩，仿佛什么也没有留下。之前前年过完春节，忽然意识到自己已经到了而立之年，再想想一事无成，内心突然开始惶恐焦虑起来。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>2019 年，希望自己找准方向，更努力一些，争取各方面都前进一步。所以，在 2019 年即将到来之际，给自己定一些小目标，每月检查进展，反思、总结。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>用心谈一场恋爱。愿得一心人，白首不相离。正心、净口、修身、齐家。三省吾身，改正自己的不足，培养自己的优点。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>学好英语。&lt;/p>
&lt;div class="olist loweralpha">
&lt;ol class="loweralpha" type="a">
&lt;li>
&lt;p>回译两遍《新概念英语·二》和《新概念英语·三》。每天一篇。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>背诵《新概念英语·二》和《新概念英语·三》。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>阅读 &lt;a href="https://book.douban.com/subject/25977798/" target="_blank" rel="noopener">《Word Power Made Easy》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>阅读 &lt;a href="https://book.douban.com/subject/4759840/" target="_blank" rel="noopener">《Merriam-Webster’s Vocabulary Builder》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>阅读 &lt;a href="https://book.douban.com/subject/6512188/" target="_blank" rel="noopener">《Steve Jobs》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>阅读 &lt;a href="https://book.douban.com/subject/1700474/" target="_blank" rel="noopener">《The Little Prince》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>阅读 &lt;a href="https://book.douban.com/subject/2588506/" target="_blank" rel="noopener">《Flipped》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>阅读 &lt;a href="https://book.douban.com/subject/1424313/" target="_blank" rel="noopener">《Animal Farm》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>阅读《Harry Potter》。&lt;/p>
&lt;div class="olist lowerroman">
&lt;ol class="lowerroman" type="i">
&lt;li>
&lt;p>&lt;a href="https://book.douban.com/subject/26260838/" target="_blank" rel="noopener">《Harry Potter and the Philosopher’s Stone》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://book.douban.com/subject/26435068/" target="_blank" rel="noopener">《Harry Potter and the Chamber of Secrets》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://book.douban.com/subject/26587557/" target="_blank" rel="noopener">《Harry Potter and the Prisoner of Azkaban》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://book.douban.com/subject/26613320/" target="_blank" rel="noopener">《Harry Potter and the Goblet of Fire》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://book.douban.com/subject/26821023/" target="_blank" rel="noopener">《Harry Potter and the Order of the Phoenix》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://book.douban.com/subject/26887355/" target="_blank" rel="noopener">《Harry Potter and the Half-Blood Prince》&lt;/a>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://book.douban.com/subject/26913883/" target="_blank" rel="noopener">《Harry Potter and the Deathly Hallows》&lt;/a>。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>只要一部计算机，就可以创造出无限的世界</title><link>https://www.diguage.com/post/computer-create-world/</link><pubDate>Tue, 21 Apr 2009 00:00:00 +0800</pubDate><guid>https://www.diguage.com/post/computer-create-world/</guid><description>&lt;div class="paragraph">
&lt;p>曾经写给学弟学妹的一封信。我觉得还行，发出来，希望对刚学计算机专业的朋友有所帮助。当然，如果哪位朋友有更好的想法，也请留言，大家讨论讨论。原文如下：&lt;/p>
&lt;/div>
&lt;div class="sidebarblock">
&lt;div class="content">
&lt;div class="title">致师弟师妹的一封信&lt;/div>
&lt;div class="paragraph">
&lt;p>各位朋友：&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>大家好！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>从本周开始，我必须专心为我的前程奋斗了。很可惜不能和大家一起学习了。这封信就算是我和大家的一个告别吧。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>首先，感谢史老师给我提供这个和大家一起学习和交流的机会，让我和大家一起度过了一段特殊而愉快的时光。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>其次，我应该感谢大家。由于本人水平有限，也许我没能让大家从我这里学到太多知识，但是大家却让我学会了很多东西，锻炼了我很多方面的能力。单凭这一点，我就应该感谢大家。同时，很多人把我当朋友，更使我感激不尽。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>再次，在这短暂而又宝贵的大学时光里，大家走的路应该和我的基本一样。我以一个过来人的身份，和大家分享一下我的学习经验吧，希望可以让大家少走甚至不走弯路。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>先说一点我们所学科目的情况吧。&lt;/p>
&lt;/div>
&lt;div class="olist arabic">
&lt;ol class="arabic">
&lt;li>
&lt;p>Linux：一定要动手敲指令。在我们一年半的学习时光里，我认为Linux是我们所学的所有实践性学科里面最简单的一科了！只要把指令记好用熟就行了！另外，指令不需要背，用多了自然就记住了。（相信95%的同学都知道cd是什么意思吧！为什么呢？因为用它的次数多！）多提一点，一定要多尝试！&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Java或者C#或者PHP（由于个人比较喜欢Java，所以，一下描述Java的地方，你可以同样换成C#或在PHP等）：这是我们大家必须要下苦功夫学好的课！而且一定一定要学精！它们在我们的课程体系结构中的作用，就像是地基对于这个房子的作用！根基不好，房子很难建高的！即使建好也是豆腐渣工程。毕竟“空中楼阁”只存在于我们的想象中。它们是学习JSP、ASP.NET、J2EE、“游戏开发”、网页开发等等的基础，Java或者C#或者PHP学不会，这些课很难学好！（我这里有Java的教学视频，感兴趣的同学可以来我寝室拷贝。也可以上网下载：http://www.verycd.com/topics/93279/（请下载J2SE的））&lt;/p>
&lt;/li>
&lt;li>
&lt;p>数据库：重点是关系模型、 SQL语句以及后面的数据库设计。做动态网站的技术、做桌面程序，甚至做手机应用等，都会用到数据库！以后工作中数据模型设计、数据查询等都要求有比较扎实的数据库基础才行。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>数据结构：我个人认为学习数据结构就是学习一种解决问题的思想。其实，类库已经实现了我们所学的所有的数据结构，到时会用就行了！&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div class="paragraph">
&lt;p>现在NBA正在进行季后赛，就用乔丹的一句话，作为所有学科的一个共同的建议送给大家：&lt;/p>
&lt;/div>
&lt;div class="quoteblock">
&lt;blockquote>
&lt;div class="paragraph">
&lt;p>I can accept failure, but I can’t accept not trying.&lt;/p>
&lt;/div>
&lt;/blockquote>
&lt;div class="attribution">
— Michael Jordan
&lt;/div>
&lt;/div>
&lt;div class="paragraph">
&lt;p>其实学习就是不断尝试，不断总结，不断进取的一个过程。我可以用我的尝试告你一个正确的结果，但是自己尝试出来的结果印象更加深刻！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>再说一点我的个人的学习感触吧。我个人认为，这些比我们大家在学校学到知识更为重要！知识马上就会过时，但是学习的方法却可以带领我们走的更远。&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>一、享受学习。&lt;/strong>如果我们把学习当成像玩游戏、听音乐一样的娱乐活动时，我想这会给我们一种全新的感觉。Study hard，have fun，make history！ By Jeff Bezos &amp;amp; Joy Lee （努力学习，乐在其中，并创造历史！—Jeff Bezos（Amazon.com的创办者兼CEO，Joy Lee就是我。(*&lt;sup>__&lt;/sup>*) 嘻嘻…… 这句话是我从他的一句话里改编过来的！））&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>其实，学计算机学科非常有趣！Linux里面，几行指令，我们就可以顺利的让鼠标在两个操作系统自由转换！Java里面几行，几十行代码就有一个漂亮的窗口！C#里面随便一个拖拽，也是一个不错的窗口！“只要一部计算机，就可以创造出无限的世界。”（出自蔡学镛《写程序，好好玩》，《Java夜未眠》里面的一篇，本书是本很搞笑的编程感触散文集，推荐看看。）加油吧！相信你行！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>二、坚持不懈。&lt;/strong>任何事情都不是可以一蹴而就的，都需要我们坚持！再送给大家一句话：失败只有一种，那就是半途而废！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>三、舍我其谁的豪迈和霸气。&lt;/strong>“Horse’s，Gosling能创造出来Java，我就不信我学不会！一个破Java，我还不放到眼里呢！！”，“别人能创造出来，我就不相信我学不会？！”不过，也要给大家提醒一句：“战略上，藐视敌人；战术上，重视敌人”！一定要下功夫学习才行！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>四、信心。&lt;/strong>一定要相信自己的能力！你是独一无二，无可替代的！信心能让你从一个更高的角度看待你的学习！能给你一种驾驭知识的成就感！这种感觉能让你从学习中找到乐趣！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>&lt;strong>五、行动！&lt;/strong>上面的大道理，我们大家都懂！但是，谁实际做到呢？伟大与平凡的区别也许就在于这一点吧。我以我自己的高中、大学对比来看，行动与否结果绝对是不一样的！只想不做，最多是个空想家！所以，大家一定要“坚持不懈的行动”！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>最后，送给大家一句话：&lt;strong>你充满了潜能，但你的努力还远远不够！&lt;/strong>再次怀念我们共同的学习时光！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>祝大家学有所成！&lt;/p>
&lt;/div>
&lt;div class="paragraph">
&lt;p>D瓜哥&lt;/p>
&lt;/div>
&lt;/div>
&lt;/div></description></item></channel></rss>