<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://yyym-y.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://yyym-y.github.io/" rel="alternate" type="text/html" /><updated>2024-05-08T06:33:14+00:00</updated><id>https://yyym-y.github.io/feed.xml</id><title type="html">yyym</title><subtitle>This is yyym's personal website, which contains some blogs, resources, and... some secrets :) Stay tuned for many useful and interesting tools that are under development. Thank you for your visit. If you have any ideas, please get in touch with me (●'◡'●)
</subtitle><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><entry><title type="html">贝叶斯分类器</title><link href="https://yyym-y.github.io/2024/05/05/%E8%B4%9D%E5%8F%B6%E6%96%AF%E5%88%86%E7%B1%BB%E5%99%A8/" rel="alternate" type="text/html" title="贝叶斯分类器" /><published>2024-05-05T00:00:00+00:00</published><updated>2024-05-05T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/05/05/%E8%B4%9D%E5%8F%B6%E6%96%AF%E5%88%86%E7%B1%BB%E5%99%A8</id><content type="html" xml:base="https://yyym-y.github.io/2024/05/05/%E8%B4%9D%E5%8F%B6%E6%96%AF%E5%88%86%E7%B1%BB%E5%99%A8/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#贝叶斯概率" id="markdown-toc-贝叶斯概率">贝叶斯概率</a></li>
  <li><a href="#贝叶斯法则" id="markdown-toc-贝叶斯法则">贝叶斯法则</a></li>
  <li><a href="#贝叶斯和朴素贝叶斯分类器" id="markdown-toc-贝叶斯和朴素贝叶斯分类器">贝叶斯和朴素贝叶斯分类器</a></li>
</ul>

<h2 id="贝叶斯概率">贝叶斯概率</h2>

<p>在数理统计学中，有两种学派，一种是频率学派（古典学派），一种是贝叶斯学派</p>

<p>频率学派是一种客观概率，其定义可以理解为：</p>

<p><strong>在 $N$ 次 重复独立投币的实验中，硬币正面朝上的频率为 $\frac{f}{N}$（其中 $f$ 为硬币正面朝上的次数）将在常数 $\alpha$ 左右波动</strong></p>

<p><strong>于是正面朝上的概率就是 $P = \alpha$</strong></p>

<p>而贝叶斯概率是一种<strong>主观概率</strong>，贝叶斯概率应该测量某一个体对于一个不确定命题的置信程度，因此在这个意义下是主观的</p>

<h2 id="贝叶斯法则">贝叶斯法则</h2>

<p><strong>贝叶斯法则是贝叶斯概率的理论表达</strong></p>

<p>首先明白一点：</p>

<blockquote>
  <p>对于两个事件 $X$ , $Y$</p>

  <p>如果 $X$ 独立于 $Y$ ， 那么：$P(XY) = P(X)P(Y)$</p>

  <p>如果不独立， 则： $P(XY) = P(Y)P(X|Y) = P(X)P(Y|X)$</p>

  <p>(可以理解为：$Y$ 发生的概率乘上 $Y$ 发生并且 $X$ 发生的概率)</p>
</blockquote>

<p>下面便是贝叶斯法则的描述：</p>

<p>如果有 $k$ 个互斥事件 $y_1, y_2, \dots , y_k$ ， 并且 $y_1 + y_2 + \dots + y_k = 1$ ， 且有一个可观测事件 $X$ ， 则：</p>

\[P(y_i|X) = \frac{P(Xy_i)}{P(X)} = \frac{P(y_i)P(X|y_i)}{P(X)} \qquad (i = 1,2,\dots,k)\]

<p>几个概念</p>

<blockquote>
  <p>$P(y_i)$ 代表<strong>先验概率(Prior)</strong> ，是指根据以往经验和分析得到的概率， 是和 $X$ 无关的概率</p>

  <p>$P(y_i | X)$ 代表<strong>后验概率(Posterior)</strong> ，是指在得到“结果”的信息后重新修正的概率，是事件 $X$ 发生下，事件 $y_i$ 发生的概率</p>

  <p>$P(X|y_i)$ 代表<strong>数据似然(Likelihood)</strong> ，是事件 $y_i$ 发生下，事件 $X$ 发生的概率，该值越大，说明事件 $y_i$ 越有助于事件 $X$ 发生</p>
</blockquote>

<p>最后，我们将上述公式再一次完善：</p>

\[P(y_i|X) = \frac{P(Xy_i)}{P(X)} = \frac{P(y_i)P(X|y_i)}{P(X)} = \frac{P(y_i)P(X|y_i)}{\sum\limits_{i = 1}^k P(y_i)P(X|y_i)}\]

<h2 id="贝叶斯和朴素贝叶斯分类器">贝叶斯和朴素贝叶斯分类器</h2>

<p><strong>NewBing的解释</strong> :smile:</p>

<p>贝叶斯分类是一类分类算法的总称，这类算法均以贝叶斯定理为基础，故统称为贝叶斯分类。</p>

<p>它是统计学的一种分类方法，利用概率统计知识进行分类。</p>

<p>贝叶斯分类器是各种分类器中分类错误概率最小或者在预先给定代价的情况下平均风险最小的分类器</p>

<p><strong>我来瞎扯</strong> :upside_down_face:</p>

<p>我们用一个例子引入：</p>

<p>现在判断一个商品是否会被购买，我们现有的可以参考的条件有两点，一个是性别，另一个是年龄段</p>

<p>他们分别有以下几个值：</p>

<blockquote>
  <p><strong>是否购买</strong> ： yes	no</p>

  <p><strong>性别</strong> ： 0	1</p>

  <p><strong>年龄段</strong> ： A	B	C</p>
</blockquote>

<p>贝叶斯分类器可以将上述的一些特征对结果的影响数值话</p>

<p>我们在这个例子中主要是判断一个人是否会购买，所以这个时候是否购买为 $X$</p>

<p>因为有两种情况，所以有两个 $y$ ，分别是 ：$y_1$ $y_2$ ，代表买和不买</p>

<p>现在假设一个人性别特征是 $0$ ， 年龄段为 $A$ ,是否会购买</p>

<p>这个时候性别 $y_1 = 0$ , 年龄段 $y_2 = A$ , 记为条件 $X$</p>

<p>而贝叶斯分离器就是可以解决这种问题，只需要算出这个人买的概率和不买的概率即可</p>

<p>有购买的概率</p>

\[P(y = y_1 | X) = \frac{P(y = y_1)P(X|y = y_1)}{\sum\limits_{i = 1}^kP(y = y_i)P(X | y = y_i)} = \frac{在X条件下购买的情况}{X发生的所有情况}\]

<p>有不购买的情况：</p>

\[P(y = y_2 | X) = \frac{P(y = y_2)P(X|y = y_2)}{\sum\limits_{i = 1}^kP(y = y_i)P(X | y = y_i)} = \frac{在X条件下不购买的情况}{X发生的所有情况}\]

<p>之后我们采取<strong>最大后验概率估计原则(MAP)</strong>选择</p>

<p>说人话就是谁最大选哪个, 在我们的例子中是：</p>

\[\begin{align}
	\begin{cases}
		P(y = y_1 | X) &gt; P(y = y_2 | X) \qquad Yes\\\\
		P(y = y_1 | X) &lt; P(y = y_2 | X) \qquad No\\\\
		P(y = y_1 | X) = P(y = y_2 | X) \qquad Any
	\end{cases}
\end{align}\]]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[贝叶斯概率 贝叶斯法则 贝叶斯和朴素贝叶斯分类器 贝叶斯概率 在数理统计学中，有两种学派，一种是频率学派（古典学派），一种是贝叶斯学派 频率学派是一种客观概率，其定义可以理解为： 在 $N$ 次 重复独立投币的实验中，硬币正面朝上的频率为 $\frac{f}{N}$（其中 $f$ 为硬币正面朝上的次数）将在常数 $\alpha$ 左右波动 于是正面朝上的概率就是 $P = \alpha$ 而贝叶斯概率是一种主观概率，贝叶斯概率应该测量某一个体对于一个不确定命题的置信程度，因此在这个意义下是主观的 贝叶斯法则 贝叶斯法则是贝叶斯概率的理论表达 首先明白一点： 对于两个事件 $X$ , $Y$ 如果 $X$ 独立于 $Y$ ， 那么：$P(XY) = P(X)P(Y)$ 如果不独立， 则： $P(XY) = P(Y)P(X|Y) = P(X)P(Y|X)$ (可以理解为：$Y$ 发生的概率乘上 $Y$ 发生并且 $X$ 发生的概率) 下面便是贝叶斯法则的描述： 如果有 $k$ 个互斥事件 $y_1, y_2, \dots , y_k$ ， 并且 $y_1 + y_2 + \dots + y_k = 1$ ， 且有一个可观测事件 $X$ ， 则： \[P(y_i|X) = \frac{P(Xy_i)}{P(X)} = \frac{P(y_i)P(X|y_i)}{P(X)} \qquad (i = 1,2,\dots,k)\] 几个概念 $P(y_i)$ 代表先验概率(Prior) ，是指根据以往经验和分析得到的概率， 是和 $X$ 无关的概率 $P(y_i | X)$ 代表后验概率(Posterior) ，是指在得到“结果”的信息后重新修正的概率，是事件 $X$ 发生下，事件 $y_i$ 发生的概率 $P(X|y_i)$ 代表数据似然(Likelihood) ，是事件 $y_i$ 发生下，事件 $X$ 发生的概率，该值越大，说明事件 $y_i$ 越有助于事件 $X$ 发生 最后，我们将上述公式再一次完善： \[P(y_i|X) = \frac{P(Xy_i)}{P(X)} = \frac{P(y_i)P(X|y_i)}{P(X)} = \frac{P(y_i)P(X|y_i)}{\sum\limits_{i = 1}^k P(y_i)P(X|y_i)}\] 贝叶斯和朴素贝叶斯分类器 NewBing的解释 :smile: 贝叶斯分类是一类分类算法的总称，这类算法均以贝叶斯定理为基础，故统称为贝叶斯分类。 它是统计学的一种分类方法，利用概率统计知识进行分类。 贝叶斯分类器是各种分类器中分类错误概率最小或者在预先给定代价的情况下平均风险最小的分类器 我来瞎扯 :upside_down_face: 我们用一个例子引入： 现在判断一个商品是否会被购买，我们现有的可以参考的条件有两点，一个是性别，另一个是年龄段 他们分别有以下几个值： 是否购买 ： yes no 性别 ： 0 1 年龄段 ： A B C 贝叶斯分类器可以将上述的一些特征对结果的影响数值话 我们在这个例子中主要是判断一个人是否会购买，所以这个时候是否购买为 $X$ 因为有两种情况，所以有两个 $y$ ，分别是 ：$y_1$ $y_2$ ，代表买和不买 现在假设一个人性别特征是 $0$ ， 年龄段为 $A$ ,是否会购买 这个时候性别 $y_1 = 0$ , 年龄段 $y_2 = A$ , 记为条件 $X$ 而贝叶斯分离器就是可以解决这种问题，只需要算出这个人买的概率和不买的概率即可 有购买的概率 \[P(y = y_1 | X) = \frac{P(y = y_1)P(X|y = y_1)}{\sum\limits_{i = 1}^kP(y = y_i)P(X | y = y_i)} = \frac{在X条件下购买的情况}{X发生的所有情况}\] 有不购买的情况： \[P(y = y_2 | X) = \frac{P(y = y_2)P(X|y = y_2)}{\sum\limits_{i = 1}^kP(y = y_i)P(X | y = y_i)} = \frac{在X条件下不购买的情况}{X发生的所有情况}\] 之后我们采取最大后验概率估计原则(MAP)选择 说人话就是谁最大选哪个, 在我们的例子中是： \[\begin{align} \begin{cases} P(y = y_1 | X) &gt; P(y = y_2 | X) \qquad Yes\\\\ P(y = y_1 | X) &lt; P(y = y_2 | X) \qquad No\\\\ P(y = y_1 | X) = P(y = y_2 | X) \qquad Any \end{cases} \end{align}\]]]></summary></entry><entry><title type="html">chapter7.0 - 持久化存储</title><link href="https://yyym-y.github.io/2024/04/29/Android_note7/" rel="alternate" type="text/html" title="chapter7.0 - 持久化存储" /><published>2024-04-29T00:00:00+00:00</published><updated>2024-04-29T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/04/29/Android_note7</id><content type="html" xml:base="https://yyym-y.github.io/2024/04/29/Android_note7/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#前言" id="markdown-toc-前言">前言</a></li>
  <li><a href="#文件存储" id="markdown-toc-文件存储">文件存储</a>    <ul>
      <li><a href="#保存数据到文件中" id="markdown-toc-保存数据到文件中">保存数据到文件中</a></li>
      <li><a href="#从文件中读取数据" id="markdown-toc-从文件中读取数据">从文件中读取数据</a></li>
    </ul>
  </li>
  <li><a href="#sharedpreferences-存储" id="markdown-toc-sharedpreferences-存储">SharedPreferences 存储</a>    <ul>
      <li><a href="#获取-sharedpreferences-对象" id="markdown-toc-获取-sharedpreferences-对象">获取 SharedPreferences 对象</a></li>
      <li><a href="#从-sharedpreferences-中写入" id="markdown-toc-从-sharedpreferences-中写入">从 SharedPreferences 中写入</a></li>
      <li><a href="#从-sharedpreferences-中读入" id="markdown-toc-从-sharedpreferences-中读入">从 SharedPreferences 中读入</a></li>
    </ul>
  </li>
  <li><a href="#sqlite-数据库存储" id="markdown-toc-sqlite-数据库存储">SQLite 数据库存储</a>    <ul>
      <li><a href="#创建数据库" id="markdown-toc-创建数据库">创建数据库</a></li>
      <li><a href="#插入数据" id="markdown-toc-插入数据">插入数据</a></li>
      <li><a href="#更新数据" id="markdown-toc-更新数据">更新数据</a></li>
      <li><a href="#删除数据" id="markdown-toc-删除数据">删除数据</a></li>
      <li><a href="#查询数据" id="markdown-toc-查询数据">查询数据</a></li>
      <li><a href="#使用sql-操作数据库" id="markdown-toc-使用sql-操作数据库">使用SQL 操作数据库</a></li>
      <li><a href="#事务操作" id="markdown-toc-事务操作">事务操作</a></li>
    </ul>
  </li>
  <li><a href="#kotlin-进阶课堂" id="markdown-toc-kotlin-进阶课堂">Kotlin 进阶课堂</a>    <ul>
      <li><a href="#高阶函数的应用" id="markdown-toc-高阶函数的应用">高阶函数的应用</a></li>
    </ul>
  </li>
</ul>

<h2 id="前言">前言</h2>

<p>所谓持久化存储, 就是指将那些内存中的瞬时数据保存到存储设备中，保证即使在手机或计算机关机 的情况下，这些数据仍然不会丢失。</p>

<p>我们将主要介绍三种持久化存储手段, 分别是 :</p>

<ul>
  <li>文件存储</li>
  <li><code class="language-plaintext highlighter-rouge">SharedPreferences</code> 存储</li>
  <li>数据库存储</li>
</ul>

<h2 id="文件存储">文件存储</h2>

<h3 id="保存数据到文件中">保存数据到文件中</h3>

<p>我们先来展示一下保存的代码</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">save</span><span class="p">(</span><span class="n">inputText</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">try</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">output</span> <span class="p">=</span> <span class="nf">openFileOutput</span><span class="p">(</span><span class="s">"data"</span><span class="p">,</span> <span class="nc">Context</span><span class="p">.</span><span class="nc">MODE_PRIVATE</span><span class="p">)</span>
        <span class="kd">val</span> <span class="py">writer</span> <span class="p">=</span> <span class="nc">BufferedWriter</span><span class="p">(</span><span class="nc">OutputStreamWriter</span><span class="p">(</span><span class="n">output</span><span class="p">))</span>
        <span class="n">writer</span><span class="p">.</span><span class="nf">use</span> <span class="p">{</span>
            <span class="n">it</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">inputText</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="nc">IOException</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">e</span><span class="p">.</span><span class="nf">printStackTrace</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Context</code> 类中提供了一个 <code class="language-plaintext highlighter-rouge">openFileOutput()</code> 方法，可以用于将数据存储到指定的文件中。</p>

<p>这个方法接收两个参数：</p>

<blockquote>
  <p>第一个参数是文件名，在文件创建的时候使用，注意这里指定的文件名不可以包含路径，因为所有的文件都默认存储到 <code class="language-plaintext highlighter-rouge">/data/data//files/</code> 目录下</p>

  <p>第二个参数是文件的操作模式，主要有 <strong>MODE_PRIVATE</strong> 和 <strong>MODE_APPEND</strong> 两种模式可选，默认是 MODE_PRIVATE，表示当指定相同文件名的时候，所写入的内容将会覆盖原文件中的内容，而 MODE_APPEND 则表示如果该文件已存在，就往文件里面追加内容，不存在就创建新文件。</p>

  <blockquote>
    <p>其实文件的操作模式本来还有另外两种：<strong>MODE_WORLD_READABLE</strong> 和 <strong>MODE_WORLD_WRITEABLE</strong>。这两种模式表示允许其他应用程序对我们程序中的文件进行读写操作，不过由于这两种模式过于危险，很容易引起应用的安全漏洞，已在Android 4.2版本中被废弃</p>
  </blockquote>
</blockquote>

<p><img src="/assets/images/Android/img/file-store.png" alt="file-store" /></p>

<h3 id="从文件中读取数据">从文件中读取数据</h3>

<p>我们先看看从文件中读取数据的代码:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">fun</span> <span class="nf">load</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">content</span> <span class="p">=</span> <span class="nc">StringBuilder</span><span class="p">()</span>
    <span class="k">try</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">input</span> <span class="p">=</span> <span class="nf">openFileInput</span><span class="p">(</span><span class="s">"data"</span><span class="p">)</span>
        <span class="kd">val</span> <span class="py">reader</span> <span class="p">=</span> <span class="nc">BufferedReader</span><span class="p">(</span><span class="nc">InputStreamReader</span><span class="p">(</span><span class="n">input</span><span class="p">))</span>
        <span class="n">reader</span><span class="p">.</span><span class="nf">use</span> <span class="p">{</span>
            <span class="n">reader</span><span class="p">.</span><span class="nf">forEachLine</span> <span class="p">{</span>
                <span class="n">content</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="nc">IOException</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">e</span><span class="p">.</span><span class="nf">printStackTrace</span><span class="p">()</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">content</span><span class="p">.</span><span class="nf">toString</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>在这段代码中，首先通过 <code class="language-plaintext highlighter-rouge">openFileInput()</code> 方法获取了一个 <code class="language-plaintext highlighter-rouge">FileInputStream</code> 对象，然后借助它又构建出了一个 <code class="language-plaintext highlighter-rouge">InputStreamReader</code> 对象，接着再使用 <code class="language-plaintext highlighter-rouge">InputStreamReader</code> 构建出 一个 <code class="language-plaintext highlighter-rouge">BufferedReader</code> 对象，这样我们就可以通过<code class="language-plaintext highlighter-rouge">BufferedReader</code> 将文件中的数据一行行读 取出来，并拼接到 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 对象当中，最后将读取的内容返回就可以了。</p>

<p>注意，这里从文件中读取数据使用了一个 <code class="language-plaintext highlighter-rouge">forEachLine</code> 函数，这也是 <code class="language-plaintext highlighter-rouge">Kotlin</code> 提供的一个内置扩展函数，它会将读到的每行内容都回调到Lambda表达式中，我们在Lambda表达式中完成拼接逻辑即可</p>

<h2 id="sharedpreferences-存储">SharedPreferences 存储</h2>

<p>不同于文件的存储方式，<code class="language-plaintext highlighter-rouge">SharedPreferences</code> 是使用<strong>键值对</strong>的方式来存储数据的。也就是说， 当保存一条数据的时候，需要给这条数据提供一个对应的键，这样在读取数据的时候就可以通 过这个键把相应的值取出来。而且<code class="language-plaintext highlighter-rouge">SharedPreferences</code> 还支持多种不同的数据类型存储，如果 存储的数据类型是整型，那么读取出来的数据也是整型的；如果存储的数据是一个字符串，那 么读取出来的数据仍然是字符串。</p>

<blockquote>
  <p>可以理解为是一个 <code class="language-plaintext highlighter-rouge">Map</code></p>
</blockquote>

<h3 id="获取-sharedpreferences-对象">获取 SharedPreferences 对象</h3>

<p>我们主要有以下几种方法来获取</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">Context</code>  类中的 <code class="language-plaintext highlighter-rouge">getSharedPreferences()</code> 方法</strong></li>
</ul>

<p>此方法接收两个参数：</p>

<p>第一个参数用于指定 <code class="language-plaintext highlighter-rouge">SharedPreferences</code> 文件的名称，如果指定的 文件不存在则会创建一个</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">SharedPreferences</code> 文件都是存放在 <code class="language-plaintext highlighter-rouge">/data/data//shared_prefs/</code> 目录下的</p>
</blockquote>

<p>第二个参数用于指定操作模式，目前只有默认的 <code class="language-plaintext highlighter-rouge">MODE_PRIVATE</code> 这一种模式可选，它和直接传入 $0$ 的效果是相同的，表示只有当前的应用程序才可以对这个 <code class="language-plaintext highlighter-rouge">SharedPreferences</code> 文件进行读写。其他几种操作模式均已被废弃</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">Activity</code> 类中的 <code class="language-plaintext highlighter-rouge">getPreferences()</code> 方法</strong></li>
</ul>

<p>这个方法和 <code class="language-plaintext highlighter-rouge">Context</code> 中的 <code class="language-plaintext highlighter-rouge">getSharedPreferences()</code> 方法很相似，不过它只接收一个操作模式参数，因为使用这个方法时会自动将当前 <code class="language-plaintext highlighter-rouge">Activity</code> 的类名作为 <code class="language-plaintext highlighter-rouge">SharedPreferences</code> 的文件名。</p>

<h3 id="从-sharedpreferences-中写入">从 SharedPreferences 中写入</h3>

<p>接下来就是如何往里面添加数据了, 一般可分为以下几步:</p>

<ol>
  <li>调用 <code class="language-plaintext highlighter-rouge">SharedPreferences</code> 对象的 <code class="language-plaintext highlighter-rouge">edit()</code> 方法获取一个 <code class="language-plaintext highlighter-rouge">SharedPreferences.Editor</code> 对象</li>
  <li>向 <code class="language-plaintext highlighter-rouge">SharedPreferences.Editor</code> 对象中添加数据，比如添加一个布尔型数据就使用 <code class="language-plaintext highlighter-rouge">putBoolean()</code> 方法，添加一个字符串则使用 <code class="language-plaintext highlighter-rouge">putString()</code> 方法，以此类推</li>
  <li>调用 <code class="language-plaintext highlighter-rouge">apply()</code> 方法将添加的数据提交，从而完成数据存储操作</li>
</ol>

<p>我们用代码演示如下 :</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">saveBySharedPreferences</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">editor</span> <span class="p">=</span> <span class="nf">getSharedPreferences</span><span class="p">(</span><span class="s">"data"</span><span class="p">,</span> <span class="nc">Context</span><span class="p">.</span><span class="nc">MODE_PRIVATE</span><span class="p">).</span><span class="nf">edit</span><span class="p">()</span>
    <span class="n">editor</span><span class="p">.</span><span class="nf">putString</span><span class="p">(</span><span class="s">"name"</span><span class="p">,</span> <span class="s">"Tom"</span><span class="p">)</span>
    <span class="n">editor</span><span class="p">.</span><span class="nf">putInt</span><span class="p">(</span><span class="s">"age"</span><span class="p">,</span> <span class="mi">28</span><span class="p">)</span>
    <span class="n">editor</span><span class="p">.</span><span class="nf">putBoolean</span><span class="p">(</span><span class="s">"married"</span><span class="p">,</span> <span class="k">false</span><span class="p">)</span>
    <span class="n">editor</span><span class="p">.</span><span class="nf">apply</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/images/Android/img/xml-store.png" alt="xml-store" /></p>

<h3 id="从-sharedpreferences-中读入">从 SharedPreferences 中读入</h3>

<p>接下来就是如何从 <code class="language-plaintext highlighter-rouge">SharedPreferences</code> 对象中获取数据了, 这也非常简单</p>

<p><code class="language-plaintext highlighter-rouge">SharedPreferences</code> 对象中 提供了一系列的 <code class="language-plaintext highlighter-rouge">get</code> 方法，用于读取存储的数据，每种 <code class="language-plaintext highlighter-rouge">get</code> 方法都对应了 <code class="language-plaintext highlighter-rouge">SharedPreferences.Editor</code> 中的一种 <code class="language-plaintext highlighter-rouge">put</code> 方法，比如读取一个布尔型数据就使用 <code class="language-plaintext highlighter-rouge">getBoolean()</code> 方法，读取一个字符串就使用 <code class="language-plaintext highlighter-rouge">getString()</code> 方法。这些 <code class="language-plaintext highlighter-rouge">get</code> 方法都接收两个参 数：第一个参数是键，传入存储数据时使用的键就可以得到相应的值了；第二个参数是默认值，即表示当传入的键找不到对应的值时会以什么样的默认值进行返回</p>

<p>我们用代码示例解释:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">getDataFromSharedPreferences</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">prefs</span> <span class="p">=</span> <span class="nf">getSharedPreferences</span><span class="p">(</span><span class="s">"data"</span><span class="p">,</span> <span class="nc">Context</span><span class="p">.</span><span class="nc">MODE_PRIVATE</span><span class="p">)</span>
    <span class="kd">val</span> <span class="py">name</span> <span class="p">=</span> <span class="n">prefs</span><span class="p">.</span><span class="nf">getString</span><span class="p">(</span><span class="s">"name"</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
    <span class="kd">val</span> <span class="py">age</span> <span class="p">=</span> <span class="n">prefs</span><span class="p">.</span><span class="nf">getInt</span><span class="p">(</span><span class="s">"age"</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="kd">val</span> <span class="py">married</span> <span class="p">=</span> <span class="n">prefs</span><span class="p">.</span><span class="nf">getBoolean</span><span class="p">(</span><span class="s">"married"</span><span class="p">,</span> <span class="k">false</span><span class="p">)</span>
    <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="k">this</span><span class="p">,</span>
                   <span class="s">"name is $name, age is $age, married is $married"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="sqlite-数据库存储">SQLite 数据库存储</h2>

<p><code class="language-plaintext highlighter-rouge">SQLite</code> 是一款轻量级的关系型数据库，它的运算速度非常快，占用资源很少，通常只需要几百KB的内存就足够了，因而特别适合在移动设备上使用。<code class="language-plaintext highlighter-rouge">SQLite</code> 不仅支持标准的SQL语法，还遵循了数据库的ACID事务，所以只要你以前使用过其他的关系型数据 库，就可以很快地上手SQLite。而SQLite又比一般的数据库要简单得多，它甚至不用设置用户 名和密码就可以使用, <strong>主要是它是安卓自带的数据库, 无需配置</strong></p>

<h3 id="创建数据库">创建数据库</h3>

<p>Android为了让我们能够更加方便地管理数据库，专门提供了一个 <code class="language-plaintext highlighter-rouge">SQLiteOpenHelper</code> 帮助类</p>

<p>首先，你要知道 <code class="language-plaintext highlighter-rouge">SQLiteOpenHelper</code> 是一个抽象类，这意味着如果我们想要使用它，就需要创建一个自己的帮助类去继承它。<code class="language-plaintext highlighter-rouge">SQLiteOpenHelper</code> 中有两个抽象方法：<code class="language-plaintext highlighter-rouge">onCreate()</code> 和 <code class="language-plaintext highlighter-rouge">onUpgrade()</code> 。我们必须在自己的帮助类里重写这两个方法，然后分别在这两个方法中实现创建和升级数据库的逻辑。</p>

<p><code class="language-plaintext highlighter-rouge">SQLiteOpenHelper</code> 中有两个非常重要的实例方法：<code class="language-plaintext highlighter-rouge">getReadableDatabase()</code> 和 <code class="language-plaintext highlighter-rouge">getWritableDatabase()</code> 。这两个方法都可以创建或打开一个现有的数据库（如果数据库已存在则直接打开，否则要创建一个新的数据库），并返回一个可对数据库进行读写操作的对象</p>

<p>当数据库不可写入的时候（如磁盘空间已满），<code class="language-plaintext highlighter-rouge">getReadableDatabase()</code> 方法返回的对象将以只读的方式打开数据库，而 <code class="language-plaintext highlighter-rouge">getWritableDatabase()</code> 方法则将出现异常</p>

<p>SQLiteOpenHelper中有两个构造方法可供重写, 接收 $4$ 个参数：</p>

<blockquote>
  <p>第一个参数是 <code class="language-plaintext highlighter-rouge">Context</code>，这个没什么好说的，必须有它才能对数 据库进行操作；</p>

  <p>第二个参数是数据库名，创建数据库时使用的就是这里指定的名称；</p>

  <p>第三个参 数允许我们在查询数据的时候返回一个自定义的 <code class="language-plaintext highlighter-rouge">Cursor</code>，一般传入 <code class="language-plaintext highlighter-rouge">null</code> 即可；</p>

  <p>第四个参数表示 当前数据库的版本号，可用于对数据库进行升级操作</p>
</blockquote>

<p>之后让我们简单演示一下具体是如何使用的:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyDatabaseHelper</span><span class="p">(</span><span class="kd">val</span> <span class="py">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">version</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">:</span>
    <span class="nc">SQLiteOpenHelper</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="n">version</span><span class="p">)</span> <span class="p">{</span>

    <span class="k">private</span> <span class="kd">val</span> <span class="py">createBook</span> <span class="p">=</span> <span class="s">"create table Book ("</span> <span class="p">+</span>
            <span class="s">"id integer primary key autoincrement,"</span> <span class="p">+</span>
            <span class="s">"author text,"</span> <span class="p">+</span>
            <span class="s">"price real,"</span> <span class="p">+</span>
            <span class="s">"pages integer,"</span> <span class="p">+</span>
            <span class="s">"name text)"</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="nc">SQLiteDatabase</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">execSQL</span><span class="p">(</span><span class="n">createBook</span><span class="p">)</span>
        <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">"Create succeeded"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onUpgrade</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="nc">SQLiteDatabase</span><span class="p">,</span> <span class="n">oldVersion</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">newVersion</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这样一张数据库表便创建成功了</p>

<p>接下来我们讲讲 <code class="language-plaintext highlighter-rouge">onUpgrade</code> 方法是如何使用的, 当你想对一张表进行更新的时候, <code class="language-plaintext highlighter-rouge">createBook</code> 方法是不能够运行的, 这是因为监测到表存在后就不会继续往后执行了, 于是就需要 <code class="language-plaintext highlighter-rouge">onUpgrade</code> 方法了</p>

<p>我们可以将整个类修改成下面的样子以演示我们的需求:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyDatabaseHelper</span><span class="p">(</span><span class="kd">val</span> <span class="py">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">version</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">:</span>
    <span class="nc">SQLiteOpenHelper</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="n">version</span><span class="p">)</span> <span class="p">{</span>

    <span class="k">private</span> <span class="kd">val</span> <span class="py">createBook</span> <span class="p">=</span> <span class="s">"create table Book ("</span> <span class="p">+</span>
            <span class="s">"id integer primary key autoincrement,"</span> <span class="p">+</span>
            <span class="s">"author text,"</span> <span class="p">+</span>
            <span class="s">"price real,"</span> <span class="p">+</span>
            <span class="s">"pages integer,"</span> <span class="p">+</span>
            <span class="s">"name text)"</span>
    
    <span class="k">private</span> <span class="kd">val</span> <span class="py">createCategory</span> <span class="p">=</span> <span class="s">"create table Category ("</span> <span class="p">+</span>
            <span class="s">"id integer primary key autoincrement,"</span> <span class="p">+</span>
            <span class="s">"category_name text,"</span> <span class="p">+</span>
            <span class="s">"category_code integer)"</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="nc">SQLiteDatabase</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">execSQL</span><span class="p">(</span><span class="n">createBook</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">execSQL</span><span class="p">(</span><span class="n">createCategory</span><span class="p">)</span>
        <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">"Create succeeded"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onUpgrade</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="nc">SQLiteDatabase</span><span class="p">,</span> <span class="n">oldVersion</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">newVersion</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">execSQL</span><span class="p">(</span><span class="s">"drop table if exists Book"</span><span class="p">)</span>
        <span class="n">db</span><span class="p">.</span><span class="nf">execSQL</span><span class="p">(</span><span class="s">"drop table if exists Category"</span><span class="p">)</span>
        <span class="nf">onCreate</span><span class="p">(</span><span class="n">db</span><span class="p">)</span>
    <span class="p">}</span>

<span class="p">}</span>
</code></pre></div></div>

<p>如上面的代码, 我们想要创建两张表, 但因为 <code class="language-plaintext highlighter-rouge">Book</code> 表已经创建了, 所以下面的 <code class="language-plaintext highlighter-rouge">Category</code> 表无法创建, 所以需要  <code class="language-plaintext highlighter-rouge">onUpgrade</code> 方法删除掉 <code class="language-plaintext highlighter-rouge">Book</code> 表后再创建</p>

<p>至于如何调用  <code class="language-plaintext highlighter-rouge">onUpgrade</code> 方法也很简单: <strong>在创建 <code class="language-plaintext highlighter-rouge">MyDatabaseHelper</code> 类的时候提高版本号即可</strong></p>

<h3 id="插入数据">插入数据</h3>

<p>我们可以使用 insert 方法来插入数据, 主要语法如下:</p>

<p><code class="language-plaintext highlighter-rouge">insert</code> 接收3个参数：</p>

<blockquote>
  <p>第一个参数是表名，我们希望向哪张表 里添加数据，这里就传入该表的名字；</p>

  <p>第二个参数用于在未指定添加数据的情况下给某些可为 空的列自动赋值 <code class="language-plaintext highlighter-rouge">NULL</code>，一般我们用不到这个功能，直接传入 <code class="language-plaintext highlighter-rouge">null</code> 即可；</p>

  <p>第三个参数是一个 <code class="language-plaintext highlighter-rouge">ContentValues</code> 对象，它提供了一系列的 <code class="language-plaintext highlighter-rouge">put()</code> 方法重载，用于向 <code class="language-plaintext highlighter-rouge">ContentValues</code> 中添加数据，只需要将表中的每个列名以及相应的待添加数据传入即可</p>
</blockquote>

<p>示例如下:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span> 
 
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span> 
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span> 
        <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span> 
        <span class="kd">val</span> <span class="py">dbHelper</span> <span class="p">=</span> <span class="nc">MyDatabaseHelper</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"BookStore.db"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> 
        <span class="n">addData</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span> 
            <span class="kd">val</span> <span class="py">db</span> <span class="p">=</span> <span class="n">dbHelper</span><span class="p">.</span><span class="n">writableDatabase</span> 
            <span class="kd">val</span> <span class="py">values1</span> <span class="p">=</span> <span class="nc">ContentValues</span><span class="p">().</span><span class="nf">apply</span> <span class="p">{</span> 
                <span class="nf">put</span><span class="p">(</span><span class="s">"name"</span><span class="p">,</span> <span class="s">"The Da Vinci Code"</span><span class="p">)</span> 
                <span class="nf">put</span><span class="p">(</span><span class="s">"author"</span><span class="p">,</span> <span class="s">"Dan Brown"</span><span class="p">)</span> 
                <span class="nf">put</span><span class="p">(</span><span class="s">"pages"</span><span class="p">,</span> <span class="mi">454</span><span class="p">)</span> 
                <span class="nf">put</span><span class="p">(</span><span class="s">"price"</span><span class="p">,</span> <span class="mf">16.96</span><span class="p">)</span> 
            <span class="p">}</span> 
            <span class="n">db</span><span class="p">.</span><span class="nf">insert</span><span class="p">(</span><span class="s">"Book"</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="n">values1</span><span class="p">)</span> <span class="c1">// 插入第一条数据 </span>
        <span class="p">}</span> 
    <span class="p">}</span> 
<span class="p">}</span> 
</code></pre></div></div>

<h3 id="更新数据">更新数据</h3>

<p>同理, 我们使用 <code class="language-plaintext highlighter-rouge">upgrade</code> 方法:</p>

<p><code class="language-plaintext highlighter-rouge">upgrade</code> 接收4个参数：</p>

<blockquote>
  <p>第一个参数和 <code class="language-plaintext highlighter-rouge">insert()</code> 方法一样，也是表名，指定更新哪张表里的数据；</p>

  <p>第二个参数是 <code class="language-plaintext highlighter-rouge">ContentValues</code> 对象，要把更新数据在这里组装进去；</p>

  <p>第三、第四个参数用于约束更 新某一行或某几行中的数据，不指定的话默认会更新所有行</p>
</blockquote>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span> 
 
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span> 
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span> 
        <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span> 
        <span class="kd">val</span> <span class="py">dbHelper</span> <span class="p">=</span> <span class="nc">MyDatabaseHelper</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"BookStore.db"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> 
        <span class="n">updateData</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span> 
            <span class="kd">val</span> <span class="py">db</span> <span class="p">=</span> <span class="n">dbHelper</span><span class="p">.</span><span class="n">writableDatabase</span> 
            <span class="kd">val</span> <span class="py">values</span> <span class="p">=</span> <span class="nc">ContentValues</span><span class="p">()</span> 
            <span class="n">values</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"price"</span><span class="p">,</span> <span class="mf">10.99</span><span class="p">)</span> 
            <span class="n">db</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="s">"Book"</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="s">"name = ?"</span><span class="p">,</span> <span class="nf">arrayOf</span><span class="p">(</span><span class="s">"The Da Vinci Code"</span><span class="p">))</span> 
        <span class="p">}</span> 
    <span class="p">}</span> 
<span class="p">}</span>
</code></pre></div></div>

<p>第三个参数对应的是SQL语句的where部分，表示更新所有name等于?的行，而?是一 个占位符，可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应 的内容</p>

<h3 id="删除数据">删除数据</h3>

<p><code class="language-plaintext highlighter-rouge">SQLiteDatabase</code> 中提供了一个 <code class="language-plaintext highlighter-rouge">delete()</code> 方法，专门用于删除数据。</p>

<p>这个方法接收3个参数：</p>

<blockquote>
  <p>第一个参数仍然是表名，这个没什么好说的；</p>

  <p>第二、第三个参数用于约束删除某一行或某几行 的数据，不指定的话默认会删除所有行</p>
</blockquote>

<p>如下面的代码所示:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span> 
    
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span> 
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span> 
        <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span> 
        <span class="kd">val</span> <span class="py">dbHelper</span> <span class="p">=</span> <span class="nc">MyDatabaseHelper</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"BookStore.db"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> 
        <span class="n">deleteData</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span> 
            <span class="kd">val</span> <span class="py">db</span> <span class="p">=</span> <span class="n">dbHelper</span><span class="p">.</span><span class="n">writableDatabase</span> 
            <span class="n">db</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="s">"Book"</span><span class="p">,</span> <span class="s">"pages &gt; ?"</span><span class="p">,</span> <span class="nf">arrayOf</span><span class="p">(</span><span class="s">"500"</span><span class="p">))</span> 
        <span class="p">}</span> 
    <span class="p">}</span> 
<span class="p">}</span>
</code></pre></div></div>

<h3 id="查询数据">查询数据</h3>

<p><code class="language-plaintext highlighter-rouge">SQLiteDatabase</code> 中提供了一个 <code class="language-plaintext highlighter-rouge">query()</code> 方法用于对数据进行查询。</p>

<p>这 个方法的参数非常复杂，最短的一个方法重载也需要传入7个参数。</p>

<blockquote>
  <p>第一个参数还是表名，表示我们希望从哪张表中查询数据。</p>

  <p>第二个参数用于指定去查询哪几列，如果不指定则默认查询所有列。</p>

  <p>第三、第四个参数用于约束查询某一行或某几行的数据，不指定则默认查询所有行的数据。</p>

  <p>第五个参数用于指定需要去 <code class="language-plaintext highlighter-rouge">group by</code> 的列，不指定则表示不对查询结果进行group by操作。</p>

  <p>第六个参数用于对 <code class="language-plaintext highlighter-rouge">group by</code> 之后的数据进行进一步的过滤，不指定则表示不进行过滤。</p>

  <p>第七个参数用于指定查询结果的排序方式，不指定则表示使用默认的排序方式。</p>
</blockquote>

<p>与之对应的 SQL 语句如下图所示:</p>

<p><img src="/assets/images/Android/img/sql.png" alt="sql" style="zoom:80%;" /></p>

<p>调用 <code class="language-plaintext highlighter-rouge">query()</code> 方 法后会返回一个 <code class="language-plaintext highlighter-rouge">Cursor</code> 对象，查询到的所有数据都将从这个对象中取出</p>

<p>我们简单演示一下 <code class="language-plaintext highlighter-rouge">SELECT * FROM Book;</code> 的操作:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span> 
 
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span> 
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span> 
        <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span> 
        <span class="kd">val</span> <span class="py">dbHelper</span> <span class="p">=</span> <span class="nc">MyDatabaseHelper</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"BookStore.db"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> 
        <span class="n">queryData</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span> 
            <span class="kd">val</span> <span class="py">db</span> <span class="p">=</span> <span class="n">dbHelper</span><span class="p">.</span><span class="n">writableDatabase</span> 
            <span class="kd">val</span> <span class="py">cursor</span> <span class="p">=</span> <span class="n">db</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="s">"Book"</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="k">null</span><span class="p">)</span> 
            <span class="k">if</span> <span class="p">(</span><span class="n">cursor</span><span class="p">.</span><span class="nf">moveToFirst</span><span class="p">())</span> <span class="p">{</span> 
                <span class="k">do</span> <span class="p">{</span> 
                    <span class="c1">// 遍历Cursor对象，取出数据并打印 </span>
                    <span class="kd">val</span> <span class="py">name</span> <span class="p">=</span> <span class="n">cursor</span><span class="p">.</span><span class="nf">getString</span><span class="p">(</span><span class="n">cursor</span><span class="p">.</span><span class="nf">getColumnIndex</span><span class="p">(</span><span class="s">"name"</span><span class="p">))</span> 
                    <span class="kd">val</span> <span class="py">author</span> <span class="p">=</span> <span class="n">cursor</span><span class="p">.</span><span class="nf">getString</span><span class="p">(</span><span class="n">cursor</span><span class="p">.</span><span class="nf">getColumnIndex</span><span class="p">(</span><span class="s">"author"</span><span class="p">))</span> 
                    <span class="kd">val</span> <span class="py">pages</span> <span class="p">=</span> <span class="n">cursor</span><span class="p">.</span><span class="nf">getInt</span><span class="p">(</span><span class="n">cursor</span><span class="p">.</span><span class="nf">getColumnIndex</span><span class="p">(</span><span class="s">"pages"</span><span class="p">))</span> 
                    <span class="kd">val</span> <span class="py">price</span> <span class="p">=</span> <span class="n">cursor</span><span class="p">.</span><span class="nf">getDouble</span><span class="p">(</span><span class="n">cursor</span><span class="p">.</span><span class="nf">getColumnIndex</span><span class="p">(</span><span class="s">"price"</span><span class="p">))</span> 
                    <span class="nc">Log</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="s">"MainActivity"</span><span class="p">,</span> <span class="s">"book name is $name"</span><span class="p">)</span> 
                    <span class="nc">Log</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="s">"MainActivity"</span><span class="p">,</span> <span class="s">"book author is $author"</span><span class="p">)</span> 
                    <span class="nc">Log</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="s">"MainActivity"</span><span class="p">,</span> <span class="s">"book pages is $pages"</span><span class="p">)</span> 
                    <span class="nc">Log</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="s">"MainActivity"</span><span class="p">,</span> <span class="s">"book price is $price"</span><span class="p">)</span> 
                <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">cursor</span><span class="p">.</span><span class="nf">moveToNext</span><span class="p">())</span> 
            <span class="p">}</span> 
            <span class="n">cursor</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span> 
        <span class="p">}</span> 
    <span class="p">}</span> 
<span class="p">}</span> 
</code></pre></div></div>

<p>最后注意要将 <code class="language-plaintext highlighter-rouge">cursor</code> 关闭, 也就是 <code class="language-plaintext highlighter-rouge">cursor.close()</code></p>

<h3 id="使用sql-操作数据库">使用SQL 操作数据库</h3>

<p>我们也可以直接使用 SQL 语言来执行操作:</p>

<p>主要操作如下:</p>

<ul>
  <li>
    <p>添加数据:</p>

    <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">db</span><span class="p">.</span><span class="nf">execSQL</span><span class="p">(</span><span class="s">"insert into Book (name, author, pages, price) values(?, ?, ?, ?)"</span><span class="p">,</span> 
    <span class="nf">arrayOf</span><span class="p">(</span><span class="s">"The Da Vinci Code"</span><span class="p">,</span> <span class="s">"Dan Brown"</span><span class="p">,</span> <span class="s">"454"</span><span class="p">,</span> <span class="s">"16.96"</span><span class="p">)</span> 
<span class="p">)</span> 
<span class="n">db</span><span class="p">.</span><span class="nf">execSQL</span><span class="p">(</span><span class="s">"insert into Book (name, author, pages, price) values(?, ?, ?, ?)"</span><span class="p">,</span> 
    <span class="nf">arrayOf</span><span class="p">(</span><span class="s">"The Lost Symbol"</span><span class="p">,</span> <span class="s">"Dan Brown"</span><span class="p">,</span> <span class="s">"510"</span><span class="p">,</span> <span class="s">"19.95"</span><span class="p">)</span> 
<span class="p">)</span> 
</code></pre></div>    </div>
  </li>
  <li>
    <p>更新数据</p>

    <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">db</span><span class="p">.</span><span class="nf">execSQL</span><span class="p">(</span><span class="s">"update Book set price = ? where name = ?"</span><span class="p">,</span> <span class="nf">arrayOf</span><span class="p">(</span><span class="s">"10.99"</span><span class="p">,</span> <span class="s">"The Da Vinci Code"</span><span class="p">))</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>删除数据</p>

    <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">db</span><span class="p">.</span><span class="nf">execSQL</span><span class="p">(</span><span class="s">"delete from Book where pages &gt; ?"</span><span class="p">,</span> <span class="nf">arrayOf</span><span class="p">(</span><span class="s">"500"</span><span class="p">))</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>查询数据</p>

    <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">cursor</span> <span class="p">=</span> <span class="n">db</span><span class="p">.</span><span class="nf">rawQuery</span><span class="p">(</span><span class="s">"select * from Book"</span><span class="p">,</span> <span class="k">null</span><span class="p">)</span> 
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="事务操作">事务操作</h3>

<p>我们直接用代码解释如何开启事务:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span> 
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span> 
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span> 
        <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span> 
        <span class="kd">val</span> <span class="py">dbHelper</span> <span class="p">=</span> <span class="nc">MyDatabaseHelper</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"BookStore.db"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> 
        <span class="n">replaceData</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span> 
            <span class="kd">val</span> <span class="py">db</span> <span class="p">=</span> <span class="n">dbHelper</span><span class="p">.</span><span class="n">writableDatabase</span> 
            <span class="n">db</span><span class="p">.</span><span class="nf">beginTransaction</span><span class="p">()</span> <span class="c1">// 开启事务 </span>
            <span class="k">try</span> <span class="p">{</span> 
                <span class="c1">// ..... 执行语句</span>
                <span class="n">db</span><span class="p">.</span><span class="nf">setTransactionSuccessful</span><span class="p">()</span> <span class="c1">// 事务已经执行成功 </span>
            <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="nc">Exception</span><span class="p">)</span> <span class="p">{</span> 
                <span class="n">e</span><span class="p">.</span><span class="nf">printStackTrace</span><span class="p">()</span> 
            <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> 
                <span class="n">db</span><span class="p">.</span><span class="nf">endTransaction</span><span class="p">()</span> <span class="c1">// 结束事务 </span>
            <span class="p">}</span> 
        <span class="p">}</span> 
    <span class="p">}</span> 
<span class="p">}</span>
</code></pre></div></div>

<h2 id="kotlin-进阶课堂">Kotlin 进阶课堂</h2>

<h3 id="高阶函数的应用">高阶函数的应用</h3>]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[前言 文件存储 保存数据到文件中 从文件中读取数据 SharedPreferences 存储 获取 SharedPreferences 对象 从 SharedPreferences 中写入 从 SharedPreferences 中读入 SQLite 数据库存储 创建数据库 插入数据 更新数据 删除数据 查询数据 使用SQL 操作数据库 事务操作 Kotlin 进阶课堂 高阶函数的应用 前言 所谓持久化存储, 就是指将那些内存中的瞬时数据保存到存储设备中，保证即使在手机或计算机关机 的情况下，这些数据仍然不会丢失。 我们将主要介绍三种持久化存储手段, 分别是 : 文件存储 SharedPreferences 存储 数据库存储 文件存储 保存数据到文件中 我们先来展示一下保存的代码 fun save(inputText: String) { try { val output = openFileOutput("data", Context.MODE_PRIVATE) val writer = BufferedWriter(OutputStreamWriter(output)) writer.use { it.write(inputText) } } catch (e: IOException) { e.printStackTrace() } } Context 类中提供了一个 openFileOutput() 方法，可以用于将数据存储到指定的文件中。 这个方法接收两个参数： 第一个参数是文件名，在文件创建的时候使用，注意这里指定的文件名不可以包含路径，因为所有的文件都默认存储到 /data/data//files/ 目录下 第二个参数是文件的操作模式，主要有 MODE_PRIVATE 和 MODE_APPEND 两种模式可选，默认是 MODE_PRIVATE，表示当指定相同文件名的时候，所写入的内容将会覆盖原文件中的内容，而 MODE_APPEND 则表示如果该文件已存在，就往文件里面追加内容，不存在就创建新文件。 其实文件的操作模式本来还有另外两种：MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE。这两种模式表示允许其他应用程序对我们程序中的文件进行读写操作，不过由于这两种模式过于危险，很容易引起应用的安全漏洞，已在Android 4.2版本中被废弃 从文件中读取数据 我们先看看从文件中读取数据的代码: private fun load(): String { val content = StringBuilder() try { val input = openFileInput("data") val reader = BufferedReader(InputStreamReader(input)) reader.use { reader.forEachLine { content.append(it) } } } catch (e: IOException) { e.printStackTrace() } return content.toString() } 在这段代码中，首先通过 openFileInput() 方法获取了一个 FileInputStream 对象，然后借助它又构建出了一个 InputStreamReader 对象，接着再使用 InputStreamReader 构建出 一个 BufferedReader 对象，这样我们就可以通过BufferedReader 将文件中的数据一行行读 取出来，并拼接到 StringBuilder 对象当中，最后将读取的内容返回就可以了。 注意，这里从文件中读取数据使用了一个 forEachLine 函数，这也是 Kotlin 提供的一个内置扩展函数，它会将读到的每行内容都回调到Lambda表达式中，我们在Lambda表达式中完成拼接逻辑即可 SharedPreferences 存储 不同于文件的存储方式，SharedPreferences 是使用键值对的方式来存储数据的。也就是说， 当保存一条数据的时候，需要给这条数据提供一个对应的键，这样在读取数据的时候就可以通 过这个键把相应的值取出来。而且SharedPreferences 还支持多种不同的数据类型存储，如果 存储的数据类型是整型，那么读取出来的数据也是整型的；如果存储的数据是一个字符串，那 么读取出来的数据仍然是字符串。 可以理解为是一个 Map 获取 SharedPreferences 对象 我们主要有以下几种方法来获取 Context 类中的 getSharedPreferences() 方法 此方法接收两个参数： 第一个参数用于指定 SharedPreferences 文件的名称，如果指定的 文件不存在则会创建一个 SharedPreferences 文件都是存放在 /data/data//shared_prefs/ 目录下的 第二个参数用于指定操作模式，目前只有默认的 MODE_PRIVATE 这一种模式可选，它和直接传入 $0$ 的效果是相同的，表示只有当前的应用程序才可以对这个 SharedPreferences 文件进行读写。其他几种操作模式均已被废弃 Activity 类中的 getPreferences() 方法 这个方法和 Context 中的 getSharedPreferences() 方法很相似，不过它只接收一个操作模式参数，因为使用这个方法时会自动将当前 Activity 的类名作为 SharedPreferences 的文件名。 从 SharedPreferences 中写入 接下来就是如何往里面添加数据了, 一般可分为以下几步: 调用 SharedPreferences 对象的 edit() 方法获取一个 SharedPreferences.Editor 对象 向 SharedPreferences.Editor 对象中添加数据，比如添加一个布尔型数据就使用 putBoolean() 方法，添加一个字符串则使用 putString() 方法，以此类推 调用 apply() 方法将添加的数据提交，从而完成数据存储操作 我们用代码演示如下 : fun saveBySharedPreferences() { val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit() editor.putString("name", "Tom") editor.putInt("age", 28) editor.putBoolean("married", false) editor.apply() } 从 SharedPreferences 中读入 接下来就是如何从 SharedPreferences 对象中获取数据了, 这也非常简单 SharedPreferences 对象中 提供了一系列的 get 方法，用于读取存储的数据，每种 get 方法都对应了 SharedPreferences.Editor 中的一种 put 方法，比如读取一个布尔型数据就使用 getBoolean() 方法，读取一个字符串就使用 getString() 方法。这些 get 方法都接收两个参 数：第一个参数是键，传入存储数据时使用的键就可以得到相应的值了；第二个参数是默认值，即表示当传入的键找不到对应的值时会以什么样的默认值进行返回 我们用代码示例解释: fun getDataFromSharedPreferences() { val prefs = getSharedPreferences("data", Context.MODE_PRIVATE) val name = prefs.getString("name", "") val age = prefs.getInt("age", 0) val married = prefs.getBoolean("married", false) Toast.makeText(this, "name is $name, age is $age, married is $married", Toast.LENGTH_SHORT).show() } SQLite 数据库存储 SQLite 是一款轻量级的关系型数据库，它的运算速度非常快，占用资源很少，通常只需要几百KB的内存就足够了，因而特别适合在移动设备上使用。SQLite 不仅支持标准的SQL语法，还遵循了数据库的ACID事务，所以只要你以前使用过其他的关系型数据 库，就可以很快地上手SQLite。而SQLite又比一般的数据库要简单得多，它甚至不用设置用户 名和密码就可以使用, 主要是它是安卓自带的数据库, 无需配置 创建数据库 Android为了让我们能够更加方便地管理数据库，专门提供了一个 SQLiteOpenHelper 帮助类 首先，你要知道 SQLiteOpenHelper 是一个抽象类，这意味着如果我们想要使用它，就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper 中有两个抽象方法：onCreate() 和 onUpgrade() 。我们必须在自己的帮助类里重写这两个方法，然后分别在这两个方法中实现创建和升级数据库的逻辑。 SQLiteOpenHelper 中有两个非常重要的实例方法：getReadableDatabase() 和 getWritableDatabase() 。这两个方法都可以创建或打开一个现有的数据库（如果数据库已存在则直接打开，否则要创建一个新的数据库），并返回一个可对数据库进行读写操作的对象 当数据库不可写入的时候（如磁盘空间已满），getReadableDatabase() 方法返回的对象将以只读的方式打开数据库，而 getWritableDatabase() 方法则将出现异常 SQLiteOpenHelper中有两个构造方法可供重写, 接收 $4$ 个参数： 第一个参数是 Context，这个没什么好说的，必须有它才能对数 据库进行操作； 第二个参数是数据库名，创建数据库时使用的就是这里指定的名称； 第三个参 数允许我们在查询数据的时候返回一个自定义的 Cursor，一般传入 null 即可； 第四个参数表示 当前数据库的版本号，可用于对数据库进行升级操作 之后让我们简单演示一下具体是如何使用的: class MyDatabaseHelper(val context: Context, name: String, version: Int) : SQLiteOpenHelper(context, name, null, version) { private val createBook = "create table Book (" + "id integer primary key autoincrement," + "author text," + "price real," + "pages integer," + "name text)" override fun onCreate(db: SQLiteDatabase) { db.execSQL(createBook) Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show() } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { } } 这样一张数据库表便创建成功了 接下来我们讲讲 onUpgrade 方法是如何使用的, 当你想对一张表进行更新的时候, createBook 方法是不能够运行的, 这是因为监测到表存在后就不会继续往后执行了, 于是就需要 onUpgrade 方法了 我们可以将整个类修改成下面的样子以演示我们的需求: class MyDatabaseHelper(val context: Context, name: String, version: Int) : SQLiteOpenHelper(context, name, null, version) { private val createBook = "create table Book (" + "id integer primary key autoincrement," + "author text," + "price real," + "pages integer," + "name text)" private val createCategory = "create table Category (" + "id integer primary key autoincrement," + "category_name text," + "category_code integer)" override fun onCreate(db: SQLiteDatabase) { db.execSQL(createBook) db.execSQL(createCategory) Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show() } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { db.execSQL("drop table if exists Book") db.execSQL("drop table if exists Category") onCreate(db) } } 如上面的代码, 我们想要创建两张表, 但因为 Book 表已经创建了, 所以下面的 Category 表无法创建, 所以需要 onUpgrade 方法删除掉 Book 表后再创建 至于如何调用 onUpgrade 方法也很简单: 在创建 MyDatabaseHelper 类的时候提高版本号即可 插入数据 我们可以使用 insert 方法来插入数据, 主要语法如下: insert 接收3个参数： 第一个参数是表名，我们希望向哪张表 里添加数据，这里就传入该表的名字； 第二个参数用于在未指定添加数据的情况下给某些可为 空的列自动赋值 NULL，一般我们用不到这个功能，直接传入 null 即可； 第三个参数是一个 ContentValues 对象，它提供了一系列的 put() 方法重载，用于向 ContentValues 中添加数据，只需要将表中的每个列名以及相应的待添加数据传入即可 示例如下: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2) addData.setOnClickListener { val db = dbHelper.writableDatabase val values1 = ContentValues().apply { put("name", "The Da Vinci Code") put("author", "Dan Brown") put("pages", 454) put("price", 16.96) } db.insert("Book", null, values1) // 插入第一条数据 } } } 更新数据 同理, 我们使用 upgrade 方法: upgrade 接收4个参数： 第一个参数和 insert() 方法一样，也是表名，指定更新哪张表里的数据； 第二个参数是 ContentValues 对象，要把更新数据在这里组装进去； 第三、第四个参数用于约束更 新某一行或某几行中的数据，不指定的话默认会更新所有行 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2) updateData.setOnClickListener { val db = dbHelper.writableDatabase val values = ContentValues() values.put("price", 10.99) db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code")) } } } 第三个参数对应的是SQL语句的where部分，表示更新所有name等于?的行，而?是一 个占位符，可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应 的内容 删除数据 SQLiteDatabase 中提供了一个 delete() 方法，专门用于删除数据。 这个方法接收3个参数： 第一个参数仍然是表名，这个没什么好说的； 第二、第三个参数用于约束删除某一行或某几行 的数据，不指定的话默认会删除所有行 如下面的代码所示: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2) deleteData.setOnClickListener { val db = dbHelper.writableDatabase db.delete("Book", "pages &gt; ?", arrayOf("500")) } } } 查询数据 SQLiteDatabase 中提供了一个 query() 方法用于对数据进行查询。 这 个方法的参数非常复杂，最短的一个方法重载也需要传入7个参数。 第一个参数还是表名，表示我们希望从哪张表中查询数据。 第二个参数用于指定去查询哪几列，如果不指定则默认查询所有列。 第三、第四个参数用于约束查询某一行或某几行的数据，不指定则默认查询所有行的数据。 第五个参数用于指定需要去 group by 的列，不指定则表示不对查询结果进行group by操作。 第六个参数用于对 group by 之后的数据进行进一步的过滤，不指定则表示不进行过滤。 第七个参数用于指定查询结果的排序方式，不指定则表示使用默认的排序方式。 与之对应的 SQL 语句如下图所示: 调用 query() 方 法后会返回一个 Cursor 对象，查询到的所有数据都将从这个对象中取出 我们简单演示一下 SELECT * FROM Book; 的操作: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2) queryData.setOnClickListener { val db = dbHelper.writableDatabase val cursor = db.query("Book", null, null, null, null, null, null) if (cursor.moveToFirst()) { do { // 遍历Cursor对象，取出数据并打印 val name = cursor.getString(cursor.getColumnIndex("name")) val author = cursor.getString(cursor.getColumnIndex("author")) val pages = cursor.getInt(cursor.getColumnIndex("pages")) val price = cursor.getDouble(cursor.getColumnIndex("price")) Log.d("MainActivity", "book name is $name") Log.d("MainActivity", "book author is $author") Log.d("MainActivity", "book pages is $pages") Log.d("MainActivity", "book price is $price") } while (cursor.moveToNext()) } cursor.close() } } } 最后注意要将 cursor 关闭, 也就是 cursor.close() 使用SQL 操作数据库 我们也可以直接使用 SQL 语言来执行操作: 主要操作如下: 添加数据: db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96") ) db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", arrayOf("The Lost Symbol", "Dan Brown", "510", "19.95") ) 更新数据 db.execSQL("update Book set price = ? where name = ?", arrayOf("10.99", "The Da Vinci Code")) 删除数据 db.execSQL("delete from Book where pages &gt; ?", arrayOf("500")) 查询数据 val cursor = db.rawQuery("select * from Book", null) 事务操作 我们直接用代码解释如何开启事务: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2) replaceData.setOnClickListener { val db = dbHelper.writableDatabase db.beginTransaction() // 开启事务 try { // ..... 执行语句 db.setTransactionSuccessful() // 事务已经执行成功 } catch (e: Exception) { e.printStackTrace() } finally { db.endTransaction() // 结束事务 } } } } Kotlin 进阶课堂 高阶函数的应用]]></summary></entry><entry><title type="html">Java基础常见面试题总结(上)</title><link href="https://yyym-y.github.io/2024/04/25/java-basic-questions-01/" rel="alternate" type="text/html" title="Java基础常见面试题总结(上)" /><published>2024-04-25T00:00:00+00:00</published><updated>2024-04-25T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/04/25/java-basic-questions-01</id><content type="html" xml:base="https://yyym-y.github.io/2024/04/25/java-basic-questions-01/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#基础概念与常识" id="markdown-toc-基础概念与常识">基础概念与常识</a>    <ul>
      <li><a href="#java-语言有哪些特点" id="markdown-toc-java-语言有哪些特点">Java 语言有哪些特点?</a></li>
      <li><a href="#java-se-vs-java-ee" id="markdown-toc-java-se-vs-java-ee">Java SE vs Java EE</a></li>
      <li><a href="#jvm-vs-jdk-vs-jre" id="markdown-toc-jvm-vs-jdk-vs-jre">JVM vs JDK vs JRE</a>        <ul>
          <li><a href="#jvm" id="markdown-toc-jvm">JVM</a></li>
          <li><a href="#jdk-和-jre" id="markdown-toc-jdk-和-jre">JDK 和 JRE</a></li>
        </ul>
      </li>
      <li><a href="#什么是字节码采用字节码的好处是什么" id="markdown-toc-什么是字节码采用字节码的好处是什么">什么是字节码?采用字节码的好处是什么?</a></li>
      <li><a href="#为什么说-java-语言编译与解释并存" id="markdown-toc-为什么说-java-语言编译与解释并存">为什么说 Java 语言“编译与解释并存”？</a></li>
      <li><a href="#aot-有什么优点为什么不全部使用-aot-呢" id="markdown-toc-aot-有什么优点为什么不全部使用-aot-呢">AOT 有什么优点？为什么不全部使用 AOT 呢？</a></li>
      <li><a href="#oracle-jdk-vs-openjdk" id="markdown-toc-oracle-jdk-vs-openjdk">Oracle JDK vs OpenJDK</a></li>
      <li><a href="#java-和-c-的区别" id="markdown-toc-java-和-c-的区别">Java 和 C++ 的区别?</a></li>
    </ul>
  </li>
  <li><a href="#基本语法" id="markdown-toc-基本语法">基本语法</a>    <ul>
      <li><a href="#注释有哪几种形式" id="markdown-toc-注释有哪几种形式">注释有哪几种形式？</a></li>
      <li><a href="#标识符和关键字的区别是什么" id="markdown-toc-标识符和关键字的区别是什么">标识符和关键字的区别是什么？</a></li>
      <li><a href="#java-语言关键字有哪些" id="markdown-toc-java-语言关键字有哪些">Java 语言关键字有哪些？</a></li>
      <li><a href="#自增自减运算符" id="markdown-toc-自增自减运算符">自增自减运算符</a></li>
      <li><a href="#移位运算符" id="markdown-toc-移位运算符">移位运算符</a></li>
      <li><a href="#continuebreak-和-return-的区别是什么" id="markdown-toc-continuebreak-和-return-的区别是什么">continue、break 和 return 的区别是什么？</a></li>
    </ul>
  </li>
  <li><a href="#基本数据类型" id="markdown-toc-基本数据类型">基本数据类型</a>    <ul>
      <li><a href="#java-中的几种基本数据类型了解么" id="markdown-toc-java-中的几种基本数据类型了解么">Java 中的几种基本数据类型了解么？</a></li>
      <li><a href="#基本类型和包装类型的区别" id="markdown-toc-基本类型和包装类型的区别">基本类型和包装类型的区别？</a></li>
      <li><a href="#包装类型的缓存机制了解么" id="markdown-toc-包装类型的缓存机制了解么">包装类型的缓存机制了解么？</a></li>
      <li><a href="#自动装箱与拆箱了解吗原理是什么" id="markdown-toc-自动装箱与拆箱了解吗原理是什么">自动装箱与拆箱了解吗？原理是什么？</a></li>
      <li><a href="#为什么浮点数运算的时候会有精度丢失的风险" id="markdown-toc-为什么浮点数运算的时候会有精度丢失的风险">为什么浮点数运算的时候会有精度丢失的风险？</a></li>
      <li><a href="#如何解决浮点数运算的精度丢失问题" id="markdown-toc-如何解决浮点数运算的精度丢失问题">如何解决浮点数运算的精度丢失问题？</a></li>
      <li><a href="#超过-long-整型的数据应该如何表示" id="markdown-toc-超过-long-整型的数据应该如何表示">超过 long 整型的数据应该如何表示？</a></li>
    </ul>
  </li>
  <li><a href="#变量" id="markdown-toc-变量">变量</a>    <ul>
      <li><a href="#成员变量与局部变量的区别" id="markdown-toc-成员变量与局部变量的区别">成员变量与局部变量的区别？</a></li>
      <li><a href="#静态变量有什么作用" id="markdown-toc-静态变量有什么作用">静态变量有什么作用？</a></li>
      <li><a href="#字符型常量和字符串常量的区别" id="markdown-toc-字符型常量和字符串常量的区别">字符型常量和字符串常量的区别?</a></li>
    </ul>
  </li>
  <li><a href="#方法" id="markdown-toc-方法">方法</a>    <ul>
      <li><a href="#什么是方法的返回值方法有哪几种类型" id="markdown-toc-什么是方法的返回值方法有哪几种类型">什么是方法的返回值?方法有哪几种类型？</a></li>
      <li><a href="#静态方法为什么不能调用非静态成员" id="markdown-toc-静态方法为什么不能调用非静态成员">静态方法为什么不能调用非静态成员?</a></li>
      <li><a href="#静态方法和实例方法有何不同" id="markdown-toc-静态方法和实例方法有何不同">静态方法和实例方法有何不同？</a></li>
      <li><a href="#重载和重写有什么区别" id="markdown-toc-重载和重写有什么区别">重载和重写有什么区别？</a>        <ul>
          <li><a href="#重载" id="markdown-toc-重载">重载</a></li>
          <li><a href="#重写" id="markdown-toc-重写">重写</a></li>
          <li><a href="#总结" id="markdown-toc-总结">总结</a></li>
        </ul>
      </li>
      <li><a href="#什么是可变长参数" id="markdown-toc-什么是可变长参数">什么是可变长参数？</a></li>
    </ul>
  </li>
  <li><a href="#参考" id="markdown-toc-参考">参考</a></li>
</ul>

<hr />

<p><strong><mark>本系列搬运自 <a href="https://javaguide.cn/">JavaGuide</a>, 本人根据自身理解加上批注转载于此, 欢迎各位去原网站支持作者</mark></strong></p>

<hr />

<h2 id="基础概念与常识">基础概念与常识</h2>

<h3 id="java-语言有哪些特点">Java 语言有哪些特点?</h3>

<ol>
  <li>简单易学（语法简单，上手容易）；</li>
  <li>面向对象（封装，继承，多态）；</li>
  <li>平台无关性（ Java 虚拟机实现平台无关性）；</li>
  <li>支持多线程（ C++ 语言没有内置的多线程机制，因此必须调用操作系统的多线程功能来进行多线程程序设计，而 Java 语言却提供了多线程支持）；</li>
  <li>可靠性（具备异常处理和自动内存管理机制）；</li>
  <li>安全性（Java 语言本身的设计就提供了多重安全防护机制如访问权限修饰符、限制程序直接访问操作系统资源）；</li>
  <li>高效性（通过 Just In Time 编译器等技术的优化，Java 语言的运行效率还是非常不错的）；</li>
  <li>支持网络编程并且很方便；</li>
  <li>编译与解释并存；</li>
  <li>……</li>
</ol>

<blockquote>
  <p><strong>🐛 修正（参见：<a href="https://github.com/Snailclimb/JavaGuide/issues/544">issue#544</a>）</strong>：C++11 开始（2011 年的时候）,C++就引入了多线程库，在 windows、linux、macos 都可以使用<code class="language-plaintext highlighter-rouge">std::thread</code>和<code class="language-plaintext highlighter-rouge">std::async</code>来创建线程。参考链接：<a href="http://www.cplusplus.com/reference/thread/thread/?kw=thread">http://www.cplusplus.com/reference/thread/thread/?kw=thread</a></p>
</blockquote>

<p>🌈 拓展一下：</p>

<p>“Write Once, Run Anywhere（一次编写，随处运行）”这句宣传口号，真心经典，流传了好多年！以至于，直到今天，依然有很多人觉得跨平台是 Java 语言最大的优势。实际上，跨平台已经不是 Java 最大的卖点了，各种 JDK 新特性也不是。目前市面上虚拟化技术已经非常成熟，比如你通过 Docker 就很容易实现跨平台了。在我看来，Java 强大的生态才是！</p>

<h3 id="java-se-vs-java-ee">Java SE vs Java EE</h3>

<ul>
  <li>Java SE（Java Platform，Standard Edition）: Java 平台标准版，Java 编程语言的基础，它包含了支持 Java 应用程序开发和运行的核心类库以及虚拟机等核心组件。Java SE 可以用于构建桌面应用程序或简单的服务器应用程序。</li>
  <li>Java EE（Java Platform, Enterprise Edition ）：Java 平台企业版，建立在 Java SE 的基础上，包含了支持企业级应用程序开发和部署的标准和规范（比如 Servlet、JSP、EJB、JDBC、JPA、JTA、JavaMail、JMS）。 Java EE 可以用于构建分布式、可移植、健壮、可伸缩和安全的服务端 Java 应用程序，例如 Web 应用程序。</li>
</ul>

<p>简单来说，Java SE 是 Java 的基础版本，Java EE 是 Java 的高级版本。Java SE 更适合开发桌面应用程序或简单的服务器应用程序，Java EE 更适合开发复杂的企业级应用程序或 Web 应用程序。</p>

<p>除了 Java SE 和 Java EE，还有一个 Java ME（Java Platform，Micro Edition）。Java ME 是 Java 的微型版本，主要用于开发嵌入式消费电子设备的应用程序，例如手机、PDA、机顶盒、冰箱、空调等。Java ME 无需重点关注，知道有这个东西就好了，现在已经用不上了。</p>

<h3 id="jvm-vs-jdk-vs-jre">JVM vs JDK vs JRE</h3>

<h4 id="jvm">JVM</h4>

<p>Java 虚拟机（JVM）是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现（Windows，Linux，macOS），目的是使用相同的字节码，它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译，随处可以运行”的关键所在。</p>

<p><img src="/assets/images/JavaGuide/java-virtual-machine-program-language-os.png" alt="运行在 Java 虚拟机之上的编程语言" /></p>

<p><strong>JVM 并不是只有一种！只要满足 JVM 规范，每个公司、组织或者个人都可以开发自己的专属 JVM。</strong> 也就是说我们平时接触到的 HotSpot VM 仅仅是是 JVM 规范的一种实现而已。</p>

<p>除了我们平时最常用的 HotSpot VM 外，还有 J9 VM、Zing VM、JRockit VM 等 JVM 。维基百科上就有常见 JVM 的对比：<a href="https://en.wikipedia.org/wiki/Comparison_of_Java_virtual_machines">Comparison of Java virtual machines</a> ，感兴趣的可以去看看。并且，你可以在 <a href="https://docs.oracle.com/javase/specs/index.html">Java SE Specifications</a> 上找到各个版本的 JDK 对应的 JVM 规范。</p>

<p><img src="/assets/images/JavaGuide/JavaSeSpecifications.jpg" alt="" /></p>

<h4 id="jdk-和-jre">JDK 和 JRE</h4>

<p>JDK（Java Development Kit），它是功能齐全的 Java SDK，是提供给开发者使用，能够创建和编译 Java 程序的开发套件。它包含了 JRE，同时还包含了编译 java 源码的编译器 javac 以及一些其他工具比如 javadoc（文档注释工具）、jdb（调试器）、jconsole（基于 JMX 的可视化监控⼯具）、javap（反编译工具）等等。</p>

<p>JRE（Java Runtime Environment） 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合，主要包括 Java 虚拟机（JVM）、Java 基础类库（Class Library）。</p>

<p>也就是说，JRE 是 Java 运行时环境，仅包含 Java 应用程序的运行时环境和必要的类库。而 JDK 则包含了 JRE，同时还包括了 javac、javadoc、jdb、jconsole、javap 等工具，可以用于 Java 应用程序的开发和调试。如果需要进行 Java 编程工作，比如编写和编译 Java 程序、使用 Java API 文档等，就需要安装 JDK。而对于某些需要使用 Java 特性的应用程序，如 JSP 转换为 Java Servlet、使用反射等，也需要 JDK 来编译和运行 Java 代码。因此，即使不打算进行 Java 应用程序的开发工作，也有可能需要安装 JDK。</p>

<p><img src="/assets/images/JavaGuide/jdk-include-jre.png" alt="JDK 包含 JRE" /></p>

<p>不过，从 JDK 9 开始，就不需要区分 JDK 和 JRE 的关系了，取而代之的是模块系统（JDK 被重新组织成 94 个模块）+ <a href="http://openjdk.java.net/jeps/282">jlink</a> 工具 (随 Java 9 一起发布的新命令行工具，用于生成自定义 Java 运行时映像，该映像仅包含给定应用程序所需的模块) 。并且，从 JDK 11 开始，Oracle 不再提供单独的 JRE 下载。</p>

<p>在 <a href="https://javaguide.cn/java/new-features/java9.html">Java 9 新特性概览</a>这篇文章中，我在介绍模块化系统的时候提到：</p>

<blockquote>
  <p>在引入了模块系统之后，JDK 被重新组织成 94 个模块。Java 应用可以通过新增的 jlink 工具，创建出只包含所依赖的 JDK 模块的自定义运行时镜像。这样可以极大的减少 Java 运行时环境的大小。</p>
</blockquote>

<p>也就是说，可以用 jlink 根据自己的需求，创建一个更小的 runtime（运行时），而不是不管什么应用，都是同样的 JRE。</p>

<p>定制的、模块化的 Java 运行时映像有助于简化 Java 应用的部署和节省内存并增强安全性和可维护性。这对于满足现代应用程序架构的需求，如虚拟化、容器化、微服务和云原生开发，是非常重要的。</p>

<h3 id="什么是字节码采用字节码的好处是什么">什么是字节码?采用字节码的好处是什么?</h3>

<p>在 Java 中，JVM 可以理解的代码就叫做字节码（即扩展名为 <code class="language-plaintext highlighter-rouge">.class</code> 的文件），它不面向任何特定的处理器，只面向虚拟机。Java 语言通过字节码的方式，在一定程度上解决了传统解释型语言执行效率低的问题，同时又保留了解释型语言可移植的特点。所以， Java 程序运行时相对来说还是高效的（不过，和 C、 C++，Rust，Go 等语言还是有一定差距的），而且，由于字节码并不针对一种特定的机器，因此，Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。</p>

<p><strong>Java 程序从源代码到运行的过程如下图所示</strong>：</p>

<p><img src="/assets/images/JavaGuide/java-code-to-machine-code.png" alt="Java程序转变为机器代码的过程" /></p>

<p>我们需要格外注意的是 <code class="language-plaintext highlighter-rouge">.class-&gt;机器码</code> 这一步。在这一步 JVM 类加载器首先加载字节码文件，然后通过解释器逐行解释执行，这种方式的执行速度会相对比较慢。而且，有些方法和代码块是经常需要被调用的(也就是所谓的热点代码)，所以后面引进了 <strong>JIT（Just in Time Compilation）</strong> 编译器，而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后，其会将字节码对应的机器码保存下来，下次可以直接使用。而我们知道，机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 <strong>Java 是编译与解释共存的语言</strong> 。</p>

<blockquote>
  <p>🌈 拓展：<a href="https://mp.weixin.qq.com/s/4haTyXUmh8m-dBQaEzwDJw">有关 JIT 的实现细节: JVM C1、C2 编译器</a></p>
</blockquote>

<p><img src="/assets/images/JavaGuide/java-code-to-machine-code-with-jit.png" alt="Java程序转变为机器代码的过程" /></p>

<blockquote>
  <p>HotSpot 采用了惰性评估(Lazy Evaluation)的做法，根据二八定律，消耗大部分系统资源的只有那一小部分的代码（热点代码），而这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化，因此执行的次数越多，它的速度就越快。</p>
</blockquote>

<p>JDK、JRE、JVM、JIT 这四者的关系如下图所示。</p>

<p><img src="/assets/images/JavaGuide/jdk-jre-jvm-jit.png" alt="JDK、JRE、JVM、JIT 这四者的关系" /></p>

<p>下面这张图是 JVM 的大致结构模型。</p>

<p><img src="/assets/images/JavaGuide/jvm-rough-structure-model.png" alt="JVM 的大致结构模型" /></p>

<h3 id="为什么说-java-语言编译与解释并存">为什么说 Java 语言“编译与解释并存”？</h3>

<p>其实这个问题我们讲字节码的时候已经提到过，因为比较重要，所以我们这里再提一下。</p>

<p>我们可以将高级编程语言按照程序的执行方式分为两种：</p>

<ul>
  <li><strong>编译型</strong>：<a href="https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E8%AA%9E%E8%A8%80">编译型语言</a> 会通过<a href="https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E5%99%A8">编译器</a>将源代码一次性翻译成可被该平台执行的机器码。一般情况下，编译语言的执行速度比较快，开发效率比较低。常见的编译性语言有 C、C++、Go、Rust 等等。</li>
  <li><strong>解释型</strong>：<a href="https://zh.wikipedia.org/wiki/%E7%9B%B4%E8%AD%AF%E8%AA%9E%E8%A8%80">解释型语言</a>会通过<a href="https://zh.wikipedia.org/wiki/直譯器">解释器</a>一句一句的将代码解释（interpret）为机器代码后再执行。解释型语言开发效率比较快，执行速度比较慢。常见的解释性语言有 Python、JavaScript、PHP 等等。</li>
</ul>

<p><img src="/assets/images/JavaGuide/compiled-and-interpreted-languages.png" alt="编译型语言和解释型语言" /></p>

<p>根据维基百科介绍：</p>

<blockquote>
  <p>为了改善解释语言的效率而发展出的<a href="https://zh.wikipedia.org/wiki/即時編譯">即时编译</a>技术，已经缩小了这两种语言间的差距。这种技术混合了编译语言与解释型语言的优点，它像编译语言一样，先把程序源代码编译成<a href="https://zh.wikipedia.org/wiki/字节码">字节码</a>。到执行期时，再将字节码直译，之后执行。<a href="https://zh.wikipedia.org/wiki/Java">Java</a>与<a href="https://zh.wikipedia.org/wiki/LLVM">LLVM</a>是这种技术的代表产物。</p>

  <p>相关阅读：<a href="https://tech.meituan.com/2020/10/22/java-jit-practice-in-meituan.html">基本功 | Java 即时编译器原理解析及实践</a></p>
</blockquote>

<p><strong>为什么说 Java 语言“编译与解释并存”？</strong></p>

<p>这是因为 Java 语言既具有编译型语言的特征，也具有解释型语言的特征。因为 Java 程序要经过先编译，后解释两个步骤，由 Java 编写的程序需要先经过编译步骤，生成字节码（<code class="language-plaintext highlighter-rouge">.class</code> 文件），这种字节码必须由 Java 解释器来解释执行。</p>

<h3 id="aot-有什么优点为什么不全部使用-aot-呢">AOT 有什么优点？为什么不全部使用 AOT 呢？</h3>

<p>JDK 9 引入了一种新的编译模式 <strong>AOT(Ahead of Time Compilation)</strong> 。和 JIT 不同的是，这种编译模式会在程序被执行前就将其编译成机器码，属于静态编译（C、 C++，Rust，Go 等语言就是静态编译）。AOT 避免了 JIT 预热等各方面的开销，可以提高 Java 程序的启动速度，避免预热时间长。并且，AOT 还能减少内存占用和增强 Java 程序的安全性（AOT 编译后的代码不容易被反编译和修改），特别适合云原生场景。</p>

<p><strong>JIT 与 AOT 两者的关键指标对比</strong>:</p>

<p><img src="/assets/images/JavaGuide/jit-vs-aot.png" alt="JIT vs AOT" /></p>

<p>可以看出，AOT 的主要优势在于启动时间、内存占用和打包体积。JIT 的主要优势在于具备更高的极限处理能力，可以降低请求的最大延迟。</p>

<p>提到 AOT 就不得不提 <a href="https://www.graalvm.org/">GraalVM</a> 了！GraalVM 是一种高性能的 JDK（完整的 JDK 发行版本），它可以运行 Java 和其他 JVM 语言，以及 JavaScript、Python 等非 JVM 语言。 GraalVM 不仅能提供 AOT 编译，还能提供 JIT 编译。感兴趣的同学，可以去看看 GraalVM 的官方文档：<a href="https://www.graalvm.org/latest/docs/">https://www.graalvm.org/latest/docs/</a>。如果觉得官方文档看着比较难理解的话，也可以找一些文章来看看，比如：</p>

<ul>
  <li><a href="https://mp.weixin.qq.com/s/4haTyXUmh8m-dBQaEzwDJw">基于静态编译构建微服务应用</a></li>
  <li><a href="https://cn.dubbo.apache.org/zh-cn/blog/2023/06/28/%e8%b5%b0%e5%90%91-native-%e5%8c%96springdubbo-aot-%e6%8a%80%e6%9c%af%e7%a4%ba%e4%be%8b%e4%b8%8e%e5%8e%9f%e7%90%86%e8%ae%b2%e8%a7%a3/">走向 Native 化：Spring&amp;Dubbo AOT 技术示例与原理讲解</a></li>
</ul>

<p><strong>既然 AOT 这么多优点，那为什么不全部使用这种编译方式呢？</strong></p>

<p>我们前面也对比过 JIT 与 AOT，两者各有优点，只能说 AOT 更适合当下的云原生场景，对微服务架构的支持也比较友好。除此之外，AOT 编译无法支持 Java 的一些动态特性，如反射、动态代理、动态加载、JNI（Java Native Interface）等。然而，很多框架和库（如 Spring、CGLIB）都用到了这些特性。如果只使用 AOT 编译，那就没办法使用这些框架和库了，或者说需要针对性地去做适配和优化。举个例子，CGLIB 动态代理使用的是 ASM 技术，而这种技术大致原理是运行时直接在内存中生成并加载修改后的字节码文件也就是 <code class="language-plaintext highlighter-rouge">.class</code> 文件，如果全部使用 AOT 提前编译，也就不能使用 ASM 技术了。为了支持类似的动态特性，所以选择使用 JIT 即时编译器。</p>

<h3 id="oracle-jdk-vs-openjdk">Oracle JDK vs OpenJDK</h3>

<p>可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么 Oracle JDK 和 OpenJDK 之间是否存在重大差异？下面我通过收集到的一些资料，为你解答这个被很多人忽视的问题。</p>

<p>首先，2006 年 SUN 公司将 Java 开源，也就有了 OpenJDK。2009 年 Oracle 收购了 Sun 公司，于是自己在 OpenJDK 的基础上搞了一个 Oracle JDK。Oracle JDK 是不开源的，并且刚开始的几个版本（Java8 ~ Java11）还会相比于 OpenJDK 添加一些特有的功能和工具。</p>

<p>其次，对于 Java 7 而言，OpenJDK 和 Oracle JDK 是十分接近的。 Oracle JDK 是基于 OpenJDK 7 构建的，只添加了一些小功能，由 Oracle 工程师参与维护。</p>

<p>下面这段话摘自 Oracle 官方在 2012 年发表的一个博客：</p>

<blockquote>
  <p>问：OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别？</p>

  <p>答：非常接近 - 我们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建，只添加了几个部分，例如部署代码，其中包括 Oracle 的 Java 插件和 Java WebStart 的实现，以及一些闭源的第三方组件，如图形光栅化器，一些开源的第三方组件，如 Rhino，以及一些零碎的东西，如附加文档或第三方字体。展望未来，我们的目的是开源 Oracle JDK 的所有部分，除了我们考虑商业功能的部分。</p>
</blockquote>

<p>最后，简单总结一下 Oracle JDK 和 OpenJDK 的区别：</p>

<ol>
  <li><strong>是否开源</strong>：OpenJDK 是一个参考模型并且是完全开源的，而 Oracle JDK 是基于 OpenJDK 实现的，并不是完全开源的（个人观点：众所周知，JDK 原来是 SUN 公司开发的，后来 SUN 公司又卖给了 Oracle 公司，Oracle 公司以 Oracle 数据库而著名，而 Oracle 数据库又是闭源的，这个时候 Oracle 公司就不想完全开源了，但是原来的 SUN 公司又把 JDK 给开源了，如果这个时候 Oracle 收购回来之后就把他给闭源，必然会引起很多 Java 开发者的不满，导致大家对 Java 失去信心，那 Oracle 公司收购回来不就把 Java 烂在手里了吗！然后，Oracle 公司就想了个骚操作，这样吧，我把一部分核心代码开源出来给你们玩，并且我要和你们自己搞的 JDK 区分下，你们叫 OpenJDK，我叫 Oracle JDK，我发布我的，你们继续玩你们的，要是你们搞出来什么好玩的东西，我后续发布 Oracle JDK 也会拿来用一下，一举两得！）OpenJDK 开源项目：<a href="https://github.com/openjdk/jdk">https://github.com/openjdk/jdk</a> 。</li>
  <li><strong>是否免费</strong>：Oracle JDK 会提供免费版本，但一般有时间限制。JDK17 之后的版本可以免费分发和商用，但是仅有 3 年时间，3 年后无法免费商用。不过，JDK8u221 之前只要不升级可以无限期免费。OpenJDK 是完全免费的。</li>
  <li><strong>功能性</strong>：Oracle JDK 在 OpenJDK 的基础上添加了一些特有的功能和工具，比如 Java Flight Recorder（JFR，一种监控工具）、Java Mission Control（JMC，一种监控工具）等工具。不过，在 Java 11 之后，OracleJDK 和 OpenJDK 的功能基本一致，之前 OracleJDK 中的私有组件大多数也已经被捐赠给开源组织。</li>
  <li><strong>稳定性</strong>：OpenJDK 不提供 LTS 服务，而 OracleJDK 大概每三年都会推出一个 LTS 版进行长期支持。不过，很多公司都基于 OpenJDK 提供了对应的和 OracleJDK 周期相同的 LTS 版。因此，两者稳定性其实也是差不多的。</li>
  <li><strong>协议</strong>：Oracle JDK 使用 BCL/OTN 协议获得许可，而 OpenJDK 根据 GPL v2 许可获得许可。</li>
</ol>

<blockquote>
  <p>既然 Oracle JDK 这么好，那为什么还要有 OpenJDK？</p>

  <p>答：</p>

  <ol>
    <li>OpenJDK 是开源的，开源意味着你可以对它根据你自己的需要进行修改、优化，比如 Alibaba 基于 OpenJDK 开发了 Dragonwell8：<a href="https://github.com/alibaba/dragonwell8">https://github.com/alibaba/dragonwell8</a></li>
    <li>OpenJDK 是商业免费的（这也是为什么通过 yum 包管理器上默认安装的 JDK 是 OpenJDK 而不是 Oracle JDK）。虽然 Oracle JDK 也是商业免费（比如 JDK 8），但并不是所有版本都是免费的。</li>
    <li>OpenJDK 更新频率更快。Oracle JDK 一般是每 6 个月发布一个新版本，而 OpenJDK 一般是每 3 个月发布一个新版本。（现在你知道为啥 Oracle JDK 更稳定了吧，先在 OpenJDK 试试水，把大部分问题都解决掉了才在 Oracle JDK 上发布）</li>
  </ol>

  <p>基于以上这些原因，OpenJDK 还是有存在的必要的！</p>
</blockquote>

<p><img src="/assets/images/JavaGuide/oracle-jdk-release-cadence.jpg" alt="oracle jdk release cadence" /></p>

<p><strong>Oracle JDK 和 OpenJDK 如何选择？</strong></p>

<p>建议选择 OpenJDK 或者基于 OpenJDK 的发行版，比如 AWS 的 Amazon Corretto，阿里巴巴的 Alibaba Dragonwell。</p>

<p>🌈 拓展一下：</p>

<ul>
  <li>BCL 协议（Oracle Binary Code License Agreement）：可以使用 JDK（支持商用），但是不能进行修改。</li>
  <li>OTN 协议（Oracle Technology Network License Agreement）：11 及之后新发布的 JDK 用的都是这个协议，可以自己私下用，但是商用需要付费。</li>
</ul>

<h3 id="java-和-c-的区别">Java 和 C++ 的区别?</h3>

<p>我知道很多人没学过 C++，但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀！没办法！！！就算没学过 C++，也要记下来。</p>

<p>虽然，Java 和 C++ 都是面向对象的语言，都支持封装、继承和多态，但是，它们还是有挺多不相同的地方：</p>

<ul>
  <li>Java 不提供指针来直接访问内存，程序内存更加安全</li>
  <li>Java 的类是单继承的，C++ 支持多重继承；虽然 Java 的类不可以多继承，但是接口可以多继承。</li>
  <li>Java 有自动内存管理垃圾回收机制(GC)，不需要程序员手动释放无用内存。</li>
  <li>C ++同时支持方法重载和操作符重载，但是 Java 只支持方法重载（操作符重载增加了复杂性，这与 Java 最初的设计思想不符）。</li>
  <li>……</li>
</ul>

<h2 id="基本语法">基本语法</h2>

<h3 id="注释有哪几种形式">注释有哪几种形式？</h3>

<p>Java 中的注释有三种：</p>

<p><img src="/assets/images/JavaGuide/java-annotation-types.png" alt="Java 注释类型总结" /></p>

<ol>
  <li>
    <p><strong>单行注释</strong>：通常用于解释方法内某单行代码的作用。</p>
  </li>
  <li>
    <p><strong>多行注释</strong>：通常用于解释一段代码的作用。</p>
  </li>
  <li>
    <p><strong>文档注释</strong>：通常用于生成 Java 开发文档。</p>
  </li>
</ol>

<p>用的比较多的还是单行注释和文档注释，多行注释在实际开发中使用的相对较少。</p>

<p><img src="/assets/images/JavaGuide/image-20220714112336911.png" alt="" /></p>

<p>在我们编写代码的时候，如果代码量比较少，我们自己或者团队其他成员还可以很轻易地看懂代码，但是当项目结构一旦复杂起来，我们就需要用到注释了。注释并不会执行(编译器在编译代码之前会把代码中的所有注释抹掉,字节码中不保留注释)，是我们程序员写给自己看的，注释是你的代码说明书，能够帮助看代码的人快速地理清代码之间的逻辑关系。因此，在写程序的时候随手加上注释是一个非常好的习惯。</p>

<p>《Clean Code》这本书明确指出：</p>

<blockquote>
  <p><strong>代码的注释不是越详细越好。实际上好的代码本身就是注释，我们要尽量规范和美化自己的代码来减少不必要的注释。</strong></p>

  <p><strong>若编程语言足够有表达力，就不需要注释，尽量通过代码来阐述。</strong></p>

  <p>举个例子：</p>

  <p>去掉下面复杂的注释，只需要创建一个与注释所言同一事物的函数即可</p>

  <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// check to see if the employee is eligible for full benefits</span>
<span class="k">if</span> <span class="o">((</span><span class="n">employee</span><span class="o">.</span><span class="na">flags</span> <span class="o">&amp;</span> <span class="no">HOURLY_FLAG</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="n">employee</span><span class="o">.</span><span class="na">age</span> <span class="o">&gt;</span> <span class="mi">65</span><span class="o">))</span>
</code></pre></div>  </div>

  <p>应替换为</p>

  <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">employee</span><span class="o">.</span><span class="na">isEligibleForFullBenefits</span><span class="o">())</span>
</code></pre></div>  </div>
</blockquote>

<h3 id="标识符和关键字的区别是什么">标识符和关键字的区别是什么？</h3>

<p>在我们编写程序的时候，需要大量地为程序、类、变量、方法等取名字，于是就有了 <strong>标识符</strong> 。简单来说， <strong>标识符就是一个名字</strong> 。</p>

<p>有一些标识符，Java 语言已经赋予了其特殊的含义，只能用于特定的地方，这些特殊的标识符就是 <strong>关键字</strong> 。简单来说，<strong>关键字是被赋予特殊含义的标识符</strong> 。比如，在我们的日常生活中，如果我们想要开一家店，则要给这个店起一个名字，起的这个“名字”就叫标识符。但是我们店的名字不能叫“警察局”，因为“警察局”这个名字已经被赋予了特殊的含义，而“警察局”就是我们日常生活中的关键字。</p>

<h3 id="java-语言关键字有哪些">Java 语言关键字有哪些？</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">分类</th>
      <th>关键字</th>
      <th> </th>
      <th> </th>
      <th> </th>
      <th> </th>
      <th> </th>
      <th> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">访问控制</td>
      <td>private</td>
      <td>protected</td>
      <td>public</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">类，方法和变量修饰符</td>
      <td>abstract</td>
      <td>class</td>
      <td>extends</td>
      <td>final</td>
      <td>implements</td>
      <td>interface</td>
      <td>native</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td>new</td>
      <td>static</td>
      <td>strictfp</td>
      <td>synchronized</td>
      <td>transient</td>
      <td>volatile</td>
      <td>enum</td>
    </tr>
    <tr>
      <td style="text-align: left">程序控制</td>
      <td>break</td>
      <td>continue</td>
      <td>return</td>
      <td>do</td>
      <td>while</td>
      <td>if</td>
      <td>else</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td>for</td>
      <td>instanceof</td>
      <td>switch</td>
      <td>case</td>
      <td>default</td>
      <td>assert</td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">错误处理</td>
      <td>try</td>
      <td>catch</td>
      <td>throw</td>
      <td>throws</td>
      <td>finally</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">包相关</td>
      <td>import</td>
      <td>package</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">基本类型</td>
      <td>boolean</td>
      <td>byte</td>
      <td>char</td>
      <td>double</td>
      <td>float</td>
      <td>int</td>
      <td>long</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td>short</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">变量引用</td>
      <td>super</td>
      <td>this</td>
      <td>void</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">保留字</td>
      <td>goto</td>
      <td>const</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>Tips：所有的关键字都是小写的，在 IDE 中会以特殊颜色显示。</p>

  <p><code class="language-plaintext highlighter-rouge">default</code> 这个关键字很特殊，既属于程序控制，也属于类，方法和变量修饰符，还属于访问控制。</p>

  <ul>
    <li>在程序控制中，当在 <code class="language-plaintext highlighter-rouge">switch</code> 中匹配不到任何情况时，可以使用 <code class="language-plaintext highlighter-rouge">default</code> 来编写默认匹配的情况。</li>
    <li>在类，方法和变量修饰符中，从 JDK8 开始引入了默认方法，可以使用 <code class="language-plaintext highlighter-rouge">default</code> 关键字来定义一个方法的默认实现。</li>
    <li>在访问控制中，如果一个方法前没有任何修饰符，则默认会有一个修饰符 <code class="language-plaintext highlighter-rouge">default</code>，但是这个修饰符加上了就会报错。</li>
  </ul>
</blockquote>

<p>⚠️ 注意：虽然 <code class="language-plaintext highlighter-rouge">true</code>, <code class="language-plaintext highlighter-rouge">false</code>, 和 <code class="language-plaintext highlighter-rouge">null</code> 看起来像关键字但实际上他们是字面值，同时你也不可以作为标识符来使用。</p>

<p>官方文档：<a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html">https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html</a></p>

<h3 id="自增自减运算符">自增自减运算符</h3>

<p>在写代码的过程中，常见的一种情况是需要某个整数类型变量增加 1 或减少 1，Java 提供了一种特殊的运算符，用于这种表达式，叫做自增运算符（++)和自减运算符（–）。</p>

<p>++ 和 – 运算符可以放在变量之前，也可以放在变量之后，当运算符放在变量之前时(前缀)，先自增/减，再赋值；当运算符放在变量之后时(后缀)，先赋值，再自增/减。例如，当 <code class="language-plaintext highlighter-rouge">b = ++a</code> 时，先自增（自己增加 1），再赋值（赋值给 b）；当 <code class="language-plaintext highlighter-rouge">b = a++</code> 时，先赋值(赋值给 b)，再自增（自己增加 1）。也就是，++a 输出的是 a+1 的值，a++输出的是 a 值。用一句口诀就是：“符号在前就先加/减，符号在后就后加/减”。</p>

<h3 id="移位运算符">移位运算符</h3>

<p>移位运算符是最基本的运算符之一，几乎每种编程语言都包含这一运算符。移位操作中，被操作的数据被视为二进制数，移位就是将其向左或向右移动若干位的运算。</p>

<p>移位运算符在各种框架以及 JDK 自身的源码中使用还是挺广泛的，<code class="language-plaintext highlighter-rouge">HashMap</code>（JDK1.8） 中的 <code class="language-plaintext highlighter-rouge">hash</code> 方法的源码就用到了移位运算符：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="nf">hash</span><span class="o">(</span><span class="nc">Object</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">int</span> <span class="n">h</span><span class="o">;</span>
    <span class="c1">// key.hashCode()：返回散列值也就是hashcode</span>
    <span class="c1">// ^：按位异或</span>
    <span class="c1">// &gt;&gt;&gt;:无符号右移，忽略符号位，空位都以0补齐</span>
    <span class="k">return</span> <span class="o">(</span><span class="n">key</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="o">(</span><span class="n">h</span> <span class="o">=</span> <span class="n">key</span><span class="o">.</span><span class="na">hashCode</span><span class="o">())</span> <span class="o">^</span> <span class="o">(</span><span class="n">h</span> <span class="o">&gt;&gt;&gt;</span> <span class="mi">16</span><span class="o">);</span>
  <span class="o">}</span>

</code></pre></div></div>

<p>在 Java 代码里使用 <code class="language-plaintext highlighter-rouge">&lt;&lt;</code>、 <code class="language-plaintext highlighter-rouge">&gt;&gt;</code> 和<code class="language-plaintext highlighter-rouge">&gt;&gt;&gt;</code>转换成的指令码运行起来会更高效些。</p>

<p>掌握最基本的移位运算符知识还是很有必要的，这不光可以帮助我们在代码中使用，还可以帮助我们理解源码中涉及到移位运算符的代码。</p>

<p>Java 中有三种移位运算符：</p>

<p><img src="/assets/images/JavaGuide/shift-operator.png" alt="Java 移位运算符总结" /></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">&lt;&lt;</code> :左移运算符，向左移若干位，高位丢弃，低位补零。<code class="language-plaintext highlighter-rouge">x &lt;&lt; 1</code>,相当于 x 乘以 2(不溢出的情况下)。</li>
  <li><code class="language-plaintext highlighter-rouge">&gt;&gt;</code> :带符号右移，向右移若干位，高位补符号位，低位丢弃。正数高位补 0,负数高位补 1。<code class="language-plaintext highlighter-rouge">x &gt;&gt; 1</code>,相当于 x 除以 2。</li>
  <li><code class="language-plaintext highlighter-rouge">&gt;&gt;&gt;</code> :无符号右移，忽略符号位，空位都以 0 补齐。</li>
</ul>

<p>由于 <code class="language-plaintext highlighter-rouge">double</code>，<code class="language-plaintext highlighter-rouge">float</code> 在二进制中的表现比较特殊，因此不能来进行移位操作。</p>

<p>移位操作符实际上支持的类型只有<code class="language-plaintext highlighter-rouge">int</code>和<code class="language-plaintext highlighter-rouge">long</code>，编译器在对<code class="language-plaintext highlighter-rouge">short</code>、<code class="language-plaintext highlighter-rouge">byte</code>、<code class="language-plaintext highlighter-rouge">char</code>类型进行移位前，都会将其转换为<code class="language-plaintext highlighter-rouge">int</code>类型再操作。</p>

<p><strong>如果移位的位数超过数值所占有的位数会怎样？</strong></p>

<p>当 int 类型左移/右移位数大于等于 32 位操作时，会先求余（%）后再进行左移/右移操作。也就是说左移/右移 32 位相当于不进行移位操作（32%32=0），左移/右移 42 位相当于左移/右移 10 位（42%32=10）。当 long 类型进行左移/右移操作时，由于 long 对应的二进制是 64 位，因此求余操作的基数也变成了 64。</p>

<p>也就是说：<code class="language-plaintext highlighter-rouge">x&lt;&lt;42</code>等同于<code class="language-plaintext highlighter-rouge">x&lt;&lt;10</code>，<code class="language-plaintext highlighter-rouge">x&gt;&gt;42</code>等同于<code class="language-plaintext highlighter-rouge">x&gt;&gt;10</code>，<code class="language-plaintext highlighter-rouge">x &gt;&gt;&gt;42</code>等同于<code class="language-plaintext highlighter-rouge">x &gt;&gt;&gt; 10</code>。</p>

<p><strong>左移运算符代码示例</strong>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"初始数据："</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"初始数据对应的二进制字符串："</span> <span class="o">+</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">toBinaryString</span><span class="o">(</span><span class="n">i</span><span class="o">));</span>
<span class="n">i</span> <span class="o">&lt;&lt;=</span> <span class="mi">10</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"左移 10 位后的数据 "</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"左移 10 位后的数据对应的二进制字符 "</span> <span class="o">+</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">toBinaryString</span><span class="o">(</span><span class="n">i</span><span class="o">));</span>
</code></pre></div></div>

<p>输出：</p>

<pre><code class="language-plain">初始数据：-1
初始数据对应的二进制字符串：11111111111111111111111111111111
左移 10 位后的数据 -1024
左移 10 位后的数据对应的二进制字符 11111111111111111111110000000000
</code></pre>

<p>由于左移位数大于等于 32 位操作时，会先求余（%）后再进行左移操作，所以下面的代码左移 42 位相当于左移 10 位（42%32=10），输出结果和前面的代码一样。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"初始数据："</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"初始数据对应的二进制字符串："</span> <span class="o">+</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">toBinaryString</span><span class="o">(</span><span class="n">i</span><span class="o">));</span>
<span class="n">i</span> <span class="o">&lt;&lt;=</span> <span class="mi">42</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"左移 10 位后的数据 "</span> <span class="o">+</span> <span class="n">i</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"左移 10 位后的数据对应的二进制字符 "</span> <span class="o">+</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">toBinaryString</span><span class="o">(</span><span class="n">i</span><span class="o">));</span>
</code></pre></div></div>

<p>右移运算符使用类似，篇幅问题，这里就不做演示了。</p>

<h3 id="continuebreak-和-return-的区别是什么">continue、break 和 return 的区别是什么？</h3>

<p>在循环结构中，当循环条件不满足或者循环次数达到要求时，循环会正常结束。但是，有时候可能需要在循环的过程中，当发生了某种条件之后 ，提前终止循环，这就需要用到下面几个关键词：</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">continue</code>：指跳出当前的这一次循环，继续下一次循环。</li>
  <li><code class="language-plaintext highlighter-rouge">break</code>：指跳出整个循环体，继续执行循环下面的语句。</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">return</code> 用于跳出所在方法，结束该方法的运行。return 一般有两种用法：</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">return;</code>：直接使用 return 结束方法执行，用于没有返回值函数的方法</li>
  <li><code class="language-plaintext highlighter-rouge">return value;</code>：return 一个特定值，用于有返回值函数的方法</li>
</ol>

<p>思考一下：下列语句的运行结果是什么？</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="kt">boolean</span> <span class="n">flag</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
        <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="mi">3</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"0"</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"1"</span><span class="o">);</span>
                <span class="k">continue</span><span class="o">;</span>
            <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"2"</span><span class="o">);</span>
                <span class="n">flag</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
            <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">3</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"3"</span><span class="o">);</span>
                <span class="k">break</span><span class="o">;</span>
            <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">4</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"4"</span><span class="o">);</span>
            <span class="o">}</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"xixi"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">flag</span><span class="o">)</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"haha"</span><span class="o">);</span>
            <span class="k">return</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"heihei"</span><span class="o">);</span>
    <span class="o">}</span>
</code></pre></div></div>

<p>运行结果：</p>

<pre><code class="language-plain">0
xixi
1
2
xixi
3
haha
</code></pre>

<h2 id="基本数据类型">基本数据类型</h2>

<h3 id="java-中的几种基本数据类型了解么">Java 中的几种基本数据类型了解么？</h3>

<p>Java 中有 8 种基本数据类型，分别为：</p>

<ul>
  <li>6 种数字类型：
    <ul>
      <li>4 种整数型：<code class="language-plaintext highlighter-rouge">byte</code>、<code class="language-plaintext highlighter-rouge">short</code>、<code class="language-plaintext highlighter-rouge">int</code>、<code class="language-plaintext highlighter-rouge">long</code></li>
      <li>2 种浮点型：<code class="language-plaintext highlighter-rouge">float</code>、<code class="language-plaintext highlighter-rouge">double</code></li>
    </ul>
  </li>
  <li>1 种字符类型：<code class="language-plaintext highlighter-rouge">char</code></li>
  <li>1 种布尔型：<code class="language-plaintext highlighter-rouge">boolean</code>。</li>
</ul>

<p>这 8 种基本数据类型的默认值以及所占空间的大小如下：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">基本类型</th>
      <th style="text-align: left">位数</th>
      <th style="text-align: left">字节</th>
      <th style="text-align: left">默认值</th>
      <th>取值范围</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">byte</code></td>
      <td style="text-align: left">8</td>
      <td style="text-align: left">1</td>
      <td style="text-align: left">0</td>
      <td>-128 ~ 127</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">short</code></td>
      <td style="text-align: left">16</td>
      <td style="text-align: left">2</td>
      <td style="text-align: left">0</td>
      <td>-32768（-2^15） ~ 32767（2^15 - 1）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">int</code></td>
      <td style="text-align: left">32</td>
      <td style="text-align: left">4</td>
      <td style="text-align: left">0</td>
      <td>-2147483648 ~ 2147483647</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">long</code></td>
      <td style="text-align: left">64</td>
      <td style="text-align: left">8</td>
      <td style="text-align: left">0L</td>
      <td>-9223372036854775808（-2^63） ~ 9223372036854775807（2^63 -1）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">char</code></td>
      <td style="text-align: left">16</td>
      <td style="text-align: left">2</td>
      <td style="text-align: left">‘u0000’</td>
      <td>0 ~ 65535（2^16 - 1）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">float</code></td>
      <td style="text-align: left">32</td>
      <td style="text-align: left">4</td>
      <td style="text-align: left">0f</td>
      <td>1.4E-45 ~ 3.4028235E38</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">double</code></td>
      <td style="text-align: left">64</td>
      <td style="text-align: left">8</td>
      <td style="text-align: left">0d</td>
      <td>4.9E-324 ~ 1.7976931348623157E308</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">boolean</code></td>
      <td style="text-align: left">1</td>
      <td style="text-align: left"> </td>
      <td style="text-align: left">false</td>
      <td>true、false</td>
    </tr>
  </tbody>
</table>

<p>可以看到，像 <code class="language-plaintext highlighter-rouge">byte</code>、<code class="language-plaintext highlighter-rouge">short</code>、<code class="language-plaintext highlighter-rouge">int</code>、<code class="language-plaintext highlighter-rouge">long</code>能表示的最大正数都减 1 了。这是为什么呢？这是因为在二进制补码表示法中，最高位是用来表示符号的（0 表示正数，1 表示负数），其余位表示数值部分。所以，如果我们要表示最大的正数，我们需要把除了最高位之外的所有位都设为 1。如果我们再加 1，就会导致溢出，变成一个负数。</p>

<p>对于 <code class="language-plaintext highlighter-rouge">boolean</code>，官方文档未明确定义，它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位，但是实际中会考虑计算机高效存储因素。</p>

<p>另外，Java 的每种基本类型所占存储空间的大小不会像其他大多数语言那样随机器硬件架构的变化而变化。这种所占存储空间大小的不变性是 Java 程序比用其他大多数语言编写的程序更具可移植性的原因之一（《Java 编程思想》2.2 节有提到）。</p>

<p><strong>注意：</strong></p>

<ol>
  <li>Java 里使用 <code class="language-plaintext highlighter-rouge">long</code> 类型的数据一定要在数值后面加上 <strong>L</strong>，否则将作为整型解析。</li>
  <li><code class="language-plaintext highlighter-rouge">char a = 'h'</code>char :单引号，<code class="language-plaintext highlighter-rouge">String a = "hello"</code> :双引号。</li>
</ol>

<p>这八种基本类型都有对应的包装类分别为：<code class="language-plaintext highlighter-rouge">Byte</code>、<code class="language-plaintext highlighter-rouge">Short</code>、<code class="language-plaintext highlighter-rouge">Integer</code>、<code class="language-plaintext highlighter-rouge">Long</code>、<code class="language-plaintext highlighter-rouge">Float</code>、<code class="language-plaintext highlighter-rouge">Double</code>、<code class="language-plaintext highlighter-rouge">Character</code>、<code class="language-plaintext highlighter-rouge">Boolean</code> 。</p>

<h3 id="基本类型和包装类型的区别">基本类型和包装类型的区别？</h3>

<p><img src="/assets/images/JavaGuide/primitive-type-vs-packaging-type.png" alt="基本类型 vs 包装类型" /></p>

<ul>
  <li><strong>用途</strong>：除了定义一些常量和局部变量之外，我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量。并且，包装类型可用于泛型，而基本类型不可以。</li>
  <li><strong>存储方式</strong>：基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中，基本数据类型的成员变量（未被 <code class="language-plaintext highlighter-rouge">static</code> 修饰 ）存放在 Java 虚拟机的堆中。包装类型属于对象类型，我们知道几乎所有对象实例都存在于堆中。</li>
  <li><strong>占用空间</strong>：相比于包装类型（对象类型）， 基本数据类型占用的空间往往非常小。</li>
  <li><strong>默认值</strong>：成员变量包装类型不赋值就是 <code class="language-plaintext highlighter-rouge">null</code> ，而基本类型有默认值且不是 <code class="language-plaintext highlighter-rouge">null</code>。</li>
  <li><strong>比较方式</strong>：对于基本数据类型来说，<code class="language-plaintext highlighter-rouge">==</code> 比较的是值。对于包装数据类型来说，<code class="language-plaintext highlighter-rouge">==</code> 比较的是对象的内存地址。所有整型包装类对象之间值的比较，全部使用 <code class="language-plaintext highlighter-rouge">equals()</code> 方法。</li>
</ul>

<p><strong>为什么说是几乎所有对象实例都存在于堆中呢？</strong> 这是因为 HotSpot 虚拟机引入了 JIT 优化之后，会对对象进行逃逸分析，如果发现某一个对象并没有逃逸到方法外部，那么就可能通过标量替换来实现栈上分配，而避免堆上分配内存</p>

<p>⚠️ 注意：<strong>基本数据类型存放在栈中是一个常见的误区！</strong> 基本数据类型的存储位置取决于它们的作用域和声明方式。如果它们是局部变量，那么它们会存放在栈中；如果它们是成员变量，那么它们会存放在堆中。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Test</span> <span class="o">{</span>
    <span class="c1">// 成员变量，存放在堆中</span>
    <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">10</span><span class="o">;</span>
    <span class="c1">// 被 static 修饰，也存放在堆中，但属于类，不属于对象</span>
    <span class="c1">// JDK1.7 静态变量从永久代移动了 Java 堆中</span>
    <span class="kd">static</span> <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">20</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">method</span><span class="o">()</span> <span class="o">{</span>
        <span class="c1">// 局部变量，存放在栈中</span>
        <span class="kt">int</span> <span class="n">c</span> <span class="o">=</span> <span class="mi">30</span><span class="o">;</span>
        <span class="kd">static</span> <span class="kt">int</span> <span class="n">d</span> <span class="o">=</span> <span class="mi">40</span><span class="o">;</span> <span class="c1">// 编译错误，不能在方法中使用 static 修饰局部变量</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="包装类型的缓存机制了解么">包装类型的缓存机制了解么？</h3>

<p>Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。</p>

<p><code class="language-plaintext highlighter-rouge">Byte</code>,<code class="language-plaintext highlighter-rouge">Short</code>,<code class="language-plaintext highlighter-rouge">Integer</code>,<code class="language-plaintext highlighter-rouge">Long</code> 这 4 种包装类默认创建了数值 <strong>[-128，127]</strong> 的相应类型的缓存数据，<code class="language-plaintext highlighter-rouge">Character</code> 创建了数值在 <strong>[0,127]</strong> 范围的缓存数据，<code class="language-plaintext highlighter-rouge">Boolean</code> 直接返回 <code class="language-plaintext highlighter-rouge">True</code> or <code class="language-plaintext highlighter-rouge">False</code>。</p>

<p><strong>Integer 缓存源码：</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">Integer</span> <span class="nf">valueOf</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">i</span> <span class="o">&gt;=</span> <span class="nc">IntegerCache</span><span class="o">.</span><span class="na">low</span> <span class="o">&amp;&amp;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="nc">IntegerCache</span><span class="o">.</span><span class="na">high</span><span class="o">)</span>
        <span class="k">return</span> <span class="nc">IntegerCache</span><span class="o">.</span><span class="na">cache</span><span class="o">[</span><span class="n">i</span> <span class="o">+</span> <span class="o">(-</span><span class="nc">IntegerCache</span><span class="o">.</span><span class="na">low</span><span class="o">)];</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">Integer</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">IntegerCache</span> <span class="o">{</span>
    <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">low</span> <span class="o">=</span> <span class="o">-</span><span class="mi">128</span><span class="o">;</span>
    <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">high</span><span class="o">;</span>
    <span class="kd">static</span> <span class="o">{</span>
        <span class="c1">// high value may be configured by property</span>
        <span class="kt">int</span> <span class="n">h</span> <span class="o">=</span> <span class="mi">127</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">Character</code> 缓存源码:</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">Character</span> <span class="nf">valueOf</span><span class="o">(</span><span class="kt">char</span> <span class="n">c</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">c</span> <span class="o">&lt;=</span> <span class="mi">127</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// must cache</span>
      <span class="k">return</span> <span class="nc">CharacterCache</span><span class="o">.</span><span class="na">cache</span><span class="o">[(</span><span class="kt">int</span><span class="o">)</span><span class="n">c</span><span class="o">];</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">Character</span><span class="o">(</span><span class="n">c</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">CharacterCache</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="nf">CharacterCache</span><span class="o">(){}</span>
    <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Character</span> <span class="n">cache</span><span class="o">[]</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Character</span><span class="o">[</span><span class="mi">127</span> <span class="o">+</span> <span class="mi">1</span><span class="o">];</span>
    <span class="kd">static</span> <span class="o">{</span>
        <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">cache</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span>
            <span class="n">cache</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Character</span><span class="o">((</span><span class="kt">char</span><span class="o">)</span><span class="n">i</span><span class="o">);</span>
    <span class="o">}</span>

<span class="o">}</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">Boolean</code> 缓存源码：</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">Boolean</span> <span class="nf">valueOf</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="o">(</span><span class="n">b</span> <span class="o">?</span> <span class="no">TRUE</span> <span class="o">:</span> <span class="no">FALSE</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>如果超出对应范围仍然会去创建新的对象，缓存的范围区间的大小只是在性能和资源之间的权衡。</p>

<p>两种浮点数类型的包装类 <code class="language-plaintext highlighter-rouge">Float</code>,<code class="language-plaintext highlighter-rouge">Double</code> 并没有实现缓存机制。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Integer</span> <span class="n">i1</span> <span class="o">=</span> <span class="mi">33</span><span class="o">;</span>
<span class="nc">Integer</span> <span class="n">i2</span> <span class="o">=</span> <span class="mi">33</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">i1</span> <span class="o">==</span> <span class="n">i2</span><span class="o">);</span><span class="c1">// 输出 true</span>

<span class="nc">Float</span> <span class="n">i11</span> <span class="o">=</span> <span class="mi">333</span><span class="n">f</span><span class="o">;</span>
<span class="nc">Float</span> <span class="n">i22</span> <span class="o">=</span> <span class="mi">333</span><span class="n">f</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">i11</span> <span class="o">==</span> <span class="n">i22</span><span class="o">);</span><span class="c1">// 输出 false</span>

<span class="nc">Double</span> <span class="n">i3</span> <span class="o">=</span> <span class="mf">1.2</span><span class="o">;</span>
<span class="nc">Double</span> <span class="n">i4</span> <span class="o">=</span> <span class="mf">1.2</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">i3</span> <span class="o">==</span> <span class="n">i4</span><span class="o">);</span><span class="c1">// 输出 false</span>
</code></pre></div></div>

<p>下面我们来看一个问题：下面的代码的输出结果是 <code class="language-plaintext highlighter-rouge">true</code> 还是 <code class="language-plaintext highlighter-rouge">false</code> 呢？</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Integer</span> <span class="n">i1</span> <span class="o">=</span> <span class="mi">40</span><span class="o">;</span>
<span class="nc">Integer</span> <span class="n">i2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Integer</span><span class="o">(</span><span class="mi">40</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">i1</span><span class="o">==</span><span class="n">i2</span><span class="o">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Integer i1=40</code> 这一行代码会发生装箱，也就是说这行代码等价于 <code class="language-plaintext highlighter-rouge">Integer i1=Integer.valueOf(40)</code> 。因此，<code class="language-plaintext highlighter-rouge">i1</code> 直接使用的是缓存中的对象。而<code class="language-plaintext highlighter-rouge">Integer i2 = new Integer(40)</code> 会直接创建新的对象。</p>

<p>因此，答案是 <code class="language-plaintext highlighter-rouge">false</code> 。你答对了吗？</p>

<p>记住：<strong>所有整型包装类对象之间值的比较，全部使用 equals 方法比较</strong>。</p>

<p><img src="/assets/images/JavaGuide/up-1ae0425ce8646adfb768b5374951eeb820d.png" alt="" /></p>

<h3 id="自动装箱与拆箱了解吗原理是什么">自动装箱与拆箱了解吗？原理是什么？</h3>

<p><strong>什么是自动拆装箱？</strong></p>

<ul>
  <li><strong>装箱</strong>：将基本类型用它们对应的引用类型包装起来；</li>
  <li><strong>拆箱</strong>：将包装类型转换为基本数据类型；</li>
</ul>

<p>举例：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Integer</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">10</span><span class="o">;</span>  <span class="c1">//装箱</span>
<span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">i</span><span class="o">;</span>   <span class="c1">//拆箱</span>
</code></pre></div></div>

<p>上面这两行代码对应的字节码为：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   <span class="no">L1</span>

    <span class="no">LINENUMBER</span> <span class="mi">8</span> <span class="no">L1</span>

    <span class="no">ALOAD</span> <span class="mi">0</span>

    <span class="no">BIPUSH</span> <span class="mi">10</span>

    <span class="no">INVOKESTATIC</span> <span class="n">java</span><span class="o">/</span><span class="n">lang</span><span class="o">/</span><span class="nc">Integer</span><span class="o">.</span><span class="na">valueOf</span> <span class="o">(</span><span class="no">I</span><span class="o">)</span><span class="nc">Ljava</span><span class="o">/</span><span class="n">lang</span><span class="o">/</span><span class="nc">Integer</span><span class="o">;</span>

    <span class="no">PUTFIELD</span> <span class="nc">AutoBoxTest</span><span class="o">.</span><span class="na">i</span> <span class="o">:</span> <span class="nc">Ljava</span><span class="o">/</span><span class="n">lang</span><span class="o">/</span><span class="nc">Integer</span><span class="o">;</span>

   <span class="no">L2</span>

    <span class="no">LINENUMBER</span> <span class="mi">9</span> <span class="no">L2</span>

    <span class="no">ALOAD</span> <span class="mi">0</span>

    <span class="no">ALOAD</span> <span class="mi">0</span>

    <span class="no">GETFIELD</span> <span class="nc">AutoBoxTest</span><span class="o">.</span><span class="na">i</span> <span class="o">:</span> <span class="nc">Ljava</span><span class="o">/</span><span class="n">lang</span><span class="o">/</span><span class="nc">Integer</span><span class="o">;</span>

    <span class="no">INVOKEVIRTUAL</span> <span class="n">java</span><span class="o">/</span><span class="n">lang</span><span class="o">/</span><span class="nc">Integer</span><span class="o">.</span><span class="na">intValue</span> <span class="o">()</span><span class="no">I</span>

    <span class="no">PUTFIELD</span> <span class="nc">AutoBoxTest</span><span class="o">.</span><span class="na">n</span> <span class="o">:</span> <span class="no">I</span>

    <span class="no">RETURN</span>
</code></pre></div></div>

<p>从字节码中，我们发现装箱其实就是调用了 包装类的<code class="language-plaintext highlighter-rouge">valueOf()</code>方法，拆箱其实就是调用了 <code class="language-plaintext highlighter-rouge">xxxValue()</code>方法。</p>

<p>因此，</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Integer i = 10</code> 等价于 <code class="language-plaintext highlighter-rouge">Integer i = Integer.valueOf(10)</code></li>
  <li><code class="language-plaintext highlighter-rouge">int n = i</code> 等价于 <code class="language-plaintext highlighter-rouge">int n = i.intValue()</code>;</li>
</ul>

<p>注意：<strong>如果频繁拆装箱的话，也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kt">long</span> <span class="nf">sum</span><span class="o">()</span> <span class="o">{</span>
    <span class="c1">// 应该使用 long 而不是 Long</span>
    <span class="nc">Long</span> <span class="n">sum</span> <span class="o">=</span> <span class="mi">0L</span><span class="o">;</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">long</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">MAX_VALUE</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span>
        <span class="n">sum</span> <span class="o">+=</span> <span class="n">i</span><span class="o">;</span>
    <span class="k">return</span> <span class="n">sum</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="为什么浮点数运算的时候会有精度丢失的风险">为什么浮点数运算的时候会有精度丢失的风险？</h3>

<p>浮点数运算精度丢失代码演示：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">float</span> <span class="n">a</span> <span class="o">=</span> <span class="mf">2.0f</span> <span class="o">-</span> <span class="mf">1.9f</span><span class="o">;</span>
<span class="kt">float</span> <span class="n">b</span> <span class="o">=</span> <span class="mf">1.8f</span> <span class="o">-</span> <span class="mf">1.7f</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span><span class="c1">// 0.100000024</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">b</span><span class="o">);</span><span class="c1">// 0.099999905</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span> <span class="o">==</span> <span class="n">b</span><span class="o">);</span><span class="c1">// false</span>
</code></pre></div></div>

<p>为什么会出现这个问题呢？</p>

<p>这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的，而且计算机在表示一个数字时，宽度是有限的，无限循环的小数存储在计算机时，只能被截断，所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。</p>

<p>就比如说十进制下的 0.2 就没办法精确转换成二进制小数：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 0.2 转换为二进制数的过程为，不断乘以 2，直到不存在小数为止，</span>
<span class="c1">// 在这个计算过程中，得到的整数部分从上到下排列就是二进制的结果。</span>
<span class="mf">0.2</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">=</span> <span class="mf">0.4</span> <span class="o">-&gt;</span> <span class="mi">0</span>
<span class="mf">0.4</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">=</span> <span class="mf">0.8</span> <span class="o">-&gt;</span> <span class="mi">0</span>
<span class="mf">0.8</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">=</span> <span class="mf">1.6</span> <span class="o">-&gt;</span> <span class="mi">1</span>
<span class="mf">0.6</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">=</span> <span class="mf">1.2</span> <span class="o">-&gt;</span> <span class="mi">1</span>
<span class="mf">0.2</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">=</span> <span class="mf">0.4</span> <span class="o">-&gt;</span> <span class="mi">0</span><span class="err">（</span><span class="n">发生循环</span><span class="err">）</span>
<span class="o">...</span>
</code></pre></div></div>

<p>关于浮点数的更多内容，建议看一下<a href="http://kaito-kidd.com/2018/08/08/computer-system-float-point/">计算机系统基础（四）浮点数</a>这篇文章。</p>

<h3 id="如何解决浮点数运算的精度丢失问题">如何解决浮点数运算的精度丢失问题？</h3>

<p><code class="language-plaintext highlighter-rouge">BigDecimal</code> 可以实现对浮点数的运算，不会造成精度丢失。通常情况下，大部分需要浮点数精确运算结果的业务场景（比如涉及到钱的场景）都是通过 <code class="language-plaintext highlighter-rouge">BigDecimal</code> 来做的。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">BigDecimal</span> <span class="n">a</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"1.0"</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"0.9"</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">c</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BigDecimal</span><span class="o">(</span><span class="s">"0.8"</span><span class="o">);</span>

<span class="nc">BigDecimal</span> <span class="n">x</span> <span class="o">=</span> <span class="n">a</span><span class="o">.</span><span class="na">subtract</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">y</span> <span class="o">=</span> <span class="n">b</span><span class="o">.</span><span class="na">subtract</span><span class="o">(</span><span class="n">c</span><span class="o">);</span>

<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x</span><span class="o">);</span> <span class="cm">/* 0.1 */</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">y</span><span class="o">);</span> <span class="cm">/* 0.1 */</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">Objects</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">y</span><span class="o">));</span> <span class="cm">/* true */</span>
</code></pre></div></div>

<p>关于 <code class="language-plaintext highlighter-rouge">BigDecimal</code> 的详细介绍，可以看看我写的这篇文章：<a href="https://javaguide.cn/java/basis/bigdecimal.html">BigDecimal 详解</a>。</p>

<h3 id="超过-long-整型的数据应该如何表示">超过 long 整型的数据应该如何表示？</h3>

<p>基本数值类型都有一个表达范围，如果超过这个范围就会有数值溢出的风险。</p>

<p>在 Java 中，64 位 long 整型是最大的整数类型。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">long</span> <span class="n">l</span> <span class="o">=</span> <span class="nc">Long</span><span class="o">.</span><span class="na">MAX_VALUE</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">l</span> <span class="o">+</span> <span class="mi">1</span><span class="o">);</span> <span class="c1">// -9223372036854775808</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">l</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">==</span> <span class="nc">Long</span><span class="o">.</span><span class="na">MIN_VALUE</span><span class="o">);</span> <span class="c1">// true</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">BigInteger</code> 内部使用 <code class="language-plaintext highlighter-rouge">int[]</code> 数组来存储任意大小的整形数据。</p>

<p>相对于常规整数类型的运算来说，<code class="language-plaintext highlighter-rouge">BigInteger</code> 运算的效率会相对较低。</p>

<h2 id="变量">变量</h2>

<h3 id="成员变量与局部变量的区别">成员变量与局部变量的区别？</h3>

<p><img src="/assets/images/JavaGuide/member-var-vs-local-var.png" alt="成员变量 vs 局部变量" /></p>

<ul>
  <li><strong>语法形式</strong>：从语法形式上看，成员变量是属于类的，而局部变量是在代码块或方法中定义的变量或是方法的参数；成员变量可以被 <code class="language-plaintext highlighter-rouge">public</code>,<code class="language-plaintext highlighter-rouge">private</code>,<code class="language-plaintext highlighter-rouge">static</code> 等修饰符所修饰，而局部变量不能被访问控制修饰符及 <code class="language-plaintext highlighter-rouge">static</code> 所修饰；但是，成员变量和局部变量都能被 <code class="language-plaintext highlighter-rouge">final</code> 所修饰。</li>
  <li><strong>存储方式</strong>：从变量在内存中的存储方式来看，如果成员变量是使用 <code class="language-plaintext highlighter-rouge">static</code> 修饰的，那么这个成员变量是属于类的，如果没有使用 <code class="language-plaintext highlighter-rouge">static</code> 修饰，这个成员变量是属于实例的。而对象存在于堆内存，局部变量则存在于栈内存。</li>
  <li><strong>生存时间</strong>：从变量在内存中的生存时间上看，成员变量是对象的一部分，它随着对象的创建而存在，而局部变量随着方法的调用而自动生成，随着方法的调用结束而消亡。</li>
  <li><strong>默认值</strong>：从变量是否有默认值来看，成员变量如果没有被赋初始值，则会自动以类型的默认值而赋值（一种情况例外:被 <code class="language-plaintext highlighter-rouge">final</code> 修饰的成员变量也必须显式地赋值），而局部变量则不会自动赋值。</li>
</ul>

<p><strong>为什么成员变量有默认值？</strong></p>

<ol>
  <li>
    <p>先不考虑变量类型，如果没有默认值会怎样？变量存储的是内存地址对应的任意随机值，程序读取该值运行会出现意外。</p>
  </li>
  <li>
    <p>默认值有两种设置方式：手动和自动，根据第一点，没有手动赋值一定要自动赋值。成员变量在运行时可借助反射等方法手动赋值，而局部变量不行。</p>
  </li>
  <li>
    <p>对于编译器（javac）来说，局部变量没赋值很好判断，可以直接报错。而成员变量可能是运行时赋值，无法判断，误报“没默认值”又会影响用户体验，所以采用自动赋默认值。</p>
  </li>
</ol>

<p>成员变量与局部变量代码示例：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">VariableExample</span> <span class="o">{</span>

    <span class="c1">// 成员变量</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">age</span><span class="o">;</span>

    <span class="c1">// 方法中的局部变量</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">method</span><span class="o">()</span> <span class="o">{</span>
        <span class="kt">int</span> <span class="n">num1</span> <span class="o">=</span> <span class="mi">10</span><span class="o">;</span> <span class="c1">// 栈中分配的局部变量</span>
        <span class="nc">String</span> <span class="n">str</span> <span class="o">=</span> <span class="s">"Hello, world!"</span><span class="o">;</span> <span class="c1">// 栈中分配的局部变量</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">num1</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// 带参数的方法中的局部变量</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">method2</span><span class="o">(</span><span class="kt">int</span> <span class="n">num2</span><span class="o">)</span> <span class="o">{</span>
        <span class="kt">int</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">num2</span> <span class="o">+</span> <span class="mi">10</span><span class="o">;</span> <span class="c1">// 栈中分配的局部变量</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">sum</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// 构造方法中的局部变量</span>
    <span class="kd">public</span> <span class="nf">VariableExample</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">int</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span> <span class="c1">// 对成员变量进行赋值</span>
        <span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span> <span class="c1">// 对成员变量进行赋值</span>
        <span class="kt">int</span> <span class="n">num3</span> <span class="o">=</span> <span class="mi">20</span><span class="o">;</span> <span class="c1">// 栈中分配的局部变量</span>
        <span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="s">"Hello, "</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">+</span> <span class="s">"!"</span><span class="o">;</span> <span class="c1">// 栈中分配的局部变量</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">num3</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str2</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>

</code></pre></div></div>

<h3 id="静态变量有什么作用">静态变量有什么作用？</h3>

<p>静态变量也就是被 <code class="language-plaintext highlighter-rouge">static</code> 关键字修饰的变量。它可以被类的所有实例共享，无论一个类创建了多少个对象，它们都共享同一份静态变量。也就是说，静态变量只会被分配一次内存，即使创建多个对象，这样可以节省内存。</p>

<p>静态变量是通过类名来访问的，例如<code class="language-plaintext highlighter-rouge">StaticVariableExample.staticVar</code>（如果被 <code class="language-plaintext highlighter-rouge">private</code>关键字修饰就无法这样访问了）。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">StaticVariableExample</span> <span class="o">{</span>
    <span class="c1">// 静态变量</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="n">staticVar</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p>通常情况下，静态变量会被 <code class="language-plaintext highlighter-rouge">final</code> 关键字修饰成为常量。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConstantVariableExample</span> <span class="o">{</span>
    <span class="c1">// 常量</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">constantVar</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="字符型常量和字符串常量的区别">字符型常量和字符串常量的区别?</h3>

<ul>
  <li><strong>形式</strong> : 字符常量是单引号引起的一个字符，字符串常量是双引号引起的 0 个或若干个字符。</li>
  <li><strong>含义</strong> : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)。</li>
  <li><strong>占内存大小</strong>：字符常量只占 2 个字节; 字符串常量占若干个字节。</li>
</ul>

<p>⚠️ 注意 <code class="language-plaintext highlighter-rouge">char</code> 在 Java 中占两个字节。</p>

<p>字符型常量和字符串常量代码示例：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">StringExample</span> <span class="o">{</span>
    <span class="c1">// 字符型常量</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">char</span> <span class="no">LETTER_A</span> <span class="o">=</span> <span class="sc">'A'</span><span class="o">;</span>

    <span class="c1">// 字符串常量</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">GREETING_MESSAGE</span> <span class="o">=</span> <span class="s">"Hello, world!"</span><span class="o">;</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"字符型常量占用的字节数为："</span><span class="o">+</span><span class="nc">Character</span><span class="o">.</span><span class="na">BYTES</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"字符串常量占用的字节数为："</span><span class="o">+</span><span class="no">GREETING_MESSAGE</span><span class="o">.</span><span class="na">getBytes</span><span class="o">().</span><span class="na">length</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>输出：</p>

<pre><code class="language-plain">字符型常量占用的字节数为：2
字符串常量占用的字节数为：13
</code></pre>

<h2 id="方法">方法</h2>

<h3 id="什么是方法的返回值方法有哪几种类型">什么是方法的返回值?方法有哪几种类型？</h3>

<p><strong>方法的返回值</strong> 是指我们获取到的某个方法体中的代码执行后产生的结果！（前提是该方法可能产生结果）。返回值的作用是接收出结果，使得它可以用于其他的操作！</p>

<p>我们可以按照方法的返回值和参数类型将方法分为下面这几种：</p>

<p><strong>1、无参数无返回值的方法</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">f1</span><span class="o">()</span> <span class="o">{</span>
    <span class="c1">//......</span>
<span class="o">}</span>
<span class="c1">// 下面这个方法也没有返回值，虽然用到了 return</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">f</span><span class="o">(</span><span class="kt">int</span> <span class="n">a</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(...)</span> <span class="o">{</span>
        <span class="c1">// 表示结束方法的执行,下方的输出语句不会执行</span>
        <span class="k">return</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>2、有参数无返回值的方法</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">f2</span><span class="o">(</span><span class="nc">Parameter</span> <span class="mi">1</span><span class="o">,</span> <span class="o">...,</span> <span class="nc">Parameter</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">//......</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>3、有返回值无参数的方法</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">int</span> <span class="nf">f3</span><span class="o">()</span> <span class="o">{</span>
    <span class="c1">//......</span>
    <span class="k">return</span> <span class="n">x</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>4、有返回值有参数的方法</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">int</span> <span class="nf">f4</span><span class="o">(</span><span class="kt">int</span> <span class="n">a</span><span class="o">,</span> <span class="kt">int</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">a</span> <span class="o">*</span> <span class="n">b</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="静态方法为什么不能调用非静态成员">静态方法为什么不能调用非静态成员?</h3>

<p>这个需要结合 JVM 的相关知识，主要原因如下：</p>

<ol>
  <li>静态方法是属于类的，在类加载的时候就会分配内存，可以通过类名直接访问。而非静态成员属于实例对象，只有在对象实例化之后才存在，需要通过类的实例对象去访问。</li>
  <li>在类的非静态成员不存在的时候静态方法就已经存在了，此时调用在内存中还不存在的非静态成员，属于非法操作。</li>
</ol>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Example</span> <span class="o">{</span>
    <span class="c1">// 定义一个字符型常量</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">char</span> <span class="no">LETTER_A</span> <span class="o">=</span> <span class="sc">'A'</span><span class="o">;</span>

    <span class="c1">// 定义一个字符串常量</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">GREETING_MESSAGE</span> <span class="o">=</span> <span class="s">"Hello, world!"</span><span class="o">;</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 输出字符型常量的值</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"字符型常量的值为："</span> <span class="o">+</span> <span class="no">LETTER_A</span><span class="o">);</span>

        <span class="c1">// 输出字符串常量的值</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"字符串常量的值为："</span> <span class="o">+</span> <span class="no">GREETING_MESSAGE</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="静态方法和实例方法有何不同">静态方法和实例方法有何不同？</h3>

<p><strong>1、调用方式</strong></p>

<p>在外部调用静态方法时，可以使用 <code class="language-plaintext highlighter-rouge">类名.方法名</code> 的方式，也可以使用 <code class="language-plaintext highlighter-rouge">对象.方法名</code> 的方式，而实例方法只有后面这种方式。也就是说，<strong>调用静态方法可以无需创建对象</strong> 。</p>

<p>不过，需要注意的是一般不建议使用 <code class="language-plaintext highlighter-rouge">对象.方法名</code> 的方式来调用静态方法。这种方式非常容易造成混淆，静态方法不属于类的某个对象而是属于这个类。</p>

<p>因此，一般建议使用 <code class="language-plaintext highlighter-rouge">类名.方法名</code> 的方式来调用静态方法。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Person</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">method</span><span class="o">()</span> <span class="o">{</span>
      <span class="c1">//......</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">staicMethod</span><span class="o">(){</span>
      <span class="c1">//......</span>
    <span class="o">}</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Person</span> <span class="n">person</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Person</span><span class="o">();</span>
        <span class="c1">// 调用实例方法</span>
        <span class="n">person</span><span class="o">.</span><span class="na">method</span><span class="o">();</span>
        <span class="c1">// 调用静态方法</span>
        <span class="nc">Person</span><span class="o">.</span><span class="na">staicMethod</span><span class="o">()</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>2、访问类成员是否存在限制</strong></p>

<p>静态方法在访问本类的成员时，只允许访问静态成员（即静态成员变量和静态方法），不允许访问实例成员（即实例成员变量和实例方法），而实例方法不存在这个限制。</p>

<h3 id="重载和重写有什么区别">重载和重写有什么区别？</h3>

<blockquote>
  <p>重载就是同样的一个方法能够根据输入数据的不同，做出不同的处理</p>

  <p>重写就是当子类继承自父类的相同方法，输入数据一样，但要做出有别于父类的响应时，你就要覆盖父类方法</p>
</blockquote>

<h4 id="重载">重载</h4>

<p>发生在同一个类中（或者父类和子类之间），方法名必须相同，参数类型不同、个数不同、顺序不同，方法返回值和访问修饰符可以不同。</p>

<p>《Java 核心技术》这本书是这样介绍重载的：</p>

<blockquote>
  <p>如果多个方法(比如 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 的构造方法)有相同的名字、不同的参数， 便产生了重载。</p>

  <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">StringBuilder</span> <span class="n">sb</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuilder</span><span class="o">();</span>
<span class="nc">StringBuilder</span> <span class="n">sb2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuilder</span><span class="o">(</span><span class="s">"HelloWorld"</span><span class="o">);</span>
</code></pre></div>  </div>

  <p>编译器必须挑选出具体执行哪个方法，它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。 如果编译器找不到匹配的参数， 就会产生编译时错误， 因为根本不存在匹配， 或者没有一个比其他的更好(这个过程被称为重载解析(overloading resolution))。</p>

  <p>Java 允许重载任何方法， 而不只是构造器方法。</p>
</blockquote>

<p>综上：重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。</p>

<h4 id="重写">重写</h4>

<p>重写发生在运行期，是子类对父类的允许访问的方法的实现过程进行重新编写。</p>

<ol>
  <li>方法名、参数列表必须相同，子类方法返回值类型应比父类方法返回值类型更小或相等，抛出的异常范围小于等于父类，访问修饰符范围大于等于父类。</li>
  <li>如果父类方法访问修饰符为 <code class="language-plaintext highlighter-rouge">private/final/static</code> 则子类就不能重写该方法，但是被 <code class="language-plaintext highlighter-rouge">static</code> 修饰的方法能够被再次声明。</li>
  <li>构造方法无法被重写</li>
</ol>

<h4 id="总结">总结</h4>

<p>综上：<strong>重写就是子类对父类方法的重新改造，外部样子不能改变，内部逻辑可以改变。</strong></p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">区别点</th>
      <th style="text-align: left">重载方法</th>
      <th style="text-align: left">重写方法</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">发生范围</td>
      <td style="text-align: left">同一个类</td>
      <td style="text-align: left">子类</td>
    </tr>
    <tr>
      <td style="text-align: left">参数列表</td>
      <td style="text-align: left">必须修改</td>
      <td style="text-align: left">一定不能修改</td>
    </tr>
    <tr>
      <td style="text-align: left">返回类型</td>
      <td style="text-align: left">可修改</td>
      <td style="text-align: left">子类方法返回值类型应比父类方法返回值类型更小或相等</td>
    </tr>
    <tr>
      <td style="text-align: left">异常</td>
      <td style="text-align: left">可修改</td>
      <td style="text-align: left">子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等；</td>
    </tr>
    <tr>
      <td style="text-align: left">访问修饰符</td>
      <td style="text-align: left">可修改</td>
      <td style="text-align: left">一定不能做更严格的限制（可以降低限制）</td>
    </tr>
    <tr>
      <td style="text-align: left">发生阶段</td>
      <td style="text-align: left">编译期</td>
      <td style="text-align: left">运行期</td>
    </tr>
  </tbody>
</table>

<p><strong>方法的重写要遵循“两同两小一大”</strong>（以下内容摘录自《疯狂 Java 讲义》，<a href="https://github.com/Snailclimb/JavaGuide/issues/892">issue#892</a> ）：</p>

<ul>
  <li>“两同”即方法名相同、形参列表相同；</li>
  <li>“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等，子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等；</li>
  <li>“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。</li>
</ul>

<p>⭐️ 关于 <strong>重写的返回值类型</strong> 这里需要额外多说明一下，上面的表述不太清晰准确：如果方法的返回类型是 void 和基本数据类型，则返回值重写时不可修改。但是如果方法的返回值是引用类型，重写时是可以返回该引用类型的子类的。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Hero</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">name</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="s">"超级英雄"</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SuperMan</span> <span class="kd">extends</span> <span class="nc">Hero</span><span class="o">{</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">name</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="s">"超人"</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="kd">public</span> <span class="nc">Hero</span> <span class="nf">hero</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Hero</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SuperSuperMan</span> <span class="kd">extends</span> <span class="nc">SuperMan</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">name</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="s">"超级超级英雄"</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">SuperMan</span> <span class="nf">hero</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">SuperMan</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="什么是可变长参数">什么是可变长参数？</h3>

<p>从 Java5 开始，Java 支持定义可变长参数，所谓可变长参数就是允许在调用方法时传入不定长度的参数。就比如下面的这个 <code class="language-plaintext highlighter-rouge">printVariable</code> 方法就可以接受 0 个或者多个参数。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">method1</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
   <span class="c1">//......</span>
<span class="o">}</span>
</code></pre></div></div>

<p>另外，可变参数只能作为函数的最后一个参数，但其前面可以有也可以没有任何其他参数。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">method2</span><span class="o">(</span><span class="nc">String</span> <span class="n">arg1</span><span class="o">,</span> <span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
   <span class="c1">//......</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>遇到方法重载的情况怎么办呢？会优先匹配固定参数还是可变参数的方法呢？</strong></p>

<p>答案是会优先匹配固定参数的方法，因为固定参数的方法匹配度更高。</p>

<p>我们通过下面这个例子来证明一下。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
 *
 * @author Guide哥
 * @date 2021/12/13 16:52
 **/</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">VariableLengthArgument</span> <span class="o">{</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">printVariable</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">s</span> <span class="o">:</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">printVariable</span><span class="o">(</span><span class="nc">String</span> <span class="n">arg1</span><span class="o">,</span> <span class="nc">String</span> <span class="n">arg2</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">arg1</span> <span class="o">+</span> <span class="n">arg2</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">printVariable</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">);</span>
        <span class="n">printVariable</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="s">"c"</span><span class="o">,</span> <span class="s">"d"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>输出：</p>

<pre><code class="language-plain">ab
a
b
c
d
</code></pre>

<p>另外，Java 的可变参数编译后实际会被转换成一个数组，我们看编译后生成的 <code class="language-plaintext highlighter-rouge">class</code>文件就可以看出来了。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">VariableLengthArgument</span> <span class="o">{</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">printVariable</span><span class="o">(</span><span class="nc">String</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">String</span><span class="o">[]</span> <span class="n">var1</span> <span class="o">=</span> <span class="n">args</span><span class="o">;</span>
        <span class="kt">int</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>

        <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">var3</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">var3</span> <span class="o">&lt;</span> <span class="n">var2</span><span class="o">;</span> <span class="o">++</span><span class="n">var3</span><span class="o">)</span> <span class="o">{</span>
            <span class="nc">String</span> <span class="n">s</span> <span class="o">=</span> <span class="n">var1</span><span class="o">[</span><span class="n">var3</span><span class="o">];</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
        <span class="o">}</span>

    <span class="o">}</span>
    <span class="c1">// ......</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="参考">参考</h2>

<ul>
  <li>What is the difference between JDK and JRE?：<a href="https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre">https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre</a></li>
  <li>Oracle vs OpenJDK：<a href="https://www.educba.com/oracle-vs-openjdk/">https://www.educba.com/oracle-vs-openjdk/</a></li>
  <li>Differences between Oracle JDK and OpenJDK：<a href="https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk">https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk</a></li>
  <li>彻底弄懂 Java 的移位操作符：<a href="https://juejin.cn/post/6844904025880526861">https://juejin.cn/post/6844904025880526861</a></li>
</ul>

<!-- @include: @article-footer.snippet.md -->]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Java基础常见面试题总结(中)</title><link href="https://yyym-y.github.io/2024/04/25/java-basic-questions-02/" rel="alternate" type="text/html" title="Java基础常见面试题总结(中)" /><published>2024-04-25T00:00:00+00:00</published><updated>2024-04-25T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/04/25/java-basic-questions-02</id><content type="html" xml:base="https://yyym-y.github.io/2024/04/25/java-basic-questions-02/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#面向对象基础" id="markdown-toc-面向对象基础">面向对象基础</a>    <ul>
      <li><a href="#面向对象和面向过程的区别" id="markdown-toc-面向对象和面向过程的区别">面向对象和面向过程的区别</a></li>
      <li><a href="#创建一个对象用什么运算符对象实体与对象引用有何不同" id="markdown-toc-创建一个对象用什么运算符对象实体与对象引用有何不同">创建一个对象用什么运算符?对象实体与对象引用有何不同?</a></li>
      <li><a href="#对象的相等和引用相等的区别" id="markdown-toc-对象的相等和引用相等的区别">对象的相等和引用相等的区别</a></li>
      <li><a href="#如果一个类没有声明构造方法该程序能正确执行吗" id="markdown-toc-如果一个类没有声明构造方法该程序能正确执行吗">如果一个类没有声明构造方法，该程序能正确执行吗?</a></li>
      <li><a href="#构造方法有哪些特点是否可被-override" id="markdown-toc-构造方法有哪些特点是否可被-override">构造方法有哪些特点？是否可被 override?</a></li>
      <li><a href="#面向对象三大特征" id="markdown-toc-面向对象三大特征">面向对象三大特征</a>        <ul>
          <li><a href="#封装" id="markdown-toc-封装">封装</a></li>
          <li><a href="#继承" id="markdown-toc-继承">继承</a></li>
          <li><a href="#多态" id="markdown-toc-多态">多态</a></li>
        </ul>
      </li>
      <li><a href="#接口和抽象类有什么共同点和区别" id="markdown-toc-接口和抽象类有什么共同点和区别">接口和抽象类有什么共同点和区别？</a></li>
      <li><a href="#深拷贝和浅拷贝区别了解吗什么是引用拷贝" id="markdown-toc-深拷贝和浅拷贝区别了解吗什么是引用拷贝">深拷贝和浅拷贝区别了解吗？什么是引用拷贝？</a>        <ul>
          <li><a href="#浅拷贝" id="markdown-toc-浅拷贝">浅拷贝</a></li>
          <li><a href="#深拷贝" id="markdown-toc-深拷贝">深拷贝</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#object" id="markdown-toc-object">Object</a>    <ul>
      <li><a href="#object-类的常见方法有哪些" id="markdown-toc-object-类的常见方法有哪些">Object 类的常见方法有哪些？</a></li>
      <li><a href="#-和-equals-的区别" id="markdown-toc--和-equals-的区别">== 和 equals() 的区别</a></li>
      <li><a href="#hashcode-有什么用" id="markdown-toc-hashcode-有什么用">hashCode() 有什么用？</a></li>
      <li><a href="#为什么要有-hashcode" id="markdown-toc-为什么要有-hashcode">为什么要有 hashCode？</a></li>
      <li><a href="#为什么重写-equals-时必须重写-hashcode-方法" id="markdown-toc-为什么重写-equals-时必须重写-hashcode-方法">为什么重写 equals() 时必须重写 hashCode() 方法？</a></li>
    </ul>
  </li>
  <li><a href="#string" id="markdown-toc-string">String</a>    <ul>
      <li><a href="#stringstringbufferstringbuilder-的区别" id="markdown-toc-stringstringbufferstringbuilder-的区别">String、StringBuffer、StringBuilder 的区别？</a></li>
      <li><a href="#string-为什么是不可变的" id="markdown-toc-string-为什么是不可变的">String 为什么是不可变的?</a></li>
      <li><a href="#字符串拼接用-还是-stringbuilder" id="markdown-toc-字符串拼接用-还是-stringbuilder">字符串拼接用“+” 还是 StringBuilder?</a></li>
      <li><a href="#stringequals-和-objectequals-有何区别" id="markdown-toc-stringequals-和-objectequals-有何区别">String#equals() 和 Object#equals() 有何区别？</a></li>
      <li><a href="#字符串常量池的作用了解吗" id="markdown-toc-字符串常量池的作用了解吗">字符串常量池的作用了解吗？</a></li>
      <li><a href="#string-s1--new-stringabc这句话创建了几个字符串对象" id="markdown-toc-string-s1--new-stringabc这句话创建了几个字符串对象">String s1 = new String(“abc”);这句话创建了几个字符串对象？</a></li>
      <li><a href="#stringintern-方法有什么作用" id="markdown-toc-stringintern-方法有什么作用">String#intern 方法有什么作用?</a></li>
      <li><a href="#string-类型的变量和常量做运算时发生了什么" id="markdown-toc-string-类型的变量和常量做运算时发生了什么">String 类型的变量和常量做“+”运算时发生了什么？</a></li>
    </ul>
  </li>
  <li><a href="#参考" id="markdown-toc-参考">参考</a></li>
</ul>

<hr />

<hr />

<p><strong><mark>本系列搬运自 <a href="https://javaguide.cn/">JavaGuide</a>, 本人根据自身理解加上批注转载于此, 欢迎各位去原网站支持作者</mark></strong></p>

<hr />

<h2 id="面向对象基础">面向对象基础</h2>

<h3 id="面向对象和面向过程的区别">面向对象和面向过程的区别</h3>

<p>两者的主要区别在于解决问题的方式不同：</p>

<ul>
  <li>面向过程把解决问题的过程拆成一个个方法，通过一个个方法的执行解决问题。</li>
  <li>面向对象会先抽象出对象，然后用对象执行方法的方式解决问题。</li>
</ul>

<p>另外，面向对象开发的程序一般更易维护、易复用、易扩展。</p>

<p>相关 issue : <a href="https://github.com/Snailclimb/JavaGuide/issues/431">面向过程：面向过程性能比面向对象高？？</a> 。</p>

<p>下面是一个求圆的面积和周长的示例，简单分别展示了面向对象和面向过程两种不同的解决方案。</p>

<p><strong>面向对象</strong>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Circle</span> <span class="o">{</span>
    <span class="c1">// 定义圆的半径</span>
    <span class="kd">private</span> <span class="kt">double</span> <span class="n">radius</span><span class="o">;</span>

    <span class="c1">// 构造函数</span>
    <span class="kd">public</span> <span class="nf">Circle</span><span class="o">(</span><span class="kt">double</span> <span class="n">radius</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">radius</span> <span class="o">=</span> <span class="n">radius</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">// 计算圆的面积</span>
    <span class="kd">public</span> <span class="kt">double</span> <span class="nf">getArea</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">Math</span><span class="o">.</span><span class="na">PI</span> <span class="o">*</span> <span class="n">radius</span> <span class="o">*</span> <span class="n">radius</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">// 计算圆的周长</span>
    <span class="kd">public</span> <span class="kt">double</span> <span class="nf">getPerimeter</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="mi">2</span> <span class="o">*</span> <span class="nc">Math</span><span class="o">.</span><span class="na">PI</span> <span class="o">*</span> <span class="n">radius</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 创建一个半径为3的圆</span>
        <span class="nc">Circle</span> <span class="n">circle</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Circle</span><span class="o">(</span><span class="mf">3.0</span><span class="o">);</span>

        <span class="c1">// 输出圆的面积和周长</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"圆的面积为："</span> <span class="o">+</span> <span class="n">circle</span><span class="o">.</span><span class="na">getArea</span><span class="o">());</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"圆的周长为："</span> <span class="o">+</span> <span class="n">circle</span><span class="o">.</span><span class="na">getPerimeter</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>我们定义了一个 <code class="language-plaintext highlighter-rouge">Circle</code> 类来表示圆，该类包含了圆的半径属性和计算面积、周长的方法。</p>

<p><strong>面向过程</strong>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Main</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// 定义圆的半径</span>
        <span class="kt">double</span> <span class="n">radius</span> <span class="o">=</span> <span class="mf">3.0</span><span class="o">;</span>

        <span class="c1">// 计算圆的面积和周长</span>
        <span class="kt">double</span> <span class="n">area</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">PI</span> <span class="o">*</span> <span class="n">radius</span> <span class="o">*</span> <span class="n">radius</span><span class="o">;</span>
        <span class="kt">double</span> <span class="n">perimeter</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="nc">Math</span><span class="o">.</span><span class="na">PI</span> <span class="o">*</span> <span class="n">radius</span><span class="o">;</span>

        <span class="c1">// 输出圆的面积和周长</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"圆的面积为："</span> <span class="o">+</span> <span class="n">area</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"圆的周长为："</span> <span class="o">+</span> <span class="n">perimeter</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>我们直接定义了圆的半径，并使用该半径直接计算出圆的面积和周长。</p>

<h3 id="创建一个对象用什么运算符对象实体与对象引用有何不同">创建一个对象用什么运算符?对象实体与对象引用有何不同?</h3>

<p>new 运算符，new 创建对象实例（对象实例在堆内存中），对象引用指向对象实例（对象引用存放在栈内存中）。</p>

<ul>
  <li>一个对象引用可以指向 0 个或 1 个对象（一根绳子可以不系气球，也可以系一个气球）；</li>
  <li>一个对象可以有 n 个引用指向它（可以用 n 条绳子系住一个气球）。</li>
</ul>

<h3 id="对象的相等和引用相等的区别">对象的相等和引用相等的区别</h3>

<ul>
  <li>对象的相等一般比较的是内存中存放的内容是否相等。</li>
  <li>引用相等一般比较的是他们指向的内存地址是否相等。</li>
</ul>

<p>这里举一个例子：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">str1</span> <span class="o">=</span> <span class="s">"hello"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"hello"</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">str3</span> <span class="o">=</span> <span class="s">"hello"</span><span class="o">;</span>
<span class="c1">// 使用 == 比较字符串的引用相等</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str1</span> <span class="o">==</span> <span class="n">str2</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str1</span> <span class="o">==</span> <span class="n">str3</span><span class="o">);</span>
<span class="c1">// 使用 equals 方法比较字符串的相等</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str1</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">str2</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str1</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">str3</span><span class="o">));</span>

</code></pre></div></div>

<p>输出结果：</p>

<pre><code class="language-plain">false
true
true
true
</code></pre>

<p>从上面的代码输出结果可以看出：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">str1</code> 和 <code class="language-plaintext highlighter-rouge">str2</code> 不相等，而 <code class="language-plaintext highlighter-rouge">str1</code> 和 <code class="language-plaintext highlighter-rouge">str3</code> 相等。这是因为 <code class="language-plaintext highlighter-rouge">==</code> 运算符比较的是字符串的引用是否相等。</li>
  <li><code class="language-plaintext highlighter-rouge">str1</code>、 <code class="language-plaintext highlighter-rouge">str2</code>、<code class="language-plaintext highlighter-rouge">str3</code> 三者的内容都相等。这是因为<code class="language-plaintext highlighter-rouge">equals</code> 方法比较的是字符串的内容，即使这些字符串的对象引用不同，只要它们的内容相等，就认为它们是相等的。</li>
</ul>

<h3 id="如果一个类没有声明构造方法该程序能正确执行吗">如果一个类没有声明构造方法，该程序能正确执行吗?</h3>

<p>构造方法是一种特殊的方法，主要作用是完成对象的初始化工作。</p>

<p>如果一个类没有声明构造方法，也可以执行！因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法（无论是否有参），Java 就不会添加默认的无参数的构造方法了。</p>

<p>我们一直在不知不觉地使用构造方法，这也是为什么我们在创建对象的时候后面要加一个括号（因为要调用无参的构造方法）。如果我们重载了有参的构造方法，记得都要把无参的构造方法也写出来（无论是否用到），因为这可以帮助我们在创建对象的时候少踩坑。</p>

<h3 id="构造方法有哪些特点是否可被-override">构造方法有哪些特点？是否可被 override?</h3>

<p>构造方法特点如下：</p>

<ul>
  <li>名字与类名相同。</li>
  <li>没有返回值，但不能用 void 声明构造函数。</li>
  <li>生成类的对象时自动执行，无需调用。</li>
</ul>

<p>构造方法不能被 override（重写）,但是可以 overload（重载）,所以你可以看到一个类中有多个构造函数的情况。</p>

<h3 id="面向对象三大特征">面向对象三大特征</h3>

<h4 id="封装">封装</h4>

<p>封装是指把一个对象的状态信息（也就是属性）隐藏在对象内部，不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息（也就是属性），但是可以通过遥控器（方法）来控制空调。如果属性不想被外界访问，我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法，那么这个类也没有什么意义了。就好像如果没有空调遥控器，那么我们就无法操控空凋制冷，空调本身就没有意义了（当然现在还有很多其他方法 ，这里只是为了举例子）。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Student</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">id</span><span class="o">;</span><span class="c1">//id属性私有化</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span><span class="c1">//name属性私有化</span>

    <span class="c1">//获取id的方法</span>
    <span class="kd">public</span> <span class="kt">int</span> <span class="nf">getId</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">id</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">//设置id的方法</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setId</span><span class="o">(</span><span class="kt">int</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">id</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">//获取name的方法</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="c1">//设置name的方法</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="继承">继承</h4>

<p>不同类型的对象，相互之间经常有一定数量的共同点。例如，小明同学、小红同学、小李同学，都共享学生的特性（班级、学号等）。同时，每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好，小红的性格惹人喜爱；小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术，新类的定义可以增加新的数据或新的功能，也可以用父类的功能，但不能选择性地继承父类。通过使用继承，可以快速地创建新的类，可以提高代码的重用，程序的可维护性，节省大量创建新类的时间 ，提高我们的开发效率。</p>

<p><strong>关于继承如下 3 点请记住：</strong></p>

<ol>
  <li>子类拥有父类对象所有的属性和方法（包括私有属性和私有方法），但是父类中的私有属性和方法子类是无法访问，<strong>只是拥有</strong>。</li>
  <li>子类可以拥有自己属性和方法，即子类可以对父类进行扩展。</li>
  <li>子类可以用自己的方式实现父类的方法。（以后介绍）。</li>
</ol>

<h4 id="多态">多态</h4>

<p>多态，顾名思义，表示一个对象具有多种的状态，具体表现为父类的引用指向子类的实例。</p>

<p><strong>多态的特点:</strong></p>

<ul>
  <li>对象类型和引用类型之间具有继承（类）/实现（接口）的关系；</li>
  <li>引用类型变量发出的方法调用的到底是哪个类中的方法，必须在程序运行期间才能确定；</li>
  <li>多态不能调用“只在子类存在但在父类不存在”的方法；</li>
  <li>如果子类重写了父类的方法，真正执行的是子类重写的方法，如果子类没有重写父类的方法，执行的是父类的方法。</li>
</ul>

<h3 id="接口和抽象类有什么共同点和区别">接口和抽象类有什么共同点和区别？</h3>

<p><strong>共同点</strong>：</p>

<ul>
  <li>都不能被实例化。</li>
  <li>都可以包含抽象方法。</li>
  <li>都可以有默认实现的方法（Java 8 可以用 <code class="language-plaintext highlighter-rouge">default</code> 关键字在接口中定义默认方法）。</li>
</ul>

<p><strong>区别</strong>：</p>

<ul>
  <li>接口主要用于对类的行为进行约束，你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用，强调的是所属关系。</li>
  <li>一个类只能继承一个类，但是可以实现多个接口。</li>
  <li>接口中的成员变量只能是 <code class="language-plaintext highlighter-rouge">public static final</code> 类型的，不能被修改且必须有初始值，而抽象类的成员变量默认 default，可在子类中被重新定义，也可被重新赋值。</li>
</ul>

<h3 id="深拷贝和浅拷贝区别了解吗什么是引用拷贝">深拷贝和浅拷贝区别了解吗？什么是引用拷贝？</h3>

<p>关于深拷贝和浅拷贝区别，我这里先给结论：</p>

<ul>
  <li><strong>浅拷贝</strong>：浅拷贝会在堆上创建一个新的对象（区别于引用拷贝的一点），不过，如果原对象内部的属性是引用类型的话，浅拷贝会直接复制内部对象的引用地址，也就是说拷贝对象和原对象共用同一个内部对象。</li>
  <li><strong>深拷贝</strong>：深拷贝会完全复制整个对象，包括这个对象所包含的内部对象。</li>
</ul>

<p>上面的结论没有完全理解的话也没关系，我们来看一个具体的案例！</p>

<h4 id="浅拷贝">浅拷贝</h4>

<p>浅拷贝的示例代码如下，我们这里实现了 <code class="language-plaintext highlighter-rouge">Cloneable</code> 接口，并重写了 <code class="language-plaintext highlighter-rouge">clone()</code> 方法。</p>

<p><code class="language-plaintext highlighter-rouge">clone()</code> 方法的实现很简单，直接调用的是父类 <code class="language-plaintext highlighter-rouge">Object</code> 的 <code class="language-plaintext highlighter-rouge">clone()</code> 方法。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Address</span> <span class="kd">implements</span> <span class="nc">Cloneable</span><span class="o">{</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
    <span class="c1">// 省略构造函数、Getter&amp;Setter方法</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Address</span> <span class="nf">clone</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="k">return</span> <span class="o">(</span><span class="nc">Address</span><span class="o">)</span> <span class="kd">super</span><span class="o">.</span><span class="na">clone</span><span class="o">();</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">CloneNotSupportedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">AssertionError</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Person</span> <span class="kd">implements</span> <span class="nc">Cloneable</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="nc">Address</span> <span class="n">address</span><span class="o">;</span>
    <span class="c1">// 省略构造函数、Getter&amp;Setter方法</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Person</span> <span class="nf">clone</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="nc">Person</span> <span class="n">person</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Person</span><span class="o">)</span> <span class="kd">super</span><span class="o">.</span><span class="na">clone</span><span class="o">();</span>
            <span class="k">return</span> <span class="n">person</span><span class="o">;</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">CloneNotSupportedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">AssertionError</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>测试：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Person</span> <span class="n">person1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Person</span><span class="o">(</span><span class="k">new</span> <span class="nc">Address</span><span class="o">(</span><span class="s">"武汉"</span><span class="o">));</span>
<span class="nc">Person</span> <span class="n">person1Copy</span> <span class="o">=</span> <span class="n">person1</span><span class="o">.</span><span class="na">clone</span><span class="o">();</span>
<span class="c1">// true</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">person1</span><span class="o">.</span><span class="na">getAddress</span><span class="o">()</span> <span class="o">==</span> <span class="n">person1Copy</span><span class="o">.</span><span class="na">getAddress</span><span class="o">());</span>
</code></pre></div></div>

<p>从输出结构就可以看出， <code class="language-plaintext highlighter-rouge">person1</code> 的克隆对象和 <code class="language-plaintext highlighter-rouge">person1</code> 使用的仍然是同一个 <code class="language-plaintext highlighter-rouge">Address</code> 对象。</p>

<h4 id="深拷贝">深拷贝</h4>

<p>这里我们简单对 <code class="language-plaintext highlighter-rouge">Person</code> 类的 <code class="language-plaintext highlighter-rouge">clone()</code> 方法进行修改，连带着要把 <code class="language-plaintext highlighter-rouge">Person</code> 对象内部的 <code class="language-plaintext highlighter-rouge">Address</code> 对象一起复制。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Person</span> <span class="nf">clone</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="nc">Person</span> <span class="n">person</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Person</span><span class="o">)</span> <span class="kd">super</span><span class="o">.</span><span class="na">clone</span><span class="o">();</span>
        <span class="n">person</span><span class="o">.</span><span class="na">setAddress</span><span class="o">(</span><span class="n">person</span><span class="o">.</span><span class="na">getAddress</span><span class="o">().</span><span class="na">clone</span><span class="o">());</span>
        <span class="k">return</span> <span class="n">person</span><span class="o">;</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">CloneNotSupportedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">AssertionError</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>测试：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Person</span> <span class="n">person1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Person</span><span class="o">(</span><span class="k">new</span> <span class="nc">Address</span><span class="o">(</span><span class="s">"武汉"</span><span class="o">));</span>
<span class="nc">Person</span> <span class="n">person1Copy</span> <span class="o">=</span> <span class="n">person1</span><span class="o">.</span><span class="na">clone</span><span class="o">();</span>
<span class="c1">// false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">person1</span><span class="o">.</span><span class="na">getAddress</span><span class="o">()</span> <span class="o">==</span> <span class="n">person1Copy</span><span class="o">.</span><span class="na">getAddress</span><span class="o">());</span>
</code></pre></div></div>

<p>从输出结构就可以看出，显然 <code class="language-plaintext highlighter-rouge">person1</code> 的克隆对象和 <code class="language-plaintext highlighter-rouge">person1</code> 包含的 <code class="language-plaintext highlighter-rouge">Address</code> 对象已经是不同的了。</p>

<p><strong>那什么是引用拷贝呢？</strong> 简单来说，引用拷贝就是两个不同的引用指向同一个对象。</p>

<p>我专门画了一张图来描述浅拷贝、深拷贝、引用拷贝：</p>

<p><img src="/assets/images/JavaGuide/shallow&amp;deep-copy.png" alt="浅拷贝、深拷贝、引用拷贝示意图" /></p>

<h2 id="object">Object</h2>

<h3 id="object-类的常见方法有哪些">Object 类的常见方法有哪些？</h3>

<p>Object 类是一个特殊的类，是所有类的父类。它主要提供了以下 11 个方法：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * native 方法，用于返回当前运行时对象的 Class 对象，使用了 final 关键字修饰，故不允许子类重写。
 */</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">native</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">getClass</span><span class="o">()</span>
<span class="cm">/**
 * native 方法，用于返回对象的哈希码，主要使用在哈希表中，比如 JDK 中的HashMap。
 */</span>
<span class="kd">public</span> <span class="kd">native</span> <span class="kt">int</span> <span class="nf">hashCode</span><span class="o">()</span>
<span class="cm">/**
 * 用于比较 2 个对象的内存地址是否相等，String 类对该方法进行了重写以用于比较字符串的值是否相等。
 */</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">obj</span><span class="o">)</span>
<span class="cm">/**
 * native 方法，用于创建并返回当前对象的一份拷贝。
 */</span>
<span class="kd">protected</span> <span class="kd">native</span> <span class="nc">Object</span> <span class="nf">clone</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">CloneNotSupportedException</span>
<span class="cm">/**
 * 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
 */</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">toString</span><span class="o">()</span>
<span class="cm">/**
 * native 方法，并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
 */</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">native</span> <span class="kt">void</span> <span class="nf">notify</span><span class="o">()</span>
<span class="cm">/**
 * native 方法，并且不能重写。跟 notify 一样，唯一的区别就是会唤醒在此对象监视器上等待的所有线程，而不是一个线程。
 */</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">native</span> <span class="kt">void</span> <span class="nf">notifyAll</span><span class="o">()</span>
<span class="cm">/**
 * native方法，并且不能重写。暂停线程的执行。注意：sleep 方法没有释放锁，而 wait 方法释放了锁 ，timeout 是等待时间。
 */</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kd">native</span> <span class="kt">void</span> <span class="nf">wait</span><span class="o">(</span><span class="kt">long</span> <span class="n">timeout</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InterruptedException</span>
<span class="cm">/**
 * 多了 nanos 参数，这个参数表示额外时间（以纳秒为单位，范围是 0-999999）。 所以超时的时间还需要加上 nanos 纳秒。。
 */</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">wait</span><span class="o">(</span><span class="kt">long</span> <span class="n">timeout</span><span class="o">,</span> <span class="kt">int</span> <span class="n">nanos</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InterruptedException</span>
<span class="cm">/**
 * 跟之前的2个wait方法一样，只不过该方法一直等待，没有超时时间这个概念
 */</span>
<span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">wait</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">InterruptedException</span>
<span class="cm">/**
 * 实例被垃圾回收器回收的时候触发的操作
 */</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">finalize</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Throwable</span> <span class="o">{</span> <span class="o">}</span>
</code></pre></div></div>

<h3 id="-和-equals-的区别">== 和 equals() 的区别</h3>

<p><strong><code class="language-plaintext highlighter-rouge">==</code></strong> 对于基本类型和引用类型的作用效果是不同的：</p>

<ul>
  <li>对于基本数据类型来说，<code class="language-plaintext highlighter-rouge">==</code> 比较的是值。</li>
  <li>对于引用数据类型来说，<code class="language-plaintext highlighter-rouge">==</code> 比较的是对象的内存地址。</li>
</ul>

<blockquote>
  <p>因为 Java 只有值传递，所以，对于 == 来说，不管是比较基本数据类型，还是引用数据类型的变量，其本质比较的都是值，只是引用类型变量存的值是对象的地址。</p>
</blockquote>

<p><strong><code class="language-plaintext highlighter-rouge">equals()</code></strong> 不能用于判断基本数据类型的变量，只能用来判断两个对象是否相等。<code class="language-plaintext highlighter-rouge">equals()</code>方法存在于<code class="language-plaintext highlighter-rouge">Object</code>类中，而<code class="language-plaintext highlighter-rouge">Object</code>类是所有类的直接或间接父类，因此所有的类都有<code class="language-plaintext highlighter-rouge">equals()</code>方法。</p>

<p><code class="language-plaintext highlighter-rouge">Object</code> 类 <code class="language-plaintext highlighter-rouge">equals()</code> 方法：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">obj</span><span class="o">)</span> <span class="o">{</span>
     <span class="k">return</span> <span class="o">(</span><span class="k">this</span> <span class="o">==</span> <span class="n">obj</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">equals()</code> 方法存在两种使用情况：</p>

<ul>
  <li><strong>类没有重写 <code class="language-plaintext highlighter-rouge">equals()</code>方法</strong>：通过<code class="language-plaintext highlighter-rouge">equals()</code>比较该类的两个对象时，等价于通过“==”比较这两个对象，使用的默认是 <code class="language-plaintext highlighter-rouge">Object</code>类<code class="language-plaintext highlighter-rouge">equals()</code>方法。</li>
  <li><strong>类重写了 <code class="language-plaintext highlighter-rouge">equals()</code>方法</strong>：一般我们都重写 <code class="language-plaintext highlighter-rouge">equals()</code>方法来比较两个对象中的属性是否相等；若它们的属性相等，则返回 true(即，认为这两个对象相等)。</li>
</ul>

<p>举个例子（这里只是为了举例。实际上，你按照下面这种写法的话，像 IDEA 这种比较智能的 IDE 都会提示你将 <code class="language-plaintext highlighter-rouge">==</code> 换成 <code class="language-plaintext highlighter-rouge">equals()</code> ）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">a</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"ab"</span><span class="o">);</span> <span class="c1">// a 为一个引用</span>
<span class="nc">String</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"ab"</span><span class="o">);</span> <span class="c1">// b为另一个引用,对象的内容一样</span>
<span class="nc">String</span> <span class="n">aa</span> <span class="o">=</span> <span class="s">"ab"</span><span class="o">;</span> <span class="c1">// 放在常量池中</span>
<span class="nc">String</span> <span class="n">bb</span> <span class="o">=</span> <span class="s">"ab"</span><span class="o">;</span> <span class="c1">// 从常量池中查找</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">aa</span> <span class="o">==</span> <span class="n">bb</span><span class="o">);</span><span class="c1">// true</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span> <span class="o">==</span> <span class="n">b</span><span class="o">);</span><span class="c1">// false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">a</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">b</span><span class="o">));</span><span class="c1">// true</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="mi">42</span> <span class="o">==</span> <span class="mf">42.0</span><span class="o">);</span><span class="c1">// true</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">String</code> 中的 <code class="language-plaintext highlighter-rouge">equals</code> 方法是被重写过的，因为 <code class="language-plaintext highlighter-rouge">Object</code> 的 <code class="language-plaintext highlighter-rouge">equals</code> 方法是比较的对象的内存地址，而 <code class="language-plaintext highlighter-rouge">String</code> 的 <code class="language-plaintext highlighter-rouge">equals</code> 方法比较的是对象的值。</p>

<p>当创建 <code class="language-plaintext highlighter-rouge">String</code> 类型的对象时，虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象，如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 <code class="language-plaintext highlighter-rouge">String</code> 对象。</p>

<p><code class="language-plaintext highlighter-rouge">String</code>类<code class="language-plaintext highlighter-rouge">equals()</code>方法：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">anObject</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="k">this</span> <span class="o">==</span> <span class="n">anObject</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">anObject</span> <span class="k">instanceof</span> <span class="nc">String</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="n">anotherString</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="n">anObject</span><span class="o">;</span>
        <span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">value</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">n</span> <span class="o">==</span> <span class="n">anotherString</span><span class="o">.</span><span class="na">value</span><span class="o">.</span><span class="na">length</span><span class="o">)</span> <span class="o">{</span>
            <span class="kt">char</span> <span class="n">v1</span><span class="o">[]</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>
            <span class="kt">char</span> <span class="n">v2</span><span class="o">[]</span> <span class="o">=</span> <span class="n">anotherString</span><span class="o">.</span><span class="na">value</span><span class="o">;</span>
            <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
            <span class="k">while</span> <span class="o">(</span><span class="n">n</span><span class="o">--</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">v1</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">!=</span> <span class="n">v2</span><span class="o">[</span><span class="n">i</span><span class="o">])</span>
                    <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
                <span class="n">i</span><span class="o">++;</span>
            <span class="o">}</span>
            <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="hashcode-有什么用">hashCode() 有什么用？</h3>

<p><code class="language-plaintext highlighter-rouge">hashCode()</code> 的作用是获取哈希码（<code class="language-plaintext highlighter-rouge">int</code> 整数），也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。</p>

<p><img src="/assets/images/JavaGuide/java-hashcode-method.png" alt="hashCode() 方法" /></p>

<p><code class="language-plaintext highlighter-rouge">hashCode()</code> 定义在 JDK 的 <code class="language-plaintext highlighter-rouge">Object</code> 类中，这就意味着 Java 中的任何类都包含有 <code class="language-plaintext highlighter-rouge">hashCode()</code> 函数。另外需要注意的是：<code class="language-plaintext highlighter-rouge">Object</code> 的 <code class="language-plaintext highlighter-rouge">hashCode()</code> 方法是本地方法，也就是用 C 语言或 C++ 实现的。</p>

<blockquote>
  <p>⚠️ 注意：该方法在 <strong>Oracle OpenJDK8</strong> 中默认是 “使用线程局部状态来实现 Marsaglia’s xor-shift 随机数生成”, 并不是 “地址” 或者 “地址转换而来”, 不同 JDK/VM 可能不同在 <strong>Oracle OpenJDK8</strong> 中有六种生成方式 (其中第五种是返回地址), 通过添加 VM 参数: -XX:hashCode=4 启用第五种。参考源码:</p>

  <ul>
    <li><a href="https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/globals.hpp">https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/globals.hpp</a>（1127 行）</li>
    <li><a href="https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/synchronizer.cpp">https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/synchronizer.cpp</a>（537 行开始）</li>
  </ul>
</blockquote>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">native</span> <span class="kt">int</span> <span class="nf">hashCode</span><span class="o">();</span>
</code></pre></div></div>

<p>散列表存储的是键值对(key-value)，它的特点是：<strong>能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码！（可以快速找到所需要的对象）</strong></p>

<h3 id="为什么要有-hashcode">为什么要有 hashCode？</h3>

<p>我们以“<code class="language-plaintext highlighter-rouge">HashSet</code> 如何检查重复”为例子来说明为什么要有 <code class="language-plaintext highlighter-rouge">hashCode</code>？</p>

<p>下面这段内容摘自我的 Java 启蒙书《Head First Java》:</p>

<blockquote>
  <p>当你把对象加入 <code class="language-plaintext highlighter-rouge">HashSet</code> 时，<code class="language-plaintext highlighter-rouge">HashSet</code> 会先计算对象的 <code class="language-plaintext highlighter-rouge">hashCode</code> 值来判断对象加入的位置，同时也会与其他已经加入的对象的 <code class="language-plaintext highlighter-rouge">hashCode</code> 值作比较，如果没有相符的 <code class="language-plaintext highlighter-rouge">hashCode</code>，<code class="language-plaintext highlighter-rouge">HashSet</code> 会假设对象没有重复出现。但是如果发现有相同 <code class="language-plaintext highlighter-rouge">hashCode</code> 值的对象，这时会调用 <code class="language-plaintext highlighter-rouge">equals()</code> 方法来检查 <code class="language-plaintext highlighter-rouge">hashCode</code> 相等的对象是否真的相同。如果两者相同，<code class="language-plaintext highlighter-rouge">HashSet</code> 就不会让其加入操作成功。如果不同的话，就会重新散列到其他位置。这样我们就大大减少了 <code class="language-plaintext highlighter-rouge">equals</code> 的次数，相应就大大提高了执行速度。</p>
</blockquote>

<p>其实， <code class="language-plaintext highlighter-rouge">hashCode()</code> 和 <code class="language-plaintext highlighter-rouge">equals()</code>都是用于比较两个对象是否相等。</p>

<p><strong>那为什么 JDK 还要同时提供这两个方法呢？</strong></p>

<p>这是因为在一些容器（比如 <code class="language-plaintext highlighter-rouge">HashMap</code>、<code class="language-plaintext highlighter-rouge">HashSet</code>）中，有了 <code class="language-plaintext highlighter-rouge">hashCode()</code> 之后，判断元素是否在对应容器中的效率会更高（参考添加元素进<code class="language-plaintext highlighter-rouge">HashSet</code>的过程）！</p>

<p>我们在前面也提到了添加元素进<code class="language-plaintext highlighter-rouge">HashSet</code>的过程，如果 <code class="language-plaintext highlighter-rouge">HashSet</code> 在对比的时候，同样的 <code class="language-plaintext highlighter-rouge">hashCode</code> 有多个对象，它会继续使用 <code class="language-plaintext highlighter-rouge">equals()</code> 来判断是否真的相同。也就是说 <code class="language-plaintext highlighter-rouge">hashCode</code> 帮助我们大大缩小了查找成本。</p>

<p><strong>那为什么不只提供 <code class="language-plaintext highlighter-rouge">hashCode()</code> 方法呢？</strong></p>

<p>这是因为两个对象的<code class="language-plaintext highlighter-rouge">hashCode</code> 值相等并不代表两个对象就相等。</p>

<p><strong>那为什么两个对象有相同的 <code class="language-plaintext highlighter-rouge">hashCode</code> 值，它们也不一定是相等的？</strong></p>

<p>因为 <code class="language-plaintext highlighter-rouge">hashCode()</code> 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞，但这也与数据值域分布的特性有关（所谓哈希碰撞也就是指的是不同的对象得到相同的 <code class="language-plaintext highlighter-rouge">hashCode</code> )。</p>

<p>总结下来就是：</p>

<ul>
  <li>如果两个对象的<code class="language-plaintext highlighter-rouge">hashCode</code> 值相等，那这两个对象不一定相等（哈希碰撞）。</li>
  <li>如果两个对象的<code class="language-plaintext highlighter-rouge">hashCode</code> 值相等并且<code class="language-plaintext highlighter-rouge">equals()</code>方法也返回 <code class="language-plaintext highlighter-rouge">true</code>，我们才认为这两个对象相等。</li>
  <li>如果两个对象的<code class="language-plaintext highlighter-rouge">hashCode</code> 值不相等，我们就可以直接认为这两个对象不相等。</li>
</ul>

<p>相信大家看了我前面对 <code class="language-plaintext highlighter-rouge">hashCode()</code> 和 <code class="language-plaintext highlighter-rouge">equals()</code> 的介绍之后，下面这个问题已经难不倒你们了。</p>

<h3 id="为什么重写-equals-时必须重写-hashcode-方法">为什么重写 equals() 时必须重写 hashCode() 方法？</h3>

<p>因为两个相等的对象的 <code class="language-plaintext highlighter-rouge">hashCode</code> 值必须是相等。也就是说如果 <code class="language-plaintext highlighter-rouge">equals</code> 方法判断两个对象是相等的，那这两个对象的 <code class="language-plaintext highlighter-rouge">hashCode</code> 值也要相等。</p>

<p>如果重写 <code class="language-plaintext highlighter-rouge">equals()</code> 时没有重写 <code class="language-plaintext highlighter-rouge">hashCode()</code> 方法的话就可能会导致 <code class="language-plaintext highlighter-rouge">equals</code> 方法判断是相等的两个对象，<code class="language-plaintext highlighter-rouge">hashCode</code> 值却不相等。</p>

<p><strong>思考</strong>：重写 <code class="language-plaintext highlighter-rouge">equals()</code> 时没有重写 <code class="language-plaintext highlighter-rouge">hashCode()</code> 方法的话，使用 <code class="language-plaintext highlighter-rouge">HashMap</code> 可能会出现什么问题。</p>

<p><strong>总结</strong>：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">equals</code> 方法判断两个对象是相等的，那这两个对象的 <code class="language-plaintext highlighter-rouge">hashCode</code> 值也要相等。</li>
  <li>两个对象有相同的 <code class="language-plaintext highlighter-rouge">hashCode</code> 值，他们也不一定是相等的（哈希碰撞）。</li>
</ul>

<p>更多关于 <code class="language-plaintext highlighter-rouge">hashCode()</code> 和 <code class="language-plaintext highlighter-rouge">equals()</code> 的内容可以查看：<a href="https://www.cnblogs.com/skywang12345/p/3324958.html">Java hashCode() 和 equals()的若干问题解答</a></p>

<h2 id="string">String</h2>

<h3 id="stringstringbufferstringbuilder-的区别">String、StringBuffer、StringBuilder 的区别？</h3>

<p><strong>可变性</strong></p>

<p><code class="language-plaintext highlighter-rouge">String</code> 是不可变的（后面会详细分析原因）。</p>

<p><code class="language-plaintext highlighter-rouge">StringBuilder</code> 与 <code class="language-plaintext highlighter-rouge">StringBuffer</code> 都继承自 <code class="language-plaintext highlighter-rouge">AbstractStringBuilder</code> 类，在 <code class="language-plaintext highlighter-rouge">AbstractStringBuilder</code> 中也是使用字符数组保存字符串，不过没有使用 <code class="language-plaintext highlighter-rouge">final</code> 和 <code class="language-plaintext highlighter-rouge">private</code> 关键字修饰，最关键的是这个 <code class="language-plaintext highlighter-rouge">AbstractStringBuilder</code> 类还提供了很多修改字符串的方法比如 <code class="language-plaintext highlighter-rouge">append</code> 方法。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractStringBuilder</span> <span class="kd">implements</span> <span class="nc">Appendable</span><span class="o">,</span> <span class="nc">CharSequence</span> <span class="o">{</span>
    <span class="kt">char</span><span class="o">[]</span> <span class="n">value</span><span class="o">;</span>
    <span class="kd">public</span> <span class="nc">AbstractStringBuilder</span> <span class="nf">append</span><span class="o">(</span><span class="nc">String</span> <span class="n">str</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">str</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span>
            <span class="k">return</span> <span class="nf">appendNull</span><span class="o">();</span>
        <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">str</span><span class="o">.</span><span class="na">length</span><span class="o">();</span>
        <span class="n">ensureCapacityInternal</span><span class="o">(</span><span class="n">count</span> <span class="o">+</span> <span class="n">len</span><span class="o">);</span>
        <span class="n">str</span><span class="o">.</span><span class="na">getChars</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">len</span><span class="o">,</span> <span class="n">value</span><span class="o">,</span> <span class="n">count</span><span class="o">);</span>
        <span class="n">count</span> <span class="o">+=</span> <span class="n">len</span><span class="o">;</span>
        <span class="k">return</span> <span class="k">this</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="c1">//...</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>线程安全性</strong></p>

<p><code class="language-plaintext highlighter-rouge">String</code> 中的对象是不可变的，也就可以理解为常量，线程安全。<code class="language-plaintext highlighter-rouge">AbstractStringBuilder</code> 是 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 与 <code class="language-plaintext highlighter-rouge">StringBuffer</code> 的公共父类，定义了一些字符串的基本操作，如 <code class="language-plaintext highlighter-rouge">expandCapacity</code>、<code class="language-plaintext highlighter-rouge">append</code>、<code class="language-plaintext highlighter-rouge">insert</code>、<code class="language-plaintext highlighter-rouge">indexOf</code> 等公共方法。<code class="language-plaintext highlighter-rouge">StringBuffer</code> 对方法加了同步锁或者对调用的方法加了同步锁，所以是线程安全的。<code class="language-plaintext highlighter-rouge">StringBuilder</code> 并没有对方法进行加同步锁，所以是非线程安全的。</p>

<p><strong>性能</strong></p>

<p>每次对 <code class="language-plaintext highlighter-rouge">String</code> 类型进行改变的时候，都会生成一个新的 <code class="language-plaintext highlighter-rouge">String</code> 对象，然后将指针指向新的 <code class="language-plaintext highlighter-rouge">String</code> 对象。<code class="language-plaintext highlighter-rouge">StringBuffer</code> 每次都会对 <code class="language-plaintext highlighter-rouge">StringBuffer</code> 对象本身进行操作，而不是生成新的对象并改变对象引用。相同情况下使用 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 相比使用 <code class="language-plaintext highlighter-rouge">StringBuffer</code> 仅能获得 10%~15% 左右的性能提升，但却要冒多线程不安全的风险。</p>

<p><strong>对于三者使用的总结：</strong></p>

<ul>
  <li>操作少量的数据: 适用 <code class="language-plaintext highlighter-rouge">String</code></li>
  <li>单线程操作字符串缓冲区下操作大量数据: 适用 <code class="language-plaintext highlighter-rouge">StringBuilder</code></li>
  <li>多线程操作字符串缓冲区下操作大量数据: 适用 <code class="language-plaintext highlighter-rouge">StringBuffer</code></li>
</ul>

<h3 id="string-为什么是不可变的">String 为什么是不可变的?</h3>

<p><code class="language-plaintext highlighter-rouge">String</code> 类中使用 <code class="language-plaintext highlighter-rouge">final</code> 关键字修饰字符数组来保存字符串，<del>所以<code class="language-plaintext highlighter-rouge">String</code> 对象是不可变的。</del></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">String</span> <span class="kd">implements</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">Serializable</span><span class="o">,</span> <span class="nc">Comparable</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;,</span> <span class="nc">CharSequence</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="kt">char</span> <span class="n">value</span><span class="o">[];</span>
  <span class="c1">//...</span>
<span class="o">}</span>
</code></pre></div></div>

<blockquote>
  <p>🐛 修正：我们知道被 <code class="language-plaintext highlighter-rouge">final</code> 关键字修饰的类不能被继承，修饰的方法不能被重写，修饰的变量是基本数据类型则值不能改变，修饰的变量是引用类型则不能再指向其他对象。因此，<code class="language-plaintext highlighter-rouge">final</code> 关键字修饰的数组保存字符串并不是 <code class="language-plaintext highlighter-rouge">String</code> 不可变的根本原因，因为这个数组保存的字符串是可变的（<code class="language-plaintext highlighter-rouge">final</code> 修饰引用类型变量的情况）。</p>

  <p><code class="language-plaintext highlighter-rouge">String</code> 真正不可变有下面几点原因：</p>

  <ol>
    <li>保存字符串的数组被 <code class="language-plaintext highlighter-rouge">final</code> 修饰且为私有的，并且<code class="language-plaintext highlighter-rouge">String</code> 类没有提供/暴露修改这个字符串的方法。</li>
    <li><code class="language-plaintext highlighter-rouge">String</code> 类被 <code class="language-plaintext highlighter-rouge">final</code> 修饰导致其不能被继承，进而避免了子类破坏 <code class="language-plaintext highlighter-rouge">String</code> 不可变。</li>
  </ol>

  <p>相关阅读：<a href="https://www.zhihu.com/question/20618891/answer/114125846">如何理解 String 类型值的不可变？ - 知乎提问</a></p>

  <p>补充（来自<a href="https://github.com/Snailclimb/JavaGuide/issues/675">issue 675</a>）：在 Java 9 之后，<code class="language-plaintext highlighter-rouge">String</code>、<code class="language-plaintext highlighter-rouge">StringBuilder</code> 与 <code class="language-plaintext highlighter-rouge">StringBuffer</code> 的实现改用 <code class="language-plaintext highlighter-rouge">byte</code> 数组存储字符串。</p>

  <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">String</span> <span class="kd">implements</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">Serializable</span><span class="o">,</span><span class="nc">Comparable</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;,</span> <span class="nc">CharSequence</span> <span class="o">{</span>
    <span class="c1">// @Stable 注解表示变量最多被修改一次，称为“稳定的”。</span>
    <span class="nd">@Stable</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>

<span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractStringBuilder</span> <span class="kd">implements</span> <span class="nc">Appendable</span><span class="o">,</span> <span class="nc">CharSequence</span> <span class="o">{</span>
    <span class="kt">byte</span><span class="o">[]</span> <span class="n">value</span><span class="o">;</span>

<span class="o">}</span>
</code></pre></div>  </div>

  <p><strong>Java 9 为何要将 <code class="language-plaintext highlighter-rouge">String</code> 的底层实现由 <code class="language-plaintext highlighter-rouge">char[]</code> 改成了 <code class="language-plaintext highlighter-rouge">byte[]</code> ?</strong></p>

  <p>新版的 String 其实支持两个编码方案：Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符，那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下，<code class="language-plaintext highlighter-rouge">byte</code> 占一个字节(8 位)，<code class="language-plaintext highlighter-rouge">char</code> 占用 2 个字节（16），<code class="language-plaintext highlighter-rouge">byte</code> 相较 <code class="language-plaintext highlighter-rouge">char</code> 节省一半的内存空间。</p>

  <p>JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。</p>

  <p><img src="/assets/images/JavaGuide/jdk9-string-latin1.png" alt="" /></p>

  <p>如果字符串中包含的汉字超过 Latin-1 可表示范围内的字符，<code class="language-plaintext highlighter-rouge">byte</code> 和 <code class="language-plaintext highlighter-rouge">char</code> 所占用的空间是一样的。</p>

  <p>这是官方的介绍：<a href="https://openjdk.java.net/jeps/254">https://openjdk.java.net/jeps/254</a> 。</p>
</blockquote>

<h3 id="字符串拼接用-还是-stringbuilder">字符串拼接用“+” 还是 StringBuilder?</h3>

<p>Java 语言本身并不支持运算符重载，“+”和“+=”是专门为 String 类重载过的运算符，也是 Java 中仅有的两个重载过的运算符。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">str1</span> <span class="o">=</span> <span class="s">"he"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="s">"llo"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">str3</span> <span class="o">=</span> <span class="s">"world"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">str4</span> <span class="o">=</span> <span class="n">str1</span> <span class="o">+</span> <span class="n">str2</span> <span class="o">+</span> <span class="n">str3</span><span class="o">;</span>
</code></pre></div></div>

<p>上面的代码对应的字节码如下：</p>

<p><img src="/assets/images/JavaGuide/image-20220422161637929.png" alt="" /></p>

<p>可以看出，字符串对象通过“+”的字符串拼接方式，实际上是通过 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 调用 <code class="language-plaintext highlighter-rouge">append()</code> 方法实现的，拼接完成之后调用 <code class="language-plaintext highlighter-rouge">toString()</code> 得到一个 <code class="language-plaintext highlighter-rouge">String</code> 对象 。</p>

<p>不过，在循环内使用“+”进行字符串的拼接的话，存在比较明显的缺陷：<strong>编译器不会创建单个 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 以复用，会导致创建过多的 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 对象</strong>。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span><span class="o">[]</span> <span class="n">arr</span> <span class="o">=</span> <span class="o">{</span><span class="s">"he"</span><span class="o">,</span> <span class="s">"llo"</span><span class="o">,</span> <span class="s">"world"</span><span class="o">};</span>
<span class="nc">String</span> <span class="n">s</span> <span class="o">=</span> <span class="s">""</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">arr</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
    <span class="n">s</span> <span class="o">+=</span> <span class="n">arr</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>
<span class="o">}</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">StringBuilder</code> 对象是在循环内部被创建的，这意味着每循环一次就会创建一个 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 对象。</p>

<p><img src="/assets/images/JavaGuide/image-20220422161320823.png" alt="" /></p>

<p>如果直接使用 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 对象进行字符串拼接的话，就不会存在这个问题了。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span><span class="o">[]</span> <span class="n">arr</span> <span class="o">=</span> <span class="o">{</span><span class="s">"he"</span><span class="o">,</span> <span class="s">"llo"</span><span class="o">,</span> <span class="s">"world"</span><span class="o">};</span>
<span class="nc">StringBuilder</span> <span class="n">s</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuilder</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">value</span> <span class="o">:</span> <span class="n">arr</span><span class="o">)</span> <span class="o">{</span>
    <span class="n">s</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
</code></pre></div></div>

<p><img src="/assets/images/JavaGuide/image-20220422162327415.png" alt="" /></p>

<p>如果你使用 IDEA 的话，IDEA 自带的代码检查机制也会提示你修改代码。</p>

<p>不过，使用 “+” 进行字符串拼接会产生大量的临时对象的问题在 JDK9 中得到了解决。在 JDK9 当中，字符串相加 “+” 改为了用动态方法 <code class="language-plaintext highlighter-rouge">makeConcatWithConstants()</code> 来实现，而不是大量的 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 了。这个改进是 JDK9 的 <a href="https://openjdk.org/jeps/280">JEP 280</a> 提出的，这也意味着 JDK 9 之后，你可以放心使用“+” 进行字符串拼接了。关于这部分改进的详细介绍，推荐阅读这篇文章：还在无脑用 <a href="https://juejin.cn/post/7182872058743750715">StringBuilder？来重温一下字符串拼接吧</a> 。</p>

<h3 id="stringequals-和-objectequals-有何区别">String#equals() 和 Object#equals() 有何区别？</h3>

<p><code class="language-plaintext highlighter-rouge">String</code> 中的 <code class="language-plaintext highlighter-rouge">equals</code> 方法是被重写过的，比较的是 String 字符串的值是否相等。 <code class="language-plaintext highlighter-rouge">Object</code> 的 <code class="language-plaintext highlighter-rouge">equals</code> 方法是比较的对象的内存地址。</p>

<h3 id="字符串常量池的作用了解吗">字符串常量池的作用了解吗？</h3>

<p><strong>字符串常量池</strong> 是 JVM 为了提升性能和减少内存消耗针对字符串（String 类）专门开辟的一块区域，主要目的是为了避免字符串的重复创建。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 在堆中创建字符串对象”ab“</span>
<span class="c1">// 将字符串对象”ab“的引用保存在字符串常量池中</span>
<span class="nc">String</span> <span class="n">aa</span> <span class="o">=</span> <span class="s">"ab"</span><span class="o">;</span>
<span class="c1">// 直接返回字符串常量池中字符串对象”ab“的引用</span>
<span class="nc">String</span> <span class="n">bb</span> <span class="o">=</span> <span class="s">"ab"</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">aa</span><span class="o">==</span><span class="n">bb</span><span class="o">);</span><span class="c1">// true</span>
</code></pre></div></div>

<p>更多关于字符串常量池的介绍可以看一下 <a href="https://javaguide.cn/java/jvm/memory-area.html">Java 内存区域详解</a> 这篇文章。</p>

<h3 id="string-s1--new-stringabc这句话创建了几个字符串对象">String s1 = new String(“abc”);这句话创建了几个字符串对象？</h3>

<p>会创建 1 或 2 个字符串对象。</p>

<p>1、如果字符串常量池中不存在字符串对象“abc”的引用，那么它会在堆上创建两个字符串对象，其中一个字符串对象的引用会被保存在字符串常量池中。</p>

<p>示例代码（JDK 1.8）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">s1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"abc"</span><span class="o">);</span>
</code></pre></div></div>

<p>对应的字节码：</p>

<p><img src="/assets/images/JavaGuide/image-20220413175809959.png" alt="" /></p>

<p><code class="language-plaintext highlighter-rouge">ldc</code> 命令用于判断字符串常量池中是否保存了对应的字符串对象的引用，如果保存了的话直接返回，如果没有保存的话，会在堆中创建对应的字符串对象并将该字符串对象的引用保存到字符串常量池中。</p>

<p>2、如果字符串常量池中已存在字符串对象“abc”的引用，则只会在堆中创建 1 个字符串对象“abc”。</p>

<p>示例代码（JDK 1.8）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 字符串常量池中已存在字符串对象“abc”的引用</span>
<span class="nc">String</span> <span class="n">s1</span> <span class="o">=</span> <span class="s">"abc"</span><span class="o">;</span>
<span class="c1">// 下面这段代码只会在堆中创建 1 个字符串对象“abc”</span>
<span class="nc">String</span> <span class="n">s2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"abc"</span><span class="o">);</span>
</code></pre></div></div>

<p>对应的字节码：</p>

<p><img src="/assets/images/JavaGuide/image-20220413180021072.png" alt="" /></p>

<p>这里就不对上面的字节码进行详细注释了，7 这个位置的 <code class="language-plaintext highlighter-rouge">ldc</code> 命令不会在堆中创建新的字符串对象“abc”，这是因为 0 这个位置已经执行了一次 <code class="language-plaintext highlighter-rouge">ldc</code> 命令，已经在堆中创建过一次字符串对象“abc”了。7 这个位置执行 <code class="language-plaintext highlighter-rouge">ldc</code> 命令会直接返回字符串常量池中字符串对象“abc”对应的引用。</p>

<h3 id="stringintern-方法有什么作用">String#intern 方法有什么作用?</h3>

<p><code class="language-plaintext highlighter-rouge">String.intern()</code> 是一个 native（本地）方法，其作用是将指定的字符串对象的引用保存在字符串常量池中，可以简单分为两种情况：</p>

<ul>
  <li>如果字符串常量池中保存了对应的字符串对象的引用，就直接返回该引用。</li>
  <li>如果字符串常量池中没有保存了对应的字符串对象的引用，那就在常量池中创建一个指向该字符串对象的引用并返回。</li>
</ul>

<p>示例代码（JDK 1.8） :</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 在堆中创建字符串对象”Java“</span>
<span class="c1">// 将字符串对象”Java“的引用保存在字符串常量池中</span>
<span class="nc">String</span> <span class="n">s1</span> <span class="o">=</span> <span class="s">"Java"</span><span class="o">;</span>
<span class="c1">// 直接返回字符串常量池中字符串对象”Java“对应的引用</span>
<span class="nc">String</span> <span class="n">s2</span> <span class="o">=</span> <span class="n">s1</span><span class="o">.</span><span class="na">intern</span><span class="o">();</span>
<span class="c1">// 会在堆中在单独创建一个字符串对象</span>
<span class="nc">String</span> <span class="n">s3</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">(</span><span class="s">"Java"</span><span class="o">);</span>
<span class="c1">// 直接返回字符串常量池中字符串对象”Java“对应的引用</span>
<span class="nc">String</span> <span class="n">s4</span> <span class="o">=</span> <span class="n">s3</span><span class="o">.</span><span class="na">intern</span><span class="o">();</span>
<span class="c1">// s1 和 s2 指向的是堆中的同一个对象</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s1</span> <span class="o">==</span> <span class="n">s2</span><span class="o">);</span> <span class="c1">// true</span>
<span class="c1">// s3 和 s4 指向的是堆中不同的对象</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s3</span> <span class="o">==</span> <span class="n">s4</span><span class="o">);</span> <span class="c1">// false</span>
<span class="c1">// s1 和 s4 指向的是堆中的同一个对象</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s1</span> <span class="o">==</span> <span class="n">s4</span><span class="o">);</span> <span class="c1">//true</span>
</code></pre></div></div>

<h3 id="string-类型的变量和常量做运算时发生了什么">String 类型的变量和常量做“+”运算时发生了什么？</h3>

<p>先来看字符串不加 <code class="language-plaintext highlighter-rouge">final</code> 关键字拼接的情况（JDK1.8）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">str1</span> <span class="o">=</span> <span class="s">"str"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="s">"ing"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">str3</span> <span class="o">=</span> <span class="s">"str"</span> <span class="o">+</span> <span class="s">"ing"</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">str4</span> <span class="o">=</span> <span class="n">str1</span> <span class="o">+</span> <span class="n">str2</span><span class="o">;</span>
<span class="nc">String</span> <span class="n">str5</span> <span class="o">=</span> <span class="s">"string"</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str3</span> <span class="o">==</span> <span class="n">str4</span><span class="o">);</span><span class="c1">//false</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str3</span> <span class="o">==</span> <span class="n">str5</span><span class="o">);</span><span class="c1">//true</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">str4</span> <span class="o">==</span> <span class="n">str5</span><span class="o">);</span><span class="c1">//false</span>
</code></pre></div></div>

<blockquote>
  <p><strong>注意</strong>：比较 String 字符串的值是否相等，可以使用 <code class="language-plaintext highlighter-rouge">equals()</code> 方法。 <code class="language-plaintext highlighter-rouge">String</code> 中的 <code class="language-plaintext highlighter-rouge">equals</code> 方法是被重写过的。 <code class="language-plaintext highlighter-rouge">Object</code> 的 <code class="language-plaintext highlighter-rouge">equals</code> 方法是比较的对象的内存地址，而 <code class="language-plaintext highlighter-rouge">String</code> 的 <code class="language-plaintext highlighter-rouge">equals</code> 方法比较的是字符串的值是否相等。如果你使用 <code class="language-plaintext highlighter-rouge">==</code> 比较两个字符串是否相等的话，IDEA 还是提示你使用 <code class="language-plaintext highlighter-rouge">equals()</code> 方法替换。</p>
</blockquote>

<p><img src="/assets/images/JavaGuide/image-20210817123252441.png" alt="" /></p>

<p><strong>对于编译期可以确定值的字符串，也就是常量字符串 ，jvm 会将其存入字符串常量池。并且，字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池，这个得益于编译器的优化。</strong></p>

<p>在编译过程中，Javac 编译器（下文中统称为编译器）会进行一个叫做 <strong>常量折叠(Constant Folding)</strong> 的代码优化。《深入理解 Java 虚拟机》中是也有介绍到：</p>

<p><img src="/assets/images/JavaGuide/image-20210817142715396.png" alt="" /></p>

<p>常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中，这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)。</p>

<p>对于 <code class="language-plaintext highlighter-rouge">String str3 = "str" + "ing";</code> 编译器会给你优化成 <code class="language-plaintext highlighter-rouge">String str3 = "string";</code> 。</p>

<p>并不是所有的常量都会进行折叠，只有编译器在程序编译期就可以确定值的常量才可以：</p>

<ul>
  <li>基本数据类型( <code class="language-plaintext highlighter-rouge">byte</code>、<code class="language-plaintext highlighter-rouge">boolean</code>、<code class="language-plaintext highlighter-rouge">short</code>、<code class="language-plaintext highlighter-rouge">char</code>、<code class="language-plaintext highlighter-rouge">int</code>、<code class="language-plaintext highlighter-rouge">float</code>、<code class="language-plaintext highlighter-rouge">long</code>、<code class="language-plaintext highlighter-rouge">double</code>)以及字符串常量。</li>
  <li><code class="language-plaintext highlighter-rouge">final</code> 修饰的基本数据类型和字符串变量</li>
  <li>字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算（加减乘除）、基本数据类型的位运算（«、&gt;&gt;、&gt;&gt;&gt; ）</li>
</ul>

<p><strong>引用的值在程序编译期是无法确定的，编译器无法对其进行优化。</strong></p>

<p>对象引用和“+”的字符串拼接方式，实际上是通过 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 调用 <code class="language-plaintext highlighter-rouge">append()</code> 方法实现的，拼接完成之后调用 <code class="language-plaintext highlighter-rouge">toString()</code> 得到一个 <code class="language-plaintext highlighter-rouge">String</code> 对象 。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">str4</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StringBuilder</span><span class="o">().</span><span class="na">append</span><span class="o">(</span><span class="n">str1</span><span class="o">).</span><span class="na">append</span><span class="o">(</span><span class="n">str2</span><span class="o">).</span><span class="na">toString</span><span class="o">();</span>
</code></pre></div></div>

<p>我们在平时写代码的时候，尽量避免多个字符串对象拼接，因为这样会重新创建对象。如果需要改变字符串的话，可以使用 <code class="language-plaintext highlighter-rouge">StringBuilder</code> 或者 <code class="language-plaintext highlighter-rouge">StringBuffer</code>。</p>

<p>不过，字符串使用 <code class="language-plaintext highlighter-rouge">final</code> 关键字声明之后，可以让编译器当做常量来处理。</p>

<p>示例代码：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="nc">String</span> <span class="n">str1</span> <span class="o">=</span> <span class="s">"str"</span><span class="o">;</span>
<span class="kd">final</span> <span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="s">"ing"</span><span class="o">;</span>
<span class="c1">// 下面两个表达式其实是等价的</span>
<span class="nc">String</span> <span class="n">c</span> <span class="o">=</span> <span class="s">"str"</span> <span class="o">+</span> <span class="s">"ing"</span><span class="o">;</span><span class="c1">// 常量池中的对象</span>
<span class="nc">String</span> <span class="n">d</span> <span class="o">=</span> <span class="n">str1</span> <span class="o">+</span> <span class="n">str2</span><span class="o">;</span> <span class="c1">// 常量池中的对象</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">c</span> <span class="o">==</span> <span class="n">d</span><span class="o">);</span><span class="c1">// true</span>
</code></pre></div></div>

<p>被 <code class="language-plaintext highlighter-rouge">final</code> 关键字修饰之后的 <code class="language-plaintext highlighter-rouge">String</code> 会被编译器当做常量来处理，编译器在程序编译期就可以确定它的值，其效果就相当于访问常量。</p>

<p>如果 ，编译器在运行时才能知道其确切值的话，就无法对其优化。</p>

<p>示例代码（<code class="language-plaintext highlighter-rouge">str2</code> 在运行时才能确定其值）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="nc">String</span> <span class="n">str1</span> <span class="o">=</span> <span class="s">"str"</span><span class="o">;</span>
<span class="kd">final</span> <span class="nc">String</span> <span class="n">str2</span> <span class="o">=</span> <span class="n">getStr</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">c</span> <span class="o">=</span> <span class="s">"str"</span> <span class="o">+</span> <span class="s">"ing"</span><span class="o">;</span><span class="c1">// 常量池中的对象</span>
<span class="nc">String</span> <span class="n">d</span> <span class="o">=</span> <span class="n">str1</span> <span class="o">+</span> <span class="n">str2</span><span class="o">;</span> <span class="c1">// 在堆上创建的新的对象</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">c</span> <span class="o">==</span> <span class="n">d</span><span class="o">);</span><span class="c1">// false</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getStr</span><span class="o">()</span> <span class="o">{</span>
      <span class="k">return</span> <span class="s">"ing"</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="参考">参考</h2>

<ul>
  <li>深入解析 String#intern：<a href="https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html">https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html</a></li>
  <li>R 大（RednaxelaFX）关于常量折叠的回答：<a href="https://www.zhihu.com/question/55976094/answer/147302764">https://www.zhihu.com/question/55976094/answer/147302764</a></li>
</ul>

<!-- @include: @article-footer.snippet.md -->]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">chapter5.0 - 数据链路层</title><link href="https://yyym-y.github.io/2024/04/19/ComputerNetwork_note5/" rel="alternate" type="text/html" title="chapter5.0 - 数据链路层" /><published>2024-04-19T00:00:00+00:00</published><updated>2024-04-19T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/04/19/ComputerNetwork_note5</id><content type="html" xml:base="https://yyym-y.github.io/2024/04/19/ComputerNetwork_note5/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#概述" id="markdown-toc-概述">概述</a></li>
  <li><a href="#三个基本问题" id="markdown-toc-三个基本问题">三个基本问题</a>    <ul>
      <li><a href="#封装成帧" id="markdown-toc-封装成帧">封装成帧</a></li>
      <li><a href="#透明传输" id="markdown-toc-透明传输">透明传输</a></li>
      <li><a href="#差错检验" id="markdown-toc-差错检验">差错检验</a></li>
    </ul>
  </li>
  <li><a href="#点对点协议-ppp" id="markdown-toc-点对点协议-ppp">点对点协议 PPP</a>    <ul>
      <li><a href="#ppp协议帧各部分的组成" id="markdown-toc-ppp协议帧各部分的组成">PPP协议帧各部分的组成</a></li>
      <li><a href="#ppp-协议的透明传输" id="markdown-toc-ppp-协议的透明传输">PPP 协议的透明传输</a></li>
      <li><a href="#ppp-协议的工作状态" id="markdown-toc-ppp-协议的工作状态">PPP 协议的工作状态</a></li>
    </ul>
  </li>
  <li><a href="#使用广播信道的数据链路层" id="markdown-toc-使用广播信道的数据链路层">使用广播信道的数据链路层</a>    <ul>
      <li><a href="#动态接入控制" id="markdown-toc-动态接入控制">动态接入控制</a>        <ul>
          <li><a href="#aloha-协议" id="markdown-toc-aloha-协议">ALOHA 协议</a></li>
          <li><a href="#csma-协议" id="markdown-toc-csma-协议">CSMA 协议</a></li>
          <li><a href="#csmacd-协议" id="markdown-toc-csmacd-协议">CSMA/CD 协议</a></li>
          <li><a href="#csmaca-协议" id="markdown-toc-csmaca-协议">CSMA/CA 协议</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#以太网的-mac-层" id="markdown-toc-以太网的-mac-层">以太网的 MAC 层</a></li>
  <li><a href="#使用集线器的共享式以太网" id="markdown-toc-使用集线器的共享式以太网">使用集线器的共享式以太网</a></li>
  <li><a href="#在物理层拓展以太网" id="markdown-toc-在物理层拓展以太网">在物理层拓展以太网</a></li>
  <li><a href="#在数据链路层拓展以太网" id="markdown-toc-在数据链路层拓展以太网">在数据链路层拓展以太网</a>    <ul>
      <li><a href="#使用网桥拓展以太网" id="markdown-toc-使用网桥拓展以太网">使用网桥拓展以太网</a></li>
    </ul>
  </li>
  <li><a href="#交换式以太网" id="markdown-toc-交换式以太网">交换式以太网</a>    <ul>
      <li><a href="#以太网交换机" id="markdown-toc-以太网交换机">以太网交换机</a></li>
    </ul>
  </li>
  <li><a href="#虚拟局域网-vlan" id="markdown-toc-虚拟局域网-vlan">虚拟局域网 VLAN</a></li>
</ul>

<h2 id="概述">概述</h2>

<blockquote>
  <p><strong>数据链路层存在的原因以及理由</strong></p>
</blockquote>

<p>我们知道 TCP/IP 协议所在的网络层和传输层可以确保应用层发送的信息是可以可靠的传输到另一台主机中的, TCP/IP 协议中主要是解决传送的数据报乱序以及丢失所产生的问题, 但并不会怀疑我传来的数据报到底是不是有错误的</p>

<p>而物理层只是规定了比特 $1$ 和 比特 $0$ 到底是怎么传输的, 即我们应该怎么将我们的一串二进制数据转化为电磁信号或者光信号发出去并且如何接受比特.</p>

<p>你会发现少了一环 : 在物理层传输的数据实际上是可能出错的, 如果不经过处理直接交给网络层就会导致数据出错, 这正是我们数据链路层要解决的核心问题: <strong>如何确保传输过来的 $01$ 串是正确的</strong></p>

<p>在这一章中, 我们循序渐进的讲解整个数据链路层是如何工作并且拓展的</p>

<blockquote>
  <p><strong>数据链路层的地位</strong></p>
</blockquote>

<p>因为数据链路层可以保障传输的 $01$ 串内容是正确的, 所以所有交换装置都必须实现数据链路层</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-0.png" alt="chapter5-0" style="zoom:80%;" /></p>

<p>主机 $H1$ 到主机 $H2$ 所经过的网络可以是多种不同类型的</p>

<p><strong>注意：不同的链路层可能采用不同的数据链路层协议</strong></p>

<blockquote>
  <p><strong>局域网属于数据链路层</strong></p>
</blockquote>

<p>局域网虽然是个网络。但我们并不把局域网放在网络层中讨论。这是因为在网络层要讨论的是多个网络互连的问题，是讨论分组怎么从一个网络，通过路由器，转发到另一个网络。</p>

<p>而在同一个局域网中，分组怎么从一台主机传送到另一台主机，但并不经过路由器转发。从整个互联网来看，局域网仍属于数据链路层的范围</p>

<blockquote>
  <p><strong>数据链路层的几个共同问题</strong></p>
</blockquote>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">链路</code> 和 <code class="language-plaintext highlighter-rouge">数据链路</code> 概念不同</strong></li>
</ul>

<p><strong>链路</strong>：一个节点到相邻节点的一段物理路线，而中间任何其他的交换节点</p>

<blockquote>
  <p>在数据通信的时候，两台计算机之间的通信路径往往要经过许多段这样的链路，可见链路只是一条路径的组成部分</p>
</blockquote>

<p><strong>数据链路 (data link)</strong>：<strong>除了物理线路外，还必须有通信协议来控制这些数据的传输</strong>。若把实现这些协议的硬件和软件加到链路上，就构成了数据链路</p>

<blockquote>
  <p>现在最常用的方法是使用适配器（即网卡）来实现这些协议的硬件和软件</p>

  <p><strong>一般的适配器都包括了数据链路层和物理层这两层的功能</strong></p>
</blockquote>

<p>也有人采用另外的术语。这就是把<strong>链路分为物理链路和逻辑链路</strong></p>

<blockquote>
  <p>物理链路就是上面所说的链路</p>

  <p>逻辑链路就是上面的数据链路，是物理链路加上必要的通信协议</p>
</blockquote>

<ul>
  <li><strong>数据链路层的协议数据单元: 帧</strong></li>
</ul>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-1.png" alt="chapter5-1" style="zoom:80%;" /></p>

<h2 id="三个基本问题">三个基本问题</h2>

<p>三个基本问题分别是 <strong>封装成帧</strong> , <strong>透明传输</strong> , <strong>差错检验</strong></p>

<h3 id="封装成帧">封装成帧</h3>

<p>封装成帧是指数据链路层给上层交付的协议数据单元添加帧头和帧尾使之成为帧</p>

<p>一个帧的帧长 = 这个帧的数据部分长度 + 帧头的长度 + 帧尾的长度</p>

<p>每一个数据链路层都会规定帧的数据部分的最大长度, 也叫 <strong>最大传输单元MTU(max transfer Unit)</strong></p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-2.png" alt="chapter5-2" style="zoom:80%;" /></p>

<p>我们会用 <strong>帧定界符</strong> 来确定确定一个帧的开始以及结尾, 帧定界符一般是不可打印的</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-3.png" alt="chapter5-3" style="zoom:80%;" /></p>

<p><code class="language-plaintext highlighter-rouge">SOH</code> 用十六进制表示为 $\mathrm{0x01}$ , <code class="language-plaintext highlighter-rouge">EOT</code> 用十六进制表示为 $\mathrm{0x04}$</p>

<p>数据链路层只有完整的收到帧开始符合帧结束符才认为这个帧接收完毕, 如果遇到其他情况将直接丢弃</p>

<h3 id="透明传输">透明传输</h3>

<p>透明传输是指<strong>数据链路层对上层交付的传输数据没有任何限制</strong>，好像数据链路层不存在一样</p>

<p>在这个部分, 我们主要解决帧定界符在帧的数据部分出现的问题, 如果数据部分出现帧定界符, 我们会在前面插入转义字符</p>

<p>这个转义字符为 ESC (十六进制表示为 $\mathrm{0x1B}$)</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-4.png" alt="chapter5-4" style="zoom:80%;" /></p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-5.png" alt="chapter5-5" style="zoom:80%;" /></p>

<h3 id="差错检验">差错检验</h3>

<p>这个是数据链路层的核心功能之一,  数据链路层广泛使用 <code class="language-plaintext highlighter-rouge">CRC循环冗余校验</code> 来校验错误</p>

<p>关于 <code class="language-plaintext highlighter-rouge">CRC循环冗余校验</code> 的解释, 请看这篇文章 :</p>

<p><a href="https://zhuanlan.zhihu.com/p/650922035">奇偶校验 + 循环冗余校验 - 知乎 (zhihu.com)</a></p>

<h2 id="点对点协议-ppp">点对点协议 PPP</h2>

<p>我们知道, 互联网中的用户通常都需要接入到 <code class="language-plaintext highlighter-rouge">ISP</code> 才能连接上互联网, 而在这个过程中, 数据链路层采用的协议一般是 <strong>点对点协议PPP</strong></p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-6.png" alt="chapter5-6" style="zoom:80%;" /></p>

<p>在这个部分, 我们将重点介绍一下 点对点协议PPP</p>

<h3 id="ppp协议帧各部分的组成">PPP协议帧各部分的组成</h3>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-7.png" alt="chapter5-7" style="zoom:80%;" /></p>

<ul>
  <li>
    <p>帧的首部：开始标识 $F$ 占1个字节，用十六进制 $7E$ 表示；$A$ 占 $1$ 个字节，$C$ 占一个字节，协议占 $2$ 个字节</p>

    <blockquote>
      <p>$A$ 代表地址, 一般是十六进制 $FF$ , $C$ 是控制字段, 一般是十六进制 $03$</p>

      <p>这两个字段都是无意义的</p>
    </blockquote>
  </li>
  <li>
    <p>IP数据报也就是信息部分不超过 $1500$ 个字节</p>
  </li>
  <li>
    <p>$FCS$ 占2个字节</p>

    <blockquote>
      <p>$FCS$ 是循环冗余校验码存放的地方</p>
    </blockquote>
  </li>
  <li>
    <p>帧的尾部：包含 $FCS$ (帧检验序列)和尾部标识 $7E$ 占据 $1$ 个字节。</p>
  </li>
</ul>

<p><strong>协议字段（两个字节长）不同的值代表不同的协议</strong></p>

<ol>
  <li>当协议字段为 $\mathrm{0x0021}$时，PPP帧的信息字段就是 IP 数据报。</li>
  <li>当协议字段为 $\mathrm{0xC021}$ 时，PPP帧的信息字段就是 PPP 链路控制协议 LCP 的数据。</li>
  <li>当协议字段为 $\mathrm{0x8021}$ 时，PPP帧的信息字段就是网络层的控制数据。</li>
</ol>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-8.png" alt="chapter5-8" style="zoom:80%;" /></p>

<h3 id="ppp-协议的透明传输">PPP 协议的透明传输</h3>

<p><strong>面向字节的异步链路：字节填充法（插入“转义字符”）</strong></p>

<ol>
  <li>将信息字段中出现的每个$\mathrm{0x7E}$ 字节转变为 $\mathrm{ (0x7D,0x5E)}$ (数值减去 $\mathrm{0x20}$)</li>
  <li>将信息字段中出现的每个 $\mathrm{0x7D}$ 字节转变为 $\mathrm{(0x7D,0x5D)}$ (数值减去 $\mathrm{0x20}$)</li>
  <li>若信息字段出现 <code class="language-plaintext highlighter-rouge">ASCII</code> 码的控制字符（即数值小于 $\mathrm{0x20}$ 的字符），则在该字符前面要加上一个 $\mathrm{0x7D}$ 。同时将该字符的编码改变。(数值加上 $\mathrm{0x20}$)</li>
</ol>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-9.png" alt="chapter5-9" style="zoom:80%;" /></p>

<p><strong>面向比特的同步链路：比特填充法（插入“比特0”）</strong></p>

<p>每连续出现五个连续的 $1$ , 就往后面插入一个 $0$</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-10.png" alt="chapter5-10" style="zoom:80%;" /></p>

<h3 id="ppp-协议的工作状态">PPP 协议的工作状态</h3>

<p>从工作状态上来看, PPP 协议已经不只是一个简单的数据链路层协议了, 还包含了物理层和网络层的内容</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-11.png" alt="chapter5-11" style="zoom:80%;" /></p>

<p>我们按照步骤一一讲述 PPP 协议的工作状态</p>

<ul>
  <li>
    <p>当我们的用户呼叫路由器请求上网的时候, PPP协议双方就会建立一条物理层的连接</p>
  </li>
  <li>
    <p>接着 <code class="language-plaintext highlighter-rouge">LCP</code> 开始协商配置选项</p>

    <blockquote>
      <p>这个时候会发送 <code class="language-plaintext highlighter-rouge">LCP</code> 的<strong>配置请求帧</strong> , 通常要发送多个这样的帧才能协商成功</p>

      <p>链路的另一端可以发送的响应有下面几种:</p>

      <p><strong>配置确认帧(Configure-Ack)</strong> : 所有选项都接受</p>

      <p><strong>配置否认帧(Configure-Nak)</strong> : 所有选项都理解但不能接受，</p>

      <p><strong>配置拒绝帧(Configure-Reject)</strong> : 选项有的无法识别或不能接受，需要协商</p>
    </blockquote>
  </li>
  <li>
    <p>协商结束后双方就建立了 <strong>LCP链路</strong> , 接下来就进入<strong>鉴别状态</strong></p>

    <blockquote>
      <p>所谓鉴别, 实际上就是身份验证</p>

      <p>可以使用<strong>口令鉴别协议 PAP (Password Authentication Protocol)</strong>，或 <strong>口令握手鉴别协议 CHAP(Challenge-Handshake Authentication Protocol)。</strong></p>
    </blockquote>
  </li>
  <li>
    <p>鉴别成功后就进入了<strong>网络协议状态</strong></p>

    <blockquote>
      <p>NCP 给新接入的 PC 机分配一个临时的 IP 地址，使 PC 机成为因特网上的一个主机</p>
    </blockquote>
  </li>
  <li>
    <p>之后便是<strong>链路打开状态</strong></p>

    <blockquote>
      <p>链路的两个 PPP 端点可以彼此向对方发送分组。两个 PPP 端点还可发送回送请求<strong>LCP分组(Echo-Request)</strong>和<strong>回送回答 LCP 分组(Echo-Reply)</strong>，以检查链路的状态</p>
    </blockquote>
  </li>
  <li>
    <p>当出现故障或者结束通讯的时候进入<strong>链路静止状态</strong></p>
  </li>
</ul>

<h2 id="使用广播信道的数据链路层">使用广播信道的数据链路层</h2>

<p>这一项技术一般在局域网或者无线网络中使用, 我们知道最简单的计算机通信方式是直接将两个主机两两相连, 但是如果主机较少的情况下, 这样确实会十分的便捷, 但是如果主机过多的情况下, 这会造成严重的问题</p>

<p>我们假设有 $n$ 台主机, 如果我们都两两相连的话, 那么就需要 $\frac{n(n-1)}{2}$ 条线路相连才能满足</p>

<p>当 $n$ 足够大的时候开销就会非常大, 所以就提出了复用的想法, 即这些主机通行会共用总线</p>

<p>可能的共用方法有 :</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-12.png" alt="chapter5-12" style="zoom:80%;" /></p>

<p>现在我们在广域网中不会使用这种方式, 而是使用分组交换的方式(我们之后会讲), 但是由于无线网络的天然特性以及在局域网中的优点, 使用广播信道依然实用, 所以我们依然需要学习</p>

<p>使用广播信道的数据链路层最大的问题就是会<strong>产生冲突</strong>, 当两台主机同时通信的时候, 就会造成冲突</p>

<p>所以我们接下来学习的协议都是为了解决冲突</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-13.png" alt="chapter5-13" style="zoom:80%;" /></p>

<p>关于静态划分信道这一部分, 我们在这部分进行讲解</p>

<p>感兴趣可以直接点击跳转 : <a href="https://yyym-y.github.io/2024/03/30/ComputerNetwork_note6/#复用技术">yyym blog (yyym-y.github.io) // 复用技术</a></p>

<p>我们重点讲解 <strong>动态接入控制</strong></p>

<h3 id="动态接入控制">动态接入控制</h3>

<p>动态接入控制也可以理解为动态划分信道, 相比于静态划分信道, 动态划分信道更加的灵活, 效率更高</p>

<p>我们将讲解一下这几个动态接入控制协议:</p>

<p><strong>ALOHA 协议</strong> 	<strong>CSMA 协议</strong>	<strong>CSMA/CD 协议</strong> 	<strong>CSMA/CA 协议</strong>	<strong>令牌传递协议</strong></p>

<h4 id="aloha-协议">ALOHA 协议</h4>

<p>一个讲的还行的视频 : <a href="https://www.bilibili.com/video/BV1ae4y1g7Ek?p=26">3.26.ALOHA协议 <em>哔哩哔哩</em> bilibili</a></p>

<p>所谓的 ALOHA 协议其实非常简单, 换算成人话就是: 我想发我就发, 如果发生冲突了, 我就等一段随机事件之后再发送</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-14.png" alt="chapter5-14" style="zoom:80%;" /></p>

<p>纯粹的 ALOHA 协议的效率会非常低, 所以为了提高他的效率, 就有了 <strong>时隙ALOHA协议</strong></p>

<p>它与传统的 ALOHA 协议只有一个区别, 就是对发送的时间有要求, 时间被分成了一个又一个的时间片, 只有在时间片的开始才能发送信息</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-15.png" alt="chapter5-15" style="zoom:80%;" /></p>

<p>尽管如此, 碰撞冲突的概率还是非常大, 效率大概是传统的两倍, 现在基本已经不再使用 ALOHA 协议了</p>

<h4 id="csma-协议">CSMA 协议</h4>

<p>CSMA 协议是我们后面两个协议的基础, 理解了 <code class="language-plaintext highlighter-rouge">CSMA</code> 协议会方便你之后的理解</p>

<p>CSMA 协议又叫 <strong>载波监听多路访问协议</strong></p>

<p>其核心为 : 在发送信息之前会监听信道上是否空闲, 如果空闲就按照一定的策略发送消息</p>

<blockquote>
  <p>如何监听型号是否空闲或者冲突</p>

  <p>在有线局域网中, 如果使用 CSMA 协议, 数据链路层会监测信道的电压值 (光纤的话是其他测量值)</p>

  <p>如果这个电压值低于某一个阈值, 就认为当前信道是空闲的</p>

  <p>如果这个电压值高于某一个阈值, 就认为当前信道发生了冲突</p>

  <p>注意: 监测是监测当前终端接入口的电压, 忽略了信号在信道的传播时延, 这也是为什么某些情况下空闲发送但是会冲突的原因</p>
</blockquote>

<p>CSMA 协议有下面几种策略:</p>

<ul>
  <li>
    <p><strong>1 - 坚持CSMA</strong></p>

    <blockquote>
      <p>如果监听到当前信道是空闲的, 就会立即将要发送的数据发送出去, 否则就会一直监听下去</p>

      <p>如果监听到当前信道是冲突的, 就会退避一段随机时间后再监听信道</p>
    </blockquote>
  </li>
  <li>
    <p><strong>非坚持CSMA</strong></p>

    <blockquote>
      <p>如果监听到当前信道是空闲的, 就会立即将要发送的数据发送出去</p>

      <p>如果检测到当前信道是忙/冲突的时候将会退避一段随机时间后再监听</p>

      <p><strong>为什么要退避一段时间再监听</strong></p>

      <p>假设我们有 $10$ 台机器使用 <code class="language-plaintext highlighter-rouge">1 - 坚持CSMA</code> , 那么当信道从忙转到空闲的时候, 这 $10$ 台机器就会一起发送信息, 这样冲突的概率会非常大</p>

      <p>但是由于非坚持CSMA的特点, 造成了某些时刻信道空闲但是没有机器发送信息, 造成了资源浪费</p>
    </blockquote>
  </li>
  <li>
    <p><strong>p - 坚持CSMA</strong></p>

    <blockquote>
      <p>如果监听到当前信道是空闲的, 就会以 $p$ 的发送概率将发送的数据发送出去, 否则就会一直监听下去</p>

      <p>如果检测到当前信道是忙/冲突的时候将会退避一段随机时间后再监听</p>

      <blockquote>
        <p>由于 $p&lt;1$，所以即使信道是空闲的，节点也可能不会立即发送数据，这可能会导致信道资源的浪费</p>
      </blockquote>
    </blockquote>

    <p><strong>注意, CSMA在发现冲突的时候, 会坚持将数据帧发送完毕 , 这样就造成了浪费</strong></p>
  </li>
</ul>

<h4 id="csmacd-协议">CSMA/CD 协议</h4>

<p>我们发现 CSMA 协议 在发生冲突的时候会依然将数据帧发完,</p>

<p><strong>而 CSMA/CD 协议会一直监听信道, 如果检测到冲突就会立即停止发送数据帧</strong></p>

<p>概括来说就是 : <strong>先听后发, 边听边发, 冲突停发, 随机重发</strong></p>

<p>对于 CSMA/CD 协议我们有一些需要注意的点:</p>

<ul>
  <li>
    <p><strong>CSMA/CD 协议一般在以太网中使用 (有线网络)</strong></p>
  </li>
  <li>
    <p><strong>CSMA/CD 协议只支持半双工, 不支持全双工</strong></p>

    <blockquote>
      <p>如果两端同时发送消息, 必然会冲突</p>
    </blockquote>
  </li>
  <li>
    <p><strong>发送端检测到冲突的最长时间为 $2\tau$</strong></p>

    <blockquote>
      <p>我们以下面的一张图为例:</p>

      <p><img src="/assets/images/ComputerNetwork/img/chapter5-16.png" alt="chapter5-16" style="zoom:80%;" /></p>

      <p>当冲突点无限接近于 $B$ 点的时候, $A$ 检测到冲突的时间就无限接近于 $2\tau$</p>

      <p>$2\tau$ 也被称为 <strong>冲突窗口</strong> 和 <strong>争用期</strong></p>
    </blockquote>
  </li>
  <li>
    <p><strong>确保帧的长度足够长以便可以实现冲突停发</strong></p>

    <blockquote>
      <p>我们假设数据帧的长度过小, 发送这个数据帧的时间 $t &lt; 2\tau$ , 那么在检测到冲突的时候已经将数据帧发完了</p>

      <p>如果在剩余的时间也发送了其他数据帧, 就不知道到底是哪一个数据帧出现了错误</p>

      <p>于是就必须规定发送数据帧的时间要大于争用期 $2\tau$</p>

      <p>$最小帧长 = 2 \times 传播时延 \times 数据率 = 2\tau\times 数据率$</p>

      <p>以太网规定了最小帧长为 $64$ 个字节, 也就是 $64B$</p>
    </blockquote>
  </li>
  <li>
    <p><strong>重传的时间和策略</strong></p>

    <blockquote>
      <p>CSMA/CD 协议采用的重传的策略为 <strong>p - 坚持CSMA</strong></p>

      <p>至于退避的时间采用 <strong>截断二进制指数退避算法</strong></p>

      <blockquote>
        <p>截断二进制指数退避算法的规则如下:</p>

        <p>退避时间为 $t = r\times 2\tau\quad r\in [0, 1, \cdots, 2^{k} - 1] \quad k = \min(10, 重传次数)$</p>

        <p>如果我们此时的重传次数为 $3$ , 那么将会从 $[0,1,2,3,4,5,6,7]$ 中随机选一个数作为 $r$ 计算退避时间</p>
      </blockquote>

      <p>当退避计时器的时间还未减小到零时而信道又转变为忙状态，这时就冻结退避计时器的数值，重新等待信道变为空闲, 继续启动退避计时器。</p>

      <p>重传的最大次数有限制 : <strong>最大重传次数为 $16$ 次</strong> , 如果超出这个数, 就会向上层报错</p>

      <blockquote>
        <p>为什么要用 $2\tau$ 作为最基本的时间</p>

        <p>如果重传双方都是小于 $2\tau$ 时间, 可能再次造成冲突</p>
      </blockquote>
    </blockquote>
  </li>
</ul>

<h4 id="csmaca-协议">CSMA/CA 协议</h4>

<p>我们知道 CSMA/CD 协议是一个优秀的协议, 他在有线网中能正常的工作, 但是在无线网络中就无法正常的工作了</p>

<p>首先是载波监听十分的困难, 无线网络因为其自身的特性, 可能会出现隐蔽站的问题</p>

<p>同时无线网是无法进行 360 度信道碰撞检测 : 在无限局域网中 , 无线信号 360 度发送 , 是无法检测所有方向的碰撞的</p>

<blockquote>
  <p>$A, C$ 站点 , 要给 $B$ 站点发送数据 , 发送前检测信道 , 如果 $A,C$ 都检测不到信号 , 认为信道空闲 , 同时向 $B$ 发送数据 , 此时就会导致冲突 ;</p>

  <p>如果 $A$ 要给 $B$ 发送数据 , $C$ 没有检测到信号 , 此时强行给 $C$ 发送数据 , 就会导致冲突 ;</p>

</blockquote>

<p>所以就有了 CSMA/CA 协议, 这里的 CA 是避免碰撞的意思</p>

<p>除此之外, 我们还需要了解一些概念</p>

<blockquote>
  <p>802.11标准规定，所有的站点必须<strong>在持续检测到信道空闲一段指定时间后才能发送帧</strong>，<strong>这段时间称为帧间间隔IFS</strong></p>

  <p>帧间间隔的长短取决于该站点要发送的帧的类型:</p>

  <ul>
    <li>高优先级帧需要等待的时间较短，因此可优先获得发送权</li>
    <li>低优先级帧需要等待的时间较长。若某个站的低优先级帧还没来得及发送，而其他站的高优先级已发送到信道上，则信道变为忙态，因而低优先级帧就只能再推迟发送了。这样就减少了发生碰撞的机会。</li>
  </ul>

  <p>常见的两种帧间间隔如下:</p>

  <ul>
    <li>
      <p>短帧间间隔 <strong>SIFS</strong>(28us)</p>

      <blockquote>
        <p>最短的帧间间隔，用来分隔开属于一次对话的各帧。一个站点应当能够在这段时间内从发送方式切换到接收方式。使用 SIFS 的帧类型有 ACK帧 、CTS帧、由过长的 MAC帧 分片后的数据帧、以及所有回答 AP 探询的帧和在 PCF 方式中接入点 AP 发送出的任何帧。</p>
      </blockquote>
    </li>
    <li>
      <p>DCF帧间间隔 <strong>DIFS</strong>(128us)</p>

      <blockquote>
        <p>它比短帧间间隔SIFS要长得多，在DCF方式中用来发送数据帧和管理帧</p>
      </blockquote>
    </li>
  </ul>
</blockquote>

<p>接下来我们来讲讲 CSMA/CA 的工作原理:</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-17.png" alt="chapter5-17" style="zoom:80%;" /></p>

<ul>
  <li>
    <p>当源站想要发送一个数据帧的时候, 他会监测当前信道是否空闲</p>
  </li>
  <li>
    <p>如果信道空闲, 他会等待一个 DIFS 时间后再发送自己的数据帧</p>
  </li>
</ul>

<blockquote>
  <p>等待DIFS数据帧是为了如果有其他更高优先级的帧要发送就让它先发送</p>
</blockquote>

<ul>
  <li>当目的站接收到这个数据帧之后, 经过检验通过后, 在等待一个 SIFS 间隔后再发送 ACK 帧</li>
</ul>

<blockquote>
  <p>SIFS 是用来分隔开一次对话的各个帧, 在 SIFS期间, 源站有时间从发送方转化为接收方</p>

  <p>如果源站没有接收到 ACK 帧, 由超时计时器控制的时间过后会按照重传策略重传</p>
</blockquote>

<ul>
  <li>
    <p>在这个过程中, 如果有其他站想要发送信息, 载波监听会发现信道忙, 就会处于退避状态</p>
  </li>
  <li>
    <p>当信道空闲的时候, 就会等待一个 DIFS 后, 再等待一个随机退避时间后再发送</p>
  </li>
</ul>

<blockquote>
  <p>等待一个随机退避时间是为了不让多个站点同时发送帧发生冲突</p>
</blockquote>

<p>接下来我们讲讲什么情况下需要退避随机时间,什么时候不需要</p>

<ul>
  <li>
    <p>不退避随机时间</p>

    <blockquote>
      <p>发送第一个帧不用随机退避，只需等待一个DIFS</p>
    </blockquote>
  </li>
  <li>
    <p>退避随机时间</p>

    <blockquote>
      <ol>
        <li>在发送数据帧之前检测到信道处于忙状态时;</li>
        <li>在每一次重传一个数据帧时;</li>
        <li>在每一次成功发送后要连续发送下一个帧时(这是为了避免一个站点长时间占用信道)</li>
      </ol>
    </blockquote>
  </li>
</ul>

<p>接下来我们讲讲 CSMA/CA 的退避时间算法</p>

<p>首先会随机选取一个退避时间, 这个退避时间使用 <strong>截断二进制指数退避算法</strong> 来选择</p>

<p>不过这个时候的 $k$ 取值为 $k =\min(6, 退避次数)$</p>

<p>规则如下:</p>

<ul>
  <li>当退避计时器的时间减小到零时，就开始发送数据;</li>
  <li>当退避计时器的时间还未减小到零时而信道又转变为忙状态，这时就冻结退避计时器的数值，重新等待信道变为空闲，再经过时间DIFS后，继续启动退避计时器。</li>
</ul>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-18.png" alt="chapter5-18" style="zoom:80%;" /></p>

<p>最后我们介绍一下 CSMA/CA 的信道预约和虚拟载波监听</p>

<p>源站会发送 <strong>RTS 帧</strong>, 这个帧会它包括源地址、目的地址以及<strong>这次通信(包括相应的确认帧)所需的持续时间。</strong></p>

<p>若目的站正确收到源站发来的RTS帧，且媒体空闲，就发送一个响应控制帧，称为允许发送CTS(CearTosend)，它也包括这次通信所需的持续时间(从RTS帧中将此持续时间复制到CTS帧中)。</p>

<p>除源站和目的站以外的其他各站，在收到CTS帧(或数据帧)后就推迟接入到无线局域网中。这样就保证了源站和目的站之间的通信不会受到其他站的干扰。</p>

<blockquote>
  <p>CTS 帧是广播发送, 也就是说: 如果其他站收到 CTS 帧, 那么在 CTS 中申请的那段时间内就都不会发送帧</p>
</blockquote>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-19.png" alt="chapter5-19" style="zoom:80%;" /></p>

<p>如果RTS帧发生碰撞，源站就收不到CTS帧，需执行退避算法重传RTS帧。</p>

<p>由于 <code class="language-plaintext highlighter-rouge">RTS</code> 帧和 <code class="language-plaintext highlighter-rouge">CTS</code> 帧很短，发送碰撞的概率、碰撞产生的开销及本身的开销都很小。而对于一般的数据帧，其发送时延往往大于传播时延(因为是局域网)，碰撞的概率很大，且一旦发生碰撞而导致数据帧重发，则浪费的时间就很多，因此用很小的代价对信道进行预约往往是值得的。</p>

<p>一般有三种情况可以使用:</p>

<ul>
  <li>使用RTS帧和CTS帧</li>
  <li>不使用RTS帧和CTS帧</li>
  <li>只有当数据帧的长度超过某一数值时才使用RTS帧和CTS帧</li>
</ul>

<p>除此之外, 数据帧也能携带通所需要的时间, 这就是虚拟载波监听机制</p>

<p>由于利用虚拟载波监听机制，站点<strong>只要监听到RTS帧、CTS帧或数据帧中的任何一个，就能知道信道被占用的持续时间</strong>，而不需要真正监听到信道上的信号，因此虚拟载波监听机制能减少隐蔽站带来的碰撞问题。</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-20.png" alt="chapter5-20" style="zoom:80%;" /></p>

<h2 id="以太网的-mac-层">以太网的 MAC 层</h2>

<p>我们前面解决了多台主机同时想要发送信息的问题, 这样就确认了如何保证发送方可以发送到接收方</p>

<p>接下来我们就需要解决接收方的问题了: 如何确定这条信息是发送给自己的</p>

<p>当多个主机连接到一条广播总线上的时候, 每一台机器都需要一个地址来标识自身, 在数据链路层, 这个标识就是 <strong>MAC 地址</strong></p>

<p>MAC 地址理论上是全球唯一的, 一个MAC 地址由下面的几个部分构成:</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-21.png" alt="chapter5-21" style="zoom:80%;" /></p>

<ul>
  <li>一个地址块可以生成 224 个不同的地址。这种 48 位地址称为 MAC-48，它的通用名称是 EUI-48</li>
  <li>生产适配器时，6 字节的 MAC 地址已被固化在适配器的 ROM，因此，<strong>MAC 地址也叫做硬件地址 (hardware address) 或物理地址</strong></li>
  <li>“MAC 地址”实际上就是适配器地址或适配器标识符 EUI-48</li>
</ul>

<p><strong>MAC 地址还分为单播地址, 组地址 和广播地址</strong></p>

<h2 id="使用集线器的共享式以太网">使用集线器的共享式以太网</h2>

<p>早期的传统以太网是使用<strong>粗同轴电缆</strong>的共享总线以太网，后来发展到使用价格相对便宜的<strong>细同轴电缆</strong></p>

<p>这也就是我们之前将过的方法, 即只是用一条线来连接所有的主机</p>

<p>然而，实践证明这种使用无源电缆线和大量机械接口的总线型以太网并不像人们想象的那么可靠</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-22.png" alt="chapter5-22" style="zoom:80%;" /></p>

<p>若总线上某一个连接点接触不良或者断开, 都有可能对整个总线网络带来巨大的打击</p>

<p>于是人们提出了使用集线器的共享式以太网</p>

<p>他是用双绞线和 RJ-45 接口进行连接, 价格更便宜也更方便</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-23.png" alt="chapter5-23" style="zoom:80%;" /></p>

<ul>
  <li>使用集线器的以太网虽然物理拓扑是星型的，但在逻辑上仍然是一个总线网。总线上的各站点共享总线资源，使用的还是CSMA/CD协议</li>
  <li>集线器只工作在物理层，它的每个接口仅简单地转发比特，并不进行碰撞检测。碰撞检测的任务由各站点中的网卡负责。</li>
  <li>集线器一般都有少量的容错能力和网络管理功能例如，若网络中某个站点的网卡出现了故障而不停地发送帧，集线器可以检测到这个问题，在内部断开与出故障网卡的连线，使整个以太网能正常工作</li>
</ul>

<blockquote>
  <p>集线器的工作方式就是转发各端口的信号</p>
</blockquote>

<p>IEEE于1990年制定了<strong>10BASE-T</strong>星型以太网的标准802.3i，这种以太网是局域网发展史上的一座非常重要的里程碑，它为以太网在局域网中的统治地位奠定了牢固的基础。</p>

<blockquote>
  <p><strong>10BASE-T</strong> :  $10$ 代表传输速率为 $10Mb/s$ , BASE 值采用基带信号进行传输, $T$ 值使用双绞线作为传输媒体</p>

  <p>10BASE-T 的传输距离较短, 每个站点到集线器的距离不能超过 $100m$</p>

  <p>如果使用光纤作为连接媒体, 那么名字就是 <strong>10BASE-F</strong> , 它的传输距离较长, 可以达到 $1km$</p>
</blockquote>

<h2 id="在物理层拓展以太网">在物理层拓展以太网</h2>

<p>在早期广泛使用粗同轴电缆或细同轴电缆共享总线以太网时，为了提高网络的地理覆盖范围，常用的是<strong>工作在物理层的转发器</strong>。</p>

<p>IEEE 802.3标准规定，两个网段可用一个转发器连接起来，任意两个站点之间最多可以经过三个网段。</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-24.png" alt="chapter5-24" style="zoom:80%;" /></p>

<p>但是随着集线器共享式以太网的发展, 转发器以前不再那么流行, 现在已经流行使用光纤进行拓展距离</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-25.png" alt="chapter5-25" style="zoom:80%;" /></p>

<p>当集线器的接口用完后, 可以使用一个主干集线器将两个不同的碰撞域相连, 从而达到拓展的目的</p>

<p>但是使用这种方式会降低吞吐率, 毕竟碰撞域变大了</p>

<h2 id="在数据链路层拓展以太网">在数据链路层拓展以太网</h2>

<h3 id="使用网桥拓展以太网">使用网桥拓展以太网</h3>

<p>我们在上一节讲到, 可以使用一个主干集线器将两个不同的碰撞域连接起来形成一个碰撞域, 但是这样做会造成吞吐量的下降</p>

<p>但是如果我们使用网桥进行连接的话, 就会解决这个问题</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-26.png" alt="chapter5-26" style="zoom:80%;" /></p>

<p>我们简单说明一下网桥的工作原理</p>

<blockquote>
  <p>当 $A$ 向 $D$ 发送一个单播帧的时候, 网桥收到这个帧, 在表中查看是否有 MAC 帧指定的 MAC 地址</p>

  <p>查表发现 $D$ 在接口 $2$ 中, 于是向 $2$ 发出</p>
</blockquote>

<blockquote>
  <p>当 $A$ 向 $C$ 发送一个单播帧的时候, 网桥收到这个帧, 发现 $C$ 的 MAC 地址在接口 $1$</p>

  <p>但是这个帧就是从 $1$ 中来的, 于是直接不转发了</p>
</blockquote>

<blockquote>
  <p>当 $A$ 发送广播帧的时候, 网桥不会查表, 直接向所有端口转发</p>
</blockquote>

<p>注意, 网桥也遵循 CSMA/CD 协议</p>

<p>网桥的转发表使用的是自学习算法 :</p>

<blockquote>
  <p>当一个 MAC 帧转发到网桥的时候, 网桥会源地址进行登记, 如 $A \to B$ , 那么就会登记 $A$ 的地址以及接口号</p>

  <p>如果 $B$ 的 MAC 地址无法找到, 那么就会盲目的转发 (向除了这个帧发送的接口转发)</p>
</blockquote>

<p>https://www.bilibili.com/video/BV1NT411g7n6?p=35</p>

<p>为了提高网络的可靠性, 通常一个网络中会添加冗余的网桥来提高可靠性:</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-27.png" alt="chapter5-27" style="zoom:80%;" /></p>

<p>但这样就产生了环路, 如果发送广播帧(或盲目转发单播帧) 的时候就会形成广播风暴, 浪费资源</p>

<p>于是网桥采用<strong>生成树协议 (STP)</strong> , 找到了最小生成树</p>

<p>不管网桥之间连接成了怎样复杂的带环拓扑，网桥之间通过<strong>交互网桥协议单元(Bridge Protocol DatUnit，BPDU)</strong>，找出原网络拓扑的一个连通子集(即生成树)，在这个子集里整个连通的网络中不存在环路。</p>

<p>当首次连接网桥或网络拓扑发生变化时(人为改变或出现故障)，网桥都会重新构造生成树，以确保网络的连通。</p>

<h2 id="交换式以太网">交换式以太网</h2>

<p>https://www.bilibili.com/video/BV1NT411g7n6?&amp;p=36</p>

<p>网桥的接口非常少, 不能满足我们的需求, 于是就有了交换机出现, 它相对于网桥来说功能更齐全</p>

<h3 id="以太网交换机">以太网交换机</h3>

<p>交换式以太网以 <strong>以太网交换机</strong> 为核心</p>

<p>以太网交换机同样使用转发表, 转发表的生成同样是自学习算法, 当布置冗余交换机的时候, 也是使用生成树协议来避免广播风暴</p>

<ul>
  <li>
    <p>实质上就是一个多接口的网桥,每个接口都以全双工方式直接与主机或以太网交换机相连，还具有并行性，即使多对主机能同时通信(而网桥只能一次分析和转发一个帧)。相互通信的主机都是独占传输媒体，无碰撞地传输数据。</p>

    <blockquote>
      <p>注意, 交换机的接口可以接上不同的系统, 你可以接一台明确的主机, 这个时候另一台主机向这个主机发送信息的时候是不用考虑碰撞的问题的, 因为交换机可以规避碰撞, 也就不需要使用 CSMA/CD 协议</p>

      <p>交换机的接口也可以接一个共享式以太网, 这个时候就需要使用 CSMA/CD 协议了</p>

      <p>第一种方式可以做到全双工, 第二种形式只能做到半双工</p>
    </blockquote>
  </li>
  <li>
    <p>接口处还有存储器，能在输出端口繁忙时把到来的帧进行暂时缓存。</p>
  </li>
  <li>
    <p>以太网交换机是一种即插即用设备，其内部的帧交换表(又称为地址表)是通过自学习算法自动地逐渐建立起来的。</p>
  </li>
  <li>
    <p>有直通(不检查)和存储转发两种方式，使用硬件转发，速度更快。</p>

    <blockquote>
      <p>直通的话只需要接收到目的 MAC 地址就可以直接进行转发了, 更快</p>

      <p>但是直通不会进行 MAC 帧的监测, 也就是说如果出错也不会发现, 而是直接发送出去</p>
    </blockquote>
  </li>
</ul>

<p><a href="https://www.bilibili.com/video/BV1NT411g7n6?p=38">共享式以太网与交换式以太网的区别</a></p>

<blockquote>
  <p>省流: 共享式以太网扩大了广播域, 扩大了碰撞域</p>

  <p>交换式以太网扩大了广播域, 不改变碰撞域</p>
</blockquote>

<h2 id="虚拟局域网-vlan">虚拟局域网 VLAN</h2>

<p>在主机数量过大的局域网中广播，会耗费较多的资源，当网络配置错误的时候，还可能造成无限兜圈子，造成广播风暴。所以，利用以太网交换机建立虚拟局域网（VLAN），就可以把一个较大局域网分割成一些较小的局域网，也即广播域。</p>

<p>虚拟局域网 VLAN 是由一些局域网网段构成的与物理位置无关的逻辑组，而这些网段具有某些共同的需求。每一个 VLAN 的帧都有一个明确的标识符，指明发送这个帧的计算机属于哪一个 VLAN。虚拟局域网其实只是局域网给用户提供的一种服务，而并不是一种新型局域网。</p>

<p><img src="/assets/images/ComputerNetwork/img/chapter5-28.png" alt="chapter5-28" style="zoom:80%;" /></p>

<p>如图, 我们可以吧使用交换机相连的几台主机划分成不同的 VLAN 网段, 这样, 同一个 VLAN 的主机就可以相互通行, 不同VLAN 的主句就不能相互通行</p>

<p><a href="https://www.bilibili.com/video/BV1NT411g7n6?p=41">VLAN 的工作原理</a></p>]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[概述 三个基本问题 封装成帧 透明传输 差错检验 点对点协议 PPP PPP协议帧各部分的组成 PPP 协议的透明传输 PPP 协议的工作状态 使用广播信道的数据链路层 动态接入控制 ALOHA 协议 CSMA 协议 CSMA/CD 协议 CSMA/CA 协议 以太网的 MAC 层 使用集线器的共享式以太网 在物理层拓展以太网 在数据链路层拓展以太网 使用网桥拓展以太网 交换式以太网 以太网交换机 虚拟局域网 VLAN 概述 数据链路层存在的原因以及理由 我们知道 TCP/IP 协议所在的网络层和传输层可以确保应用层发送的信息是可以可靠的传输到另一台主机中的, TCP/IP 协议中主要是解决传送的数据报乱序以及丢失所产生的问题, 但并不会怀疑我传来的数据报到底是不是有错误的 而物理层只是规定了比特 $1$ 和 比特 $0$ 到底是怎么传输的, 即我们应该怎么将我们的一串二进制数据转化为电磁信号或者光信号发出去并且如何接受比特. 你会发现少了一环 : 在物理层传输的数据实际上是可能出错的, 如果不经过处理直接交给网络层就会导致数据出错, 这正是我们数据链路层要解决的核心问题: 如何确保传输过来的 $01$ 串是正确的 在这一章中, 我们循序渐进的讲解整个数据链路层是如何工作并且拓展的 数据链路层的地位 因为数据链路层可以保障传输的 $01$ 串内容是正确的, 所以所有交换装置都必须实现数据链路层 主机 $H1$ 到主机 $H2$ 所经过的网络可以是多种不同类型的 注意：不同的链路层可能采用不同的数据链路层协议 局域网属于数据链路层 局域网虽然是个网络。但我们并不把局域网放在网络层中讨论。这是因为在网络层要讨论的是多个网络互连的问题，是讨论分组怎么从一个网络，通过路由器，转发到另一个网络。 而在同一个局域网中，分组怎么从一台主机传送到另一台主机，但并不经过路由器转发。从整个互联网来看，局域网仍属于数据链路层的范围 数据链路层的几个共同问题 链路 和 数据链路 概念不同 链路：一个节点到相邻节点的一段物理路线，而中间任何其他的交换节点 在数据通信的时候，两台计算机之间的通信路径往往要经过许多段这样的链路，可见链路只是一条路径的组成部分 数据链路 (data link)：除了物理线路外，还必须有通信协议来控制这些数据的传输。若把实现这些协议的硬件和软件加到链路上，就构成了数据链路 现在最常用的方法是使用适配器（即网卡）来实现这些协议的硬件和软件 一般的适配器都包括了数据链路层和物理层这两层的功能 也有人采用另外的术语。这就是把链路分为物理链路和逻辑链路 物理链路就是上面所说的链路 逻辑链路就是上面的数据链路，是物理链路加上必要的通信协议 数据链路层的协议数据单元: 帧 三个基本问题 三个基本问题分别是 封装成帧 , 透明传输 , 差错检验 封装成帧 封装成帧是指数据链路层给上层交付的协议数据单元添加帧头和帧尾使之成为帧 一个帧的帧长 = 这个帧的数据部分长度 + 帧头的长度 + 帧尾的长度 每一个数据链路层都会规定帧的数据部分的最大长度, 也叫 最大传输单元MTU(max transfer Unit) 我们会用 帧定界符 来确定确定一个帧的开始以及结尾, 帧定界符一般是不可打印的 SOH 用十六进制表示为 $\mathrm{0x01}$ , EOT 用十六进制表示为 $\mathrm{0x04}$ 数据链路层只有完整的收到帧开始符合帧结束符才认为这个帧接收完毕, 如果遇到其他情况将直接丢弃 透明传输 透明传输是指数据链路层对上层交付的传输数据没有任何限制，好像数据链路层不存在一样 在这个部分, 我们主要解决帧定界符在帧的数据部分出现的问题, 如果数据部分出现帧定界符, 我们会在前面插入转义字符 这个转义字符为 ESC (十六进制表示为 $\mathrm{0x1B}$) 差错检验 这个是数据链路层的核心功能之一, 数据链路层广泛使用 CRC循环冗余校验 来校验错误 关于 CRC循环冗余校验 的解释, 请看这篇文章 : 奇偶校验 + 循环冗余校验 - 知乎 (zhihu.com) 点对点协议 PPP 我们知道, 互联网中的用户通常都需要接入到 ISP 才能连接上互联网, 而在这个过程中, 数据链路层采用的协议一般是 点对点协议PPP 在这个部分, 我们将重点介绍一下 点对点协议PPP PPP协议帧各部分的组成 帧的首部：开始标识 $F$ 占1个字节，用十六进制 $7E$ 表示；$A$ 占 $1$ 个字节，$C$ 占一个字节，协议占 $2$ 个字节 $A$ 代表地址, 一般是十六进制 $FF$ , $C$ 是控制字段, 一般是十六进制 $03$ 这两个字段都是无意义的 IP数据报也就是信息部分不超过 $1500$ 个字节 $FCS$ 占2个字节 $FCS$ 是循环冗余校验码存放的地方 帧的尾部：包含 $FCS$ (帧检验序列)和尾部标识 $7E$ 占据 $1$ 个字节。 协议字段（两个字节长）不同的值代表不同的协议 当协议字段为 $\mathrm{0x0021}$时，PPP帧的信息字段就是 IP 数据报。 当协议字段为 $\mathrm{0xC021}$ 时，PPP帧的信息字段就是 PPP 链路控制协议 LCP 的数据。 当协议字段为 $\mathrm{0x8021}$ 时，PPP帧的信息字段就是网络层的控制数据。 PPP 协议的透明传输 面向字节的异步链路：字节填充法（插入“转义字符”） 将信息字段中出现的每个$\mathrm{0x7E}$ 字节转变为 $\mathrm{ (0x7D,0x5E)}$ (数值减去 $\mathrm{0x20}$) 将信息字段中出现的每个 $\mathrm{0x7D}$ 字节转变为 $\mathrm{(0x7D,0x5D)}$ (数值减去 $\mathrm{0x20}$) 若信息字段出现 ASCII 码的控制字符（即数值小于 $\mathrm{0x20}$ 的字符），则在该字符前面要加上一个 $\mathrm{0x7D}$ 。同时将该字符的编码改变。(数值加上 $\mathrm{0x20}$) 面向比特的同步链路：比特填充法（插入“比特0”） 每连续出现五个连续的 $1$ , 就往后面插入一个 $0$ PPP 协议的工作状态 从工作状态上来看, PPP 协议已经不只是一个简单的数据链路层协议了, 还包含了物理层和网络层的内容 我们按照步骤一一讲述 PPP 协议的工作状态 当我们的用户呼叫路由器请求上网的时候, PPP协议双方就会建立一条物理层的连接 接着 LCP 开始协商配置选项 这个时候会发送 LCP 的配置请求帧 , 通常要发送多个这样的帧才能协商成功 链路的另一端可以发送的响应有下面几种: 配置确认帧(Configure-Ack) : 所有选项都接受 配置否认帧(Configure-Nak) : 所有选项都理解但不能接受， 配置拒绝帧(Configure-Reject) : 选项有的无法识别或不能接受，需要协商 协商结束后双方就建立了 LCP链路 , 接下来就进入鉴别状态 所谓鉴别, 实际上就是身份验证 可以使用口令鉴别协议 PAP (Password Authentication Protocol)，或 口令握手鉴别协议 CHAP(Challenge-Handshake Authentication Protocol)。 鉴别成功后就进入了网络协议状态 NCP 给新接入的 PC 机分配一个临时的 IP 地址，使 PC 机成为因特网上的一个主机 之后便是链路打开状态 链路的两个 PPP 端点可以彼此向对方发送分组。两个 PPP 端点还可发送回送请求LCP分组(Echo-Request)和回送回答 LCP 分组(Echo-Reply)，以检查链路的状态 当出现故障或者结束通讯的时候进入链路静止状态 使用广播信道的数据链路层 这一项技术一般在局域网或者无线网络中使用, 我们知道最简单的计算机通信方式是直接将两个主机两两相连, 但是如果主机较少的情况下, 这样确实会十分的便捷, 但是如果主机过多的情况下, 这会造成严重的问题 我们假设有 $n$ 台主机, 如果我们都两两相连的话, 那么就需要 $\frac{n(n-1)}{2}$ 条线路相连才能满足 当 $n$ 足够大的时候开销就会非常大, 所以就提出了复用的想法, 即这些主机通行会共用总线 可能的共用方法有 : 现在我们在广域网中不会使用这种方式, 而是使用分组交换的方式(我们之后会讲), 但是由于无线网络的天然特性以及在局域网中的优点, 使用广播信道依然实用, 所以我们依然需要学习 使用广播信道的数据链路层最大的问题就是会产生冲突, 当两台主机同时通信的时候, 就会造成冲突 所以我们接下来学习的协议都是为了解决冲突 关于静态划分信道这一部分, 我们在这部分进行讲解 感兴趣可以直接点击跳转 : yyym blog (yyym-y.github.io) // 复用技术 我们重点讲解 动态接入控制 动态接入控制 动态接入控制也可以理解为动态划分信道, 相比于静态划分信道, 动态划分信道更加的灵活, 效率更高 我们将讲解一下这几个动态接入控制协议: ALOHA 协议 CSMA 协议 CSMA/CD 协议 CSMA/CA 协议 令牌传递协议 ALOHA 协议 一个讲的还行的视频 : 3.26.ALOHA协议 哔哩哔哩 bilibili 所谓的 ALOHA 协议其实非常简单, 换算成人话就是: 我想发我就发, 如果发生冲突了, 我就等一段随机事件之后再发送 纯粹的 ALOHA 协议的效率会非常低, 所以为了提高他的效率, 就有了 时隙ALOHA协议 它与传统的 ALOHA 协议只有一个区别, 就是对发送的时间有要求, 时间被分成了一个又一个的时间片, 只有在时间片的开始才能发送信息 尽管如此, 碰撞冲突的概率还是非常大, 效率大概是传统的两倍, 现在基本已经不再使用 ALOHA 协议了 CSMA 协议 CSMA 协议是我们后面两个协议的基础, 理解了 CSMA 协议会方便你之后的理解 CSMA 协议又叫 载波监听多路访问协议 其核心为 : 在发送信息之前会监听信道上是否空闲, 如果空闲就按照一定的策略发送消息 如何监听型号是否空闲或者冲突 在有线局域网中, 如果使用 CSMA 协议, 数据链路层会监测信道的电压值 (光纤的话是其他测量值) 如果这个电压值低于某一个阈值, 就认为当前信道是空闲的 如果这个电压值高于某一个阈值, 就认为当前信道发生了冲突 注意: 监测是监测当前终端接入口的电压, 忽略了信号在信道的传播时延, 这也是为什么某些情况下空闲发送但是会冲突的原因 CSMA 协议有下面几种策略: 1 - 坚持CSMA 如果监听到当前信道是空闲的, 就会立即将要发送的数据发送出去, 否则就会一直监听下去 如果监听到当前信道是冲突的, 就会退避一段随机时间后再监听信道 非坚持CSMA 如果监听到当前信道是空闲的, 就会立即将要发送的数据发送出去 如果检测到当前信道是忙/冲突的时候将会退避一段随机时间后再监听 为什么要退避一段时间再监听 假设我们有 $10$ 台机器使用 1 - 坚持CSMA , 那么当信道从忙转到空闲的时候, 这 $10$ 台机器就会一起发送信息, 这样冲突的概率会非常大 但是由于非坚持CSMA的特点, 造成了某些时刻信道空闲但是没有机器发送信息, 造成了资源浪费 p - 坚持CSMA 如果监听到当前信道是空闲的, 就会以 $p$ 的发送概率将发送的数据发送出去, 否则就会一直监听下去 如果检测到当前信道是忙/冲突的时候将会退避一段随机时间后再监听 由于 $p&lt;1$，所以即使信道是空闲的，节点也可能不会立即发送数据，这可能会导致信道资源的浪费 注意, CSMA在发现冲突的时候, 会坚持将数据帧发送完毕 , 这样就造成了浪费 CSMA/CD 协议 我们发现 CSMA 协议 在发生冲突的时候会依然将数据帧发完, 而 CSMA/CD 协议会一直监听信道, 如果检测到冲突就会立即停止发送数据帧 概括来说就是 : 先听后发, 边听边发, 冲突停发, 随机重发 对于 CSMA/CD 协议我们有一些需要注意的点: CSMA/CD 协议一般在以太网中使用 (有线网络) CSMA/CD 协议只支持半双工, 不支持全双工 如果两端同时发送消息, 必然会冲突 发送端检测到冲突的最长时间为 $2\tau$ 我们以下面的一张图为例: 当冲突点无限接近于 $B$ 点的时候, $A$ 检测到冲突的时间就无限接近于 $2\tau$ $2\tau$ 也被称为 冲突窗口 和 争用期 确保帧的长度足够长以便可以实现冲突停发 我们假设数据帧的长度过小, 发送这个数据帧的时间 $t &lt; 2\tau$ , 那么在检测到冲突的时候已经将数据帧发完了 如果在剩余的时间也发送了其他数据帧, 就不知道到底是哪一个数据帧出现了错误 于是就必须规定发送数据帧的时间要大于争用期 $2\tau$ $最小帧长 = 2 \times 传播时延 \times 数据率 = 2\tau\times 数据率$ 以太网规定了最小帧长为 $64$ 个字节, 也就是 $64B$ 重传的时间和策略 CSMA/CD 协议采用的重传的策略为 p - 坚持CSMA 至于退避的时间采用 截断二进制指数退避算法 截断二进制指数退避算法的规则如下: 退避时间为 $t = r\times 2\tau\quad r\in [0, 1, \cdots, 2^{k} - 1] \quad k = \min(10, 重传次数)$ 如果我们此时的重传次数为 $3$ , 那么将会从 $[0,1,2,3,4,5,6,7]$ 中随机选一个数作为 $r$ 计算退避时间 当退避计时器的时间还未减小到零时而信道又转变为忙状态，这时就冻结退避计时器的数值，重新等待信道变为空闲, 继续启动退避计时器。 重传的最大次数有限制 : 最大重传次数为 $16$ 次 , 如果超出这个数, 就会向上层报错 为什么要用 $2\tau$ 作为最基本的时间 如果重传双方都是小于 $2\tau$ 时间, 可能再次造成冲突 CSMA/CA 协议 我们知道 CSMA/CD 协议是一个优秀的协议, 他在有线网中能正常的工作, 但是在无线网络中就无法正常的工作了 首先是载波监听十分的困难, 无线网络因为其自身的特性, 可能会出现隐蔽站的问题 同时无线网是无法进行 360 度信道碰撞检测 : 在无限局域网中 , 无线信号 360 度发送 , 是无法检测所有方向的碰撞的 $A, C$ 站点 , 要给 $B$ 站点发送数据 , 发送前检测信道 , 如果 $A,C$ 都检测不到信号 , 认为信道空闲 , 同时向 $B$ 发送数据 , 此时就会导致冲突 ; 如果 $A$ 要给 $B$ 发送数据 , $C$ 没有检测到信号 , 此时强行给 $C$ 发送数据 , 就会导致冲突 ; 所以就有了 CSMA/CA 协议, 这里的 CA 是避免碰撞的意思 除此之外, 我们还需要了解一些概念 802.11标准规定，所有的站点必须在持续检测到信道空闲一段指定时间后才能发送帧，这段时间称为帧间间隔IFS 帧间间隔的长短取决于该站点要发送的帧的类型: 高优先级帧需要等待的时间较短，因此可优先获得发送权 低优先级帧需要等待的时间较长。若某个站的低优先级帧还没来得及发送，而其他站的高优先级已发送到信道上，则信道变为忙态，因而低优先级帧就只能再推迟发送了。这样就减少了发生碰撞的机会。 常见的两种帧间间隔如下: 短帧间间隔 SIFS(28us) 最短的帧间间隔，用来分隔开属于一次对话的各帧。一个站点应当能够在这段时间内从发送方式切换到接收方式。使用 SIFS 的帧类型有 ACK帧 、CTS帧、由过长的 MAC帧 分片后的数据帧、以及所有回答 AP 探询的帧和在 PCF 方式中接入点 AP 发送出的任何帧。 DCF帧间间隔 DIFS(128us) 它比短帧间间隔SIFS要长得多，在DCF方式中用来发送数据帧和管理帧 接下来我们来讲讲 CSMA/CA 的工作原理: 当源站想要发送一个数据帧的时候, 他会监测当前信道是否空闲 如果信道空闲, 他会等待一个 DIFS 时间后再发送自己的数据帧 等待DIFS数据帧是为了如果有其他更高优先级的帧要发送就让它先发送 当目的站接收到这个数据帧之后, 经过检验通过后, 在等待一个 SIFS 间隔后再发送 ACK 帧 SIFS 是用来分隔开一次对话的各个帧, 在 SIFS期间, 源站有时间从发送方转化为接收方 如果源站没有接收到 ACK 帧, 由超时计时器控制的时间过后会按照重传策略重传 在这个过程中, 如果有其他站想要发送信息, 载波监听会发现信道忙, 就会处于退避状态 当信道空闲的时候, 就会等待一个 DIFS 后, 再等待一个随机退避时间后再发送 等待一个随机退避时间是为了不让多个站点同时发送帧发生冲突 接下来我们讲讲什么情况下需要退避随机时间,什么时候不需要 不退避随机时间 发送第一个帧不用随机退避，只需等待一个DIFS 退避随机时间 在发送数据帧之前检测到信道处于忙状态时; 在每一次重传一个数据帧时; 在每一次成功发送后要连续发送下一个帧时(这是为了避免一个站点长时间占用信道) 接下来我们讲讲 CSMA/CA 的退避时间算法 首先会随机选取一个退避时间, 这个退避时间使用 截断二进制指数退避算法 来选择 不过这个时候的 $k$ 取值为 $k =\min(6, 退避次数)$ 规则如下: 当退避计时器的时间减小到零时，就开始发送数据; 当退避计时器的时间还未减小到零时而信道又转变为忙状态，这时就冻结退避计时器的数值，重新等待信道变为空闲，再经过时间DIFS后，继续启动退避计时器。 最后我们介绍一下 CSMA/CA 的信道预约和虚拟载波监听 源站会发送 RTS 帧, 这个帧会它包括源地址、目的地址以及这次通信(包括相应的确认帧)所需的持续时间。 若目的站正确收到源站发来的RTS帧，且媒体空闲，就发送一个响应控制帧，称为允许发送CTS(CearTosend)，它也包括这次通信所需的持续时间(从RTS帧中将此持续时间复制到CTS帧中)。 除源站和目的站以外的其他各站，在收到CTS帧(或数据帧)后就推迟接入到无线局域网中。这样就保证了源站和目的站之间的通信不会受到其他站的干扰。 CTS 帧是广播发送, 也就是说: 如果其他站收到 CTS 帧, 那么在 CTS 中申请的那段时间内就都不会发送帧 如果RTS帧发生碰撞，源站就收不到CTS帧，需执行退避算法重传RTS帧。 由于 RTS 帧和 CTS 帧很短，发送碰撞的概率、碰撞产生的开销及本身的开销都很小。而对于一般的数据帧，其发送时延往往大于传播时延(因为是局域网)，碰撞的概率很大，且一旦发生碰撞而导致数据帧重发，则浪费的时间就很多，因此用很小的代价对信道进行预约往往是值得的。 一般有三种情况可以使用: 使用RTS帧和CTS帧 不使用RTS帧和CTS帧 只有当数据帧的长度超过某一数值时才使用RTS帧和CTS帧 除此之外, 数据帧也能携带通所需要的时间, 这就是虚拟载波监听机制 由于利用虚拟载波监听机制，站点只要监听到RTS帧、CTS帧或数据帧中的任何一个，就能知道信道被占用的持续时间，而不需要真正监听到信道上的信号，因此虚拟载波监听机制能减少隐蔽站带来的碰撞问题。 以太网的 MAC 层 我们前面解决了多台主机同时想要发送信息的问题, 这样就确认了如何保证发送方可以发送到接收方 接下来我们就需要解决接收方的问题了: 如何确定这条信息是发送给自己的 当多个主机连接到一条广播总线上的时候, 每一台机器都需要一个地址来标识自身, 在数据链路层, 这个标识就是 MAC 地址 MAC 地址理论上是全球唯一的, 一个MAC 地址由下面的几个部分构成: 一个地址块可以生成 224 个不同的地址。这种 48 位地址称为 MAC-48，它的通用名称是 EUI-48 生产适配器时，6 字节的 MAC 地址已被固化在适配器的 ROM，因此，MAC 地址也叫做硬件地址 (hardware address) 或物理地址 “MAC 地址”实际上就是适配器地址或适配器标识符 EUI-48 MAC 地址还分为单播地址, 组地址 和广播地址 使用集线器的共享式以太网 早期的传统以太网是使用粗同轴电缆的共享总线以太网，后来发展到使用价格相对便宜的细同轴电缆 这也就是我们之前将过的方法, 即只是用一条线来连接所有的主机 然而，实践证明这种使用无源电缆线和大量机械接口的总线型以太网并不像人们想象的那么可靠 若总线上某一个连接点接触不良或者断开, 都有可能对整个总线网络带来巨大的打击 于是人们提出了使用集线器的共享式以太网 他是用双绞线和 RJ-45 接口进行连接, 价格更便宜也更方便 使用集线器的以太网虽然物理拓扑是星型的，但在逻辑上仍然是一个总线网。总线上的各站点共享总线资源，使用的还是CSMA/CD协议 集线器只工作在物理层，它的每个接口仅简单地转发比特，并不进行碰撞检测。碰撞检测的任务由各站点中的网卡负责。 集线器一般都有少量的容错能力和网络管理功能例如，若网络中某个站点的网卡出现了故障而不停地发送帧，集线器可以检测到这个问题，在内部断开与出故障网卡的连线，使整个以太网能正常工作 集线器的工作方式就是转发各端口的信号 IEEE于1990年制定了10BASE-T星型以太网的标准802.3i，这种以太网是局域网发展史上的一座非常重要的里程碑，它为以太网在局域网中的统治地位奠定了牢固的基础。 10BASE-T : $10$ 代表传输速率为 $10Mb/s$ , BASE 值采用基带信号进行传输, $T$ 值使用双绞线作为传输媒体 10BASE-T 的传输距离较短, 每个站点到集线器的距离不能超过 $100m$ 如果使用光纤作为连接媒体, 那么名字就是 10BASE-F , 它的传输距离较长, 可以达到 $1km$ 在物理层拓展以太网 在早期广泛使用粗同轴电缆或细同轴电缆共享总线以太网时，为了提高网络的地理覆盖范围，常用的是工作在物理层的转发器。 IEEE 802.3标准规定，两个网段可用一个转发器连接起来，任意两个站点之间最多可以经过三个网段。 但是随着集线器共享式以太网的发展, 转发器以前不再那么流行, 现在已经流行使用光纤进行拓展距离 当集线器的接口用完后, 可以使用一个主干集线器将两个不同的碰撞域相连, 从而达到拓展的目的 但是使用这种方式会降低吞吐率, 毕竟碰撞域变大了 在数据链路层拓展以太网 使用网桥拓展以太网 我们在上一节讲到, 可以使用一个主干集线器将两个不同的碰撞域连接起来形成一个碰撞域, 但是这样做会造成吞吐量的下降 但是如果我们使用网桥进行连接的话, 就会解决这个问题 我们简单说明一下网桥的工作原理 当 $A$ 向 $D$ 发送一个单播帧的时候, 网桥收到这个帧, 在表中查看是否有 MAC 帧指定的 MAC 地址 查表发现 $D$ 在接口 $2$ 中, 于是向 $2$ 发出 当 $A$ 向 $C$ 发送一个单播帧的时候, 网桥收到这个帧, 发现 $C$ 的 MAC 地址在接口 $1$ 但是这个帧就是从 $1$ 中来的, 于是直接不转发了 当 $A$ 发送广播帧的时候, 网桥不会查表, 直接向所有端口转发 注意, 网桥也遵循 CSMA/CD 协议 网桥的转发表使用的是自学习算法 : 当一个 MAC 帧转发到网桥的时候, 网桥会源地址进行登记, 如 $A \to B$ , 那么就会登记 $A$ 的地址以及接口号 如果 $B$ 的 MAC 地址无法找到, 那么就会盲目的转发 (向除了这个帧发送的接口转发) https://www.bilibili.com/video/BV1NT411g7n6?p=35 为了提高网络的可靠性, 通常一个网络中会添加冗余的网桥来提高可靠性: 但这样就产生了环路, 如果发送广播帧(或盲目转发单播帧) 的时候就会形成广播风暴, 浪费资源 于是网桥采用生成树协议 (STP) , 找到了最小生成树 不管网桥之间连接成了怎样复杂的带环拓扑，网桥之间通过交互网桥协议单元(Bridge Protocol DatUnit，BPDU)，找出原网络拓扑的一个连通子集(即生成树)，在这个子集里整个连通的网络中不存在环路。 当首次连接网桥或网络拓扑发生变化时(人为改变或出现故障)，网桥都会重新构造生成树，以确保网络的连通。 交换式以太网 https://www.bilibili.com/video/BV1NT411g7n6?&amp;p=36 网桥的接口非常少, 不能满足我们的需求, 于是就有了交换机出现, 它相对于网桥来说功能更齐全 以太网交换机 交换式以太网以 以太网交换机 为核心 以太网交换机同样使用转发表, 转发表的生成同样是自学习算法, 当布置冗余交换机的时候, 也是使用生成树协议来避免广播风暴 实质上就是一个多接口的网桥,每个接口都以全双工方式直接与主机或以太网交换机相连，还具有并行性，即使多对主机能同时通信(而网桥只能一次分析和转发一个帧)。相互通信的主机都是独占传输媒体，无碰撞地传输数据。 注意, 交换机的接口可以接上不同的系统, 你可以接一台明确的主机, 这个时候另一台主机向这个主机发送信息的时候是不用考虑碰撞的问题的, 因为交换机可以规避碰撞, 也就不需要使用 CSMA/CD 协议 交换机的接口也可以接一个共享式以太网, 这个时候就需要使用 CSMA/CD 协议了 第一种方式可以做到全双工, 第二种形式只能做到半双工 接口处还有存储器，能在输出端口繁忙时把到来的帧进行暂时缓存。 以太网交换机是一种即插即用设备，其内部的帧交换表(又称为地址表)是通过自学习算法自动地逐渐建立起来的。 有直通(不检查)和存储转发两种方式，使用硬件转发，速度更快。 直通的话只需要接收到目的 MAC 地址就可以直接进行转发了, 更快 但是直通不会进行 MAC 帧的监测, 也就是说如果出错也不会发现, 而是直接发送出去 共享式以太网与交换式以太网的区别 省流: 共享式以太网扩大了广播域, 扩大了碰撞域 交换式以太网扩大了广播域, 不改变碰撞域 虚拟局域网 VLAN 在主机数量过大的局域网中广播，会耗费较多的资源，当网络配置错误的时候，还可能造成无限兜圈子，造成广播风暴。所以，利用以太网交换机建立虚拟局域网（VLAN），就可以把一个较大局域网分割成一些较小的局域网，也即广播域。 虚拟局域网 VLAN 是由一些局域网网段构成的与物理位置无关的逻辑组，而这些网段具有某些共同的需求。每一个 VLAN 的帧都有一个明确的标识符，指明发送这个帧的计算机属于哪一个 VLAN。虚拟局域网其实只是局域网给用户提供的一种服务，而并不是一种新型局域网。 如图, 我们可以吧使用交换机相连的几台主机划分成不同的 VLAN 网段, 这样, 同一个 VLAN 的主机就可以相互通行, 不同VLAN 的主句就不能相互通行 VLAN 的工作原理]]></summary></entry><entry><title type="html">算法复健::ID_1</title><link href="https://yyym-y.github.io/2024/04/16/AlgorithmRecover-ID_1/" rel="alternate" type="text/html" title="算法复健::ID_1" /><published>2024-04-16T00:00:00+00:00</published><updated>2024-04-16T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/04/16/AlgorithmRecover-ID_1</id><content type="html" xml:base="https://yyym-y.github.io/2024/04/16/AlgorithmRecover-ID_1/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#d-another-problem-about-dividing-numbers" id="markdown-toc-d-another-problem-about-dividing-numbers">D. Another Problem About Dividing Numbers</a></li>
  <li><a href="#d-sum-of-xor-functions" id="markdown-toc-d-sum-of-xor-functions">D. Sum of XOR Functions</a></li>
</ul>

<h1 id="d-another-problem-about-dividing-numbers">D. Another Problem About Dividing Numbers</h1>

<p>题目传送门 : https://codeforces.com/problemset/problem/1538/D</p>

<p>复健后第一道成功 AC 的题, 好像还是 [普及+/提高]</p>

<p>我们先分析特殊情况, 如果 $c = 1$ 的时候, 那么如果可以, 那么要么 $a = kb$ 成立或者 $b = ka$ 成立</p>

<blockquote>
  <p>注意, $c = 1$ 的时候 $a, b$ 不能相等, 即 $k \ne 1$, 因为题目要求 $c &gt; 1$</p>
</blockquote>

<p>所以如果 $c = 1$ ,如果有一方是另一方的倍数, 且 $a,b$ 不相等那么就可以, 否则一定不行</p>

<p>接下来分析 $c \ne 1$ 的情况, 我们知道最少需要两步就一定可以让两个数相等</p>

<p>那就是 $a$ 除 $b$, $b$ 除 $a$</p>

<p>我们就考虑如何能将 $a$ 和 $b$ 拆分成 $k$ 个数相乘就可以得到最大的拆分次数了</p>

<p>$k$ 的最大值可以用分解质因数获得, 直接分解会超时, 所以我们将质数打表即可</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span><span class="cpf">&lt;bits/stdc++.h&gt;</span><span class="cp">
</span><span class="k">using</span> <span class="k">namespace</span> <span class="n">std</span><span class="p">;</span>
<span class="k">typedef</span> <span class="kt">long</span> <span class="kt">long</span> <span class="kt">int</span> <span class="n">lli</span><span class="p">;</span>

<span class="n">vector</span><span class="o">&lt;</span><span class="n">lli</span><span class="o">&gt;</span><span class="n">info</span><span class="p">;</span>
<span class="n">bitset</span><span class="o">&lt;</span><span class="n">lli</span><span class="p">(</span><span class="mf">1e8</span> <span class="o">+</span> <span class="mi">10</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">Check</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">Euler_sieve</span><span class="p">(</span><span class="n">lli</span> <span class="n">Limit</span><span class="p">){</span>
    <span class="k">for</span><span class="p">(</span><span class="n">lli</span> <span class="n">temp</span> <span class="o">=</span> <span class="mi">2</span> <span class="p">;</span> <span class="n">temp</span> <span class="o">&lt;=</span> <span class="n">Limit</span> <span class="p">;</span> <span class="n">temp</span><span class="o">++</span><span class="p">){</span>
        <span class="k">if</span><span class="p">(</span><span class="n">Check</span><span class="p">[</span><span class="n">temp</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="n">info</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">temp</span><span class="p">);</span>
        <span class="k">for</span><span class="p">(</span><span class="n">lli</span> <span class="n">temp2</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">;</span> <span class="n">temp</span><span class="o">*</span><span class="n">info</span><span class="p">[</span><span class="n">temp2</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">Limit</span> <span class="p">;</span> <span class="n">temp2</span><span class="o">++</span><span class="p">){</span>
            <span class="n">Check</span><span class="p">[</span><span class="n">temp</span><span class="o">*</span><span class="n">info</span><span class="p">[</span><span class="n">temp2</span><span class="p">]]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
            <span class="k">if</span><span class="p">(</span><span class="n">temp</span> <span class="o">%</span> <span class="n">info</span><span class="p">[</span><span class="n">temp2</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">solve</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">lli</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">;</span>
    <span class="n">cin</span> <span class="o">&gt;&gt;</span> <span class="n">a</span> <span class="o">&gt;&gt;</span> <span class="n">b</span> <span class="o">&gt;&gt;</span> <span class="n">c</span><span class="p">;</span>
    <span class="k">if</span><span class="p">(</span><span class="n">c</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span><span class="p">(</span><span class="n">max</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="o">%</span> <span class="n">min</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">a</span> <span class="o">!=</span> <span class="n">b</span><span class="p">)</span> <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"YES</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
        <span class="k">else</span> <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"NO</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">lli</span> <span class="n">count1</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">count2</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">info</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">info</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">info</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">a</span> <span class="p">;</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">a</span> <span class="o">%</span> <span class="p">(</span><span class="n">info</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">count2</span> <span class="o">++</span><span class="p">;</span> <span class="n">a</span> <span class="o">/=</span> <span class="n">info</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">if</span><span class="p">(</span><span class="n">a</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span> <span class="n">count2</span> <span class="o">++</span><span class="p">;</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">info</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">info</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">info</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">b</span> <span class="p">;</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">while</span> <span class="p">(</span><span class="n">b</span> <span class="o">%</span> <span class="n">info</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">count1</span> <span class="o">++</span><span class="p">;</span> <span class="n">b</span> <span class="o">/=</span> <span class="n">info</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">if</span><span class="p">(</span><span class="n">b</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span> <span class="n">count1</span> <span class="o">++</span><span class="p">;</span>
    <span class="k">if</span><span class="p">(</span><span class="n">c</span> <span class="o">&gt;</span> <span class="n">count1</span> <span class="o">+</span> <span class="n">count2</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"NO</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span> <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span><span class="p">(</span><span class="n">c</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">count1</span> <span class="o">&amp;&amp;</span> <span class="n">count2</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"NO</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span> <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"YES</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">signed</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">ios</span><span class="o">::</span><span class="n">sync_with_stdio</span><span class="p">(</span><span class="nb">false</span><span class="p">),</span> <span class="n">cin</span><span class="p">.</span><span class="n">tie</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">cout</span><span class="p">.</span><span class="n">tie</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">lli</span> <span class="n">ask</span><span class="p">;</span> <span class="n">cin</span> <span class="o">&gt;&gt;</span> <span class="n">ask</span><span class="p">;</span>
    <span class="n">Euler_sieve</span><span class="p">(</span><span class="mf">1e5</span><span class="p">);</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">ask</span> <span class="o">--</span><span class="p">)</span>
        <span class="n">solve</span><span class="p">();</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="d-sum-of-xor-functions">D. Sum of XOR Functions</h1>

<p>题目链接: https://codeforces.com/problemset/problem/1879/D</p>

<p>第二道过的题目, 好像也是 [普及+/提高], 感觉写起来挺 caodan 的</p>

<p>思路倒是不是很困难的想出来, 但是写起来巨麻烦和恶心, 一直在理清自己的思路</p>

<p>但这好像有套路 (感觉可以回去看看题解总结一下) : https://www.luogu.com.cn/problem/solution/CF1879D</p>

<p>具体的做法就是 :</p>

<p>每一位算贡献, 计算这一位在答案中总共被加上了多少次</p>

<p>我们重点关注从 $[a_0, a_1, a_2] \Rightarrow [a_0, a_1, a_2, a_3]$ 增加的值</p>

<p>假设我们有一个序列 $[1, 0, 1, 0]$, 我们用递推的思维简单看一下:</p>

<blockquote>
  <p>$[1]$ : 答案就是 $1$</p>

  <p>$[1,0]$ : 相比于上一个序列增加了 $1\times0 + 2\times1$</p>

  <p>$[1,0,1]$ : 相比于上一个序列增加了 $1\times1 + 2\times1 + 3\times0$</p>

  <p>$[1,0,1,0]$ : 相比于上一个序列增加了 $1\times0 + 2\times1 + 3\times1 + 4\times0$</p>
</blockquote>

<p>到这里规律其实就很明显了, [不想说了, 自己看….]</p>

<p>所以难写的点在于怎么滤清需要什么变量来维护这些变化</p>

<p>直接上代码吧:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span><span class="cpf">&lt;bits/stdc++.h&gt;</span><span class="cp">
</span><span class="k">using</span> <span class="k">namespace</span> <span class="n">std</span><span class="p">;</span>
<span class="k">typedef</span> <span class="kt">long</span> <span class="kt">long</span> <span class="kt">int</span> <span class="n">lli</span><span class="p">;</span>
<span class="n">lli</span> <span class="n">len</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">N</span> <span class="o">=</span> <span class="mf">5e5</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">P</span> <span class="o">=</span> <span class="mi">998244353</span><span class="p">;</span>

<span class="n">lli</span> <span class="n">arr</span><span class="p">[</span><span class="n">N</span><span class="p">];</span>
<span class="n">lli</span> <span class="n">bit</span><span class="p">[</span><span class="mi">33</span><span class="p">][</span><span class="n">N</span><span class="p">];</span>

<span class="n">lli</span> <span class="nf">queryNum</span><span class="p">(</span><span class="kt">int</span> <span class="n">pos</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">lli</span> <span class="n">ans</span> <span class="o">=</span> <span class="n">bit</span><span class="p">[</span><span class="n">pos</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">lli</span> <span class="n">last_add</span> <span class="o">=</span> <span class="n">bit</span><span class="p">[</span><span class="n">pos</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">lli</span> <span class="n">one_num</span> <span class="o">=</span> <span class="n">bit</span><span class="p">[</span><span class="n">pos</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">lli</span> <span class="n">sum</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">2</span> <span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">len</span><span class="p">;</span> <span class="n">sum</span> <span class="o">+=</span> <span class="n">i</span><span class="p">,</span> <span class="n">i</span> <span class="o">++</span><span class="p">,</span> <span class="n">sum</span> <span class="o">%=</span> <span class="n">P</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">lli</span> <span class="n">add</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="k">if</span><span class="p">(</span><span class="n">bit</span><span class="p">[</span><span class="n">pos</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">add</span> <span class="o">=</span> <span class="n">last_add</span> <span class="o">+</span> <span class="n">one_num</span><span class="p">;</span>
            <span class="n">add</span> <span class="o">%=</span> <span class="n">P</span><span class="p">;</span>
        <span class="p">}</span><span class="k">else</span> <span class="p">{</span>
            <span class="n">lli</span> <span class="n">tem</span> <span class="o">=</span> <span class="p">(</span><span class="n">sum</span> <span class="o">-</span> <span class="n">last_add</span> <span class="o">+</span> <span class="n">P</span><span class="p">)</span> <span class="o">%</span> <span class="n">P</span><span class="p">;</span>
            <span class="n">tem</span> <span class="o">%=</span> <span class="n">P</span><span class="p">;</span>
            <span class="n">one_num</span> <span class="o">=</span> <span class="p">(</span><span class="n">i</span> <span class="o">-</span> <span class="n">one_num</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">+</span> <span class="n">P</span><span class="p">)</span> <span class="o">%</span> <span class="n">P</span><span class="p">;</span>
            <span class="n">add</span> <span class="o">=</span> <span class="p">(</span><span class="n">tem</span> <span class="o">+</span> <span class="n">one_num</span><span class="p">)</span> <span class="o">%</span> <span class="n">P</span><span class="p">;</span>
            <span class="n">one_num</span> <span class="o">++</span><span class="p">;</span> <span class="n">add</span> <span class="o">++</span><span class="p">;</span>
            <span class="n">one_num</span> <span class="o">%=</span> <span class="n">P</span><span class="p">;</span> <span class="n">add</span> <span class="o">%=</span> <span class="n">P</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">ans</span> <span class="o">+=</span> <span class="n">add</span><span class="p">;</span>
        <span class="n">ans</span> <span class="o">%=</span> <span class="n">P</span><span class="p">;</span>
        <span class="n">last_add</span> <span class="o">=</span> <span class="n">add</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">ans</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">signed</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">ios</span><span class="o">::</span><span class="n">sync_with_stdio</span><span class="p">(</span><span class="nb">false</span><span class="p">),</span> <span class="n">cin</span><span class="p">.</span><span class="n">tie</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">cout</span><span class="p">.</span><span class="n">tie</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">cin</span> <span class="o">&gt;&gt;</span> <span class="n">len</span><span class="p">;</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span> <span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">len</span> <span class="p">;</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span> <span class="n">cin</span> <span class="o">&gt;&gt;</span> <span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="mi">32</span> <span class="p">;</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">1</span> <span class="p">;</span> <span class="n">j</span> <span class="o">&lt;=</span> <span class="n">len</span> <span class="p">;</span> <span class="n">j</span> <span class="o">++</span><span class="p">)</span>
            <span class="n">bit</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">arr</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">&gt;&gt;</span> <span class="n">i</span> <span class="o">&amp;</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">lli</span> <span class="n">ans</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="mi">32</span> <span class="p">;</span> <span class="n">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">lli</span> <span class="n">num</span> <span class="o">=</span> <span class="n">queryNum</span><span class="p">(</span><span class="n">i</span><span class="p">);</span> <span class="n">num</span> <span class="o">%=</span> <span class="n">P</span><span class="p">;</span>
        <span class="n">ans</span> <span class="o">+=</span> <span class="p">(</span><span class="n">num</span> <span class="o">*</span> <span class="p">((</span><span class="mi">1LL</span> <span class="o">&lt;&lt;</span> <span class="n">i</span><span class="p">)</span> <span class="o">%</span> <span class="n">P</span><span class="p">))</span> <span class="o">%</span> <span class="n">P</span><span class="p">;</span>
        <span class="n">ans</span> <span class="o">%=</span> <span class="n">P</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">ans</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">chapter6.0 - 广播机制</title><link href="https://yyym-y.github.io/2024/04/15/Android_note6/" rel="alternate" type="text/html" title="chapter6.0 - 广播机制" /><published>2024-04-15T00:00:00+00:00</published><updated>2024-04-15T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/04/15/Android_note6</id><content type="html" xml:base="https://yyym-y.github.io/2024/04/15/Android_note6/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#广播机制简介" id="markdown-toc-广播机制简介">广播机制简介</a></li>
  <li><a href="#接收系统广播" id="markdown-toc-接收系统广播">接收系统广播</a>    <ul>
      <li><a href="#动态注册" id="markdown-toc-动态注册">动态注册</a></li>
      <li><a href="#静态注册" id="markdown-toc-静态注册">静态注册</a></li>
    </ul>
  </li>
  <li><a href="#发送自定义广播" id="markdown-toc-发送自定义广播">发送自定义广播</a>    <ul>
      <li><a href="#发送标准广播" id="markdown-toc-发送标准广播">发送标准广播</a></li>
      <li><a href="#发送有序广播" id="markdown-toc-发送有序广播">发送有序广播</a></li>
    </ul>
  </li>
  <li><a href="#kotlin-进阶教学" id="markdown-toc-kotlin-进阶教学">kotlin-进阶教学</a></li>
</ul>

<h2 id="广播机制简介">广播机制简介</h2>

<p><code class="language-plaintext highlighter-rouge">Android</code> 中的每个应用程序都可以对自己感兴趣的广播进行注册，这样该程序就只会收到自己所关心的广播内容，这些广播可能是来自 于系统的，也可能是来自于其他应用程序的。<code class="language-plaintext highlighter-rouge">Android</code> 提供了一套完整的 <code class="language-plaintext highlighter-rouge">API</code>，允许应用程序自由地发送和接收广播</p>

<p>广播的类型主要有两种, 分别是<strong>标准广播</strong>和<strong>有序广播</strong></p>

<ul>
  <li>标准广播</li>
</ul>

<p>是一种完全异步执行的广播，在广播发出之后，所有的 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 几乎会在同一时刻收到这条广播消息，因此它们之间没有任何先后顺 序可言。这种广播的效率会比较高，但同时也意味着它是无法被截断的</p>

<p><img src="/assets/images/Android/img/normal_broadcase.png" alt="normal_broadcase" style="zoom:80%;" /></p>

<ul>
  <li>有序广播</li>
</ul>

<p>是一种同步执行的广播，在广播发出之后，同一时刻只会有一个 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 能够收到这条广播消息，当这个<code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 中的逻辑执行完毕后，广播才会继续传递。所以此时的 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 是有先后顺序的， 优先级高的<code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 就可以先收到广播消息，并且前面的 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 还可以截断正在传递的广播，这样后面的<code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 就无法收到广播消息了</p>

<p><img src="/assets/images/Android/img/order_broadcase.png" alt="order_broadcase" style="zoom:80%;" /></p>

<h2 id="接收系统广播">接收系统广播</h2>

<p><code class="language-plaintext highlighter-rouge">Android</code> 内置了很多系统级别的广播，我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息</p>

<p>接收广播一般使用 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> , 我们来演示一下如何使用 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 来接收时间变化的广播</p>

<p>注册 BroadcastReceiver的方式一般有两种：</p>

<p><strong>在代码中注册</strong>和<strong>在AndroidManifest.xml</strong>中注册。其中前者也被称为<strong>动态注册</strong>，后者也被称为<strong>静态注册</strong></p>

<h3 id="动态注册">动态注册</h3>

<p>我们只需要新建一个类，让它继承自 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code>，并重写父类的 <code class="language-plaintext highlighter-rouge">onReceive()</code> 方法就行了。这样当有广播到来时， <code class="language-plaintext highlighter-rouge">onReceive()</code> 方法就会得到执行，具体的逻辑就可以在这个方法中处理</p>

<p>我们以下面的代码为例:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">timeChangeReceiver</span> <span class="p">:</span> <span class="nc">TimeChangeReceiver</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span>
        <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span>

        <span class="kd">val</span> <span class="py">intentFilter</span> <span class="p">=</span> <span class="nc">IntentFilter</span><span class="p">()</span>
        <span class="n">intentFilter</span><span class="p">.</span><span class="nf">addAction</span><span class="p">(</span><span class="s">"android.intent.action.TIME_TICK"</span><span class="p">)</span>
        <span class="n">timeChangeReceiver</span> <span class="p">=</span> <span class="nc">TimeChangeReceiver</span><span class="p">()</span>
        <span class="nf">registerReceiver</span><span class="p">(</span><span class="n">timeChangeReceiver</span><span class="p">,</span> <span class="n">intentFilter</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onDestroy</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="p">.</span><span class="nf">onDestroy</span><span class="p">()</span>
        <span class="nf">unregisterReceiver</span><span class="p">(</span><span class="n">timeChangeReceiver</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">inner</span> <span class="kd">class</span> <span class="nc">TimeChangeReceiver</span> <span class="p">:</span> <span class="nc">BroadcastReceiver</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">onReceive</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">?,</span> <span class="n">intent</span><span class="p">:</span> <span class="nc">Intent</span><span class="p">?)</span> <span class="p">{</span>
            <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">"Time has changed"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>我们在MainActivity中定义了一个内部类TimeChangeReceiver，这个类是继承 自BroadcastReceiver的，并重写了父类的onReceive()方法。</p>

<p>观察 <code class="language-plaintext highlighter-rouge">onCreate()</code> 方法，首先我们创建了一个 <code class="language-plaintext highlighter-rouge">IntentFilter</code> 的实例，并给它添加了一个 值为 <code class="language-plaintext highlighter-rouge">android.intent.action.TIME_TICK</code>的 <code class="language-plaintext highlighter-rouge">action</code> ，为什么要添加这个值呢？因为当系统时间发生变化时，系统发出的正是一条值为 <code class="language-plaintext highlighter-rouge">android.intent.action.TIME_TICK</code>的广播， 也就是说我们的 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 想要监听什么广播，就在这里添加相应的 <code class="language-plaintext highlighter-rouge">action</code>。接下 来创建了一个<code class="language-plaintext highlighter-rouge">TimeChangeReceiver</code> 的实例，然后调用 <code class="language-plaintext highlighter-rouge">registerReceiver()</code> 方法进行注 册，将 <code class="language-plaintext highlighter-rouge">TimeChangeReceiver</code> 的实例和 <code class="language-plaintext highlighter-rouge">IntentFilter</code>的实例都传了进去，这样 <code class="language-plaintext highlighter-rouge">TimeChangeReceiver</code> 就会收到所有值为 <code class="language-plaintext highlighter-rouge">android.intent.action.TIME_TICK</code> 的广播， 也就实现了监听系统时间变化的功能</p>

<p>最后要记得，动态注册的 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 一定要取消注册才行，这里我们是在 <code class="language-plaintext highlighter-rouge">onDestroy()</code> 方法中通过调用<code class="language-plaintext highlighter-rouge">unregisterReceiver()</code>方法来实现的。</p>

<blockquote>
  <p>关于 Android 系统的所有广播, 你可以在本地的以下路径中找到:</p>

  <p><strong><code class="language-plaintext highlighter-rouge">/platforms/&lt;任意android api版本&gt;/data/broadcast_actions.txt</code></strong></p>

  <p>如 : <code class="language-plaintext highlighter-rouge">E:\environment\AndroidSDK\platforms\android-32\data\broadcast_actions.txt</code></p>
</blockquote>

<h3 id="静态注册">静态注册</h3>

<p>动态注册的 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 可以自由地控制注册与注销，在灵活性方面有很大的优势。但是它存在着一个缺点，即必须在程序启动之后才能接收广播，因为注册的逻辑是写在 <code class="language-plaintext highlighter-rouge">onCreate()</code> 方法中的。</p>

<p>如果我们希望在程序运行前也能接收广播, 那么就需要静态注册了</p>

<blockquote>
  <p>其实从理论上来说，动态注册能监听到的系统广播，静态注册也应该能监听到，在过去的 Android系统中确实是这样的。但是由于大量恶意的应用程序利用这个机制在程序未启动的情况 下监听系统广播，从而使任何应用都可以频繁地从后台被唤醒，严重影响了用户手机的电量和 性能，因此Android系统几乎每个版本都在削减静态注册BroadcastReceiver的功能。</p>

  <p>在Android 8.0系统之后，所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的 是那些没有具体指定发送给哪个应用程序的广播，大多数系统广播属于隐式广播，但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。这些特殊的系统广播列表详见 <a href="https://developer.android.google.cn/guide/components/broadcast-exceptions.html">这里</a></p>
</blockquote>

<p>现在我们使用静态注册来实现开机的时候弹出 <code class="language-plaintext highlighter-rouge">Toast</code></p>

<p>我们新建一个外部类 <code class="language-plaintext highlighter-rouge">BootCompleteReceiver</code> 类 :  →New→Other→Broadcast Receiver</p>

<p>我们简单修改一下代码, 就让 <code class="language-plaintext highlighter-rouge">onReceive()</code> 函数只是简单的弹出 <code class="language-plaintext highlighter-rouge">Toast</code></p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">BootCompleteReceiver</span> <span class="p">:</span> <span class="nc">BroadcastReceiver</span><span class="p">()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onReceive</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span> <span class="n">intent</span><span class="p">:</span> <span class="nc">Intent</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">"BOOT FINISH"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>之后我们需要在 <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> 文件中注册, 好在我们使用上述创建方法的话, Android Stdio 会帮我们自动注册</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;receiver</span>
	<span class="na">android:name=</span><span class="s">".BootCompleteReceiver"</span>
	<span class="na">android:enabled=</span><span class="s">"true"</span>
	<span class="na">android:exported=</span><span class="s">"true"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>在文件中出现了上述的一个块, 所有静态的 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 都是在这里进行注册的。它的用法其实和标签非常相似，也是通过<code class="language-plaintext highlighter-rouge">android:name</code> 指定具体注册哪一个 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> ，而 <code class="language-plaintext highlighter-rouge">enabled</code> 和 <code class="language-plaintext highlighter-rouge">exported</code> 属性则是根据我们刚才勾选的状态自动生成的。</p>

<p>不过目前的 <code class="language-plaintext highlighter-rouge">BootCompleteReceiver</code> 是无法收到开机广播的，因为我们还需要对 <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> 文件进行修改才行</p>

<p>我们增加这样几行:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;manifest</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span> 
          <span class="na">package=</span><span class="s">"com.example.broadcasttest"</span><span class="nt">&gt;</span> 
    <span class="nt">&lt;uses-permission</span> <span class="na">android:name=</span><span class="s">"android.permission.RECEIVE_BOOT_COMPLETED"</span> <span class="nt">/&gt;</span> 
    ...
    <span class="nt">&lt;receiver</span>
        <span class="na">android:name=</span><span class="s">".BootCompleteReceiver"</span>
        <span class="na">android:enabled=</span><span class="s">"true"</span>
        <span class="na">android:exported=</span><span class="s">"true"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;intent-filter&gt;</span>
            <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.BOOT_COMPLETED"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;/intent-filter&gt;</span>
    <span class="nt">&lt;/receiver&gt;</span>
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<p>由于Android系统启动完成后会发出一条值为 <code class="language-plaintext highlighter-rouge">android.intent.action.BOOT_COMPLETED</code> 的广播，因此我们在标签中又添加了一个标签，并在里面声明了相应的 <code class="language-plaintext highlighter-rouge">action</code></p>

<p>这里有非常重要的一点需要说明。<code class="language-plaintext highlighter-rouge">Android</code> 系统为了保护用户设备的安全和隐私，做了严格的规定：如果程序需要进行一些对用户来说比较敏感的操作，必须在 <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> 文件中进行权限声明，否则程序将会直接崩溃。比如这里接收系统的开机广播就是需要进行权限声明的，所以我们在上述代码中使用标签声明 了 <code class="language-plaintext highlighter-rouge">android.permission.RECEIVE_BOOT_COMPLETED</code> 权限</p>

<blockquote>
  <p>需要注意的是，不要在 <code class="language-plaintext highlighter-rouge">onReceive()</code> 方法中添加过多的逻辑或者进行任何的耗时操作，因为 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 中是不允许开启线程的，当 <code class="language-plaintext highlighter-rouge">onReceive()</code> 方法运行了较长时间而没有结束时，程序就会出现错误</p>
</blockquote>

<h2 id="发送自定义广播">发送自定义广播</h2>

<h3 id="发送标准广播">发送标准广播</h3>

<p>我们需要先定义一个我们自己的广播接收程序：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyReceiver</span> <span class="p">:</span> <span class="nc">BroadcastReceiver</span><span class="p">()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onReceive</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span> <span class="n">intent</span><span class="p">:</span> <span class="nc">Intent</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">"receive my Broadcast"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>之后我们在 <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> 中进行注册：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;receiver</span>
	<span class="na">android:name=</span><span class="s">".MyReceiver"</span>
	<span class="na">android:enabled=</span><span class="s">"true"</span>
	<span class="na">android:exported=</span><span class="s">"true"</span><span class="nt">&gt;</span>
	<span class="nt">&lt;intent-filter&gt;</span>
		<span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"org.yyym.broadcastTest.MY_BROADCAST"</span> <span class="nt">/&gt;</span>
	<span class="nt">&lt;/intent-filter&gt;</span>
<span class="nt">&lt;/receiver&gt;</span>
</code></pre></div></div>

<p>这样， <code class="language-plaintext highlighter-rouge">MyReceiver</code> 就会接受一个 <code class="language-plaintext highlighter-rouge">org.yyym.broadcastTest.MY_BROADCAST</code> 的广播了</p>

<p>之后我们在 <code class="language-plaintext highlighter-rouge">MainActivity</code> 中加入一个按钮点击的逻辑用来产生广播</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span>
        <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span>

        <span class="kd">val</span> <span class="py">button</span> <span class="p">:</span> <span class="nc">Button</span> <span class="p">=</span> <span class="nf">findViewById</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="n">sendBroadcast</span><span class="p">)</span>
        <span class="n">button</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span>
            <span class="kd">val</span> <span class="py">intent</span> <span class="p">=</span> <span class="nc">Intent</span><span class="p">(</span><span class="s">"org.yyym.broadcastTest.MY_BROADCAST"</span><span class="p">)</span>
            <span class="n">intent</span><span class="p">.</span><span class="nf">setPackage</span><span class="p">(</span><span class="n">packageName</span><span class="p">)</span>
            <span class="nf">sendBroadcast</span><span class="p">(</span><span class="n">intent</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>我们首先构建了一个 <code class="language-plaintext highlighter-rouge">Intent</code> 对象，并把要发送的广播的值传入。然后调用 <code class="language-plaintext highlighter-rouge">Intent</code> 的 <code class="language-plaintext highlighter-rouge">setPackage()</code> 方法，并传入当前应用程序的包名。</p>

<p><code class="language-plaintext highlighter-rouge">packageName</code> 是 <code class="language-plaintext highlighter-rouge">getPackageName()</code> 的语法糖写法，用于获取当前应用程序的包名。</p>

<p>最后调用 <code class="language-plaintext highlighter-rouge">sendBroadcast()</code> 方法将广播发送出去，这样所有监听 <code class="language-plaintext highlighter-rouge">com.example.broadcasttest.MY_BROADCAST</code> 这条广播的<code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 就会收到消息了。此时发出去的广播就是一条标准广播。</p>

<p>这里我还得对第2步调用的 <code class="language-plaintext highlighter-rouge">setPackage()</code> 方法进行更详细的说明。前面已经说过，在 <code class="language-plaintext highlighter-rouge">Android 8.0</code> 系统之后，静态注册的<code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 是无法接收隐式广播的，而默认情况下我们发出的自定义广播恰恰都是隐式广播。因此这里一定要调用<code class="language-plaintext highlighter-rouge">setPackage()</code> 方法，指定这条广播是发送给哪个应用程序的，从而让它变成一条显式广播，否则静态注册的 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 将 无法接收到这条广播</p>

<h3 id="发送有序广播">发送有序广播</h3>

<p>和标准广播不同，有序广播是一种同步执行的广播，并且是可以被截断的</p>

<p>我们再建立一个广播接收器</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">AnotherReceiver</span> <span class="p">:</span> <span class="nc">BroadcastReceiver</span><span class="p">()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onReceive</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span> <span class="n">intent</span><span class="p">:</span> <span class="nc">Intent</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">"This is another receiver"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>同时修改 xml 的配置让它能够接受 <code class="language-plaintext highlighter-rouge">org.yyym.broadcastTest.MY_BROADCAST</code> 广播</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;receiver</span>
	<span class="na">android:name=</span><span class="s">".AnotherReceiver"</span>
	<span class="na">android:enabled=</span><span class="s">"true"</span>
	<span class="na">android:exported=</span><span class="s">"true"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;intent-filter&gt;</span>
        <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"org.yyym.broadcastTest.MY_BROADCAST"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/intent-filter&gt;</span>
<span class="nt">&lt;/receiver&gt;</span>
</code></pre></div></div>

<p>如果此时运行应用, 将会受到两个 Toast 消息, 因为我们之前讲的发送方法发送的是标准广播</p>

<p>接下来我们将标准广播改成有序广播</p>

<p>我们只需要将 <code class="language-plaintext highlighter-rouge">sendBroadcast()</code> 改成 <code class="language-plaintext highlighter-rouge">sendOrderedBroadcast()</code> 即可, 如下:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span>
        <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span>

        <span class="kd">val</span> <span class="py">button</span> <span class="p">:</span> <span class="nc">Button</span> <span class="p">=</span> <span class="nf">findViewById</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="n">sendBroadcast</span><span class="p">)</span>
        <span class="n">button</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span>
            <span class="kd">val</span> <span class="py">intent</span> <span class="p">=</span> <span class="nc">Intent</span><span class="p">(</span><span class="s">"org.yyym.broadcastTest.MY_BROADCAST"</span><span class="p">)</span>
            <span class="n">intent</span><span class="p">.</span><span class="nf">setPackage</span><span class="p">(</span><span class="n">packageName</span><span class="p">)</span>
            <span class="nf">sendOrderedBroadcast</span><span class="p">(</span><span class="n">intent</span><span class="p">,</span> <span class="k">null</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">sendOrderedBroadcast()</code> 方法接收两个参数：第一个 参数仍然是Intent；第二个参数是一个与权限相关的字符串，这里传入null就行了</p>

<p>那么该如何设定 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 的先后顺序呢？当然是在注册的时候进行设定了，修改 AndroidManifest.xml中的代码，如下所示：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;receiver</span>
	<span class="na">android:name=</span><span class="s">".AnotherReceiver"</span>
	<span class="na">android:enabled=</span><span class="s">"true"</span>
	<span class="na">android:exported=</span><span class="s">"true"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;intent-filter</span> <span class="na">android:priority=</span><span class="s">"100"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"org.yyym.broadcastTest.MY_BROADCAST"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/intent-filter&gt;</span>
<span class="nt">&lt;/receiver&gt;</span>
</code></pre></div></div>

<p>我们通过 <code class="language-plaintext highlighter-rouge">android:priority</code> 属性给 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code> 设置了优先级，优先级比较高的 <code class="language-plaintext highlighter-rouge">BroadcastReceiver</code>就可以先收到广播</p>

<p>既然已经获得了接收广播的优先权，那么MyBroadcastReceiver就可以选择是否允许广播继续 传递了</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">AnotherReceiver</span> <span class="p">:</span> <span class="nc">BroadcastReceiver</span><span class="p">()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onReceive</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">,</span> <span class="n">intent</span><span class="p">:</span> <span class="nc">Intent</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">"This is another receiver"</span><span class="p">,</span> <span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span><span class="p">).</span><span class="nf">show</span><span class="p">()</span>
        <span class="nf">abortBroadcast</span><span class="p">()</span> <span class="c1">// 截断广播</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>之后我们就只能收到一个Receiver的回应了</p>

<h2 id="kotlin-进阶教学">kotlin-进阶教学</h2>]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[广播机制简介 接收系统广播 动态注册 静态注册 发送自定义广播 发送标准广播 发送有序广播 kotlin-进阶教学 广播机制简介 Android 中的每个应用程序都可以对自己感兴趣的广播进行注册，这样该程序就只会收到自己所关心的广播内容，这些广播可能是来自 于系统的，也可能是来自于其他应用程序的。Android 提供了一套完整的 API，允许应用程序自由地发送和接收广播 广播的类型主要有两种, 分别是标准广播和有序广播 标准广播 是一种完全异步执行的广播，在广播发出之后，所有的 BroadcastReceiver 几乎会在同一时刻收到这条广播消息，因此它们之间没有任何先后顺 序可言。这种广播的效率会比较高，但同时也意味着它是无法被截断的 有序广播 是一种同步执行的广播，在广播发出之后，同一时刻只会有一个 BroadcastReceiver 能够收到这条广播消息，当这个BroadcastReceiver 中的逻辑执行完毕后，广播才会继续传递。所以此时的 BroadcastReceiver 是有先后顺序的， 优先级高的BroadcastReceiver 就可以先收到广播消息，并且前面的 BroadcastReceiver 还可以截断正在传递的广播，这样后面的BroadcastReceiver 就无法收到广播消息了 接收系统广播 Android 内置了很多系统级别的广播，我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息 接收广播一般使用 BroadcastReceiver , 我们来演示一下如何使用 BroadcastReceiver 来接收时间变化的广播 注册 BroadcastReceiver的方式一般有两种： 在代码中注册和在AndroidManifest.xml中注册。其中前者也被称为动态注册，后者也被称为静态注册 动态注册 我们只需要新建一个类，让它继承自 BroadcastReceiver，并重写父类的 onReceive() 方法就行了。这样当有广播到来时， onReceive() 方法就会得到执行，具体的逻辑就可以在这个方法中处理 我们以下面的代码为例: class MainActivity : AppCompatActivity() { lateinit var timeChangeReceiver : TimeChangeReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val intentFilter = IntentFilter() intentFilter.addAction("android.intent.action.TIME_TICK") timeChangeReceiver = TimeChangeReceiver() registerReceiver(timeChangeReceiver, intentFilter) } override fun onDestroy() { super.onDestroy() unregisterReceiver(timeChangeReceiver) } inner class TimeChangeReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).show() } } } 我们在MainActivity中定义了一个内部类TimeChangeReceiver，这个类是继承 自BroadcastReceiver的，并重写了父类的onReceive()方法。 观察 onCreate() 方法，首先我们创建了一个 IntentFilter 的实例，并给它添加了一个 值为 android.intent.action.TIME_TICK的 action ，为什么要添加这个值呢？因为当系统时间发生变化时，系统发出的正是一条值为 android.intent.action.TIME_TICK的广播， 也就是说我们的 BroadcastReceiver 想要监听什么广播，就在这里添加相应的 action。接下 来创建了一个TimeChangeReceiver 的实例，然后调用 registerReceiver() 方法进行注 册，将 TimeChangeReceiver 的实例和 IntentFilter的实例都传了进去，这样 TimeChangeReceiver 就会收到所有值为 android.intent.action.TIME_TICK 的广播， 也就实现了监听系统时间变化的功能 最后要记得，动态注册的 BroadcastReceiver 一定要取消注册才行，这里我们是在 onDestroy() 方法中通过调用unregisterReceiver()方法来实现的。 关于 Android 系统的所有广播, 你可以在本地的以下路径中找到: /platforms/&lt;任意android api版本&gt;/data/broadcast_actions.txt 如 : E:\environment\AndroidSDK\platforms\android-32\data\broadcast_actions.txt 静态注册 动态注册的 BroadcastReceiver 可以自由地控制注册与注销，在灵活性方面有很大的优势。但是它存在着一个缺点，即必须在程序启动之后才能接收广播，因为注册的逻辑是写在 onCreate() 方法中的。 如果我们希望在程序运行前也能接收广播, 那么就需要静态注册了 其实从理论上来说，动态注册能监听到的系统广播，静态注册也应该能监听到，在过去的 Android系统中确实是这样的。但是由于大量恶意的应用程序利用这个机制在程序未启动的情况 下监听系统广播，从而使任何应用都可以频繁地从后台被唤醒，严重影响了用户手机的电量和 性能，因此Android系统几乎每个版本都在削减静态注册BroadcastReceiver的功能。 在Android 8.0系统之后，所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的 是那些没有具体指定发送给哪个应用程序的广播，大多数系统广播属于隐式广播，但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。这些特殊的系统广播列表详见 这里 现在我们使用静态注册来实现开机的时候弹出 Toast 我们新建一个外部类 BootCompleteReceiver 类 : →New→Other→Broadcast Receiver 我们简单修改一下代码, 就让 onReceive() 函数只是简单的弹出 Toast class BootCompleteReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Toast.makeText(context, "BOOT FINISH", Toast.LENGTH_SHORT).show() } } 之后我们需要在 AndroidManifest.xml 文件中注册, 好在我们使用上述创建方法的话, Android Stdio 会帮我们自动注册 &lt;receiver android:name=".BootCompleteReceiver" android:enabled="true" android:exported="true" /&gt; 在文件中出现了上述的一个块, 所有静态的 BroadcastReceiver 都是在这里进行注册的。它的用法其实和标签非常相似，也是通过android:name 指定具体注册哪一个 BroadcastReceiver ，而 enabled 和 exported 属性则是根据我们刚才勾选的状态自动生成的。 不过目前的 BootCompleteReceiver 是无法收到开机广播的，因为我们还需要对 AndroidManifest.xml 文件进行修改才行 我们增加这样几行: &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.broadcasttest"&gt; &lt;uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /&gt; ... &lt;receiver android:name=".BootCompleteReceiver" android:enabled="true" android:exported="true"&gt; &lt;intent-filter&gt; &lt;action android:name="android.intent.action.BOOT_COMPLETED" /&gt; &lt;/intent-filter&gt; &lt;/receiver&gt; &lt;/manifest&gt; 由于Android系统启动完成后会发出一条值为 android.intent.action.BOOT_COMPLETED 的广播，因此我们在标签中又添加了一个标签，并在里面声明了相应的 action 这里有非常重要的一点需要说明。Android 系统为了保护用户设备的安全和隐私，做了严格的规定：如果程序需要进行一些对用户来说比较敏感的操作，必须在 AndroidManifest.xml 文件中进行权限声明，否则程序将会直接崩溃。比如这里接收系统的开机广播就是需要进行权限声明的，所以我们在上述代码中使用标签声明 了 android.permission.RECEIVE_BOOT_COMPLETED 权限 需要注意的是，不要在 onReceive() 方法中添加过多的逻辑或者进行任何的耗时操作，因为 BroadcastReceiver 中是不允许开启线程的，当 onReceive() 方法运行了较长时间而没有结束时，程序就会出现错误 发送自定义广播 发送标准广播 我们需要先定义一个我们自己的广播接收程序： class MyReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Toast.makeText(context, "receive my Broadcast", Toast.LENGTH_SHORT).show() } } 之后我们在 AndroidManifest.xml 中进行注册： &lt;receiver android:name=".MyReceiver" android:enabled="true" android:exported="true"&gt; &lt;intent-filter&gt; &lt;action android:name="org.yyym.broadcastTest.MY_BROADCAST" /&gt; &lt;/intent-filter&gt; &lt;/receiver&gt; 这样， MyReceiver 就会接受一个 org.yyym.broadcastTest.MY_BROADCAST 的广播了 之后我们在 MainActivity 中加入一个按钮点击的逻辑用来产生广播 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val button : Button = findViewById(R.id.sendBroadcast) button.setOnClickListener { val intent = Intent("org.yyym.broadcastTest.MY_BROADCAST") intent.setPackage(packageName) sendBroadcast(intent) } } } 我们首先构建了一个 Intent 对象，并把要发送的广播的值传入。然后调用 Intent 的 setPackage() 方法，并传入当前应用程序的包名。 packageName 是 getPackageName() 的语法糖写法，用于获取当前应用程序的包名。 最后调用 sendBroadcast() 方法将广播发送出去，这样所有监听 com.example.broadcasttest.MY_BROADCAST 这条广播的BroadcastReceiver 就会收到消息了。此时发出去的广播就是一条标准广播。 这里我还得对第2步调用的 setPackage() 方法进行更详细的说明。前面已经说过，在 Android 8.0 系统之后，静态注册的BroadcastReceiver 是无法接收隐式广播的，而默认情况下我们发出的自定义广播恰恰都是隐式广播。因此这里一定要调用setPackage() 方法，指定这条广播是发送给哪个应用程序的，从而让它变成一条显式广播，否则静态注册的 BroadcastReceiver 将 无法接收到这条广播 发送有序广播 和标准广播不同，有序广播是一种同步执行的广播，并且是可以被截断的 我们再建立一个广播接收器 class AnotherReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Toast.makeText(context, "This is another receiver", Toast.LENGTH_SHORT).show() } } 同时修改 xml 的配置让它能够接受 org.yyym.broadcastTest.MY_BROADCAST 广播 &lt;receiver android:name=".AnotherReceiver" android:enabled="true" android:exported="true"&gt; &lt;intent-filter&gt; &lt;action android:name="org.yyym.broadcastTest.MY_BROADCAST" /&gt; &lt;/intent-filter&gt; &lt;/receiver&gt; 如果此时运行应用, 将会受到两个 Toast 消息, 因为我们之前讲的发送方法发送的是标准广播 接下来我们将标准广播改成有序广播 我们只需要将 sendBroadcast() 改成 sendOrderedBroadcast() 即可, 如下: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val button : Button = findViewById(R.id.sendBroadcast) button.setOnClickListener { val intent = Intent("org.yyym.broadcastTest.MY_BROADCAST") intent.setPackage(packageName) sendOrderedBroadcast(intent, null) } } } sendOrderedBroadcast() 方法接收两个参数：第一个 参数仍然是Intent；第二个参数是一个与权限相关的字符串，这里传入null就行了 那么该如何设定 BroadcastReceiver 的先后顺序呢？当然是在注册的时候进行设定了，修改 AndroidManifest.xml中的代码，如下所示： &lt;receiver android:name=".AnotherReceiver" android:enabled="true" android:exported="true"&gt; &lt;intent-filter android:priority="100"&gt; &lt;action android:name="org.yyym.broadcastTest.MY_BROADCAST" /&gt; &lt;/intent-filter&gt; &lt;/receiver&gt; 我们通过 android:priority 属性给 BroadcastReceiver 设置了优先级，优先级比较高的 BroadcastReceiver就可以先收到广播 既然已经获得了接收广播的优先权，那么MyBroadcastReceiver就可以选择是否允许广播继续 传递了 class AnotherReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Toast.makeText(context, "This is another receiver", Toast.LENGTH_SHORT).show() abortBroadcast() // 截断广播 } } 之后我们就只能收到一个Receiver的回应了 kotlin-进阶教学]]></summary></entry><entry><title type="html">chapter5.0 - 探究Fragment</title><link href="https://yyym-y.github.io/2024/04/08/Android_note5/" rel="alternate" type="text/html" title="chapter5.0 - 探究Fragment" /><published>2024-04-08T00:00:00+00:00</published><updated>2024-04-08T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/04/08/Android_note5</id><content type="html" xml:base="https://yyym-y.github.io/2024/04/08/Android_note5/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#fragment-是什么" id="markdown-toc-fragment-是什么">Fragment 是什么</a></li>
  <li><a href="#fragment-的使用" id="markdown-toc-fragment-的使用">Fragment 的使用</a>    <ul>
      <li><a href="#fragement-的简单使用" id="markdown-toc-fragement-的简单使用">Fragement 的简单使用</a></li>
      <li><a href="#动态添加-fragment" id="markdown-toc-动态添加-fragment">动态添加 Fragment</a></li>
      <li><a href="#在fragment-中实现返回栈" id="markdown-toc-在fragment-中实现返回栈">在Fragment 中实现返回栈</a></li>
      <li><a href="#fragment-和-activity-之间的交互" id="markdown-toc-fragment-和-activity-之间的交互">Fragment 和 Activity 之间的交互</a></li>
    </ul>
  </li>
  <li><a href="#fragment-的生命周期" id="markdown-toc-fragment-的生命周期">Fragment 的生命周期</a></li>
  <li><a href="#动态加载布局的方法" id="markdown-toc-动态加载布局的方法">动态加载布局的方法</a>    <ul>
      <li><a href="#使用限定符" id="markdown-toc-使用限定符">使用限定符</a></li>
      <li><a href="#使用最小宽度限定符" id="markdown-toc-使用最小宽度限定符">使用最小宽度限定符</a></li>
    </ul>
  </li>
  <li><a href="#kotlin-进阶课堂" id="markdown-toc-kotlin-进阶课堂">Kotlin 进阶课堂</a>    <ul>
      <li><a href="#拓展函数" id="markdown-toc-拓展函数">拓展函数</a></li>
      <li><a href="#运算符重载" id="markdown-toc-运算符重载">运算符重载</a></li>
    </ul>
  </li>
</ul>

<h2 id="fragment-是什么">Fragment 是什么</h2>

<p>Fragment是一种可以嵌入在Activity当中的UI片段，它能让程序更加合理和充分地利用大屏幕 的空间，因而在平板上应用得非常广泛</p>

<p>它和 <code class="language-plaintext highlighter-rouge">Activity</code> 实在是太像了, 可以理解为一个迷你型 <code class="language-plaintext highlighter-rouge">Activity</code></p>

<p>至于功能来说, 如果我们使用 <code class="language-plaintext highlighter-rouge">Fragment</code>, 我们可以在平板上展示更好的页面, 提供更好的观感</p>

<p>举个小小的例子, 一个软件在手机和平板上可以有这样不同的显示效果</p>

<p><img src="/assets/images/Android/img/fragment_phone.png" alt="fragment_phone" /></p>

<p><img src="/assets/images/Android/img/fragment_table.png" alt="fragment_table" /></p>

<h2 id="fragment-的使用">Fragment 的使用</h2>

<h3 id="fragement-的简单使用">Fragement 的简单使用</h3>

<p>我们先在一个 Activity 中添加两个 <code class="language-plaintext highlighter-rouge">Fragment</code> , 让他们各占一半的空间</p>

<p>我们先对两个 <code class="language-plaintext highlighter-rouge">Fragement</code> 的布局文件进行编辑, 我们让 <code class="language-plaintext highlighter-rouge">LeftFragment</code> 包含一个按钮, <code class="language-plaintext highlighter-rouge">RightFragment</code> 显示一串文字</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- fragment_left.xml --&gt;</span>
<span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="nt">&lt;LinearLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
    <span class="na">xmlns:tools=</span><span class="s">"http://schemas.android.com/tools"</span>
    <span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
    <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
    <span class="na">android:background=</span><span class="s">"#CA9393"</span>
    <span class="na">android:orientation=</span><span class="s">"vertical"</span>
    <span class="na">tools:context=</span><span class="s">".fragement.LeftFragment"</span><span class="nt">&gt;</span>

    <span class="nt">&lt;Button</span>
        <span class="na">android:id=</span><span class="s">"@+id/leftFragmentButton"</span>
        <span class="na">android:layout_width=</span><span class="s">"wrap_content"</span>
        <span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
        <span class="na">android:layout_gravity=</span><span class="s">"center_horizontal"</span>
        <span class="na">android:text=</span><span class="s">"Button"</span> <span class="nt">/&gt;</span>

<span class="nt">&lt;/LinearLayout&gt;</span>
</code></pre></div></div>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c">&lt;!-- fragment_right.xml --&gt;</span>
 <span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
 <span class="nt">&lt;FrameLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
     <span class="na">xmlns:tools=</span><span class="s">"http://schemas.android.com/tools"</span>
     <span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
     <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
     <span class="na">android:background=</span><span class="s">"#00ff00"</span>
     <span class="na">tools:context=</span><span class="s">".fragement.RightFragment"</span><span class="nt">&gt;</span>
 
     <span class="nt">&lt;TextView</span>
         <span class="na">android:layout_width=</span><span class="s">"wrap_content"</span>
         <span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
         <span class="na">android:layout_gravity=</span><span class="s">"center_horizontal"</span>
         <span class="na">android:text=</span><span class="s">"This is Right Fragment"</span>
         <span class="na">android:textSize=</span><span class="s">"24sp"</span> <span class="nt">/&gt;</span>
 
 <span class="nt">&lt;/FrameLayout&gt;</span>
</code></pre></div></div>

<p>之后我们注册这两个 <code class="language-plaintext highlighter-rouge">Fragment</code></p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">LeftFragment</span> <span class="p">:</span> <span class="nc">Fragment</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreateView</span><span class="p">(</span>
        <span class="n">inflater</span><span class="p">:</span> <span class="nc">LayoutInflater</span><span class="p">,</span> <span class="n">container</span><span class="p">:</span> <span class="nc">ViewGroup</span><span class="p">?,</span>
        <span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?</span>
    <span class="p">):</span> <span class="nc">View</span><span class="p">?</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">inflater</span><span class="p">.</span><span class="nf">inflate</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">fragment_left</span><span class="p">,</span> <span class="n">container</span><span class="p">,</span> <span class="k">false</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">RightFragment</span> <span class="p">:</span> <span class="nc">Fragment</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreateView</span><span class="p">(</span>
        <span class="n">inflater</span><span class="p">:</span> <span class="nc">LayoutInflater</span><span class="p">,</span> <span class="n">container</span><span class="p">:</span> <span class="nc">ViewGroup</span><span class="p">?,</span>
        <span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?</span>
    <span class="p">):</span> <span class="nc">View</span><span class="p">?</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">inflater</span><span class="p">.</span><span class="nf">inflate</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">fragment_right</span><span class="p">,</span> <span class="n">container</span><span class="p">,</span> <span class="k">false</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>注意:</p>

  <p>有两个不同包下的 <code class="language-plaintext highlighter-rouge">Fragment</code> 供你选择：一个是系统内置的 <code class="language-plaintext highlighter-rouge">android.app.Fragment</code> ，一个是 <code class="language-plaintext highlighter-rouge">AndroidX</code>库中 的 <code class="language-plaintext highlighter-rouge">androidx.fragment.app.Fragment</code> 。这里请一定要使用 <code class="language-plaintext highlighter-rouge">AndroidX</code> 库中的 <code class="language-plaintext highlighter-rouge">Fragment</code>，因 为它可以让 <code class="language-plaintext highlighter-rouge">Fragment</code> 的特性在所有 <code class="language-plaintext highlighter-rouge">Android</code> 系统版本中保持一致，而系统内置的 <code class="language-plaintext highlighter-rouge">Fragment</code> 在 <code class="language-plaintext highlighter-rouge">Android 9.0</code>版本中已被废弃</p>
</blockquote>

<p>之后修改 <code class="language-plaintext highlighter-rouge">activity_main.xml</code> 文件即可</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="nt">&lt;LinearLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
    <span class="na">xmlns:tools=</span><span class="s">"http://schemas.android.com/tools"</span>
    <span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
    <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
    <span class="na">android:orientation=</span><span class="s">"horizontal"</span>
    <span class="na">tools:context=</span><span class="s">".MainActivity"</span><span class="nt">&gt;</span>

    <span class="nt">&lt;fragment</span>
        <span class="na">android:id=</span><span class="s">"@+id/leftFrag"</span>
        <span class="na">android:layout_width=</span><span class="s">"0dp"</span>
        <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
        <span class="na">android:layout_weight=</span><span class="s">"1"</span>
        <span class="na">android:name=</span><span class="s">"com.example.learnfragment.fragement.LeftFragment"</span> <span class="nt">/&gt;</span>

    <span class="nt">&lt;androidx.fragment.app.FragmentContainerView</span>
        <span class="na">android:id=</span><span class="s">"@+id/rightFrag"</span>
        <span class="na">android:layout_width=</span><span class="s">"0dp"</span>
        <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
        <span class="na">android:layout_weight=</span><span class="s">"1"</span>
        <span class="na">android:name=</span><span class="s">"com.example.learnfragment.fragement.RightFragment"</span> <span class="nt">/&gt;</span>
    
<span class="nt">&lt;/LinearLayout&gt;</span>
</code></pre></div></div>

<blockquote>
  <p>虽然说 <code class="language-plaintext highlighter-rouge">&lt;fragment&gt;</code> 标签是正确的, 但是更推荐使用 <code class="language-plaintext highlighter-rouge">androidx.fragment.app.FragmentContainerView</code></p>
</blockquote>

<p>之后我们运行即可得到结果:</p>

<p><img src="/assets/images/Android/img/fragment_first.jpg" alt="fragment_first" /></p>

<h3 id="动态添加-fragment">动态添加 Fragment</h3>

<p>所谓动态的添加 <code class="language-plaintext highlighter-rouge">Fragment</code> , 实际上就是我们可以通过一些事件将某个 Fragment 替换成另一个 Fragment</p>

<p>现在我们就来实现点击左侧的按钮, 将右边的 Fragement 替换</p>

<p>我们新建一个新的 <code class="language-plaintext highlighter-rouge">Fragment</code> 叫 <code class="language-plaintext highlighter-rouge">AnotherRightFragment</code> , 它与 <code class="language-plaintext highlighter-rouge">RightFragment</code> 唯一的区别在于背景颜色不同并且文字不同, 这里就不展示代码了</p>

<p>之后我们修改 <code class="language-plaintext highlighter-rouge">activity_main</code> 的内容, 值得注意的是, 我们不是吧右边的 <code class="language-plaintext highlighter-rouge">Fragment</code> 写死了, 而是用 <code class="language-plaintext highlighter-rouge">FrameLayout</code> 代替</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="nt">&lt;LinearLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
    <span class="na">xmlns:tools=</span><span class="s">"http://schemas.android.com/tools"</span>
    <span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
    <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
    <span class="na">android:orientation=</span><span class="s">"horizontal"</span>
    <span class="na">tools:context=</span><span class="s">".MainActivity"</span><span class="nt">&gt;</span>

    <span class="nt">&lt;fragment</span>
        <span class="na">android:id=</span><span class="s">"@+id/leftFrag"</span>
        <span class="na">android:layout_width=</span><span class="s">"0dp"</span>
        <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
        <span class="na">android:layout_weight=</span><span class="s">"1"</span>
        <span class="na">android:name=</span><span class="s">"com.example.learnfragment.fragement.LeftFragment"</span> <span class="nt">/&gt;</span>
	
    <span class="nt">&lt;FrameLayout</span>
        <span class="na">android:id=</span><span class="s">"@+id/rightLayout"</span>
        <span class="na">android:layout_width=</span><span class="s">"0dp"</span>
        <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
        <span class="na">android:layout_weight=</span><span class="s">"1"</span> <span class="nt">/&gt;</span>

<span class="nt">&lt;/LinearLayout&gt;</span>
</code></pre></div></div>

<blockquote>
  <p>FrameLayout 是Android中最简单的一种布局，所有的控件默认都会摆放在布局的左上角。由于这里仅需要在布局里放入一个Fragment，不需要任何定位，因此非常适合使用 FrameLayout。</p>
</blockquote>

<p>之后我们在 <code class="language-plaintext highlighter-rouge">MainActivity</code> 中添加填充 <code class="language-plaintext highlighter-rouge">Fragment</code> 的逻辑即可</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
        <span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span>
        <span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span>
        <span class="n">leftFragmentButton</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span>
            <span class="nf">replaceFragment</span><span class="p">(</span><span class="nc">AnotherRightFragment</span><span class="p">())</span>
        <span class="p">}</span>
        <span class="nf">replaceFragment</span><span class="p">(</span><span class="nc">RightFragment</span><span class="p">())</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">fun</span> <span class="nf">replaceFragment</span><span class="p">(</span><span class="n">fragment</span><span class="p">:</span> <span class="nc">Fragment</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">fragmentManager</span> <span class="p">=</span> <span class="n">supportFragmentManager</span>
        <span class="kd">val</span> <span class="py">transaction</span> <span class="p">=</span> <span class="n">fragmentManager</span><span class="p">.</span><span class="nf">beginTransaction</span><span class="p">()</span>
        <span class="n">transaction</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="n">rightLayout</span><span class="p">,</span> <span class="n">fragment</span><span class="p">)</span>
        <span class="n">transaction</span><span class="p">.</span><span class="nf">commit</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>可以看到，首先我们给左侧 <code class="language-plaintext highlighter-rouge">Fragment</code> 中的按钮注册了一个点击事件，然后调用 <code class="language-plaintext highlighter-rouge">replaceFragment()</code> 方法动态添加了 <code class="language-plaintext highlighter-rouge">RightFragment</code></p>

<p>当点击左侧 <code class="language-plaintext highlighter-rouge">Fragment</code> 中的按钮时， 又会调用 <code class="language-plaintext highlighter-rouge">replaceFragment()</code> 方法，将右侧 <code class="language-plaintext highlighter-rouge">Fragment</code>替换成<code class="language-plaintext highlighter-rouge">AnotherRightFragment</code></p>

<p>结合 <code class="language-plaintext highlighter-rouge">replaceFragment()</code> 方法中的代码可以看出，动态添加 <code class="language-plaintext highlighter-rouge">Fragment</code> 主要分为 $5$步。</p>

<ol>
  <li>创建待添加 <code class="language-plaintext highlighter-rouge">Fragment</code> 的实例</li>
  <li>获取 <code class="language-plaintext highlighter-rouge">FragmentManager</code> ，在 <code class="language-plaintext highlighter-rouge">Activity</code> 中可以直接调用 <code class="language-plaintext highlighter-rouge">getSupportFragmentManager()</code> 方法获取</li>
  <li>开启一个事务，通过调用 <code class="language-plaintext highlighter-rouge">beginTransaction()</code> 方法开启</li>
  <li>向容器内添加或替换 <code class="language-plaintext highlighter-rouge">Fragment</code> ，一般使用 <code class="language-plaintext highlighter-rouge">replace()</code> 方法实现，需要传入容器的 <code class="language-plaintext highlighter-rouge">id</code> 和待添 加的<code class="language-plaintext highlighter-rouge">Fragment</code> 实例</li>
  <li>提交事务，调用 <code class="language-plaintext highlighter-rouge">commit()</code> 方法来完成</li>
</ol>

<p>至此, 我们便实现了这个功能</p>

<h3 id="在fragment-中实现返回栈">在Fragment 中实现返回栈</h3>

<p>在上一小节中，我们成功实现了向 <code class="language-plaintext highlighter-rouge">Activity</code> 中动态添加 <code class="language-plaintext highlighter-rouge">Fragment</code> 的功能。不过你尝试一下就会发现，通过点击按钮添加了一个 <code class="language-plaintext highlighter-rouge">Fragment</code> 之后，这时按下 <code class="language-plaintext highlighter-rouge">Back</code> 键程序就会直接退出。如果我们想实现类似于返回栈的效果，按下 <code class="language-plaintext highlighter-rouge">Back</code> 键可以回到上一个 <code class="language-plaintext highlighter-rouge">Fragment</code></p>

<p>其实很简单，<code class="language-plaintext highlighter-rouge">FragmentTransaction</code> 中提供了一个 <code class="language-plaintext highlighter-rouge">addToBackStack()</code> 方法，可以用于将一 个事务添加到返回栈中</p>

<p>即修改 <code class="language-plaintext highlighter-rouge">replaceFragment()</code> 为:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">fun</span> <span class="nf">replaceFragment</span><span class="p">(</span><span class="n">fragment</span><span class="p">:</span> <span class="nc">Fragment</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">fragmentManager</span> <span class="p">=</span> <span class="n">supportFragmentManager</span>
    <span class="kd">val</span> <span class="py">transaction</span> <span class="p">=</span> <span class="n">fragmentManager</span><span class="p">.</span><span class="nf">beginTransaction</span><span class="p">()</span>
    <span class="n">transaction</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="n">rightLayout</span><span class="p">,</span> <span class="n">fragment</span><span class="p">)</span>
    <span class="n">transaction</span><span class="p">.</span><span class="nf">addToBackStack</span><span class="p">(</span><span class="k">null</span><span class="p">)</span>
    <span class="n">transaction</span><span class="p">.</span><span class="nf">commit</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里我们在事务提交之前调用了 <code class="language-plaintext highlighter-rouge">FragmentTransaction</code> 的 <code class="language-plaintext highlighter-rouge">addToBackStack()</code> 方法，它可以接收一个名字用于描述返回栈的状态，一般传入 <code class="language-plaintext highlighter-rouge">null</code>即可</p>

<h3 id="fragment-和-activity-之间的交互">Fragment 和 Activity 之间的交互</h3>

<blockquote>
  <p><strong>Activity 与 Fragment 交互</strong></p>
</blockquote>

<p>虽然 <code class="language-plaintext highlighter-rouge">Fragment</code> 是嵌入在 <code class="language-plaintext highlighter-rouge">Activity</code> 中显示的，可是它们的关系并没有那么亲密。实际上， <code class="language-plaintext highlighter-rouge">Fragment</code> 和<code class="language-plaintext highlighter-rouge">Activity</code> 是各自存在于一个独立的类当中的，它们之间并没有那么明显的方式来直接进行交互</p>

<p>为了方便 <code class="language-plaintext highlighter-rouge">Fragment</code> 和 <code class="language-plaintext highlighter-rouge">Activity</code> 之间进行交互，<code class="language-plaintext highlighter-rouge">FragmentManager</code> 提供了一个类似于 <code class="language-plaintext highlighter-rouge">findViewById()</code> 的方法，专门用于从布局文件中获取 <code class="language-plaintext highlighter-rouge">Fragment</code> 的实例</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">fragment</span> <span class="p">=</span> <span class="n">supportFragmentManager</span><span class="p">.</span><span class="nf">findFragmentById</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="n">leftFrag</span><span class="p">)</span> <span class="k">as</span> <span class="nc">LeftFragment</span>
</code></pre></div></div>

<p>同时 <code class="language-plaintext highlighter-rouge">kotlin-android-extensions</code> 也可以进行简化为 :</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">fragment</span> <span class="p">=</span> <span class="n">leftFrag</span> <span class="k">as</span> <span class="nc">LeftFragment</span>
</code></pre></div></div>

<blockquote>
  <p><strong>Fragment 与 Activity 交互</strong></p>
</blockquote>

<p>每个 <code class="language-plaintext highlighter-rouge">Fragment</code> 中都可以通过调用 <code class="language-plaintext highlighter-rouge">getActivity()</code> 方法来得到和当前 <code class="language-plaintext highlighter-rouge">Fragment</code> 相关联的 <code class="language-plaintext highlighter-rouge">Activity</code> 实例，代码如下所示：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">activity</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> 
    <span class="kd">val</span> <span class="py">mainActivity</span> <span class="p">=</span> <span class="n">activity</span> <span class="k">as</span> <span class="nc">MainActivity</span> 
<span class="p">}</span> 
</code></pre></div></div>

<p>这里使用了语法糖</p>

<p>由于 <code class="language-plaintext highlighter-rouge">getActivity()</code> 方法有可能返回 <code class="language-plaintext highlighter-rouge">null</code>，因此我们需要先进行一个判空处理。有了 <code class="language-plaintext highlighter-rouge">Activity</code> 的实例，在<code class="language-plaintext highlighter-rouge">Fragment</code> 中调用 <code class="language-plaintext highlighter-rouge">Activity</code> 里的方法就变得轻而易举了。另外当 <code class="language-plaintext highlighter-rouge">Fragment</code> 中需要使用 <code class="language-plaintext highlighter-rouge">Context</code>对象时，也可以使用 <code class="language-plaintext highlighter-rouge">getActivity()</code> 方法，因为获取到的 <code class="language-plaintext highlighter-rouge">Activity</code> 本身就 是一个 <code class="language-plaintext highlighter-rouge">Context</code> 对象</p>

<h2 id="fragment-的生命周期">Fragment 的生命周期</h2>

<p>Fragment 的生命周期和 Activity 的生命周期大差不差, 也分为下面的几个阶段:</p>

<ul>
  <li><strong>运行状态</strong></li>
</ul>

<p>当一个Fragment所关联的Activity正处于运行状态时，该Fragment也处于运行状态</p>

<ul>
  <li><strong>暂停状态</strong></li>
</ul>

<p>当一个Activity进入暂停状态时（由于另一个未占满屏幕的Activity被添加到了栈顶），与 它相关联的Fragment就会进入暂停状态</p>

<ul>
  <li><strong>停止状态</strong></li>
</ul>

<p>当一个Activity进入停止状态时，与它相关联的Fragment就会进入停止状态，或者通过调用FragmentTransaction的remove()、replace()方法将Fragment从Activity中移 除，但在事务提交之前调用了addToBackStack()方法，这时的Fragment也会进入停止状态。总的来说，进入停止状态的Fragment对用户来说是完全不可见的，有可能会被系统 回收</p>

<ul>
  <li><strong>销毁状态</strong></li>
</ul>

<p>Fragment总是依附于Activity而存在，因此当Activity被销毁时，与它相关联的 Fragment就会进入销毁状态。或者通过调用FragmentTransaction的remove()、 replace()方法将Fragment从Activity中移除，但在事务提交之前并没有调用 addToBackStack()方法，这时的Fragment也会进入销毁状态。</p>

<blockquote>
  <p>Activity 中提供的生命周期的回调函数 Fragment 基本上都有, 但是有许多 Fragment 独有的回调函数</p>
</blockquote>

<ul>
  <li><code class="language-plaintext highlighter-rouge">onAttach()</code>：当Fragment和Activity建立关联时调用</li>
  <li><code class="language-plaintext highlighter-rouge">onCreateView()</code>：为Fragment创建视图（加载布局）时调用</li>
  <li><code class="language-plaintext highlighter-rouge">onActivityCreated()</code>：确保与Fragment相关联的Activity已经创建完毕时调用</li>
  <li><code class="language-plaintext highlighter-rouge">onDestroyView()</code>：当与Fragment关联的视图被移除时调用</li>
  <li><code class="language-plaintext highlighter-rouge">onDetach()</code>：当Fragment和Activity解除关联时调用</li>
</ul>

<p>完整的函数回调图片如下图所示 :</p>

<h2 id="动态加载布局的方法">动态加载布局的方法</h2>

<p>在这一节中, 我认为才是到了 Fragment 的核心用法了</p>

<p>之前我们实现 Fragment 可以把它当成一个迷你的 <code class="language-plaintext highlighter-rouge">Activity</code> , 现在我们要实现的是 <strong>当显示的设备不同的时候(如平板/手机), 我们可以动态的展示不同的效果</strong> , 就像我们一开始展示的图片一样</p>

<h3 id="使用限定符">使用限定符</h3>

<p>如果你经常使用平板，应该会发现很多平板应用采用的是双页模式（程序会在左侧的面板上显 示一个包含子项的列表，在右侧的面板上显示内容），因为平板的屏幕足够大，完全可以同时 显示两页的内容，但手机的屏幕就只能显示一页的内容，因此两个页面需要分开显示</p>

<p>我们可以借助限定符来实现到底是使用单页模式还是双叶模式</p>

<p>我们修改 <code class="language-plaintext highlighter-rouge">Activity_main</code> 的文件内容, 我们只留下一个左侧Fragment，并让它充满整个父布局, 它将作为单页模式的布局文件</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;fragment</span>
	<span class="na">android:id=</span><span class="s">"@+id/leftFrag"</span>
	<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
	<span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
	<span class="na">android:name=</span><span class="s">"com.example.learnfragment.fragement.LeftFragment"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>之后我们在 <code class="language-plaintext highlighter-rouge">res</code> 目录 下新建 <code class="language-plaintext highlighter-rouge">layout-large</code> 文件夹，在这个文件夹下新建一个布局，也叫作 <code class="language-plaintext highlighter-rouge">activity_main.xml</code> ，代码如下:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;LinearLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
    <span class="na">android:orientation=</span><span class="s">"horizontal"</span>
    <span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
    <span class="na">android:layout_height=</span><span class="s">"match_parent"</span><span class="nt">&gt;</span>
    
    <span class="nt">&lt;androidx.fragment.app.FragmentContainerView</span>
        <span class="na">android:id=</span><span class="s">"@+id/leftFrag"</span>
        <span class="na">android:name=</span><span class="s">"com.example.learnfragment.fragement.LeftFragment"</span>
        <span class="na">android:layout_width=</span><span class="s">"0dp"</span>
        <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
        <span class="na">android:layout_weight=</span><span class="s">"1"</span> <span class="nt">/&gt;</span>

    <span class="nt">&lt;androidx.fragment.app.FragmentContainerView</span>
        <span class="na">android:id=</span><span class="s">"@+id/rightFrag"</span>
        <span class="na">android:name=</span><span class="s">"com.example.learnfragment.fragement.RightFragment"</span>
        <span class="na">android:layout_width=</span><span class="s">"0dp"</span>
        <span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
        <span class="na">android:layout_weight=</span><span class="s">"3"</span> <span class="nt">/&gt;</span>

<span class="nt">&lt;/LinearLayout&gt;</span>
</code></pre></div></div>

<p>可以看到，<code class="language-plaintext highlighter-rouge">layout/activity_main</code> 布局只包含了一个 <code class="language-plaintext highlighter-rouge">Fragment</code> ，即单页模式，而 <code class="language-plaintext highlighter-rouge">layout large/ activity_main</code> 布局包含了两个<code class="language-plaintext highlighter-rouge">Fragment</code> ，即双页模式。其中，<code class="language-plaintext highlighter-rouge">large</code> 就是一个限定 符，那些屏幕被认为是 <code class="language-plaintext highlighter-rouge">large</code> 的设备就会自动加载 <code class="language-plaintext highlighter-rouge">layout-large</code> 文件夹下的布局，小屏幕的设备 则还是会加载 <code class="language-plaintext highlighter-rouge">layout</code> 文件夹下的布局。</p>

<p>这样我们就可以实现在两个不同的设备中展示不同的界面了</p>

<p><img src="/assets/images/Android/img/Fragment_table_2.png" alt="Fragment_table_2" style="zoom: 80%;" /></p>

<p><img src="/assets/images/Android/img/Fragment_phone_1.png" alt="Fragment_phone_1" style="zoom:80%;" /></p>

<p><strong>常用的限定符有:</strong></p>

<h3 id="使用最小宽度限定符">使用最小宽度限定符</h3>

<p>在上一小节中我们使用 <code class="language-plaintext highlighter-rouge">large</code> 限定符成功解决了单页双页的判断问题，不过很快又有一个新的问题出现了：<code class="language-plaintext highlighter-rouge">large</code> 到底是指多大呢？有时候我们希望可以更加灵活地为不同设备加载布局，不管它们是不是被系统认定为 <code class="language-plaintext highlighter-rouge">large</code>，这时就可以使用最小宽度限定符<code class="language-plaintext highlighter-rouge">（smallest-width qualifier）</code></p>

<p>在res目录下新建 <code class="language-plaintext highlighter-rouge">layout-sw600dp</code> 文件夹, 这就意味着，当程序运行在屏幕宽度大于等于 <code class="language-plaintext highlighter-rouge">600 dp</code> 的设备上时，会加载 <code class="language-plaintext highlighter-rouge">layout sw600dp/activity_main</code>布局，当程序运行在屏幕宽度小于 <code class="language-plaintext highlighter-rouge">600 dp</code> 的设备上时，则仍然加载 默认的 <code class="language-plaintext highlighter-rouge">layout/activity_main</code>布局。</p>

<h2 id="kotlin-进阶课堂">Kotlin 进阶课堂</h2>

<h3 id="拓展函数">拓展函数</h3>

<p>扩展函数表示即使在不修改某个类的源码的情况下，仍然可以打开这个类，向该类添加新的函数</p>

<p>比如说我们希望在 <code class="language-plaintext highlighter-rouge">String</code> 类里面新增一个新的函数 : <code class="language-plaintext highlighter-rouge">lettersCount()</code> , 用来计算这个字符串有多少个字母</p>

<p>我们可以直接将我们自己写的函数 “添加到源码中” , 从而可以直接进行调用</p>

<p>拓展函数的结构为:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nc">ClassName</span><span class="p">.</span><span class="nf">methodName</span><span class="p">(</span><span class="n">param1</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">param2</span><span class="p">:</span> <span class="nc">Int</span><span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span> 
    <span class="k">return</span> <span class="mi">0</span> 
<span class="p">}</span>
</code></pre></div></div>

<p>下面我们就来试试写一个拓展函数:</p>

<p>由于我们希望向String类中添加一个扩展函数，因此需要先创建一个String.kt文件。文件名虽 然并没有固定的要求，但是我建议向哪个类中添加扩展函数，就定义一个同名的Kotlin文件，这样便于你以后查找。<strong>最好将其创造为顶层方法</strong></p>

<p>我们在 <code class="language-plaintext highlighter-rouge">String.kt</code> 文件中添加这样的代码:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nc">String</span><span class="p">.</span><span class="nf">lettersCount</span><span class="p">()</span> <span class="p">:</span> <span class="nc">Int</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">num</span> <span class="p">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span><span class="p">(</span><span class="n">char</span> <span class="k">in</span> <span class="k">this</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span><span class="p">(</span><span class="n">char</span><span class="p">.</span><span class="nf">isLetter</span><span class="p">())</span>
            <span class="n">num</span><span class="p">++</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">num</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">str</span> <span class="p">=</span> <span class="s">"^&amp;#GH##%%^DDSJGW#**"</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">str</span><span class="p">.</span><span class="nf">lettersCount</span><span class="p">())</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这样便成功实现了拓展函数</p>

<h3 id="运算符重载">运算符重载</h3>

<p>运算符重载使用的是 <code class="language-plaintext highlighter-rouge">operator</code> 关键字，只要在指定函数的前面加上<code class="language-plaintext highlighter-rouge">operator</code> 关键字，就可以实现运算符重载的功能了</p>

<p>这点其实没什么好说的, 重载运算符后, 运算符两边的类型我们都是可以自定义的, 但也要符合逻辑</p>

<p>一般的格式如下:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Obj</span> <span class="p">{</span> 
    <span class="k">operator</span> <span class="k">fun</span> <span class="nf">plus</span><span class="p">(</span><span class="n">obj</span><span class="p">:</span> <span class="nc">Obj</span><span class="p">):</span> <span class="nc">Obj</span> <span class="p">{</span> 
        <span class="c1">// 处理相加的逻辑 </span>
    <span class="p">}</span> 
<span class="p">}</span>  
</code></pre></div></div>

<p>可重载的运算符所对应的的函数名以及顺序如下:</p>

<p><img src="/assets/images/Android/img/operate.png" alt="operate" style="zoom:80%;" /></p>]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">第一个Tampermonkey脚本</title><link href="https://yyym-y.github.io/2024/04/08/Tampermonkey_note1/" rel="alternate" type="text/html" title="第一个Tampermonkey脚本" /><published>2024-04-08T00:00:00+00:00</published><updated>2024-04-08T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/04/08/Tampermonkey_note1</id><content type="html" xml:base="https://yyym-y.github.io/2024/04/08/Tampermonkey_note1/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#初遇-tampermonkey" id="markdown-toc-初遇-tampermonkey">初遇 Tampermonkey</a></li>
  <li><a href="#油猴脚本概述" id="markdown-toc-油猴脚本概述">油猴脚本概述</a></li>
  <li><a href="#油猴脚本实践" id="markdown-toc-油猴脚本实践">油猴脚本实践</a></li>
</ul>

<h2 id="初遇-tampermonkey">初遇 Tampermonkey</h2>

<p>某一个夜晚, 在我准备出去吃饭的时候, 浏览器突然蹦出来油猴脚本的更新提示, 虽然不是很想鸟它, 但是不知道怎么就也突然想学油猴脚本了</p>

<p>虽然不是第一次使用油猴脚本, 但是自己也只是在网上找找现成的脚本直接安装, 也是突然就想学习一下具体的语法</p>

<p>一开始在 B 站找了个视频, 发现没有任何屁用, 于是打开浏览器寻找, 结果还真给我找到一个非常不错的教程</p>

<p>教程链接如下 : <a href="https://learn.scriptcat.org/">油猴开发指南</a></p>

<p>学习油猴脚本需要有前端编程的经验, 很不巧刚好会前端 ~~~~, 于是便开启了我的脚本之路….</p>

<h2 id="油猴脚本概述">油猴脚本概述</h2>

<p>所有油猴脚本, 实际上就是一个前端 js 代码植入, 通过这些 js 代码来实现我们的一些功能</p>

<p>一个基本的油猴脚本内容如下所示</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ==UserScript==</span>
<span class="c1">// @name         New Userscript</span>
<span class="c1">// @namespace    http://tampermonkey.net/</span>
<span class="c1">// @version      2024-04-09</span>
<span class="c1">// @description  try to take over the world!</span>
<span class="c1">// @author       You</span>
<span class="c1">// @match        http://*/*</span>
<span class="c1">// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==</span>
<span class="c1">// @grant        none</span>
<span class="c1">// ==/UserScript==</span>

<span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>

    <span class="c1">// Your code here...</span>
<span class="p">})();</span>
</code></pre></div></div>

<p>我们主要讲解一下上面的几个注解:</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">@name</code> : 这个脚本的名字</p>

  <p><code class="language-plaintext highlighter-rouge">@version</code> : 这个脚本的版本</p>

  <p><code class="language-plaintext highlighter-rouge">@description</code> : 这个脚本的描述</p>

  <p><code class="language-plaintext highlighter-rouge">@author</code> : 这个脚本的作者</p>

  <p><code class="language-plaintext highlighter-rouge">@match</code> : 这个脚本所适配的网站, 可以有多个, 可以使用通配符 <code class="language-plaintext highlighter-rouge">*</code></p>
</blockquote>

<p>更多的注解可以查阅官方文档 : <a href="https://www.tampermonkey.net/documentation.php?ext=dhdg">注解官方文档</a></p>

<blockquote>
  <p>虽然有些也解释的不明不白….建议不明白直接百度…</p>
</blockquote>

<h2 id="油猴脚本实践">油猴脚本实践</h2>

<p>虽然才看了这么点内容, 但毕竟脚本的核心是 js 代码, 而我对js 代码还算熟悉, 于是直接上手</p>

<p>我早就对 SCNU 的砺儒云课堂非常不满了, 不能保留用户登录信息, 每次登录还挺麻烦的</p>

<p>于是我打算写一个脚本来实现自动化登录</p>

<p>主要的脚本代码如下 :</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ==UserScript==</span>
<span class="c1">// @name         砺儒云课堂自动登录程序</span>
<span class="c1">// @namespace    https://moodle.scnu.edu.cn/</span>
<span class="c1">// @version      1.0.0</span>
<span class="c1">// @description  帮助你自动登录SCNU课程网站-砺儒云课堂</span>
<span class="c1">// @author       yyym</span>
<span class="c1">// @match        https://moodle.scnu.edu.cn/*</span>
<span class="c1">// @match        https://sso.scnu.edu.cn/AccountService/user/login.html</span>
<span class="c1">// @match        https://sso.scnu.edu.cn/AccountService/openapi/auth.html*</span>
<span class="c1">// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==</span>
<span class="c1">// @require      https://scriptcat.org/lib/513/2.0.0/ElementGetter.js</span>
<span class="c1">// @grant        none</span>
<span class="c1">// ==/UserScript==</span>


<span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">username</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">username</span><span class="dl">"</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">password</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">pwd</span><span class="dl">"</span><span class="p">;</span>

    <span class="cm">/* 首先我们需要获取当前网站的 url
    *  因为整个登录过程会跳转到不同的 url, 我希望这个脚本根据不同的 url 来执行不同的逻辑
    *  因为 url 有多个, 所以我也在注解中配置了多个 @match
    */</span>
    <span class="kd">let</span> <span class="nx">url</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">;</span>
    <span class="c1">// 因为网址可能在 ? 带有多个参数, 所以需要过滤掉 ? 后面的参数</span>
    <span class="kd">let</span> <span class="nx">index</span> <span class="o">=</span> <span class="nx">url</span><span class="p">.</span><span class="nx">lastIndexOf</span><span class="p">(</span><span class="dl">'</span><span class="s1">?</span><span class="dl">'</span><span class="p">);</span>
    <span class="k">if</span><span class="p">(</span><span class="nx">index</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">url</span> <span class="o">=</span> <span class="nx">url</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">index</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 在主登录页面的逻辑, 即砺儒云首页</span>
    <span class="k">if</span><span class="p">(</span><span class="nx">url</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">https://moodle.scnu.edu.cn/</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 通过开发者工具找到对应的 DOM 元素</span>
        <span class="kd">let</span> <span class="nx">loginButton</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">.forgotpass a</span><span class="dl">"</span><span class="p">);</span>
        <span class="c1">// if loginButton is null, it mean user has logined</span>
        <span class="k">if</span><span class="p">(</span><span class="nx">loginButton</span> <span class="o">==</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
        <span class="nx">loginButton</span><span class="p">.</span><span class="nx">click</span><span class="p">();</span> <span class="c1">// js模拟点击跳转</span>
    <span class="p">}</span>
    <span class="c1">// 在选择统一身份认证还是游客登录的逻辑, 默认统一身份认证</span>
    <span class="k">if</span><span class="p">(</span><span class="nx">url</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">https://moodle.scnu.edu.cn/login/index.php</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 异步获取 DOM 对象</span>
        <span class="nx">elmGetter</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">#ssobtn</span><span class="dl">'</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">but</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="k">if</span><span class="p">(</span><span class="nx">but</span> <span class="o">==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">ERROR</span><span class="dl">"</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="nx">but</span><span class="p">.</span><span class="nx">click</span><span class="p">();</span>
        <span class="p">});</span>
    <span class="p">}</span>
    <span class="c1">// 在输入账号密码的逻辑</span>
    <span class="k">if</span><span class="p">(</span><span class="nx">url</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">https://sso.scnu.edu.cn/AccountService/user/login.html</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 如果账号密码错误脚本将在这里被停止</span>
        <span class="kd">let</span> <span class="nx">msgtext</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">#msgtext</span><span class="dl">"</span><span class="p">);</span>
        <span class="k">if</span><span class="p">(</span><span class="nx">msgtext</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">!=</span> <span class="dl">""</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">alert</span><span class="p">(</span><span class="nx">msgtext</span><span class="p">.</span><span class="nx">innerHTML</span><span class="p">);</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="kd">let</span> <span class="nx">account_input</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">#account</span><span class="dl">"</span><span class="p">);</span>
        <span class="kd">let</span> <span class="nx">pwd_input</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">#password</span><span class="dl">"</span><span class="p">);</span>
        <span class="nx">account_input</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">username</span><span class="p">;</span>
        <span class="nx">pwd_input</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">password</span><span class="p">;</span>
        <span class="kd">let</span> <span class="nx">but</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">#btn-login</span><span class="dl">"</span><span class="p">);</span>
        <span class="nx">but</span><span class="p">.</span><span class="nx">click</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="c1">// 确认登录逻辑</span>
    <span class="k">if</span><span class="p">(</span><span class="nx">url</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">https://sso.scnu.edu.cn/AccountService/openapi/auth.html</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">confirm</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">.login-check-comfirm a</span><span class="dl">"</span><span class="p">);</span>
        <span class="nx">confirm</span><span class="p">.</span><span class="nx">click</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">})();</span>
</code></pre></div></div>

<p>值得注意的是, 我们在一个 <code class="language-plaintext highlighter-rouge">php</code> 页面执行了异步获取元素的操作</p>

<p>这是因为直接使用 <code class="language-plaintext highlighter-rouge">document.querySelector("...")</code> 会找不到对象</p>

<p>一般有两种原因, 一个是使用 <code class="language-plaintext highlighter-rouge">iframe</code> 框架, 一个是在查找元素的时候页面还没有加载</p>

<p>经过排查属于第二种情况, 所以我打算使用 <code class="language-plaintext highlighter-rouge">ElementGetter 库</code></p>

<p><code class="language-plaintext highlighter-rouge">ElementGetter 库</code> 的使用教程: <a href="https://bbs.tampermonkey.net.cn/thread-2726-1-1.html">ElementGetter 库教程</a></p>]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">网站使用说明喵~~~</title><link href="https://yyym-y.github.io/2024/04/05/help/" rel="alternate" type="text/html" title="网站使用说明喵~~~" /><published>2024-04-05T00:00:00+00:00</published><updated>2024-04-05T00:00:00+00:00</updated><id>https://yyym-y.github.io/2024/04/05/help</id><content type="html" xml:base="https://yyym-y.github.io/2024/04/05/help/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#yyym-y-的个人网站" id="markdown-toc-yyym-y-的个人网站">yyym-y 的个人网站</a>    <ul>
      <li><a href="#说明" id="markdown-toc-说明">说明</a></li>
      <li><a href="#基于本网站构建自己的网站" id="markdown-toc-基于本网站构建自己的网站">基于本网站构建自己的网站</a></li>
      <li><a href="#二次开发说明" id="markdown-toc-二次开发说明">二次开发说明</a>        <ul>
          <li><a href="#如何添加自己的博客" id="markdown-toc-如何添加自己的博客">如何添加自己的博客</a>            <ul>
              <li><a href="#增加文章元信息" id="markdown-toc-增加文章元信息">增加文章元信息</a></li>
              <li><a href="#文章的命名以及-url" id="markdown-toc-文章的命名以及-url">文章的命名以及 url</a></li>
              <li><a href="#修改主页显示的文章菜单" id="markdown-toc-修改主页显示的文章菜单">修改主页显示的文章菜单</a></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<h1 id="yyym-y-的个人网站">yyym-y 的个人网站</h1>

<h2 id="说明">说明</h2>

<p>本网站基于 Jekyll 并且借助 Github Page 进行部署</p>

<p>网站的 UI 设计是以 <a href="https://github.com/old-jekyll-templates/Strata-Jekyll-Theme">Strata-Jekyll-Theme</a> 为基础, 之后进行了大量自定义设计和修改而成</p>

<blockquote>
  <p>网站展示</p>
</blockquote>

<p><img src="/assets\basic\main.png" alt="" /></p>

<p><img src="/assets\basic\postImg.png" alt="" /></p>

<h2 id="基于本网站构建自己的网站">基于本网站构建自己的网站</h2>

<p>你可以将本网站进行修改, 之后部署到本地或者部署到 Github Page 中</p>

<p>部署到 Github Page 的方法见此链接: <a href="https://docs.github.com/zh/pages/getting-started-with-github-pages/creating-a-github-pages-site">创建你的 GitHub Page</a></p>

<p>部署到本地的方法较为麻烦已经繁琐, 但也不会太离谱</p>

<p>部署的教程如下 : <a href="https://zhuanlan.zhihu.com/p/139567128">本地部署 Jekyll</a></p>

<p>本地部署完 jekyll 环境后, 使用 <code class="language-plaintext highlighter-rouge">bundle</code> 来安装项目所需要的依赖</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle install
</code></pre></div></div>

<p>之后在项目的根目录执行 <code class="language-plaintext highlighter-rouge">jekyll</code> 命令即部署成功</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle exec jekyll serve
</code></pre></div></div>

<h2 id="二次开发说明">二次开发说明</h2>

<p>本项目使用 <code class="language-plaintext highlighter-rouge">jekyll</code>, <code class="language-plaintext highlighter-rouge">Element</code> 和 <code class="language-plaintext highlighter-rouge">Vue</code> 联合开发</p>

<p>项目文件结构如下:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yyym-y.github.io
├─ 404.html         # 网站 404 时显示的页面
├─ assets           # 静态资源存放路径
├─ css              # 网站 css 文件存储文件夹
├─ fonts            # 字体文件存储文件夹
├─ Gemfile
├─ Gemfile.lock
├─ index.html       # 网站入口页面
├─ js               # 网站所需 js 文件存储文件夹
├─ LICENSE          # 许可证
├─ pdf              # 博客的 pdf
├─ README.md        # 本文件
├─ robots.txt
├─ siteicon.png     # 网站图标(PC)
├─ touch-icon.png   # 网站图标(移动端)
├─ _config.yml      # jekyll 配置文件
├─ _data            # jekyll 数据
├─ _drafts          # jekyll 草稿
├─ _includes
├─ _layouts         # jekyll 布局文件
└─ _posts           # 博客存放的文件夹
</code></pre></div></div>

<h3 id="如何添加自己的博客">如何添加自己的博客</h3>

<h4 id="增加文章元信息">增加文章元信息</h4>

<p>首先准备一个使用 <code class="language-plaintext highlighter-rouge">markdown</code> 书写的文章, 你可以复制 <code class="language-plaintext highlighter-rouge">_posts/_defaultes.md</code> 文件中的内容复制到你的文章开头, 如下所示:</p>

<blockquote>
  <p>真正复制的时候不要包含注释 (# 后面的内容为注释)</p>
</blockquote>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---
layout : post               # 文章采用的 layout 布局, 不需要动
title : ""                  # 这篇文章的标题
githubUrl : ""              # 这篇文章的 github 链接
pdfUrl : "/pdf/"            # 这篇文章的 pdf 文件路径
cover : "/assets/cover/"    # 文章封面
previousUrl : ""            # 上一篇文章的 url
nextUrl : ""                # 下一篇文章的 url
---

# 下面两行用来显示目录(如果没有将没有目录功能)
* awsl
{:toc}

# 这里是你的正文部分
正文...
</code></pre></div></div>

<p>上面的部分为文章的元信息, 有些部分可以省略</p>

<blockquote>
  <p>比如说你没有 github 链接或者 pdf 链接就可以省略
没有上一片文章或者下一篇文章都可以省略</p>
</blockquote>

<h4 id="文章的命名以及-url">文章的命名以及 url</h4>

<p>所有的文章都必须放在 <code class="language-plaintext highlighter-rouge">_post</code> 目录之下, 文章可以在不同的子文件夹中, 建议按类别区分</p>

<p><code class="language-plaintext highlighter-rouge">jekyll</code> 解释器对文章的命名格式有限制:</p>

<p>假设我们的文章名字为 <code class="language-plaintext highlighter-rouge">note.md</code> , 那么我们需要将名字改为 <code class="language-plaintext highlighter-rouge">2024-03-15-note.markdown</code></p>

<p>日期默认含义为文件创建日期(也可以随便填), 此时这个文章的 url 为 <code class="language-plaintext highlighter-rouge">2024/03/15/note</code></p>

<h4 id="修改主页显示的文章菜单">修改主页显示的文章菜单</h4>

<p>主页面的菜单为:</p>

<p><img src="/assets/basic/main-menu.png" alt="" /></p>

<p>我们打开下面的文件 <code class="language-plaintext highlighter-rouge">/js/categoryData.js</code>:</p>

<p>文件的格式如下所示:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
    <span class="na">point_color</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">#0bbd87</span><span class="dl">"</span><span class="p">,</span>

    <span class="na">catagoryInfo</span> <span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span> <span class="c1">// 第一层信息</span>
        <span class="na">leftInfo</span> <span class="p">:</span> <span class="p">{},</span>
        <span class="na">rightInfo</span> <span class="p">:</span> <span class="p">{}</span>
      <span class="p">},</span>
    <span class="p">],</span>
    <span class="na">catagoryVis</span> <span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span><span class="na">leftVis</span> <span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">rightVis</span> <span class="p">:</span> <span class="kc">false</span><span class="p">},</span> <span class="c1">// 第一层可见性</span>
      <span class="p">{</span><span class="na">leftVis</span> <span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">rightVis</span> <span class="p">:</span> <span class="kc">null</span><span class="p">}</span> <span class="c1">// 第二层可见性</span>
    <span class="p">],</span>
    <span class="na">catagoryDir</span> <span class="p">:</span> <span class="p">[</span>
      <span class="p">{</span> <span class="c1">// 第一层目录</span>
        <span class="na">leftDir</span> <span class="p">:</span> <span class="p">[],</span>
        <span class="na">rightDir</span> <span class="p">:</span> <span class="p">[]</span>
      <span class="p">},</span>
    <span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">catagoryInfo</code> 中列出了直接展示在主页面的文章目录信息, 包括名字,封面等等</p>

<p><code class="language-plaintext highlighter-rouge">catagoryDir</code> 中包含了点击文章目录信息后展示的内容</p>

<p><code class="language-plaintext highlighter-rouge">catagoryVis</code> 代表 <code class="language-plaintext highlighter-rouge">catagoryDir</code> 的可见性, 取值为 <code class="language-plaintext highlighter-rouge">false</code> , 不存在则为 <code class="language-plaintext highlighter-rouge">null</code></p>]]></content><author><name>yyym</name><email>yinmingyang89@gmail.com</email></author><summary type="html"><![CDATA[]]></summary></entry></feed>