<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Blog</title>
  
  <subtitle>Test Blog</subtitle>
  <link href="https://yzlzzz.xyz/atom.xml" rel="self"/>
  
  <link href="https://yzlzzz.xyz/"/>
  <updated>2025-03-01T07:36:48.954Z</updated>
  <id>https://yzlzzz.xyz/</id>
  
  <author>
    <name>Yzl</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>动态规划心得</title>
    <link href="https://yzlzzz.xyz/2025/03/01/dp/"/>
    <id>https://yzlzzz.xyz/2025/03/01/dp/</id>
    <published>2025-03-01T07:24:45.000Z</published>
    <updated>2025-03-01T07:36:48.954Z</updated>
    
    <content type="html"><![CDATA[<p>动态规划（Dynamic Programming）是一种重要算法，通过将原始问题分解成一系列更小的问题，并且通过子问题来避免重复计算，以此来提升时间效率。</p><h2 id="动态规划的特性"><a href="#动态规划的特性" class="headerlink" title="动态规划的特性"></a>动态规划的特性</h2><h3 id="动态规划、分治、回溯的区别"><a href="#动态规划、分治、回溯的区别" class="headerlink" title="动态规划、分治、回溯的区别"></a>动态规划、分治、回溯的区别</h3><p>动态规划是通过将问题分解为子问题来解决问题，而子问题的分解是一种通用的思路，而动态规划与分治、回溯的侧重点有所不同：</p><ul><li><strong>动态规划：</strong> 动态规划会对问题进行递归分解，但是分解后的子问题直接是相互依赖的，子问题直接会存在推导关系，另外分解过程中也会出现很多相同子问题。</li><li><strong>分治：</strong> 分治算法递归地将原问题划分为多个相互独立的子问题，直至最小子问题，并在回溯中合并子问题的解，最终得到原问题的解。</li><li><strong>回溯：</strong> 回溯会尝试穷举问题的所有解，并通过剪枝来避免不必要的分支。</li></ul><p>动态规划常用来求解最优化问题，不仅包含重叠子问题，还有两个特性：最优子结构、无后效性。</p><h3 id="最优子结构"><a href="#最优子结构" class="headerlink" title="最优子结构"></a>最优子结构</h3><p>原问题的最优解是由子问题的最优解推导</p><p>dp[i] &#x3D; f(dp[i - 1])</p><h3 id="无后效性"><a href="#无后效性" class="headerlink" title="无后效性"></a>无后效性</h3><p>给定一个确定的状态，当前状态与过去状态无关，当前状态可以推出未来状态</p><h2 id="解题思路"><a href="#解题思路" class="headerlink" title="解题思路"></a>解题思路</h2><p>如何判断一个问题是否可以由动态规划解决，以及判断后如何入手。</p><h3 id="问题判断"><a href="#问题判断" class="headerlink" title="问题判断"></a>问题判断</h3><p>如果一个问题包含重叠子问题、最优子结构，并满足无后效性，那么它通常就适合用动态规划求解。但我们很难从问题描述上直接提取出这些特性。所以通常会观察问题是否适合回溯解决。</p><blockquote><p>适合用回溯解决的问题通常满足“决策树模型”，这种问题可以使用树形结构来描述，其中每一个节点代表一个决策，每一条路径代表一个决策序列。 <br /><br>换句话说，如果问题包含明确的决策概念，并且解是通过一系列决策产生的，那么它就满足决策树模型，通常可以使用回溯来解决。</p></blockquote><p>问题判断的加分项：</p><ul><li>问题包含最优化描述</li><li>问题状态可使用列表、矩阵、树来表示，状态直接存在推导关系</li></ul><p>问题判断的减分项：</p><ul><li>问题目标是找出所有解</li><li>问题描述有明显的排列组合特征</li></ul><h3 id="问题求解步骤"><a href="#问题求解步骤" class="headerlink" title="问题求解步骤"></a>问题求解步骤</h3><p>大概流程为：描述决策，定义状态，建立 dp 表，推导状态转移方程，确定边界条件等。</p><h2 id="0-1-背包问题"><a href="#0-1-背包问题" class="headerlink" title="0-1 背包问题"></a>0-1 背包问题</h2><p>背包问题是一个非常好的动态规划入门题目，是动态规划中最常见的问题形式。</p><p><strong>0-1 背包问题：</strong> 给定 n 个物品，第 𝑖 个物品的重量为 wgt[i−1]、价值为 val[i−1]，和一个容量为 c 的背包。每个物品只能选择一次，求出在不超过背包容量下能放入物品的最大价值。</p><p>将 0‑1 背包问题看作是一个由 n 轮决策组成的过程，每个物体都有不放入和放入两种决策，因此该问题是满足决策树模型的。该问题的目标是求解“在限定背包容量下的最大价值”，因此较大概率是个动态规划问题。</p><h3 id="解题思路-1"><a href="#解题思路-1" class="headerlink" title="解题思路"></a>解题思路</h3><ol><li><p><strong>描述决策：</strong> 对于每个物品来说，不放入背包，背包容量不变；放入背包，背包容量减小。</p></li><li><p><strong>定义状态：</strong> 前 i 个物品在剩余容量为 𝑐 的背包中的最大价值，记为 dp[i, c]。</p></li><li><p><strong>建立 dp 表：</strong> 根据上一步建立 (n + 1) * (c + 1) 的二维 dp 表。</p></li><li><p><strong>推导状态转移方程：</strong><br>当我们做出物品 𝑖 的决策后，剩余的是前 i−1 个物品的决策，可分为以下两种情况。</p><ul><li>不放入物品 i：背包容量不变，状态变化为 [i−1, c]。</li><li>放入物品 i：背包容量减小 wgt[i−1]，价值增加 val[𝑖−1]，状态变化为[i−1, c−wgt[i−1]]。</li></ul></li></ol><p><strong>最大价值 dp[i, c] 等于不放入物品 i 和放入物品 i 两种方案中的价值更大的那一个。</strong></p><ol><li><strong>确定边界条件：</strong>当无物品或无剩余背包容量时最大价值为 0。</li></ol><h2 id="完全背包问题"><a href="#完全背包问题" class="headerlink" title="完全背包问题"></a>完全背包问题</h2><p><strong>完全背包问题：</strong> 给定 n 个物品，第 i 个物品的重量为 wgt[i−1]、价值为 val[i−1]，和一个容量为 c 的背包。每个物品可以重复选取，问在不超过背包容量下能放入物品的最大价值。</p><p>完全背包和 0‑1 背包问题非常相似，区别仅在于<strong>不限制物品的选择次数</strong>，在选择物品 i 之后，仍可以选择物品 i。</p><p>在完全背包问题中，状态 [i, c] 为：</p><ul><li>不放入物品 i ：与 0‑1 背包相同，转移至[i−1, c]。</li><li>放入物品 i ：与 0‑1 背包不同，转移至[i, c−wgt[i−1]]。</li></ul><h2 id="零钱兑换问题"><a href="#零钱兑换问题" class="headerlink" title="零钱兑换问题"></a>零钱兑换问题</h2><p><strong>零钱兑换问题：</strong> 给定 n 种硬币，第 𝑖 种硬币的面值为 coins[𝑖−1]，目标金额为 amt，每种硬币可以重复选取，问能够凑出目标金额的最少硬币个数。如果无法凑出目标金额则返回 −1。</p><p>零钱兑换问题是完全背包问题的一种特例，背包问题是要最大化物品价值，零钱兑换问题是要最小化硬币数量；背包问题是求<strong>不超过</strong>背包容量下的解，零钱兑换是求<strong>恰好</strong>凑到目标金额的解。</p><p>在状态转移过程中，要求目标金额的最少硬币数，因此是要求最小值。</p><p>在确定边界条件时，无法凑出目标金额时，即为无解，返回 -1。</p><h2 id="零钱兑换问题-Ⅱ"><a href="#零钱兑换问题-Ⅱ" class="headerlink" title="零钱兑换问题 Ⅱ"></a>零钱兑换问题 Ⅱ</h2><p><strong>零钱兑换问题 Ⅱ：</strong> 给定 n 种硬币，第 𝑖 种硬币的面值为 coins[i−1]，目标金额为 amt，每种硬币可以重复选取，问在凑出目标金额的硬币组合数量。</p><p>定义状态时，子问题应该为前 i 种硬币能够凑出金额 a 的组合数量。当前状态的组合数量等于不选当前硬币与选当前硬币这两种决策的组合数量之和。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>所有动态规划都可以使用回溯算法解决，但在回溯算法的递归树中存在大量的重叠子问题，效率极低。通过引入记忆化列表，可以存储所有计算过的子问题的解，从而保证重叠子问题只被计算一次。</li><li>记忆化递归是一种从顶至底的递归式解法，而与之对应的动态规划是一种从底至顶的递推式解法。</li></ul><h2 id="相关链接"><a href="#相关链接" class="headerlink" title="相关链接"></a>相关链接</h2><p><a href="https://www.hello-algo.com/chapter_dynamic_programming/">动态规划 - Hello 算法</a></p><p><a href="https://leetcode.cn/circle/discuss/tXLS3i/">动态规划题单 - LeetCode</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;动态规划（Dynamic Programming）是一种重要算法，通过将原始问题分解成一系列更小的问题，并且通过子问题来避免重复计算，以此来提升时间效率。&lt;/p&gt;
&lt;h2 id=&quot;动态规划的特性&quot;&gt;&lt;a href=&quot;#动态规划的特性&quot; class=&quot;headerlink&quot; </summary>
      
    
    
    
    
    <category term="算法" scheme="https://yzlzzz.xyz/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="动态规划" scheme="https://yzlzzz.xyz/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
  </entry>
  
  <entry>
    <title>InnoDB 中的 MVCC</title>
    <link href="https://yzlzzz.xyz/2024/08/28/mysql-mvcc/"/>
    <id>https://yzlzzz.xyz/2024/08/28/mysql-mvcc/</id>
    <published>2024-08-27T16:14:17.000Z</published>
    <updated>2024-09-13T16:15:51.103Z</updated>
    
    <content type="html"><![CDATA[<h2 id="MVCC"><a href="#MVCC" class="headerlink" title="MVCC"></a>MVCC</h2><p>多版本并发控制 (MVCC, Multi-Version Concurrency Control)，一种并发控制机制，用于在多个并发事务同时读写数据库时保持数据的一致性和隔离性。通过在每个数据行上维护多个版本的数据来实现的。当一个事务要对数据库中的数据进行修改时，MVCC 会为该事务创建一个数据快照，而不是直接修改实际的数据行。</p><h3 id="查询操作"><a href="#查询操作" class="headerlink" title="查询操作"></a>查询操作</h3><p>当一个事务执行读操作时，它会使用快照读取。快照读取是基于事务开始时数据库中的状态创建的，因此事务不会读取其他事务尚未提交的修改。流程如下：</p><ol><li>对于查询，事务会查找符合条件的数据行，并选择符合其事务开始时间的数据版本进行读取。</li><li>如果某个数据行有多个版本，事务会选择不晚于其开始时间的最新版本，确保事务只读取在它开始之前已经存在的数据。</li><li>事务读取的是快照数据，因此其他并发事务对数据行的修改不会影响当前事务的读取操作。</li></ol><h3 id="更新操作"><a href="#更新操作" class="headerlink" title="更新操作"></a>更新操作</h3><p>当一个事务执行更新操作时，它会生成一个新的数据版本，并将修改后的数据写入数据库。流程如下：</p><ol><li>对于写操作，事务会为要修改的数据行创建一个新的版本，并将修改后的数据写入新版本。</li><li>新版本的数据会带有当前事务的版本号，以便其他事务能够正确读取相应版本的数据。</li><li>原始版本的数据仍然存在，供其他事务使用快照读取，这保证了其他事务不受当前事务的写操作影响</li></ol><h3 id="事务的提交与回滚"><a href="#事务的提交与回滚" class="headerlink" title="事务的提交与回滚"></a>事务的提交与回滚</h3><p>当一个事务提交时，它所做的修改将成为数据库的最新版本，并且对其他事务可见。</p><p>当一个事务回滚时，它所做的修改将被撤销，对其他事务不可见。</p><h3 id="版本的回收"><a href="#版本的回收" class="headerlink" title="版本的回收"></a>版本的回收</h3><p>MVCC 定期回收版本，防止数据库中的版本无限增长，删除已经不再需要的旧版本数据，释放空间</p><h2 id="一致性非锁定读与锁定读"><a href="#一致性非锁定读与锁定读" class="headerlink" title="一致性非锁定读与锁定读"></a>一致性非锁定读与锁定读</h2><h3 id="一致性非锁定读"><a href="#一致性非锁定读" class="headerlink" title="一致性非锁定读"></a>一致性非锁定读</h3><p>使用版本号或者时间戳字段，在更新数据的同时版本号 + 1 或者更新时间戳。将当前可见的版本号与对应记录的版本号进行比对，如果记录的版本小于可见版本，则表示该记录可见</p><p>在 InnoDB 存储引擎中，多版本控制 (multi versioning) 就是对<strong>非锁定读</strong>的实现。如果读取的行正在执行 <code>DELETE</code> 或 <code>UPDATE</code> 操作，这时读取操作不会去等待行上锁的释放。相反地，InnoDB 存储引擎会去读取行的一个快照数据，对于这种读取历史数据的方式，我们叫它快照读 (snapshot read)</p><p>在<strong>读取未提交</strong>和<strong>读取已提交</strong>的隔离等级下，执行普通的 SELECT 语句，会使用<strong>一致性非锁定读</strong>，在<strong>可重复读</strong>隔离等级下防止部分幻读</p><h3 id="锁定读"><a href="#锁定读" class="headerlink" title="锁定读"></a>锁定读</h3><p>在<strong>锁定读</strong>下，读取的是数据的最新版本，这种读也被称为<strong>当前读（current read）</strong>。锁定读会对读取到的记录加锁。在执行更新操作时，使用锁定读。</p><p>在一致性非锁定读下，即使读取的记录已被其它事务加上 X 锁，这时记录也是可以被读取的，即读取的快照数据。上面说了，在<strong>重复读</strong>隔离等级下 MVCC 防止了部分幻读， 是指在<strong>一致性非锁定读</strong>情况下，只能读取到第一次查询之前所插入的数据。</p><p>如果是当前读 ，每次读取的都是最新数据，这时如果两次查询中间有其它事务插入数据，就会产生幻读。所以， InnoDB 在实现<strong>重复读</strong>隔离等级时，如果执行的是<strong>当前读</strong>，则会对读取的记录使用 Next-key Lock ，来防止其它事务在间隙间插入数据。</p><h2 id="InnoDB-对-MVCC-的实现"><a href="#InnoDB-对-MVCC-的实现" class="headerlink" title="InnoDB 对 MVCC 的实现"></a>InnoDB 对 MVCC 的实现</h2><p>实现依赖于：<strong>隐藏字段、Read View、undo log</strong>。在内部实现中，InnoDB 通过数据行的 <code>DB_TRX_ID</code> 和 <code>Read View</code> 来判断数据的可见性，如不可见，则通过数据行的 <code>DB_ROLL_PTR</code> 找到 <code>undo log</code> 中的历史版本。每个事务读到的数据版本可能是不一样的，在同一个事务中，用户只能看到该事务创建 <code>Read View</code> 之前已经提交的修改和该事务本身做的修改。</p><h3 id="隐藏字段"><a href="#隐藏字段" class="headerlink" title="隐藏字段"></a>隐藏字段</h3><p>InnoDB 为每行数据添加三个隐藏字段：</p><ul><li><strong>DB_TRX_ID</strong>：表示最后一次插入或更新该行的事务 id。此外，DELETE 操作在内部被视为更新，只不过会在记录头 Record header 中的 deleted_flag 字段将其标记为已删除</li><li><strong>DB_ROLL_PTR</strong>：回滚指针，指向该行的 undo log 。如果该行未被更新，则为空</li><li><strong>DB_ROW_ID</strong>：如果没有设置主键且该表没有唯一非空索引时，InnoDB 会使用该 id 来生成聚簇索引</li></ul><h3 id="Read-View"><a href="#Read-View" class="headerlink" title="Read View"></a>Read View</h3><p>判断可见性，保存了 <strong>“当前对本事务不可见的其他活跃事务”</strong>：</p><p><strong>m_low_limit_id</strong>：目前出现过的最大的事务 ID+1，即下一个将被分配的事务 ID。大于等于这个 ID 的数据版本均不可见</p><p><strong>m_up_limit_id</strong>：活跃事务列表 m_ids 中最小的事务 ID，如果 m_ids 为空，则 m_up_limit_id 为 m_low_limit_id。小于这个 ID 的数据版本均可见</p><p><strong>m_ids</strong>：Read View 创建时其他未提交的活跃事务 ID 列表。创建 Read View 时，将当前未提交事务 ID 记录下来，后续即使它们修改了记录行的值，对于当前事务也是不可见的。m_ids 不包括当前事务自己和已提交的事务（正在内存中）</p><p><strong>m_creator_trx_id</strong>：创建该 Read View 的事务 ID</p><h3 id="undo-log"><a href="#undo-log" class="headerlink" title="undo log"></a>undo log</h3><ul><li>事务回滚时用于将数据恢复</li><li>在 MVCC 中，读取记录时，若该记录被其他事务占用或当前版本对该事务不可见，则可以通过 <strong>undo log</strong> 读取之前的版本数据，以此实现<strong>非锁定读</strong></li></ul><p>在 InnoDB 中分为两种：</p><ol><li><strong>insert undo log</strong>：在 insert 操作中产生的 <strong>undo log</strong>。因为 insert 操作的记录只对事务本身可见，对其他事务不可见，故该 <strong>undo log</strong> 可以在事务提交后直接删除。</li><li><strong>update undo log</strong>：update 或 delete 操作中产生的 undo log。该 undo log 可能需要提供 MVCC 机制，因此不能在事务提交时就进行删除。提交时放入 undo log 链表，等待 purge 线程 进行最后的删除。不同事务或者相同事务的对同一记录行的修改，会使该记录行的 undo log 成为一条链表，链首就是最新的记录，链尾就是最早的旧记录。</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;MVCC&quot;&gt;&lt;a href=&quot;#MVCC&quot; class=&quot;headerlink&quot; title=&quot;MVCC&quot;&gt;&lt;/a&gt;MVCC&lt;/h2&gt;&lt;p&gt;多版本并发控制 (MVCC, Multi-Version Concurrency Control)，一种并发控制机制，用于在</summary>
      
    
    
    
    
    <category term="MySQL" scheme="https://yzlzzz.xyz/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>MySQL 事务</title>
    <link href="https://yzlzzz.xyz/2024/08/27/mysql-transaction/"/>
    <id>https://yzlzzz.xyz/2024/08/27/mysql-transaction/</id>
    <published>2024-08-26T16:13:20.000Z</published>
    <updated>2024-09-13T16:15:03.514Z</updated>
    
    <content type="html"><![CDATA[<h2 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h2><p>事务是构成单一的逻辑工作单元的操作集合，要么全部执行，要么全都不执行。</p><h3 id="事务特性"><a href="#事务特性" class="headerlink" title="事务特性"></a>事务特性</h3><ul><li><strong>原子性</strong>：事务是不可分割的，要么执行全部操作，要么根本不执行</li><li><strong>一致性</strong>：以隔离的方式执行事务，以保持数据库的一致性</li><li><strong>隔离性</strong>：事务是单一的单元，他的操作不能被其它不属于事务的数据库操作分割开</li><li><strong>持久性</strong>：在一个事务成功完成后，他对数据库的改变必须是永久的</li></ul><h2 id="并发事务的问题"><a href="#并发事务的问题" class="headerlink" title="并发事务的问题"></a>并发事务的问题</h2><h3 id="脏读（dirty-read）"><a href="#脏读（dirty-read）" class="headerlink" title="脏读（dirty read）"></a>脏读（dirty read）</h3><p>一个事务读取数据并且对数据进行了修改，这个修改对其他事务来说是可见的，即使当前事务没有提交。</p><p>这时另一个事务读取了这个还未提交的数据，此时第一个事务回滚，数据并没有被提交到数据库，第二个事务读取到的就是脏数据。</p><h3 id="丢失修改（lost-to-modify）"><a href="#丢失修改（lost-to-modify）" class="headerlink" title="丢失修改（lost to modify）"></a>丢失修改（lost to modify）</h3><p>在一个事务读取一个数据时，另外一个事务也访问了该数据，那么在第一个事务中修改了这个数据后，第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失，因此称为丢失修改。</p><h3 id="不可重复读（unrepeatable-read）"><a href="#不可重复读（unrepeatable-read）" class="headerlink" title="不可重复读（unrepeatable read）"></a>不可重复读（unrepeatable read）</h3><p>一个事务内多次读同一数据。在这个事务还没有结束时，另一个事务也访问该数据。在第一个事务中的两次读数据之间，由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。</p><h3 id="幻读（phantom-read）"><a href="#幻读（phantom-read）" class="headerlink" title="幻读（phantom read）"></a>幻读（phantom read）</h3><p>一个事务读取了几行数据，接着另一个并发事务插入了一些数据时。在随后的查询中，第一个事务就会发现多了一些原本不存在的记录，幻读与不可重复读类似</p><h3 id="不可重复读与幻读的区别"><a href="#不可重复读与幻读的区别" class="headerlink" title="不可重复读与幻读的区别"></a>不可重复读与幻读的区别</h3><ul><li>不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改；</li><li>幻读的重点在于记录新增比如多次执行同一条查询语句时，发现查到的记录增加了。</li></ul><p>幻读其实可以看作是不可重复读的一种特殊情况</p><h2 id="并发事务的控制方式"><a href="#并发事务的控制方式" class="headerlink" title="并发事务的控制方式"></a>并发事务的控制方式</h2><h3 id="锁"><a href="#锁" class="headerlink" title="锁"></a>锁</h3><p>通过锁来显式控制共享资源而不是通过调度手段，MySQL 中主要是通过<strong>读写锁</strong>来实现并发控制。（悲观锁）</p><p><strong>共享锁（S 锁）</strong>：又称<strong>读锁</strong>，事务在读取记录的时候获取共享锁，允许多个事务同时获取（锁兼容）。</p><p><strong>排他锁（X 锁）</strong>：又称<strong>写锁&#x2F;独占锁</strong>，事务在修改记录的时候获取排他锁，不允许多个事务同时获取。如果一个记录已经被加了排他锁，那其他事务不能再对这条记录加任何类型的锁（锁不兼容）。</p><p>可以分为表级锁与行级锁，InnoDB 支持表级锁，也支持行级锁。行级锁的粒度更小，仅对相关的记录上锁即可，对于并发写入操作来说， InnoDB 的性能更高。</p><h2 id="MVCC"><a href="#MVCC" class="headerlink" title="MVCC"></a>MVCC</h2><p>多版本并发控制（MVCC，Multi-version concurrency control），对一份数据会存储多个版本，通过事务的可见性来保证事务能看到自己应该看到的版本。通常会有一个全局的版本分配器来为每一行数据设置版本号，版本号是唯一的。（乐观锁）</p><h2 id="事务隔离级别总结"><a href="#事务隔离级别总结" class="headerlink" title="事务隔离级别总结"></a>事务隔离级别总结</h2><ul><li><strong>读取未提交（READ-UNCOMMITTED）</strong> ：最低的隔离级别，允许读取尚未提交的数据变更，可能会导致脏读、幻读或不可重复读。</li><li><strong>读取已提交（READ-COMMITTED）</strong>：允许读取并发事务已经提交的数据，可以阻止脏读，但是幻读或不可重复读仍有可能发生。</li><li><strong>可重复读（REPEATABLE-READ）</strong>：对同一字段的多次读取结果都是一致的，除非数据是被本身事务自己所修改，可以阻止脏读和不可重复读，但幻读仍有可能发生。</li><li><strong>可串行化（SERIALIZABLE）</strong>：最高的隔离级别，完全服从 ACID 的隔离级别。所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。</li></ul><p>MySQL InnoDB 存储引擎的默认支持的隔离级别是<strong>可重复读</strong></p><p>InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的，核心思想就是一个事务在操作某张表数据的时候，另外一个事务不允许新增或者删除这张表中的数据了。主要有下面两种情况：</p><ul><li><strong>快照读</strong>：由 MVCC 机制来保证不出现幻读。</li><li><strong>当前读</strong>：使用 Next-Key Lock 进行加锁来保证不出现幻读，Next-Key Lock 是行锁（Record Lock）和间隙锁（Gap Lock）的结合，行锁只能锁住已经存在的行，为了避免插入新行，需要依赖间隙锁。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;事务&quot;&gt;&lt;a href=&quot;#事务&quot; class=&quot;headerlink&quot; title=&quot;事务&quot;&gt;&lt;/a&gt;事务&lt;/h2&gt;&lt;p&gt;事务是构成单一的逻辑工作单元的操作集合，要么全部执行，要么全都不执行。&lt;/p&gt;
&lt;h3 id=&quot;事务特性&quot;&gt;&lt;a href=&quot;#事务特性&quot; </summary>
      
    
    
    
    
    <category term="MySQL" scheme="https://yzlzzz.xyz/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>MySQL 索引</title>
    <link href="https://yzlzzz.xyz/2024/08/26/mysql-index/"/>
    <id>https://yzlzzz.xyz/2024/08/26/mysql-index/</id>
    <published>2024-08-25T16:11:19.000Z</published>
    <updated>2024-09-13T16:15:03.521Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>索引是一种用于快速查询和检索数据的数据结构，其本质可以看成是一种排序好的数据结构。</p><p>在 MySQL 中，MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构</p><h2 id="底层结构"><a href="#底层结构" class="headerlink" title="底层结构"></a>底层结构</h2><table><thead><tr><th>B 树</th><th>B+ 树</th></tr></thead><tbody><tr><td>B 树的所有节点既存放键(key) 也存放数据(data)</td><td>B+ 树只有叶子节点存放 key 和 data，其他内节点只存放 key。</td></tr><tr><td>B 树的叶子节点都是独立的</td><td>B+ 树的叶子节点有一条引用链指向与它相邻的叶子节点</td></tr><tr><td>B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找，可能还没有到达叶子节点，检索就结束了</td><td>B+ 树的检索效率就很稳定了，任何查找都是从根节点到叶子节点的过程，叶子节点的顺序检索很明显</td></tr><tr><td>B 树中进行范围查询时，首先找到要查找的下限，然后对 B 树进行中序遍历，直到找到查找的上限</td><td>B+ 树的范围查询，只需要对链表进行遍历即可</td></tr></tbody></table><h2 id="索引的类型"><a href="#索引的类型" class="headerlink" title="索引的类型"></a>索引的类型</h2><h3 id="按照底层存储方式角度划分"><a href="#按照底层存储方式角度划分" class="headerlink" title="按照底层存储方式角度划分"></a>按照底层存储方式角度划分</h3><ul><li><strong>聚簇（聚集）索引：</strong>索引结构和数据一起存放的索引，InnoDB 中的主键索引就属于聚簇索引。</li><li><strong>非聚簇索引：</strong>索引结构和数据分开存放的索引，二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎，不管主键还是非主键，使用的都是非聚簇索引。</li></ul><p><strong>聚簇索引的优缺点</strong></p><p><strong>优点</strong>：</p><ul><li><strong>查询速度非常快</strong>：聚簇索引的查询速度非常的快，因为整个 B+ 树本身就是一颗多叉平衡树，叶子节点也都是有序的，定位到索引的节点，就相当于定位到了数据。相比于非聚簇索引， 聚簇索引少了一次读取数据的 IO 操作。</li><li><strong>对排序查找和范围查找优化</strong>：聚簇索引对于主键的排序查找和范围查找速度非常快。</li></ul><p><strong>缺点：</strong></p><ul><li><strong>依赖于有序的数据</strong>：因为 B+ 树是多路平衡树，如果索引的数据不是有序的，那么就需要在插入时排序，如果数据是整型还好，否则类似于字符串或 UUID 这种又长又难比较的数据，插入或查找的速度肯定比较慢。</li><li><strong>更新代价大</strong>：如果对索引列的数据被修改时，那么对应的索引也将会被修改，而且聚簇索引的叶子节点还存放着数据，修改代价肯定是较大的，所以对于主键索引来说，主键一般都是不可被修改的。</li></ul><p><strong>非聚簇索引的优缺点</strong></p><p><strong>优点</strong>：</p><ul><li>更新代价比聚簇索引要小。非聚簇索引的更新代价就没有聚簇索引那么大了，非聚簇索引的叶子节点是不存放数据的。</li></ul><p><strong>缺点</strong>：</p><ul><li><strong>依赖于有序的数据：</strong> 跟聚簇索引一样，非聚簇索引也依赖于有序的数据</li><li><strong>可能会二次查询(回表)：</strong> 这应该是非聚簇索引最大的缺点了。 当查到索引对应的指针或主键后，可能还需要根据指针或主键再到数据文件或表中查询。</li></ul><h3 id="按照应用维度划分"><a href="#按照应用维度划分" class="headerlink" title="按照应用维度划分"></a>按照应用维度划分</h3><ul><li>主键索引：加速查询 + 列值唯一（不可以有 NULL）+ 表中只有一个。</li><li>普通索引：仅加速查询。</li><li>唯一索引：加速查询 + 列值唯一（可以有 NULL）。</li><li>覆盖索引：一个索引包含（覆盖）所有需要查询的字段的值。</li><li>联合索引：多列值组成一个索引，专门用于组合搜索，其效率大于索引合并。</li><li>全文索引：对文本的内容进行分词，进行搜索。目前只有 <code>CHAR</code>、<code>VARCHAR</code> ，<code>TEXT</code> 列上可以创建全文索引。一般不会使用，效率较低，通常使用搜索引擎如 ElasticSearch 代替。</li></ul><h2 id="索引优化"><a href="#索引优化" class="headerlink" title="索引优化"></a>索引优化</h2><h3 id="选择合适的字段创建索引"><a href="#选择合适的字段创建索引" class="headerlink" title="选择合适的字段创建索引"></a>选择合适的字段创建索引</h3><ul><li><strong>不是 NULL 的字段</strong>：索引字段的数据应该尽量不为 NULL，因为对于数据为 NULL 的字段，数据库较难优化。如果字段频繁被查询，但又避免不了为 NULL，建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。</li><li><strong>被频繁查询的字段</strong>：我们创建索引的字段应该是查询操作非常频繁的字段。</li><li><strong>被作为条件查询的字段</strong>：被作为 WHERE 条件查询的字段，应该被考虑建立索引。</li><li><strong>频繁需要排序的字段</strong>：索引已经排序，这样查询可以利用索引的排序，加快排序查询时间。</li><li><strong>被经常频繁用于连接的字段</strong>：经常用于连接的字段可能是一些外键列，对于外键列并不一定要建立外键，只是说该列涉及到表与表的关系。对于频繁被连接查询的字段，可以考虑建立索引，提高多表连接查询的效率。</li></ul><h3 id="频繁更新的字段应该慎重建立索引"><a href="#频繁更新的字段应该慎重建立索引" class="headerlink" title="频繁更新的字段应该慎重建立索引"></a>频繁更新的字段应该慎重建立索引</h3><p>索引能带来查询上的效率，但维护索引的成本也是不小的。</p><p>如果一个字段不被经常查询，反而被经常修改，不应该在这种字段上建立索引</p><h3 id="限制每张表上的索引数量"><a href="#限制每张表上的索引数量" class="headerlink" title="限制每张表上的索引数量"></a>限制每张表上的索引数量</h3><p>索引并不是越多越好，建议单张表索引不超过 5 个</p><p>索引可以提高效率同样可以降低效率。索引可以增加查询效率，但同样也会降低插入和更新的效率，甚至有些情况下会降低查询效率。</p><p>因为 MySQL 优化器在选择如何优化查询时，会根据统一信息，对每一个可以用到的索引来进行评估，以生成出一个最好的执行计划，如果同时有很多个索引都可以用于查询，就会<strong>增加</strong> MySQL 优化器生成执行计划的时间，同样会降低查询性能。</p><h3 id="考虑联合索引而不是单列索引"><a href="#考虑联合索引而不是单列索引" class="headerlink" title="考虑联合索引而不是单列索引"></a>考虑联合索引而不是单列索引</h3><p>因为索引是需要占用磁盘空间的，可以简单理解为每个索引都对应着一颗 B+ 树</p><p>如果一个表的字段过多，索引过多，那么当这个表的数据达到一个体量后，索引占用的空间也是很多的，且修改索引时，耗费的时间也是较多的。</p><p>如果是联合索引，多个字段在一个索引上，那么将会节约很大磁盘空间，且修改数据的操作效率也会提升。</p><h3 id="避免冗余索引"><a href="#避免冗余索引" class="headerlink" title="避免冗余索引"></a>避免冗余索引</h3><p>冗余索引指的是索引的功能相同，（name, city）和（name）这两个索引就是冗余索引，能够命中前者的查询肯定是能够命中后者的 在大多数情况下，都应该尽量扩展已有的索引而不是创建新索引。</p><h3 id="字符串类型的字段使用前缀索引代替普通索引"><a href="#字符串类型的字段使用前缀索引代替普通索引" class="headerlink" title="字符串类型的字段使用前缀索引代替普通索引"></a>字符串类型的字段使用前缀索引代替普通索引</h3><p>前缀索引仅限于字符串类型，较普通索引会占用更小的空间，所以可以考虑使用前缀索引代替普通索引</p><h3 id="避免索引失效"><a href="#避免索引失效" class="headerlink" title="避免索引失效"></a>避免索引失效</h3><ul><li><code>SELECT *</code> 不会直接导致索引失效（如果不走索引大概率是因为 where 查询范围过大导致的），但它可能会带来一些其他的性能问题比如造成网络传输和数据处理的浪费、无法使用索引覆盖;</li><li>创建了联合索引，但查询条件未遵守最左匹配原则;</li><li>在索引列上进行计算、函数、类型转换等操作;</li><li>以 % 开头的 LIKE 查询比如 <code>LIKE &#39;%abc&#39;</code>;</li><li>查询条件中使用 OR，且 OR 的前后条件中有一个列没有索引，涉及的索引都不会被使用到;</li><li>IN 的取值范围较大时会导致索引失效，走全表扫描(NOT IN 和 IN 的失效场景相同);</li><li>发生隐式转换</li></ul><h3 id="删除长期未使用的索引"><a href="#删除长期未使用的索引" class="headerlink" title="删除长期未使用的索引"></a>删除长期未使用的索引</h3><p>长期未使用的索引的存在会造成不必要的性能损耗</p><h3 id="使用-EXPLAIN-检查查询语句"><a href="#使用-EXPLAIN-检查查询语句" class="headerlink" title="使用 EXPLAIN 检查查询语句"></a>使用 EXPLAIN 检查查询语句</h3><p>可以使用 <code>EXPLAIN</code> 命令来分析 SQL 的执行计划，这样就知道语句是否成功命中索引。</p><blockquote><p><strong>执行计划：</strong>一条 SQL 语句在经过 MySQL 查询优化器的优化后，具体的执行方式。</p></blockquote><p><code>EXPLAIN</code> 并不会真的去执行相关的语句，而是通过 查询优化器 对语句进行分析，找出最优的查询方案，并显示对应的信息。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h2&gt;&lt;p&gt;索引是一种用于快速查询和检索数据的数据结构，其本质可以看成是一种排序好的数据结构。&lt;/p&gt;
&lt;p&gt;在 MySQL 中，MyISAM 引擎和 </summary>
      
    
    
    
    
    <category term="MySQL" scheme="https://yzlzzz.xyz/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>三种常见的缓存读写策略</title>
    <link href="https://yzlzzz.xyz/2024/08/19/cache-pattern/"/>
    <id>https://yzlzzz.xyz/2024/08/19/cache-pattern/</id>
    <published>2024-08-18T16:23:11.000Z</published>
    <updated>2024-09-04T18:00:51.678Z</updated>
    
    <content type="html"><![CDATA[<h2 id="旁路缓存模式（cache-aside-pattern）"><a href="#旁路缓存模式（cache-aside-pattern）" class="headerlink" title="旁路缓存模式（cache aside pattern）"></a>旁路缓存模式（cache aside pattern）</h2><p>旁路缓存中服务端需要同时更新数据库与缓存，并以数据库结果为准。适合读多写少的场景。</p><p>写：</p><ol><li>更新数据库</li><li>删除缓存</li></ol><p>读：</p><ol><li>从缓存中读取数据</li><li>如果读取不到，就从数据库中读取</li><li>并将结果写入缓存</li></ol><h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p><strong>在写的过程中，更新数据库后，为什么删除缓存，而不是更新缓存？</strong></p><ol><li><strong>服务端资源浪费：</strong>缓存中存放的数据可能需要经过计算才能得出，如果在这个过程中缓存没有被读过，而又频繁的写，会造成资源的浪费。</li><li><strong>数据不一致问题：</strong>并发场景下，更新缓存数据产生数据不一致的可能性更大。</li></ol><p><strong>在写时，为什么先更新数据库，后删除缓存？</strong></p><p>首先，在速度上更新缓存速度 ＞ 更新数据库，无论是先更新数据库还是先更新缓存，都会存在数据库与缓存数据不一致的情况。</p><p>先删除缓存，后更新数据库，那么数据库与缓存不一致的时间是是更新数据库的时间；</p><p>先更新数据库，后删除缓存，数据不一致的时间是删除缓存的时间；</p><p>因此，先更新数据库，后删除缓存，最小化了数据不一致时间</p><p>在数据强一致场景下，为了保证数据库与缓存数据的一致性，可以在更新时加锁来保证。</p><h3 id="模式缺陷"><a href="#模式缺陷" class="headerlink" title="模式缺陷"></a>模式缺陷</h3><p><strong>写操作过多，会导致缓存频繁被删除，影响缓存命中率，需要导致频繁的从数据库中读取数据，影响读取效率。</strong></p><h3 id="业务场景"><a href="#业务场景" class="headerlink" title="业务场景"></a>业务场景</h3><p><strong>适用场景：</strong></p><ul><li><strong>高读低写场景</strong>：数据读取频繁但更新不频繁，例如社交媒体用户资料、电子商务网站的商品详情页面等。用户会频繁查看这些数据，但数据的实际更改不频繁。</li><li><strong>数据对一致性要求不高的场景</strong>：对数据的一致性要求不严格，允许在短时间内缓存和数据库之间存在数据不一致的情况。</li><li><strong>缓存命中率不高的场景</strong>：对于一些查询频率较低的数据，使用旁路缓存可以避免在缓存中存储大量不常用的数据。</li></ul><p><strong>典型应用：</strong></p><ul><li>内容管理系统（CMS）</li><li>商品详情页、博客文章等不经常更新的数据</li></ul><h2 id="读写穿透模式（read-write-through-pattern）"><a href="#读写穿透模式（read-write-through-pattern）" class="headerlink" title="读写穿透模式（read&#x2F;write through pattern）"></a>读写穿透模式（read&#x2F;write through pattern）</h2><p>在读写穿透模式下，服务端中将缓存视为主要存储点，从缓存中读取数据并写入，缓存服务写入数据库。</p><p>写：</p><ol><li>读取缓存，如果缓存不存在，直接更新至数据库</li><li>如果缓存存在，更新缓存，缓存服务更新数据库</li></ol><p>读：</p><ol><li>从缓存中读取数据</li><li>如果缓存不存在，缓存服务从数据库中读取数据写入缓存，缓存服务返回数据</li></ol><p>对应用程序来说，缓存与数据库的交互是透明的，应用程序只负责跟数据库交互，减轻应用程序责任。</p><h3 id="业务场景-1"><a href="#业务场景-1" class="headerlink" title="业务场景"></a>业务场景</h3><p><strong>适用场景：</strong></p><ul><li><strong>读多写少场景</strong>：数据读取操作远多于写入操作</li><li><strong>数据一致性要求较高的场景</strong>：对数据的实时性和一致性要求高，不能容忍缓存和数据库之间存在不一致的情况，例如金融系统中的账户余额查询和更新。</li><li><strong>缓存和数据库有统一管理需求的场景</strong>：需要简化应用程序代码，让缓存系统负责数据加载和持久化。</li></ul><p><strong>典型应用：</strong></p><ul><li>金融交易系统（如银行系统的账户余额）</li><li>电商库存管理</li><li>实时统计系统（如实时分析用户行为数据）</li></ul><h2 id="异步缓存写入模式（write-behind-pattern）"><a href="#异步缓存写入模式（write-behind-pattern）" class="headerlink" title="异步缓存写入模式（write behind pattern）"></a>异步缓存写入模式（write behind pattern）</h2><p>异步缓存写入与读写穿透模式相似，读写穿透模式是同步更新，异步缓存模式是异步批量更新数据库。</p><p>这种情况下读写性能高，适合经常变化且对数据一致性要求不高的场景，例如点赞数、播放数等。</p><h3 id="模式缺陷-1"><a href="#模式缺陷-1" class="headerlink" title="模式缺陷"></a>模式缺陷</h3><p>数据一致性差，存在还未更新至数据库，缓存服务直接挂掉的情况。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;旁路缓存模式（cache-aside-pattern）&quot;&gt;&lt;a href=&quot;#旁路缓存模式（cache-aside-pattern）&quot; class=&quot;headerlink&quot; title=&quot;旁路缓存模式（cache aside pattern）&quot;&gt;&lt;/a&gt;旁路缓存模</summary>
      
    
    
    
    
    <category term="数据库" scheme="https://yzlzzz.xyz/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    <category term="缓存" scheme="https://yzlzzz.xyz/tags/%E7%BC%93%E5%AD%98/"/>
    
  </entry>
  
  <entry>
    <title>CopyOnWrite</title>
    <link href="https://yzlzzz.xyz/2024/08/18/copyonwrite/"/>
    <id>https://yzlzzz.xyz/2024/08/18/copyonwrite/</id>
    <published>2024-08-17T17:50:58.000Z</published>
    <updated>2024-09-04T17:53:09.528Z</updated>
    
    <content type="html"><![CDATA[<h2 id="CopyOnWrite-容器"><a href="#CopyOnWrite-容器" class="headerlink" title="CopyOnWrite 容器"></a>CopyOnWrite 容器</h2><p>CopyOnWrite 容器即<strong>写时复制的容器</strong>，当我们往一个容器中添加元素的时候，不直接往容器中添加，而是将当前容器进行 copy，复制出来一个新的容器，然后向新容器中添加我们需要的元素，最后将原容器的引用指向新容器。</p><p>这样做的好处在于，我们可以在并发的场景下对容器进行”读操作”而不需要”加锁”，从而达到读写分离的目的。</p><blockquote><p>CopyOnWrite 机制：当有多个调用者同时去请求一个资源数据的时候，有一个调用者出于某些原因需要对当前的数据源进行修改，这个时候系统将会复制一个当前数据源的副本给调用者修改。</p></blockquote><h2 id="CopyOnWriteArrayList"><a href="#CopyOnWriteArrayList" class="headerlink" title="CopyOnWriteArrayList"></a>CopyOnWriteArrayList</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>CopyOnWriteArrayList 经常被用于“读多写少”的并发场景，是因为 CopyOnWriteArrayList 无需任何同步措施，大大增强了读的性能。</p><p>在 Java 中遍历线程非安全的 List(如：ArrayList 和 LinkedList)的时候，若中途有别的线程对 List 容器进行修改，那么会抛出 ConcurrentModificationException 异常。CopyOnWriteArrayList 由于其”读写分离”，遍历和修改操作分别作用在不同的 List 容器，所以在使用迭代器遍历的时候，则不会抛出异常。</p><p><strong>内存消耗大：</strong>CopyOnWriteArrayList 每次执行写操作都会将原容器进行拷贝了一份，数据量大的时候，内存会存在较大的压力，可能会引起频繁 Full GC。</p><p><strong>数据不一致：</strong>CopyOnWriteArrayList 由于实现的原因，写和读分别作用在不同新老容器上，在写操作执行过程中，读不会阻塞，但读取到的却是老容器的数据。</p><h3 id="add-方法"><a href="#add-方法" class="headerlink" title="add() 方法"></a>add() 方法</h3><ol><li>使用 synchronized 锁，保证线程安全</li><li>复制原容器</li><li>在新副本上进行写操作</li><li>最后切换引用</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Appends the specified element to the end of this list.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> e element to be appended to this list</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> &#123;<span class="doctag">@code</span> true&#125; (as specified by &#123;<span class="doctag">@link</span> Collection#add&#125;)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">            Object[] es = getArray();</span><br><span class="line">            <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> es.length;</span><br><span class="line">            es = Arrays.copyOf(es, len + <span class="number">1</span>);</span><br><span class="line">            es[len] = e;</span><br><span class="line">            setArray(es);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="remove-方法"><a href="#remove-方法" class="headerlink" title="remove() 方法"></a>remove() 方法</h3><ol><li>使用 synchronized 锁，保证线程安全</li><li>将要 remove 元素之外的其他元素拷贝到新的副本中</li><li>将原容器的引用指向新的副本中</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Removes the element at the specified position in this list.</span></span><br><span class="line"><span class="comment">     * Shifts any subsequent elements to the left (subtracts one from their</span></span><br><span class="line"><span class="comment">     * indices).  Returns the element that was removed from the list.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> IndexOutOfBoundsException &#123;<span class="doctag">@inheritDoc</span>&#125;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">remove</span><span class="params">(<span class="type">int</span> index)</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">            Object[] es = getArray();</span><br><span class="line">            <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> es.length;</span><br><span class="line">            <span class="type">E</span> <span class="variable">oldValue</span> <span class="operator">=</span> elementAt(es, index);</span><br><span class="line">            <span class="type">int</span> <span class="variable">numMoved</span> <span class="operator">=</span> len - index - <span class="number">1</span>;</span><br><span class="line">            Object[] newElements;</span><br><span class="line">            <span class="keyword">if</span> (numMoved == <span class="number">0</span>)</span><br><span class="line">                newElements = Arrays.copyOf(es, len - <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                newElements = <span class="keyword">new</span> <span class="title class_">Object</span>[len - <span class="number">1</span>];</span><br><span class="line">                System.arraycopy(es, <span class="number">0</span>, newElements, <span class="number">0</span>, index);</span><br><span class="line">                System.arraycopy(es, index + <span class="number">1</span>, newElements, index,</span><br><span class="line">                                 numMoved);</span><br><span class="line">            &#125;</span><br><span class="line">            setArray(newElements);</span><br><span class="line">            <span class="keyword">return</span> oldValue;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="get-方法"><a href="#get-方法" class="headerlink" title="get() 方法"></a>get() 方法</h3><p>在 CopyOnWriteArrayList 中读操作效率很高，因为没有加锁</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">_    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * &#123;<span class="doctag">@inheritDoc</span>&#125;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> IndexOutOfBoundsException &#123;<span class="doctag">@inheritDoc</span>&#125;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">get</span><span class="params">(<span class="type">int</span> index)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> elementAt(getArray(), index);</span><br><span class="line">__    &#125;_</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">    <span class="keyword">static</span> &lt;E&gt; E <span class="title function_">elementAt</span><span class="params">(Object[] a, <span class="type">int</span> index)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (E) a[index];</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h2 id="业务场景"><a href="#业务场景" class="headerlink" title="业务场景"></a>业务场景</h2><p><code>CopyOnWriteArrayList</code> 主要适用于<strong>读多写少</strong>的业务场景，即读操作非常频繁而写操作相对较少的情况。因为它的读取操作不需要加锁，可以保证高效的并发读性能。</p><ul><li><strong>配置管理</strong>：在某些系统中，应用的配置数据可能存储在一个列表中，这些配置数据大部分时间是不变的，可能偶尔会被管理员更新。<code>CopyOnWriteArrayList</code> 可以在读取这些配置信息时提供高效的性能。</li><li><strong>版本控制</strong>：在一些场景下，可能需要快速拍摄某个数据状态的快照来记录历史数据，而不影响当前的数据处理。</li><li><strong>定期统计分析</strong>：例如，在大数据分析中，可能需要对数据进行定期统计，统计完成后再进行数据刷新。数据统计阶段有大量读操作，而数据刷新阶段则是批量的写操作。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;CopyOnWrite-容器&quot;&gt;&lt;a href=&quot;#CopyOnWrite-容器&quot; class=&quot;headerlink&quot; title=&quot;CopyOnWrite 容器&quot;&gt;&lt;/a&gt;CopyOnWrite 容器&lt;/h2&gt;&lt;p&gt;CopyOnWrite 容器即&lt;strong</summary>
      
    
    
    
    
    <category term="Java 并发" scheme="https://yzlzzz.xyz/tags/Java-%E5%B9%B6%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>ThreadLocal</title>
    <link href="https://yzlzzz.xyz/2024/08/17/threadlocal/"/>
    <id>https://yzlzzz.xyz/2024/08/17/threadlocal/</id>
    <published>2024-08-16T17:50:10.000Z</published>
    <updated>2024-09-04T17:53:09.524Z</updated>
    
    <content type="html"><![CDATA[<p><code>ThreadLocal</code> 是一个本地线程副本变量工具类。内部是一个<strong>弱引用</strong>的 Map 来维护。允许你为每个线程创建独立的变量副本，从而避免线程之间的并发问题。在多线程环境中，通常需要确保多个线程不会共享相同的变量，<code>ThreadLocal</code> 正是解决这个问题的一种机制。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadLocalExample</span> &#123;</span><br><span class="line">    <span class="comment">// 创建一个 ThreadLocal 变量</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;Integer&gt; threadLocalValue = ThreadLocal.withInitial(() -&gt; <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 启动两个线程来演示 ThreadLocal 的作用</span></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Task</span>(), <span class="string">&quot;Thread-1&quot;</span>);</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Task</span>(), <span class="string">&quot;Thread-2&quot;</span>);</span><br><span class="line"></span><br><span class="line">        thread1.start();</span><br><span class="line">        thread2.start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Task</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="comment">// 获取当前线程的 ThreadLocal 变量副本的值</span></span><br><span class="line">            <span class="type">Integer</span> <span class="variable">value</span> <span class="operator">=</span> threadLocalValue.get();</span><br><span class="line">            System.out.println(Thread.currentThread().getName() + <span class="string">&quot; 初始值: &quot;</span> + value);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 修改 ThreadLocal 变量的值</span></span><br><span class="line">            threadLocalValue.set(value + <span class="number">1</span>);</span><br><span class="line">            System.out.println(Thread.currentThread().getName() + <span class="string">&quot; 修改后的值: &quot;</span> + threadLocalValue.get());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="核心概念"><a href="#核心概念" class="headerlink" title="核心概念"></a>核心概念</h2><p><strong>线程局部变量</strong>：每个线程都会拥有自己独立的 <code>ThreadLocal</code> 变量副本。一个线程的副本只属于该线程，其他线程无法访问或修改这个副本。</p><p><strong>避免共享状态</strong>：使用 <code>ThreadLocal</code> 可以避免多线程环境下的共享状态问题，因为每个线程操作的都是自己的变量副本。</p><p><strong>线程安全性</strong>：<code>ThreadLocal</code> 提供了一种简单的线程安全方式，避免了显式的同步（<code>synchronized</code>）。</p><h2 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h2><p><strong>数据库连接管理</strong>：可以为每个线程分配一个独立的数据库连接对象，以避免多个线程竞争同一个连接资源。</p><p><strong>Session</strong> <strong>管理</strong>：在 Web 应用中，可以使用 <code>ThreadLocal</code> 存储每个用户的 Session 信息。</p><p><strong>避免参数传递</strong>：在多个方法调用链中，可以使用 <code>ThreadLocal</code> 传递数据，而不需要在方法参数中显式传递。</p><h2 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h2><h3 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h3><p><code>Thread</code> 类有一个类型为 <code>ThreadLocal.ThreadLocalMap</code> 的实例变量 <code>threadLocals</code>，也就是说每个线程有一个自己的 <code>ThreadLocalMap</code>。</p><p><code>ThreadLocalMap</code> 有自己的独立实现，可以简单地将它的 <code>key</code> 视作 <code>ThreadLocal</code>，<code>value</code> 为代码中放入的值（实际上 <code>key</code> 并不是 <code>ThreadLocal</code> 本身，而是它的一个<strong>弱引用</strong>）。</p><p>每个线程在往 <code>ThreadLocal</code> 里放值的时候，都会往自己的 <code>ThreadLocalMap</code> 里存，读也是以 <code>ThreadLocal</code> 作为引用，在自己的 <code>map</code> 里找对应的 <code>key</code>，从而实现了线程隔离。</p><h3 id="内存泄露风险"><a href="#内存泄露风险" class="headerlink" title="内存泄露风险"></a>内存泄露风险</h3><p><code>ThreadLocalMap</code> 中的键是弱引用，值是强引用。即使 <code>ThreadLocal</code> 被垃圾回收了，值仍然可能被持有，导致内存泄漏。所以在不再需要时，应调用 <code>ThreadLocal.remove()</code> 方法清理相关数据。</p><blockquote><p><strong>强引用：</strong>我们常常 new 出来的对象就是强引用类型，只要强引用存在，垃圾回收器将永远不会回收被引用的对象，哪怕内存不足的时候<br><strong>弱引用：</strong>使用 WeakReference 修饰的对象被称为弱引用，只要发生垃圾回收，若这个对象只被弱引用指向，那么就会被回收</p></blockquote><h3 id="set-方法"><a href="#set-方法" class="headerlink" title="set() 方法"></a>set() 方法</h3><p>set() 方法具体流程如下：</p><ol><li>获取 Thread 中的 <code>ThreadLocalMap </code></li><li>如果不存在，则创建新的 <code>ThreadLocalMap</code></li><li>如果存在，则设置 map 中的值，key 为当前 <code>ThreadLocal</code>， value 为要设置的值</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Sets the current thread&#x27;s copy of this thread-local variable</span></span><br><span class="line"><span class="comment">     * to the specified value.  Most subclasses will have no need to</span></span><br><span class="line"><span class="comment">     * override this method, relying solely on the &#123;<span class="doctag">@link</span> #initialValue&#125;</span></span><br><span class="line"><span class="comment">     * method to set the values of thread-locals.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> value the value to be stored in the current thread&#x27;s copy of</span></span><br><span class="line"><span class="comment">     *        this thread-local.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(T value)</span> &#123;</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">        <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">        <span class="keyword">if</span> (map != <span class="literal">null</span>) &#123;</span><br><span class="line">            map.set(<span class="built_in">this</span>, value);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            createMap(t, value);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;code&gt;ThreadLocal&lt;/code&gt; 是一个本地线程副本变量工具类。内部是一个&lt;strong&gt;弱引用&lt;/strong&gt;的 Map 来维护。允许你为每个线程创建独立的变量副本，从而避免线程之间的并发问题。在多线程环境中，通常需要确保多个线程不会共享相同的变量，&lt;c</summary>
      
    
    
    
    
    <category term="Java 并发" scheme="https://yzlzzz.xyz/tags/Java-%E5%B9%B6%E5%8F%91/"/>
    
    <category term="JVM" scheme="https://yzlzzz.xyz/tags/JVM/"/>
    
  </entry>
  
  <entry>
    <title>下一个更大的元素</title>
    <link href="https://yzlzzz.xyz/2024/08/17/monotonous-stack/"/>
    <id>https://yzlzzz.xyz/2024/08/17/monotonous-stack/</id>
    <published>2024-08-16T17:07:08.000Z</published>
    <updated>2024-08-16T17:13:42.868Z</updated>
    
    <content type="html"><![CDATA[<p>这是我碰到的一道非常经典的单调栈的题目，题目中考察的正是单调栈的特性及用法，可以一起学习一下。</p><h2 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h2><p>给定一个循环数组 <code>nums</code> （ <code>nums[nums.length - 1]</code> 的下一个元素是 <code>nums[0]</code> ），返回 <em>nums</em>_ 中每个元素的 _<strong>下一个更大元素</strong> 。</p><p>数字 <code>x</code> 的 <strong>下一个更大的元素</strong> 是按数组遍历顺序，这个数字之后的第一个比它更大的数，这意味着你应该循环地搜索它的下一个更大的数。如果不存在，则输出 <code>-1</code> 。</p><p><strong>示例 1:</strong></p><p><strong>输入:</strong> nums &#x3D; [1,2,1]<br><strong>输出:</strong> [2,-1,2]<br><strong>解释:</strong></p><p>第一个 1 的下一个更大的数是 2；<br>数字 2 找不到下一个更大的数；<br>第二个 1 的下一个最大的数需要循环搜索，结果也是 2。</p><p><strong>示例 2:</strong></p><p><strong>输入:</strong> nums &#x3D; [1,2,3,4,3]<br><strong>输出:</strong> [2,3,4,-1,4]</p><p><strong>提示:</strong></p><ul><li><code>1 &lt;= nums.length &lt;= 10(4)</code></li><li><code>-10(9) &lt;= nums[i] &lt;= 10(9)</code></li></ul><h2 id="解析-1"><a href="#解析-1" class="headerlink" title="解析 1"></a>解析 1</h2><p>何为<strong>单调栈</strong>，即栈中栈底到栈顶存储的数字是单调的，而在本题中栈中存放的数组中对应的下标，这些下标所对应的数组中的数字在栈中是单调不升的。</p><p>总体思路为：</p><ul><li>因为数组为循环数组，所以要遍历两边数组</li><li>如果栈不为空且栈顶元素小于等于当前元素，将弹出栈顶元素，并将弹出元素的下个最大元素设为当前元素</li><li>并将当前元素下标压栈</li><li>重复以上过程</li></ul><p>遍历数组时，当前元素为 <code>nums[i]</code>，当前栈顶元素值小于该数组元素时，栈顶元素的下个更大值即为当前元素 <code>nums[i]</code>。只要遍历到比栈顶元素值更大的数，就意味着栈顶元素找到了答案，记录答案，然后弹出栈顶。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] nextGreaterElements(<span class="type">int</span>[] nums) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span>[] res = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">        Arrays.fill(res, -<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">        Deque&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">2</span> * n - <span class="number">1</span>; i++)&#123;</span><br><span class="line">            <span class="keyword">while</span>(!stack.isEmpty() &amp;&amp; nums[stack.peek()] &lt; nums[i % n])&#123;</span><br><span class="line">                res[stack.pop()] = nums[i % n];</span><br><span class="line">            &#125;</span><br><span class="line">            stack.push(i % n);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p><strong>时间复杂度：</strong></p><p>O(n)</p><p><strong>空间复杂度：</strong></p><p>O(n)</p><h2 id="解析-2"><a href="#解析-2" class="headerlink" title="解析 2"></a>解析 2</h2><p>解析 1 中是从左至右遍历数组中的元素的，还有一种自右向左遍历的思路，总体算法思路相似，细节略有不同。</p><p>自右向左遍历，栈中记录的是下一个更大元素的 <code>次选</code>。</p><h2 id="参考-2"><a href="#参考-2" class="headerlink" title="参考 2"></a>参考 2</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] nextGreaterElements(<span class="type">int</span>[] nums) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">        <span class="type">int</span>[] ans = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">        </span><br><span class="line">        Arrays.fill(ans, -<span class="number">1</span>);</span><br><span class="line">        Deque&lt;Integer&gt; stack = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span> * n - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> nums[i % n];</span><br><span class="line">            <span class="keyword">while</span> (!stack.isEmpty() &amp;&amp; x &gt;= stack.peek()) &#123;</span><br><span class="line">                <span class="comment">// 由于 x 的出现，栈顶元素永远不会是左边元素的「下一个更大元素」</span></span><br><span class="line">                stack.pop();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (i &lt; n &amp;&amp; !stack.isEmpty()) &#123;</span><br><span class="line">                ans[i] = stack.peek();</span><br><span class="line">            &#125;</span><br><span class="line">            stack.push(x);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ans;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p><strong>时间复杂度：</strong></p><p>O(n)</p><p><strong>空间复杂度：</strong></p><p>O(n)</p><h2 id="相关链接"><a href="#相关链接" class="headerlink" title="相关链接"></a>相关链接</h2><p><a href="https://leetcode.cn/problems/next-greater-element-ii/">503. 下一个更大元素 II - 力扣（LeetCode）</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这是我碰到的一道非常经典的单调栈的题目，题目中考察的正是单调栈的特性及用法，可以一起学习一下。&lt;/p&gt;
&lt;h2 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h2&gt;&lt;p&gt;给定一个循环数</summary>
      
    
    
    
    
    <category term="算法" scheme="https://yzlzzz.xyz/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="Java" scheme="https://yzlzzz.xyz/tags/Java/"/>
    
    <category term="栈" scheme="https://yzlzzz.xyz/tags/%E6%A0%88/"/>
    
  </entry>
  
  <entry>
    <title>AQS</title>
    <link href="https://yzlzzz.xyz/2024/08/16/aqs/"/>
    <id>https://yzlzzz.xyz/2024/08/16/aqs/</id>
    <published>2024-08-15T18:20:19.000Z</published>
    <updated>2024-09-03T18:22:32.198Z</updated>
    
    <content type="html"><![CDATA[<p><strong>AQS</strong> 是 <code>AbstractQueuedSynchronizer</code> 的简称，即 <code>抽象队列同步器</code>，从字面意思上理解:</p><ul><li>抽象：抽象类，只实现一些主要逻辑，有些方法由子类实现；</li><li>队列：使用先进先出（FIFO）队列存储数据；</li><li>同步：实现了同步的功能。</li></ul><p>AQS 是一个用来构建锁和同步器的框架，使用 AQS 能简单且高效地构造出应用广泛的同步器。</p><h2 id="资源共享模式"><a href="#资源共享模式" class="headerlink" title="资源共享模式"></a>资源共享模式</h2><p>资源有两种共享模式，或者说两种同步方式：</p><ul><li>独占模式（Exclusive）：资源是独占的，一次只能一个线程获取。如 ReentrantLock。</li><li>共享模式（Share）：同时可以被多个线程获取，具体的资源个数可以通过参数指定。如 Semaphore&#x2F;CountDownLatch。</li></ul><h2 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h2><h3 id="基本结构"><a href="#基本结构" class="headerlink" title="基本结构"></a>基本结构</h3><p>AQS 类本身实现的是一些排队和阻塞的机制，比如具体线程等待队列的维护（如获取资源失败入队&#x2F;唤醒出队等）。它内部使用了一个先进先出（FIFO）的双端队列，并使用了两个指针 head 和 tail 用于标识队列的头部和尾部。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** CLH Nodes */</span></span><br><span class="line">    <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">        <span class="keyword">volatile</span> Node prev;       <span class="comment">// initially attached via casTail</span></span><br><span class="line">        <span class="keyword">volatile</span> Node next;       <span class="comment">// visibly nonnull when signallable</span></span><br><span class="line">        Thread waiter;            <span class="comment">// visibly nonnull when enqueued</span></span><br><span class="line">        <span class="keyword">volatile</span> <span class="type">int</span> status;      <span class="comment">// written by owner, atomic bit ops by others</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// methods for atomic operations</span></span><br><span class="line">        <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">casPrev</span><span class="params">(Node c, Node v)</span> &#123;  <span class="comment">// for cleanQueue</span></span><br><span class="line">            <span class="keyword">return</span> U.weakCompareAndSetReference(<span class="built_in">this</span>, PREV, c, v);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">casNext</span><span class="params">(Node c, Node v)</span> &#123;  <span class="comment">// for cleanQueue</span></span><br><span class="line">            <span class="keyword">return</span> U.weakCompareAndSetReference(<span class="built_in">this</span>, NEXT, c, v);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">getAndUnsetStatus</span><span class="params">(<span class="type">int</span> v)</span> &#123;     <span class="comment">// for signalling</span></span><br><span class="line">            <span class="keyword">return</span> U.getAndBitwiseAndInt(<span class="built_in">this</span>, STATUS, ~v);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">setPrevRelaxed</span><span class="params">(Node p)</span> &#123;      <span class="comment">// for off-queue assignment</span></span><br><span class="line">            U.putReference(<span class="built_in">this</span>, PREV, p);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">setStatusRelaxed</span><span class="params">(<span class="type">int</span> s)</span> &#123;     <span class="comment">// for off-queue assignment</span></span><br><span class="line">            U.putInt(<span class="built_in">this</span>, STATUS, s);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">clearStatus</span><span class="params">()</span> &#123;               <span class="comment">// for reducing unneeded signals</span></span><br><span class="line">            U.putIntOpaque(<span class="built_in">this</span>, STATUS, <span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">STATUS</span></span><br><span class="line">            <span class="operator">=</span> U.objectFieldOffset(Node.class, <span class="string">&quot;status&quot;</span>);</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">NEXT</span></span><br><span class="line">            <span class="operator">=</span> U.objectFieldOffset(Node.class, <span class="string">&quot;next&quot;</span>);</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">PREV</span></span><br><span class="line">            <span class="operator">=</span> U.objectFieldOffset(Node.class, <span class="string">&quot;prev&quot;</span>);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="状态"><a href="#状态" class="headerlink" title="状态"></a>状态</h3><p>AQS 使用 int 成员变量 <code>state</code> 表示同步状态，通过内置的 FIFO 线程等待&#x2F;等待队列 来完成获取资源线程的排队工作。</p><p><code>state</code> 变量由 <code>volatile</code> 修饰，用于展示当前临界资源的获锁情况。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * The synchronization state.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> state;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns the current value of synchronization state.</span></span><br><span class="line"><span class="comment">     * This operation has memory semantics of a &#123;<span class="doctag">@code</span> volatile&#125; read.</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> current state value</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">getState</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Sets the value of synchronization state.</span></span><br><span class="line"><span class="comment">     * This operation has memory semantics of a &#123;<span class="doctag">@code</span> volatile&#125; write.</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> newState the new state value</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">setState</span><span class="params">(<span class="type">int</span> newState)</span> &#123;</span><br><span class="line">        state = newState;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="获取资源"><a href="#获取资源" class="headerlink" title="获取资源"></a>获取资源</h3><p>获取资源的入口是 acquire(int arg) 方法。arg 是要获取的资源的个数，在独占模式下始终为 1。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Acquires in exclusive mode, ignoring interrupts.  Implemented</span></span><br><span class="line"><span class="comment">     * by invoking at least once &#123;<span class="doctag">@link</span> #tryAcquire&#125;,</span></span><br><span class="line"><span class="comment">     * returning on success.  Otherwise the thread is queued, possibly</span></span><br><span class="line"><span class="comment">     * repeatedly blocking and unblocking, invoking &#123;<span class="doctag">@link</span></span></span><br><span class="line"><span class="comment">     * #tryAcquire&#125; until success.  This method can be used</span></span><br><span class="line"><span class="comment">     * to implement method &#123;<span class="doctag">@link</span> Lock#lock&#125;.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arg the acquire argument.  This value is conveyed to</span></span><br><span class="line"><span class="comment">     *        &#123;<span class="doctag">@link</span> #tryAcquire&#125; but is otherwise uninterpreted and</span></span><br><span class="line"><span class="comment">     *        can represent anything you like.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">acquire</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!tryAcquire(arg))</span><br><span class="line">            acquire(<span class="literal">null</span>, arg, <span class="literal">false</span>, <span class="literal">false</span>, <span class="literal">false</span>, <span class="number">0L</span>);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><ul><li>首先调用 tryAcquire(arg) 尝试去获取资源。前面提到了这个方法是在子类具体实现的。</li><li>如果获取资源失败，就通过 addWaiter(Node.EXCLUSIVE) 方法把这个线程插入到等待队列中。其中传入的参数代表要插入的 Node 是独占式的。</li><li>处于等待队列的结点是从头结点一个一个去获取资源的。</li><li>结点进入等待队列后，是调用 park 使它进入阻塞状态的。只有头结点的线程是处于活跃状态的。</li></ul><h3 id="释放资源"><a href="#释放资源" class="headerlink" title="释放资源"></a>释放资源</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">release</span><span class="params">(<span class="type">int</span> arg)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (tryRelease(arg)) &#123;</span><br><span class="line">        <span class="type">Node</span> <span class="variable">h</span> <span class="operator">=</span> head;</span><br><span class="line">        <span class="keyword">if</span> (h != <span class="literal">null</span> &amp;&amp; h.waitStatus != <span class="number">0</span>)</span><br><span class="line">            unparkSuccessor(h);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">unparkSuccessor</span><span class="params">(Node node)</span> &#123;</span><br><span class="line">    <span class="comment">// 如果状态是负数，尝试把它设置为0</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">ws</span> <span class="operator">=</span> node.waitStatus;</span><br><span class="line">    <span class="keyword">if</span> (ws &lt; <span class="number">0</span>)</span><br><span class="line">        compareAndSetWaitStatus(node, ws, <span class="number">0</span>);</span><br><span class="line">    <span class="comment">// 得到头结点的后继结点head.next</span></span><br><span class="line">    <span class="type">Node</span> <span class="variable">s</span> <span class="operator">=</span> node.next;</span><br><span class="line">    <span class="comment">// 如果这个后继结点为空或者状态大于0</span></span><br><span class="line">    <span class="comment">// 通过前面的定义我们知道，大于0只有一种可能，就是这个结点已被取消</span></span><br><span class="line">    <span class="keyword">if</span> (s == <span class="literal">null</span> || s.waitStatus &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        s = <span class="literal">null</span>;</span><br><span class="line">        <span class="comment">// 等待队列中所有还有用的结点，都向前移动</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">Node</span> <span class="variable">t</span> <span class="operator">=</span> tail; t != <span class="literal">null</span> &amp;&amp; t != node; t = t.prev)</span><br><span class="line">            <span class="keyword">if</span> (t.waitStatus &lt;= <span class="number">0</span>)</span><br><span class="line">                s = t;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果后继结点不为空，</span></span><br><span class="line">    <span class="keyword">if</span> (s != <span class="literal">null</span>)</span><br><span class="line">        LockSupport.unpark(s.thread);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;strong&gt;AQS&lt;/strong&gt; 是 &lt;code&gt;AbstractQueuedSynchronizer&lt;/code&gt; 的简称，即 &lt;code&gt;抽象队列同步器&lt;/code&gt;，从字面意思上理解:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;抽象：抽象类，只实现一些主要逻辑，有些方法由子类</summary>
      
    
    
    
    
    <category term="Java 并发" scheme="https://yzlzzz.xyz/tags/Java-%E5%B9%B6%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>CAS</title>
    <link href="https://yzlzzz.xyz/2024/08/15/cas/"/>
    <id>https://yzlzzz.xyz/2024/08/15/cas/</id>
    <published>2024-08-14T17:50:15.000Z</published>
    <updated>2024-09-01T18:51:55.412Z</updated>
    
    <content type="html"><![CDATA[<p>CAS 的全称是：比较并交换（Compare And Swap）。</p><p>在 CAS 中，有这样三个值：</p><ul><li>V：要更新的变量(var)</li><li>E：预期值(expected)</li><li>N：新值(new)</li></ul><p>比较并交换的过程如下：</p><p>判断 V 是否等于 E，如果等于，将 V 的值设置为 N；如果不等，说明已经有其它线程更新了 V，则当前线程放弃更新，什么都不做。所以这里的<strong>预期值 E 本质上指的是“旧值”</strong>。</p><p><strong>当多个线程同时使用 CAS 操作一个变量时，只有一个会胜出，并成功更新，其余均会失败，但失败的线程并不会被挂起，仅是被告知失败，并且允许再次尝试，当然也允许失败的线程放弃操作。</strong></p><h2 id="java-中的-CAS"><a href="#java-中的-CAS" class="headerlink" title="java 中的 CAS"></a>java 中的 CAS</h2><p><code>sun.misc</code> 包下的 <code>Unsafe</code> 类提供了 <code>compareAndSwapObject</code>、<code>compareAndSwapInt</code>、<code>compareAndSwapLong</code> 方法来实现的对 <code>Object</code>、<code>int</code>、<code>long</code> 类型的 CAS 操作。</p><p>而这些方法都是直接调用的 <code>native</code> 方法，Java 语言并没有直接用 Java 实现 CAS，Java 中 CAS 是 C++ 内联汇编的形式实现的，通过 JNI（Java Native Interface） 调用。因此，CAS 的具体实现与操作系统以及 CPU 密切相关。</p><p>CAS 操作可能会因为并发冲突而失败，因此通常会与 <code>while</code> 循环搭配使用，在失败后不断重试，直到操作成功。这就是 自旋锁机制 。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Atomically updates Java variable to &#123;<span class="doctag">@code</span> x&#125; if it is currently</span></span><br><span class="line"><span class="comment"> * holding &#123;<span class="doctag">@code</span> expected&#125;.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;This operation has memory semantics of a &#123;<span class="doctag">@code</span> volatile&#125; read</span></span><br><span class="line"><span class="comment"> * and write.  Corresponds to C11 atomic_compare_exchange_strong.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> &#123;<span class="doctag">@code</span> true&#125; if successful</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@ForceInline</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">compareAndSwapObject</span><span class="params">(Object o, <span class="type">long</span> offset,</span></span><br><span class="line"><span class="params">                                          Object expected,</span></span><br><span class="line"><span class="params">                                          Object x)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> theInternalUnsafe.compareAndSetReference(o, offset, expected, x);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Atomically updates Java variable to &#123;<span class="doctag">@code</span> x&#125; if it is currently</span></span><br><span class="line"><span class="comment"> * holding &#123;<span class="doctag">@code</span> expected&#125;.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;This operation has memory semantics of a &#123;<span class="doctag">@code</span> volatile&#125; read</span></span><br><span class="line"><span class="comment"> * and write.  Corresponds to C11 atomic_compare_exchange_strong.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> &#123;<span class="doctag">@code</span> true&#125; if successful</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@ForceInline</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">compareAndSwapInt</span><span class="params">(Object o, <span class="type">long</span> offset,</span></span><br><span class="line"><span class="params">                                       <span class="type">int</span> expected,</span></span><br><span class="line"><span class="params">                                       <span class="type">int</span> x)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> theInternalUnsafe.compareAndSetInt(o, offset, expected, x);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Atomically updates Java variable to &#123;<span class="doctag">@code</span> x&#125; if it is currently</span></span><br><span class="line"><span class="comment"> * holding &#123;<span class="doctag">@code</span> expected&#125;.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;This operation has memory semantics of a &#123;<span class="doctag">@code</span> volatile&#125; read</span></span><br><span class="line"><span class="comment"> * and write.  Corresponds to C11 atomic_compare_exchange_strong.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> &#123;<span class="doctag">@code</span> true&#125; if successful</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@ForceInline</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">compareAndSwapLong</span><span class="params">(Object o, <span class="type">long</span> offset,</span></span><br><span class="line"><span class="params">                                        <span class="type">long</span> expected,</span></span><br><span class="line"><span class="params">                                        <span class="type">long</span> x)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> theInternalUnsafe.compareAndSetLong(o, offset, expected, x);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="CAS-相关问题"><a href="#CAS-相关问题" class="headerlink" title="CAS 相关问题"></a>CAS 相关问题</h2><h3 id="ABA-问题"><a href="#ABA-问题" class="headerlink" title="ABA 问题"></a>ABA 问题</h3><p>一个值原来是 A，变成了 B，又变回了 A。这个时候使用 CAS 是检查不出变化的，但实际上却被更新了两次。</p><p>ABA 问题的解决思路是在变量前面追加上<strong>版本号或者时间戳</strong>。</p><p>Java 中就是会首先检查当前引用是否等于预期引用，再去检查值是否相同，如果都相同，则再去更新值。</p><h3 id="循环时间开销大"><a href="#循环时间开销大" class="headerlink" title="循环时间开销大"></a>循环时间开销大</h3><p>CAS 经常会用到自旋操作来进行重试，也就是不成功就一直循环执行直到成功。如果长时间不成功，会给 CPU 带来非常大的执行开销。</p><p>解决思路是让 JVM 支持处理器提供的 <strong>pause 指令</strong>。</p><p>pause 指令能让自旋失败时 cpu 睡眠一小段时间再继续自旋，从而使得读操作的频率低很多,为解决内存顺序冲突而导致的 CPU 流水线重排的代价也会小很多。</p><h3 id="只能保证一个共享变量的原子操作"><a href="#只能保证一个共享变量的原子操作" class="headerlink" title="只能保证一个共享变量的原子操作"></a>只能保证一个共享变量的原子操作</h3><p>CAS 操作仅能对单个共享变量有效。当需要操作多个共享变量时，CAS 就显得无能为力。不过，从 JDK 1.5 开始，Java 提供了 <code>AtomicReference</code> 类，这使得我们能够保证引用对象之间的原子性。通过将多个变量封装在一个对象中，我们可以使用 <code>AtomicReference</code> 来执行 CAS 操作。</p><p>还可以使用锁来保证多个变量的更新的原子性。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;CAS 的全称是：比较并交换（Compare And Swap）。&lt;/p&gt;
&lt;p&gt;在 CAS 中，有这样三个值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;V：要更新的变量(var)&lt;/li&gt;
&lt;li&gt;E：预期值(expected)&lt;/li&gt;
&lt;li&gt;N：新值(new)&lt;/li&gt;
&lt;/ul</summary>
      
    
    
    
    
    <category term="Java 并发" scheme="https://yzlzzz.xyz/tags/Java-%E5%B9%B6%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>悲观锁与乐观锁</title>
    <link href="https://yzlzzz.xyz/2024/08/14/pe-op-lock/"/>
    <id>https://yzlzzz.xyz/2024/08/14/pe-op-lock/</id>
    <published>2024-08-13T19:23:34.000Z</published>
    <updated>2024-09-01T18:49:53.557Z</updated>
    
    <content type="html"><![CDATA[<h2 id="悲观锁"><a href="#悲观锁" class="headerlink" title="悲观锁"></a>悲观锁</h2><p>悲观锁就是我们常说的锁。对于悲观锁来说，它总是认为每次访问共享资源时会发生冲突，所以必须对每次数据操作加上锁，以保证临界区的程序同一时间只能有一个线程在执行。也就是说，共享资源每次只给一个线程使用，其它线程阻塞，用完后再把资源转让给其它线程。</p><p>Java 中的 <code>synchronize</code> 和 <code>ReentrantLock</code> 等独占锁就是悲观锁的具体实现。</p><p>高并发的场景下，激烈的锁竞争会造成线程阻塞，大量阻塞线程会导致系统的上下文切换，增加系统的性能开销。并且，悲观锁还可能会存在死锁问题（线程获得锁的顺序不当时），影响代码的正常运行。</p><p>悲观锁多用于”写多读少“的环境，避免频繁失败和重试影响性能。</p><h2 id="乐观锁"><a href="#乐观锁" class="headerlink" title="乐观锁"></a>乐观锁</h2><p>乐观锁又称为“无锁”，乐观锁总是假设对共享资源的访问没有冲突，线程可以不停地执行，无需加锁也无需等待。</p><p>而一旦多个线程发生冲突，乐观锁通常是使用一种称为 CAS 的技术来保证线程执行的安全性。</p><p>由于无锁操作中没有锁的存在，因此不可能出现死锁的情况，也就是说<strong>乐观锁天生免疫死锁</strong>。</p><p>在 Java 中 <code>java.util.concurrent.atomic</code> 包下面的原子变量类（比如 <code>AtomicInteger</code>、<code>LongAdder</code>）就是使用了乐观锁的一种实现方式。</p><p>乐观锁多用于“读多写少“的环境，避免频繁加锁影响性能</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;悲观锁&quot;&gt;&lt;a href=&quot;#悲观锁&quot; class=&quot;headerlink&quot; title=&quot;悲观锁&quot;&gt;&lt;/a&gt;悲观锁&lt;/h2&gt;&lt;p&gt;悲观锁就是我们常说的锁。对于悲观锁来说，它总是认为每次访问共享资源时会发生冲突，所以必须对每次数据操作加上锁，以保证临界区的程序同一</summary>
      
    
    
    
    
    <category term="Java 并发" scheme="https://yzlzzz.xyz/tags/Java-%E5%B9%B6%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>ReentrantLock</title>
    <link href="https://yzlzzz.xyz/2024/08/12/reentrantlock/"/>
    <id>https://yzlzzz.xyz/2024/08/12/reentrantlock/</id>
    <published>2024-08-11T19:26:20.000Z</published>
    <updated>2024-09-02T18:28:42.524Z</updated>
    
    <content type="html"><![CDATA[<p>ReentrantLock 是一个可重入的互斥锁，又被称为“独占锁”。</p><p>ReentrantLock 锁在同一个时间点只能被一个线程锁持有；可重入表示，ReentrantLock 锁可以被同一个线程多次获取。</p><p>ReentrantLock 是通过一个 FIFO 的等待队列来管理获取该锁所有线程的。在“公平锁”的机制下，线程依次排队获取锁；而“非公平锁”在锁是可获取状态时，不管自己是不是在队列的开头都会获取锁。</p><h2 id="ReentrantLock-和-Synchronized-比较"><a href="#ReentrantLock-和-Synchronized-比较" class="headerlink" title="ReentrantLock 和 Synchronized 比较"></a>ReentrantLock 和 Synchronized 比较</h2><ol><li>synchronized 是独占锁，加锁和解锁的过程自动进行，易于操作，但不够灵活。ReentrantLock 也是独占锁，加锁和解锁的过程需要手动进行，不易操作，但非常灵活。</li><li>synchronized 可重入，因为加锁和解锁自动进行，不必担心最后是否释放锁；ReentrantLock 也可重入，但加锁和解锁需要手动进行，且次数需一样，否则其他线程无法获得锁。</li><li>synchronized 不可响应中断，一个线程获取不到锁就一直等着；ReentrantLock 可以相应中断。</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;ReentrantLock 是一个可重入的互斥锁，又被称为“独占锁”。&lt;/p&gt;
&lt;p&gt;ReentrantLock 锁在同一个时间点只能被一个线程锁持有；可重入表示，ReentrantLock 锁可以被同一个线程多次获取。&lt;/p&gt;
&lt;p&gt;ReentrantLock 是通过一个</summary>
      
    
    
    
    
    <category term="Java 并发" scheme="https://yzlzzz.xyz/tags/Java-%E5%B9%B6%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>synchronized 关键字</title>
    <link href="https://yzlzzz.xyz/2024/08/12/synchronized/"/>
    <id>https://yzlzzz.xyz/2024/08/12/synchronized/</id>
    <published>2024-08-11T18:45:38.000Z</published>
    <updated>2024-09-01T18:54:17.134Z</updated>
    
    <content type="html"><![CDATA[<p>在 Java 中，<code>synchronized</code> 关键字用于实现多线程环境下的同步控制，确保只有一个线程可以访问共享资源或代码块，从而避免数据的不一致性和竞争条件。</p><p><strong>Java 多线程的锁都是基于对象的</strong>，Java 中的每一个对象都可以作为一个锁。</p><p>有以下三种形式：</p><ol><li>关键字在实例方法上，锁为当前实例</li><li>关键字在静态方法上，锁为当前 Class 对象</li><li>关键字在代码块上，锁为括号里面的对象hex</li></ol><h2 id="四种锁状态"><a href="#四种锁状态" class="headerlink" title="四种锁状态"></a>四种锁状态</h2><p>一个对象其实有四种锁状态，它们级别由低到高依次是：</p><ol><li>无锁状态：</li><li>偏向锁状态</li><li>轻量级锁状态</li><li>重量级锁状态</li></ol><p>几种锁会随着竞争情况逐渐升级，锁的升级很容易发生，但是锁降级发生的条件会比较苛刻，锁降级发生在 Stop The World 期间，当 JVM 进入安全点的时候，会检查是否有闲置的锁，然后进行降级。</p><h3 id="无锁（No-Lock）"><a href="#无锁（No-Lock）" class="headerlink" title="无锁（No Lock）"></a>无锁（No Lock）</h3><p>当代码块不涉及多线程竞争时，JVM 不会为其分配锁。没有对资源进行锁定，任何线程都可以尝试去修改它。</p><h3 id="偏向锁（Biased-Lock）"><a href="#偏向锁（Biased-Lock）" class="headerlink" title="偏向锁（Biased Lock）"></a>偏向锁（Biased Lock）</h3><p>大多数情况下<strong>锁不仅不存在多线程竞争，而且总是由同一线程多次获得</strong>，于是引入了偏向锁。</p><p>JVM 会将锁的状态标记为“偏向锁”并记录拥有锁的线程 ID。如果在偏向期间没有其他线程尝试获取该锁，那么持有偏向锁的线程就不需要进行任何同步操作。</p><p><strong>锁撤销</strong>：如果在偏向锁期间，另一个线程尝试获取该锁，那么偏向锁会被撤销，并升级为轻量级锁（自旋锁）。</p><p>偏向锁升级成轻量级锁时，会暂停拥有偏向锁的线程，重置偏向锁标识，这个过程看起来容易，实则开销还是很大的，大概的过程如下：</p><ol><li>在一个安全点（在这个时间点上没有字节码正在执行）停止拥有锁的线程。</li><li>遍历线程栈，如果存在锁记录的话，需要修复锁记录和 Mark Word，使其变成无锁状态。</li><li>唤醒被停止的线程，将当前锁升级成轻量级锁。</li></ol><h3 id="轻量级锁（Lightweight-Lock）"><a href="#轻量级锁（Lightweight-Lock）" class="headerlink" title="轻量级锁（Lightweight Lock）"></a>轻量级锁（Lightweight Lock）</h3><p>轻量级锁是一种适用于短时间内只涉及少量线程竞争的场景（或者多个线程在不同时段获取同一把锁）。此时 JVM 会使用自旋锁（spin lock）机制来避免线程阻塞和上下文切换的开销。</p><p>自旋锁的工作原理是：线程在尝试获取锁时，不会立即阻塞自己，而是自旋等待（即反复检查锁是否可用），以期锁可以很快被释放。在短时间内，锁的持有时间很短时，自旋锁的性能优于重量级锁，因为避免了线程的挂起和唤醒操作。</p><p><strong>锁膨胀</strong>：如果线程在自旋一段时间后仍未获得锁，轻量级锁将升级为重量级锁。</p><h3 id="重量级锁（Heavyweight-Lock）"><a href="#重量级锁（Heavyweight-Lock）" class="headerlink" title="重量级锁（Heavyweight Lock）"></a>重量级锁（Heavyweight Lock）</h3><p>重量级锁使用操作系统的同步机制来实现。当锁升级为重量级锁时，JVM 会阻塞那些尝试获取锁但未能成功的线程。被阻塞的线程在获得锁之前必须进入等待状态，直到其他线程释放锁。</p><p>重量级锁会导致线程上下文切换和系统调用，这些操作通常比轻量级锁开销大。</p><h3 id="锁的升级流程"><a href="#锁的升级流程" class="headerlink" title="锁的升级流程"></a>锁的升级流程</h3><ol><li><strong>无锁到偏向锁</strong>：对象初始化时，默认处于无锁状态。如果一个线程尝试获取锁，且该锁目前处于无锁状态，则升级为偏向锁。</li><li><strong>偏向锁到轻量级锁</strong>：如果偏向锁被另一个线程竞争，偏向锁将被撤销，锁状态升级为轻量级锁。</li><li><strong>轻量级锁到重量级锁</strong>：当自旋锁的线程数量超过一定阈值（或自旋时间过长），锁会从轻量级锁升级为重量级锁，线程被阻塞等待锁释放。</li></ol><h3 id="锁的对比"><a href="#锁的对比" class="headerlink" title="锁的对比"></a>锁的对比</h3><table><thead><tr><th>锁</th><th>优点</th><th>缺点</th><th>适用场景</th></tr></thead><tbody><tr><td>偏向锁</td><td>加锁和解锁不需要额外的消耗，和执行非同步方法比仅存在纳秒级的差距。</td><td>如果线程间存在锁竞争，会带来额外的<strong>锁撤销</strong>的消耗。</td><td>适用于只有一个线程访问同步块场景。</td></tr><tr><td>轻量级锁</td><td>竞争的线程不会阻塞，提高了程序的响应速度。</td><td>如果始终得不到锁的竞争的线程使用自旋会消耗 CPU。</td><td>追求响应时间。同步块执行速度非常快。</td></tr><tr><td>重量级锁</td><td>线程竞争不使用自旋，不会消耗 CPU。</td><td>线程阻塞，响应时间缓慢。</td><td>追求吞吐量。同步块执行时间较长。</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在 Java 中，&lt;code&gt;synchronized&lt;/code&gt; 关键字用于实现多线程环境下的同步控制，确保只有一个线程可以访问共享资源或代码块，从而避免数据的不一致性和竞争条件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Java 多线程的锁都是基于对象的&lt;/strong&gt;，Ja</summary>
      
    
    
    
    
    <category term="Java 并发" scheme="https://yzlzzz.xyz/tags/Java-%E5%B9%B6%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>HashMap, HashSet, ConcurrentHashMap 源码分析</title>
    <link href="https://yzlzzz.xyz/2024/08/10/java-map/"/>
    <id>https://yzlzzz.xyz/2024/08/10/java-map/</id>
    <published>2024-08-09T17:45:31.000Z</published>
    <updated>2024-09-13T16:34:38.260Z</updated>
    
    <content type="html"><![CDATA[<h2 id="HashMap-源码"><a href="#HashMap-源码" class="headerlink" title="HashMap 源码"></a>HashMap 源码</h2><h3 id="Node-数组"><a href="#Node-数组" class="headerlink" title="Node 数组"></a>Node 数组</h3><p>HashMap 底层是由 Node 数组组成的，其中包括 key，value，hash 以及指向下个节点的 next</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Basic hash bin node, used for most entries.  (See below for_</span></span><br><span class="line"><span class="comment">_ * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)_</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Node</span>&lt;K,V&gt; <span class="keyword">implements</span> <span class="title class_">Map</span>.Entry&lt;K,V&gt; &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> hash;</span><br><span class="line">    <span class="keyword">final</span> K key;</span><br><span class="line">    V value;</span><br><span class="line">    Node&lt;K,V&gt; next;</span><br><span class="line"></span><br><span class="line">    Node(<span class="type">int</span> hash, K key, V value, Node&lt;K,V&gt; next) &#123;</span><br><span class="line">        <span class="built_in">this</span>.hash = hash;</span><br><span class="line">        <span class="built_in">this</span>.key = key;</span><br><span class="line">        <span class="built_in">this</span>.value = value;</span><br><span class="line">        <span class="built_in">this</span>.next = next;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> K <span class="title function_">getKey</span><span class="params">()</span>        &#123; <span class="keyword">return</span> key; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> V <span class="title function_">getValue</span><span class="params">()</span>      &#123; <span class="keyword">return</span> value; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> String <span class="title function_">toString</span><span class="params">()</span> &#123; <span class="keyword">return</span> key + <span class="string">&quot;=&quot;</span> + value; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Objects._hashCode_(key) ^ Objects._hashCode_(value);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> V <span class="title function_">setValue</span><span class="params">(V newValue)</span> &#123;</span><br><span class="line">        <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> value;</span><br><span class="line">        value = newValue;</span><br><span class="line">        <span class="keyword">return</span> oldValue;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (o == <span class="built_in">this</span>)</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> o <span class="keyword">instanceof</span> Map.Entry&lt;?, ?&gt; e</span><br><span class="line">                &amp;&amp; Objects._equals_(key, e.getKey())</span><br><span class="line">                &amp;&amp; Objects._equals_(value, e.getValue());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="扰动函数（hash-方法）"><a href="#扰动函数（hash-方法）" class="headerlink" title="扰动函数（hash 方法）"></a>扰动函数（hash 方法）</h3><p>HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值，然后通过 <code>(n - 1) &amp; hash</code> 判断当前元素存放的位置（n 是数组的长度），如果当前位置存在元素的话，就判断该元素与要存入的元素的 hash 值以及 key 是否相同，如果相同的话，直接覆盖，不相同就通过拉链法解决冲突。</p><p>扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Computes key.hashCode() and spreads (XORs) higher bits of hash_</span></span><br><span class="line"><span class="comment">_ * to lower.  Because the table uses power-of-two masking, sets of_</span></span><br><span class="line"><span class="comment">_ * hashes that vary only in bits above the current mask will_</span></span><br><span class="line"><span class="comment">_ * always collide. (Among known examples are sets of Float keys_</span></span><br><span class="line"><span class="comment">_ * holding consecutive whole numbers in small tables.)  So we_</span></span><br><span class="line"><span class="comment">_ * apply a transform that spreads the impact of higher bits_</span></span><br><span class="line"><span class="comment">_ * downward. There is a tradeoff between speed, utility, and_</span></span><br><span class="line"><span class="comment">_ * quality of bit-spreading. Because many common sets of hashes_</span></span><br><span class="line"><span class="comment">_ * are already reasonably distributed (so don&#x27;t benefit from_</span></span><br><span class="line"><span class="comment">_ * spreading), and because we use trees to handle large sets of_</span></span><br><span class="line"><span class="comment">_ * collisions in bins, we just XOR some shifted bits in the_</span></span><br><span class="line"><span class="comment">_ * cheapest possible way to reduce systematic lossage, as well as_</span></span><br><span class="line"><span class="comment">_ * to incorporate impact of the highest bits that would otherwise_</span></span><br><span class="line"><span class="comment">_ * never be used in index calculations because of table bounds._</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">hash</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">    <span class="type">int</span> h;</span><br><span class="line">    <span class="keyword">return</span> (key == <span class="literal">null</span>) ? <span class="number">0</span> : (h = key.hashCode()) ^ (h &gt;&gt;&gt; <span class="number">16</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="拉链法"><a href="#拉链法" class="headerlink" title="拉链法"></a>拉链法</h3><p>将链表和数组相结合。也就是说创建一个链表数组，数组中每一格就是一个链表。若遇到哈希冲突，则将冲突的值加到链表中即可。</p><p>在 jdk1.8 之后，当链表的长度大于 <code>TREEIFY_THRESHOLD</code> （默认为 8）之后，会会首先调用 <code>treeifyBin()</code> 方法。这个方法会根据 HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 <code>MIN_TREEIFY_CAPACITY</code> （默认为 64） 的情况下，才会执行转换红黑树操作，以减少搜索时间。否则，就是只是执行 <code>resize()</code> 方法对数组扩容。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Replaces all linked nodes in bin at index for given hash unless_</span></span><br><span class="line"><span class="comment">_ * table is too small, in which case resizes instead._</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">treeifyBin</span><span class="params">(Node&lt;K,V&gt;[] tab, <span class="type">int</span> hash)</span> &#123;</span><br><span class="line">    <span class="type">int</span> n, index; Node&lt;K,V&gt; e;</span><br><span class="line">    <span class="keyword">if</span> (tab == <span class="literal">null</span> || (n = tab.length) &lt; _MIN_TREEIFY_CAPACITY_)</span><br><span class="line">        resize();</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> ((e = tab[index = (n - <span class="number">1</span>) &amp; hash]) != <span class="literal">null</span>) &#123;</span><br><span class="line">        TreeNode&lt;K,V&gt; hd = <span class="literal">null</span>, tl = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            TreeNode&lt;K,V&gt; p = replacementTreeNode(e, <span class="literal">null</span>);</span><br><span class="line">            <span class="keyword">if</span> (tl == <span class="literal">null</span>)</span><br><span class="line">                hd = p;</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                p.prev = tl;</span><br><span class="line">                tl.next = p;</span><br><span class="line">            &#125;</span><br><span class="line">            tl = p;</span><br><span class="line">        &#125; <span class="keyword">while</span> ((e = e.next) != <span class="literal">null</span>);</span><br><span class="line">        <span class="keyword">if</span> ((tab[index] = hd) != <span class="literal">null</span>)</span><br><span class="line">            hd.treeify(tab);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="resize-方法"><a href="#resize-方法" class="headerlink" title="resize 方法"></a>resize 方法</h3><p>resize 方法是对 HashMap 中数组进行扩容的方法具体流程为如下：</p><ol><li>判断数组容量是否超过 <code>MAXIMUM_CAPACITY</code>（默认为 2^30）</li><li>超过后不再继续扩容</li><li>未超过，容量变为原来的两倍</li><li>重新计算 <code>threshold</code></li><li>初始化新的 table，将原来元素插入新的 table 中</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Initializes or doubles table size.  If null, allocates in</span></span><br><span class="line"><span class="comment"> * accord with initial capacity target held in field threshold.</span></span><br><span class="line"><span class="comment"> * Otherwise, because we are using power-of-two expansion, the</span></span><br><span class="line"><span class="comment"> * elements from each bin must either stay at same index, or move</span></span><br><span class="line"><span class="comment"> * with a power of two offset in the new table.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the table</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">final</span> Node&lt;K,V&gt;[] resize() &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] oldTab = table;</span><br><span class="line">    <span class="type">int</span> <span class="variable">oldCap</span> <span class="operator">=</span> (oldTab == <span class="literal">null</span>) ? <span class="number">0</span> : oldTab.length;</span><br><span class="line">    <span class="type">int</span> <span class="variable">oldThr</span> <span class="operator">=</span> threshold;</span><br><span class="line">    <span class="type">int</span> newCap, newThr = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (oldCap &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (oldCap &gt;= MAXIMUM_CAPACITY) &#123;</span><br><span class="line">            threshold = Integer.MAX_VALUE;</span><br><span class="line">            <span class="keyword">return</span> oldTab;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((newCap = oldCap &lt;&lt; <span class="number">1</span>) &lt; MAXIMUM_CAPACITY &amp;&amp;</span><br><span class="line">                 oldCap &gt;= DEFAULT_INITIAL_CAPACITY)</span><br><span class="line">            newThr = oldThr &lt;&lt; <span class="number">1</span>; <span class="comment">// double threshold</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (oldThr &gt; <span class="number">0</span>) <span class="comment">// initial capacity was placed in threshold</span></span><br><span class="line">        newCap = oldThr;</span><br><span class="line">    <span class="keyword">else</span> &#123;               <span class="comment">// zero initial threshold signifies using defaults</span></span><br><span class="line">        newCap = DEFAULT_INITIAL_CAPACITY;</span><br><span class="line">        newThr = (<span class="type">int</span>)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (newThr == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">float</span> <span class="variable">ft</span> <span class="operator">=</span> (<span class="type">float</span>)newCap * loadFactor;</span><br><span class="line">        newThr = (newCap &lt; MAXIMUM_CAPACITY &amp;&amp; ft &lt; (<span class="type">float</span>)MAXIMUM_CAPACITY ?</span><br><span class="line">                  (<span class="type">int</span>)ft : Integer.MAX_VALUE);</span><br><span class="line">    &#125;</span><br><span class="line">    threshold = newThr;</span><br><span class="line">    <span class="meta">@SuppressWarnings(&#123;&quot;rawtypes&quot;,&quot;unchecked&quot;&#125;)</span></span><br><span class="line">    Node&lt;K,V&gt;[] newTab = (Node&lt;K,V&gt;[])<span class="keyword">new</span> <span class="title class_">Node</span>[newCap];</span><br><span class="line">    table = newTab;</span><br><span class="line">    <span class="keyword">if</span> (oldTab != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; oldCap; ++j) &#123;</span><br><span class="line">            Node&lt;K,V&gt; e;</span><br><span class="line">            <span class="keyword">if</span> ((e = oldTab[j]) != <span class="literal">null</span>) &#123;</span><br><span class="line">                oldTab[j] = <span class="literal">null</span>;</span><br><span class="line">                <span class="keyword">if</span> (e.next == <span class="literal">null</span>)</span><br><span class="line">                    newTab[e.hash &amp; (newCap - <span class="number">1</span>)] = e;</span><br><span class="line">                <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line">                    ((TreeNode&lt;K,V&gt;)e).split(<span class="built_in">this</span>, newTab, j, oldCap);</span><br><span class="line">                <span class="keyword">else</span> &#123; <span class="comment">// preserve order</span></span><br><span class="line">                    Node&lt;K,V&gt; loHead = <span class="literal">null</span>, loTail = <span class="literal">null</span>;</span><br><span class="line">                    Node&lt;K,V&gt; hiHead = <span class="literal">null</span>, hiTail = <span class="literal">null</span>;</span><br><span class="line">                    Node&lt;K,V&gt; next;</span><br><span class="line">                    <span class="keyword">do</span> &#123;</span><br><span class="line">                        next = e.next;</span><br><span class="line">                        <span class="keyword">if</span> ((e.hash &amp; oldCap) == <span class="number">0</span>) &#123;</span><br><span class="line">                            <span class="keyword">if</span> (loTail == <span class="literal">null</span>)</span><br><span class="line">                                loHead = e;</span><br><span class="line">                            <span class="keyword">else</span></span><br><span class="line">                                loTail.next = e;</span><br><span class="line">                            loTail = e;</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">else</span> &#123;</span><br><span class="line">                            <span class="keyword">if</span> (hiTail == <span class="literal">null</span>)</span><br><span class="line">                                hiHead = e;</span><br><span class="line">                            <span class="keyword">else</span></span><br><span class="line">                                hiTail.next = e;</span><br><span class="line">                            hiTail = e;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125; <span class="keyword">while</span> ((e = next) != <span class="literal">null</span>);</span><br><span class="line">                    <span class="keyword">if</span> (loTail != <span class="literal">null</span>) &#123;</span><br><span class="line">                        loTail.next = <span class="literal">null</span>;</span><br><span class="line">                        newTab[j] = loHead;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">if</span> (hiTail != <span class="literal">null</span>) &#123;</span><br><span class="line">                        hiTail.next = <span class="literal">null</span>;</span><br><span class="line">                        newTab[j + oldCap] = hiHead;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> newTab;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="TreeNode-节点"><a href="#TreeNode-节点" class="headerlink" title="TreeNode 节点"></a>TreeNode 节点</h3><p>在转换为红黑树后，链表节点会变为 TreeNode</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn_</span></span><br><span class="line"><span class="comment">_ * extends Node) so can be used as extension of either regular or_</span></span><br><span class="line"><span class="comment">_ * linked node._</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">TreeNode</span>&lt;K,V&gt; <span class="keyword">extends</span> <span class="title class_">LinkedHashMap</span>.Entry&lt;K,V&gt; &#123;</span><br><span class="line">    TreeNode&lt;K,V&gt; parent;  <span class="comment">// red-black tree links</span></span><br><span class="line">    TreeNode&lt;K,V&gt; left;</span><br><span class="line">    TreeNode&lt;K,V&gt; right;</span><br><span class="line">    TreeNode&lt;K,V&gt; prev;    <span class="comment">// needed to unlink next upon deletion</span></span><br><span class="line">    <span class="type">boolean</span> red;</span><br><span class="line">    TreeNode(<span class="type">int</span> hash, K key, V val, Node&lt;K,V&gt; next) &#123;</span><br><span class="line">        <span class="built_in">super</span>(hash, key, val, next);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    _<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_     * Returns root of tree containing this node._</span></span><br><span class="line"><span class="comment">_     */</span>_</span><br><span class="line">_    _final TreeNode&lt;K,V&gt; <span class="title function_">root</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (TreeNode&lt;K,V&gt; r = <span class="built_in">this</span>, p;;) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((p = r.parent) == <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">return</span> r;</span><br><span class="line">            r = p;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="loadFactor-属性"><a href="#loadFactor-属性" class="headerlink" title="loadFactor 属性"></a>loadFactor 属性</h3><p>用来控制 HashMap 中数组的疏密程度的属性，默认为 0.75f。</p><ul><li>越接近 1 代表数组中的数越稠密，越容易发生碰撞，从而链表更长，查找效率比较低。</li><li>越接近 0 代表数组中的数越稀疏，越不容易发生碰撞，但同时数组存放数据越少，空间利用率低。</li></ul><h3 id="put-方法"><a href="#put-方法" class="headerlink" title="put 方法"></a>put 方法</h3><p>HashMap 中插入元素使用的方法</p><p>具体流程如下：</p><ol><li>如果定位到的数组位置没有元素，直接插入</li><li>如果定位到的数组位置有元素，比较两个元素的 key</li><li>如果 key 相同就直接覆盖</li><li>如果 key 不相同，判断 p 是否是一个树节点</li><li>如果 p 是树节点，调用 putTreeVal() 方法将元素插入树中</li><li>如果 p 不是树节点，则 p 为链表，将 元素放置元素链表末尾</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Associates the specified value with the specified key in this map.</span></span><br><span class="line"><span class="comment"> * If the map previously contained a mapping for the key, the old</span></span><br><span class="line"><span class="comment"> * value is replaced.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key key with which the specified value is to be associated</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value value to be associated with the specified key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the previous value associated with &#123;<span class="doctag">@code</span> key&#125;, or</span></span><br><span class="line"><span class="comment"> *         &#123;<span class="doctag">@code</span> null&#125; if there was no mapping for &#123;<span class="doctag">@code</span> key&#125;.</span></span><br><span class="line"><span class="comment"> *         (A &#123;<span class="doctag">@code</span> null&#125; return can also indicate that the map</span></span><br><span class="line"><span class="comment"> *         previously associated &#123;<span class="doctag">@code</span> null&#125; with &#123;<span class="doctag">@code</span> key&#125;.)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> V <span class="title function_">put</span><span class="params">(K key, V value)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> putVal(hash(key), key, value, <span class="literal">false</span>, <span class="literal">true</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Implements Map.put and related methods.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> hash hash for key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> key the key</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> value the value to put</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> onlyIfAbsent if true, don&#x27;t change existing value</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> evict if false, the table is in creation mode.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> previous value, or null if none</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">boolean</span> onlyIfAbsent,</span></span><br><span class="line"><span class="params">               <span class="type">boolean</span> evict)</span> &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; <span class="type">int</span> n, i;</span><br><span class="line">    <span class="keyword">if</span> ((tab = table) == <span class="literal">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line">        n = (tab = resize()).length;</span><br><span class="line">    <span class="keyword">if</span> ((p = tab[i = (n - <span class="number">1</span>) &amp; hash]) == <span class="literal">null</span>)</span><br><span class="line">        tab[i] = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        Node&lt;K,V&gt; e; K k;</span><br><span class="line">        <span class="keyword">if</span> (p.hash == hash &amp;&amp;</span><br><span class="line">            ((k = p.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">            e = p;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (p <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line">            e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(<span class="built_in">this</span>, tab, hash, key, value);</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">binCount</span> <span class="operator">=</span> <span class="number">0</span>; ; ++binCount) &#123;</span><br><span class="line">                <span class="keyword">if</span> ((e = p.next) == <span class="literal">null</span>) &#123;</span><br><span class="line">                    p.next = newNode(hash, key, value, <span class="literal">null</span>);</span><br><span class="line">                    <span class="keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD - <span class="number">1</span>) <span class="comment">// -1 for 1st</span></span><br><span class="line">                        treeifyBin(tab, hash);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                    ((k = e.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                p = e;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (e != <span class="literal">null</span>) &#123; <span class="comment">// existing mapping for key</span></span><br><span class="line">            <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e.value;</span><br><span class="line">            <span class="keyword">if</span> (!onlyIfAbsent || oldValue == <span class="literal">null</span>)</span><br><span class="line">                e.value = value;</span><br><span class="line">            afterNodeAccess(e);</span><br><span class="line">            <span class="keyword">return</span> oldValue;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ++modCount;</span><br><span class="line">    <span class="keyword">if</span> (++size &gt; threshold)</span><br><span class="line">        resize();</span><br><span class="line">    afterNodeInsertion(evict);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="get-方法"><a href="#get-方法" class="headerlink" title="get 方法"></a>get 方法</h3><p>HashMap 中 get 方法与 put 方法的流程相似，都是相同的判断</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Returns the value to which the specified key is mapped,_</span></span><br><span class="line"><span class="comment">_ * or &#123;_**<span class="doctag">@code</span> **_null&#125; if this map contains no mapping for the key._</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * &lt;p&gt;More formally, if this map contains a mapping from a key_</span></span><br><span class="line"><span class="comment">_ * &#123;_**<span class="doctag">@code</span> **_k&#125; to a value &#123;_**<span class="doctag">@code</span> **_v&#125; such that &#123;_**<span class="doctag">@code</span> **_(key==null ? k==null :_</span></span><br><span class="line"><span class="comment">_ * key.equals(k))&#125;, then this method returns &#123;_**<span class="doctag">@code</span> **_v&#125;; otherwise_</span></span><br><span class="line"><span class="comment">_ * it returns &#123;_**<span class="doctag">@code</span> **_null&#125;.  (There can be at most one such mapping.)_</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * &lt;p&gt;A return value of &#123;_**<span class="doctag">@code</span> **_null&#125; does not &lt;i&gt;necessarily&lt;/i&gt;_</span></span><br><span class="line"><span class="comment">_ * indicate that the map contains no mapping for the key; it&#x27;s also_</span></span><br><span class="line"><span class="comment">_ * possible that the map explicitly maps the key to &#123;_**<span class="doctag">@code</span> **_null&#125;._</span></span><br><span class="line"><span class="comment">_ * The &#123;_**<span class="doctag">@link</span> **_#containsKey containsKey&#125; operation may be used to_</span></span><br><span class="line"><span class="comment">_ * distinguish these two cases._</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@see</span> **_#put(Object, Object)_</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">public</span> V <span class="title function_">get</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">    Node&lt;K,V&gt; e;</span><br><span class="line">    <span class="keyword">return</span> (e = getNode(key)) == <span class="literal">null</span> ? <span class="literal">null</span> : e.value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Implements Map.get and related methods._</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span> **_key the key_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@return</span> **_the node, or null if none_</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">final</span> Node&lt;K,V&gt; <span class="title function_">getNode</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; first, e; <span class="type">int</span> n, hash; K k;</span><br><span class="line">    <span class="keyword">if</span> ((tab = table) != <span class="literal">null</span> &amp;&amp; (n = tab.length) &gt; <span class="number">0</span> &amp;&amp;</span><br><span class="line">        (first = tab[(n - <span class="number">1</span>) &amp; (hash = _hash_(key))]) != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (first.hash == hash &amp;&amp; <span class="comment">// always check first node</span></span><br><span class="line">            ((k = first.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">            <span class="keyword">return</span> first;</span><br><span class="line">        <span class="keyword">if</span> ((e = first.next) != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (first <span class="keyword">instanceof</span> TreeNode)</span><br><span class="line">                <span class="keyword">return</span> ((TreeNode&lt;K,V&gt;)first).getTreeNode(hash, key);</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                    ((k = e.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">                    <span class="keyword">return</span> e;</span><br><span class="line">            &#125; <span class="keyword">while</span> ((e = e.next) != <span class="literal">null</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="HashSet-源码"><a href="#HashSet-源码" class="headerlink" title="HashSet 源码"></a>HashSet 源码</h2><h3 id="初始化方法"><a href="#初始化方法" class="headerlink" title="初始化方法"></a>初始化方法</h3><p>HashSet 底层是基于 HashMap 实现的，因此源码基本是直接调用 HashMap 中的方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Constructs a new, empty set; the backing &#123;_**<span class="doctag">@code</span> **_HashMap&#125; instance has_</span></span><br><span class="line"><span class="comment">_ * default initial capacity (16) and load factor (0.75)._</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">public</span> <span class="title function_">HashSet</span><span class="params">()</span> &#123;</span><br><span class="line">    map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Constructs a new set containing the elements in the specified_</span></span><br><span class="line"><span class="comment">_ * collection.  The &#123;_**<span class="doctag">@code</span> **_HashMap&#125; is created with default load factor_</span></span><br><span class="line"><span class="comment">_ * (0.75) and an initial capacity sufficient to contain the elements in_</span></span><br><span class="line"><span class="comment">_ * the specified collection._</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span> **_c the collection whose elements are to be placed into this set_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@throws</span> **_NullPointerException if the specified collection is null_</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">public</span> <span class="title function_">HashSet</span><span class="params">(Collection&lt;? extends E&gt; c)</span> &#123;</span><br><span class="line">    map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(Math._max_((<span class="type">int</span>) (c.size()/<span class="number">.75f</span>) + <span class="number">1</span>, <span class="number">16</span>));</span><br><span class="line">    addAll(c);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Constructs a new, empty set; the backing &#123;_**<span class="doctag">@code</span> **_HashMap&#125; instance has_</span></span><br><span class="line"><span class="comment">_ * the specified initial capacity and the specified load factor._</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span>      **_initialCapacity   the initial capacity of the hash map_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span>      **_loadFactor        the load factor of the hash map_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@throws</span>     **_IllegalArgumentException if the initial capacity is less_</span></span><br><span class="line"><span class="comment">_ *             than zero, or if the load factor is nonpositive_</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">public</span> <span class="title function_">HashSet</span><span class="params">(<span class="type">int</span> initialCapacity, <span class="type">float</span> loadFactor)</span> &#123;</span><br><span class="line">    map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(initialCapacity, loadFactor);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Constructs a new, empty set; the backing &#123;_**<span class="doctag">@code</span> **_HashMap&#125; instance has_</span></span><br><span class="line"><span class="comment">_ * the specified initial capacity and default load factor (0.75)._</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span>      **_initialCapacity   the initial capacity of the hash table_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@throws</span>     **_IllegalArgumentException if the initial capacity is less_</span></span><br><span class="line"><span class="comment">_ *             than zero_</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">public</span> <span class="title function_">HashSet</span><span class="params">(<span class="type">int</span> initialCapacity)</span> &#123;</span><br><span class="line">    map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(initialCapacity);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Constructs a new, empty linked hash set.  (This package private_</span></span><br><span class="line"><span class="comment">_ * constructor is only used by LinkedHashSet.) The backing_</span></span><br><span class="line"><span class="comment">_ * HashMap instance is a LinkedHashMap with the specified initial_</span></span><br><span class="line"><span class="comment">_ * capacity and the specified load factor._</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span>      **_initialCapacity   the initial capacity of the hash map_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span>      **_loadFactor        the load factor of the hash map_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span>      **_dummy             ignored (distinguishes this_</span></span><br><span class="line"><span class="comment">_ *             constructor from other int, float constructor.)_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@throws</span>     **_IllegalArgumentException if the initial capacity is less_</span></span><br><span class="line"><span class="comment">_ *             than zero, or if the load factor is nonpositive_</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="title function_">HashSet</span><span class="params">(<span class="type">int</span> initialCapacity, <span class="type">float</span> loadFactor, <span class="type">boolean</span> dummy)</span> &#123;</span><br><span class="line">    map = <span class="keyword">new</span> <span class="title class_">LinkedHashMap</span>&lt;&gt;(initialCapacity, loadFactor);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="add-方法"><a href="#add-方法" class="headerlink" title="add 方法"></a>add 方法</h3><p>插入元素时，调用 HashMap 中的 put 方法，key 为插入元素，value 为默认的 Object 对象</p><p>当你把对象加入 <code>HashSet</code> 时，<code>HashSet</code> 会先计算对象的 <code>hashcode</code> 值来判断对象加入的位置，同时也会与其他加入的对象的 <code>hashcode</code> 值作比较，如果没有相符的 <code>hashcode</code>，<code>HashSet</code> 会假设对象没有重复出现。但是如果发现有相同 <code>hashcode</code> 值的对象，这时会调用 <code>equals()</code> 方法来检查 <code>hashcode</code> 相等的对象是否真的相同。如果两者相同，<code>HashSet</code> 就不会让加入操作成功。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Dummy value to associate with an Object in the backing Map</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Object _PRESENT _= <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Adds the specified element to this set if it is not already present.</span></span><br><span class="line"><span class="comment"> * More formally, adds the specified element &#123;<span class="doctag">@code</span> e&#125; to this set if</span></span><br><span class="line"><span class="comment"> * this set contains no element &#123;<span class="doctag">@code</span> e2&#125; such that</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@code</span> Objects.equals(e, e2)&#125;.</span></span><br><span class="line"><span class="comment"> * If this set already contains the element, the call leaves the set</span></span><br><span class="line"><span class="comment"> * unchanged and returns &#123;<span class="doctag">@code</span> false&#125;.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> e element to be added to this set</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> &#123;<span class="doctag">@code</span> true&#125; if this set did not already contain the specified</span></span><br><span class="line"><span class="comment"> * element</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> map.put(e, PRESENT)==<span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="ConcurrentHashMap-源码"><a href="#ConcurrentHashMap-源码" class="headerlink" title="ConcurrentHashMap 源码"></a>ConcurrentHashMap 源码</h2><p>ConcurrentHashMap 底层与 HashMap 相同，在具体操作上使用了 CAS （Compare-And-Swap）操作与 synchronized 锁来保证线程安全。</p><h3 id="initialTable-方法"><a href="#initialTable-方法" class="headerlink" title="initialTable 方法"></a>initialTable 方法</h3><p>实现了延迟初始化，只有在真正需要时才会分配和初始化内部数据结构，通过 CAS 操作来保证多个线程在并发情况下能够安全地初始化哈希表，具体流程如下：</p><ol><li>判断 sizeCtl 是否小于 0（sizeCtl 用于控制表的初始化和扩容行为）</li><li>如果小于 0，表示有线程正在进行初始化或扩容操作</li><li>如果大于等于 0，通过 CAS 将 sizeCtl 更新为 1。</li><li>并初始化 table</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Initializes table, using the size recorded in sizeCtl.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Node&lt;K,V&gt;[] initTable() &#123;</span><br><span class="line">    Node&lt;K,V&gt;[] tab; <span class="type">int</span> sc;</span><br><span class="line">    <span class="keyword">while</span> ((tab = table) == <span class="literal">null</span> || tab.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((sc = sizeCtl) &lt; <span class="number">0</span>)</span><br><span class="line">            Thread.<span class="keyword">yield</span>(); <span class="comment">// lost initialization race; just spin</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSetInt(<span class="built_in">this</span>, SIZECTL, sc, -<span class="number">1</span>)) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> ((tab = table) == <span class="literal">null</span> || tab.length == <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> (sc &gt; <span class="number">0</span>) ? sc : DEFAULT_CAPACITY;</span><br><span class="line">                    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">                    Node&lt;K,V&gt;[] nt = (Node&lt;K,V&gt;[])<span class="keyword">new</span> <span class="title class_">Node</span>&lt;?,?&gt;[n];</span><br><span class="line">                    table = tab = nt;</span><br><span class="line">                    sc = n - (n &gt;&gt;&gt; <span class="number">2</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                sizeCtl = sc;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> tab;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="put-方法-1"><a href="#put-方法-1" class="headerlink" title="put 方法"></a>put 方法</h3><p><code>put()</code> 方法用于将一个键值对插入到 <code>ConcurrentHashMap</code> 中。如果指定的键已存在，则更新其值；如果不存在，则插入新的键值对。具体流程如下：</p><ol><li>计算键的哈希值</li><li>如果哈希桶的位置为空，使用 CAS 操作尝试插入新节点、</li><li>如果不为空，使用 synchronize 同步块，来更新更新值，具体更新流程与 HashMap 相同</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line">_<span class="comment">/**_</span></span><br><span class="line"><span class="comment">_ * Maps the specified key to the specified value in this table._</span></span><br><span class="line"><span class="comment">_ * Neither the key nor the value can be null._</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * &lt;p&gt;The value can be retrieved by calling the &#123;_**<span class="doctag">@code</span> **_get&#125; method_</span></span><br><span class="line"><span class="comment">_ * with a key that is equal to the original key._</span></span><br><span class="line"><span class="comment">_ *_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span> **_key key with which the specified value is to be associated_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@param</span> **_value value to be associated with the specified key_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@return</span> **_the previous value associated with &#123;_**<span class="doctag">@code</span> **_key&#125;, or_</span></span><br><span class="line"><span class="comment">_ *         &#123;_**<span class="doctag">@code</span> **_null&#125; if there was no mapping for &#123;_**<span class="doctag">@code</span> **_key&#125;_</span></span><br><span class="line"><span class="comment">_ * _**<span class="doctag">@throws</span> **_NullPointerException if the specified key or value is null_</span></span><br><span class="line"><span class="comment">_ */</span>_</span><br><span class="line"><span class="keyword">public</span> V <span class="title function_">put</span><span class="params">(K key, V value)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> putVal(key, value, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">_<span class="comment">/** Implementation for put and putIfAbsent */</span>_</span><br><span class="line"><span class="keyword">final</span> V <span class="title function_">putVal</span><span class="params">(K key, V value, <span class="type">boolean</span> onlyIfAbsent)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (key == <span class="literal">null</span> || value == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> _spread_(key.hashCode());</span><br><span class="line">    <span class="type">int</span> <span class="variable">binCount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (Node&lt;K,V&gt;[] tab = table;;) &#123;</span><br><span class="line">        Node&lt;K,V&gt; f; <span class="type">int</span> n, i, fh; K fk; V fv;</span><br><span class="line">        <span class="keyword">if</span> (tab == <span class="literal">null</span> || (n = tab.length) == <span class="number">0</span>)</span><br><span class="line">            tab = initTable();</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((f = _tabAt_(tab, i = (n - <span class="number">1</span>) &amp; hash)) == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (_casTabAt_(tab, i, <span class="literal">null</span>, <span class="keyword">new</span> <span class="title class_">Node</span>&lt;K,V&gt;(hash, key, value)))</span><br><span class="line">                <span class="keyword">break</span>;                   <span class="comment">// no lock when adding to empty bin</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.hash) == _MOVED_)</span><br><span class="line">            tab = helpTransfer(tab, f);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (onlyIfAbsent <span class="comment">// check first node without acquiring lock</span></span><br><span class="line">                 &amp;&amp; fh == hash</span><br><span class="line">                 &amp;&amp; ((fk = f.key) == key || (fk != <span class="literal">null</span> &amp;&amp; key.equals(fk)))</span><br><span class="line">                 &amp;&amp; (fv = f.val) != <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">return</span> fv;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">V</span> <span class="variable">oldVal</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">synchronized</span> (f) &#123;</span><br><span class="line">                <span class="keyword">if</span> (_tabAt_(tab, i) == f) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (fh &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                        binCount = <span class="number">1</span>;</span><br><span class="line">                        <span class="keyword">for</span> (Node&lt;K,V&gt; e = f;; ++binCount) &#123;</span><br><span class="line">                            K ek;</span><br><span class="line">                            <span class="keyword">if</span> (e.hash == hash &amp;&amp;</span><br><span class="line">                                ((ek = e.key) == key ||</span><br><span class="line">                                 (ek != <span class="literal">null</span> &amp;&amp; key.equals(ek)))) &#123;</span><br><span class="line">                                oldVal = e.val;</span><br><span class="line">                                <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line">                                    e.val = value;</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                            Node&lt;K,V&gt; pred = e;</span><br><span class="line">                            <span class="keyword">if</span> ((e = e.next) == <span class="literal">null</span>) &#123;</span><br><span class="line">                                pred.next = <span class="keyword">new</span> <span class="title class_">Node</span>&lt;K,V&gt;(hash, key, value);</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> TreeBin) &#123;</span><br><span class="line">                        Node&lt;K,V&gt; p;</span><br><span class="line">                        binCount = <span class="number">2</span>;</span><br><span class="line">                        <span class="keyword">if</span> ((p = ((TreeBin&lt;K,V&gt;)f).putTreeVal(hash, key,</span><br><span class="line">                                                       value)) != <span class="literal">null</span>) &#123;</span><br><span class="line">                            oldVal = p.val;</span><br><span class="line">                            <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line">                                p.val = value;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span> (f <span class="keyword">instanceof</span> ReservationNode)</span><br><span class="line">                        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Recursive update&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (binCount != <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (binCount &gt;= _TREEIFY_THRESHOLD_)</span><br><span class="line">                    treeifyBin(tab, i);</span><br><span class="line">                <span class="keyword">if</span> (oldVal != <span class="literal">null</span>)</span><br><span class="line">                    <span class="keyword">return</span> oldVal;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    addCount(<span class="number">1L</span>, binCount);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;HashMap-源码&quot;&gt;&lt;a href=&quot;#HashMap-源码&quot; class=&quot;headerlink&quot; title=&quot;HashMap 源码&quot;&gt;&lt;/a&gt;HashMap 源码&lt;/h2&gt;&lt;h3 id=&quot;Node-数组&quot;&gt;&lt;a href=&quot;#Node-数组&quot; class</summary>
      
    
    
    
    
    <category term="Java" scheme="https://yzlzzz.xyz/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>ArrayList 与 LinkedList 源码分析及比较</title>
    <link href="https://yzlzzz.xyz/2024/08/06/list-diff/"/>
    <id>https://yzlzzz.xyz/2024/08/06/list-diff/</id>
    <published>2024-08-05T16:26:24.000Z</published>
    <updated>2024-08-05T16:44:44.165Z</updated>
    
    <content type="html"><![CDATA[<h2 id="ArrayList-源码分析"><a href="#ArrayList-源码分析" class="headerlink" title="ArrayList 源码分析"></a>ArrayList 源码分析</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ArrayList</span>&lt;E&gt; <span class="keyword">extends</span> <span class="title class_">AbstractList</span>&lt;E&gt;</span><br><span class="line">        <span class="keyword">implements</span> <span class="title class_">List</span>&lt;E&gt;, RandomAccess, Cloneable, java.io.Serializable</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Default initial capacity.</span></span><br><span class="line"><span class="comment">     * 默认初始大小为10</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">DEFAULT_CAPACITY</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * ArrayList底层是Object数组</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">transient</span> Object[] elementData; <span class="comment">// non-private to simplify nested class access</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * The size of the ArrayList (the number of elements it contains).</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@serial</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> size;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 带初始大小的构造方法</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ArrayList</span><span class="params">(<span class="type">int</span> initialCapacity)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (initialCapacity &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="built_in">this</span>.elementData = <span class="keyword">new</span> <span class="title class_">Object</span>[initialCapacity];</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (initialCapacity == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="built_in">this</span>.elementData = _EMPTY_ELEMENTDATA_;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Illegal Capacity: &quot;</span>+</span><br><span class="line">                                               initialCapacity);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 默认无参构造函数，此时数组大小初始化为10</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ArrayList</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 如有必要，增加此ArrayList实例的容量，以确保它至少能容纳元素的数量</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> minCapacity 所需的最小容量</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ensureCapacity</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line">        <span class="comment">//如果是true，minExpand的值为0，如果是false,minExpand的值为10</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">minExpand</span> <span class="operator">=</span> (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)</span><br><span class="line">                <span class="comment">// any size if not default element table</span></span><br><span class="line">                ? <span class="number">0</span></span><br><span class="line">                <span class="comment">// larger than default for default empty table. It&#x27;s already</span></span><br><span class="line">                <span class="comment">// supposed to be at default size.</span></span><br><span class="line">                : DEFAULT_CAPACITY;</span><br><span class="line">        <span class="comment">//如果最小容量大于已有的最大容量</span></span><br><span class="line">        <span class="keyword">if</span> (minCapacity &gt; minExpand) &#123;</span><br><span class="line">            ensureExplicitCapacity(minCapacity);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">   </span><br><span class="line">    <span class="comment">// 根据给定的最小容量和当前数组元素来计算所需容量。</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">calculateCapacity</span><span class="params">(Object[] elementData, <span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line">        <span class="comment">// 如果当前数组元素为空数组（初始情况），返回默认容量和最小容量中的较大值作为所需容量</span></span><br><span class="line">        <span class="keyword">if</span> (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) &#123;</span><br><span class="line">            <span class="keyword">return</span> Math.max(DEFAULT_CAPACITY, minCapacity);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 否则直接返回最小容量</span></span><br><span class="line">        <span class="keyword">return</span> minCapacity;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 确保内部容量达到指定的最小容量。</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">ensureCapacityInternal</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line">        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//判断是否需要扩容</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">ensureExplicitCapacity</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line">        modCount++;</span><br><span class="line">        <span class="comment">// overflow-conscious code</span></span><br><span class="line">        <span class="keyword">if</span> (minCapacity - elementData.length &gt; <span class="number">0</span>)</span><br><span class="line">            <span class="comment">//调用grow方法进行扩容，调用此方法代表已经开始扩容了</span></span><br><span class="line">            grow(minCapacity);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * ArrayList扩容的核心方法。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">grow</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line">        <span class="comment">// oldCapacity为旧容量，newCapacity为新容量</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">oldCapacity</span> <span class="operator">=</span> elementData.length;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//将oldCapacity 右移一位，其效果相当于oldCapacity /2，</span></span><br><span class="line">        <span class="comment">//我们知道位运算的速度远远快于整除运算，整句运算式的结果就是将新容量更新为旧容量的1.5倍，</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> oldCapacity + (oldCapacity &gt;&gt; <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//然后检查新容量是否大于最小需要容量，若还是小于最小需要容量，那么就把最小需要容量当作数组的新容量，</span></span><br><span class="line">        <span class="keyword">if</span> (newCapacity - minCapacity &lt; <span class="number">0</span>)</span><br><span class="line">            newCapacity = minCapacity;</span><br><span class="line">        <span class="comment">//再检查新容量是否超出了ArrayList所定义的最大容量，</span></span><br><span class="line">        <span class="comment">//若超出了，则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE，</span></span><br><span class="line">        <span class="comment">//如果minCapacity大于MAX_ARRAY_SIZE，则新容量则为Integer.MAX_VALUE，否则，新容量大小则为 MAX_ARRAY_SIZE。</span></span><br><span class="line">        <span class="keyword">if</span> (newCapacity - MAX_ARRAY_SIZE &gt; <span class="number">0</span>)</span><br><span class="line">            newCapacity = hugeCapacity(minCapacity);</span><br><span class="line">        <span class="comment">// minCapacity is usually close to size, so this is a win:</span></span><br><span class="line">        elementData = Arrays.copyOf(elementData, newCapacity);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ArrayList 的默认构造函数时，初始赋值的 Object 数组是空数组，等到真正需要添加元素时，此时分配容量，数组的大小初始化为 10，如果数组容量达到上限触发扩容，容量会扩容至原来容量的 1.5 倍，根据 <code>int newCapacity = oldCapacity + (oldCapacity &gt;&gt; 1)</code> 。</p><h2 id="LinkedList-源码分析"><a href="#LinkedList-源码分析" class="headerlink" title="LinkedList 源码分析"></a>LinkedList 源码分析</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LinkedList</span>&lt;E&gt;</span><br><span class="line">    <span class="keyword">extends</span> <span class="title class_">AbstractSequentialList</span>&lt;E&gt;</span><br><span class="line">    <span class="keyword">implements</span> <span class="title class_">List</span>&lt;E&gt;, Deque&lt;E&gt;, Cloneable, java.io.Serializable</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">transient</span> <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Pointer to first node.</span></span><br><span class="line"><span class="comment">     * 头节点</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">transient</span> Node&lt;E&gt; first;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Pointer to last node.</span></span><br><span class="line"><span class="comment">     * 尾节点</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">transient</span> Node&lt;E&gt; last;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Constructs an empty list.</span></span><br><span class="line"><span class="comment">     * 默认构造函数，构造空链表</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">LinkedList</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns the first element in this list.</span></span><br><span class="line"><span class="comment">     * 返回第一个节点元素</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> the first element in this list</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> NoSuchElementException if this list is empty</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">getFirst</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> Node&lt;E&gt; f = first;</span><br><span class="line">        <span class="keyword">if</span> (f == <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NoSuchElementException</span>();</span><br><span class="line">        <span class="keyword">return</span> f.item;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns the last element in this list.</span></span><br><span class="line"><span class="comment">     * 返回末尾节点元素</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> the last element in this list</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> NoSuchElementException if this list is empty</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">getLast</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> Node&lt;E&gt; l = last;</span><br><span class="line">        <span class="keyword">if</span> (l == <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NoSuchElementException</span>();</span><br><span class="line">        <span class="keyword">return</span> l.item;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">     <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Appends the specified element to the end of this list.</span></span><br><span class="line"><span class="comment">     * 将元素加入链表的末尾</span></span><br><span class="line"><span class="comment">     * &lt;p&gt;This method is equivalent to &#123;<span class="doctag">@link</span> #addLast&#125;.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> e element to be appended to this list</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> &#123;<span class="doctag">@code</span> true&#125; (as specified by &#123;<span class="doctag">@link</span> Collection#add&#125;)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line">        linkLast(e);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Links e as last element.</span></span><br><span class="line"><span class="comment">    * add函数中调用的方法，将e连接至链表末尾</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">linkLast</span><span class="params">(E e)</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> Node&lt;E&gt; l = last;</span><br><span class="line">        <span class="keyword">final</span> Node&lt;E&gt; newNode = <span class="keyword">new</span> <span class="title class_">Node</span>&lt;&gt;(l, e, <span class="literal">null</span>);</span><br><span class="line">        last = newNode;</span><br><span class="line">        <span class="keyword">if</span> (l == <span class="literal">null</span>)</span><br><span class="line">            first = newNode;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            l.next = newNode;</span><br><span class="line">        size++;</span><br><span class="line">        modCount++;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>链表节点的 Node 类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Node</span>&lt;E&gt; &#123;</span><br><span class="line">        E item;</span><br><span class="line">        Node&lt;E&gt; next;</span><br><span class="line">        Node&lt;E&gt; prev;</span><br><span class="line"></span><br><span class="line">        Node(Node&lt;E&gt; prev, E element, Node&lt;E&gt; next) &#123;</span><br><span class="line">            <span class="built_in">this</span>.item = element;</span><br><span class="line">            <span class="built_in">this</span>.next = next;</span><br><span class="line">            <span class="built_in">this</span>.prev = prev;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>LinkedList 是基于双向链表实现的类，一般场景下跟 ArrayList 相比，效率较差。</p><h2 id="对比"><a href="#对比" class="headerlink" title="对比"></a>对比</h2><ul><li><p><strong>底层数据结构：</strong><code>ArrayList</code> 底层是 Object 数组；<code>LinkedList</code> 底层是双向链表。</p></li><li><p><strong>访问的时间复杂度：</strong><code>ArrayList</code> 访问的时间复杂度为 O(1)，可以通过下标直接访问；<code>LinkedList</code> 访问的时间复杂度为 O(n)，需要通过节点逐步遍历至所需要访问的节点。</p></li><li><p><strong>插入&#x2F;删除的时间复杂度：</strong><code>ArrayList</code> 在尾部插入&#x2F;删除的时间复杂度为 O(1)，在特定位置插入&#x2F;删除的时间复杂度为 O(n)，需要将插入&#x2F;删除元素之后的元素前移或后移；<code>LinkedList</code> 插入&#x2F;删除的时间复杂度为 O(1)，只需要断开链表指针即可。</p></li><li><p><strong>存储方式：</strong><code>ArrayList</code> 以连续的内存空间存储；<code>LinkedList</code> 可分散存储在内存。</p></li><li><p><strong>空间占用：</strong><code>ArrayList</code> 相比 <code>LinkedList</code> 占用空间更少，<code>ArrayList</code> 需要预留一定的空间，而 <code>LinkedList</code> 是每个元素都要消耗比 <code>ArrayList</code> 更多的空间。</p></li><li><p>是否线程安全：<code>ArrayList</code> 和 <code>LinkedList</code> 都是不同步的，不保证线程安全。</p></li><li><p><strong>缓存局部性：</strong><code>ArrayList</code> 对局部更友好，数组具有更高的缓存命中率，因此它在操作效率上通常优于链表。具体表现在：</p><ul><li><strong>占用空间</strong>：链表元素比数组元素占用空间更多，导致缓存中容纳的有效数据量更少。</li><li><strong>缓存行</strong>：链表数据分散在内存各处，而缓存是“按行加载”的，因此加载到无效数据的比例更高。</li><li><strong>预取机制</strong>：数组比链表的数据访问模式更具“可预测性”，即系统更容易猜出即将被加载的数据。</li><li><strong>空间局部性</strong>：数组被存储在集中的内存空间中，因此被加载数据附近的数据更有可能即将被访问。</li></ul></li></ul><p>在《hello algorithm》中如此说：</p><blockquote><p>缓存虽然在空间容量上远小于内存，但它比内存快得多，在程序执行速度上起着至关重要的作用。由于缓存的容量有限，只能存储一小部分频繁访问的数据，因此当 CPU 尝试访问的数据不在缓存中时，就会发生<u>缓存未命中（cache miss）</u>，此时 CPU 不得不从速度较慢的内存中加载所需数据。</p></blockquote><blockquote><p>显然，<strong>“缓存未命中”越少，CPU 读写数据的效率就越高</strong>，程序性能也就越好。</p></blockquote><blockquote><p>为了尽可能达到更高的效率，缓存会采取以下数据加载机制。</p><ul><li><strong>缓存行</strong>：缓存不是单个字节地存储与加载数据，而是以缓存行为单位。相比于单个字节的传输，缓存行的传输形式更加高效。</li><li><strong>预取机制</strong>：处理器会尝试预测数据访问模式（例如顺序访问、固定步长跳跃访问等），并根据特定模式将数据加载至缓存之中，从而提升命中率。</li><li><strong>空间局部性</strong>：如果一个数据被访问，那么它附近的数据可能近期也会被访问。因此，缓存在加载某一数据时，也会加载其附近的数据，以提高命中率。</li><li><strong>时间局部性</strong>：如果一个数据被访问，那么它在不久的将来很可能再次被访问。缓存利用这一原理，通过保留最近访问过的数据来提高命中率。</li></ul></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;ArrayList-源码分析&quot;&gt;&lt;a href=&quot;#ArrayList-源码分析&quot; class=&quot;headerlink&quot; title=&quot;ArrayList 源码分析&quot;&gt;&lt;/a&gt;ArrayList 源码分析&lt;/h2&gt;&lt;figure class=&quot;highlight </summary>
      
    
    
    
    
    <category term="Java" scheme="https://yzlzzz.xyz/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>2024年秋招用友Java笔试</title>
    <link href="https://yzlzzz.xyz/2024/08/02/yonyou-test/"/>
    <id>https://yzlzzz.xyz/2024/08/02/yonyou-test/</id>
    <published>2024-08-02T15:28:56.000Z</published>
    <updated>2024-08-02T15:34:51.178Z</updated>
    
    <content type="html"><![CDATA[<h2 id="找出合规工序"><a href="#找出合规工序" class="headerlink" title="找出合规工序"></a>找出合规工序</h2><h3 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h3><p>工厂中有不同的工序，请按<strong>升序</strong>输出所有的<strong>合规工序</strong>。</p><p><strong>合规工序</strong>：工序的所有的路径工序都会到达<strong>终点工序</strong>。<strong>终点工序</strong>不会指向任何工序。</p><p>所有<strong>终点工序</strong>都为<strong>合规工序。</strong></p><h3 id="输入"><a href="#输入" class="headerlink" title="输入"></a>输入</h3><p>先输入 n，代表的工序的个数</p><p>接下来会有 n 行，第 i 行所代表的是第 i 个工序的下个工序</p><p>终点工序指向-1</p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p><strong>输入：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">7</span></span><br><span class="line"><span class="number">1</span> <span class="number">2</span></span><br><span class="line"><span class="number">2</span> <span class="number">3</span></span><br><span class="line"><span class="number">5</span></span><br><span class="line"><span class="number">0</span></span><br><span class="line"><span class="number">5</span></span><br><span class="line">-<span class="number">1</span></span><br><span class="line">-<span class="number">1</span></span><br></pre></td></tr></table></figure><p><strong>输出：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2</span> <span class="number">4</span> <span class="number">5</span> <span class="number">6</span></span><br></pre></td></tr></table></figure><p><strong>解释：</strong></p><p>工序 5 和 6 为终点工序，即为合规工序</p><p>工序 2 和 4 开始的所有下游工序最终都指向终点工序，按照升序排列最终结果为 2，4，5，6</p><h3 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h3><p>看到这题，最初的想法是 dfs，但是发现吃力不讨好，也想过用并查集，但也不好做。最后用贪心解决了。</p><p>由题意得，合规工序的所有路径都会指向终点工序，且终点工序不会指向其他任何工序。那么可以从图的角度理解，图中只要有环，那么环中的所有节点（工序）都不是合规工序，因为这些工序的某些路经无法到达终点工序。</p><p>从终点工序倒推，所有终点工序都是合规工序。</p><p>如果有工序只指向了合规工序，且没有指向其他工序，那么这个节点的所有路径都可以到达终点工序，这个节点为终点工序。接下来重复以上步骤，直到无法推导为止。</p><p>其中图上环中的所有工序无法被进入这个流程，因为他们相互指向，始终存在不是合规工序的子节点。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="type">int</span> n;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">static</span> List&lt;Integer&gt;[] adj;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">static</span> Set&lt;Integer&gt; set;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">in</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line"></span><br><span class="line">        n = in.nextInt();</span><br><span class="line"></span><br><span class="line">        adj = <span class="keyword">new</span> <span class="title class_">List</span>[n];</span><br><span class="line">        set = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">        in.nextLine();</span><br><span class="line">        <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//邻接矩阵的构造</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            String[] temp = in.nextLine().split(<span class="string">&quot; &quot;</span>);</span><br><span class="line">            adj[i] = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(temp.length);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> (String s : temp) &#123;</span><br><span class="line">                <span class="type">Integer</span> <span class="variable">val</span> <span class="operator">=</span> Integer.parseInt(s);</span><br><span class="line">                <span class="keyword">if</span> (val == -<span class="number">1</span>) &#123;</span><br><span class="line">                    end = val;</span><br><span class="line">                    set.add(i);</span><br><span class="line">                &#125;</span><br><span class="line">                adj[i].add(val);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">p</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(p) &#123;</span><br><span class="line">            p = <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">                <span class="keyword">if</span>(set.contains(i)) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (check(i)) &#123;</span><br><span class="line">                    set.add(i);</span><br><span class="line">                    p = <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        List&lt;Integer&gt; ans = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(set);</span><br><span class="line">        ans.sort(<span class="literal">null</span>);</span><br><span class="line">        <span class="keyword">for</span> (Integer a : ans) &#123;</span><br><span class="line">            System.out.print(a + <span class="string">&quot; &quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//检查一个工序指向的是否全是合规工序</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">check</span><span class="params">(<span class="type">int</span> i)</span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (Integer j : adj[i]) &#123;</span><br><span class="line">            <span class="keyword">if</span>(!set.contains(j)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="检查数字是否超过上限"><a href="#检查数字是否超过上限" class="headerlink" title="检查数字是否超过上限"></a>检查数字是否超过上限</h2><h3 id="题目描述-1"><a href="#题目描述-1" class="headerlink" title="题目描述"></a>题目描述</h3><p>数字可以被分成 <strong>n</strong> 份（n ≥ 2），存在一个上限 <strong>x</strong>，检查分成的子数的乘积是否超过上限。</p><p>返回数字子数的乘积超过上限，返回 <code>true</code>，反之返回 <code>false</code>。</p><h3 id="输入-1"><a href="#输入-1" class="headerlink" title="输入"></a>输入</h3><p>输入 x 与 n 分别代表上限和数字</p><h3 id="示例-1"><a href="#示例-1" class="headerlink" title="示例"></a>示例</h3><p><strong>输入：</strong> 161 14</p><p><strong>输出：</strong> true</p><p><strong>解释：</strong></p><p>14 &#x3D; 3 + 3 + 3 + 3 + 2</p><p>3 * 3 * 3 * 3 * 2 &#x3D; 162 &gt; 161</p><h3 id="解析-1"><a href="#解析-1" class="headerlink" title="解析"></a>解析</h3><p>由题意得，找出 num 的子数的最大乘积，从而判断是否超过上限。对于不同的数，不存在一个绝对最大分法，因此需要去搜索找到最大值。</p><p>递归搜索 + 保存计算结果 &#x3D; 记忆化搜索</p><p>使用动态规划解决本题，因为题中存在明显的子结构问题，因而可以使用动态来推导。</p><p>dfs(i, j)表示的是求数字 i 的最大乘积，并返回 i * j。</p><p><strong>转移公式：</strong> dfs(num, mul) &#x3D; max{dfs(num - i, mul * i)}，其中 1 ≤ i ≤ num</p><p>当 i &#x3D; num 时，即为 num * mul</p><p>存储其中的最大值，下次碰到时直接返回。</p><h3 id="参考-1"><a href="#参考-1" class="headerlink" title="参考"></a>参考</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> Map&lt;Long, Long&gt; memo = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ClassNotFoundException, InstantiationException, IllegalAccessException &#123;</span><br><span class="line">        <span class="type">Scanner</span> <span class="variable">in</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line"></span><br><span class="line">        <span class="type">long</span> <span class="variable">upLimit</span> <span class="operator">=</span> in.nextInt();</span><br><span class="line">        <span class="type">long</span> <span class="variable">sum</span> <span class="operator">=</span> in.nextInt();</span><br><span class="line"></span><br><span class="line">        <span class="type">long</span> <span class="variable">ans</span> <span class="operator">=</span> dfs(sum, <span class="number">1</span>);</span><br><span class="line">        </span><br><span class="line">        System.out.println(ans &gt; upLimit);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">long</span> <span class="title function_">dfs</span><span class="params">(<span class="type">long</span> num, <span class="type">long</span> mul)</span>&#123;</span><br><span class="line">        <span class="comment">//num 为1或为0时直接返回</span></span><br><span class="line">        <span class="keyword">if</span>(num == <span class="number">0</span> || num == <span class="number">1</span>) <span class="keyword">return</span> mul;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//如果已经计算过，就直接返回</span></span><br><span class="line">        <span class="keyword">if</span>(memo.containsKey(num)) <span class="keyword">return</span> memo.get(num) * mul;</span><br><span class="line"></span><br><span class="line">        <span class="type">long</span> <span class="variable">max</span> <span class="operator">=</span> num * mul;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; num; i++)&#123;</span><br><span class="line">            max = Math.max(max, dfs(num - i, mul * i));</span><br><span class="line">        &#125;</span><br><span class="line">        memo.put(num, max / mul);</span><br><span class="line">        <span class="keyword">return</span> max;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;找出合规工序&quot;&gt;&lt;a href=&quot;#找出合规工序&quot; class=&quot;headerlink&quot; title=&quot;找出合规工序&quot;&gt;&lt;/a&gt;找出合规工序&lt;/h2&gt;&lt;h3 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题</summary>
      
    
    
    
    
    <category term="算法" scheme="https://yzlzzz.xyz/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="Java" scheme="https://yzlzzz.xyz/tags/Java/"/>
    
    <category term="动态规划" scheme="https://yzlzzz.xyz/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
    <category term="贪心" scheme="https://yzlzzz.xyz/tags/%E8%B4%AA%E5%BF%83/"/>
    
  </entry>
  
  <entry>
    <title>找出数组中的多数元素</title>
    <link href="https://yzlzzz.xyz/2024/08/01/majority-element/"/>
    <id>https://yzlzzz.xyz/2024/08/01/majority-element/</id>
    <published>2024-07-31T16:03:20.000Z</published>
    <updated>2024-07-31T16:12:37.088Z</updated>
    
    <content type="html"><![CDATA[<p>今天做到一道题，题很简单，但是其中一些解法非常有意思，算法思想可以学习。</p><h2 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h2><p>给定一个大小为 <code>n</code> 的数组 <code>nums</code> ，返回其中的多数元素。多数元素是指在数组中出现次数 <strong>大于</strong> <code>⌊ n/2 ⌋</code> 的元素。</p><p>你可以假设数组是非空的，并且给定的数组总是存在多数元素。</p><p><strong>进阶：</strong> 尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。</p><h2 id="解析-1（Boyer-Moore-投票算法）"><a href="#解析-1（Boyer-Moore-投票算法）" class="headerlink" title="解析 1（Boyer-Moore 投票算法）"></a>解析 1（<strong>Boyer-Moore 投票算法</strong>）</h2><p>本题的普通解法很简单，可以用 HashMap 记录出现次数，这样做时间复杂度是 O(n)，空间复杂度是 O(n)；也可以直接排序，返回数组的下标为 n&#x2F;2 的数即可，时间复杂度为 O(nlogn)，空间复杂度是 O(1)。</p><p>但这题还有一种解法非常有意思，叫 <strong>Boyer-Moore 投票算法</strong>，也叫<strong>摩尔投票算法</strong>。这一算法应用的问题原型是在集合中寻找可能存在的多数元素，这一元素在输入的序列重复出现并占到了序列元素的一半以上。</p><p>算法有 <code>计数器</code> 与 <code>当前多数元素</code></p><ul><li><code>计数器</code> 初始为 0</li><li>对数组进行遍历，当碰到元素 x 时，如果 <code>计数器</code> 为 0，则将当前元素 x 赋值给 <code>当前多数元素</code>，并判断当前 <code>当前多数元素</code> 与元素 x 是否相等，如果相等 <code>计数器</code>+1，否则-1。</li><li>这个过程结束后，最终的 <code>当前多数元素</code> 即是数组中的多数元素。</li></ul><p>此外，还可以二次遍历，来确定此多数元素的次数，本题无此需要。</p><h2 id="参考-1"><a href="#参考-1" class="headerlink" title="参考 1"></a>参考 1</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">majorityElement</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">candidate</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> num : nums)&#123;</span><br><span class="line">        <span class="keyword">if</span>(count == <span class="number">0</span>) candidate = num;</span><br><span class="line"></span><br><span class="line">        count += (num == candidate) ? <span class="number">1</span> : -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> candidate;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>时间复杂度：</strong> O(n)</p><p><strong>空间复杂度：</strong> O(1)</p><h2 id="解析-2（随机化）"><a href="#解析-2（随机化）" class="headerlink" title="解析 2（随机化）"></a>解析 2（随机化）</h2><p>还有另一种解法也很有意思，因为是寻找数组中的多数元素，所以可以去随机寻找数组任意下标的数，并验证其出是否是多数元素，如果不是，则重复以上步骤，直到找出多数元素。因为寻找的是多数元素，所以随机找到的概率很大。</p><h2 id="参考-2"><a href="#参考-2" class="headerlink" title="参考 2"></a>参考 2</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">majorityElement</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">Random</span> <span class="variable">random</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">target</span> <span class="operator">=</span> nums.length / <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>)&#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">candidate</span> <span class="operator">=</span> random.nextInt(nums.length);</span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> num : nums)&#123;</span><br><span class="line">            <span class="keyword">if</span>(num == candidate) count++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span>(count &gt;= target) <span class="keyword">return</span> candidate;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>时间复杂度：</strong> O(n)，在最坏的情况下，时间复杂度为 O(∞)，这取决于所谓的“运气”。但由于寻找的是多数元素，寻找的期望次数为 2，因此时间复杂度是 O(n)。</p><p><strong>空间复杂度：</strong> O(1)</p><h2 id="相关链接"><a href="#相关链接" class="headerlink" title="相关链接"></a>相关链接</h2><p><a href="https://leetcode.cn/problems/majority-element">169. 多数元素 - 力扣（LeetCode）</a></p><p><a href="https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_majority_vote_algorithm">Boyer–Moore majority vote algorithm - Wikipedia</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;今天做到一道题，题很简单，但是其中一些解法非常有意思，算法思想可以学习。&lt;/p&gt;
&lt;h2 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h2&gt;&lt;p&gt;给定一个大小为 &lt;code&gt;n&lt;/c</summary>
      
    
    
    
    
    <category term="算法" scheme="https://yzlzzz.xyz/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="Java" scheme="https://yzlzzz.xyz/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>String 类的不可变性</title>
    <link href="https://yzlzzz.xyz/2024/07/30/immutable-string/"/>
    <id>https://yzlzzz.xyz/2024/07/30/immutable-string/</id>
    <published>2024-07-30T14:23:00.000Z</published>
    <updated>2024-07-31T09:57:12.313Z</updated>
    
    <content type="html"><![CDATA[<p>今天在 String 是发现 String 在 Java 中非常有趣。首先，String 是不可变的，当我们修改 String 的对象时，并不是在原对象上进行修改，而是新建一个 String 对象，并指向这个对象。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">String</span></span><br><span class="line">    <span class="keyword">implements</span> <span class="title class_">java</span>.io.Serializable, Comparable&lt;String&gt;, CharSequence,</span><br><span class="line">               Constable, ConstantDesc &#123;</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line">    <span class="meta">@Stable</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">byte</span>[] value;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>String 类的使用 <code>private</code> 和 <code>final</code> 关键字修饰存放字符的数组，且没有提供修改数组的方法。</p><blockquote><p><code>final</code> 关键字修饰类无法被继承，修饰的方法不能被重写，修饰的基本数据类型的值无法更改，修饰的引用类型无法修改地址。<code>final</code> 修饰数组不是 String 不可变的<strong>根本原因</strong>，因为 final 修饰下的数组地址不可改变，而其中的值仍然可以改变。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;aaa&quot;</span>;</span><br><span class="line">    _appendString_(s);</span><br><span class="line">    System._out_.println(<span class="string">&quot;String aaa &gt;&gt;&gt; &quot;</span>+s);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">appendString</span><span class="params">(String s)</span>&#123;</span><br><span class="line">    s += <span class="string">&quot;bbb&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//String aaa &gt;&gt;&gt; aaa</span></span><br></pre></td></tr></table></figure><p>像在以上的情况下，由于 String 的不可变性，对 String 的修改会让 String 指向一个新的地址，而 Java 是值传递语言，方法中对 String 的改变只改变了形参 S 的地址，并没有改变实参 S 的地址。</p><img src="/images/string/string.png" alt="String地址改变的示例图片" width="50%" style="margin-left: 25%">]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;今天在 String 是发现 String 在 Java 中非常有趣。首先，String 是不可变的，当我们修改 String 的对象时，并不是在原对象上进行修改，而是新建一个 String 对象，并指向这个对象。&lt;/p&gt;
&lt;figure class=&quot;highlight </summary>
      
    
    
    
    
    <category term="Java" scheme="https://yzlzzz.xyz/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>统计不是特殊数字的数字数量</title>
    <link href="https://yzlzzz.xyz/2024/07/28/lc-weekly-408-2/"/>
    <id>https://yzlzzz.xyz/2024/07/28/lc-weekly-408-2/</id>
    <published>2024-07-28T09:56:39.000Z</published>
    <updated>2024-07-28T10:03:25.691Z</updated>
    
    <content type="html"><![CDATA[<h2 id="描述"><a href="#描述" class="headerlink" title="描述"></a>描述</h2><p>给你两个 <strong>正整数</strong> <code>l</code> 和 <code>r</code>。对于任何数字 <code>x</code>，<code>x</code> 的所有正因数（除了 <code>x</code> 本身）被称为 <code>x</code> 的 <strong>真因数</strong>。</p><p>如果一个数字恰好仅有两个 <strong>真因数</strong>，则称该数字为 <strong>特殊数字</strong>。例如：</p><ul><li>数字 4 是 <strong>特殊数字</strong>，因为它的真因数为 1 和 2。</li><li>数字 6 不是 <strong>特殊数字</strong>，因为它的真因数为 1、2 和 3。</li></ul><p>返回区间 <code>[l, r]</code> 内<strong>不是特殊数字</strong>的数字数量。</p><p><strong>示例 ：</strong></p><p><strong>输入：</strong> l &#x3D; 5, r &#x3D; 7</p><p><strong>输出：</strong> 3</p><p><strong>解释：</strong> 区间 <code>[5, 7]</code> 内不存在特殊数字。</p><p><strong>提示：</strong></p><ul><li><code>1 &lt;= l &lt;= r &lt;= 10^9</code></li></ul><h2 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h2><p>leetcode的第408周赛第2题，通过题目描述，需要寻找有两个<strong>真因数</strong>的特殊数字，我们可以暴力遍历[1, 1000]区间内的特殊数字。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt; i; j++)&#123;</span><br><span class="line">            <span class="keyword">if</span>(i % j == <span class="number">0</span>) count++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span>(count == <span class="number">2</span>) System._out_.println(i);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出结果为</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">4</span></span><br><span class="line"><span class="number">9</span></span><br><span class="line"><span class="number">25</span></span><br><span class="line"><span class="number">49</span></span><br><span class="line"><span class="number">121</span></span><br><span class="line"><span class="number">169</span></span><br><span class="line"><span class="number">289</span></span><br><span class="line"><span class="number">361</span></span><br><span class="line"><span class="number">529</span></span><br><span class="line"><span class="number">841</span></span><br><span class="line"><span class="number">961</span></span><br></pre></td></tr></table></figure><p>可以发现一个共同点，这些数字的除 1 以外的另一个<strong>真因数</strong>是<strong>素数</strong>，且这个素数是特殊数字的开方数。</p><p>根据这个特点可以推出特殊数字是素数的平方数。因此求的是[√l, √r]区间内的素数的数量。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">nonSpecialCount</span><span class="params">(<span class="type">int</span> l, <span class="type">int</span> r)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">low</span> <span class="operator">=</span> (<span class="type">int</span>) Math.sqrt(l);</span><br><span class="line">        <span class="type">int</span> <span class="variable">up</span> <span class="operator">=</span> (<span class="type">int</span>) Math.sqrt(r);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(low * low &lt; l) low++;<span class="comment">//确保在[√l, √r]的区间内</span></span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> Math.max(low, <span class="number">2</span>); i &lt;= up; i++)&#123;</span><br><span class="line">            <span class="keyword">if</span>(isPrime(i)) count++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> r - l + <span class="number">1</span> - count ;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isPrime</span><span class="params">(<span class="type">int</span> num)</span>&#123;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">2</span>; i * i &lt;= num; i++)&#123;</span><br><span class="line">            <span class="keyword">if</span>(num % i == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p><strong>复杂度分析：</strong></p><ul><li>时间复杂度：O(√r * √r)</li><li>空间复杂度：O(1)</li></ul><h2 id="相关链接"><a href="#相关链接" class="headerlink" title="相关链接"></a>相关链接</h2><p><a href="https://leetcode.cn/problems/find-the-count-of-numbers-which-are-not-special">100371. 统计不是特殊数字的数字数量 - 力扣（LeetCode）</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;描述&quot;&gt;&lt;a href=&quot;#描述&quot; class=&quot;headerlink&quot; title=&quot;描述&quot;&gt;&lt;/a&gt;描述&lt;/h2&gt;&lt;p&gt;给你两个 &lt;strong&gt;正整数&lt;/strong&gt; &lt;code&gt;l&lt;/code&gt; 和 &lt;code&gt;r&lt;/code&gt;。对于任何数字 &lt;code</summary>
      
    
    
    
    
    <category term="算法" scheme="https://yzlzzz.xyz/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="Java" scheme="https://yzlzzz.xyz/tags/Java/"/>
    
    <category term="数学" scheme="https://yzlzzz.xyz/tags/%E6%95%B0%E5%AD%A6/"/>
    
  </entry>
  
  <entry>
    <title>回溯法</title>
    <link href="https://yzlzzz.xyz/2024/07/14/%E5%9B%9E%E6%BA%AF%E6%B3%95/"/>
    <id>https://yzlzzz.xyz/2024/07/14/%E5%9B%9E%E6%BA%AF%E6%B3%95/</id>
    <published>2024-07-14T15:29:23.000Z</published>
    <updated>2024-07-16T15:11:35.436Z</updated>
    
    <content type="html"><![CDATA[<h2 id="定义："><a href="#定义：" class="headerlink" title="定义："></a>定义：</h2><ul><li>回溯算法是类似于枚举的过程，在搜索过程中寻找满足条件的解，当不满足条件时，就退回重新选择。经常被用在深度优先搜索（DFS）和广度优先搜索（BFS）的技巧。关键点是：走不通就回头。</li></ul><h2 id="算法过程："><a href="#算法过程：" class="headerlink" title="算法过程："></a>算法过程：</h2><ol><li>构造空间树：通过构造空间树来搜索所有可能的结果，树的一个节点代表了问题的一个状态。从根节点开始，逐步向下扩展，直到叶子节点。</li><li>遍历：在回溯算法中，遍历过程实际上是深度优先搜索的过程。</li><li>如遇到边界条件，即不再向下搜索，转而搜索另一条链：当遇到错误状态或无法继续遍历时，会回溯到上一个状态，即 <strong>“走不通就回头”</strong>。</li><li>达到目标条件，输出结果。</li></ol><h2 id="例题"><a href="#例题" class="headerlink" title="例题"></a>例题</h2><p><strong>中心对称数：</strong></p><p>给定一个整数 <code>n</code> ，返回所有长度为 <code>n</code> 的 <strong>中心对称数</strong> 。你可以以 <strong>任何顺序</strong> 返回答案。</p><p><strong>中心对称数</strong> 是一个数字在旋转了 <code>180</code> 度之后看起来依旧相同的数字（或者上下颠倒地看）。</p><p><strong>示例 1:</strong></p><p><strong>输入：</strong> n &#x3D; 2<br><strong>输出：</strong>[“11”,”69”,”88”,”96”]</p><p><strong>示例 2:</strong></p><p><strong>输入：</strong> n &#x3D; 1<br><strong>输出：</strong>[“0”,”1”,”8”]</p><p><strong>解析：</strong></p><p>由题意得，满足条件的数字分别有 0 和 0、1 和 1、8 和 8、6 和 9、9 和 6 这五种情况，其中 0 不能不是开头（即不能有前缀零），69 这两个数不能在数字的**”中心”。**</p><p><strong>答案：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    List&lt;String&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="type">char</span>[] path;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> List&lt;String&gt; <span class="title function_">findStrobogrammatic</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">        path = <span class="keyword">new</span> <span class="title class_">char</span>[n];</span><br><span class="line">        </span><br><span class="line">        backtracking(n, <span class="number">0</span>, n - <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtracking</span><span class="params">(<span class="type">int</span> n, <span class="type">int</span> left, <span class="type">int</span> right)</span> &#123;</span><br><span class="line">        <span class="comment">//终止条件</span></span><br><span class="line">        <span class="keyword">if</span>(left &gt; right) &#123;</span><br><span class="line">            res.add(<span class="keyword">new</span> <span class="title class_">String</span>(path));</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//除长度为1的情况，首位非0</span></span><br><span class="line">        <span class="keyword">if</span>(left &gt; <span class="number">0</span> || n == <span class="number">1</span>) &#123;</span><br><span class="line">            path[left] = <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">            path[right] = <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">            backtracking(n, left + <span class="number">1</span>, right - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//前后为1的情况</span></span><br><span class="line">        path[left] = <span class="string">&#x27;1&#x27;</span>;</span><br><span class="line">        path[right] = <span class="string">&#x27;1&#x27;</span>;</span><br><span class="line">        backtracking(n, left + <span class="number">1</span>, right - <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//前后为8的情况</span></span><br><span class="line">        path[left] = <span class="string">&#x27;8&#x27;</span>;</span><br><span class="line">        path[right] = <span class="string">&#x27;8&#x27;</span>;</span><br><span class="line">        backtracking(n, left + <span class="number">1</span>, right - <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//不是中心时，前后为69的情况</span></span><br><span class="line">        <span class="keyword">if</span>(left != right)&#123;</span><br><span class="line">            path[left] = <span class="string">&#x27;6&#x27;</span>;</span><br><span class="line">            path[right] = <span class="string">&#x27;9&#x27;</span>;</span><br><span class="line">            backtracking(n, left + <span class="number">1</span>, right - <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">            path[left] = <span class="string">&#x27;9&#x27;</span>;</span><br><span class="line">            path[right] = <span class="string">&#x27;6&#x27;</span>;</span><br><span class="line">            backtracking(n, left + <span class="number">1</span>, right - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>相关链接：</strong></p><p><a href="https://leetcode.cn/problems/strobogrammatic-number-ii/">247. 中心对称数 II - 力扣（LeetCode）</a></p><p><a href="https://oi-wiki.org/search/backtracking/">回溯法 - OI Wiki</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;定义：&quot;&gt;&lt;a href=&quot;#定义：&quot; class=&quot;headerlink&quot; title=&quot;定义：&quot;&gt;&lt;/a&gt;定义：&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;回溯算法是类似于枚举的过程，在搜索过程中寻找满足条件的解，当不满足条件时，就退回重新选择。经常被用在深度优先搜索（DFS</summary>
      
    
    
    
    
    <category term="算法" scheme="https://yzlzzz.xyz/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="回溯" scheme="https://yzlzzz.xyz/tags/%E5%9B%9E%E6%BA%AF/"/>
    
    <category term="Java" scheme="https://yzlzzz.xyz/tags/Java/"/>
    
  </entry>
  
</feed>
