<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Jeffrey</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://cdn.jsdelivr.net/gh/huzhanfei/blog-assets/images/favicon-32x32.png</icon>
  <id>https://www.bytelife.net/</id>
  <link href="https://www.bytelife.net/" rel="alternate"/>
  <link href="https://www.bytelife.net/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Jeffrey</rights>
  <subtitle>技术交流与学习自留地</subtitle>
  <title>ByteLife</title>
  <updated>2026-09-03T17:11:19.415Z</updated>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="大数据" scheme="https://www.bytelife.net/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="kafka" scheme="https://www.bytelife.net/tags/kafka/"/>
    <category term="中间件" scheme="https://www.bytelife.net/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/"/>
    <category term="消息队列" scheme="https://www.bytelife.net/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/"/>
    <content>
      <![CDATA[<p>Kafka 作为大数据领域常用的消息中间件，其核心原理相对于其它消息中间件而言更为复杂，本文主要介绍 Kafka 消费者的 rebalance 机制以及 controller broker 选举机制、副本选举机制等实现原理。</p><span id="more"></span><h2 id="Kafka简易拓扑结构"><a href="#Kafka简易拓扑结构" class="headerlink" title="Kafka简易拓扑结构"></a>Kafka 简易拓扑结构</h2><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/05/092353406p8YZt.png"></p><h2 id="Kafka核心总控制器Controller"><a href="#Kafka核心总控制器Controller" class="headerlink" title="Kafka核心总控制器Controller"></a>Kafka 核心总控制器 Controller</h2><p>在 Kafka 集群中会有一个或者多个 broker，其中有一个 broker 会被选举为控制器（Kafka Controller），它负责管理整个集群中所有分区和副本的状态。</p><ul><li>当某个分区的 leader 副本出现故障时，由控制器负责为该分区选举新的 leader 副本。 </li><li>当检测到某个分区的 ISR 集合发生变化时，由控制器负责通知所有 broker 更新其元数据信息。 </li><li>当使用<code>kafka-topics.sh</code>脚本为某个 topic 增加分区数量时，同样还是由控制器负责分区的重新分配。</li></ul><h3 id="Controller选举机制"><a href="#Controller选举机制" class="headerlink" title="Controller选举机制"></a>Controller 选举机制</h3><p>在 kafka 集群启动的时候，会自动选举一台 broker 作为 controller 来管理整个集群，选举的过程是集群中每个 broker 都会尝试在 zookeeper 上创建一个<code>/controller</code>临时节点，zookeeper 会保证有且仅有一个 broker 能创建成功，这个 broker 就会成为集群的总控器 controller。<br>当这个 controller 角色的 broker 宕机了，此时 zookeeper 临时节点会消失，集群里其他 broker 会一直监听这个临时节点，发现临时节点消失了，就竞争再次创建临时节点，zookeeper 又会保证有一个 broker 成为新的 controller。</p><h3 id="Controller职责"><a href="#Controller职责" class="headerlink" title="Controller职责"></a>Controller 职责</h3><p>具备控制器身份的 broker 需要比其他普通的 broker 多一份职责，具体细节如下：</p><ol><li>监听 broker 相关的变化。为 Zookeeper 中的<code>/brokers/ids/</code>节点添加<code>BrokerChangeListener</code>，用来处理 broker 增减的变化。</li><li>监听 topic 相关的变化。为 Zookeeper 中的<code>/brokers/topics</code>节点添加<code>TopicChangeListener</code>，用来处理 topic 增减的变化；为 Zookeeper 中的<code>/admin/delete_topics</code>节点添加<code>TopicDeletionListener</code>，用来处理删除 topic 的动作。</li><li>从 Zookeeper 中读取获取当前所有与 topic、partition 以及 broker 有关的信息并进行相应的管理。对于所有 topic 所对应的 Zookeeper 中的<code>/brokers/topics/[topic]</code>节点添加<code>PartitionModificationsListener</code>，用来监听 topic 中的分区分配变化。</li><li>更新集群的元数据信息，同步到其他普通的 broker 节点中。</li></ol><h2 id="Partition-Replicates副本选举机制"><a href="#Partition-Replicates副本选举机制" class="headerlink" title="Partition Replicates副本选举机制"></a>Partition Replicates 副本选举机制</h2><p>controller 感知到分区 leader 所在的 broker 挂了 (controller 监听了很多 zk 节点可以感知到 broker 存活)，controller 会从每个 parititon 的 replicas 副本列表中取出第一个 broker 作为 leader，当然这个 broker 需要也同时在 ISR 列表里。</p><h2 id="Consumer-Rebalance机制"><a href="#Consumer-Rebalance机制" class="headerlink" title="Consumer Rebalance机制"></a>Consumer Rebalance 机制</h2><h3 id="消费者消费消息的offset记录机制"><a href="#消费者消费消息的offset记录机制" class="headerlink" title="消费者消费消息的offset记录机制"></a>消费者消费消息的 offset 记录机制</h3><p>每个 consumer 会定期将自己消费分区的 offset 提交给 kafka 内部 topic：<code>__consumer_offsets</code>，提交过去的时候，key 是<code>consumerGroupId+topic+分区号</code>，value 就是当前 offset 的值，kafka 会定期清理 topic 里的消息，最后就保留最新的那条数据，因为<code>__consumer_offsets</code>可能会接收高并发的请求，kafka 默认给其分配 50 个分区 (可以通过<code>offsets.topic.num.partitions</code>设置)，这样可以通过加机器的方式抗大并发。</p><h3 id="消费者Rebalance机制"><a href="#消费者Rebalance机制" class="headerlink" title="消费者Rebalance机制"></a>消费者 Rebalance 机制</h3><p>消费者 rebalance 发生在如果 consumer group 中某个消费者挂了，此时会自动把分配给他的分区交给其他的消费者，如果他又重启了，那么又会把一些分区重新交还给他如下情况可能会触发消费者 rebalance，常见的情况如下：</p><ol><li>consumer 所在服务重启或宕机了</li><li>动态给 topic 增加了分区</li><li>消费组订阅了更多的 topic</li></ol><h3 id="Rebalance过程"><a href="#Rebalance过程" class="headerlink" title="Rebalance过程"></a>Rebalance 过程</h3><p>当有消费者加入消费组时，消费者、消费组及组协调器之间会经历以下几个阶段。 </p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/05/100006544WYbPX.png"></p><h4 id="第一阶段：选择组协调器"><a href="#第一阶段：选择组协调器" class="headerlink" title="第一阶段：选择组协调器"></a>第一阶段：选择组协调器</h4><p><strong>组协调器 GroupCoordinator</strong>：每个<code>consumer group</code>都会选择一个 broker 作为自己的组协调器 coordinator，负责监控这个消费组里的所有消费者的心跳，以及判断是否宕机，然后开启消费者 rebalance。<code>consumer group</code>中的每个 consumer 启动时会向 kafka 集群中的某个节点发送<code>FindCoordinatorRequest</code>请求来查找对应的组协调器<code>GroupCoordinator</code>，并跟其建立网络连接。 </p><p><strong>组协调器选择方式</strong>：通过如下公式可以选出 consumer 消费的 offset 要提交到<code>__consumer_offsets</code>的哪个分区，这个分区 leader 对应的 broker 就是这个<code>consumer group</code>的 coordinator</p><p><strong>公式</strong>：<code>hash(consumer group id) % __consumer_offsets主题的分区数</code></p><h4 id="第二阶段：加入消费组JOIN-GROUP"><a href="#第二阶段：加入消费组JOIN-GROUP" class="headerlink" title="第二阶段：加入消费组JOIN GROUP"></a>第二阶段：加入消费组 JOIN GROUP</h4><p>在成功找到消费组所对应的<code>GroupCoordinator</code>之后就进入加入消费组的阶段，在此阶段的消费者会向<code>GroupCoordinator</code>发送<code>JoinGroupRequest</code>请求，并处理响应。然后<code>GroupCoordinator</code>从一个<code>consumer group</code>中选择<strong>第一个</strong>加入 group 的 consumer 作为<strong> leader (消费组协调器)</strong>，把<code>consumer group</code>情况发送给这个 leader，接着这个 leader 会负责制定分区方案（由于 rebalance 等策略有客户端配置决定，因此分区方案需要 consumer 来制定，以消费组协调器的配置为准）。</p><h4 id="第三阶段：SYNC-GROUP"><a href="#第三阶段：SYNC-GROUP" class="headerlink" title="第三阶段：SYNC GROUP"></a>第三阶段：SYNC GROUP</h4><p><code>consumer leader</code>通过给<code>GroupCoordinator</code>发送<code>SyncGroupRequest</code>，接着<code>GroupCoordinator</code>就把分区方案下发给各个 consumer，他们会根据指定分区的 leader broker 进行网络连接以及消息消费。</p><h3 id="消费者Rebalance分区分配策略"><a href="#消费者Rebalance分区分配策略" class="headerlink" title="消费者Rebalance分区分配策略"></a>消费者 Rebalance 分区分配策略</h3><p>主要有三种 rebalance 的策略：<code>range</code>、<code>round-robin</code>、<code>sticky</code>。 Kafka 提供了消费者客户端参数<code>partition.assignment.strategy</code>来设置消费者与订阅主题之间的分区分配策略。</p><p>默认情况为 range 分配策略，假设一个主题有 10 个分区 (0-9)，现在有三个 consumer 消费： </p><ul><li><strong>range 策略</strong>：按照分区序号排序，假设 <code>n＝分区数／消费者数量=3</code>，<code>m＝分区数%消费者数量 = 1</code>，那么前<code>m</code>个消费者每个分配<code>n+1</code>个分区，后面的（<code>消费者数量－m</code>）个消费者每个分配<code>n</code>个分区。比如分区 0-3 给一个 consumer，分区 4-6 给一个 consumer，分区 7-9 给一个 consumer。 </li><li><strong>round-robin 策略</strong>：轮询分配，比如分区 0、3、6、9 给一个 consumer，分区 1、4、7 给一个 consumer，分区 2、5、8 给一个 consumer</li><li><strong>sticky 策略</strong>：在 rebalance 的时候，需要保证如下两个原则。 <ol><li>分区的分配要尽可能均匀。 </li><li>分区的分配尽可能与上次分配的保持相同。</li></ol></li></ul><div class="note warning"><p>sticky 策略当两者发生冲突时，第一个目标优先于第二个目标。<br>这样可以最大程度维持原来的分区分配的策略。比如对于第一种 range 情况的分配，如果第三个 consumer 挂了，那么重新用 sticky 策略分配的结果如下： </p><ul><li>consumer1 除了原有的 0~3，会再分配一个 7</li><li>consumer2 除了原有的 4~6，会再分配 8 和 9</li></ul></div>]]>
    </content>
    <id>https://www.bytelife.net/articles/62460.html</id>
    <link href="https://www.bytelife.net/articles/62460.html"/>
    <published>2021-05-09T15:49:38.000Z</published>
    <summary>
      <![CDATA[<p>Kafka 作为大数据领域常用的消息中间件，其核心原理相对于其它消息中间件而言更为复杂，本文主要介绍 Kafka 消费者的 rebalance 机制以及 controller broker 选举机制、副本选举机制等实现原理。</p>]]>
    </summary>
    <title>Kafka Rebalance 机制和选举策略总结</title>
    <updated>2026-09-03T17:11:19.415Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="建站" scheme="https://www.bytelife.net/categories/website/"/>
    <category term="Hexo" scheme="https://www.bytelife.net/tags/Hexo/"/>
    <category term="腾讯云" scheme="https://www.bytelife.net/tags/%E8%85%BE%E8%AE%AF%E4%BA%91/"/>
    <content>
      <![CDATA[<p>近期把博客折腾到腾讯云了，对比了腾讯云和阿里云后，发现腾讯云近期 CDN 做了很多升级，对于 CDN 缓存节点的配置更加细致，另外毕竟腾讯云价格更低嘛，对个人站长更友好一些。这篇文章主要讲讲如何使用腾讯云的 COS+CDN 部署静态网站，以及使用腾讯云函数服务完美解决（对官方提供的刷新函数做了些优化）CDN 节点的缓存自动刷新问题。</p><span id="more"></span><h2 id="我的部署方案"><a href="#我的部署方案" class="headerlink" title="我的部署方案"></a>我的部署方案</h2><ul><li>运行环境：站点解析采用境内和境外分开解析的方案，境内解析到腾讯云的 CDN 节点，境外解析到 Vercel 的 CDN 节点，虽然站点的主要访问由于就是国内用户，但毕竟谷歌、必应等等搜索引擎都是国外的，而国内 CDN 对于境外的加速又比较贵，所以就分开解析啦。<ul><li>境内：腾讯云 COS 对象存储 + 腾讯云 CDN 加速</li><li>境外：Vercel 静态网站托管</li></ul></li><li>自动化部署：由于站点的源码托管在 GitHub 上面，所以使用 GitHub Actions 进行自动化部署，写完文章直接 push 上去就可以自动部署到多个平台，简直不要太方便。</li></ul><h2 id="GitHub-Actions自动部署到腾讯云COS"><a href="#GitHub-Actions自动部署到腾讯云COS" class="headerlink" title="GitHub Actions自动部署到腾讯云COS"></a>GitHub Actions 自动部署到腾讯云 COS</h2><p>腾讯云提供了非常好用的 cli 工具，执行一些简单的命令就可以快速上传文件到 COS 对象存储。在 Git 工程的根目录下创建<code>.github/workflows/xxx.yml</code>文件，就可以创建一个 Actions 配置。<br>使用下面的配置可以将 Hexo 部署到腾讯云 COS 对象存储：</p><figure class="highlight yaml"><figcaption><span>.github/workflows/deploy.yml</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># workflow</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">Blog</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">master</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">deploy:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">Blog</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">env:</span></span><br><span class="line">      <span class="attr">TZ:</span> <span class="string">Asia/Shanghai</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">    <span class="comment"># check it to your workflow can access it</span></span><br><span class="line">    <span class="comment"># from: https://github.com/actions/checkout</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">Repository</span> <span class="string">master</span> <span class="string">branch</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line">      <span class="attr">with:</span> </span><br><span class="line">        <span class="attr">ref:</span> <span class="string">'master'</span></span><br><span class="line">        <span class="attr">submodules:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># from: https://github.com/actions/setup-node</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Node.js</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">actions/setup-node@master</span></span><br><span class="line">      <span class="attr">with:</span> </span><br><span class="line">        <span class="attr">node-version:</span> <span class="string">"14.x"</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Yarn</span> <span class="string">Install</span> <span class="string">Cache</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">c-hive/gha-yarn-cache@v1</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Dependencies</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">yarn</span> <span class="string">install</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">## generate files</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Generate</span> <span class="string">Hexo</span> <span class="string">Site</span> <span class="string">Public</span> <span class="string">Files</span> <span class="string">&amp;</span> <span class="string">Create</span> <span class="string">Files</span> <span class="string">for</span> <span class="string">Blog</span> <span class="string">Assets</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">yarn</span> <span class="string">build</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">## deploy to tencent cos</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">to</span> <span class="string">Tencent</span> <span class="string">COS</span></span><br><span class="line">      <span class="attr">env:</span></span><br><span class="line">        <span class="attr">SECRET_ID:</span> <span class="string">${{</span> <span class="string">secrets.TENCENT_SECRET_ID</span> <span class="string">}}</span></span><br><span class="line">        <span class="attr">SECRET_KEY:</span> <span class="string">${{</span> <span class="string">secrets.TENCENT_SECRET_KEY</span> <span class="string">}}</span></span><br><span class="line">        <span class="attr">BUCKET:</span> <span class="string">${{</span> <span class="string">secrets.TENCENT_COS_BUCKET</span> <span class="string">}}</span></span><br><span class="line">        <span class="attr">REGION:</span> <span class="string">ap-shanghai</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        sudo pip install coscmd</span></span><br><span class="line"><span class="string">        coscmd config -a ${SECRET_ID} -s ${SECRET_KEY} -b ${BUCKET} -r ${REGION}</span></span><br><span class="line"><span class="string">        coscmd upload -rs --delete ./public/ / -f</span></span><br></pre></td></tr></tbody></table></figure><p>其中：</p><ul><li><code>${{ secrets.TENCENT_SECRET_ID }}</code>、<code>${{ secrets.TENCENT_SECRET_KEY }}</code>、<code>${{ secrets.TENCENT_COS_BUCKET }}</code>需要在 github 仓库的 secrets 设置中进行配置。</li><li>命令<code>coscmd upload -rs --delete ./public/ / -f</code>的意思是比较当前 COS 存储桶内的文件，如果发生变更则更新，如果存储桶中存在但 public 目录中不存在，则删除对应的文件，<code>coscmd</code>的具体使用方法可以参考<span class="exturl" data-url="aHR0cHM6Ly9jbG91ZC50ZW5jZW50LmNvbS9kb2N1bWVudC9wcm9kdWN0LzQzNi8xMDk3Ng==">腾讯云官方文档<i class="fa fa-external-link-alt"></i></span>。</li></ul><h2 id="开启COS静态网站功能"><a href="#开启COS静态网站功能" class="headerlink" title="开启COS静态网站功能"></a>开启 COS 静态网站功能</h2><p>将网站文件上传到 COS 还不够，需要开启 COS 的静态网站功能，打开<strong>存储桶 -&gt; 基础配置 -&gt; 静态网站</strong>，按照下图所示进行配置：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/24222121JQqFld.png"></p><p>这时就可以通过图中的访问节点提供的域名来访问网站了，当然也可以绑定自定义域名，前提是域名已经备案。</p><h2 id="腾讯云CDN加速"><a href="#腾讯云CDN加速" class="headerlink" title="腾讯云CDN加速"></a>腾讯云 CDN 加速</h2><h3 id="创建域名"><a href="#创建域名" class="headerlink" title="创建域名"></a>创建域名</h3><p>如果你的域名有备案的话，同时可以使用腾讯云 CDN 加速 COS 静态网站，配置十分简单，在 CDN 页面中创建一个域名，按下图所示进行配置：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/24222607oTRVmO.png"></p><h3 id="配置缓存"><a href="#配置缓存" class="headerlink" title="配置缓存"></a>配置缓存</h3><p>缓存配置十分重要，由于站点是静态站点，网站的内容变化比较少，为了降低 CDN 的回源请求，一定要配置节点缓存策略，可以参考我的配置如下，其中浏览器缓存可以按需配置：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/242230517Zd4wT.png"></p><p>注意：这里不用担心节点缓存时间配置的过长会导致页面无法更新，后面我会讲如何配置缓存的自动刷新。</p><p>关于 CDN 配置的更多细节就不讲了，都很简单，一看就懂。</p><h2 id="CDN节点缓存自动刷新"><a href="#CDN节点缓存自动刷新" class="headerlink" title="CDN节点缓存自动刷新"></a>CDN 节点缓存自动刷新</h2><h3 id="官方方案"><a href="#官方方案" class="headerlink" title="官方方案"></a>官方方案</h3><p>由于上一步中配置了 CDN 节点的缓存策略，请求只要能够命中缓存，就不会进行回源请求了，这会导致我们的页面更新不能及时的展现给用户，因此需要考虑如何进行 CDN 节点的缓存自动刷新。</p><p>腾讯云官方给我们提供了一个解决方案，可以在 COS 存储桶的<strong>函数计算 -&gt;CDN 缓存刷新函数</strong>中配置一个函数，可参考下图所示配置：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/242240109L8lbS.png"></p><p>但这个方案存在一个问题，由于我们的静态网站有默认索引页面 index.html，而官方提供的这个函数只会刷新对应的文件的 URL，而不会刷新索引 URL，例如<code>http://www.bytelife.net/index.html</code>这个文件，通常我们的请求是<code>http://www.bytelife.net/</code>，因此官方的方案针对于静态网站来说不算完美。</p><h3 id="优化方案"><a href="#优化方案" class="headerlink" title="优化方案"></a>优化方案</h3><p>可以通过简单修改官方的函数来解决这个问题，点击刚刚创建的<code>CDN缓存刷新函数</code>列表中的函数名称，可以跳转的函数的编辑页面：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/24225038nCibnY.png"></p><p>将 index.js 文件内容替换为下面的代码，最后点击右上角的 “部署” 按钮即可：</p><figure class="highlight js"><figcaption><span>index.js</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">'use strict'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CosSdk</span> = <span class="built_in">require</span>(<span class="string">'cos-nodejs-sdk-v5'</span>)</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CdnSdk</span> = <span class="built_in">require</span>(<span class="string">'./common/CdnSdk'</span>)</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CdnRefreshTask</span> = <span class="built_in">require</span>(<span class="string">'./common/CdnRefreshTask'</span>)</span><br><span class="line"><span class="keyword">const</span> {</span><br><span class="line">  getParams,</span><br><span class="line">  getObjectUrl,</span><br><span class="line">  logger,</span><br><span class="line">  getLogSummary</span><br><span class="line">} = <span class="built_in">require</span>(<span class="string">'./common/utils'</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">main_handler</span> = <span class="title function_">async</span> (event, context, callback) =&gt; {</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * parse param from event and process.env</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">const</span> { objects, cdnHosts, secretId, secretKey, token } = <span class="title function_">getParams</span>(event)</span><br><span class="line"></span><br><span class="line">  <span class="title function_">logger</span>({</span><br><span class="line">    <span class="attr">title</span>: <span class="string">'param is parsed success, param as follow: '</span>,</span><br><span class="line">    <span class="attr">data</span>: { objects, cdnHosts, event }</span><br><span class="line">  })</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * init cos instance</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">if</span> (!secretId || !secretKey || !token) {</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`secretId, secretKey or token is missing`</span>)</span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> cdnSdkInstance = <span class="keyword">new</span> <span class="title class_">CdnSdk</span>({ secretId, secretKey, token })</span><br><span class="line">  <span class="keyword">const</span> cosInstance = <span class="keyword">new</span> <span class="title class_">CosSdk</span>({</span><br><span class="line">    <span class="title class_">SecretId</span>: secretId,</span><br><span class="line">    <span class="title class_">SecretKey</span>: secretKey,</span><br><span class="line">    <span class="title class_">XCosSecurityToken</span>: token</span><br><span class="line">  })</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> taskList = objects.<span class="title function_">map</span>(<span class="function">(<span class="params">{ bucket, region, key }</span>) =&gt;</span> {</span><br><span class="line">    <span class="keyword">const</span> purgeUrls = [];</span><br><span class="line">    <span class="comment">// 主要变更内容在这个位置</span></span><br><span class="line">    cdnHosts.<span class="title function_">forEach</span>(<span class="function"><span class="params">host</span> =&gt;</span> {</span><br><span class="line">      <span class="keyword">const</span> tempUrl = <span class="title function_">getObjectUrl</span>({</span><br><span class="line">        cosInstance,</span><br><span class="line">        bucket,</span><br><span class="line">        region,</span><br><span class="line">        key,</span><br><span class="line">        <span class="attr">origin</span>: <span class="string">`<span class="subst">${/^(http\:\/\/|https\:\/\/)/.test(host) ? <span class="string">''</span> : <span class="string">'https://'</span>}</span><span class="subst">${host}</span>`</span></span><br><span class="line">      });</span><br><span class="line">      purgeUrls.<span class="title function_">push</span>(tempUrl);</span><br><span class="line">      <span class="comment">// 如果以 /index.html 结尾，则增加目录首页/</span></span><br><span class="line">      <span class="comment">// 例如 https://www.xxxx.com/index.html, 则增加 https://www.xxxx.com/</span></span><br><span class="line">      <span class="keyword">if</span>(tempUrl.<span class="title function_">lastIndexOf</span>(<span class="string">'/index.html'</span>) == (tempUrl.<span class="property">length</span> - <span class="number">11</span>)){</span><br><span class="line">        purgeUrls.<span class="title function_">push</span>(tempUrl.<span class="title function_">substr</span>(<span class="number">0</span>, tempUrl.<span class="property">length</span> - <span class="number">10</span>))</span><br><span class="line">      }</span><br><span class="line">    });</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CdnRefreshTask</span>({</span><br><span class="line">      cdnSdkInstance,</span><br><span class="line">      <span class="attr">urls</span>: purgeUrls</span><br><span class="line">    })</span><br><span class="line">  })</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> taskResults = []</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> task <span class="keyword">of</span> taskList) {</span><br><span class="line">    <span class="keyword">const</span> results = <span class="keyword">await</span> task.<span class="title function_">runPurgeTasks</span>()</span><br><span class="line">    taskResults.<span class="title function_">push</span>(...results)</span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="title function_">logger</span>({</span><br><span class="line">    <span class="attr">title</span>: <span class="string">'cdn refresh full logs:'</span>,</span><br><span class="line">    <span class="attr">data</span>: taskResults</span><br><span class="line">  })</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> { status, messages } = <span class="title function_">getLogSummary</span>(taskResults)</span><br><span class="line"></span><br><span class="line">  <span class="title function_">logger</span>({</span><br><span class="line">    <span class="attr">messages</span>: messages.<span class="title function_">map</span>(<span class="function"><span class="params">item</span> =&gt;</span> item.<span class="title function_">replace</span>(<span class="regexp">/\,\ /g</span>, <span class="string">'\n'</span>))</span><br><span class="line">  })</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (status === <span class="string">'fail'</span>) {</span><br><span class="line">    <span class="keyword">throw</span> messages.<span class="title function_">join</span>(<span class="string">'; '</span>)</span><br><span class="line">  } <span class="keyword">else</span> {</span><br><span class="line">    <span class="keyword">return</span> messages.<span class="title function_">join</span>(<span class="string">'; '</span>)</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.bytelife.net/articles/47472.html</id>
    <link href="https://www.bytelife.net/articles/47472.html"/>
    <published>2021-03-24T13:34:04.000Z</published>
    <summary>
      <![CDATA[<p>近期把博客折腾到腾讯云了，对比了腾讯云和阿里云后，发现腾讯云近期 CDN 做了很多升级，对于 CDN 缓存节点的配置更加细致，另外毕竟腾讯云价格更低嘛，对个人站长更友好一些。这篇文章主要讲讲如何使用腾讯云的 COS+CDN 部署静态网站，以及使用腾讯云函数服务完美解决（对官方提供的刷新函数做了些优化）CDN 节点的缓存自动刷新问题。</p>]]>
    </summary>
    <title>Hexo 静态网站托管到腾讯云 COS+CDN 加速以及缓存自动刷新完美方案</title>
    <updated>2026-09-03T17:11:19.415Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="软件技巧" scheme="https://www.bytelife.net/categories/software/"/>
    <category term="黑苹果" scheme="https://www.bytelife.net/tags/%E9%BB%91%E8%8B%B9%E6%9E%9C/"/>
    <category term="OpenCore" scheme="https://www.bytelife.net/tags/OpenCore/"/>
    <content>
      <![CDATA[<div class="note warning"><p>本文章内容仅为展示使用，不保证内容更新，最新版本 EFI 文件请前往我的 GitHub 仓库：<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2h1emhhbmZlaS9BU1VTLVBSSU1FLVozNzBNLVBMVVMtSUktT1BFTkNPUkUtRUZJ">https://github.com/huzhanfei/ASUS-PRIME-Z370M-PLUS-II-OPENCORE-EFI<i class="fa fa-external-link-alt"></i></span></p></div><p>华硕 ASUS Prime Z370M-PLUS II 黑苹果 Hackintosh OpenCore EFI 文件，支持核显 + 独显共同硬解，<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2FjaWRhbnRoZXJhL09wZW5Db3JlUGtn">OpenCore<i class="fa fa-external-link-alt"></i></span> 版本：0.6.7</p><span id="more"></span><h2 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h2><ul><li><input checked="" disabled="" type="checkbox"> 声卡（板载）/ 网卡（板载）</li><li><input checked="" disabled="" type="checkbox"> 显卡（核显 + 独显）/ 硬解 4K（HEVC + H.264）</li><li><input checked="" disabled="" type="checkbox"> WiFi（PCI-E 设备） / 蓝牙（PEI-E 载 USB 设备）</li><li><input checked="" disabled="" type="checkbox"> 隔空投送 / 接力 / 随航</li><li><input checked="" disabled="" type="checkbox"> FaceTime / iMessage</li><li><input checked="" disabled="" type="checkbox"> 睿频 / HWP 变频 / 原生电源管理</li><li><input checked="" disabled="" type="checkbox"> 睡眠 / 键盘、鼠标唤醒</li><li><input checked="" disabled="" type="checkbox"> 其他白果功能（99%）</li></ul><h2 id="硬件"><a href="#硬件" class="headerlink" title="硬件"></a>硬件</h2><h3 id="我的配置"><a href="#我的配置" class="headerlink" title="我的配置"></a>我的配置</h3><table><thead><tr><th>硬件</th><th>型号</th></tr></thead><tbody><tr><td>CPU</td><td>Intel Core i5 9600K</td></tr><tr><td>主板</td><td>华硕 Asus Prime Z370m-PLUS II</td></tr><tr><td>显卡</td><td>迪兰恒进 RX 580 8G 战神</td></tr><tr><td>硬盘</td><td>HP EX900 M.2 NVMe 250GB</td></tr><tr><td>内存</td><td>英睿达 8GB DDR4 3200MHz x 4</td></tr><tr><td>无线 + 蓝牙</td><td>BCM943602CS（双频 1750Mbps + 蓝牙 4.2）PCI-E x1 无线网卡</td></tr><tr><td>机箱 + 电源 + 散热</td><td>乔思伯 V4 + 长城 500W + 利民 AXP-90I + 利民 TF8 硅脂</td></tr><tr><td>显示器</td><td>AOC 2K</td></tr><tr><td>音箱</td><td>漫步者 R1600T III</td></tr><tr><td>键盘</td><td>罗技 MX Keys</td></tr><tr><td>鼠标</td><td>罗技 MX Master 2S</td></tr></tbody></table><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><h3 id="BIOS设置"><a href="#BIOS设置" class="headerlink" title="BIOS设置"></a>BIOS 设置</h3><h4 id="关闭"><a href="#关闭" class="headerlink" title="关闭"></a>关闭</h4><ul><li>快速启动</li><li>安全启动</li><li>VT-d（如果<code>DisableIoMapper</code>设置为 True，则可以开启）</li><li>CSM</li><li>Intel SGX</li><li>Intel Platform Trust</li><li>CFG Lock</li></ul><h4 id="开启"><a href="#开启" class="headerlink" title="开启"></a>开启</h4><ul><li>VT-x</li><li>大于 4G 解码（Above 4G decoding）</li><li>EHCI/XHCI Hand-off</li><li>操作系统类型：Windows 8.1/10 UEFI Mode</li><li>DVMT Pre-Allocated(iGPU Memory): 64MB</li><li>SATA Mode: AHCI</li><li>ErP：S4+S5</li></ul><h3 id="核显配置"><a href="#核显配置" class="headerlink" title="核显配置"></a>核显配置</h3><p>修改 config.plist 的 DeviceProperties &gt; Add &gt; PciRoot (0x0)/Pci (0x2,0x0) &gt; AAPL,ig-platform-id 值为下面任意一个，均可开启核显满频：</p><ul><li><p><code>0300913E</code>：适用于有独立显卡，不使用核显驱动显示器，只用作计算。系统报告中不显示核显信息，但可使用核显加速。</p></li><li><p><code>00009B3E</code>：适用于没有独立显卡，使用核显驱动显示器，系统报告中显示核显信息（有独立显卡也可使用此配置，但开机速度略慢）</p></li></ul><h3 id="HWP变频"><a href="#HWP变频" class="headerlink" title="HWP变频"></a>HWP 变频</h3><p>Kexts 中的<code>CPUFriendDataProvider.kext</code>为 9600K 专用文件，如果你使用的是其它型号的 CPU，需要自行更换</p><h3 id="USB映射"><a href="#USB映射" class="headerlink" title="USB映射"></a>USB 映射</h3><p>映射端口如图所示，应该可以满足绝大多数情况，如果和你的 USB 端口情况不符，请自行映射替换 USBPorts.kext<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/15010853TCrvKd.png"></p><h3 id="修改SMBIOS"><a href="#修改SMBIOS" class="headerlink" title="修改SMBIOS"></a>修改 SMBIOS</h3><p>自行生成 SMBIOS 替换图中所示内容：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/15010920ZKLahB.png"></p><h2 id="截图"><a href="#截图" class="headerlink" title="截图"></a>截图</h2><h3 id="关于本机"><a href="#关于本机" class="headerlink" title="关于本机"></a>关于本机</h3><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/15010946ubbOj2.png"></p><h3 id="显卡信息"><a href="#显卡信息" class="headerlink" title="显卡信息"></a>显卡信息</h3><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/15011001153Air.png"></p><h3 id="变频"><a href="#变频" class="headerlink" title="变频"></a>变频</h3><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/15011058IVKnKe.png"></p><h3 id="硬解"><a href="#硬解" class="headerlink" title="硬解"></a>硬解</h3><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/15011040UKKHwe.png"></p><h3 id="iMessage"><a href="#iMessage" class="headerlink" title="iMessage"></a>iMessage</h3><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/15011107iojGua.png"></p><h3 id="隔空投送"><a href="#隔空投送" class="headerlink" title="隔空投送"></a>隔空投送</h3><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/15011132y53o6u.png"></p><h2 id="更新记录"><a href="#更新记录" class="headerlink" title="更新记录"></a>更新记录</h2><h3 id="2021-03-15"><a href="#2021-03-15" class="headerlink" title="2021-03-15"></a>2021-03-15</h3><ul><li>升级 OpenCore 0.6.7</li><li>升级 AppleALC 1.5.8</li><li>升级 Lilu 1.5.1</li><li>升级 WhateverGreen 1.4.8</li><li>升级 VirtualSMC 1.2.1</li><li>升级 CPUFriend 1.2.3</li><li>升级 IntelMausiEthernet 1.0.5</li></ul><h3 id="2020-11-17"><a href="#2020-11-17" class="headerlink" title="2020-11-17"></a>2020-11-17</h3><ul><li>升级 OpenCore 0.6.3</li><li>升级 AppleALC 1.5.4</li><li>升级 Lilu 1.4.9</li><li>升级 WhateverGreen 1.4.4</li><li>升级 VirtualSMC 1.1.8</li><li>升级 CPUFriend 1.2.2</li><li>升级 IntelMausiEthernet 1.0.4</li></ul><h3 id="2020-09-22"><a href="#2020-09-22" class="headerlink" title="2020-09-22"></a>2020-09-22</h3><ul><li>升级 OpenCore 0.6.1</li><li>升级 AppleALC 1.5.2</li><li>升级 Lilu 1.4.7</li><li>升级 WhateverGreen 1.4.2</li><li>升级 VirtualSMC 1.1.6</li></ul><h3 id="2020-08-11"><a href="#2020-08-11" class="headerlink" title="2020-08-11"></a>2020-08-11</h3><ul><li>升级 AppleALC 1.5.1</li><li>升级 Lilu 1.4.6</li><li>升级 WhateverGreen 1.4.1</li><li>升级 CPUFriend 1.2.1</li><li>升级 VirtualSMC 1.1.5</li></ul>]]>
    </content>
    <id>https://www.bytelife.net/articles/16681.html</id>
    <link href="https://www.bytelife.net/articles/16681.html"/>
    <published>2021-03-14T17:13:37.000Z</published>
    <summary>
      <![CDATA[<div class="note warning"><p>本文章内容仅为展示使用，不保证内容更新，最新版本 EFI 文件请前往我的 GitHub 仓库：<a href="https://github.com/huzhanfei/ASUS-PRIME-Z370M-PLUS-II-OPENCORE-EFI">https://github.com/huzhanfei/ASUS-PRIME-Z370M-PLUS-II-OPENCORE-EFI</a></p>
</div>

<p>华硕 ASUS Prime Z370M-PLUS II 黑苹果 Hackintosh OpenCore EFI 文件，支持核显 + 独显共同硬解，<a href="https://github.com/acidanthera/OpenCorePkg">OpenCore</a> 版本：0.6.7</p>]]>
    </summary>
    <title>华硕 ASUS Prime Z370M-PLUS II 黑苹果 OpenCore EFI</title>
    <updated>2021-03-14T17:13:37.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="异常" scheme="https://www.bytelife.net/tags/%E5%BC%82%E5%B8%B8/"/>
    <category term="validator" scheme="https://www.bytelife.net/tags/validator/"/>
    <category term="工作日志" scheme="https://www.bytelife.net/tags/%E5%B7%A5%E4%BD%9C%E6%97%A5%E5%BF%97/"/>
    <content>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>最近遇到一个问题，使用 Hibernate Validator 做参数校验时，本地环境运行无任何问题，打包上线以后报下面的异常：</p><span id="more"></span><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">HV000221: An error occurred while extracting values in value extractor org.hibernate.validator.internal.engine.valueextraction.MapValueExtractor.</span><br><span class="line">at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)</span><br><span class="line">at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)</span><br><span class="line">at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)</span><br><span class="line">at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)</span><br><span class="line">at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)</span><br><span class="line">...</span><br></pre></td></tr></tbody></table></figure><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>排查了好一阵，最后实在没有办法怀疑到了 JDK 头上，没想到真的是 JDK 的问题。<br>线上环境运行的 JDK 版本是 8u20，升级到 8u191 后问题解决。</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/42700.html</id>
    <link href="https://www.bytelife.net/articles/42700.html"/>
    <published>2021-03-14T15:10:31.000Z</published>
    <summary>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>最近遇到一个问题，使用 Hibernate Validator 做参数校验时，本地环境运行无任何问题，打包上线以后报下面的异常：</p>]]>
    </summary>
    <title>Hibernate Validator 异常 HV000221 解决办法</title>
    <updated>2021-03-14T15:10:31.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="大数据" scheme="https://www.bytelife.net/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="ElasticSearch" scheme="https://www.bytelife.net/tags/ElasticSearch/"/>
    <category term="聚合查询" scheme="https://www.bytelife.net/tags/%E8%81%9A%E5%90%88%E6%9F%A5%E8%AF%A2/"/>
    <category term="搜索引擎" scheme="https://www.bytelife.net/tags/%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/"/>
    <content>
      <![CDATA[<p>本文将讨论如何在 ElasticSearch 中使用 nested 结构进行数据的存储、查询和聚合，并结合 K-V 场景讨论 ElasticSearch 针对 field 数量限制的解决方案。</p><span id="more"></span><h2 id="为何要使用Nested结构存储KV（键值对）"><a href="#为何要使用Nested结构存储KV（键值对）" class="headerlink" title="为何要使用Nested结构存储KV（键值对）?"></a>为何要使用 Nested 结构存储 KV（键值对）?</h2><p>ElasticSearch 对于 field 的数量有限制，默认情况下 field 的数量如果超过 1000 个，写入时再创建新的 fields 就会报错：</p><figure class="highlight stylus"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">java<span class="selector-class">.lang</span><span class="selector-class">.IllegalArgumentException</span>: Limit of total fields <span class="selector-attr">[1000]</span> <span class="keyword">in</span> index <span class="selector-attr">[(index_name)]</span> has been exceeded</span><br><span class="line"> at org<span class="selector-class">.elasticsearch</span><span class="selector-class">.index</span><span class="selector-class">.mapper</span><span class="selector-class">.MapperService</span><span class="selector-class">.checkTotalFieldsLimit</span>(MapperService<span class="selector-class">.java</span>:<span class="number">630</span>)</span><br></pre></td></tr></tbody></table></figure><p>但有些场景的 field 数量并不是我们能控制的，例如在监控系统中的业务数据所携带的业务标签，其中可能包含了监控系统不能预知的业务字段。<br>对于这种情景，可能想到的解决方案两个：</p><ol><li>调整 ElasticSearch 的配置，增加 field 的限制数量：这种方案仅仅适用于可以预测出 field 数量极限的情况，治标不治本，一旦 field 数量再次抵达限制，又会面临同样的问题。</li><li>就是使用 Pair 结构来存储</li></ol><p>假设第 2 种方案的数据结构为：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"ip"</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">"value"</span><span class="punctuation">:</span> <span class="string">"127.0.0.1"</span></span><br><span class="line">     <span class="punctuation">}</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"ip"</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">"value"</span><span class="punctuation">:</span> <span class="string">"127.0.0.2"</span></span><br><span class="line">     <span class="punctuation">}</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>那么 es 查询就会存在一个问题，例如下面的查询：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"query"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"bool"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"must"</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">                <span class="punctuation">{</span></span><br><span class="line">                    <span class="attr">"match"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line">                        <span class="attr">"key"</span><span class="punctuation">:</span><span class="string">"ip"</span></span><br><span class="line">                    <span class="punctuation">}</span></span><br><span class="line">                <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">                <span class="punctuation">{</span></span><br><span class="line">                    <span class="attr">"match"</span><span class="punctuation">:</span><span class="punctuation">{</span></span><br><span class="line">                        <span class="attr">"value"</span><span class="punctuation">:</span><span class="string">"127.0.0.1"</span></span><br><span class="line">                    <span class="punctuation">}</span></span><br><span class="line">                <span class="punctuation">}</span></span><br><span class="line">            <span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">}</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>这个查询会把例子中的的数据全部查询出来，并不符合我们的预期。这是因为 es 在存储索引时，对于普通 object 类型的 field 实际上是打平来存储的，比如这样：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels.key"</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">        <span class="string">"ip"</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">"labels.value"</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">        <span class="string">"127.0.0.1"</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">"127.0.0.2"</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>可以看见，索引打平后，对象的关联关系丢失了。对于这种情况，ElasticSearch 提供的 nested 结构可以帮助我们解决类似的问题。Nested 结构保留了子文档数据中的关联性，如果 labels 的数据格式被定义为 nested，那么每一个 nested object 将会作为一个隐藏的单独文本建立索引。如下：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">     <span class="attr">"labels.key"</span><span class="punctuation">:</span><span class="string">"ip"</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">"labels.value"</span><span class="punctuation">:</span><span class="string">"127.0.0.1"</span></span><br><span class="line"><span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">{</span></span><br><span class="line">     <span class="attr">"labels.key"</span><span class="punctuation">:</span><span class="string">"ip"</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">"labels.value"</span><span class="punctuation">:</span><span class="string">"127.0.0.2"</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>通过分开给每个 nested object 建索引，object 内部的字段间的关系就能保持。当执行查询时，只会匹配’match’同时出现在相同的 nested object 的结果。</p><h2 id="定义mappings"><a href="#定义mappings" class="headerlink" title="定义mappings"></a>定义 mappings</h2><p>使用 nested 结构非常简单，指定字段的 type 为 nested 即可。下面的例子中定义了一个名为 labels 的 nested 结构，其中包含两个字段，分别是 key 和 value。</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"mappings"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"demoType"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"labels"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                <span class="comment">// 字段类型设置为nested</span></span><br><span class="line">                <span class="attr">"type"</span><span class="punctuation">:</span> <span class="string">"nested"</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">"properties"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                    <span class="attr">"key"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                        <span class="attr">"type"</span><span class="punctuation">:</span> <span class="string">"keyword"</span></span><br><span class="line">                    <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">"value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                        <span class="attr">"type"</span><span class="punctuation">:</span> <span class="string">"keyword"</span></span><br><span class="line">                    <span class="punctuation">}</span></span><br><span class="line">                <span class="punctuation">}</span></span><br><span class="line">            <span class="punctuation">}</span></span><br><span class="line">        <span class="punctuation">}</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><h2 id="查询"><a href="#查询" class="headerlink" title="查询"></a>查询</h2><p>nested 结构的数据查询和普通 object 略有不同，nested object 作为一个独立隐藏文档单独建索引，因此，不能直接查询到它们。取而代之，我们必须使用 nested 查询或者 nested filter。例如：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">  <span class="attr">"query"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"bool"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">      <span class="attr">"must"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">{</span></span><br><span class="line">          <span class="attr">"nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"path"</span><span class="punctuation">:</span> <span class="string">"labels"</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">"query"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">              <span class="attr">"bool"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                <span class="attr">"must"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                  <span class="punctuation">{</span></span><br><span class="line">                    <span class="attr">"term"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                      <span class="attr">"labels.key"</span><span class="punctuation">:</span> <span class="string">"ip"</span></span><br><span class="line">                    <span class="punctuation">}</span></span><br><span class="line">                  <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">                  <span class="punctuation">{</span></span><br><span class="line">                    <span class="attr">"term"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                      <span class="attr">"labels.value"</span><span class="punctuation">:</span> <span class="string">"127.0.0.1"</span></span><br><span class="line">                    <span class="punctuation">}</span></span><br><span class="line">                  <span class="punctuation">}</span></span><br><span class="line">                <span class="punctuation">]</span></span><br><span class="line">              <span class="punctuation">}</span></span><br><span class="line">            <span class="punctuation">}</span></span><br><span class="line">          <span class="punctuation">}</span></span><br><span class="line">        <span class="punctuation">}</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line">  <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>这个查询可以返回我们预期的正确结果：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span><span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"ip"</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">"value"</span><span class="punctuation">:</span> <span class="string">"127.0.0.1"</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span><span class="punctuation">]</span></span><br></pre></td></tr></tbody></table></figure><h2 id="分桶聚合"><a href="#分桶聚合" class="headerlink" title="分桶聚合"></a>分桶聚合</h2><p>查询的问题解决了，聚合时问题又来了，前面我们说到，nested 结构存储在一个隐藏的单独文本索引中，那么普通的聚合查询自然便无法访问到它们。因此，nested 结构在聚合时，需要使用特定的 nested 聚合。</p><h3 id="nested聚合"><a href="#nested聚合" class="headerlink" title="nested聚合"></a>nested 聚合</h3><p>假设 es 中存储如下数据：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">[</span><span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"ip"</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">"value"</span><span class="punctuation">:</span> <span class="string">"127.0.0.1"</span></span><br><span class="line">    <span class="punctuation">}</span><span class="punctuation">,</span><span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"os"</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">"value"</span><span class="punctuation">:</span> <span class="string">"windows"</span></span><br><span class="line">    <span class="punctuation">}</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">}</span><span class="punctuation">,</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels"</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"ip"</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">"value"</span><span class="punctuation">:</span> <span class="string">"127.0.0.2"</span></span><br><span class="line">    <span class="punctuation">}</span><span class="punctuation">,</span><span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"os"</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">"value"</span><span class="punctuation">:</span> <span class="string">"linux"</span></span><br><span class="line">    <span class="punctuation">}</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">}</span><span class="punctuation">]</span></span><br></pre></td></tr></tbody></table></figure><p>我们要聚合所有对<code>labels.value</code>进行聚合，可以使用下面的方式：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">  <span class="attr">"size"</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels_nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">      <span class="attr">"nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"path"</span><span class="punctuation">:</span> <span class="string">"labels"</span></span><br><span class="line">      <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"nested_value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">          <span class="attr">"terms"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"field"</span><span class="punctuation">:</span> <span class="string">"labels.value"</span></span><br><span class="line">          <span class="punctuation">}</span></span><br><span class="line">        <span class="punctuation">}</span></span><br><span class="line">      <span class="punctuation">}</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line">  <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>这个查询将会得到下面类似的结果：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">  <span class="attr">"aggregations"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels_nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">      <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">"nested_value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"buckets"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">          <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"127.0.0.1"</span></span><br><span class="line">          <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">          <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"127.0.0.2"</span></span><br><span class="line">          <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">          <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"windows"</span></span><br><span class="line">          <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">          <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"linux"</span></span><br><span class="line">          <span class="punctuation">}</span></span><br><span class="line">        <span class="punctuation">]</span></span><br><span class="line">      <span class="punctuation">}</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line">  <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><h3 id="过滤属性值"><a href="#过滤属性值" class="headerlink" title="过滤属性值"></a>过滤属性值</h3><p>上面的例子可以看到，其只是单纯的将所有的 value 进行了聚合，并没有针对 k-v 中的 key 进行过滤，因此导致<code>labels.key</code>为<code>ip</code>和<code>os</code>的数据均被统计到了其中，这通常不符合我们实际场景中的需求。</p><p>现在假设要对所有<code>labels.key</code>为<code>ip</code>的<code>labels.value</code>进行聚合，那么可以使用如下的方式：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">  <span class="attr">"size"</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels_nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">      <span class="attr">"nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"path"</span><span class="punctuation">:</span> <span class="string">"labels"</span></span><br><span class="line">      <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"nested_ip"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">          <span class="attr">"filter"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"term"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">              <span class="attr">"labels.key"</span><span class="punctuation">:</span> <span class="string">"ip"</span></span><br><span class="line">            <span class="punctuation">}</span></span><br><span class="line">          <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"nested_value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">              <span class="attr">"terms"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                <span class="attr">"field"</span><span class="punctuation">:</span> <span class="string">"labels.value"</span></span><br><span class="line">              <span class="punctuation">}</span></span><br><span class="line">            <span class="punctuation">}</span></span><br><span class="line">          <span class="punctuation">}</span></span><br><span class="line">        <span class="punctuation">}</span></span><br><span class="line">      <span class="punctuation">}</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line">  <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>通过这样的方式就可以把<code>labels.key</code>不是<code>ip</code>的文档过滤掉，经过这个查询将得到类似如下的结果：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">  <span class="attr">"aggregations"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels_nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">      <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">"nested_ip"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">"nested_value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">          <span class="attr">"buckets"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="punctuation">{</span></span><br><span class="line">              <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">              <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"127.0.0.1"</span></span><br><span class="line">            <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">            <span class="punctuation">{</span></span><br><span class="line">              <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">              <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"127.0.0.2"</span></span><br><span class="line">            <span class="punctuation">}</span></span><br><span class="line">          <span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">}</span></span><br><span class="line">      <span class="punctuation">}</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line">  <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><h3 id="nested多重聚合"><a href="#nested多重聚合" class="headerlink" title="nested多重聚合"></a>nested 多重聚合</h3><p>如果想在 nested 聚合下嵌套聚合其它字段，直接嵌套是不行的，这里需要使用到<code>reverse_nested</code>跳出当前 nested 聚合后，再进行嵌套聚合。<br><strong>注意：无论是嵌套其它 nested 字段还是普通字段，都需要使用 reverse_nested 跳出当前 nested 聚合。</strong></p><p>例如想对<code>labels.key</code>为<code>ip</code>聚合后，再对<code>labels.key</code>为<code>os</code>进行聚合：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">  <span class="attr">"size"</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels_nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">      <span class="attr">"nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"path"</span><span class="punctuation">:</span> <span class="string">"labels"</span></span><br><span class="line">      <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"nested_ip"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">          <span class="attr">"filter"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"term"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">              <span class="attr">"labels.key"</span><span class="punctuation">:</span> <span class="string">"ip"</span></span><br><span class="line">            <span class="punctuation">}</span></span><br><span class="line">          <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">            <span class="attr">"nested_ip_value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">              <span class="attr">"terms"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                <span class="attr">"field"</span><span class="punctuation">:</span> <span class="string">"labels.value"</span></span><br><span class="line">              <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">              <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                <span class="attr">"reverse_labels"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                  <span class="attr">"reverse_nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span><span class="punctuation">}</span><span class="punctuation">,</span> <span class="comment">//注意这里</span></span><br><span class="line">                  <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                    <span class="attr">"nested_os"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                      <span class="attr">"nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                        <span class="attr">"path"</span><span class="punctuation">:</span> <span class="string">"labels"</span></span><br><span class="line">                      <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">                      <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                        <span class="attr">"labels_os"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                          <span class="attr">"filter"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                            <span class="attr">"term"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                              <span class="attr">"labels.key"</span><span class="punctuation">:</span> <span class="string">"os"</span></span><br><span class="line">                            <span class="punctuation">}</span></span><br><span class="line">                          <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">                          <span class="attr">"aggs"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                            <span class="attr">"labels_os_value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                              <span class="attr">"terms"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                                <span class="attr">"field"</span><span class="punctuation">:</span> <span class="string">"labels.value"</span></span><br><span class="line">                              <span class="punctuation">}</span></span><br><span class="line">                            <span class="punctuation">}</span></span><br><span class="line">                          <span class="punctuation">}</span></span><br><span class="line">                        <span class="punctuation">}</span></span><br><span class="line">                      <span class="punctuation">}</span></span><br><span class="line">                    <span class="punctuation">}</span></span><br><span class="line">                  <span class="punctuation">}</span></span><br><span class="line">                <span class="punctuation">}</span></span><br><span class="line">              <span class="punctuation">}</span></span><br><span class="line">            <span class="punctuation">}</span></span><br><span class="line">          <span class="punctuation">}</span></span><br><span class="line">        <span class="punctuation">}</span></span><br><span class="line">      <span class="punctuation">}</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line">  <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>如此，将得到类似下面的结果：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line">  <span class="attr">"aggregations"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">    <span class="attr">"labels_nested"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">      <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">"nested_ip"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">        <span class="attr">"nested_ip_value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">          <span class="attr">"buckets"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="punctuation">{</span></span><br><span class="line">              <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">              <span class="attr">"reverse_labels"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">"nested_os"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                  <span class="attr">"labels_os"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                    <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">"labels_os_value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                      <span class="attr">"buckets"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                        <span class="punctuation">{</span></span><br><span class="line">                          <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                          <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"windows"</span></span><br><span class="line">                        <span class="punctuation">}</span></span><br><span class="line">                      <span class="punctuation">]</span></span><br><span class="line">                    <span class="punctuation">}</span></span><br><span class="line">                  <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">                  <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span></span><br><span class="line">                <span class="punctuation">}</span></span><br><span class="line">              <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">              <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"127.0.0.1"</span></span><br><span class="line">            <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">            <span class="punctuation">{</span></span><br><span class="line">              <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">              <span class="attr">"reverse_labels"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">"nested_os"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                  <span class="attr">"labels_os"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                    <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">"labels_os_value"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line">                      <span class="attr">"buckets"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                        <span class="punctuation">{</span></span><br><span class="line">                          <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                          <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"linux"</span></span><br><span class="line">                        <span class="punctuation">}</span></span><br><span class="line">                      <span class="punctuation">]</span></span><br><span class="line">                    <span class="punctuation">}</span></span><br><span class="line">                  <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">                  <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">1</span></span><br><span class="line">                <span class="punctuation">}</span></span><br><span class="line">              <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">              <span class="attr">"key"</span><span class="punctuation">:</span> <span class="string">"127.0.0.2"</span></span><br><span class="line">            <span class="punctuation">}</span></span><br><span class="line">          <span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">"doc_count"</span><span class="punctuation">:</span> <span class="number">2</span></span><br><span class="line">      <span class="punctuation">}</span></span><br><span class="line">    <span class="punctuation">}</span></span><br><span class="line">  <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>至此，关于 nested 结构存储 K-V 的用法就介绍完啦！使用 nested 结构可以帮助我们保持 object 内部的关联性，借此解决 elasticsearch 对 field 数量的限制。nested 结构不仅可以应用在 K-V 结构的场景，还可以应用于其它任何需要保持 object 内部关联性的场景。</p><p><strong>注意：使用 nested 结构也会存在一些问题：</strong></p><ul><li>增加，改变或者删除一个 nested 文本，整个文本必须重新建索引。nested 文本越多，代价越大。</li><li>检索请求会返回整个文本，而不仅是匹配的 nested 文本。尽管有计划正在执行以能够支持返回根文本的同时返回最匹配的 nested 文本，但目前还未实现。</li></ul>]]>
    </content>
    <id>https://www.bytelife.net/articles/51440.html</id>
    <link href="https://www.bytelife.net/articles/51440.html"/>
    <published>2021-03-06T20:59:20.000Z</published>
    <summary>
      <![CDATA[<p>本文将讨论如何在 ElasticSearch 中使用 nested 结构进行数据的存储、查询和聚合，并结合 K-V 场景讨论 ElasticSearch 针对 field 数量限制的解决方案。</p>]]>
    </summary>
    <title>ElasticSearch (ES) 使用 Nested 结构存储 KV 及聚合查询</title>
    <updated>2021-03-07T04:46:20.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="建站" scheme="https://www.bytelife.net/categories/website/"/>
    <category term="Hexo" scheme="https://www.bytelife.net/tags/Hexo/"/>
    <category term="阿里云" scheme="https://www.bytelife.net/tags/%E9%98%BF%E9%87%8C%E4%BA%91/"/>
    <content>
      <![CDATA[<p>现在用 Hexo 来搭建静态站点的人越来越多了，很多人会选择试用 Github Pages 服务来部署自己的静态站，但 Github Pages 国内访问速度实在无法接受，本文介绍一下如何一键使用阿里云 OSS 对象存储服务部署 Hexo 站点，同时解决一个浏览器缓存的坑。</p><span id="more"></span><style type="">  /* post-图片阴影 */.posts-expand .post-body img {  box-shadow: none!important;}</style><h2 id="加速方案"><a href="#加速方案" class="headerlink" title="加速方案"></a>加速方案</h2><ul><li>Netlify：与 Github 配合使用很方便，但国内访问速度不佳，需要搭配国内 CDN 使用</li><li>Vercel：与 Netlify 类似，国内访问速度不错，域名没有备案的话是个不错的选择</li><li>对象存储：使用阿里云 OSS 或类似的对象存储服务，国内访问速度不错，也可搭配国内 CDN 使用，速度最好，但要求你的域名有<strong>备案号</strong></li><li>境内对象存储 + 境外 Vercel/Netlify：我所采用的方案，应该是最佳的方案了</li></ul><h2 id="创建Bucket"><a href="#创建Bucket" class="headerlink" title="创建Bucket"></a>创建 Bucket</h2><p>首先要做的事情就是去阿里云 (<span class="exturl" data-url="aHR0cHM6Ly9hbGl5dW4uY29tLw==">https://aliyun.com<i class="fa fa-external-link-alt"></i></span>) 创建一个 Bucket:<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144490476215.jpg"></p><ul><li>Bucket 名称：随便起，只要不重复就行</li><li>地域：按你的需求，阿里云的节点国内访问速度都可以，如果你不打算使用 CDN 服务的话，可以选择华东节点，全国访问速度都能兼顾。如果使用 CDN 服务的话，那就没区别了，选哪都行。</li><li>存储类型：一般选择标准存储就可以了，当然如果访问量很小的话，选择低频访问存储也可以，可以省一点钱。</li><li>同城冗余存储：不需要</li><li>版本控制：不需要</li><li>读写权限：这里要注意，我们选择<strong>公共读</strong>，切记不能选择私有，一方面如果不开启 CDN 的话，私有权限无法在公网访问。另一方面，如果开启 CDN，阿里云的 CDN 私有回源功能与 OSS 的静态页面功能冲突（别问我是怎么知道的，都是坑）</li><li>服务端加密方式：无</li><li>实时日志查询：看需求，有免费 7 天的实时日志查询，但有 900GB / 天的限制</li><li>定时备份：不开通</li></ul><h2 id="开启静态页面功能"><a href="#开启静态页面功能" class="headerlink" title="开启静态页面功能"></a>开启静态页面功能</h2><p>进入 Bucket 的配置页面，选择 “基础设置 - 静态页面”：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144491145634.jpg"><br>按照图中的内容进行配置：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144492022270.jpg"></p><ul><li>默认首页：index.html</li><li>默认 404 页：404.html（按照你实际的 404 页面地址填写）</li><li>子目录首页：开通，这里由于 Hexo 的分类、标签等页面，都是子目录的形式（例如：/tags/），如果不开通这个功能，不会自动定位到对应目录的 index.html</li><li>文件 404 规则：Index</li></ul><h2 id="创建AccessKey并授权"><a href="#创建AccessKey并授权" class="headerlink" title="创建AccessKey并授权"></a>创建 AccessKey 并授权</h2><p>其实现在就可以在阿里云的管理页面上传文件部署网站了，但实在是太麻烦，我们希望的是一个自动化的方案。这里我们需要创建一个 API 的授权 AccessKey：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144500122868.jpg"><br>阿里云现在开启了子账户授权的功能，因此我们可以选择跳转到子账户管理的页面：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144500659516.jpg"><br>首先创建一个子账户：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144501107194.jpg"><br>这里注意，由于我们这个账户是给 API 使用的，所以必须开启编程访问权限：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144501894558.jpg"><br>返回以后，点击刚创建的账户右侧的 “添加权限”：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144502596730.jpg"><br>这里搜索 “OSS”，选择添加<code>AliyunOSSFullAccess</code>权限：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144503464152.jpg"><br>返回列表，点击刚创建的账户名称，创建一个 AccessKey，这里要注意，创建完 AccessKey 后，会显示一个 AccessKey 和一个 SecretKey，一定要保存好，只会显示一次：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144505338176.jpg"></p><h2 id="安装一键部署脚本"><a href="#安装一键部署脚本" class="headerlink" title="安装一键部署脚本"></a>安装一键部署脚本</h2><p>我编写了一个可以一键部署 Hexo 到阿里云 OSS 的 hexo 插件，并支持配置浏览器缓存 HTTP 响应头。<br>源码地址：<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2h1emhhbmZlaS9oZXhvLWRlcGxveWVyLWFsaS1vc3MtZXh0ZW5k">https://github.com/huzhanfei/hexo-deployer-ali-oss-extend<i class="fa fa-external-link-alt"></i></span></p><p>使用很简单，在 hexo 项目下执行安装命令：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i hexo-deployer-ali-oss-extend</span><br></pre></td></tr></tbody></table></figure><p>在 hexo 项目配置文件<code>_config.yml</code>中添加如下配置：</p><figure class="highlight yaml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">deploy:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">type:</span> <span class="string">ali-oss</span></span><br><span class="line">  <span class="attr">region:</span> <span class="string">"&lt;您的oss 区域代码&gt;"</span></span><br><span class="line">  <span class="attr">accessKeyId:</span> <span class="string">"&lt;您的oss  accessKeyId&gt;"</span></span><br><span class="line">  <span class="attr">accessKeySecret:</span> <span class="string">"&lt;您的oss accessKeySecret&gt;"</span></span><br><span class="line">  <span class="attr">bucket:</span> <span class="string">"&lt;您的bucket name&gt;"</span></span><br><span class="line">  <span class="attr">cacheControl:</span></span><br><span class="line">    <span class="attr">images:</span> <span class="string">"&lt;图片文件HTTP响应头Cache-Control&gt;，留空为no-cache"</span></span><br><span class="line">    <span class="attr">css:</span> <span class="string">"&lt;CSS文件HTTP响应头Cache-Control&gt;，留空为no-cache"</span></span><br><span class="line">    <span class="attr">js:</span> <span class="string">"&lt;JS文件HTTP响应头Cache-Control&gt;，留空为no-cache"</span></span><br><span class="line">    <span class="attr">html:</span> <span class="string">"&lt;HTML文件HTTP响应头Cache-Control&gt;，留空为no-cache"</span></span><br><span class="line">    <span class="attr">other:</span> <span class="string">"&lt;其它文件HTTP响应头Cache-Control&gt;，留空为no-cache"</span></span><br></pre></td></tr></tbody></table></figure><p>其中区域代码，可以在 OSS 的 Bucket 概览当中找到，如下图所示：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144516114701.jpg"></p><p>另外，这里增加 Cache-Control 功能，是由于 OSS 对象存储并没有针对常见的网页文件（如 css、js、图片等）进行默认 HTTP 响应头的设置，这些不常更新的文件不缓存，会导致流量大量浪费，后面如果使用 CDN 的时候，CDN 获取不到缓存配置也会进行不断的回源，产生回源流量浪费。</p><p>配置就这么简单，然后执行部署命令：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo d</span><br></pre></td></tr></tbody></table></figure><p>即可将项目部署到 OSS 中，默认情况下将文件上传到 bucket 的根目录下，如果需要部署到其他目录，请在 deploy 下添加 remotePath 选项进行指定</p><figure class="highlight yaml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">remotePath:</span> <span class="string">"&lt;您要部署的目录&gt;"</span></span><br></pre></td></tr></tbody></table></figure><p>当然，如果你使用了 GitHub 的 Actions 功能，可以配置自动执行 hexo d，提交源码的时候自动执行部署脚本，简直不要太方便，具体配置方式不是本文讨论的重点内容，可自行研究。</p><h2 id="配置自定义域名"><a href="#配置自定义域名" class="headerlink" title="配置自定义域名"></a>配置自定义域名</h2><p>其实做到这一步，就已经可以访问你的网站了，可以在 Bucket 概览页面找到你的访问域名，当然我们大概率需要使用自己的域名访问，如果你的域名已经进行了备案，可以添加自定义域名。</p><p>可以在 “传输管理 - 域名管理” 中配置域名：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144521040524.jpg"><br>点击 “绑定域名”，在如图所示的内容中填入自己的域名，如果你的域名解析服务器是阿里云的话，可以点击 “自动添加 CNAME 记录” 快速设置域名解析：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16144521363658.jpg"><br>如果你的域名不在阿里云解析的话，请根据页面提示手动设置域名解析。</p><h2 id="后话"><a href="#后话" class="headerlink" title="后话"></a>后话</h2><p>到此，就可以通过你的域名访问站点了，你也可以上传 SSL 证书以开启 HTTPS 访问。<br>当然，如果你有更高的要求，也可以选择更多的优化：</p><ul><li>开启 CDN 服务：开启 CDN 服务后，相当于在全国多地部署你了的镜像站，以获取更快的访问速度，同时可以通过 CDN 服务开启图片压缩、页面优化等等功能</li><li>分区域解析：设置 DNS 解析将国内解析到阿里云 OSS 或 CDN，国外解析到 Netlify、Vercel 甚至直接解析到 GitHub Pages 服务</li></ul>]]>
    </content>
    <id>https://www.bytelife.net/articles/52761.html</id>
    <link href="https://www.bytelife.net/articles/52761.html"/>
    <published>2021-02-27T19:04:36.000Z</published>
    <summary>
      <![CDATA[<p>现在用 Hexo 来搭建静态站点的人越来越多了，很多人会选择试用 Github Pages 服务来部署自己的静态站，但 Github Pages 国内访问速度实在无法接受，本文介绍一下如何一键使用阿里云 OSS 对象存储服务部署 Hexo 站点，同时解决一个浏览器缓存的坑。</p>]]>
    </summary>
    <title>Hexo 一键部署到阿里云 OSS 并设置浏览器缓存</title>
    <updated>2021-02-28T18:06:20.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="算法" scheme="https://www.bytelife.net/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>问题： 给定数组 arr，arr 中的所有的值都为正数且不重复。每个值代表一种面值的货币，每种面值的货币可以使用任意张，再给定一个整数 aim 代表要找的钱数，求换钱有多少种方法。</p><span id="more"></span><h2 id="暴力搜索方法"><a href="#暴力搜索方法" class="headerlink" title="暴力搜索方法"></a>暴力搜索方法</h2><h3 id="思路分析"><a href="#思路分析" class="headerlink" title="思路分析"></a>思路分析</h3><p>若给定 arr={5, 10, 25, 1}，aim=1000。</p><ul><li>用 0 张 5 元的货币，让 [10, 25, 1] 组成剩下的 1000 元，最终方法数记作——res1；</li><li>用 1 张 5 元的货币，让 [10, 25, 1] 组成剩下的 995 元，最终方法数记作——res2；</li><li>用 2 张 5 元的货币，让 [10, 25, 1] 组成剩下的 990 元，最终方法数记作——res3；</li><li>用 3 张 5 元的货币，让 [10, 25, 1] 组成剩下的 985 元，最终方法数记作——res4；</li><li>……</li><li>用 200 张 5 元的货币，让 [10, 25, 1] 组成剩下的 0 元，最终方法数记作——res201；</li></ul><p>则 res1、res2……res201 的累加和即为最终的结果。</p><h3 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h3><p>定义递归函数：<code>int process1(arr, index, aim)</code>, 它的含义是如果用 arr [index……N-1] 这些面值的钱组成 aim，返回总的方法数。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">coins1</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> aim)</span> {</span><br><span class="line">    <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    <span class="keyword">if</span> (arr == <span class="literal">null</span> || arr.length == <span class="number">0</span> || aim &lt; <span class="number">0</span>) {</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    }</span><br><span class="line">    <span class="type">int</span> <span class="variable">result</span> <span class="operator">=</span> process1(arr,<span class="number">0</span>,aim);</span><br><span class="line">    <span class="type">long</span> <span class="variable">endTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    System.out.println(<span class="string">"暴力搜索方法所用时间："</span> + (endTime - startTime) +<span class="string">"ms"</span>);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">}</span><br><span class="line">    </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">process1</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> index, <span class="type">int</span> aim)</span> {</span><br><span class="line">    <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 判断是否所有面值的货币均已经计算完</span></span><br><span class="line">    <span class="keyword">if</span> (index == arr.length) {</span><br><span class="line">        <span class="comment">// 判断本次递归调用时钱的总数是否已经凑够，如果已经凑够则将总方法数加1</span></span><br><span class="line">        res = aim == <span class="number">0</span> ? <span class="number">1</span> : <span class="number">0</span>; </span><br><span class="line">    } <span class="keyword">else</span> { </span><br><span class="line">        <span class="comment">// 循环计算i张当前面值的货币</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; arr[index] * i &lt;= aim; i++) {</span><br><span class="line">            <span class="comment">// 递归调用当使用i张当前面值的货币时，用其它货币组成剩下的钱</span></span><br><span class="line">            res += process1(arr, index + <span class="number">1</span>, aim - arr[index] * i);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>暴力搜索方法比较好理解，但他在计算中存在大量的重复递归过程。 例如已经使用了 0 张 5 元和 1 张 10 元货币的情况下，后续将求： <code>process1(arr,2,990)</code> 而当计算使用 2 张 5 元和 0 张 10 元时，后续同样需要求： <code>process1(arr,2,990)</code> 因此这种重复的递归运算将造成大量的时间浪费。</p><h2 id="记忆搜索方法"><a href="#记忆搜索方法" class="headerlink" title="记忆搜索方法"></a>记忆搜索方法</h2><h3 id="思路分析-1"><a href="#思路分析-1" class="headerlink" title="思路分析"></a>思路分析</h3><p>由于暴力搜索方法中存在大量的重复递归，因此我们可以使用一个 “记忆库” 用于存储已经计算过的值，在本题中，使用 index 货币组成剩下的 aim 钱的值是一一对应的，因此可以使用 int mem [index][aim] 数组表示记忆库，其元素值为可以组成的方法数。</p><h3 id="具体实现-1"><a href="#具体实现-1" class="headerlink" title="具体实现"></a>具体实现</h3><ul><li>每计算完一个 process (index,aim)，都将结果放入 mem 中，index 和 aim 组成共同的 key，返回结果为 value。</li><li>要进入一个递归过程时，先以 index 和 aim 组成的 key 在 mem 中进行查询，如果已经存在 value，则直接使用，如果不存在，再进入递归过程。</li></ul><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">process2</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> index, <span class="type">int</span> aim, <span class="type">int</span>[][] mem)</span> {</span><br><span class="line">    <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 判断是否所有面值的货币均已经计算完</span></span><br><span class="line">    <span class="keyword">if</span> (index == arr.length) {</span><br><span class="line">        <span class="comment">// 判断本次递归调用时钱的总数是否已经凑够，如果已经凑够则将总方法数加1</span></span><br><span class="line">        res = aim == <span class="number">0</span> ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="type">int</span> <span class="variable">memVal</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 循环计算i张当前面值的货币</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; arr[index] * i &lt;= aim; i++) {</span><br><span class="line">            <span class="comment">// 获取记忆库中当使用i张index货币时，用其它货币组成剩下的钱</span></span><br><span class="line">            memVal = mem[index + <span class="number">1</span>][aim - arr[index] * i];</span><br><span class="line">            <span class="comment">// 判断记忆库中存在记录</span></span><br><span class="line">            <span class="keyword">if</span> (memVal != <span class="number">0</span>) {</span><br><span class="line">                <span class="comment">// 将记忆库中的方法数累加到结果中</span></span><br><span class="line">                res += memVal == -<span class="number">1</span> ? <span class="number">0</span> : memVal;</span><br><span class="line">            } <span class="keyword">else</span> {</span><br><span class="line">                <span class="comment">// 递归调用当使用i张当前面值的货币时，用其它货币组成剩下的钱</span></span><br><span class="line">                res += process2(arr, index + <span class="number">1</span>, aim - arr[index] * i, mem);</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 将使用index货币组成aim钱的结果存储到记忆库中</span></span><br><span class="line">    mem[index][aim] = res == <span class="number">0</span> ? -<span class="number">1</span> : res;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="动态规划方法"><a href="#动态规划方法" class="headerlink" title="动态规划方法"></a>动态规划方法</h2><h3 id="思路分析-2"><a href="#思路分析-2" class="headerlink" title="思路分析"></a>思路分析</h3><p>如果 arr 长度为 N，生成行数为 N，列数为 aim+1 的矩阵 dp。 dp [i][j] 的含义是在使用 arr [0]…arr [i] 货币的情况下，组成钱数 j 的方法数。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138517477927.jpg"></p><ol><li>如果完全不用 arr [i] 货币，只使用 arr [0]…arr [i-1] 货币时，方法数为 dp [i-1][j]。</li><li>如果用 1 张 arr [i] 货币，剩下的钱使用 arr [0]…arr [i-1] 货币组成，方法数为 dp [i-1][j-1*arr [i]]。</li><li>如果用 2 张 arr [i] 货币，剩下的钱使用 arr [0]…arr [i-1] 货币组成，方法数为 dp [i-1][j-1*arr [i]]。</li><li>如果用 3 张 arr [i] 货币，剩下的钱使用 arr [0]…arr [i-1] 货币组成，方法数为 dp [i-1][j-1*arr [i]]。</li><li>……</li></ol><p>dp [i][j] 的值即为上述所有值得累加和。 求每一个位置都需要枚举，时间复杂度为 O (aim)。dp 一共有 N*aim 个位置，所以总的时间复杂度为 O (N*aim2) 最终的结果值即为矩阵最右下角的 dp [N-1][aim]。</p><h4 id="记忆搜索方法与动态规划方法的联系"><a href="#记忆搜索方法与动态规划方法的联系" class="headerlink" title="记忆搜索方法与动态规划方法的联系"></a>记忆搜索方法与动态规划方法的联系</h4><ul><li>记忆化搜索方法就是某种形态的动态规划方法。</li><li>记忆化搜索不关心到达某一个递归路径的路程。只是单纯地堆计算过的递归过程进行记录，避免重复递归的过程。</li><li>动态规划方法则是规定好每一个递归霍城的计算顺序，一次进行计算，后面的计算过程严格依赖前面的计算过程。</li><li>两者都是空间换时间的方法，也有枚举的过程，区别在于动态规划规定计算顺序，而记忆搜索不用规定。</li></ul><h4 id="什么是动态规划方法"><a href="#什么是动态规划方法" class="headerlink" title="什么是动态规划方法"></a>什么是动态规划方法</h4><ul><li>本质是利用申请的空间来记录每一个暴力搜索的计算过程，下次要用结果的时候直接使用，而不再进行重复的递归过程。</li><li>动态规划规定每一种递归状态的计算顺序，依次进行计算。从简单到复杂，按顺序计算。</li></ul><h3 id="具体实现-2"><a href="#具体实现-2" class="headerlink" title="具体实现"></a>具体实现</h3><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">process3</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> aim)</span> {</span><br><span class="line">    <span class="comment">// 创建dp矩阵</span></span><br><span class="line">    <span class="type">int</span>[][] <span class="variable">dp</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">int</span>[arr.length][aim + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; dp.length; i++) {</span><br><span class="line">        dp[i][<span class="number">0</span>] = <span class="number">1</span>; <span class="comment">// 凑成0元的方法必然是什么货币都不用，只有1种</span></span><br><span class="line">        <span class="keyword">if</span> (i == <span class="number">0</span>) {</span><br><span class="line">            <span class="comment">// 如果只是用arr[0]这一种货币，则能凑到j钱置1</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; dp[i].length; j++) {</span><br><span class="line">                dp[i][j] = j % arr[i] == <span class="number">0</span> ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line">            }</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt; dp[i].length; j++) {</span><br><span class="line">                <span class="type">int</span> <span class="variable">temp</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">                <span class="comment">// 枚举使用k张arr[i]货币后dp[i-1]中组成剩下钱数的方法数</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> <span class="number">0</span>; k * arr[i] &lt;= j; k++) {</span><br><span class="line">                    temp += dp[i - <span class="number">1</span>][j - k * arr[i]];<span class="comment">//方法数累加</span></span><br><span class="line">                }</span><br><span class="line">                dp[i][j] = temp;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 返回dp矩阵最右下角的值即为最后结果</span></span><br><span class="line">    <span class="keyword">return</span> dp[arr.length - <span class="number">1</span>][aim];</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="动态规划方法的再优化"><a href="#动态规划方法的再优化" class="headerlink" title="动态规划方法的再优化"></a>动态规划方法的再优化</h2><h3 id="思路分析-3"><a href="#思路分析-3" class="headerlink" title="思路分析"></a>思路分析</h3><p>由于动态规划方法的执行顺序有着严格的规定，因此使得对算法的进一步优化成为可能。 对于刚才的问题中，我们需要枚举<code>dp[i-1][j-k*arr[i]]</code> (k=1,2,3…) 并与<code>dp[i-1][j]</code>累加，实际上<code>dp[i-1][j-k*arr[i]]</code> (k=1,2,3…) 的累加值就是<code>dp[i][j-arr[i]]</code>。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138517699600.jpg"><br>所以可以简化为： <code>dp[i][j] = dp[i][j-arr[i]] + dp[i-1][j]</code> 从而彻底省略枚举过程。时间复杂度从 O (N<em>aim2) 变为 O (N</em>aim)</p><h3 id="具体实现-3"><a href="#具体实现-3" class="headerlink" title="具体实现"></a>具体实现</h3><p>经过优化后的代码实现如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">process4</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> aim)</span> {</span><br><span class="line">    <span class="comment">// 创建dp矩阵</span></span><br><span class="line">    <span class="type">int</span>[][] <span class="variable">dp</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">int</span>[arr.length][aim + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; dp.length; i++) {</span><br><span class="line">        dp[i][<span class="number">0</span>] = <span class="number">1</span>; <span class="comment">// 凑成0元的方法必然是什么货币都不用，只有1种</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt; dp[i].length; j++) {</span><br><span class="line">            <span class="keyword">if</span> (i == <span class="number">0</span>) {</span><br><span class="line">                dp[i][j] = j % arr[i] == <span class="number">0</span> ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line">            } <span class="keyword">else</span> <span class="keyword">if</span>(j &gt;= arr[i]){</span><br><span class="line">                dp[i][j] = dp[i][j - arr[i]] + dp[i - <span class="number">1</span>][j];</span><br><span class="line">            } <span class="keyword">else</span> {</span><br><span class="line">                dp[i][j] = dp[i - <span class="number">1</span>][j];</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 返回dp矩阵最右下角的值即为最后结果</span></span><br><span class="line">    <span class="keyword">return</span> dp[arr.length - <span class="number">1</span>][aim];</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="动态规划方法的空间优化"><a href="#动态规划方法的空间优化" class="headerlink" title="动态规划方法的空间优化"></a>动态规划方法的空间优化</h2><p>我们可以看到，经过优化的动态规划方法速度已经非常让人满意，但是它的空间浪费却很严重，我们发现动态规划方法是严格的矩阵从上至下、从左至右的方向顺序计算，那么其实真正每次计算式只需要用到的是当前行与当前行的上一行，因此其实我们可以将原本的 dp 二维矩阵简化为一维向量。 通过读取和修改向量本身的元素值来达到目的，修改后的代码如下所示：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">process5</span><span class="params">(<span class="type">int</span>[] arr, <span class="type">int</span> aim)</span> {</span><br><span class="line">    <span class="comment">// 创建dp向量</span></span><br><span class="line">    <span class="type">int</span>[] <span class="variable">dp</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">int</span>[aim + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; arr.length; i++) {</span><br><span class="line">        dp[<span class="number">0</span>] = <span class="number">1</span>; <span class="comment">// 凑成0元的方法必然是什么货币都不用，只有1种</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt; dp.length; j++) {</span><br><span class="line">            <span class="keyword">if</span> (i == <span class="number">0</span>) {</span><br><span class="line">                dp[j] = j % arr[i] == <span class="number">0</span> ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line">            } <span class="keyword">else</span> <span class="keyword">if</span>(j &gt;= arr[i]){</span><br><span class="line">                dp[j] += dp[j - arr[i]];</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 返回dp向量尾元素即最终结果</span></span><br><span class="line">    <span class="keyword">return</span> dp[aim];</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="各种计算方法的运行速度对比"><a href="#各种计算方法的运行速度对比" class="headerlink" title="各种计算方法的运行速度对比"></a>各种计算方法的运行速度对比</h2><p>上述所有的实现代码中，都加入了记录算法开始时间和结束时间的代码，我们通过运行测试，得到下面的结果：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138517913272.jpg"></p><ul><li>可以看到，暴力搜索方法毋庸置疑是速度最慢的，因为其存在大量的重复递归过程。</li><li>记忆化搜索方法由于避免了重复递归，因此效率更高一些。</li><li>经过优化的动态规划方法可以看到在我的实测环境中，运行时间近乎为 0ms，可以说是非常快的。</li></ul><h2 id="暴力递归优化成动态规划方法的大体过程"><a href="#暴力递归优化成动态规划方法的大体过程" class="headerlink" title="暴力递归优化成动态规划方法的大体过程"></a>暴力递归优化成动态规划方法的大体过程</h2><ol><li>实现暴力递归方法；</li><li>在暴力搜索方法的函数中看看哪些参数可以代表递归过程。</li><li>找到代表递归过程的参数之后，记忆化搜索方法的实现非常容易。</li><li>通过分析记忆化搜索的依赖路径，进而实现动态规划。</li><li>根据记忆化搜索方法改出动态规划方法，进而看看是否能够化简，如果能化简，还能实现时间复杂度更低的动态规划方法。</li></ol><h2 id="动态规划方法的关键点"><a href="#动态规划方法的关键点" class="headerlink" title="动态规划方法的关键点"></a>动态规划方法的关键点</h2><ol><li><strong>最优化原理</strong>：也就是<strong>最优子结构性质</strong>。指一个最优化策略具有这样的性质，不论过去状态和决策如何，对前面的决策所形成的状态而言，余下的诸决策必须构成最优策略。简单来说就是一个最优化策略的子策略总是最优的，如果一个问题满足最优化原理，就成为其具有最优子结构性质。</li><li><strong>无后效性</strong>：指某状态下决策的收益，只与状态和决策相关，与到达该状态的路径无关。</li><li><strong>子问题的重叠性</strong>：动态规划将原来具有指数级时间复杂度的暴力搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余，这是动态规划算法的根本目的。</li></ol><blockquote><p>本文为博主学习牛客网课程<span class="exturl" data-url="aHR0cHM6Ly93d3cubm93Y29kZXIuY29tL2NvdXJzZXMvc2VtZXN0ZXIvYWxnb3JpdGht">《直通 BAT - 面试算法精讲课》<i class="fa fa-external-link-alt"></i></span>的学习笔记 如果需要购买该课程，可以使用博主的优惠码：<strong>Adg00aI</strong>，获取 10 元优惠。</p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/32340.html</id>
    <link href="https://www.bytelife.net/articles/32340.html"/>
    <published>2017-03-19T17:51:28.000Z</published>
    <summary>
      <![CDATA[<p>问题： 给定数组 arr，arr 中的所有的值都为正数且不重复。每个值代表一种面值的货币，每种面值的货币可以使用任意张，再给定一个整数 aim 代表要找的钱数，求换钱有多少种方法。</p>]]>
    </summary>
    <title>动态规划算法问题（经典找零案例）</title>
    <updated>2017-03-19T17:51:28.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="类加载" scheme="https://www.bytelife.net/tags/%E7%B1%BB%E5%8A%A0%E8%BD%BD/"/>
    <content>
      <![CDATA[<p>最近接到电话面试中，面试官问我 ClassNotFoundException 与 NoClassDefFoundError 有什么样的区别，至于前者经常遇到，我还是能答上一些，至于后者，完全没遇到过啊，只好瞎编了，都忘记当时是怎么回答的。然后面试官问我他们都会出现在哪些情况，ClassNotFoundException 我还可以说出几种情况，可是后者这下不能编了，只好求饶。结果面试官还给我讲解了一下，感觉非常尴尬。 回来以后又查阅了一些相关的资料，并尝试了一下，整理分享给网友。</p><span id="more"></span><h3 id="ClassNotFoundException"><a href="#ClassNotFoundException" class="headerlink" title="ClassNotFoundException"></a>ClassNotFoundException</h3><p>这个异常我们遇到的频率还是很高的，先来看看官方文档中的定义：</p><blockquote><p>当应用程序试图使用以下方法通过<strong>字符串名</strong>加载类时，抛出该异常：</p><ul><li>Class 类中的 forName 方法。</li><li>ClassLoader 类中的 findSystemClass 方法。</li><li>ClassLoader 类中的 loadClass 方法。</li></ul><p>但是没有找到具有指定名称的类的定义。 从 1.4 版本开始，此异常已经更新，以符合通用的异常链机制。在构造时提供并通过 <code>getException()</code> 方法访问的 “加载类时引发的可选异常”，现在被称为原因，它可以通过 <code>Throwable.getCause()</code> 方法以及与上面提到的 “遗留方法” 来访问。</p></blockquote><p>也就是说在<strong>类加载</strong>阶段，通过类全名加载类时，如果不能找到这个类，就会抛出 ClassNotFoundException，当然，通常在开发中可能出现该异常的情况总结如下：</p><ol><li>所需要的支持类库放错了地方，并没有放在类路径 (CLASSPATH 环境变量) 里面。</li><li>使用了重复的类库，且版本不一致。导致低版本的被优先使用。</li><li>类名错了，一般是使用 Class.forName 的时候，手工指定了类名的情况。</li><li>没有导入纯 JAVA 驱动包。</li></ol><p>当然啦，我经常遇到这个异常，尤其是在使用 JDBC 加载驱动的时候，驱动类的类全名真的是很难记的有木有！</p><h3 id="NoClassDefFoundError"><a href="#NoClassDefFoundError" class="headerlink" title="NoClassDefFoundError"></a>NoClassDefFoundError</h3><p>可以看到这个错误的类名以 Error 结尾，因此它属于 JVM 虚拟机错误，不需要程序员捕获，下图是它的继承结构：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138522353337.jpg"></p><p>还是同样先来看下官方文档中的定义：</p><blockquote><p>当 Java 虚拟机或 ClassLoader 实例试图在类的定义中加载（作为通常方法调用的一部分或者作为使用 new 表达式创建的新实例的一部分），但无法找到该类的定义时，抛出此异常。 当前执行的类被编译时，所搜索的类定义存在，但无法再找到该定义。</p></blockquote><p>通俗的说，就是编译的时候有这个类，而<strong>运行期</strong>的时候，这个类找不到了！就会抛出 NoClassDefFoundError 异常。 想要演示这个异常的出现很简单，只需要在编译好的 class 文件中将对应类的 class 文件删除，运行的时候就会抛出这个异常啦！ 那么在实际开发中，通常会在哪些情况遇到该异常呢？通常我们在开发过程中，肯定不会刻意的去删除某个 class 文件，但仍然有出现该异常的情况。 比如我们在使用第三方 jar 包时，有些 SDK 也会设定自己的 Classpath。编译过程结束后在运行的过程中就要将已开发的应用和所有引入的 jar 包拷贝到应用服务器的相应目录下才可以运行，而应用服务器使用的 Classpath 也很有可能与 SDK 的不同，在这个过程中就有很大的几率造成双方环境不一致。所以很多开发者就会遇到在 SDK 中可以编译，运行也没有问题，但是同样的程序放到应用服务器上就出现 NoClassDefFoundErr 这个异常这种情况。</p><blockquote><p><strong>参考文章</strong>： 1. JavaTM Platform Standard Edition 6 的 API 规范 2. <span class="exturl" data-url="aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZmVuZ2t1YW5ncWkvYXJ0aWNsZS9kZXRhaWxzLzMyMzI0MDUz">NoClassDefDoundErr 与 ClassNotFoundException 区别<i class="fa fa-external-link-alt"></i></span></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/60306.html</id>
    <link href="https://www.bytelife.net/articles/60306.html"/>
    <published>2017-03-18T02:57:13.000Z</published>
    <summary>
      <![CDATA[<p>最近接到电话面试中，面试官问我 ClassNotFoundException 与 NoClassDefFoundError 有什么样的区别，至于前者经常遇到，我还是能答上一些，至于后者，完全没遇到过啊，只好瞎编了，都忘记当时是怎么回答的。然后面试官问我他们都会出现在哪些情况，ClassNotFoundException 我还可以说出几种情况，可是后者这下不能编了，只好求饶。结果面试官还给我讲解了一下，感觉非常尴尬。 回来以后又查阅了一些相关的资料，并尝试了一下，整理分享给网友。</p>]]>
    </summary>
    <title>浅析 ClassNotFoundException 与 NoClassDefFoundError</title>
    <updated>2017-03-18T02:57:13.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <content>
      <![CDATA[<p>编码问题一直困扰着开发人员，尤其在 Java 中更加明显，因为 Java 是跨平台语言，不同平台之间编码之间的切换较多。本文将向你详细介绍 Java 中编码问题出现的根本原因，你将了解到：Java 中经常遇到的几种编码格式的区别；Java 中经常需要编码的场景；出现中文问题的原因分析；在开发 Java web 程序时可能会存在编码的几个地方，一个 HTTP 请求怎么控制编码格式？如何避免出现中文问题？</p><span id="more"></span><h2 id="几种常见的编码格式"><a href="#几种常见的编码格式" class="headerlink" title="几种常见的编码格式"></a>几种常见的编码格式</h2><h3 id="为什么要编码"><a href="#为什么要编码" class="headerlink" title="为什么要编码"></a>为什么要编码</h3><p>不知道大家有没有想过一个问题，那就是为什么要编码？我们能不能不编码？要回答这个问题必须要回到计算机是如何表示我们人类能够理解的符号的，这些符号也就是我们人类使用的语言。由于人类的语言有太多，因而表示这些语言的符号太多，无法用计算机中一个基本的存储单元—— byte 来表示，因而必须要经过拆分或一些翻译工作，才能让计算机能理解。我们可以把计算机能够理解的语言假定为英语，其它语言要能够在计算机中使用必须经过一次翻译，把它翻译成英语。这个翻译的过程就是编码。所以可以想象只要不是说英语的国家要能够使用计算机就必须要经过编码。这看起来有些霸道，但是这就是现状，这也和我们国家现在在大力推广汉语一样，希望其它国家都会说汉语，以后其它的语言都翻译成汉语，我们可以把计算机中存储信息的最小单位改成汉字，这样我们就不存在编码问题了。 所以总的来说，编码的原因可以总结为：</p><ol><li>计算机中存储信息的最小单元是一个字节即 8 个 bit，所以能表示的字符范围是 0~255 个</li><li>人类要表示的符号太多，无法用一个字节来完全表示</li><li>要解决这个矛盾必须需要一个新的数据结构 char，从 char 到 byte 必须编码</li></ol><h3 id="如何“翻译”"><a href="#如何“翻译”" class="headerlink" title="如何“翻译”"></a>如何 “翻译”</h3><p>明白了各种语言需要交流，经过翻译是必要的，那又如何来翻译呢？计算中提拱了多种翻译方式，常见的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它们都可以被看作为字典，它们规定了转化的规则，按照这个规则就可以让计算机正确的表示我们的字符。目前的编码格式很多，例如 GB2312、GBK、UTF-8、UTF-16 这几种格式都可以表示一个汉字，那我们到底选择哪种编码格式来存储汉字呢？这就要考虑到其它因素了，是存储空间重要还是编码的效率重要。根据这些因素来正确选择编码格式，下面简要介绍一下这几种编码格式。</p><ul><li>ASCII 码</li></ul><p>学过计算机的人都知道 ASCII 码，总共有 128 个，用一个字节的低 7 位表示，0<del>31 是控制字符如换行回车删除等；32</del>126 是打印字符，可以通过键盘输入并且能够显示出来。</p><ul><li>ISO-8859-1</li></ul><p>128 个字符显然是不够用的，于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码，它们是 ISO-8859-1~ISO-8859-15，其中 ISO-8859-1 涵盖了大多数西欧语言字符，所有应用的最广泛。ISO-8859-1 仍然是单字节编码，它总共能表示 256 个字符。</p><ul><li>GB2312</li></ul><p>它的全称是《信息交换用汉字编码字符集 基本集》，它是双字节编码，总的编码范围是 A1-F7，其中从 A1-A9 是符号区，总共包含 682 个符号，从 B0-F7 是汉字区，包含 6763 个汉字。</p><ul><li>GBK</li></ul><p>全称叫《汉字内码扩展规范》，是国家技术监督局为 windows95 所制定的新的汉字内码规范，它的出现是为了扩展 GB2312，加入更多的汉字，它的编码范围是 8140~FEFE（去掉 XX7F）总共有 23940 个码位，它能表示 21003 个汉字，它的编码是和 GB2312 兼容的，也就是说用 GB2312 编码的汉字可以用 GBK 来解码，并且不会有乱码。</p><ul><li>GB18030</li></ul><p>全称是《信息交换用汉字编码字符集》，是我国的强制标准，它可能是单字节、双字节或者四字节编码，它的编码与 GB2312 编码兼容，这个虽然是国家标准，但是实际应用系统中使用的并不广泛。</p><ul><li>UTF-16</li></ul><p>说到 UTF 必须要提到 Unicode（Universal Code 统一码），ISO 试图想创建一个全新的超语言字典，世界上所有的语言都可以通过这本字典来相互翻译。可想而知这个字典是多么的复杂，关于 Unicode 的详细规范可以参考相应文档。Unicode 是 Java 和 XML 的基础，下面详细介绍 Unicode 在计算机中的存储形式。 UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式，这个是定长的表示方法，不论什么字符都可以用两个字节表示，两个字节是 16 个 bit，所以叫 UTF-16。UTF-16 表示字符非常方便，每两个字节表示一个字符，这个在字符串操作时就大大简化了操作，这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。</p><ul><li>UTF-8</li></ul><p>UTF-16 统一采用两个字节表示一个字符，虽然在表示上非常简单方便，但是也有其缺点，有很大一部分字符用一个字节就可以表示的现在要两个字节表示，存储空间放大了一倍，在现在的网络带宽还非常有限的今天，这样会增大网络传输的流量，而且也没必要。而 UTF-8 采用了一种变长技术，每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。 UTF-8 有以下编码规则：</p><ol><li>如果一个字节，最高位（第 8 位）为 0，表示这是一个 ASCII 字符（00 - 7F）。可见，所有 ASCII 编码已经是 UTF-8 了。</li><li>如果一个字节，以 11 开头，连续的 1 的个数暗示这个字符的字节数，例如：110xxxxx 代表它是双字节 UTF-8 字符的首字节。</li><li>如果一个字节，以 10 开始，表示它不是首字节，需要向前查找才能得到当前字符的首字节</li></ol><h2 id="Java-中需要编码的场景"><a href="#Java-中需要编码的场景" class="headerlink" title="Java 中需要编码的场景"></a>Java 中需要编码的场景</h2><p>前面描述了常见的几种编码格式，下面将介绍 Java 中如何处理对编码的支持，什么场合中需要编码。</p><h3 id="I-O-操作中存在的编码"><a href="#I-O-操作中存在的编码" class="headerlink" title="I/O 操作中存在的编码"></a>I/O 操作中存在的编码</h3><p>我们知道涉及到编码的地方一般都在字符到字节或者字节到字符的转换上，而需要这种转换的场景主要是在 I/O 的时候，这个 I/O 包括磁盘 I/O 和网络 I/O，关于网络 I/O 部分在后面将主要以 Web 应用为例介绍。下图是 Java 中处理 I/O 问题的接口：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139223612456.png"></p><p>Reader 类是 Java 的 I/O 中读字符的父类，而 InputStream 类是读字节的父类，InputStreamReader 类就是关联字节到字符的桥梁，它负责在 I/O 过程中处理读取字节到字符的转换，而具体字节到字符的解码实现它由 StreamDecoder 去实现，在 StreamDecoder 解码过程中必须由用户指定 Charset 编码格式。值得注意的是如果你没有指定 Charset，将使用本地环境中的默认字符集，例如在中文环境中将使用 GBK 编码。 写的情况也是类似，字符的父类是 Writer，字节的父类是 OutputStream，通过 OutputStreamWriter 转换字符到字节。如下图所示：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139223660220.png"></p><p>同样 StreamEncoder 类负责将字符编码成字节，编码格式和默认编码规则与解码是一致的。 如下面一段代码，实现了文件的读写功能：</p><h5 id="清单-1-I-O-涉及的编码示例"><a href="#清单-1-I-O-涉及的编码示例" class="headerlink" title="清单 1.I/O 涉及的编码示例"></a>清单 1.I/O 涉及的编码示例</h5><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">file</span> <span class="operator">=</span> <span class="string">"c:/stream.txt"</span>; </span><br><span class="line"><span class="type">String</span> <span class="variable">charset</span> <span class="operator">=</span> <span class="string">"UTF-8"</span>; </span><br><span class="line"><span class="comment">// 写字符换转成字节流</span></span><br><span class="line"><span class="type">FileOutputStream</span> <span class="variable">outputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(file); </span><br><span class="line"><span class="type">OutputStreamWriter</span> <span class="variable">writer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OutputStreamWriter</span>( </span><br><span class="line">outputStream, charset); </span><br><span class="line"><span class="keyword">try</span> { </span><br><span class="line">   writer.write(<span class="string">"这是要保存的中文字符"</span>); </span><br><span class="line">} <span class="keyword">finally</span> { </span><br><span class="line">   writer.close(); </span><br><span class="line">} </span><br><span class="line"><span class="comment">// 读取字节转换成字符</span></span><br><span class="line"><span class="type">FileInputStream</span> <span class="variable">inputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(file); </span><br><span class="line"><span class="type">InputStreamReader</span> <span class="variable">reader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InputStreamReader</span>( </span><br><span class="line">inputStream, charset); </span><br><span class="line"><span class="type">StringBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>(); </span><br><span class="line"><span class="type">char</span>[] <span class="variable">buf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">char</span>[<span class="number">64</span>]; </span><br><span class="line"><span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>; </span><br><span class="line"><span class="keyword">try</span> { </span><br><span class="line">   <span class="keyword">while</span> ((count = reader.read(buf)) != -<span class="number">1</span>) { </span><br><span class="line">       buffer.append(buffer, <span class="number">0</span>, count); </span><br><span class="line">   } </span><br><span class="line">} <span class="keyword">finally</span> { </span><br><span class="line">   reader.close(); </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在我们的应用程序中涉及到 I/O 操作时只要注意指定统一的编解码 Charset 字符集，一般不会出现乱码问题，有些应用程序如果不注意指定字符编码，中文环境中取操作系统默认编码，如果编解码都在中文环境中，通常也没问题，但是还是强烈的不建议使用操作系统的默认编码，因为这样，你的应用程序的编码格式就和运行环境绑定起来了，在跨环境下很可能出现乱码问题。 &nbsp;</p><h3 id="内存中操作中的编码"><a href="#内存中操作中的编码" class="headerlink" title="内存中操作中的编码"></a>内存中操作中的编码</h3><p>在 Java 开发中除了 I/O 涉及到编码外，最常用的应该就是在内存中进行字符到字节的数据类型的转换，Java 中用 String 表示字符串，所以 String 类就提供转换到字节的方法，也支持将字节转换为字符串的构造函数。如下代码示例：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">"这是一段中文字符串"</span>; </span><br><span class="line"><span class="type">byte</span>[] <span class="variable">b</span> <span class="operator">=</span> s.getBytes(<span class="string">"UTF-8"</span>); </span><br><span class="line"><span class="type">String</span> <span class="variable">n</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(b,<span class="string">"UTF-8"</span>);</span><br></pre></td></tr></tbody></table></figure><p>另外一个是已经被被废弃的 ByteToCharConverter 和 CharToByteConverter 类，它们分别提供了 convertAll 方法可以实现 byte [] 和 char [] 的互转。如下代码所示：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteToCharConverter</span> <span class="variable">charConverter</span> <span class="operator">=</span> ByteToCharConverter.getConverter(<span class="string">"UTF-8"</span>); </span><br><span class="line"><span class="type">char</span> <span class="variable">c</span>[] <span class="operator">=</span> charConverter.convertAll(byteArray); </span><br><span class="line"><span class="type">CharToByteConverter</span> <span class="variable">byteConverter</span> <span class="operator">=</span> CharToByteConverter.getConverter(<span class="string">"UTF-8"</span>); </span><br><span class="line"><span class="type">byte</span>[] <span class="variable">b</span> <span class="operator">=</span> byteConverter.convertAll(c);</span><br></pre></td></tr></tbody></table></figure><p>这两个类已经被 Charset 类取代，Charset 提供 encode 与 decode 分别对应 char [] 到 byte [] 的编码和 byte [] 到 char [] 的解码。如下代码所示：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Charset</span> <span class="variable">charset</span> <span class="operator">=</span> Charset.forName(<span class="string">"UTF-8"</span>); </span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> charset.encode(string); </span><br><span class="line"><span class="type">CharBuffer</span> <span class="variable">charBuffer</span> <span class="operator">=</span> charset.decode(byteBuffer);</span><br></pre></td></tr></tbody></table></figure><p>编码与解码都在一个类中完成，通过 forName 设置编解码字符集，这样更容易统一编码格式，比 ByteToCharConverter 和 CharToByteConverter 类更方便。 Java 中还有一个 ByteBuffer 类，它提供一种 char 和 byte 之间的软转换，它们之间转换不需要编码与解码，只是把一个 16bit 的 char 格式，拆分成为 2 个 8bit 的 byte 表示，它们的实际值并没有被修改，仅仅是数据的类型做了转换。如下代码所以：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">heapByteBuffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>); </span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> heapByteBuffer.putChar(c);</span><br></pre></td></tr></tbody></table></figure><p>以上这些提供字符和字节之间的相互转换只要我们设置编解码格式统一一般都不会出现问题。 &nbsp;</p><h2 id="Java-中如何编解码"><a href="#Java-中如何编解码" class="headerlink" title="Java 中如何编解码"></a>Java 中如何编解码</h2><p>前面介绍了几种常见的编码格式，这里将以实际例子介绍 Java 中如何实现编码及解码，下面我们以 “I am 君山” 这个字符串为例介绍 Java 中如何把它以 ISO-8859-1、GB2312、GBK、UTF-16、UTF-8 编码格式进行编码的。</p><h5 id="清单-2-String-编码"><a href="#清单-2-String-编码" class="headerlink" title="清单 2.String 编码"></a>清单 2.String 编码</h5><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">encode</span><span class="params">()</span> { </span><br><span class="line">       <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">"I am 君山"</span>; </span><br><span class="line">       toHex(name.toCharArray()); </span><br><span class="line">       <span class="keyword">try</span> { </span><br><span class="line">           <span class="type">byte</span>[] <span class="variable">iso8859</span> <span class="operator">=</span> name.getBytes(<span class="string">"ISO-8859-1"</span>); </span><br><span class="line">           toHex(iso8859); </span><br><span class="line">           <span class="type">byte</span>[] <span class="variable">gb2312</span> <span class="operator">=</span> name.getBytes(<span class="string">"GB2312"</span>); </span><br><span class="line">           toHex(gb2312); </span><br><span class="line">           <span class="type">byte</span>[] <span class="variable">gbk</span> <span class="operator">=</span> name.getBytes(<span class="string">"GBK"</span>); </span><br><span class="line">           toHex(gbk); </span><br><span class="line">           <span class="type">byte</span>[] <span class="variable">utf16</span> <span class="operator">=</span> name.getBytes(<span class="string">"UTF-16"</span>); </span><br><span class="line">           toHex(utf16); </span><br><span class="line">           <span class="type">byte</span>[] <span class="variable">utf8</span> <span class="operator">=</span> name.getBytes(<span class="string">"UTF-8"</span>); </span><br><span class="line">           toHex(utf8); </span><br><span class="line">       } <span class="keyword">catch</span> (UnsupportedEncodingException e) { </span><br><span class="line">           e.printStackTrace(); </span><br><span class="line">       } </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>我们把 name 字符串按照前面说的几种编码格式进行编码转化成 byte 数组，然后以 16 进制输出，我们先看一下 Java 是如何进行编码的。 下面是 Java 中编码需要用到的类图</p><h5 id="图-1-Java-编码类图"><a href="#图-1-Java-编码类图" class="headerlink" title="图 1. Java 编码类图"></a>图 1. Java 编码类图</h5><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138518634858.jpg"></p><p>首先根据指定的 charsetName 通过 Charset.forName(charsetName) 设置 Charset 类，然后根据 Charset 创建 CharsetEncoder 对象，再调用 CharsetEncoder.encode 对字符串进行编码，不同的编码类型都会对应到一个类中，实际的编码过程是在这些类中完成的。下面是 String. getBytes (charsetName) 编码过程的时序图</p><h5 id="图-2-Java-编码时序图"><a href="#图-2-Java-编码时序图" class="headerlink" title="图 2.Java 编码时序图"></a>图 2.Java 编码时序图</h5><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138518844374.jpg"><br>从上图可以看出根据 charsetName 找到 Charset 类，然后根据这个字符集编码生成 CharsetEncoder，这个类是所有字符编码的父类，针对不同的字符编码集在其子类中定义了如何实现编码，有了 CharsetEncoder 对象后就可以调用 encode 方法去实现编码了。这个是 String.getBytes 编码方法，其它的如 StreamEncoder 中也是类似的方式。下面看看不同的字符集是如何将前面的字符串编码成 byte 数组的？ 如字符串 “I am 君山” 的 char 数组为 49 20 61 6d 20 541b 5c71，下面把它按照不同的编码格式转化成相应的字节。</p><h3 id="按照-ISO-8859-1-编码"><a href="#按照-ISO-8859-1-编码" class="headerlink" title="按照 ISO-8859-1 编码"></a>按照 ISO-8859-1 编码</h3><p>字符串 “I am 君山” 用 ISO-8859-1 编码，下面是编码结果：<br> <img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138519092605.gif"></p><p>从上图看出 7 个 char 字符经过 ISO-8859-1 编码转变成 7 个 byte 数组，ISO-8859-1 是单字节编码，中文 “君山” 被转化成值是 3f 的 byte。3f 也就是 “？” 字符，所以经常会出现中文变成 “？” 很可能就是错误的使用了 ISO-8859-1 这个编码导致的。中文字符经过 ISO-8859-1 编码会丢失信息，通常我们称之为 “黑洞”，它会把不认识的字符吸收掉。由于现在大部分基础的 Java 框架或系统默认的字符集编码都是 ISO-8859-1，所以很容易出现乱码问题，后面将会分析不同的乱码形式是怎么出现的。</p><h3 id="按照-GB2312-编码"><a href="#按照-GB2312-编码" class="headerlink" title="按照 GB2312 编码"></a>按照 GB2312 编码</h3><p>字符串 “I am 君山” 用 GB2312 编码，下面是编码结果：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138519276898.gif"><br>GB2312 对应的 Charset 是 sun.nio.cs.ext. EUC_CN 而对应的 CharsetDecoder 编码类是 sun.nio.cs.ext. DoubleByte，GB2312 字符集有一个 char 到 byte 的码表，不同的字符编码就是查这个码表找到与每个字符的对应的字节，然后拼装成 byte 数组。查表的规则如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c2b[c2bIndex[<span class="type">char</span> &gt;&gt; <span class="number">8</span>] + (<span class="type">char</span> &amp; <span class="number">0xff</span>)]</span><br></pre></td></tr></tbody></table></figure><p>如果查到的码位值大于 oxff 则是双字节，否则是单字节。双字节高 8 位作为第一个字节，低 8 位作为第二个字节，如下代码所示：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (bb &gt; <span class="number">0xff</span>) {    <span class="comment">// DoubleByte </span></span><br><span class="line">           <span class="keyword">if</span> (dl - dp &lt; <span class="number">2</span>) </span><br><span class="line">               <span class="keyword">return</span> CoderResult.OVERFLOW; </span><br><span class="line">           da[dp++] = (<span class="type">byte</span>) (bb &gt;&gt; <span class="number">8</span>); </span><br><span class="line">           da[dp++] = (<span class="type">byte</span>) bb; </span><br><span class="line">} <span class="keyword">else</span> {                      <span class="comment">// SingleByte </span></span><br><span class="line">           <span class="keyword">if</span> (dl - dp &lt; <span class="number">1</span>) </span><br><span class="line">               <span class="keyword">return</span> CoderResult.OVERFLOW; </span><br><span class="line">           da[dp++] = (<span class="type">byte</span>) bb; </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>从上图可以看出前 5 个字符经过编码后仍然是 5 个字节，而汉字被编码成双字节，在第一节中介绍到 GB2312 只支持 6763 个汉字，所以并不是所有汉字都能够用 GB2312 编码。</p><h3 id="按照-GBK-编码"><a href="#按照-GBK-编码" class="headerlink" title="按照 GBK 编码"></a>按照 GBK 编码</h3><p>字符串 “I am 君山” 用 GBK 编码，下面是编码结果：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138519716000.gif"></p><p>你可能已经发现上图与 GB2312 编码的结果是一样的，没错 GBK 与 GB2312 编码结果是一样的，由此可以得出 GBK 编码是兼容 GB2312 编码的，它们的编码算法也是一样的。不同的是它们的码表长度不一样，GBK 包含的汉字字符更多。所以只要是经过 GB2312 编码的汉字都可以用 GBK 进行解码，反过来则不然。</p><h3 id="按照-UTF-16-编码"><a href="#按照-UTF-16-编码" class="headerlink" title="按照 UTF-16 编码"></a>按照 UTF-16 编码</h3><p>字符串 “I am 君山” 用 UTF-16 编码，下面是编码结果：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138519820324.gif"></p><p>用 UTF-16 编码将 char 数组放大了一倍，单字节范围内的字符，在高位补 0 变成两个字节，中文字符也变成两个字节。从 UTF-16 编码规则来看，仅仅将字符的高位和地位进行拆分变成两个字节。特点是编码效率非常高，规则很简单，由于不同处理器对 2 字节处理方式不同，Big-endian（高位字节在前，低位字节在后）或 Little-endian（低位字节在前，高位字节在后）编码，所以在对一串字符串进行编码是需要指明到底是 Big-endian 还是 Little-endian，所以前面有两个字节用来保存 BYTE_ORDER_MARK 值，UTF-16 是用定长 16 位（2 字节）来表示的 UCS-2 或 Unicode 转换格式，通过代理对来访问 BMP 之外的字符编码。</p><h3 id="按照-UTF-8-编码"><a href="#按照-UTF-8-编码" class="headerlink" title="按照 UTF-8 编码"></a>按照 UTF-8 编码</h3><p>字符串 “I am 君山” 用 UTF-8 编码，下面是编码结果：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138519962062.gif"></p><p>UTF-16 虽然编码效率很高，但是对单字节范围内字符也放大了一倍，这无形也浪费了存储空间，另外 UTF-16 采用顺序编码，不能对单个字符的编码值进行校验，如果中间的一个字符码值损坏，后面的所有码值都将受影响。而 UTF-8 这些问题都不存在，UTF-8 对单字节范围内字符仍然用一个字节表示，对汉字采用三个字节表示。它的编码规则如下：</p><h5 id="清单-3-UTF-8-编码代码片段"><a href="#清单-3-UTF-8-编码代码片段" class="headerlink" title="清单 3.UTF-8 编码代码片段"></a>清单 3.UTF-8 编码代码片段</h5><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">CoderResult</span> <span class="title function_">encodeArrayLoop</span><span class="params">(CharBuffer src, </span></span><br><span class="line"><span class="params">ByteBuffer dst)</span>{ </span><br><span class="line">           <span class="type">char</span>[] <span class="variable">sa</span> <span class="operator">=</span> src.array(); </span><br><span class="line">           <span class="type">int</span> <span class="variable">sp</span> <span class="operator">=</span> src.arrayOffset() + src.position(); </span><br><span class="line">           <span class="type">int</span> <span class="variable">sl</span> <span class="operator">=</span> src.arrayOffset() + src.limit(); </span><br><span class="line">           <span class="type">byte</span>[] <span class="variable">da</span> <span class="operator">=</span> dst.array(); </span><br><span class="line">           <span class="type">int</span> <span class="variable">dp</span> <span class="operator">=</span> dst.arrayOffset() + dst.position(); </span><br><span class="line">           <span class="type">int</span> <span class="variable">dl</span> <span class="operator">=</span> dst.arrayOffset() + dst.limit(); </span><br><span class="line">           <span class="type">int</span> <span class="variable">dlASCII</span> <span class="operator">=</span> dp + Math.min(sl - sp, dl - dp); </span><br><span class="line">           <span class="comment">// ASCII only loop </span></span><br><span class="line">           <span class="keyword">while</span> (dp &lt; dlASCII &amp;&amp; sa[sp] &lt; <span class="string">'u0080'</span>) </span><br><span class="line">               da[dp++] = (<span class="type">byte</span>) sa[sp++]; </span><br><span class="line">           <span class="keyword">while</span> (sp &lt; sl) { </span><br><span class="line">               <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> sa[sp]; </span><br><span class="line">               <span class="keyword">if</span> (c &lt; <span class="number">0x80</span>) { </span><br><span class="line">                   <span class="comment">// Have at most seven bits </span></span><br><span class="line">                   <span class="keyword">if</span> (dp &gt;= dl) </span><br><span class="line">                       <span class="keyword">return</span> overflow(src, sp, dst, dp); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)c; </span><br><span class="line">               } <span class="keyword">else</span> <span class="keyword">if</span> (c &lt; <span class="number">0x800</span>) { </span><br><span class="line">                   <span class="comment">// 2 bytes, 11 bits </span></span><br><span class="line">                   <span class="keyword">if</span> (dl - dp &lt; <span class="number">2</span>) </span><br><span class="line">                       <span class="keyword">return</span> overflow(src, sp, dst, dp); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)(<span class="number">0xc0</span> | (c &gt;&gt; <span class="number">6</span>)); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)(<span class="number">0x80</span> | (c &amp; <span class="number">0x3f</span>)); </span><br><span class="line">               } <span class="keyword">else</span> <span class="keyword">if</span> (Character.isSurrogate(c)) { </span><br><span class="line">                   <span class="comment">// Have a surrogate pair </span></span><br><span class="line">                   <span class="keyword">if</span> (sgp == <span class="literal">null</span>) </span><br><span class="line">                       sgp = <span class="keyword">new</span> <span class="title class_">Surrogate</span>.Parser(); </span><br><span class="line">                   <span class="type">int</span> <span class="variable">uc</span> <span class="operator">=</span> sgp.parse(c, sa, sp, sl); </span><br><span class="line">                   <span class="keyword">if</span> (uc &lt; <span class="number">0</span>) { </span><br><span class="line">                       updatePositions(src, sp, dst, dp); </span><br><span class="line">                       <span class="keyword">return</span> sgp.error(); </span><br><span class="line">                   } </span><br><span class="line">                   <span class="keyword">if</span> (dl - dp &lt; <span class="number">4</span>) </span><br><span class="line">                       <span class="keyword">return</span> overflow(src, sp, dst, dp); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)(<span class="number">0xf0</span> | ((uc &gt;&gt; <span class="number">18</span>))); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)(<span class="number">0x80</span> | ((uc &gt;&gt; <span class="number">12</span>) &amp; <span class="number">0x3f</span>)); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)(<span class="number">0x80</span> | ((uc &gt;&gt;  <span class="number">6</span>) &amp; <span class="number">0x3f</span>)); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)(<span class="number">0x80</span> | (uc &amp; <span class="number">0x3f</span>)); </span><br><span class="line">                   sp++;  <span class="comment">// 2 chars </span></span><br><span class="line">               } <span class="keyword">else</span> { </span><br><span class="line">                   <span class="comment">// 3 bytes, 16 bits </span></span><br><span class="line">                   <span class="keyword">if</span> (dl - dp &lt; <span class="number">3</span>) </span><br><span class="line">                       <span class="keyword">return</span> overflow(src, sp, dst, dp); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)(<span class="number">0xe0</span> | ((c &gt;&gt; <span class="number">12</span>))); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)(<span class="number">0x80</span> | ((c &gt;&gt;  <span class="number">6</span>) &amp; <span class="number">0x3f</span>)); </span><br><span class="line">                   da[dp++] = (<span class="type">byte</span>)(<span class="number">0x80</span> | (c &amp; <span class="number">0x3f</span>)); </span><br><span class="line">               } </span><br><span class="line">               sp++; </span><br><span class="line">           } </span><br><span class="line">           updatePositions(src, sp, dst, dp); </span><br><span class="line">           <span class="keyword">return</span> CoderResult.UNDERFLOW; </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>UTF-8 编码与 GBK 和 GB2312 不同，不用查码表，所以在编码效率上 UTF-8 的效率会更好，所以在存储中文字符时 UTF-8 编码比较理想。</p><h3 id="几种编码格式的比较"><a href="#几种编码格式的比较" class="headerlink" title="几种编码格式的比较"></a>几种编码格式的比较</h3><p>对中文字符后面四种编码格式都能处理，GB2312 与 GBK 编码规则类似，但是 GBK 范围更大，它能处理所有汉字字符，所以 GB2312 与 GBK 比较应该选择 GBK。UTF-16 与 UTF-8 都是处理 Unicode 编码，它们的编码规则不太相同，相对来说 UTF-16 编码效率最高，字符到字节相互转换更简单，进行字符串操作也更好。它适合在本地磁盘和内存之间使用，可以进行字符和字节之间快速切换，如 Java 的内存编码就是采用 UTF-16 编码。但是它不适合在网络之间传输，因为网络传输容易损坏字节流，一旦字节流损坏将很难恢复，想比较而言 UTF-8 更适合网络传输，对 ASCII 字符采用单字节存储，另外单个字符损坏也不会影响后面其它字符，在编码效率上介于 GBK 和 UTF-16 之间，所以 UTF-8 在编码效率上和编码安全性上做了平衡，是理想的中文编码方式。</p><h2 id="Java-Web-涉及到的编码"><a href="#Java-Web-涉及到的编码" class="headerlink" title="Java Web 涉及到的编码"></a>Java Web 涉及到的编码</h2><p>对于使用中文来说，有 I/O 的地方就会涉及到编码，前面已经提到了 I/O 操作会引起编码，而大部分 I/O 引起的乱码都是网络 I/O，因为现在几乎所有的应用程序都涉及到网络操作，而数据经过网络传输都是以字节为单位的，所以所有的数据都必须能够被序列化为字节。在 Java 中数据被序列化必须继承 Serializable 接口。 这里有一个问题，你是否认真考虑过一段文本它的实际大小应该怎么计算，我曾经碰到过一个问题：就是要想办法压缩 Cookie 大小，减少网络传输量，当时有选择不同的压缩算法，发现压缩后字符数是减少了，但是并没有减少字节数。所谓的压缩只是将多个单字节字符通过编码转变成一个多字节字符。减少的是 String.length()，而并没有减少最终的字节数。例如将 “ab” 两个字符通过某种编码转变成一个奇怪的字符，虽然字符数从两个变成一个，但是如果采用 UTF-8 编码这个奇怪的字符最后经过编码可能又会变成三个或更多的字节。同样的道理比如整型数字 1234567 如果当成字符来存储，采用 UTF-8 来编码占用 7 个 byte，采用 UTF-16 编码将会占用 14 个 byte，但是把它当成 int 型数字来存储只需要 4 个 byte 来存储。所以看一段文本的大小，看字符本身的长度是没有意义的，即使是一样的字符采用不同的编码最终存储的大小也会不同，所以从字符到字节一定要看编码类型。 另外一个问题，你是否考虑过，当我们在电脑中某个文本编辑器里输入某个汉字时，它到底是怎么表示的？我们知道，计算机里所有的信息都是以 01 表示的，那么一个汉字，它到底是多少个 0 和 1 呢？我们能够看到的汉字都是以字符形式出现的，例如在 Java 中 “淘宝” 两个字符，它在计算机中的数值 10 进制是 28120 和 23453，16 进制是 6bd8 和 5d9d，也就是这两个字符是由这两个数字唯一表示的。Java 中一个 char 是 16 个 bit 相当于两个字节，所以两个汉字用 char 表示在内存中占用相当于四个字节的空间。 这两个问题搞清楚后，我们看一下 Java Web 中那些地方可能会存在编码转换？ 用户从浏览器端发起一个 HTTP 请求，需要存在编码的地方是 URL、Cookie、Parameter。服务器端接受到 HTTP 请求后要解析 HTTP 协议，其中 URI、Cookie 和 POST 表单参数需要解码，服务器端可能还需要读取数据库中的数据，本地或网络中其它地方的文本文件，这些数据都可能存在编码问题，当 Servlet 处理完所有请求的数据后，需要将这些数据再编码通过 Socket 发送到用户请求的浏览器里，再经过浏览器解码成为文本。这些过程如下图所示：</p><h5 id="图-3-一次-HTTP-请求的编码示例"><a href="#图-3-一次-HTTP-请求的编码示例" class="headerlink" title="图 3. 一次 HTTP 请求的编码示例"></a>图 3. 一次 HTTP 请求的编码示例</h5><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138520200887.gif"><br> 如上图所示一次 HTTP 请求设计到很多地方需要编解码，它们编解码的规则是什么？下面将会重点阐述一下：</p><h3 id="URL-的编解码"><a href="#URL-的编解码" class="headerlink" title="URL 的编解码"></a>URL 的编解码</h3><p>用户提交一个 URL，这个 URL 中可能存在中文，因此需要编码，如何对这个 URL 进行编码？根据什么规则来编码？有如何来解码？如下图一个 URL：</p><h5 id="图-4-URL-的几个组成部分"><a href="#图-4-URL-的几个组成部分" class="headerlink" title="图 4.URL 的几个组成部分"></a>图 4.URL 的几个组成部分</h5><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138520283908.gif"></p><p>上图中以 Tomcat 作为 Servlet Engine 为例，它们分别对应到下面这些配置文件中： Port 对应在 Tomcat 的 <connector port="8080"> 中配置，而 Context Path 在 <context path="/examples"> 中配置，Servlet Path 在 Web 应用的 web.xml 中的</context></connector></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">servlet-mapping</span>&gt;</span> </span><br><span class="line">       <span class="tag">&lt;<span class="name">servlet-name</span>&gt;</span>junshanExample<span class="tag">&lt;/<span class="name">servlet-name</span>&gt;</span> </span><br><span class="line">       <span class="tag">&lt;<span class="name">url-pattern</span>&gt;</span>/servlets/servlet/*<span class="tag">&lt;/<span class="name">url-pattern</span>&gt;</span> </span><br><span class="line"><span class="tag">&lt;/<span class="name">servlet-mapping</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p><url-pattern> 中配置，PathInfo 是我们请求的具体的 Servlet，QueryString 是要传递的参数，注意这里是在浏览器里直接输入 URL 所以是通过 Get 方法请求的，如果是 POST 方法请求的话，QueryString 将通过表单方式提交到服务器端，这个将在后面再介绍。 上图中 PathInfo 和 QueryString 出现了中文，当我们在浏览器中直接输入这个 URL 时，在浏览器端和服务端会如何编码和解析这个 URL 呢？为了验证浏览器是怎么编码 URL 的我们选择 FireFox 浏览器并通过 HTTPFox 插件观察我们请求的 URL 的实际的内容，以下是 URL：HTTP://localhost:8080/examples/servlets/servlet/ 君山 ?author= 君山在中文 FireFox3.6.12 的测试结果</url-pattern></p><h5 id="图-5-HTTPFox-的测试结果"><a href="#图-5-HTTPFox-的测试结果" class="headerlink" title="图 5. HTTPFox 的测试结果"></a>图 5. HTTPFox 的测试结果</h5><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138520529460.jpg"></p><p>君山的编码结果分别是：e5 90 9b e5 b1 b1，be fd c9 bd，查阅上一届的编码可知，PathInfo 是 UTF-8 编码而 QueryString 是经过 GBK 编码，至于为什么会有 “%”？查阅 URL 的编码规范 RFC3986 可知浏览器编码 URL 是将非 ASCII 字符按照某种编码格式编码成 16 进制数字然后将每个 16 进制表示的字节前加上 “%”，所以最终的 URL 就成了上图的格式了。 默认情况下中文 IE 最终的编码结果也是一样的，不过 IE 浏览器可以修改 URL 的编码格式在选项 -&gt; 高级 -&gt; 国际里面的发送 UTF-8 URL 选项可以取消。 从上面测试结果可知浏览器对 PathInfo 和 QueryString 的编码是不一样的，不同浏览器对 PathInfo 也可能不一样，这就对服务器的解码造成很大的困难，下面我们以 Tomcat 为例看一下，Tomcat 接受到这个 URL 是如何解码的。 解析请求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中，这个方法把传过来的 URL 的 byte [] 设置到 org.apache.coyote.Request 的相应的属性中。这里的 URL 仍然是 byte 格式，转成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">void</span> <span class="title function_">convertURI</span><span class="params">(MessageBytes uri, Request request)</span> </span><br><span class="line"><span class="keyword">throws</span> Exception { </span><br><span class="line">       <span class="type">ByteChunk</span> <span class="variable">bc</span> <span class="operator">=</span> uri.getByteChunk(); </span><br><span class="line">       <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> bc.getLength(); </span><br><span class="line">       <span class="type">CharChunk</span> <span class="variable">cc</span> <span class="operator">=</span> uri.getCharChunk(); </span><br><span class="line">       cc.allocate(length, -<span class="number">1</span>); </span><br><span class="line">       <span class="type">String</span> <span class="variable">enc</span> <span class="operator">=</span> connector.getURIEncoding(); </span><br><span class="line">       <span class="keyword">if</span> (enc != <span class="literal">null</span>) { </span><br><span class="line">           <span class="type">B2CConverter</span> <span class="variable">conv</span> <span class="operator">=</span> request.getURIConverter(); </span><br><span class="line">           <span class="keyword">try</span> { </span><br><span class="line">               <span class="keyword">if</span> (conv == <span class="literal">null</span>) { </span><br><span class="line">                   conv = <span class="keyword">new</span> <span class="title class_">B2CConverter</span>(enc); </span><br><span class="line">                   request.setURIConverter(conv); </span><br><span class="line">               } </span><br><span class="line">           } <span class="keyword">catch</span> (IOException e) {...} </span><br><span class="line">           <span class="keyword">if</span> (conv != <span class="literal">null</span>) { </span><br><span class="line">               <span class="keyword">try</span> { </span><br><span class="line">                   conv.convert(bc, cc, cc.getBuffer().length - </span><br><span class="line">cc.getEnd()); </span><br><span class="line">                   uri.setChars(cc.getBuffer(), cc.getStart(), </span><br><span class="line">cc.getLength()); </span><br><span class="line">                   <span class="keyword">return</span>; </span><br><span class="line">               } <span class="keyword">catch</span> (IOException e) {...} </span><br><span class="line">           } </span><br><span class="line">       } </span><br><span class="line">       <span class="comment">// Default encoding: fast conversion </span></span><br><span class="line">       <span class="type">byte</span>[] <span class="variable">bbuf</span> <span class="operator">=</span> bc.getBuffer(); </span><br><span class="line">       <span class="type">char</span>[] <span class="variable">cbuf</span> <span class="operator">=</span> cc.getBuffer(); </span><br><span class="line">       <span class="type">int</span> <span class="variable">start</span> <span class="operator">=</span> bc.getStart(); </span><br><span class="line">       <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; length; i++) { </span><br><span class="line">           cbuf[i] = (<span class="type">char</span>) (bbuf[i + start] &amp; <span class="number">0xff</span>); </span><br><span class="line">       } </span><br><span class="line">       uri.setChars(cbuf, <span class="number">0</span>, length); </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>从上面的代码中可以知道对 URL 的 URI 部分进行解码的字符集是在 connector 的 <connector uriencoding="”UTF-8”"> 中定义的，如果没有定义，那么将以默认编码 ISO-8859-1 解析。所以如果有中文 URL 时最好把 URIEncoding 设置成 UTF-8 编码。 QueryString 又如何解析？ GET 方式 HTTP 请求的 QueryString 与 POST 方式 HTTP 请求的表单参数都是作为 Parameters 保存，都是通过 request.getParameter 获取参数值。对它们的解码是在 request.getParameter 方法第一次被调用时进行的。request.getParameter 方法被调用时将会调用 org.apache.catalina.connector.Request 的 parseParameters 方法。这个方法将会对 GET 和 POST 方式传递的参数进行解码，但是它们的解码字符集有可能不一样。POST 表单的解码将在后面介绍，QueryString 的解码字符集是在哪定义的呢？它本身是通过 HTTP 的 Header 传到服务端的，并且也在 URL 中，是否和 URI 的解码字符集一样呢？从前面浏览器对 PathInfo 和 QueryString 的编码采取不同的编码格式不同可以猜测到解码字符集肯定也不会是一致的。的确是这样 QueryString 的解码字符集要么是 Header 中 ContentType 中定义的 Charset 要么就是默认的 ISO-8859-1，要使用 ContentType 中定义的编码就要设置 connector 的 <connector uriencoding="”UTF-8”" usebodyencodingforuri="”true”"> 中的 useBodyEncodingForURI 设置为 true。这个配置项的名字有点让人产生混淆，它并不是对整个 URI 都采用 BodyEncoding 进行解码而仅仅是对 QueryString 使用 BodyEncoding 解码，这一点还要特别注意。 从上面的 URL 编码和解码过程来看，比较复杂，而且编码和解码并不是我们在应用程序中能完全控制的，所以在我们的应用程序中应该尽量避免在 URL 中使用非 ASCII 字符，不然很可能会碰到乱码问题，当然在我们的服务器端最好设置 <connector> 中的 URIEncoding 和 useBodyEncodingForURI 两个参数。</connector></connector></connector></p><h3 id="HTTP-Header-的编解码"><a href="#HTTP-Header-的编解码" class="headerlink" title="HTTP Header 的编解码"></a>HTTP Header 的编解码</h3><p>当客户端发起一个 HTTP 请求除了上面的 URL 外还可能会在 Header 中传递其它参数如 Cookie、redirectPath 等，这些用户设置的值很可能也会存在编码问题，Tomcat 对它们又是怎么解码的呢？ 对 Header 中的项进行解码也是在调用 request.getHeader 是进行的，如果请求的 Header 项没有解码则调用 MessageBytes 的 toString 方法，这个方法将从 byte 到 char 的转化使用的默认编码也是 ISO-8859-1，而我们也不能设置 Header 的其它解码格式，所以如果你设置 Header 中有非 ASCII 字符解码肯定会有乱码。 我们在添加 Header 时也是同样的道理，不要在 Header 中传递非 ASCII 字符，如果一定要传递的话，我们可以先将这些字符用 org.apache.catalina.util.URLEncoder 编码然后再添加到 Header 中，这样在浏览器到服务器的传递过程中就不会丢失信息了，如果我们要访问这些项时再按照相应的字符集解码就好了。</p><h3 id="POST-表单的编解码"><a href="#POST-表单的编解码" class="headerlink" title="POST 表单的编解码"></a>POST 表单的编解码</h3><p>在前面提到了 POST 表单提交的参数的解码是在第一次调用 request.getParameter 发生的，POST 表单参数传递方式与 QueryString 不同，它是通过 HTTP 的 BODY 传递到服务端的。当我们在页面上点击 submit 按钮时浏览器首先将根据 ContentType 的 Charset 编码格式对表单填的参数进行编码然后提交到服务器端，在服务器端同样也是用 ContentType 中字符集进行解码。所以通过 POST 表单提交的参数一般不会出现问题，而且这个字符集编码是我们自己设置的，可以通过 request.setCharacterEncoding(charset) 来设置。 另外针对 multipart/form-data 类型的参数，也就是上传的文件编码同样也是使用 ContentType 定义的字符集编码，值得注意的地方是上传文件是用字节流的方式传输到服务器的本地临时目录，这个过程并没有涉及到字符编码，而真正编码是在将文件内容添加到 parameters 中，如果用这个编码不能编码时将会用默认编码 ISO-8859-1 来编码。</p><h3 id="HTTP-BODY-的编解码"><a href="#HTTP-BODY-的编解码" class="headerlink" title="HTTP BODY 的编解码"></a>HTTP BODY 的编解码</h3><p>当用户请求的资源已经成功获取后，这些内容将通过 Response 返回给客户端浏览器，这个过程先要经过编码再到浏览器进行解码。这个过程的编解码字符集可以通过 response.setCharacterEncoding 来设置，它将会覆盖 request.getCharacterEncoding 的值，并且通过 Header 的 Content-Type 返回客户端，浏览器接受到返回的 socket 流时将通过 Content-Type 的 charset 来解码，如果返回的 HTTP Header 中 Content-Type 没有设置 charset，那么浏览器将根据 Html 的 <meta http-equiv="Content-Type" content="text/html; charset=GBK"> 中的 charset 来解码。如果也没有定义的话，那么浏览器将使用默认的编码来解码。</p><h3 id="其它需要编码的地方"><a href="#其它需要编码的地方" class="headerlink" title="其它需要编码的地方"></a>其它需要编码的地方</h3><p>除了 URL 和参数编码问题外，在服务端还有很多地方可能存在编码，如可能需要读取 xml、velocity 模版引擎、JSP 或者从数据库读取数据等。 xml 文件可以通过设置头来制定编码格式</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>Velocity 模版设置编码格式：</p><figure class="highlight properties"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">services.VelocityService.input.encoding</span>=<span class="string">UTF-8</span></span><br></pre></td></tr></tbody></table></figure><p>JSP 设置编码格式：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;%@page contentType="text/html; charset=UTF-8"%&gt;</span><br></pre></td></tr></tbody></table></figure><p>访问数据库都是通过客户端 JDBC 驱动来完成，用 JDBC 来存取数据要和数据的内置编码保持一致，可以通过设置 JDBC URL 来制定如 MySQL：url=” jdbc:mysql://localhost:3306/DB?useUnicode=true&amp;characterEncoding=GBK”。</p><h2 id="常见问题分析"><a href="#常见问题分析" class="headerlink" title="常见问题分析"></a>常见问题分析</h2><p>在了解了 Java Web 中可能需要编码的地方后，下面看一下，当我们碰到一些乱码时，应该怎么处理这些问题？出现乱码问题唯一的原因都是在 char 到 byte 或 byte 到 char 转换中编码和解码的字符集不一致导致的，由于往往一次操作涉及到多次编解码，所以出现乱码时很难查找到底是哪个环节出现了问题，下面就几种常见的现象进行分析。</p><h3 id="中文变成了看不懂的字符"><a href="#中文变成了看不懂的字符" class="headerlink" title="中文变成了看不懂的字符"></a>中文变成了看不懂的字符</h3><p>例如，字符串 “淘！我喜欢！” 变成了 “Ì Ô £ ¡Î Ò Ï²»¶ £ ¡” 编码过程如下图所示<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138521252931.gif"></p><p>字符串在解码时所用的字符集与编码字符集不一致导致汉字变成了看不懂的乱码，而且是一个汉字字符变成两个乱码字符。</p><h3 id="一个汉字变成一个问号"><a href="#一个汉字变成一个问号" class="headerlink" title="一个汉字变成一个问号"></a>一个汉字变成一个问号</h3><p>例如，字符串 “淘！我喜欢！” 变成了 “？？？？？？” 编码过程如下图所示<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138521321262.gif"></p><p>将中文和中文符号经过不支持中文的 ISO-8859-1 编码后，所有字符变成了 “？”，这是因为用 ISO-8859-1 进行编解码时遇到不在码值范围内的字符时统一用 3f 表示，这也就是通常所说的 “黑洞”，所有 ISO-8859-1 不认识的字符都变成了 “？”。 &nbsp;</p><h3 id="一个汉字变成两个问号"><a href="#一个汉字变成两个问号" class="headerlink" title="一个汉字变成两个问号"></a>一个汉字变成两个问号</h3><p>例如，字符串 “淘！我喜欢！” 变成了 “？？？？？？？？？？？？” 编码过程如下图所示</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138521463158.gif"></p><p>这种情况比较复杂，中文经过多次编码，但是其中有一次编码或者解码不对仍然会出现中文字符变成 “？” 现象，出现这种情况要仔细查看中间的编码环节，找出出现编码错误的地方。 &nbsp;</p><h3 id="一种不正常的正确编码"><a href="#一种不正常的正确编码" class="headerlink" title="一种不正常的正确编码"></a>一种不正常的正确编码</h3><p>还有一种情况是在我们通过 request.getParameter 获取参数值时，当我们直接调用</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> request.getParameter(name);</span><br></pre></td></tr></tbody></table></figure><p>会出现乱码，但是如果用下面的方式</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">value</span> <span class="operator">=</span> String(request.getParameter(name).getBytes(<span class="string">"</span></span><br><span class="line"><span class="string">ISO-8859-1"</span>), <span class="string">"GBK"</span>);</span><br></pre></td></tr></tbody></table></figure><p>解析时取得的 value 会是正确的汉字字符，这种情况是怎么造成的呢？ 看下如所示：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138521592281.gif"></p><p>这种情况是这样的，ISO-8859-1 字符集的编码范围是 0000-00FF，正好和一个字节的编码范围相对应。这种特性保证了使用 ISO-8859-1 进行编码和解码可以保持编码数值 “不变”。虽然中文字符在经过网络传输时，被错误地 “拆” 成了两个欧洲字符，但由于输出时也是用 ISO-8859-1，结果被 “拆” 开的中文字的两半又被合并在一起，从而又刚好组成了一个正确的汉字。虽然最终能取得正确的汉字，但是还是不建议用这种不正常的方式取得参数值，因为这中间增加了一次额外的编码与解码，这种情况出现乱码时因为 Tomcat 的配置文件中 useBodyEncodingForURI 配置项没有设置为 ”true”，从而造成第一次解析式用 ISO-8859-1 来解析才造成乱码的。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文首先总结了几种常见编码格式的区别，然后介绍了支持中文的几种编码格式，并比较了它们的使用场景。接着介绍了 Java 那些地方会涉及到编码问题，已经 Java 中如何对编码的支持。并以网络 I/O 为例重点介绍了 HTTP 请求中的存在编码的地方，以及 Tomcat 对 HTTP 协议的解析，最后分析了我们平常遇到的乱码问题出现的原因。 综上所述，要解决中文问题，首先要搞清楚哪些地方会引起字符到字节的编码以及字节到字符的解码，最常见的地方就是读取会存储数据到磁盘，或者数据要经过网络传输。然后针对这些地方搞清楚操作这些数据的框架的或系统是如何控制编码的，正确设置编码格式，避免使用软件默认的或者是操作系统平台默认的编码格式。 &nbsp;</p><blockquote><p><strong>来自：IBM&nbsp;developerWorks</strong><br><strong>作者：许 令波, Java 工程师, 淘宝网</strong><br><strong>链接：<span class="exturl" data-url="aHR0cHM6Ly93d3cuaWJtLmNvbS9kZXZlbG9wZXJ3b3Jrcy9jbi9qYXZhL2otbG8tY2hpbmVzZWNvZGluZy8=">https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/<i class="fa fa-external-link-alt"></i></span></strong></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/11030.html</id>
    <link href="https://www.bytelife.net/articles/11030.html"/>
    <published>2017-03-18T02:57:13.000Z</published>
    <summary>
      <![CDATA[<p>编码问题一直困扰着开发人员，尤其在 Java 中更加明显，因为 Java 是跨平台语言，不同平台之间编码之间的切换较多。本文将向你详细介绍 Java 中编码问题出现的根本原因，你将了解到：Java 中经常遇到的几种编码格式的区别；Java 中经常需要编码的场景；出现中文问题的原因分析；在开发 Java web 程序时可能会存在编码的几个地方，一个 HTTP 请求怎么控制编码格式？如何避免出现中文问题？</p>]]>
    </summary>
    <title>深入分析 Java 中的中文编码问题</title>
    <updated>2017-03-18T02:57:13.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="并发" scheme="https://www.bytelife.net/tags/%E5%B9%B6%E5%8F%91/"/>
    <content>
      <![CDATA[<h2 id="ThreadLocal是什么"><a href="#ThreadLocal是什么" class="headerlink" title="ThreadLocal是什么"></a>ThreadLocal 是什么</h2><p>早在 JDK 1.2 的版本中就提供 Java.lang.ThreadLocal，ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。<br>当使用 ThreadLocal 维护变量时，ThreadLocal 为每个使用该变量的线程提供独立的变量副本，所以每一个线程都可以独立地改变自己的副本，而不会影响其它线程所对应的副本。<br>从线程的角度看，目标变量就象是线程的本地变量，这也是类名中 “Local” 所要表达的意思。<br>所以，在 Java 中编写线程局部变量的代码相对来说要笨拙一些，因此造成线程局部变量没有在 Java 开发者中得到很好的普及。 　　</p><span id="more"></span><h2 id="ThreadLocal的接口方法"><a href="#ThreadLocal的接口方法" class="headerlink" title="ThreadLocal的接口方法"></a>ThreadLocal 的接口方法</h2><p>ThreadLocal 类接口很简单，只有 4 个方法，我们先来了解一下：</p><ul><li>void set (Object value)：设置当前线程的线程局部变量的值。</li><li>public Object get ()：该方法返回当前线程所对应的线程局部变量。</li><li>public void remove ()：将当前线程局部变量的值删除，目的是为了减少内存的占用，该方法是 JDK 5.0 新增的方法。需要指出的是，当线程结束后，对应该线程的局部变量将自动被垃圾回收，所以显式调用该方法清除线程的局部变量并不是必须的操作，但它可以加快内存回收的速度。</li><li>protected Object initialValue ()：返回该线程局部变量的初始值，该方法是一个 protected 的方法，显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法，在线程第 1 次调用 get () 或 set (Object) 时才执行，并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一个 null。</li></ul><p>　　值得一提的是，在 JDK5.0 中，ThreadLocal 已经支持泛型，该类的类名已经变为 ThreadLocal。API 方法也相应进行了调整，新版本的 API 方法分别是 void set (T value)、T get () 以及 T initialValue ()。 　　 　　ThreadLocal 是如何做到为每一个线程维护变量的副本的呢？其实实现的思路很简单：在 ThreadLocal 类中有一个 Map，用于存储每一个线程的变量副本，Map 中元素的键为线程对象，而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.test;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestNum</span> {</span><br><span class="line">    <span class="comment">// ①通过匿名内部类覆盖ThreadLocal的initialValue()方法，指定初始值</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">ThreadLocal</span>&lt;Integer&gt; <span class="variable">seqNum</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;Integer&gt;() {</span><br><span class="line">        <span class="keyword">public</span> <span class="type">Integer</span> <span class="title function_">initialValue</span><span class="params">()</span> {</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        }</span><br><span class="line">    };</span><br><span class="line">    <span class="comment">// ②获取下一个序列值</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getNextNum</span><span class="params">()</span> {</span><br><span class="line">        seqNum.set(seqNum.get() + <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">return</span> seqNum.get();</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line">        <span class="type">TestNum</span> <span class="variable">sn</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TestNum</span>();</span><br><span class="line">        <span class="comment">// ③ 3个线程共享sn，各自产生序列号</span></span><br><span class="line">        <span class="type">TestClient</span> <span class="variable">t1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TestClient</span>(sn);</span><br><span class="line">        <span class="type">TestClient</span> <span class="variable">t2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TestClient</span>(sn);</span><br><span class="line">        <span class="type">TestClient</span> <span class="variable">t3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TestClient</span>(sn);</span><br><span class="line">        t1.start();</span><br><span class="line">        t2.start();</span><br><span class="line">        t3.start();</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">TestClient</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> {</span><br><span class="line">        <span class="keyword">private</span> TestNum sn;</span><br><span class="line">        <span class="type">public</span> <span class="title function_">TestClient</span><span class="params">(TestNum sn)</span> {</span><br><span class="line">            <span class="built_in">this</span>.sn = sn;</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">3</span>; i++) {</span><br><span class="line">                <span class="comment">// ④每个线程打出3个序列值</span></span><br><span class="line">                System.out.println(<span class="string">"thread["</span> + Thread.currentThread().getName() + <span class="string">"] --&gt; sn["</span></span><br><span class="line">                         + sn.getNextNum() + <span class="string">"]"</span>);</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>　　通常我们通过匿名内部类的方式定义 ThreadLocal 的子类，提供初始的变量值，如例子中①处所示。TestClient 线程产生一组序列号，在③处，我们生成 3 个 TestClient，它们共享同一个 TestNum 实例。运行以上代码，在控制台上输出以下的结果：</p><pre><code>thread[Thread-0] --&gt; sn[1]thread[Thread-1] --&gt; sn[1]thread[Thread-2] --&gt; sn[1]thread[Thread-1] --&gt; sn[2]thread[Thread-0] --&gt; sn[2]thread[Thread-1] --&gt; sn[3]thread[Thread-2] --&gt; sn[2]thread[Thread-0] --&gt; sn[3]thread[Thread-2] --&gt; sn[3]</code></pre><p>　　考察输出的结果信息，我们发现每个线程所产生的序号虽然都共享同一个 TestNum 实例，但它们并没有发生相互干扰的情况，而是各自产生独立的序列号，这是因为我们通过 ThreadLocal 为每一个线程提供了单独的副本。</p><h2 id="Thread同步机制的比较"><a href="#Thread同步机制的比较" class="headerlink" title="Thread同步机制的比较"></a>Thread 同步机制的比较</h2><p>　　ThreadLocal 和线程同步机制相比有什么优势呢？ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。 　　 　　<br>　　在同步机制中，通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的，使用同步机制要求程序慎密地分析什么时候对变量进行读写，什么时候需要锁定某个对象，什么时候释放对象锁等繁杂的问题，程序设计和编写难度相对较大。 　　 　　<br>　　而 ThreadLocal 则从另一个角度来解决多线程的并发访问。ThreadLocal 会为每一个线程提供一个独立的变量副本，从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本，从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象，在编写多线程代码时，可以把不安全的变量封装进 ThreadLocal。 　　 　　<br>　　由于 ThreadLocal 中可以持有任何类型的对象，低版本 JDK 所提供的 get () 返回的是 Object 对象，需要强制类型转换。但 JDK 5.0 通过泛型很好的解决了这个问题，在一定程度地简化 ThreadLocal 的使用，代码清单 9 2 就使用了 JDK 5.0 新的 ThreadLocal 版本。 　　 　　<br>　　概括起来说，对于多线程资源共享的问题，同步机制采用了 “以时间换空间” 的方式，而 ThreadLocal 采用了 “以空间换时间” 的方式。前者仅提供一份变量，让不同的线程排队访问，而后者为每一个线程都提供了一份变量，因此可以同时访问而互不影响。 　　 　　<br>　　spring 使用 ThreadLocal 解决线程安全问题我们知道在一般情况下，只有无状态的 Bean 才可以在多线程环境下共享，在 Spring 中，绝大部分 Bean 都可以声明为 singleton 作用域。就是因为 Spring 对一些 Bean（如 RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder 等）中非线程安全状态采用 ThreadLocal 进行处理，让它们也成为线程安全的状态，因为有状态的 Bean 就可以在多线程中共享了。 　　 　　<br>　　一般的 Web 应用划分为展现层、服务层和持久层三个层次，在不同的层中编写对应的逻辑，下层通过接口向上层开放功能调用。在一般情况下，从接收请求到返回响应所经过的所有程序调用都同属于一个线程，如图 9‑2 所示： 　　 　　<br>　　<img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139168064615.gif">　<br>　　同一线程贯通三层这样你就可以根据需要，将一些非线程安全的变量以 ThreadLocal 存放，在同一次请求响应的调用线程中，所有关联的对象引用到的都是同一个变量。 　　<br>　　下面的实例能够体现 Spring 对有状态 Bean 的改造思路： 　　　　</p><figure class="highlight java"><figcaption><span>代码清单 3 TestDao：非线程安全</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.sql.Connection;</span><br><span class="line"><span class="keyword">import</span> java.sql.SQLException;</span><br><span class="line"><span class="keyword">import</span> java.sql.Statement;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestDao</span> {</span><br><span class="line">    <span class="keyword">private</span> Connection conn;<span class="comment">// ①一个非线程安全的变量</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">addTopic</span><span class="params">()</span> <span class="keyword">throws</span> SQLException {</span><br><span class="line">        <span class="type">Statement</span> <span class="variable">stat</span> <span class="operator">=</span> conn.createStatement();<span class="comment">// ②引用非线程安全变量</span></span><br><span class="line">        <span class="comment">// …</span></span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>　　由于①处的 conn 是成员变量，因为 addTopic () 方法是非线程安全的，必须在使用时创建一个新 TopicDao 实例（非 singleton）。下面使用 ThreadLocal 对 conn 这个非线程安全的 “状态” 进行改造： 　　　　</p><figure class="highlight java"><figcaption><span>代码清单 4 TestDao：线程安全</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.sql.Connection;</span><br><span class="line"><span class="keyword">import</span> java.sql.SQLException;</span><br><span class="line"><span class="keyword">import</span> java.sql.Statement;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestDaoNew</span> {</span><br><span class="line">    <span class="comment">// ①使用ThreadLocal保存Connection变量</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">ThreadLocal</span>&lt;Connection&gt; <span class="variable">connThreadLocal</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;Connection&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">Connection</span> <span class="title function_">getConnection</span><span class="params">()</span> {</span><br><span class="line">        <span class="comment">// ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection，</span></span><br><span class="line">        <span class="comment">// 并将其保存到线程本地变量中。</span></span><br><span class="line">        <span class="keyword">if</span> (connThreadLocal.get() == <span class="literal">null</span>) {</span><br><span class="line">            <span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> getConnection();</span><br><span class="line">            connThreadLocal.set(conn);</span><br><span class="line">            <span class="keyword">return</span> conn;</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            <span class="keyword">return</span> connThreadLocal.get();<span class="comment">// ③直接返回线程本地变量</span></span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">addTopic</span><span class="params">()</span> <span class="keyword">throws</span> SQLException {</span><br><span class="line">        <span class="comment">// ④从ThreadLocal中获取线程对应的Connection</span></span><br><span class="line">        <span class="type">Statement</span> <span class="variable">stat</span> <span class="operator">=</span> getConnection().createStatement();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>　　不同的线程在使用 TopicDao 时，先判断 connThreadLocal.get() 是否是 null，如果是 null，则说明当前线程还没有对应的 Connection 对象，这时创建一个 Connection 对象并添加到本地线程变量中；如果不为 null，则说明当前的线程已经拥有了 Connection 对象，直接使用就可以了。这样，就保证了不同的线程使用线程相关的 Connection，而不会使用其它线程的 Connection。因此，这个 TopicDao 就可以做到 singleton 共享了。<br>　　当然，这个例子本身很粗糙，将 Connection 的 ThreadLocal 直接放在 DAO 只能做到本 DAO 的多个方法共享 Connection 时不发生线程安全问题，但无法和其它 DAO 共用同一个 Connection，要做到同一事务多 DAO 共享同一 Connection，必须在一个共同的外部类使用 ThreadLocal 保存 Connection。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.sql.Connection;</span><br><span class="line"><span class="keyword">import</span> java.sql.DriverManager;</span><br><span class="line"><span class="keyword">import</span> java.sql.SQLException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConnectionManager</span> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">ThreadLocal</span>&lt;Connection&gt; <span class="variable">connectionHolder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;Connection&gt;() {</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">Connection</span> <span class="title function_">initialValue</span><span class="params">()</span> {</span><br><span class="line">            <span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">try</span> {</span><br><span class="line">                conn = DriverManager.getConnection(</span><br><span class="line">                        <span class="string">"jdbc:mysql://localhost:3306/test"</span>, <span class="string">"username"</span>,</span><br><span class="line">                        <span class="string">"password"</span>);</span><br><span class="line">            } <span class="keyword">catch</span> (SQLException e) {</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            }</span><br><span class="line">            <span class="keyword">return</span> conn;</span><br><span class="line">        }</span><br><span class="line">    };</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">Connection</span> <span class="title function_">getConnection</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> connectionHolder.get();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">setConnection</span><span class="params">(Connection conn)</span> {</span><br><span class="line">        connectionHolder.set(conn);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="java-lang-ThreadLocal的具体实现"><a href="#java-lang-ThreadLocal的具体实现" class="headerlink" title="java.lang.ThreadLocal的具体实现"></a>java.lang.ThreadLocal 的具体实现</h2><p>　　那么到底 ThreadLocal 类是如何实现这种 “为每个线程提供不同的变量拷贝” 的呢？先来看一下 ThreadLocal 的 set () 方法的源码是如何实现的：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Sets the current thread's copy of this thread-local variable</span></span><br><span class="line"><span class="comment">    * to the specified value.  Most subclasses will have no need to</span></span><br><span class="line"><span class="comment">    * override this method, relying solely on the {<span class="doctag">@link</span> #initialValue}</span></span><br><span class="line"><span class="comment">    * method to set the values of thread-locals.</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> value the value to be stored in the current thread's copy of</span></span><br><span class="line"><span class="comment">    *        this thread-local.</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">set</span><span class="params">(T value)</span> {</span><br><span class="line">       <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">       <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">       <span class="keyword">if</span> (map != <span class="literal">null</span>)</span><br><span class="line">           map.set(<span class="built_in">this</span>, value);</span><br><span class="line">       <span class="keyword">else</span></span><br><span class="line">           createMap(t, value);</span><br><span class="line">   }</span><br></pre></td></tr></tbody></table></figure><p>　　在这个方法内部我们看到，首先通过 getMap (Thread t) 方法获取一个和当前线程相关的 ThreadLocalMap，然后将变量的值设置到这个 ThreadLocalMap 对象中，当然如果获取到的 ThreadLocalMap 对象为空，就通过 createMap 方法创建。 　　<br>　　线程隔离的秘密，就在于 ThreadLocalMap 这个类。ThreadLocalMap 是 ThreadLocal 类的一个静态内部类，它实现了键值对的设置和获取（对比 Map 对象来理解），每个线程中都有一个独立的 ThreadLocalMap 副本，它所存储的值，只能被当前线程读取和修改<br>　　ThreadLocal 类通过操作每一个线程特有的 ThreadLocalMap 副本，从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的，完全不会有并发错误。还有一点就是，ThreadLocalMap 存储的键值对中的键是 this 对象指向的 ThreadLocal 对象，而值就是你所设置的对象了。<br>　　为了加深理解，我们接着看上面代码中出现的 getMap 和 createMap 方法的实现：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Get the map associated with a ThreadLocal. Overridden in</span></span><br><span class="line"><span class="comment"> * InheritableThreadLocal.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  t the current thread</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the map</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">ThreadLocalMap</span> <span class="title function_">getMap</span><span class="params">(Thread t)</span> {</span><br><span class="line">    <span class="keyword">return</span> t.threadLocals;</span><br><span class="line">}</span><br><span class="line">    </span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Create the map associated with a ThreadLocal. Overridden in</span></span><br><span class="line"><span class="comment"> * InheritableThreadLocal.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> t the current thread</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> firstValue value for the initial entry of the map</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> map the map to store.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">createMap</span><span class="params">(Thread t, T firstValue)</span> {</span><br><span class="line">    t.threadLocals = <span class="keyword">new</span> <span class="title class_">ThreadLocalMap</span>(<span class="built_in">this</span>, firstValue);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>接下来再看一下 ThreadLocal 类中的 get () 方法:</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns the value in the current thread's copy of this</span></span><br><span class="line"><span class="comment"> * thread-local variable.  If the variable has no value for the</span></span><br><span class="line"><span class="comment"> * current thread, it is first initialized to the value returned</span></span><br><span class="line"><span class="comment"> * by an invocation of the {<span class="doctag">@link</span> #initialValue} method.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the current thread's value of this thread-local</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">T</span> <span class="title function_">get</span><span class="params">()</span> {</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">    <span class="keyword">if</span> (map != <span class="literal">null</span>) {</span><br><span class="line">        ThreadLocalMap.<span class="type">Entry</span> <span class="variable">e</span> <span class="operator">=</span> map.getEntry(<span class="built_in">this</span>);</span><br><span class="line">        <span class="keyword">if</span> (e != <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">return</span> (T)e.value;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> setInitialValue();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>再来看 setInitialValue () 方法：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Variant of set() to establish initialValue. Used instead</span></span><br><span class="line"><span class="comment"> * of set() in case user has overridden the set() method.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the initial value</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">T</span> <span class="title function_">setInitialValue</span><span class="params">()</span> {</span><br><span class="line">    <span class="type">T</span> <span class="variable">value</span> <span class="operator">=</span> initialValue();</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">    <span class="keyword">if</span> (map != <span class="literal">null</span>)</span><br><span class="line">        map.set(<span class="built_in">this</span>, value);</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        createMap(t, value);</span><br><span class="line">    <span class="keyword">return</span> value;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>　　获取和当前线程绑定的值时，ThreadLocalMap 对象是以 this 指向的 ThreadLocal 对象为键进行查找的，这当然和前面 set () 方法的代码是相呼应的。 　　<br>　　进一步地，我们可以创建不同的 ThreadLocal 实例来实现多个变量在不同线程间的访问隔离，为什么可以这么做？因为不同的 ThreadLocal 对象作为不同键，当然也可以在线程的 ThreadLocalMap 对象中设置不同的值了。通过 ThreadLocal 对象，在多线程中共享一个值和多个值的区别，就像你在一个 HashMap 对象中存储一个键值对和多个键值对一样，仅此而已。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>　　ThreadLocal 是解决线程安全问题一个很好的思路，它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下，ThreadLocal 比直接使用 synchronized 同步机制解决线程安全问题更简单，更方便，且结果程序拥有更高的并发性。 ConnectionManager.java</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.sql.Connection;</span><br><span class="line"><span class="keyword">import</span> java.sql.DriverManager;</span><br><span class="line"><span class="keyword">import</span> java.sql.SQLException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConnectionManager</span> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">ThreadLocal</span>&lt;Connection&gt; <span class="variable">connectionHolder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;Connection&gt;() {</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="type">Connection</span> <span class="title function_">initialValue</span><span class="params">()</span> {</span><br><span class="line">            <span class="type">Connection</span> <span class="variable">conn</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">try</span> {</span><br><span class="line">                conn = DriverManager.getConnection(</span><br><span class="line">                        <span class="string">"jdbc:mysql://localhost:3306/test"</span>, <span class="string">"username"</span>,</span><br><span class="line">                        <span class="string">"password"</span>);</span><br><span class="line">            } <span class="keyword">catch</span> (SQLException e) {</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            }</span><br><span class="line">            <span class="keyword">return</span> conn;</span><br><span class="line">        }</span><br><span class="line">    };</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">Connection</span> <span class="title function_">getConnection</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> connectionHolder.get();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">setConnection</span><span class="params">(Connection conn)</span> {</span><br><span class="line">        connectionHolder.set(conn);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>　　看到网友评论的很激烈，甚至关于 ThreadLocalMap 不是 ThreadLocal 里面的，而是 Thread 里面的这种评论都出现了，于是有了这个后记，下面先把 jdk 源码贴上，源码最有说服力了。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * ThreadLocalMap is a customized hash map suitable only for</span></span><br><span class="line"><span class="comment">     * maintaining thread local values. No operations are exported</span></span><br><span class="line"><span class="comment">     * outside of the ThreadLocal class. The class is package private to</span></span><br><span class="line"><span class="comment">     * allow declaration of fields in class Thread.  To help deal with</span></span><br><span class="line"><span class="comment">     * very large and long-lived usages, the hash table entries use</span></span><br><span class="line"><span class="comment">     * WeakReferences for keys. However, since reference queues are not</span></span><br><span class="line"><span class="comment">     * used, stale entries are guaranteed to be removed only when</span></span><br><span class="line"><span class="comment">     * the table starts running out of space.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ThreadLocalMap</span> {...}</span><br></pre></td></tr></tbody></table></figure><p>源码就是以上，这源码自然是在 ThreadLocal 里面的，有截图为证。 　　 　　<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139169894405.jpg"><br>本文是自己在学习 ThreadLocal 的时候，一时兴起，深入看了源码，思考了此类的作用、使用范围，进而联想到对传统的 synchronize 共享变量线程安全的问题进行比较，而总结的博文，总结一句话就是一个是锁机制进行时间换空间，一个是存储拷贝进行空间换时间。 (全文完)</p><blockquote><p>转载自 <span class="exturl" data-url="aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbHVmZW5nMjA=">http://blog.csdn.net/lufeng20<i class="fa fa-external-link-alt"></i></span></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/46308.html</id>
    <link href="https://www.bytelife.net/articles/46308.html"/>
    <published>2017-03-15T15:34:01.000Z</published>
    <summary>
      <![CDATA[<h2 id="ThreadLocal是什么"><a href="#ThreadLocal是什么" class="headerlink" title="ThreadLocal是什么"></a>ThreadLocal 是什么</h2><p>早在 JDK 1.2 的版本中就提供 Java.lang.ThreadLocal，ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。<br>当使用 ThreadLocal 维护变量时，ThreadLocal 为每个使用该变量的线程提供独立的变量副本，所以每一个线程都可以独立地改变自己的副本，而不会影响其它线程所对应的副本。<br>从线程的角度看，目标变量就象是线程的本地变量，这也是类名中 “Local” 所要表达的意思。<br>所以，在 Java 中编写线程局部变量的代码相对来说要笨拙一些，因此造成线程局部变量没有在 Java 开发者中得到很好的普及。 　　</p>]]>
    </summary>
    <title>ThreadLocal 详解</title>
    <updated>2017-03-15T15:34:01.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="NIO" scheme="https://www.bytelife.net/tags/NIO/"/>
    <content>
      <![CDATA[<p>Java NIO (New IO) 是一个可以替代标准 Java IO API 的 IO API（从 Java 1.4 开始)，Java NIO 提供了与标准 IO 不同的 IO 工作方式。</p><ul><li><strong>Java NIO: Channels and Buffers（通道和缓冲区）</strong>：标准的 IO 基于字节流和字符流进行操作的，而 NIO 是基于通道（Channel）和缓冲区（Buffer）进行操作，数据总是从通道读取到缓冲区中，或者从缓冲区写入到通道中。</li><li><strong>Java NIO: Non-blocking IO（非阻塞 IO）</strong>：Java NIO 可以让你非阻塞的使用 IO，例如：当线程从通道读取数据到缓冲区时，线程还是可以进行其他事情。当数据被写入到缓冲区时，线程可以继续处理它。从缓冲区写入通道也类似。</li><li><strong>Java NIO: Selectors（选择器）</strong>：Java NIO 引入了选择器的概念，选择器用于监听多个通道的事件（比如：连接打开，数据到达）。因此，单个的线程可以监听多个数据通道。</li></ul><span id="more"></span><hr><h2 id="一、Java-NIO-概述"><a href="#一、Java-NIO-概述" class="headerlink" title="一、Java NIO 概述"></a>一、Java NIO 概述</h2><p>Java NIO 由以下几个核心部分组成：</p><ul><li>Channels</li><li>Buffers</li><li>Selectors</li></ul><p>虽然 Java NIO 中除此之外还有很多类和组件，但在我看来，Channel，Buffer 和 Selector 构成了核心的 API。其它组件，如 Pipe 和 FileLock，只不过是与三个核心组件共同使用的工具类。因此，在概述中我将集中在这三个组件上。其它组件会在单独的章节中讲到。</p><h3 id="Channel-和-Buffer"><a href="#Channel-和-Buffer" class="headerlink" title="Channel 和 Buffer"></a>Channel 和 Buffer</h3><p>基本上，所有的 IO 在 NIO 中都从一个 Channel 开始。Channel 有点象流。 数据可以从 Channel 读到 Buffer 中，也可以从 Buffer 写到 Channel 中。这里有个图示：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139194654201.jpg"></p><p>Channel 和 Buffer 有好几种类型。下面是 JAVA NIO 中的一些主要 Channel 的实现：</p><ul><li>FileChannel</li><li>DatagramChannel</li><li>SocketChannel</li><li>ServerSocketChannel</li></ul><p>正如你所看到的，这些通道涵盖了 UDP 和 TCP 网络 IO，以及文件 IO。 与这些类一起的有一些有趣的接口，但为简单起见，我尽量在概述中不提到它们。本教程其它章节与它们相关的地方我会进行解释。 以下是 Java NIO 里关键的 Buffer 实现：</p><ul><li>ByteBuffer</li><li>CharBuffer</li><li>DoubleBuffer</li><li>FloatBuffer</li><li>IntBuffer</li><li>LongBuffer</li><li>ShortBuffer</li></ul><p>这些 Buffer 覆盖了你能通过 IO 发送的基本数据类型：byte, short, int, long, float, double 和 char。 Java NIO 还有个 MappedByteBuffer，用于表示内存映射文件， 我也不打算在概述中说明。</p><h3 id="Selector"><a href="#Selector" class="headerlink" title="Selector"></a>Selector</h3><p>Selector 允许单线程处理多个 Channel。如果你的应用打开了多个连接（通道），但每个连接的流量都很低，使用 Selector 就会很方便。例如，在一个聊天服务器中。 这是在一个单线程中使用一个 Selector 处理 3 个 Channel 的图示：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139194743704.jpg"></p><p>要使用 Selector，得向 Selector 注册 Channel，然后调用它的<code>select()</code>方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回，线程就可以处理这些事件，事件的例子有如新连接进来，数据接收等。</p><hr><h2 id="二、Channal"><a href="#二、Channal" class="headerlink" title="二、Channal"></a>二、Channal</h2><p>Java NIO 的通道类似流，但又有些不同：</p><ul><li>既可以从通道中读取数据，又可以写数据到通道。但流的读写通常是单向的。</li><li>通道可以异步地读写。</li><li>通道中的数据总是要先读到一个 Buffer，或者总是要从一个 Buffer 中写入。</li></ul><p>正如上面所说，从通道读取数据到缓冲区，从缓冲区写入数据到通道。如下图所示：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139194846539.jpg"></p><h3 id="Channel的实现"><a href="#Channel的实现" class="headerlink" title="Channel的实现"></a>Channel 的实现</h3><p>这些是 Java NIO 中最重要的通道的实现：</p><ul><li>FileChannel</li><li>DatagramChannel</li><li>SocketChannel</li><li>ServerSocketChannel</li></ul><p>FileChannel 从文件中读写数据。 DatagramChannel 能通过 UDP 读写网络中的数据。 SocketChannel 能通过 TCP 读写网络中的数据。 ServerSocketChannel 可以监听新进来的 TCP 连接，像 Web 服务器那样。对每一个新进来的连接都会创建一个 SocketChannel。</p><h3 id="基本的-Channel-示例"><a href="#基本的-Channel-示例" class="headerlink" title="基本的 Channel 示例"></a>基本的 Channel 示例</h3><p>下面是一个使用 FileChannel 读取数据到 Buffer 中的示例：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">RandomAccessFile</span> <span class="variable">aFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">"data/nio-data.txt"</span>, <span class="string">"rw"</span>);</span><br><span class="line"><span class="type">FileChannel</span> <span class="variable">inChannel</span> <span class="operator">=</span> aFile.getChannel();</span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">bytesRead</span> <span class="operator">=</span> inChannel.read(buf);</span><br><span class="line"><span class="keyword">while</span> (bytesRead != -<span class="number">1</span>) {</span><br><span class="line">  System.out.println(<span class="string">"Read "</span> + bytesRead);</span><br><span class="line">  buf.flip();</span><br><span class="line">  <span class="keyword">while</span>(buf.hasRemaining()){</span><br><span class="line">      System.out.print((<span class="type">char</span>) buf.get());</span><br><span class="line">  }</span><br><span class="line">  buf.clear();</span><br><span class="line">  bytesRead = inChannel.read(buf);</span><br><span class="line">}</span><br><span class="line">aFile.close();</span><br></pre></td></tr></tbody></table></figure><p>注意 buf.flip() 的调用，首先读取数据到 Buffer，然后反转 Buffer, 接着再从 Buffer 中读取数据。下一节会深入讲解 Buffer 的更多细节。</p><hr><h2 id="三、Buffer"><a href="#三、Buffer" class="headerlink" title="三、Buffer"></a>三、Buffer</h2><p>Java NIO 中的 Buffer 用于和 NIO 通道进行交互。如你所知，数据是从通道读入缓冲区，从缓冲区写入到通道中的。 缓冲区本质上是一块可以写入数据，然后可以从中读取数据的内存。这块内存被包装成 NIO Buffer 对象，并提供了一组方法，用来方便的访问该块内存。</p><h3 id="Buffer的基本用法"><a href="#Buffer的基本用法" class="headerlink" title="Buffer的基本用法"></a>Buffer 的基本用法</h3><p>使用 Buffer 读写数据一般遵循以下四个步骤：</p><ol><li>写入数据到 Buffer</li><li>调用<code>flip()</code>方法</li><li>从 Buffer 中读取数据</li><li>调用<code>clear()</code>方法或者<code>compact()</code>方法</li></ol><p>当向 buffer 写入数据时，buffer 会记录下写了多少数据。一旦要读取数据，需要通过<code>flip()</code>方法将 Buffer 从写模式切换到读模式。在读模式下，可以读取之前写入到 buffer 的所有数据。 一旦读完了所有的数据，就需要清空缓冲区，让它可以再次被写入。有两种方式能清空缓冲区：调用<code>clear()</code>或<code>compact()</code>方法。</p><ul><li><code>clear()</code>方法会清空整个缓冲区。</li><li><code>compact()</code>方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处，新写入的数据将放到缓冲区未读数据的后面。</li></ul><p>下面是一个使用 Buffer 的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">RandomAccessFile</span> <span class="variable">aFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">"data/nio-data.txt"</span>, <span class="string">"rw"</span>);</span><br><span class="line"><span class="type">FileChannel</span> <span class="variable">inChannel</span> <span class="operator">=</span> aFile.getChannel();</span><br><span class="line"><span class="comment">//create buffer with capacity of 48 bytes</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">bytesRead</span> <span class="operator">=</span> inChannel.read(buf); <span class="comment">//read into buffer.</span></span><br><span class="line"><span class="keyword">while</span> (bytesRead != -<span class="number">1</span>) {</span><br><span class="line">  buf.flip(); <span class="comment">//make buffer ready for read</span></span><br><span class="line">  <span class="keyword">while</span>(buf.hasRemaining()){</span><br><span class="line">      System.out.print((<span class="type">char</span>) buf.get()); <span class="comment">// read 1 byte at a time</span></span><br><span class="line">  }</span><br><span class="line">  buf.clear(); <span class="comment">//make buffer ready for writing</span></span><br><span class="line">  bytesRead = inChannel.read(buf);</span><br><span class="line">}</span><br><span class="line">aFile.close();</span><br></pre></td></tr></tbody></table></figure><h3 id="Buffer的capacity-position和limit"><a href="#Buffer的capacity-position和limit" class="headerlink" title="Buffer的capacity,position和limit"></a>Buffer 的 capacity,position 和 limit</h3><p>缓冲区本质上是一块可以写入数据，然后可以从中读取数据的内存。这块内存被包装成 NIO Buffer 对象，并提供了一组方法，用来方便的访问该块内存。 为了理解 Buffer 的工作原理，需要熟悉它的三个属性：</p><ul><li>capacity</li><li>position</li><li>limit</li></ul><p>position 和 limit 的含义取决于 Buffer 处在读模式还是写模式。不管 Buffer 处在什么模式，capacity 的含义总是一样的。 这里有一个关于 capacity，position 和 limit 在读写模式中的说明，详细的解释在插图后面。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139195185308.jpg"></p><ul><li><strong>capacity</strong>：作为一个内存块，Buffer 有一个固定的大小值，也叫 “capacity”.你只能往里写 capacity 个 byte、long，char 等类型。一旦 Buffer 满了，需要将其清空（通过读数据或者清除数据）才能继续写数据往里写数据。</li><li><strong>position</strong>：当你写数据到 Buffer 中时，position 表示当前的位置。初始的 position 值为 0. 当一个 byte、long 等数据写到 Buffer 后， position 会向前移动到下一个可插入数据的 Buffer 单元。position 最大可为 capacity – 1。 当读取数据时，也是从某个特定位置读。当将 Buffer 从写模式切换到读模式，position 会被重置为 0. 当从 Buffer 的 position 处读取数据时，position 向前移动到下一个可读的位置。</li><li><strong>limit</strong>：在写模式下，Buffer 的 limit 表示你最多能往 Buffer 里写多少数据。 写模式下，limit 等于 Buffer 的 capacity。当切换 Buffer 到读模式时， limit 表示你最多能读到多少数据。因此，当切换 Buffer 到读模式时，limit 会被设置成写模式下的 position 值。换句话说，你能读到之前写入的所有数据（limit 被设置成已写数据的数量，这个值在写模式下就是 position）。</li></ul><h3 id="Buffer的类型"><a href="#Buffer的类型" class="headerlink" title="Buffer的类型"></a>Buffer 的类型</h3><p>Java NIO 有以下 Buffer 类型</p><ul><li>ByteBuffer</li><li>MappedByteBuffer</li><li>CharBuffer</li><li>DoubleBuffer</li><li>FloatBuffer</li><li>IntBuffer</li><li>LongBuffer</li><li>ShortBuffer</li></ul><p>如你所见，这些 Buffer 类型代表了不同的数据类型。换句话说，就是可以通过 char，short，int，long，float 或 double 类型来操作缓冲区中的字节。 MappedByteBuffer 有些特别，在涉及它的专门章节中再讲。</p><h3 id="Buffer的分配"><a href="#Buffer的分配" class="headerlink" title="Buffer的分配"></a>Buffer 的分配</h3><p>要想获得一个 Buffer 对象首先要进行分配。 每一个 Buffer 类都有一个<code>allocate()</code>方法。下面是一个分配 48 字节 capacity 的 ByteBuffer 的例子。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br></pre></td></tr></tbody></table></figure><p>这是分配一个可存储 1024 个字符的 CharBuffer：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">CharBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> CharBuffer.allocate(<span class="number">1024</span>);</span><br></pre></td></tr></tbody></table></figure><h3 id="向Buffer中写数据"><a href="#向Buffer中写数据" class="headerlink" title="向Buffer中写数据"></a>向 Buffer 中写数据</h3><p>写数据到 Buffer 有两种方式：</p><ul><li>从 Channel 写到 Buffer。</li><li>通过 Buffer 的<code>put()</code>方法写到 Buffer 里。</li></ul><p>从 Channel 写到 Buffer 的例子</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">bytesRead</span> <span class="operator">=</span> inChannel.read(buf); <span class="comment">//read into buffer.</span></span><br></pre></td></tr></tbody></table></figure><p>通过<code>put()</code>方法写 Buffer 的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">buf.put(<span class="number">127</span>);</span><br></pre></td></tr></tbody></table></figure><p><code>put()</code>方法有很多版本，允许你以不同的方式把数据写入到 Buffer 中。例如， 写到一个指定的位置，或者把一个字节数组写入到 Buffer。 更多 Buffer 实现的细节参考 JavaDoc。</p><h4 id="flip-方法"><a href="#flip-方法" class="headerlink" title="flip()方法"></a>flip () 方法</h4><p><code>flip()</code>方法将 Buffer 从写模式切换到读模式。调用<code>flip()</code>方法会将 position 设回 0，并将 limit 设置成之前 position 的值。 换句话说，position 现在用于标记读的位置，limit 表示之前写进了多少个 byte、char 等 —— 现在能读取多少个 byte、char 等。</p><h3 id="从Buffer中读取数据"><a href="#从Buffer中读取数据" class="headerlink" title="从Buffer中读取数据"></a>从 Buffer 中读取数据</h3><p>从 Buffer 中读取数据有两种方式：</p><ul><li>从 Buffer 读取数据到 Channel。</li><li>使用<code>get()</code>方法从 Buffer 中读取数据。</li></ul><p>从 Buffer 读取数据到 Channel 的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//read from buffer into channel.</span></span><br><span class="line"><span class="type">int</span> <span class="variable">bytesWritten</span> <span class="operator">=</span> inChannel.write(buf);</span><br></pre></td></tr></tbody></table></figure><p>使用<code>get()</code>方法从 Buffer 中读取数据的例子</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">byte</span> <span class="variable">aByte</span> <span class="operator">=</span> buf.get();</span><br></pre></td></tr></tbody></table></figure><p><code>get()</code>方法有很多版本，允许你以不同的方式从 Buffer 中读取数据。例如，从指定 position 读取，或者从 Buffer 中读取数据到字节数组。更多 Buffer 实现的细节参考 JavaDoc。</p><h3 id="rewind-方法"><a href="#rewind-方法" class="headerlink" title="rewind()方法"></a>rewind () 方法</h3><p><code>Buffer.rewind()</code>将 position 设回 0，所以你可以重读 Buffer 中的所有数据。limit 保持不变，仍然表示能从 Buffer 中读取多少个元素（byte、char 等）。</p><h3 id="clear-与compact-方法"><a href="#clear-与compact-方法" class="headerlink" title="clear()与compact()方法"></a>clear () 与 compact () 方法</h3><p>一旦读完 Buffer 中的数据，需要让 Buffer 准备好再次被写入。可以通过<code>clear()</code>或<code>compact()</code>方法来完成。</p><ul><li><p>如果调用的是<code>clear()</code>方法，position 将被设回 0，limit 被设置成 capacity 的值。换句话说，Buffer 被清空了。Buffer 中的数据并未清除，只是这些标记告诉我们可以从哪里开始往 Buffer 里写数据。如果 Buffer 中有一些未读的数据，调用<code>clear()</code>方法，数据将 “被遗忘”，意味着不再有任何标记会告诉你哪些数据被读过，哪些还没有。</p></li><li><p>如果 Buffer 中仍有未读的数据，且后续还需要这些数据，但是此时想要先先写些数据，那么使用<code>compact()</code>方法。<code>compact()</code>方法将所有未读的数据拷贝到 Buffer 起始处。然后将 position 设到最后一个未读元素正后面。limit 属性依然像<code>clear()</code>方法一样，设置成 capacity。现在 Buffer 准备好写数据了，但是不会覆盖未读的数据。</p></li></ul><h3 id="mark-与reset-方法"><a href="#mark-与reset-方法" class="headerlink" title="mark()与reset()方法"></a>mark () 与 reset () 方法</h3><p>通过调用<code>Buffer.mark()</code>方法，可以标记 Buffer 中的一个特定 position。之后可以通过调用<code>Buffer.reset()</code>方法恢复到这个 position。例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">buffer.mark();</span><br><span class="line"><span class="comment">//call buffer.get() a couple of times, e.g. during parsing.</span></span><br><span class="line">buffer.reset(); <span class="comment">//set position back to mark.</span></span><br></pre></td></tr></tbody></table></figure><h3 id="equals-与compareTo-方法"><a href="#equals-与compareTo-方法" class="headerlink" title="equals()与compareTo()方法"></a>equals () 与 compareTo () 方法</h3><p>可以使用<code>equals()</code>和<code>compareTo()</code>方法两个 Buffer。 ####equals () 当满足下列条件时，表示两个 Buffer 相等：</p><ol><li>有相同的类型（byte、char、int 等）。</li><li>Buffer 中剩余的 byte、char 等的个数相等。</li><li>Buffer 中所有剩余的 byte、char 等都相同。</li></ol><p>如你所见，equals 只是比较 Buffer 的一部分，不是每一个在它里面的元素都比较。实际上，它只比较 Buffer 中的剩余元素。 ####compareTo () 方法 <code>compareTo()</code>方法比较两个 Buffer 的剩余元素 (byte、char 等)， 如果满足下列条件，则认为一个 Buffer “小于” 另一个 Buffer：</p><ol><li>第一个不相等的元素小于另一个 Buffer 中对应的元素 。</li><li>所有元素都相等，但第一个 Buffer 比另一个先耗尽 (第一个 Buffer 的元素个数比另一个少。</li></ol><p>（译注：剩余元素是从 position 到 limit 之间的元素）</p><hr><h2 id="四、Scatter-Gather"><a href="#四、Scatter-Gather" class="headerlink" title="四、Scatter/Gather"></a>四、Scatter/Gather</h2><p>Java NIO 开始支持 scatter/gather，scatter/gather 用于描述从 Channel（译者注：Channel 在中文经常翻译为通道）中读取或者写入到 Channel 的操作。</p><ul><li><strong>分散（scatter）</strong>：从 Channel 中读取是指在读操作时将读取的数据写入多个 buffer 中。因此，Channel 将从 Channel 中读取的数据 “分散（scatter）” 到多个 Buffer 中。</li><li><strong>聚集（gather）</strong>：写入 Channel 是指在写操作时将多个 buffer 的数据写入同一个 Channel，因此，Channel 将多个 Buffer 中的数据 “聚集（gather）” 后发送到 Channel。</li></ul><p>scatter / gather 经常用于需要将传输的数据分开处理的场合，例如传输一个由消息头和消息体组成的消息，你可能会将消息体和消息头分散到不同的 buffer 中，这样你可以方便的处理消息头和消息体。 ###Scattering Reads Scattering Reads 是指数据从一个 channel 读取到多个 buffer 中。如下图描述：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139195742355.jpg"><br> 代码示例如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">header</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">128</span>);</span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">body</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"><span class="type">ByteBuffer</span>[] <span class="variable">bufferArray</span> <span class="operator">=</span> { header, body };</span><br><span class="line">channel.read(bufferArray);</span><br></pre></td></tr></tbody></table></figure><p>注意 buffer 首先被插入到数组，然后再将数组作为<code>channel.read()</code> 的输入参数。<code>read()</code>方法按照 buffer 在数组中的顺序将从 channel 中读取的数据写入到 buffer，当一个 buffer 被写满后，channel 紧接着向另一个 buffer 中写。 Scattering Reads 在移动下一个 buffer 前，必须填满当前的 buffer，这也意味着它不适用于动态消息 (译者注：消息大小不固定)。换句话说，如果存在消息头和消息体，消息头必须完成填充（例如 128byte），Scattering Reads 才能正常工作。</p><h3 id="Gathering-Writes"><a href="#Gathering-Writes" class="headerlink" title="Gathering Writes"></a>Gathering Writes</h3><p>Gathering Writes 是指数据从多个 buffer 写入到同一个 channel。如下图描述：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139195928340.jpg"></p><p> 代码示例如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">header</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">128</span>);</span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">body</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"><span class="comment">//write data into buffers</span></span><br><span class="line"><span class="type">ByteBuffer</span>[] <span class="variable">bufferArray</span> <span class="operator">=</span> { header, body };</span><br><span class="line">channel.write(bufferArray);</span><br></pre></td></tr></tbody></table></figure><p>buffers 数组是<code>write()</code>方法的入参，write () 方法会按照 buffer 在数组中的顺序，将数据写入到 channel，注意只有 position 和 limit 之间的数据才会被写入。因此，如果一个 buffer 的容量为 128byte，但是仅仅包含 58byte 的数据，那么这 58byte 的数据将被写入到 channel 中。因此与 Scattering Reads 相反，Gathering Writes 能较好的处理动态消息。</p><hr><h2 id="五、通道之间的数据传输"><a href="#五、通道之间的数据传输" class="headerlink" title="五、通道之间的数据传输"></a>五、通道之间的数据传输</h2><p>在 Java NIO 中，如果两个通道中有一个是 FileChannel，那你可以直接将数据从一个 channel（译者注：channel 中文常译作通道）传输到另外一个 channel。</p><h3 id="transferFrom"><a href="#transferFrom" class="headerlink" title="transferFrom()"></a>transferFrom()</h3><p>FileChannel 的<code>transferFrom()</code>方法可以将数据从源通道传输到 FileChannel 中（译者注：这个方法在 JDK 文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中）。下面是一个简单的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">javaRandomAccessFile</span> <span class="variable">fromFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">"fromFile.txt"</span>, <span class="string">"rw"</span>);</span><br><span class="line"><span class="type">FileChannel</span> <span class="variable">fromChannel</span> <span class="operator">=</span> fromFile.getChannel();</span><br><span class="line"><span class="type">RandomAccessFile</span> <span class="variable">toFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">"toFile.txt"</span>, <span class="string">"rw"</span>);</span><br><span class="line"><span class="type">FileChannel</span> <span class="variable">toChannel</span> <span class="operator">=</span> toFile.getChannel();</span><br><span class="line"><span class="type">long</span> <span class="variable">position</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="type">long</span> <span class="variable">count</span> <span class="operator">=</span> fromChannel.size();</span><br><span class="line">toChannel.transferFrom(fromChannel, position, count);</span><br></pre></td></tr></tbody></table></figure><p>方法的输入参数 position 表示从 position 处开始向目标文件写入数据，count 表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节，则所传输的字节数要小于请求的字节数。 此外要注意，在 SoketChannel 的实现中，SocketChannel 只会传输此刻准备好的数据（可能不足 count 字节）。因此，SocketChannel 可能不会将请求的所有数据 (count 个字节) 全部传输到 FileChannel 中。</p><h3 id="transferTo"><a href="#transferTo" class="headerlink" title="transferTo()"></a>transferTo()</h3><p><code>transferTo()</code>方法将数据从 FileChannel 传输到其他的 channel 中。下面是一个简单的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">RandomAccessFile</span> <span class="variable">fromFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">"fromFile.txt"</span>, <span class="string">"rw"</span>);</span><br><span class="line"><span class="type">FileChannel</span> <span class="variable">fromChannel</span> <span class="operator">=</span> fromFile.getChannel();</span><br><span class="line"><span class="type">RandomAccessFile</span> <span class="variable">toFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">"toFile.txt"</span>, <span class="string">"rw"</span>);</span><br><span class="line"><span class="type">FileChannel</span> <span class="variable">toChannel</span> <span class="operator">=</span> toFile.getChannel();</span><br><span class="line"><span class="type">long</span> <span class="variable">position</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="type">long</span> <span class="variable">count</span> <span class="operator">=</span> fromChannel.size();</span><br><span class="line">fromChannel.transferTo(position, count, toChannel);</span><br></pre></td></tr></tbody></table></figure><p>是不是发现这个例子和前面那个例子特别相似？除了调用方法的 FileChannel 对象不一样外，其他的都一样。 上面所说的关于 SocketChannel 的问题在<code>transferTo()</code>方法中同样存在。SocketChannel 会一直传输数据直到目标 buffer 被填满。</p><hr><h2 id="六、Selector"><a href="#六、Selector" class="headerlink" title="六、Selector"></a>六、Selector</h2><p>Selector（选择器）是 Java NIO 中能够检测一到多个 NIO 通道，并能够知晓通道是否为诸如读写事件做好准备的组件。这样，一个单独的线程可以管理多个 channel，从而管理多个网络连接。</p><h3 id="为什么使用Selector"><a href="#为什么使用Selector" class="headerlink" title="为什么使用Selector?"></a>为什么使用 Selector?</h3><p>仅用单个线程来处理多个 Channels 的好处是，只需要更少的线程来处理通道。事实上，可以只用一个线程处理所有的通道。对于操作系统来说，线程之间上下文切换的开销很大，而且每个线程都要占用系统的一些资源（如内存）。因此，使用的线程越少越好。 但是，需要记住，现代的操作系统和 CPU 在多任务方面表现的越来越好，所以多线程的开销随着时间的推移，变得越来越小了。实际上，如果一个 CPU 有多个内核，不使用多任务可能是在浪费 CPU 能力。不管怎么说，关于那种设计的讨论应该放在另一篇不同的文章中。在这里，只要知道使用 Selector 能够处理多个通道就足够了。 下面是单线程使用一个 Selector 处理 3 个 channel 的示例图：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139196216197.jpg"></p><h3 id="Selector的创建"><a href="#Selector的创建" class="headerlink" title="Selector的创建"></a>Selector 的创建</h3><p>通过调用<code>Selector.open()</code>方法创建一个 Selector，如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><h3 id="向Selector注册通道"><a href="#向Selector注册通道" class="headerlink" title="向Selector注册通道"></a>向 Selector 注册通道</h3><p>为了将 Channel 和 Selector 配合使用，必须将 channel 注册到 selector 上。通过<br><code>SelectableChannel.register()</code>方法来实现，如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">channel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line"><span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> channel.register(selector, Selectionkey.OP_READ);</span><br></pre></td></tr></tbody></table></figure><p>与 Selector 一起使用时，Channel 必须处于非阻塞模式下。这意味着不能将 FileChannel 与 Selector 一起使用，因为 FileChannel 不能切换到非阻塞模式。而套接字通道都可以。 注意 register () 方法的第二个参数。这是一个 “interest 集合”，意思是在通过 Selector 监听 Channel 时对什么事件感兴趣。可以监听四种不同类型的事件：</p><ol><li>Connect</li><li>Accept</li><li>Read</li><li>Write</li></ol><p>通道触发了一个事件意思是该事件已经就绪。所以，某个 channel 成功连接到另一个服务器称为 “连接就绪”。一个 server socket channel 准备好接收新进入的连接称为 “接收就绪”。一个有数据可读的通道可以说是 “读就绪”。等待写数据的通道可以说是 “写就绪”。 这四种事件用 SelectionKey 的四个常量来表示：</p><ol><li>SelectionKey.OP_CONNECT</li><li>SelectionKey.OP_ACCEPT</li><li>SelectionKey.OP_READ</li><li>SelectionKey.OP_WRITE</li></ol><p>如果你对不止一种事件感兴趣，那么可以用 “位或” 操作符将常量连接起来，如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">interestSet</span> <span class="operator">=</span> SelectionKey.OP_READ | SelectionKey.OP_WRITE;</span><br></pre></td></tr></tbody></table></figure><p>在下面还会继续提到 interest 集合。</p><h3 id="SelectionKey"><a href="#SelectionKey" class="headerlink" title="SelectionKey"></a>SelectionKey</h3><p>在上一小节中，当向 Selector 注册 Channel 时，register () 方法会返回一个 SelectionKey 对象。这个对象包含了一些你感兴趣的属性：</p><ul><li>interest 集合</li><li>ready 集合</li><li>Channel</li><li>Selector</li><li>附加的对象（可选）</li></ul><p>下面我会描述这些属性。</p><h4 id="interest集合"><a href="#interest集合" class="headerlink" title="interest集合"></a>interest 集合</h4><p>就像向 Selector 注册通道一节中所描述的，interest 集合是你所选择的感兴趣的事件集合。可以通过 SelectionKey 读写 interest 集合，像这样：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">interestSet</span> <span class="operator">=</span> selectionKey.interestOps();</span><br><span class="line"><span class="type">boolean</span> <span class="variable">isInterestedInAccept</span> <span class="operator">=</span> (interestSet &amp; SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT；</span><br><span class="line"><span class="type">boolean</span> <span class="variable">isInterestedInConnect</span> <span class="operator">=</span> interestSet &amp; SelectionKey.OP_CONNECT;</span><br><span class="line"><span class="type">boolean</span> <span class="variable">isInterestedInRead</span> <span class="operator">=</span> interestSet &amp; SelectionKey.OP_READ;</span><br><span class="line"><span class="type">boolean</span> <span class="variable">isInterestedInWrite</span> <span class="operator">=</span> interestSet &amp; SelectionKey.OP_WRITE;</span><br></pre></td></tr></tbody></table></figure><p>可以看到，用 “位与” 操作 interest 集合和给定的 SelectionKey 常量，可以确定某个确定的事件是否在 interest 集合中。</p><h4 id="ready集合"><a href="#ready集合" class="headerlink" title="ready集合"></a>ready 集合</h4><p>ready 集合是通道已经准备就绪的操作的集合。在一次选择 (Selection) 之后，你会首先访问这个 ready set。Selection 将在下一小节进行解释。可以这样访问 ready 集合：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">readySet</span> <span class="operator">=</span> selectionKey.readyOps();</span><br></pre></td></tr></tbody></table></figure><p>可以用像检测 interest 集合那样的方法，来检测 channel 中什么事件或操作已经就绪。但是，也可以使用以下四个方法，它们都会返回一个布尔类型：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">selectionKey.isAcceptable();</span><br><span class="line">selectionKey.isConnectable();</span><br><span class="line">selectionKey.isReadable();</span><br><span class="line">selectionKey.isWritable();</span><br></pre></td></tr></tbody></table></figure><h4 id="Channel-Selector"><a href="#Channel-Selector" class="headerlink" title="Channel + Selector"></a>Channel + Selector</h4><p>从 SelectionKey 访问 Channel 和 Selector 很简单。如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Channel</span> <span class="variable">channel</span> <span class="operator">=</span> selectionKey.channel();</span><br><span class="line"><span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> selectionKey.selector();</span><br></pre></td></tr></tbody></table></figure><h4 id="附加的对象"><a href="#附加的对象" class="headerlink" title="附加的对象"></a>附加的对象</h4><p>可以将一个对象或者更多信息附着到 SelectionKey 上，这样就能方便的识别某个给定的通道。例如，可以附加 与通道一起使用的 Buffer，或是包含聚集数据的某个对象。使用方法如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">selectionKey.attach(theObject);</span><br><span class="line"><span class="type">Object</span> <span class="variable">attachedObj</span> <span class="operator">=</span> selectionKey.attachment();</span><br></pre></td></tr></tbody></table></figure><p>还可以在用<code>register()</code>方法向 Selector 注册 Channel 的时候附加对象。如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> channel.register(selector, SelectionKey.OP_READ, theObject);</span><br></pre></td></tr></tbody></table></figure><h3 id="通过Selector选择通道"><a href="#通过Selector选择通道" class="headerlink" title="通过Selector选择通道"></a>通过 Selector 选择通道</h3><p>一旦向 Selector 注册了一或多个通道，就可以调用几个重载的<code>select()</code>方法。这些方法返回你所感兴趣的事件（如连接、接受、读或写）已经准备就绪的那些通道。换句话说，如果你对 “读就绪” 的通道感兴趣，<code>select()</code>方法会返回读事件已经就绪的那些通道。 下面是 select () 方法：</p><ul><li>int select ()：阻塞到至少有一个通道在你注册的事件上就绪了。</li><li>int select (long timeout)：和<code>select()</code>一样，除了最长会阻塞 timeout 毫秒 (参数)。</li><li>int selectNow ()：不会阻塞，不管什么通道就绪都立刻返回（译者注：此方法执行非阻塞的选择操作。如果自从前一次选择操作后，没有通道变成可选择的，则此方法直接返回零）。</li></ul><p><code>select()</code>方法返回的 int 值表示有多少通道已经就绪。亦即，自上次调用<code>select()</code>方法后有多少通道变成就绪状态。如果调用<code>select()</code>方法，因为有一个通道变成就绪状态，返回了 1，若再次调用<code>select()</code>方法，如果另一个通道就绪了，它会再次返回 1。如果对第一个就绪的 channel 没有做任何操作，现在就有两个就绪的通道，但在每次<code>select()</code>方法调用之间，只有一个通道就绪了。</p><h3 id="selectedKeys"><a href="#selectedKeys" class="headerlink" title="selectedKeys()"></a>selectedKeys()</h3><p>一旦调用了<code>select()</code>方法，并且返回值表明有一个或更多个通道就绪了，然后可以通过调用 selector 的<code>selectedKeys()</code>方法，访问 “已选择键集（selected key set）” 中的就绪通道。如下所示：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Set</span> <span class="variable">selectedKeys</span> <span class="operator">=</span> selector.selectedKeys();</span><br></pre></td></tr></tbody></table></figure><p>当像 Selector 注册 Channel 时，<code>Channel.register()</code>方法会返回一个 SelectionKey 对象。这个对象代表了注册到该 Selector 的通道。可以通过 SelectionKey 的<code>selectedKeySet()</code>方法访问这些对象。 可以遍历这个已选择的键集合来访问就绪的通道。如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Set</span> <span class="variable">selectedKeys</span> <span class="operator">=</span> selector.selectedKeys();</span><br><span class="line"><span class="type">Iterator</span> <span class="variable">keyIterator</span> <span class="operator">=</span> selectedKeys.iterator();</span><br><span class="line"><span class="keyword">while</span>(keyIterator.hasNext()) {</span><br><span class="line">    <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> keyIterator.next();</span><br><span class="line">    <span class="keyword">if</span>(key.isAcceptable()) {</span><br><span class="line">        <span class="comment">// a connection was accepted by a ServerSocketChannel.</span></span><br><span class="line">    } <span class="keyword">else</span> <span class="keyword">if</span> (key.isConnectable()) {</span><br><span class="line">        <span class="comment">// a connection was established with a remote server.</span></span><br><span class="line">    } <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) {</span><br><span class="line">        <span class="comment">// a channel is ready for reading</span></span><br><span class="line">    } <span class="keyword">else</span> <span class="keyword">if</span> (key.isWritable()) {</span><br><span class="line">        <span class="comment">// a channel is ready for writing</span></span><br><span class="line">    }</span><br><span class="line">    keyIterator.remove();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这个循环遍历已选择键集中的每个键，并检测各个键所对应的通道的就绪事件。 注意每次迭代末尾的<code>keyIterator.remove()</code>调用。Selector 不会自己从已选择键集中移除 SelectionKey 实例。必须在处理完通道时自己移除。下次该通道变成就绪时，Selector 会再次将其放入已选择键集中。 <code>SelectionKey.channel()</code>方法返回的通道需要转型成你要处理的类型，如 ServerSocketChannel 或 SocketChannel 等。</p><h3 id="wakeUp"><a href="#wakeUp" class="headerlink" title="wakeUp()"></a>wakeUp()</h3><p>某个线程调用<code>select()</code>方法后阻塞了，即使没有通道已经就绪，也有办法让其从<code>select()</code>方法返回。只要让其它线程在第一个线程调用<code>select()</code>方法的那个对象上调用<code>Selector.wakeup()</code>方法即可。阻塞在 select () 方法上的线程会立马返回。 如果有其它线程调用了<code>wakeup()</code>方法，但当前没有线程阻塞在<code>select()</code>方法上，下个调用 select () 方法的线程会立即 “醒来（wake up）”。</p><h3 id="close"><a href="#close" class="headerlink" title="close()"></a>close()</h3><p>用完 Selector 后调用其<code>close()</code>方法会关闭该 Selector，且使注册到该 Selector 上的所有 SelectionKey 实例无效。通道本身并不会关闭。</p><h3 id="完整的示例"><a href="#完整的示例" class="headerlink" title="完整的示例"></a>完整的示例</h3><p>这里有一个完整的示例，打开一个 Selector，注册一个通道注册到这个 Selector 上 (通道的初始化过程略去), 然后持续监控这个 Selector 的四种事件（接受，连接，读，写）是否就绪。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">channel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line"><span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> channel.register(selector, SelectionKey.OP_READ);</span><br><span class="line"><span class="keyword">while</span>(<span class="literal">true</span>) {</span><br><span class="line">  <span class="type">int</span> <span class="variable">readyChannels</span> <span class="operator">=</span> selector.select();</span><br><span class="line">  <span class="keyword">if</span>(readyChannels == <span class="number">0</span>) <span class="keyword">continue</span>;</span><br><span class="line">  <span class="type">Set</span> <span class="variable">selectedKeys</span> <span class="operator">=</span> selector.selectedKeys();</span><br><span class="line">  <span class="type">Iterator</span> <span class="variable">keyIterator</span> <span class="operator">=</span> selectedKeys.iterator();</span><br><span class="line">  <span class="keyword">while</span>(keyIterator.hasNext()) {</span><br><span class="line">    <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> keyIterator.next();</span><br><span class="line">    <span class="keyword">if</span>(key.isAcceptable()) {</span><br><span class="line">        <span class="comment">// a connection was accepted by a ServerSocketChannel.</span></span><br><span class="line">    } <span class="keyword">else</span> <span class="keyword">if</span> (key.isConnectable()) {</span><br><span class="line">        <span class="comment">// a connection was established with a remote server.</span></span><br><span class="line">    } <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) {</span><br><span class="line">        <span class="comment">// a channel is ready for reading</span></span><br><span class="line">    } <span class="keyword">else</span> <span class="keyword">if</span> (key.isWritable()) {</span><br><span class="line">        <span class="comment">// a channel is ready for writing</span></span><br><span class="line">    }</span><br><span class="line">    keyIterator.remove();</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><hr><h2 id="七、FileChannel"><a href="#七、FileChannel" class="headerlink" title="七、FileChannel"></a>七、FileChannel</h2><p>Java NIO 中的 FileChannel 是一个连接到文件的通道。可以通过文件通道读写文件。 FileChannel 无法设置为非阻塞模式，它总是运行在阻塞模式下。</p><h3 id="打开FileChannel"><a href="#打开FileChannel" class="headerlink" title="打开FileChannel"></a>打开 FileChannel</h3><p>在使用 FileChannel 之前，必须先打开它。但是，我们无法直接打开一个 FileChannel，需要通过使用一个 InputStream、OutputStream 或 RandomAccessFile 来获取一个 FileChannel 实例。 下面是通过 RandomAccessFile 打开 FileChannel 的示例：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">RandomAccessFile</span> <span class="variable">aFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">"data/nio-data.txt"</span>, <span class="string">"rw"</span>);</span><br><span class="line"><span class="type">FileChannel</span> <span class="variable">inChannel</span> <span class="operator">=</span> aFile.getChannel();</span><br></pre></td></tr></tbody></table></figure><h3 id="从FileChannel读取数据"><a href="#从FileChannel读取数据" class="headerlink" title="从FileChannel读取数据"></a>从 FileChannel 读取数据</h3><p>调用多个<code>read()</code>方法之一从 FileChannel 中读取数据。如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">bytesRead</span> <span class="operator">=</span> inChannel.read(buf);</span><br></pre></td></tr></tbody></table></figure><p>首先，分配一个 Buffer。从 FileChannel 中读取的数据将被读到 Buffer 中。 然后，调用 FileChannel.read() 方法。该方法将数据从 FileChannel 读取到 Buffer 中。read () 方法返回的 int 值表示了有多少字节被读到了 Buffer 中。如果返回 -1，表示到了文件末尾。</p><h3 id="向FileChannel写数据"><a href="#向FileChannel写数据" class="headerlink" title="向FileChannel写数据"></a>向 FileChannel 写数据</h3><p>使用<code>FileChannel.write()</code>方法向 FileChannel 写数据，该方法的参数是一个 Buffer。如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">newData</span> <span class="operator">=</span> <span class="string">"New String to write to file..."</span> + System.currentTimeMillis();</span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line">buf.clear();</span><br><span class="line">buf.put(newData.getBytes());</span><br><span class="line">buf.flip();</span><br><span class="line"><span class="keyword">while</span>(buf.hasRemaining()) {</span><br><span class="line">    channel.write(buf);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>注意<code>FileChannel.write()</code>是在 while 循环中调用的。因为无法保证<code>write()</code>方法一次能向 FileChannel 写入多少字节，因此需要重复调用<code>write()</code>方法，直到 Buffer 中已经没有尚未写入通道的字节。</p><h3 id="关闭FileChannel"><a href="#关闭FileChannel" class="headerlink" title="关闭FileChannel"></a>关闭 FileChannel</h3><p>用完 FileChannel 后必须将其关闭。如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.close();</span><br></pre></td></tr></tbody></table></figure><h3 id="FileChannel的position方法"><a href="#FileChannel的position方法" class="headerlink" title="FileChannel的position方法"></a>FileChannel 的 position 方法</h3><p>有时可能需要在 FileChannel 的某个特定位置进行数据的读 / 写操作。可以通过调用<code>position()</code>方法获取 FileChannel 的当前位置。 也可以通过调用<code>position(long pos)</code>方法设置 FileChannel 的当前位置。 这里有两个例子:</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">pos</span> <span class="operator">=</span> channel.position();</span><br><span class="line">channel.position(pos +<span class="number">123</span>);</span><br></pre></td></tr></tbody></table></figure><ul><li>如果将位置设置在文件结束符之后，然后试图从文件通道中读取数据，读方法将返回 -1 —— 文件结束标志。</li><li>如果将位置设置在文件结束符之后，然后向通道中写数据，文件将撑大到当前位置并写入数据。这可能导致 “文件空洞”，磁盘上物理文件中写入的数据间有空隙。</li></ul><h3 id="FileChannel的size方法"><a href="#FileChannel的size方法" class="headerlink" title="FileChannel的size方法"></a>FileChannel 的 size 方法</h3><p>FileChannel 实例的<code>size()</code>方法将返回该实例所关联文件的大小。如:</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">fileSize</span> <span class="operator">=</span> channel.size();</span><br></pre></td></tr></tbody></table></figure><h3 id="FileChannel的truncate方法"><a href="#FileChannel的truncate方法" class="headerlink" title="FileChannel的truncate方法"></a>FileChannel 的 truncate 方法</h3><p>可以使用<code>FileChannel.truncate()</code>方法截取一个文件。截取文件时，文件将中指定长度后面的部分将被删除。如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.truncate(<span class="number">1024</span>);</span><br></pre></td></tr></tbody></table></figure><p>这个例子截取文件的前 1024 个字节。</p><h3 id="FileChannel的force方法"><a href="#FileChannel的force方法" class="headerlink" title="FileChannel的force方法"></a>FileChannel 的 force 方法</h3><p><code>FileChannel.force()</code>方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑，操作系统会将数据缓存在内存中，所以无法保证写入到 FileChannel 里的数据一定会即时写到磁盘上。要保证这一点，需要调用<code>force()</code>方法。 <code>force()</code>方法有一个 boolean 类型的参数，指明是否同时将文件元数据（权限信息等）写到磁盘上。 下面的例子同时将文件数据和元数据强制写到磁盘上：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.force(<span class="literal">true</span>);</span><br></pre></td></tr></tbody></table></figure><hr><h2 id="八、SocketChannel"><a href="#八、SocketChannel" class="headerlink" title="八、SocketChannel"></a>八、SocketChannel</h2><p>Java NIO 中的 SocketChannel 是一个连接到 TCP 网络套接字的通道。可以通过以下 2 种方式创建 SocketChannel：</p><ol><li>打开一个 SocketChannel 并连接到互联网上的某台服务器。</li><li>一个新连接到达 ServerSocketChannel 时，会创建一个 SocketChannel。</li></ol><h3 id="打开-SocketChannel"><a href="#打开-SocketChannel" class="headerlink" title="打开 SocketChannel"></a>打开 SocketChannel</h3><p>下面是 SocketChannel 的打开方式：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">socketChannel.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">"http://jenkov.com"</span>, <span class="number">80</span>));</span><br></pre></td></tr></tbody></table></figure><h3 id="关闭-SocketChannel"><a href="#关闭-SocketChannel" class="headerlink" title="关闭 SocketChannel"></a>关闭 SocketChannel</h3><p>当用完 SocketChannel 之后调用<code>SocketChannel.close()</code>关闭 SocketChannel：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">socketChannel.close();</span><br></pre></td></tr></tbody></table></figure><h3 id="从-SocketChannel-读取数据"><a href="#从-SocketChannel-读取数据" class="headerlink" title="从 SocketChannel 读取数据"></a>从 SocketChannel 读取数据</h3><p>要从 SocketChannel 中读取数据，调用一个<code>read()</code>的方法之一。以下是例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">bytesRead</span> <span class="operator">=</span> socketChannel.read(buf);</span><br></pre></td></tr></tbody></table></figure><p>首先，分配一个 Buffer。从 SocketChannel 读取到的数据将会放到这个 Buffer 中。 然后，调用<code>SocketChannel.read()</code>。该方法将数据从 SocketChannel 读到 Buffer 中。read () 方法返回的 int 值表示读了多少字节进 Buffer 里。如果返回的是 -1，表示已经读到了流的末尾（连接关闭了）。</p><h3 id="写入-SocketChannel"><a href="#写入-SocketChannel" class="headerlink" title="写入 SocketChannel"></a>写入 SocketChannel</h3><p>写数据到 SocketChannel 用的是<code>SocketChannel.write()</code>方法，该方法以一个 Buffer 作为参数。示例如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">newData</span> <span class="operator">=</span> <span class="string">"New String to write to file..."</span> + System.currentTimeMillis();</span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line">buf.clear();</span><br><span class="line">buf.put(newData.getBytes());</span><br><span class="line">buf.flip();</span><br><span class="line"><span class="keyword">while</span>(buf.hasRemaining()) {</span><br><span class="line">    channel.write(buf);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>注意<code>SocketChannel.write()</code>方法的调用是在一个 while 循环中的。<code>write()</code>方法无法保证能写多少字节到 SocketChannel。所以，我们重复调用<code>write()</code>直到 Buffer 没有要写的字节为止。</p><h3 id="非阻塞模式"><a href="#非阻塞模式" class="headerlink" title="非阻塞模式"></a>非阻塞模式</h3><p>可以设置 SocketChannel 为非阻塞模式（non-blocking mode）.设置之后，就可以在异步模式下调用<code>connect()</code>,<code>read()</code> 和<code>write()</code>了。</p><h4 id="connect"><a href="#connect" class="headerlink" title="connect()"></a>connect()</h4><p>如果 SocketChannel 在非阻塞模式下，此时调用<code>connect()</code>，该方法可能在连接建立之前就返回了。为了确定连接是否建立，可以调用<code>finishConnect()</code>的方法。像这样：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">socketChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">socketChannel.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">"http://jenkov.com"</span>, <span class="number">80</span>));</span><br><span class="line"><span class="keyword">while</span>(! socketChannel.finishConnect() ){</span><br><span class="line">    <span class="comment">//wait, or do something else...    </span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="write"><a href="#write" class="headerlink" title="write()"></a>write()</h4><p>非阻塞模式下，<code>write()</code>方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用<code>write()</code>。前面已经有例子了，这里就不赘述了。</p><h4 id="read"><a href="#read" class="headerlink" title="read()"></a>read()</h4><p>非阻塞模式下,<code>read()</code> 方法在尚未读取到任何数据时可能就返回了。所以需要关注它的 int 返回值，它会告诉你读取了多少字节。</p><h4 id="非阻塞模式与选择器"><a href="#非阻塞模式与选择器" class="headerlink" title="非阻塞模式与选择器"></a>非阻塞模式与选择器</h4><p>非阻塞模式与选择器搭配会工作的更好，通过将一或多个 SocketChannel 注册到 Selector，可以询问选择器哪个通道已经准备好了读取，写入等。Selector 与 SocketChannel 的搭配使用会在后面详讲。</p><hr><h2 id="九、ServerSocketChannel"><a href="#九、ServerSocketChannel" class="headerlink" title="九、ServerSocketChannel"></a>九、ServerSocketChannel</h2><p>Java NIO 中的 ServerSocketChannel 是一个可以监听新进来的 TCP 连接的通道, 就像标准 IO 中的 ServerSocket 一样。ServerSocketChannel 类在<code>java.nio.channels</code>包中。 这里有个例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ServerSocketChannel</span> <span class="variable">serverSocketChannel</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">serverSocketChannel.socket().bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">9999</span>));</span><br><span class="line"><span class="keyword">while</span>(<span class="literal">true</span>){</span><br><span class="line">    <span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span></span><br><span class="line">            serverSocketChannel.accept();</span><br><span class="line">    <span class="comment">//do something with socketChannel...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="打开-ServerSocketChannel"><a href="#打开-ServerSocketChannel" class="headerlink" title="打开 ServerSocketChannel"></a>打开 ServerSocketChannel</h3><p>通过调用 <code>ServerSocketChannel.open()</code>方法来打开 ServerSocketChannel. 如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ServerSocketChannel</span> <span class="variable">serverSocketChannel</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br></pre></td></tr></tbody></table></figure><h3 id="关闭-ServerSocketChannel"><a href="#关闭-ServerSocketChannel" class="headerlink" title="关闭 ServerSocketChannel"></a>关闭 ServerSocketChannel</h3><p>通过调用<code>ServerSocketChannel.close()</code>方法来关闭 ServerSocketChannel. 如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">serverSocketChannel.close();</span><br></pre></td></tr></tbody></table></figure><h3 id="监听新进来的连接"><a href="#监听新进来的连接" class="headerlink" title="监听新进来的连接"></a>监听新进来的连接</h3><p>通过 <code>ServerSocketChannel.accept()</code>方法监听新进来的连接。当 <code>accept()</code>方法返回的时候, 它返回一个包含新进来的连接的 SocketChannel。因此, <code>accept()</code>方法会一直阻塞到有新连接到达。 通常不会仅仅只监听一个连接, 在 while 循环中调用 <code>accept()</code>方法. 如下面的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(<span class="literal">true</span>){</span><br><span class="line">    <span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span></span><br><span class="line">            serverSocketChannel.accept();</span><br><span class="line">    <span class="comment">//do something with socketChannel...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>当然, 也可以在 while 循环中使用除了 true 以外的其它退出准则。</p><h3 id="非阻塞模式-1"><a href="#非阻塞模式-1" class="headerlink" title="非阻塞模式"></a>非阻塞模式</h3><p>ServerSocketChannel 可以设置成非阻塞模式。在非阻塞模式下，<code>accept()</code>方法会立刻返回，如果还没有新进来的连接, 返回的将是 null。 因此，需要检查返回的 SocketChannel 是否是 null. 如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ServerSocketChannel</span> <span class="variable">serverSocketChannel</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">serverSocketChannel.socket().bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">9999</span>));</span><br><span class="line">serverSocketChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line"><span class="keyword">while</span>(<span class="literal">true</span>){</span><br><span class="line">    <span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span></span><br><span class="line">            serverSocketChannel.accept();</span><br><span class="line">    <span class="keyword">if</span>(socketChannel != <span class="literal">null</span>){</span><br><span class="line">        <span class="comment">//do something with socketChannel...</span></span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><hr><h2 id="十、DatagramChannel"><a href="#十、DatagramChannel" class="headerlink" title="十、DatagramChannel"></a>十、DatagramChannel</h2><p>Java NIO 中的 DatagramChannel 是一个能收发 UDP 包的通道。因为 UDP 是无连接的网络协议，所以不能像其它通道那样读取和写入。它发送和接收的是数据包。</p><h3 id="打开-DatagramChannel"><a href="#打开-DatagramChannel" class="headerlink" title="打开 DatagramChannel"></a>打开 DatagramChannel</h3><p>下面是 DatagramChannel 的打开方式：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">DatagramChannel</span> <span class="variable">channel</span> <span class="operator">=</span> DatagramChannel.open();</span><br><span class="line">channel.socket().bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">9999</span>));</span><br></pre></td></tr></tbody></table></figure><p>这个例子打开的 DatagramChannel 可以在 UDP 端口 9999 上接收数据包。</p><h3 id="接收数据"><a href="#接收数据" class="headerlink" title="接收数据"></a>接收数据</h3><p>通过<code>receive()</code>方法从 DatagramChannel 接收数据，如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line">buf.clear();</span><br><span class="line"></span><br><span class="line">channel.receive(buf);</span><br></pre></td></tr></tbody></table></figure><p><code>receive()</code>方法会将接收到的数据包内容复制到指定的 Buffer. 如果 Buffer 容不下收到的数据，多出的数据将被丢弃。</p><h3 id="发送数据"><a href="#发送数据" class="headerlink" title="发送数据"></a>发送数据</h3><p>通过<code>send()</code>方法从 DatagramChannel 发送数据，如:</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">newData</span> <span class="operator">=</span> <span class="string">"New String to write to file..."</span></span><br><span class="line">                    + System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line">buf.clear();</span><br><span class="line">buf.put(newData.getBytes());</span><br><span class="line">buf.flip();</span><br><span class="line"><span class="type">int</span> <span class="variable">bytesSent</span> <span class="operator">=</span> channel.send(buf, <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">"jenkov.com"</span>, <span class="number">80</span>));</span><br></pre></td></tr></tbody></table></figure><p>这个例子发送一串字符到” jenkov.com” 服务器的 UDP 端口 80。 因为服务端并没有监控这个端口，所以什么也不会发生。也不会通知你发出的数据包是否已收到，因为 UDP 在数据传送方面没有任何保证。</p><h3 id="连接到特定的地址"><a href="#连接到特定的地址" class="headerlink" title="连接到特定的地址"></a>连接到特定的地址</h3><p>可以将 DatagramChannel “连接” 到网络中的特定地址的。由于 UDP 是无连接的，连接到特定地址并不会像 TCP 通道那样创建一个真正的连接。而是锁住 DatagramChannel ，让其只能从特定地址收发数据。 这里有个例子:</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">channel.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">"jenkov.com"</span>, <span class="number">80</span>));</span><br></pre></td></tr></tbody></table></figure><p>当连接后，也可以使用<code>read()</code>和<code>write()</code>方法，就像在用传统的通道一样。只是在数据传送方面没有任何保证。这里有几个例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">bytesRead</span> <span class="operator">=</span> channel.read(buf);</span><br><span class="line"><span class="type">int</span> <span class="variable">bytesWritten</span> <span class="operator">=</span> channel.write(but);</span><br></pre></td></tr></tbody></table></figure><hr><h2 id="十一、Pipe"><a href="#十一、Pipe" class="headerlink" title="十一、Pipe"></a>十一、Pipe</h2><p>Java NIO 管道是 2 个线程之间的单向数据连接。Pipe 有一个 source 通道和一个 sink 通道。数据会被写到 sink 通道，从 source 通道读取。 这里是 Pipe 原理的图示：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139199003188.jpg"></p><h3 id="创建管道"><a href="#创建管道" class="headerlink" title="创建管道"></a>创建管道</h3><p>通过<code>Pipe.open()</code>方法打开管道。例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Pipe</span> <span class="variable">pipe</span> <span class="operator">=</span> Pipe.open();</span><br></pre></td></tr></tbody></table></figure><h3 id="向管道写数据"><a href="#向管道写数据" class="headerlink" title="向管道写数据"></a>向管道写数据</h3><p>要向管道写数据，需要访问 sink 通道。像这样：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Pipe.<span class="type">SinkChannel</span> <span class="variable">sinkChannel</span> <span class="operator">=</span> pipe.sink();</span><br></pre></td></tr></tbody></table></figure><p>通过调用 SinkChannel 的<code>write()</code>方法，将数据写入 SinkChannel, 像这样：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">newData</span> <span class="operator">=</span> <span class="string">"New String to write to file..."</span> + System.currentTimeMillis();</span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line">buf.clear();</span><br><span class="line">buf.put(newData.getBytes());</span><br><span class="line">buf.flip();</span><br><span class="line"><span class="keyword">while</span>(buf.hasRemaining()) {</span><br><span class="line">    sinkChannel.write(buf);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="从管道读取数据"><a href="#从管道读取数据" class="headerlink" title="从管道读取数据"></a>从管道读取数据</h3><p>从读取管道的数据，需要访问 source 通道，像这样：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Pipe.<span class="type">SourceChannel</span> <span class="variable">sourceChannel</span> <span class="operator">=</span> pipe.source();</span><br></pre></td></tr></tbody></table></figure><p>调用 source 通道的<code>read()</code>方法来读取数据，像这样：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">bytesRead</span> <span class="operator">=</span> sourceChannel.read(buf);</span><br></pre></td></tr></tbody></table></figure><p><code>read()</code>方法返回的 int 值会告诉我们多少字节被读进了缓冲区。</p><hr><h2 id="十二、NIO与IO"><a href="#十二、NIO与IO" class="headerlink" title="十二、NIO与IO"></a>十二、NIO 与 IO</h2><p>当学习了 Java NIO 和 IO 的 API 后，一个问题马上涌入脑海： 我应该何时使用 IO，何时使用 NIO 呢？在本文中，我会尽量清晰地解析 Java NIO 和 IO 的差异、它们的使用场景，以及它们如何影响您的代码设计。</p><h3 id="Java-NIO和IO的主要区别"><a href="#Java-NIO和IO的主要区别" class="headerlink" title="Java NIO和IO的主要区别"></a>Java NIO 和 IO 的主要区别</h3><p>下表总结了 Java NIO 和 IO 之间的主要差别，我会更详细地描述表中每部分的差异。<br>IO                NIO<br>面向流            面向缓冲<br>阻塞 IO            非阻塞 IO<br>无                选择器</p><h3 id="面向流与面向缓冲"><a href="#面向流与面向缓冲" class="headerlink" title="面向流与面向缓冲"></a>面向流与面向缓冲</h3><p>Java NIO 和 IO 之间第一个最大的区别是，IO 是面向流的，NIO 是面向缓冲区的。 Java IO 面向流意味着每次从流中读一个或多个字节，直至读取所有字节，它们没有被缓存在任何地方。此外，它不能前后移动流中的数据。如果需要前后移动从流中读取的数据，需要先将它缓存到一个缓冲区。 Java NIO 的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是，还需要检查是否该缓冲区中包含所有您需要处理的数据。而且，需确保当更多的数据读入缓冲区时，不要覆盖缓冲区里尚未处理的数据。</p><h3 id="阻塞与非阻塞IO"><a href="#阻塞与非阻塞IO" class="headerlink" title="阻塞与非阻塞IO"></a>阻塞与非阻塞 IO</h3><p>Java IO 的各种流是阻塞的。这意味着，当一个线程调用<code>read()</code>或 <code>write()</code>时，该线程被阻塞，直到有一些数据被读取，或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO 的非阻塞模式，使一个线程从某通道发送请求读取数据，但是它仅能得到目前可用的数据，如果目前没有数据可用时，就什么都不会获取。而不是保持线程阻塞，所以直至数据变的可以读取之前，该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道，但不需要等待它完全写入，这个线程同时可以去做别的事情。 线程通常将非阻塞 IO 的空闲时间用于在其它通道上执行 IO 操作，所以一个单独的线程现在可以管理多个输入和输出通道（channel）。</p><h3 id="选择器（Selectors）"><a href="#选择器（Selectors）" class="headerlink" title="选择器（Selectors）"></a>选择器（Selectors）</h3><p>Java NIO 的选择器允许一个单独的线程来监视多个输入通道，你可以注册多个通道使用一个选择器，然后使用一个单独的线程来 “选择” 通道：这些通道里已经有可以处理的输入，或者选择已准备写入的通道。这种选择机制，使得一个单独的线程很容易来管理多个通道。</p><h3 id="NIO和IO如何影响应用程序的设计"><a href="#NIO和IO如何影响应用程序的设计" class="headerlink" title="NIO和IO如何影响应用程序的设计"></a>NIO 和 IO 如何影响应用程序的设计</h3><p>无论您选择 IO 或 NIO 工具箱，可能会影响您应用程序设计的以下几个方面：</p><ol><li>对 NIO 或 IO 类的 API 调用。</li><li>数据处理。</li><li>用来处理数据的线程数。</li></ol><h4 id="API调用"><a href="#API调用" class="headerlink" title="API调用"></a>API 调用</h4><p>当然，使用 NIO 的 API 调用时看起来与使用 IO 时有所不同，但这并不意外，因为并不是仅从一个 InputStream 逐字节读取，而是数据必须先读入缓冲区再处理。</p><h4 id="数据处理"><a href="#数据处理" class="headerlink" title="数据处理"></a>数据处理</h4><p>使用纯粹的 NIO 设计相较 IO 设计，数据处理也受到影响。 在 IO 设计中，我们从 InputStream 或 Reader 逐字节读取数据。假设你正在处理一基于行的文本数据流，例如：</p><figure class="highlight dart"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Name</span>: <span class="title class_">Anna</span></span><br><span class="line"><span class="title class_">Age</span>: <span class="number">25</span></span><br><span class="line"><span class="title class_">Email</span>: anna<span class="meta">@mailserver</span>.com</span><br><span class="line"><span class="title class_">Phone</span>: <span class="number">1234567890</span></span><br></pre></td></tr></tbody></table></figure><p>该文本行的流可以这样处理：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">InputStream</span> <span class="variable">input</span> <span class="operator">=</span> ... ; <span class="comment">// get the InputStream from the client socket</span></span><br><span class="line"><span class="type">BufferedReader</span> <span class="variable">reader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedReader</span>(<span class="keyword">new</span> <span class="title class_">InputStreamReader</span>(input));</span><br><span class="line"><span class="type">String</span> <span class="variable">nameLine</span> <span class="operator">=</span> reader.readLine();</span><br><span class="line"><span class="type">String</span> <span class="variable">ageLine</span> <span class="operator">=</span> reader.readLine();</span><br><span class="line"><span class="type">String</span> <span class="variable">emailLine</span> <span class="operator">=</span> reader.readLine();</span><br><span class="line"><span class="type">String</span> <span class="variable">phoneLine</span> <span class="operator">=</span> reader.readLine();</span><br></pre></td></tr></tbody></table></figure><p>请注意处理状态由程序执行多久决定。换句话说，一旦<code>reader.readLine()</code>方法返回，你就知道肯定文本行就已读完， readline () 阻塞直到整行读完，这就是原因。你也知道此行包含名称；同样，第二个<code>readline()</code>调用返回的时候，你知道这行包含年龄等。 正如你可以看到，该处理程序仅在有新数据读入时运行，并知道每步的数据是什么。一旦正在运行的线程已处理过读入的某些数据，该线程不会再回退数据（大多如此）。下图也说明了这条原则：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139202494608.jpg"></p><p><strong>Java IO: 从一个阻塞的流中读数据</strong></p><p>而一个 NIO 的实现会有所不同，下面是一个简单的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">bytesRead</span> <span class="operator">=</span> inChannel.read(buffer);</span><br></pre></td></tr></tbody></table></figure><p>注意第二行，从通道读取字节到 ByteBuffer。当这个方法调用返回时，你不知道你所需的所有数据是否在缓冲区内。你所知道的是，该缓冲区包含一些字节，这使得处理有点困难。 假设第一次<code>read(buffer)</code>调用后，读入缓冲区的数据只有半行，例如，“Name:An”，你能处理数据吗？显然不能，需要等待，直到整行数据读入缓存，在此之前，对数据的任何处理毫无意义。 所以，你怎么知道是否该缓冲区包含足够的数据可以处理呢？好了，你不知道。发现的方法只能查看缓冲区中的数据。其结果是，在你知道所有数据都在缓冲区里之前，你必须检查几次缓冲区的数据。这不仅效率低下，而且可以使程序设计方案杂乱不堪。例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">48</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">bytesRead</span> <span class="operator">=</span> inChannel.read(buffer);</span><br><span class="line"><span class="keyword">while</span>(! bufferFull(bytesRead) ) {</span><br><span class="line">    bytesRead = inChannel.read(buffer);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><code>bufferFull()</code>方法必须跟踪有多少数据读入缓冲区，并返回真或假，这取决于缓冲区是否已满。换句话说，如果缓冲区准备好被处理，那么表示缓冲区满了。 <code>bufferFull()</code>方法扫描缓冲区，但必须保持在<code>bufferFull()</code>方法被调用之前状态相同。如果没有，下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的，但却是需要注意的又一问题。 如果缓冲区已满，它可以被处理。如果它不满，并且在你的实际案例中有意义，你或许能处理其中的部分数据。但是许多情况下并非如此。 下图展示了 “缓冲区数据循环就绪”：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139202585492.jpg"></p><p><strong>Java NIO: 从一个通道里读数据，直到所有的数据都读到缓冲区里</strong></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>NIO 可让您只使用一个（或几个）单线程管理多个通道（网络连接或文件），但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。 如果需要管理同时打开的成千上万个连接，这些连接每次只是发送少量的数据，例如聊天服务器，实现 NIO 的服务器可能是一个优势。同样，如果你需要维持许多打开的连接到其他计算机上，如 P2P 网络中，使用一个单独的线程来管理你所有出站连接，可能是一个优势。一个线程多个连接的设计方案如下图所示：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139202757722.jpg"></p><p><strong>Java NIO: 单线程管理多个连接</strong></p><p>如果你有少量的连接使用非常高的带宽，一次发送大量的数据，也许典型的 IO 服务器实现可能非常契合。下图说明了一个典型的 IO 服务器设计：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139202809730.jpg"></p><p><strong>Java IO: 一个典型的 IO 服务器设计 - 一个连接通过一个线程处理</strong></p><blockquote><p>英文原文地址：<span class="exturl" data-url="aHR0cDovL3R1dG9yaWFscy5qZW5rb3YuY29tL2phdmEtbmlvL2luZGV4Lmh0bWw=">http://tutorials.jenkov.com/java-nio/index.html<i class="fa fa-external-link-alt"></i></span><br>中文来源地址：<span class="exturl" data-url="aHR0cDovL2lmZXZlLmNvbS9qYXZhLW5pby1hbGwv">http://ifeve.com/java-nio-all/<i class="fa fa-external-link-alt"></i></span><br>作者：Jakob Jenkov<br>译者：郭蕾 校对：方腾飞</p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/48365.html</id>
    <link href="https://www.bytelife.net/articles/48365.html"/>
    <published>2017-03-14T17:34:53.000Z</published>
    <summary>
      <![CDATA[<p>Java NIO (New IO) 是一个可以替代标准 Java IO API 的 IO API（从 Java 1.4 开始)，Java NIO 提供了与标准 IO 不同的 IO 工作方式。</p>
<ul>
<li><strong>Java NIO: Channels and Buffers（通道和缓冲区）</strong>：标准的 IO 基于字节流和字符流进行操作的，而 NIO 是基于通道（Channel）和缓冲区（Buffer）进行操作，数据总是从通道读取到缓冲区中，或者从缓冲区写入到通道中。</li>
<li><strong>Java NIO: Non-blocking IO（非阻塞 IO）</strong>：Java NIO 可以让你非阻塞的使用 IO，例如：当线程从通道读取数据到缓冲区时，线程还是可以进行其他事情。当数据被写入到缓冲区时，线程可以继续处理它。从缓冲区写入通道也类似。</li>
<li><strong>Java NIO: Selectors（选择器）</strong>：Java NIO 引入了选择器的概念，选择器用于监听多个通道的事件（比如：连接打开，数据到达）。因此，单个的线程可以监听多个数据通道。</li>
</ul>]]>
    </summary>
    <title>Java NIO 教程详解</title>
    <updated>2017-03-14T17:34:53.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="并发" scheme="https://www.bytelife.net/tags/%E5%B9%B6%E5%8F%91/"/>
    <content>
      <![CDATA[<p>Copy-On-Write 简称 COW，是一种用于程序设计中的优化策略。其基本思路是，从一开始大家都在共享同一个内容，当某个人想要修改这个内容的时候，才会真正把内容 Copy 出去形成一个新的内容然后再改，这是一种延时懒惰策略。从 JDK1.5 开始 Java 并发包里提供了两个使用 CopyOnWrite 机制实现的并发容器, 它们是 CopyOnWriteArrayList 和 CopyOnWriteArraySet。CopyOnWrite 容器非常有用，可以在非常多的并发场景中使用到。</p><span id="more"></span><h2 id="什么是CopyOnWrite容器"><a href="#什么是CopyOnWrite容器" class="headerlink" title="什么是CopyOnWrite容器"></a>什么是 CopyOnWrite 容器</h2><p>CopyOnWrite 容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候，不直接往当前容器添加，而是先将当前容器进行 Copy，复制出一个新的容器，然后新的容器里添加元素，添加完元素之后，再将原容器的引用指向新的容器。这样做的好处是我们可以对 CopyOnWrite 容器进行并发的读，而不需要加锁，因为当前容器不会添加任何元素。所以 CopyOnWrite 容器也是一种读写分离的思想，读和写不同的容器。</p><h2 id="CopyOnWriteArrayList的实现原理"><a href="#CopyOnWriteArrayList的实现原理" class="headerlink" title="CopyOnWriteArrayList的实现原理"></a>CopyOnWriteArrayList 的实现原理</h2><p>在使用 CopyOnWriteArrayList 之前，我们先阅读其源码了解下它是如何实现的。以下代码是向 ArrayList 里添加元素，可以发现在添加的时候是需要加锁的，否则多线程写的时候会 Copy 出 N 个副本出来。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(T e)</span> {</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">    lock.lock();</span><br><span class="line">    <span class="keyword">try</span> {</span><br><span class="line">        <span class="type">Object</span>[] <span class="variable">elements</span> <span class="operator">=</span> getArray();</span><br><span class="line">        <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> elements.length;</span><br><span class="line">        <span class="comment">// 复制出新数组</span></span><br><span class="line">        <span class="type">Object</span>[] <span class="variable">newElements</span> <span class="operator">=</span> Arrays.copyOf(elements, len + <span class="number">1</span>);</span><br><span class="line">        <span class="comment">// 把新元素添加到新数组里</span></span><br><span class="line">        newElements[len] = e;</span><br><span class="line">        <span class="comment">// 把原数组引用指向新数组</span></span><br><span class="line">        setArray(newElements);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    } <span class="keyword">finally</span> {</span><br><span class="line">        lock.unlock();</span><br><span class="line">    }</span><br><span class="line">}  </span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="type">void</span> <span class="title function_">setArray</span><span class="params">(Object[] a)</span> {</span><br><span class="line">    array = a;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>读的时候不需要加锁，如果读的时候有多个线程正在向 ArrayList 添加数据，读还是会读到旧的数据，因为写的时候不会锁住旧的 ArrayList。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">E</span> <span class="title function_">get</span><span class="params">(<span class="type">int</span> index)</span> {</span><br><span class="line">    <span class="keyword">return</span> get(getArray(), index);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>JDK 中并没有提供 CopyOnWriteMap，我们可以参考 CopyOnWriteArrayList 来实现一个，基本代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Collection;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="keyword">import</span> java.util.Set;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CopyOnWriteMap</span>&lt;K, V&gt; <span class="keyword">implements</span> <span class="title class_">Map</span>&lt;K, V&gt;, Cloneable {</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> Map&lt;K, V&gt; internalMap;</span><br><span class="line"></span><br><span class="line">    <span class="type">public</span> <span class="title function_">CopyOnWriteMap</span><span class="params">()</span> {</span><br><span class="line">        internalMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;K, V&gt;();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">V</span> <span class="title function_">put</span><span class="params">(K key, V value)</span> {</span><br><span class="line"></span><br><span class="line">        <span class="keyword">synchronized</span> (<span class="built_in">this</span>) {</span><br><span class="line">            <span class="type">Map</span>&lt;K, V&gt; <span class="variable">newMap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;K, V&gt;(internalMap);</span><br><span class="line">            <span class="type">V</span> <span class="variable">val</span> <span class="operator">=</span> newMap.put(key, value);</span><br><span class="line">            internalMap = newMap;</span><br><span class="line">            <span class="keyword">return</span> val;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">V</span> <span class="title function_">get</span><span class="params">(Object key)</span> {</span><br><span class="line">        <span class="keyword">return</span> internalMap.get(key);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">putAll</span><span class="params">(Map&lt;? extends K, ? extends V&gt; newData)</span> {</span><br><span class="line">        <span class="keyword">synchronized</span> (<span class="built_in">this</span>) {</span><br><span class="line">            <span class="type">Map</span>&lt;K, V&gt; <span class="variable">newMap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;K, V&gt;(internalMap);</span><br><span class="line">            newMap.putAll(newData);</span><br><span class="line">            internalMap = newMap;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>实现很简单，只要了解了 CopyOnWrite 机制，我们可以实现各种 CopyOnWrite 容器，并且在不同的应用场景中使用。</p><h2 id="CopyOnWrite的应用场景"><a href="#CopyOnWrite的应用场景" class="headerlink" title="CopyOnWrite的应用场景"></a>CopyOnWrite 的应用场景</h2><p>CopyOnWrite 并发容器用于读多写少的并发场景。比如白名单，黑名单，商品类目的访问和更新场景，假如我们有一个搜索网站，用户在这个网站的搜索框中，输入关键字搜索内容，但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中，黑名单每天晚上更新一次。当用户搜索时，会检查当前关键字在不在黑名单当中，如果在，则提示不能搜索。实现代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.ifeve.book;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.ifeve.book.forkjoin.CopyOnWriteMap;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 黑名单服务</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> fangtengfei</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BlackListServiceImpl</span> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">CopyOnWriteMap</span>&lt;String, Boolean&gt; <span class="variable">blackListMap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CopyOnWriteMap</span>&lt;String, Boolean&gt;(</span><br><span class="line">            <span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">isBlackList</span><span class="params">(String id)</span> {</span><br><span class="line">        <span class="keyword">return</span> blackListMap.get(id) == <span class="literal">null</span> ? <span class="literal">false</span> : <span class="literal">true</span>;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">addBlackList</span><span class="params">(String id)</span> {</span><br><span class="line">        blackListMap.put(id, Boolean.TRUE);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 批量添加黑名单</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> ids</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">addBlackList</span><span class="params">(Map&lt;String,Boolean&gt; ids)</span> {</span><br><span class="line">        blackListMap.putAll(ids);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>代码很简单，但是使用 CopyOnWriteMap 需要注意两件事情：</p><ol><li>减少扩容开销。根据实际需要，初始化 CopyOnWriteMap 的大小，避免写时 CopyOnWriteMap 扩容的开销。</li><li>使用批量添加。因为每次添加，容器每次都会进行复制，所以减少添加次数，可以减少容器的复制次数。如使用上面代码里的 addBlackList 方法。</li></ol><h2 id="CopyOnWrite的缺点"><a href="#CopyOnWrite的缺点" class="headerlink" title="CopyOnWrite的缺点"></a>CopyOnWrite 的缺点</h2><p>CopyOnWrite 容器有很多优点，但是同时也存在两个问题，即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。</p><ul><li><p><strong>内存占用问题</strong>：因为 CopyOnWrite 的写时复制机制，所以在进行写操作的时候，内存里会同时驻扎两个对象的内存，旧的对象和新写入的对象（注意: 在复制的时候只是复制容器里的引用，只是在写的时候会创建新对象添加到新容器里，而旧容器的对象还在使用，所以有两份对象内存）。如果这些对象占用的内存比较大，比如说 200M 左右，那么再写入 100M 数据进去，内存就会占用 300M，那么这个时候很有可能造成频繁的 Yong GC 和 Full GC。之前我们系统中使用了一个服务由于每晚使用 CopyOnWrite 机制更新大对象，造成了每晚 15 秒的 Full GC，应用响应时间也随之变长。</p><blockquote><p>针对内存占用问题，可以通过压缩容器中的元素的方法来减少大对象的内存消耗，比如，如果元素全是 10 进制的数字，可以考虑把它压缩成 36 进制或 64 进制。或者不使用 CopyOnWrite 容器，而使用其他的并发容器，如 ConcurrentHashMap。</p></blockquote></li><li><p><strong>数据一致性问题</strong>：CopyOnWrite 容器只能保证数据的最终一致性，不能保证数据的实时一致性。所以如果你希望写入的的数据，马上能读到，请不要使用 CopyOnWrite 容器。</p></li></ul><p>关于 C++ 的 STL 中，曾经也有过 Copy-On-Write 的玩法，参见陈皓的《C++ STL String 类中的 Copy-On-Write》，后来，因为有很多线程安全上的事，就被去掉了。</p><blockquote><p>转载自<span class="exturl" data-url="aHR0cDovL2lmZXZlLmNvbS8=">并发编程网 – ifeve.com<i class="fa fa-external-link-alt"></i></span> 本文链接地址: <span class="exturl" data-url="aHR0cDovL2lmZXZlLmNvbS9qYXZhLWNvcHktb24td3JpdGUv">聊聊并发 - Java 中的 Copy-On-Write 容器<i class="fa fa-external-link-alt"></i></span></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/46009.html</id>
    <link href="https://www.bytelife.net/articles/46009.html"/>
    <published>2017-03-14T17:16:23.000Z</published>
    <summary>
      <![CDATA[<p>Copy-On-Write 简称 COW，是一种用于程序设计中的优化策略。其基本思路是，从一开始大家都在共享同一个内容，当某个人想要修改这个内容的时候，才会真正把内容 Copy 出去形成一个新的内容然后再改，这是一种延时懒惰策略。从 JDK1.5 开始 Java 并发包里提供了两个使用 CopyOnWrite 机制实现的并发容器, 它们是 CopyOnWriteArrayList 和 CopyOnWriteArraySet。CopyOnWrite 容器非常有用，可以在非常多的并发场景中使用到。</p>]]>
    </summary>
    <title>Java 中的 Copy-On-Write 容器</title>
    <updated>2017-03-14T17:16:23.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="并发" scheme="https://www.bytelife.net/tags/%E5%B9%B6%E5%8F%91/"/>
    <content>
      <![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在并发编程中我们有时候需要使用线程安全的队列。如果我们要实现一个线程安全的队列有两种实现方式：一种是使用阻塞算法，另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁（入队和出队用同一把锁）或两个锁（入队和出队用不同的锁）等方式来实现，而非阻塞的实现方式则可以使用循环 CAS 的方式来实现，本文让我们一起来研究下 Doug Lea 是如何使用非阻塞的方式来实现线程安全队列 ConcurrentLinkedQueue 的，相信从大师身上我们能学到不少并发编程的技巧。</p><span id="more"></span><h2 id="ConcurrentLinkedQueue的介绍"><a href="#ConcurrentLinkedQueue的介绍" class="headerlink" title="ConcurrentLinkedQueue的介绍"></a>ConcurrentLinkedQueue 的介绍</h2><p>ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全队列，它采用先进先出的规则对节点进行排序，当我们添加一个元素的时候，它会添加到队列的尾部，当我们获取一个元素时，它会返回队列头部的元素。它采用了 “wait－free” 算法来实现，该算法在 Michael &amp; Scott 算法上进行了一些修改, Michael &amp; Scott 算法的详细信息可以参见参考资料一。</p><h2 id="ConcurrentLinkedQueue的结构"><a href="#ConcurrentLinkedQueue的结构" class="headerlink" title="ConcurrentLinkedQueue的结构"></a>ConcurrentLinkedQueue 的结构</h2><p>我们通过 ConcurrentLinkedQueue 的类图来分析一下它的结构。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139213452864.jpg"><br> （图 1） ConcurrentLinkedQueue 由 head 节点和 tair 节点组成，每个节点（Node）由节点元素（item）和指向下一个节点的引用 (next) 组成，节点与节点之间就是通过这个 next 关联起来，从而组成一张链表结构的队列。默认情况下 head 节点存储的元素为空，tair 节点等于 head 节点。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> <span class="type">Node</span>&lt;E&gt; <span class="variable">tail</span> <span class="operator">=</span> head;</span><br></pre></td></tr></tbody></table></figure><h2 id="入队列"><a href="#入队列" class="headerlink" title="入队列"></a>入队列</h2><p>入队列就是将入队节点添加到队列的尾部。为了方便理解入队时队列的变化，以及 head 节点和 tair 节点的变化，每添加一个节点我就做了一个队列的快照图。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139213588471.jpg"><br> （图二）</p><ul><li>第一步添加元素 1。队列更新 head 节点的 next 节点为元素 1 节点。又因为 tail 节点默认情况下等于 head 节点，所以它们的 next 节点都指向元素 1 节点。</li><li>第二步添加元素 2。队列首先设置元素 1 节点的 next 节点为元素 2 节点，然后更新 tail 节点指向元素 2 节点。</li><li>第三步添加元素 3，设置 tail 节点的 next 节点为元素 3 节点。</li><li>第四步添加元素 4，设置元素 3 的 next 节点为元素 4 节点，然后将 tail 节点指向元素 4 节点。</li></ul><p>通过 debug 入队过程并观察 head 节点和 tail 节点的变化，发现入队主要做两件事情，第一是将入队节点设置成当前队列尾节点的下一个节点。第二是更新 tail 节点，如果 tail 节点的 next 节点不为空，则将入队节点设置成 tail 节点，如果 tail 节点的 next 节点为空，则将入队节点设置成 tail 的 next 节点，所以 tail 节点不总是尾节点，理解这一点对于我们研究源码会非常有帮助。 上面的分析让我们从单线程入队的角度来理解入队过程，但是多个线程同时进行入队情况就变得更加复杂，因为可能会出现其他线程插队的情况。如果有一个线程正在入队，那么它必须先获取尾节点，然后设置尾节点的下一个节点为入队节点，但这时可能有另外一个线程插队了，那么队列的尾节点就会发生变化，这时当前线程要暂停入队操作，然后重新获取尾节点。让我们再通过源码来详细分析下它是如何使用 CAS 算法来入队的。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">offer</span><span class="params">(E e)</span> {</span><br><span class="line">    <span class="keyword">if</span> (e == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="comment">//入队前，创建一个入队节点</span></span><br><span class="line">    <span class="type">Node</span>&lt;E&gt; <span class="variable">n</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Node</span>&lt;E&gt;(e);</span><br><span class="line">    retry:</span><br><span class="line">    <span class="comment">//死循环，入队不成功反复入队。</span></span><br><span class="line">    <span class="keyword">for</span> (;;) {</span><br><span class="line">        <span class="comment">//创建一个指向tail节点的引用</span></span><br><span class="line">        <span class="type">Node</span>&lt;E&gt; <span class="variable">t</span> <span class="operator">=</span> tail;</span><br><span class="line">        <span class="comment">//p用来表示队列的尾节点，默认情况下等于tail节点。</span></span><br><span class="line">        <span class="type">Node</span>&lt;E&gt; <span class="variable">p</span> <span class="operator">=</span> t;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">hops</span> <span class="operator">=</span> <span class="number">0</span>; ; hops++) {</span><br><span class="line">        <span class="comment">//获得p节点的下一个节点。</span></span><br><span class="line">            <span class="type">Node</span>&lt;E&gt; <span class="variable">next</span> <span class="operator">=</span> succ(p);</span><br><span class="line">        <span class="comment">//next节点不为空，说明p不是尾节点，需要更新p后在将它指向next节点</span></span><br><span class="line">            <span class="keyword">if</span> (next != <span class="literal">null</span>) {</span><br><span class="line">               <span class="comment">//循环了两次及其以上，并且当前节点还是不等于尾节点</span></span><br><span class="line">                <span class="keyword">if</span> (hops &gt; HOPS &amp;&amp; t != tail)</span><br><span class="line">                    <span class="keyword">continue</span> retry; </span><br><span class="line">                p = next;</span><br><span class="line">            } </span><br><span class="line">            <span class="comment">//如果p是尾节点，则设置p节点的next节点为入队节点。</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (p.casNext(<span class="literal">null</span>, n)) {</span><br><span class="line">              <span class="comment">//如果tail节点有大于等于1个next节点，则将入队节点设置成tair节点，更新失败了也</span></span><br><span class="line">没关系，因为失败了表示有其他线程成功更新了tair节点。</span><br><span class="line">                <span class="keyword">if</span> (hops &gt;= HOPS)</span><br><span class="line">                    casTail(t, n); <span class="comment">// 更新tail节点，允许失败</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;  </span><br><span class="line">            } </span><br><span class="line">           <span class="comment">// p有next节点,表示p的next节点是尾节点，则重新设置p节点</span></span><br><span class="line">            <span class="keyword">else</span> {</span><br><span class="line">                p = succ(p);</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><strong>从源代码角度来看整个入队过程主要做二件事情</strong>。第一是定位出尾节点，第二是使用 CAS 算法能将入队节点设置成尾节点的 next 节点，如不成功则重试。 <strong>第一步定位尾节点</strong>。tail 节点并不总是尾节点，所以每次入队都必须先通过 tail 节点来找到尾节点，尾节点可能就是 tail 节点，也可能是 tail 节点的 next 节点。代码中循环体中的第一个 if 就是判断 tail 是否有 next 节点，有则表示 next 节点可能是尾节点。获取 tail 节点的 next 节点需要注意的是 p 节点等于 p 的 next 节点的情况，只有一种可能就是 p 节点和 p 的 next 节点都等于空，表示这个队列刚初始化，正准备添加第一次节点，所以需要返回 head 节点。获取 p 节点的 next 节点代码如下</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">Node</span>&lt;E&gt; <span class="title function_">succ</span><span class="params">(Node&lt;E&gt; p)</span> {</span><br><span class="line">         <span class="type">Node</span>&lt;E&gt; <span class="variable">next</span> <span class="operator">=</span> p.getNext();</span><br><span class="line">         <span class="keyword">return</span> (p == next) ? head : next;</span><br><span class="line">     }</span><br></pre></td></tr></tbody></table></figure><p><strong>第二步设置入队节点为尾节点</strong>。p.casNext(null, n) 方法用于将入队节点设置为当前队列尾节点的 next 节点，p 如果是 null 表示 p 是当前队列的尾节点，如果不为 null 表示有其他线程更新了尾节点，则需要重新获取当前队列的尾节点。 <strong>hops 的设计意图</strong>。上面分析过对于先进先出的队列入队所要做的事情就是将入队节点设置成尾节点，doug lea 写的代码和逻辑还是稍微有点复杂。那么我用以下方式来实现行不行？</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">offer</span><span class="params">(E e)</span> {</span><br><span class="line">        <span class="keyword">if</span> (e == <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">        <span class="type">Node</span>&lt;E&gt; <span class="variable">n</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Node</span>&lt;E&gt;(e);</span><br><span class="line">        <span class="keyword">for</span> (;;) {</span><br><span class="line">            <span class="type">Node</span>&lt;E&gt; <span class="variable">t</span> <span class="operator">=</span> tail;</span><br><span class="line">            <span class="keyword">if</span> (t.casNext(<span class="literal">null</span>, n) &amp;&amp; casTail(t, n)) {</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>让 tail 节点永远作为队列的尾节点，这样实现代码量非常少，而且逻辑非常清楚和易懂。但是这么做有个缺点就是每次都需要使用循环 CAS 更新 tail 节点。如果能减少 CAS 更新 tail 节点的次数，就能提高入队的效率，所以 doug lea 使用 hops 变量来控制并减少 tail 节点的更新频率，并不是每次节点入队后都将 tail 节点更新成尾节点，而是当 tail 节点和尾节点的距离大于等于常量 HOPS 的值（默认等于 1）时才更新 tail 节点，tail 和尾节点的距离越长使用 CAS 更新 tail 节点的次数就会越少，但是距离越长带来的负面效果就是每次入队时定位尾节点的时间就越长，因为循环体需要多循环一次来定位出尾节点，但是这样仍然能提高入队的效率，因为从本质上来看它通过增加对 volatile 变量的读操作来减少了对 volatile 变量的写操作，而对 volatile 变量的写操作开销要远远大于读操作，所以入队效率会有所提升。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">HOPS</span> <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></tbody></table></figure><p>还有一点需要注意的是入队方法永远返回 true，所以不要通过返回值判断入队是否成功。</p><h2 id="出队列"><a href="#出队列" class="headerlink" title="出队列"></a>出队列</h2><p>出队列的就是从队列里返回一个节点元素，并清空该节点对元素的引用。让我们通过每个节点出队的快照来观察下 head 节点的变化。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139221983079.jpg"><br> 从上图可知，并不是每次出队时都更新 head 节点，当 head 节点里有元素时，直接弹出 head 节点里的元素，而不会更新 head 节点。只有当 head 节点里没有元素时，出队操作才会更新 head 节点。这种做法也是通过 hops 变量来减少使用 CAS 更新 head 节点的消耗，从而提高出队效率。让我们再通过源码来深入分析下出队过程。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">E</span> <span class="title function_">poll</span><span class="params">()</span> {</span><br><span class="line">    <span class="type">Node</span>&lt;E&gt; <span class="variable">h</span> <span class="operator">=</span> head;</span><br><span class="line">   <span class="comment">// p表示头节点，需要出队的节点</span></span><br><span class="line">    <span class="type">Node</span>&lt;E&gt; <span class="variable">p</span> <span class="operator">=</span> h;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">hops</span> <span class="operator">=</span> <span class="number">0</span>;; hops++) {</span><br><span class="line">        <span class="comment">// 获取p节点的元素</span></span><br><span class="line">        <span class="type">E</span> <span class="variable">item</span> <span class="operator">=</span> p.getItem();</span><br><span class="line">        <span class="comment">// 如果p节点的元素不为空，使用CAS设置p节点引用的元素为null,如果成功则返回p节点的元素。</span></span><br><span class="line">        <span class="keyword">if</span> (item != <span class="literal">null</span> &amp;&amp; p.casItem(item, <span class="literal">null</span>)) {</span><br><span class="line">            <span class="keyword">if</span> (hops &gt;= HOPS) {</span><br><span class="line">                <span class="comment">//将p节点下一个节点设置成head节点</span></span><br><span class="line">                <span class="type">Node</span>&lt;E&gt; <span class="variable">q</span> <span class="operator">=</span> p.getNext();</span><br><span class="line">                updateHead(h, (q != <span class="literal">null</span>) ? q : p);</span><br><span class="line">            }</span><br><span class="line">            <span class="keyword">return</span> item;</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 如果头节点的元素为空或头节点发生了变化，这说明头节点已经被另外一个线程修改了。那么获取p节点的下一个节点 </span></span><br><span class="line">        Node&lt;&gt; next = succ(p);</span><br><span class="line">        <span class="comment">// 如果p的下一个节点也为空，说明这个队列已经空了</span></span><br><span class="line">        <span class="keyword">if</span> (next == <span class="literal">null</span>) {</span><br><span class="line">          <span class="comment">// 更新头节点。</span></span><br><span class="line">            updateHead(h, p);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 如果下一个元素不为空，则将头节点的下一个节点设置成头节点</span></span><br><span class="line">        p = next;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>首先获取头节点的元素，然后判断头节点元素是否为空，如果为空，表示另外一个线程已经进行了一次出队操作将该节点的元素取走，如果不为空，则使用 CAS 的方式将头节点的引用设置成 null，如果 CAS 成功，则直接返回头节点的元素，如果不成功，表示另外一个线程已经进行了一次出队操作更新了 head 节点，导致元素发生了变化，需要重新获取头节点。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li>简单，快速和实用的阻塞和非阻塞并发队列算法。</li><li>非阻塞算法在容器里的实现。</li><li>JDK1.6 中 ConcurrentLinkedQueue 源码和注释。</li></ol><blockquote><p>转载自<span class="exturl" data-url="aHR0cDovL2lmZXZlLmNvbS8=">并发编程网 – ifeve.com<i class="fa fa-external-link-alt"></i></span><br>本文链接地址: <span class="exturl" data-url="aHR0cDovL2lmZXZlLmNvbS9jb25jdXJyZW50bGlua2VkcXVldWUv">聊聊并发（六）ConcurrentLinkedQueue 的实现原理分析<i class="fa fa-external-link-alt"></i></span></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/12742.html</id>
    <link href="https://www.bytelife.net/articles/12742.html"/>
    <published>2017-03-14T17:10:55.000Z</published>
    <summary>
      <![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在并发编程中我们有时候需要使用线程安全的队列。如果我们要实现一个线程安全的队列有两种实现方式：一种是使用阻塞算法，另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁（入队和出队用同一把锁）或两个锁（入队和出队用不同的锁）等方式来实现，而非阻塞的实现方式则可以使用循环 CAS 的方式来实现，本文让我们一起来研究下 Doug Lea 是如何使用非阻塞的方式来实现线程安全队列 ConcurrentLinkedQueue 的，相信从大师身上我们能学到不少并发编程的技巧。</p>]]>
    </summary>
    <title>ConcurrentLinkedQueue 的实现原理分析</title>
    <updated>2017-03-14T17:10:55.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="并发" scheme="https://www.bytelife.net/tags/%E5%B9%B6%E5%8F%91/"/>
    <content>
      <![CDATA[<h3 id="术语定义"><a href="#术语定义" class="headerlink" title="术语定义"></a>术语定义</h3><p>术语: 哈希算法<br>英文: hash algorithm<br>解释: 是一种将任意内容的输入转换成相同长度输出的加密方式，其输出被称为哈希值。&nbsp;</p><span id="more"></span><p>术语: 哈希表<br>英文: hash table<br>解释: 根据设定的哈希函数 H (key) 和处理冲突方法将一组关键字映象到一个有限的地址区间上，并以关键字在地址区间中的象作为记录在表中的存储位置，这种表称为哈希表或散列，所得存储位置称为哈希地址或散列地址。</p><h3 id="线程不安全的HashMap"><a href="#线程不安全的HashMap" class="headerlink" title="线程不安全的HashMap"></a>线程不安全的 HashMap</h3><p>因为多线程环境下，使用 HashMap 进行 put 操作会引起死循环，导致 CPU 利用率接近 100%，所以在并发情况下不能使用 HashMap，如以下代码</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">HashMap</span>&lt;String, String&gt; <span class="variable">map</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, String&gt;(<span class="number">2</span>);</span><br><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() {</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++) {</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() {</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                    map.put(UUID.randomUUID().toString(), <span class="string">""</span>);</span><br><span class="line">                }</span><br><span class="line">            }, <span class="string">"ftf"</span> + i).start();</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}, <span class="string">"ftf"</span>);</span><br><span class="line">t.start();</span><br><span class="line">t.join();</span><br></pre></td></tr></tbody></table></figure><h3 id="效率低下的HashTable容器"><a href="#效率低下的HashTable容器" class="headerlink" title="效率低下的HashTable容器"></a>效率低下的 HashTable 容器</h3><p>HashTable 容器使用 synchronized 来保证线程安全，但在线程竞争激烈的情况下 HashTable 的效率非常低下。因为当一个线程访问 HashTable 的同步方法时，其他线程访问 HashTable 的同步方法时，可能会进入阻塞或轮询状态。如线程 1 使用 put 进行添加元素，线程 2 不但不能使用 put 方法添加元素，并且也不能使用 get 方法来获取元素，所以竞争越激烈效率越低。</p><h3 id="锁分段技术"><a href="#锁分段技术" class="headerlink" title="锁分段技术"></a>锁分段技术</h3><p>HashTable 容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问 HashTable 的线程都必须竞争同一把锁，那假如容器里有多把锁，每一把锁用于锁容器其中一部分数据，那么当多线程访问容器里不同数据段的数据时，线程间就不会存在锁竞争，从而可以有效的提高并发访问效率，这就是 ConcurrentHashMap 所使用的锁分段技术，首先将数据分成一段一段的存储，然后给每一段数据配一把锁，当一个线程占用锁访问其中一个段数据的时候，其他段的数据也能被其他线程访问。</p><h3 id="ConcurrentHashMap的结构"><a href="#ConcurrentHashMap的结构" class="headerlink" title="ConcurrentHashMap的结构"></a>ConcurrentHashMap 的结构</h3><p>我们通过 ConcurrentHashMap 的类图来分析 ConcurrentHashMap 的结构。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16140527435926.jpg"><br>ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。Segment 是一种可重入锁 ReentrantLock，在 ConcurrentHashMap 里扮演锁的角色，HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组，Segment 的结构和 HashMap 类似，是一种数组和链表结构， 一个 Segment 里包含一个 HashEntry 数组，每个 HashEntry 是一个链表结构的元素， 每个 Segment 守护者一个 HashEntry 数组里的元素, 当对 HashEntry 数组的数据进行修改时，必须首先获得它对应的 Segment 锁。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138516410264.jpg"></p><h3 id="ConcurrentHashMap的初始化"><a href="#ConcurrentHashMap的初始化" class="headerlink" title="ConcurrentHashMap的初始化"></a>ConcurrentHashMap 的初始化</h3><p>ConcurrentHashMap 初始化方法是通过 initialCapacity，loadFactor, concurrencyLevel 几个参数来初始化 segments 数组，段偏移量 segmentShift，段掩码 segmentMask 和每个 segment 里的 HashEntry 数组 。 初始化 segments 数组。让我们来看一下初始化 segmentShift，segmentMask 和 segments 数组的源代码。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (concurrencyLevel &gt; MAX_SEGMENTS)</span><br><span class="line">    concurrencyLevel = MAX_SEGMENTS;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Find power-of-two sizes best matching arguments</span></span><br><span class="line"><span class="type">int</span> <span class="variable">sshift</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">ssize</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">while</span> (ssize &lt; concurrencyLevel) {</span><br><span class="line">    ++sshift;</span><br><span class="line">    ssize &lt;&lt;= <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line">segmentShift = <span class="number">32</span> - sshift;</span><br><span class="line">segmentMask = ssize - <span class="number">1</span>;</span><br><span class="line"><span class="built_in">this</span>.segments = Segment.newArray(ssize);</span><br></pre></td></tr></tbody></table></figure><p>由上面的代码可知 segments 数组的长度 ssize 通过 concurrencyLevel 计算得出。为了能通过按位与的哈希算法来定位 segments 数组的索引，必须保证 segments 数组的长度是 2 的 N 次方（power-of-two size），所以必须计算出一个是大于或等于 concurrencyLevel 的最小的 2 的 N 次方值来作为 segments 数组的长度。假如 concurrencyLevel 等于 14，15 或 16，ssize 都会等于 16，即容器里锁的个数也是 16。注意 concurrencyLevel 的最大大小是 65535，意味着 segments 数组的长度最大为 65536，对应的二进制是 16 位。 初始化 segmentShift 和 segmentMask。这两个全局变量在定位 segment 时的哈希算法里需要使用，sshift 等于 ssize 从 1 向左移位的次数，在默认情况下 concurrencyLevel 等于 16，1 需要向左移位移动 4 次，所以 sshift 等于 4。segmentShift 用于定位参与 hash 运算的位数，segmentShift 等于 32 减 sshift，所以等于 28，这里之所以用 32 是因为 ConcurrentHashMap 里的 hash () 方法输出的最大数是 32 位的，后面的测试中我们可以看到这点。segmentMask 是哈希运算的掩码，等于 ssize 减 1，即 15，掩码的二进制各个位的值都是 1。因为 ssize 的最大长度是 65536，所以 segmentShift 最大值是 16，segmentMask 最大值是 65535，对应的二进制是 16 位，每个位都是 1。 初始化每个 Segment。输入参数 initialCapacity 是 ConcurrentHashMap 的初始化容量，loadfactor 是每个 segment 的负载因子，在构造方法里需要通过这两个参数来初始化数组中的每个 segment。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (initialCapacity &gt; MAXIMUM_CAPACITY)</span><br><span class="line">    initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line"><span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> initialCapacity / ssize;</span><br><span class="line"><span class="keyword">if</span> (c * ssize &lt; initialCapacity)</span><br><span class="line">    ++c;</span><br><span class="line"><span class="type">int</span> <span class="variable">cap</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="keyword">while</span> (cap &lt; c)</span><br><span class="line">    cap &lt;&lt;= <span class="number">1</span>;</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="built_in">this</span>.segments.length; ++i)</span><br><span class="line">    <span class="built_in">this</span>.segments[i] = <span class="keyword">new</span> <span class="title class_">Segment</span>&lt;K,V&gt;(cap, loadFactor);</span><br></pre></td></tr></tbody></table></figure><p>上面代码中的变量 cap 就是 segment 里 HashEntry 数组的长度，它等于 initialCapacity 除以 ssize 的倍数 c，如果 c 大于 1，就会取大于等于 c 的 2 的 N 次方值，所以 cap 不是 1，就是 2 的 N 次方。segment 的容量 threshold＝(int) cap*loadFactor，默认情况下 initialCapacity 等于 16，loadfactor 等于 0.75，通过运算 cap 等于 1，threshold 等于零。</p><h3 id="定位Segment"><a href="#定位Segment" class="headerlink" title="定位Segment"></a>定位 Segment</h3><p>既然 ConcurrentHashMap 使用分段锁 Segment 来保护不同段的数据，那么在插入和获取元素的时候，必须先通过哈希算法定位到 Segment。可以看到 ConcurrentHashMap 会首先使用 Wang/Jenkins hash 的变种算法对元素的 hashCode 进行一次再哈希。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">hash</span><span class="params">(<span class="type">int</span> h)</span> {</span><br><span class="line">        h += (h &lt;&lt; <span class="number">15</span>) ^ <span class="number">0xffffcd7d</span>;</span><br><span class="line">        h ^= (h &gt;&gt;&gt; <span class="number">10</span>);</span><br><span class="line">        h += (h &lt;&lt; <span class="number">3</span>);</span><br><span class="line">        h ^= (h &gt;&gt;&gt; <span class="number">6</span>);</span><br><span class="line">        h += (h &lt;&lt; <span class="number">2</span>) + (h &lt;&lt; <span class="number">14</span>);</span><br><span class="line">        <span class="keyword">return</span> h ^ (h &gt;&gt;&gt; <span class="number">16</span>);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>之所以进行再哈希，其目的是为了减少哈希冲突，使元素能够均匀的分布在不同的 Segment 上，从而提高容器的存取效率。假如哈希的质量差到极点，那么所有的元素都在一个 Segment 中，不仅存取元素缓慢，分段锁也会失去意义。我做了一个测试，不通过再哈希而直接执行哈希计算。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">System.out.println(Integer.parseInt(<span class="string">"0001111"</span>, <span class="number">2</span>) &amp; <span class="number">15</span>);</span><br><span class="line">System.out.println(Integer.parseInt(<span class="string">"0011111"</span>, <span class="number">2</span>) &amp; <span class="number">15</span>);</span><br><span class="line">System.out.println(Integer.parseInt(<span class="string">"0111111"</span>, <span class="number">2</span>) &amp; <span class="number">15</span>);</span><br><span class="line">System.out.println(Integer.parseInt(<span class="string">"1111111"</span>, <span class="number">2</span>) &amp; <span class="number">15</span>);</span><br></pre></td></tr></tbody></table></figure><p>计算后输出的哈希值全是 15，通过这个例子可以发现如果不进行再哈希，哈希冲突会非常严重，因为只要低位一样，无论高位是什么数，其哈希值总是一样。我们再把上面的二进制数据进行再哈希后结果如下，为了方便阅读，不足 32 位的高位补了 0，每隔四位用竖线分割下。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">0100</span>｜<span class="number">0111</span>｜<span class="number">0110</span>｜<span class="number">0111</span>｜<span class="number">1101</span>｜<span class="number">1010</span>｜<span class="number">0100</span>｜<span class="number">1110</span></span><br><span class="line"><span class="number">1111</span>｜<span class="number">0111</span>｜<span class="number">0100</span>｜<span class="number">0011</span>｜<span class="number">0000</span>｜<span class="number">0001</span>｜<span class="number">1011</span>｜<span class="number">1000</span></span><br><span class="line"><span class="number">0111</span>｜<span class="number">0111</span>｜<span class="number">0110</span>｜<span class="number">1001</span>｜<span class="number">0100</span>｜<span class="number">0110</span>｜<span class="number">0011</span>｜<span class="number">1110</span></span><br><span class="line"><span class="number">1000</span>｜<span class="number">0011</span>｜<span class="number">0000</span>｜<span class="number">0000</span>｜<span class="number">1100</span>｜<span class="number">1000</span>｜<span class="number">0001</span>｜<span class="number">1010</span></span><br></pre></td></tr></tbody></table></figure><p>可以发现每一位的数据都散列开了，通过这种再哈希能让数字的每一位都能参加到哈希运算当中，从而减少哈希冲突。ConcurrentHashMap 通过以下哈希算法定位 segment。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">Segment</span>&lt;K,V&gt; <span class="title function_">segmentFor</span><span class="params">(<span class="type">int</span> hash)</span> {</span><br><span class="line">        <span class="keyword">return</span> segments[(hash &gt;&gt;&gt; segmentShift) &amp; segmentMask];</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>默认情况下 segmentShift 为 28，segmentMask 为 15，再哈希后的数最大是 32 位二进制数据，向右无符号移动 28 位，意思是让高 4 位参与到 hash 运算中， (hash &gt;&gt;&gt; segmentShift) &amp; segmentMask 的运算结果分别是 4，15，7 和 8，可以看到 hash 值没有发生冲突。</p><h3 id="ConcurrentHashMap的get操作"><a href="#ConcurrentHashMap的get操作" class="headerlink" title="ConcurrentHashMap的get操作"></a>ConcurrentHashMap 的 get 操作</h3><p>Segment 的 get 操作实现非常简单和高效。先经过一次再哈希，然后使用这个哈希值通过哈希运算定位到 segment，再通过哈希算法定位到元素，代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">V</span> <span class="title function_">get</span><span class="params">(Object key)</span> {</span><br><span class="line">    <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> hash(key.hashCode());</span><br><span class="line">    <span class="keyword">return</span> segmentFor(hash).get(key, hash);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>get 操作的高效之处在于整个 get 过程不需要加锁，除非读到的值是空的才会加锁重读，我们知道 HashTable 容器的 get 方法是需要加锁的，那么 ConcurrentHashMap 的 get 操作是如何做到不加锁的呢？原因是它的 get 方法里将要使用的共享变量都定义成 volatile，如用于统计当前 Segement 大小的 count 字段和用于存储值的 HashEntry 的 value。定义成 volatile 的变量，能够在线程之间保持可见性，能够被多线程同时读，并且保证不会读到过期的值，但是只能被单线程写（有一种情况可以被多线程写，就是写入的值不依赖于原值），在 get 操作里只需要读不需要写共享变量 count 和 value，所以可以不用加锁。之所以不会读到过期的值，是根据 java 内存模型的 happen before 原则，对 volatile 字段的写入操作先于读操作，即使两个线程同时修改和获取 volatile 变量，get 操作也能拿到最新的值，这是用 volatile 替换锁的经典应用场景。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">transient</span> <span class="keyword">volatile</span> <span class="type">int</span> count;</span><br><span class="line"><span class="keyword">volatile</span> V value;</span><br></pre></td></tr></tbody></table></figure><p>在定位元素的代码里我们可以发现定位 HashEntry 和定位 Segment 的哈希算法虽然一样，都与数组的长度减去一相与，但是相与的值不一样，定位 Segment 使用的是元素的 hashcode 通过再哈希后得到的值的高位，而定位 HashEntry 直接使用的是再哈希后的值。其目的是避免两次哈希后的值一样，导致元素虽然在 Segment 里散列开了，但是却没有在 HashEntry 里散列开。</p><figure class="highlight arcade"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">hash</span> &gt;&gt;&gt; segmentShift) &amp; segmentMask<span class="comment">//定位Segment所使用的hash算法</span></span><br><span class="line">int index = <span class="built_in">hash</span> &amp; (<span class="literal">tab</span>.<span class="built_in">length</span> - <span class="number">1</span>);<span class="comment">// 定位HashEntry所使用的hash算法</span></span><br></pre></td></tr></tbody></table></figure><h3 id="ConcurrentHashMap的Put操作"><a href="#ConcurrentHashMap的Put操作" class="headerlink" title="ConcurrentHashMap的Put操作"></a>ConcurrentHashMap 的 Put 操作</h3><p>由于 put 方法里需要对共享变量进行写入操作，所以为了线程安全，在操作共享变量时必须得加锁。Put 方法首先定位到 Segment，然后在 Segment 里进行插入操作。插入操作需要经历两个步骤，第一步判断是否需要对 Segment 里的 HashEntry 数组进行扩容，第二步定位添加元素的位置然后放在 HashEntry 数组里。 是否需要扩容。在插入元素前会先判断 Segment 里的 HashEntry 数组是否超过容量（threshold），如果超过阀值，数组进行扩容。值得一提的是，Segment 的扩容判断比 HashMap 更恰当，因为 HashMap 是在插入元素后判断元素是否已经到达容量的，如果到达了就进行扩容，但是很有可能扩容之后没有新元素插入，这时 HashMap 就进行了一次无效的扩容。 如何扩容。扩容的时候首先会创建一个两倍于原容量的数组，然后将原数组里的元素进行再 hash 后插入到新的数组里。为了高效 ConcurrentHashMap 不会对整个容器进行扩容，而只对某个 segment 进行扩容。</p><h3 id="ConcurrentHashMap的size操作"><a href="#ConcurrentHashMap的size操作" class="headerlink" title="ConcurrentHashMap的size操作"></a>ConcurrentHashMap 的 size 操作</h3><p>如果我们要统计整个 ConcurrentHashMap 里元素的大小，就必须统计所有 Segment 里元素的大小后求和。Segment 里的全局变量 count 是一个 volatile 变量，那么在多线程场景下，我们是不是直接把所有 Segment 的 count 相加就可以得到整个 ConcurrentHashMap 大小了呢？不是的，虽然相加时可以获取每个 Segment 的 count 的最新值，但是拿到之后可能累加前使用的 count 发生了变化，那么统计结果就不准了。所以最安全的做法，是在统计 size 的时候把所有 Segment 的 put，remove 和 clean 方法全部锁住，但是这种做法显然非常低效。 因为在累加 count 操作过程中，之前累加过的 count 发生变化的几率非常小，所以 ConcurrentHashMap 的做法是先尝试 2 次通过不锁住 Segment 的方式来统计各个 Segment 大小，如果统计的过程中，容器的 count 发生了变化，则再采用加锁的方式来统计所有 Segment 的大小。 那么 ConcurrentHashMap 是如何判断在统计的时候容器是否发生了变化呢？使用 modCount 变量，在 put , remove 和 clean 方法里操作元素前都会将变量 modCount 进行加 1，那么在统计 size 前后比较 modCount 是否发生变化，从而得知容器的大小是否发生变化。 参考资料</p><ol><li>JDK1.6 源代码。</li><li>《Java 并发编程实践》。</li><li>Java 并发编程之 ConcurrentHashMap 。</li></ol><blockquote><p>转载自<span class="exturl" data-url="aHR0cDovL2lmZXZlLmNvbS8=">并发编程网 – ifeve.com<i class="fa fa-external-link-alt"></i></span> 本文链接地址: <span class="exturl" data-url="aHR0cDovL2lmZXZlLmNvbS9jb25jdXJyZW50aGFzaG1hcC8=">聊聊并发（四）深入分析 ConcurrentHashMap<i class="fa fa-external-link-alt"></i></span></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/36648.html</id>
    <link href="https://www.bytelife.net/articles/36648.html"/>
    <published>2017-03-14T14:31:36.000Z</published>
    <summary>
      <![CDATA[<h3 id="术语定义"><a href="#术语定义" class="headerlink" title="术语定义"></a>术语定义</h3><p>术语: 哈希算法<br>英文: hash algorithm<br>解释: 是一种将任意内容的输入转换成相同长度输出的加密方式，其输出被称为哈希值。&nbsp;</p>]]>
    </summary>
    <title>深入剖析 ConcurrentHashMap</title>
    <updated>2017-03-14T14:31:36.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <content>
      <![CDATA[<p>哈希表（hash table）也叫散列表，是一种非常重要的数据结构，应用场景及其丰富，许多缓存技术（比如 memcached）的核心其实就是在内存中维护一张大的哈希表，而 HashMap 的实现原理也常常出现在各类的面试题中，重要性可见一斑。本文会对 java 集合框架中的对应实现 HashMap 的实现原理进行讲解，然后会对 JDK7 的 HashMap 源码进行分析。</p><span id="more"></span><h1 id="一、什么是哈希表"><a href="#一、什么是哈希表" class="headerlink" title="一、什么是哈希表"></a>一、什么是哈希表</h1><p>在讨论哈希表之前，我们先大概了解下其他数据结构在新增，查找等基础操作执行性能<br><strong>数组</strong>：采用一段连续的存储单元来存储数据。对于指定下标的查找，时间复杂度为 O (1)；通过给定值进行查找，需要遍历数组，逐一比对给定关键字和数组元素，时间复杂度为 O (n)，当然，对于有序数组，则可采用二分查找，插值查找，斐波那契查找等方式，可将查找复杂度提高为 O (logn)；对于一般的插入删除操作，涉及到数组元素的移动，其平均复杂度也为 O (n)<br><strong>线性链表</strong>：对于链表的新增，删除等操作（在找到指定操作位置后），仅需处理结点间的引用即可，时间复杂度为 O (1)，而查找操作需要遍历链表逐一进行比对，复杂度为 O (n)<br><strong>二叉树</strong>：对一棵相对平衡的有序二叉树，对其进行插入，查找，删除等操作，平均复杂度均为 O (logn)。<br><strong>哈希表</strong>：相比上述几种数据结构，在哈希表中进行添加，删除，查找等操作，性能十分之高，不考虑哈希冲突的情况下，仅需一次定位即可完成，时间复杂度为 O (1)，接下来我们就来看看哈希表是如何实现达到惊艳的常数阶 O (1) 的。 我们知道，数据结构的物理存储结构只有两种：<br><strong>顺序存储结构</strong>和<strong>链式存储结构</strong>（像栈，队列，树，图等是从逻辑结构去抽象的，映射到内存中，也这两种物理组织形式），而在上面我们提到过，在数组中根据下标查找某个元素，一次定位就可以达到，哈希表利用了这种特性，<strong>哈希表的主干就是数组</strong>。 比如我们要新增或查找某个元素，我们通过把当前元素的关键字 通过某个函数映射到数组中的某个位置，通过数组下标一次定位就可完成操作。<br><strong>存储位置 = f (关键字)</strong> 　其中，这个函数 f 一般称为<strong>哈希函数</strong>，这个函数的设计好坏会直接影响到哈希表的优劣。举个例子，比如我们要在哈希表中执行插入操作：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139205037704.png"><br>查找操作同理，先通过哈希函数计算出实际存储地址，然后从数组中对应地址取出即可。<br><strong>哈希冲突</strong> 然而万事无完美，如果两个不同的元素，通过哈希函数得出的实际存储地址相同怎么办？也就是说，当我们对某个元素进行哈希运算，得到一个存储地址，然后要进行插入的时候，发现已经被其他元素占用了，其实这就是所谓的<strong>哈希冲突</strong>，也叫哈希碰撞。前面我们提到过，哈希函数的设计至关重要，好的哈希函数会尽可能地保证 <strong>计算简单</strong>和<strong>散列地址分布均匀,<strong> 但是，我们需要清楚的是，数组是一块连续的固定长度的内存空间，再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢？哈希冲突的解决方案有多种: 开放定址法（发生冲突，继续寻找下一块未被占用的存储地址），再散列函数法，链地址法，而 HashMap 即是采用了链地址法，也就是</strong>数组 + 链表</strong>的方式，</p><h1 id="二、HashMap实现原理"><a href="#二、HashMap实现原理" class="headerlink" title="二、HashMap实现原理"></a>二、HashMap 实现原理</h1><p>HashMap 的主干是一个 Entry 数组。Entry 是 HashMap 的基本组成单元，每一个 Entry 包含一个 key-value 键值对。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//HashMap的主干数组，可以看到就是一个Entry数组，初始值为空数组{}，主干数组的长度一定是2的次幂，至于为什么这么做，后面会有详细分析。</span></span><br><span class="line"><span class="keyword">transient</span> <span class="type">Entry</span>&lt;K,V&gt;[] <span class="variable">table</span> <span class="operator">=</span> (Entry&lt;K,V&gt;[]) EMPTY_TABLE;</span><br></pre></td></tr></tbody></table></figure><p>Entry 是 HashMap 中的一个静态内部类。代码如下</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Entry</span>&lt;K,V&gt; <span class="keyword">implements</span> <span class="title class_">Map</span>.Entry&lt;K,V&gt; {</span><br><span class="line">    <span class="keyword">final</span> K key;</span><br><span class="line">    V value;</span><br><span class="line">    Entry&lt;K,V&gt; next;<span class="comment">//存储指向下一个Entry的引用，单链表结构</span></span><br><span class="line">    <span class="type">int</span> hash;<span class="comment">//对key的hashcode值进行hash运算后得到的值，存储在Entry，避免重复计算</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Creates new entry.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Entry(<span class="type">int</span> h, K k, V v, Entry&lt;K,V&gt; n) {</span><br><span class="line">        value = v;</span><br><span class="line">        next = n;</span><br><span class="line">        key = k;</span><br><span class="line">        hash = h;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>所以，HashMap 的整体结构如下<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139205276822.png"></p><p> <strong>简单来说，HashMap 由数组 + 链表组成的，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的，如果定位到的数组位置不含链表（当前 entry 的 next 指向 null）, 那么对于查找，添加等操作很快，仅需一次寻址即可；如果定位到的数组包含链表，对于添加操作，其时间复杂度依然为 O (1)，因为最新的 Entry 会插入链表头部，急需要简单改变引用链即可，而对于查找操作来讲，此时就需要遍历链表，然后通过 key 对象的 equals 方法逐一比对查找。所以，性能考虑，HashMap 中的链表出现越少，性能才会越好。</strong> </p><p> 其他几个重要字段</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//实际存储的key-value键值对的个数</span></span><br><span class="line"><span class="keyword">transient</span> <span class="type">int</span> size;</span><br><span class="line"><span class="comment">//阈值，当table == {}时，该值为初始容量（初始容量默认为16）；当table被填充了，也就是为table分配内存空间后，threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold，后面会详细谈到</span></span><br><span class="line"><span class="type">int</span> threshold;</span><br><span class="line"><span class="comment">//负载因子，代表了table的填充度有多少，默认是0.75</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">float</span> loadFactor;</span><br><span class="line"><span class="comment">//用于快速失败，由于HashMap非线程安全，在对HashMap进行迭代时，如果期间其他线程的参与导致HashMap的结构发生变化了（比如put，remove等操作），需要抛出异常ConcurrentModificationException</span></span><br><span class="line"><span class="keyword">transient</span> <span class="type">int</span> modCount;</span><br></pre></td></tr></tbody></table></figure><p>HashMap 有 4 个构造器，其他构造器如果用户没有传入 initialCapacity 和 loadFactor 这两个参数，会使用默认值 initialCapacity 默认为 16，loadFactory 默认为 0.75 我们看下其中一个</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">public</span> <span class="title function_">HashMap</span><span class="params">(<span class="type">int</span> initialCapacity, <span class="type">float</span> loadFactor)</span> {</span><br><span class="line">　　　　　<span class="comment">//此处对传入的初始容量进行校验，最大不能超过MAXIMUM_CAPACITY = 1&lt;&lt;30(2&lt;sup&gt;30&lt;/sup&gt;)</span></span><br><span class="line">        <span class="keyword">if</span> (initialCapacity &lt; <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">"Illegal initial capacity: "</span> +</span><br><span class="line">                                               initialCapacity);</span><br><span class="line">        <span class="keyword">if</span> (initialCapacity &gt; MAXIMUM_CAPACITY)</span><br><span class="line">            initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line">        <span class="keyword">if</span> (loadFactor &lt;= <span class="number">0</span> || Float.isNaN(loadFactor))</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">"Illegal load factor: "</span> +</span><br><span class="line">                                               loadFactor);</span><br><span class="line"></span><br><span class="line">        <span class="built_in">this</span>.loadFactor = loadFactor;</span><br><span class="line">        threshold = initialCapacity;</span><br><span class="line">　　　　　</span><br><span class="line">        init();<span class="comment">//init方法在HashMap中没有实际实现，不过在其子类如 linkedHashMap中就会有对应实现</span></span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>从上面这段代码我们可以看出，<strong>在常规构造器中，没有为数组 table 分配内存空间（有一个入参为指定 Map 的构造器例外），而是在执行 put 操作的时候才真正构建 table 数组</strong> OK, 接下来我们来看看 put 操作的实现吧</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">V</span> <span class="title function_">put</span><span class="params">(K key, V value)</span> {</span><br><span class="line">    <span class="comment">//如果table数组为空数组{}，进行数组填充（为table分配实际内存空间），入参为threshold，此时threshold为initialCapacity 默认是1&lt;&lt;4(2&lt;sup&gt;4&lt;/sup&gt;=16)</span></span><br><span class="line">    <span class="keyword">if</span> (table == EMPTY_TABLE) {</span><br><span class="line">        inflateTable(threshold);</span><br><span class="line">    }</span><br><span class="line">   <span class="comment">//如果key为null，存储位置为table[0]或table[0]的冲突链上</span></span><br><span class="line">    <span class="keyword">if</span> (key == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">return</span> putForNullKey(value);</span><br><span class="line">    <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> hash(key);<span class="comment">//对key的hashcode进一步计算，确保散列均匀</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> indexFor(hash, table.length);<span class="comment">//获取在table中的实际位置</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">Entry</span>&lt;K,V&gt; <span class="variable">e</span> <span class="operator">=</span> table[i]; e != <span class="literal">null</span>; e = e.next) {</span><br><span class="line">    <span class="comment">//如果该对应数据已存在，执行覆盖操作。用新value替换旧value，并返回旧value</span></span><br><span class="line">        Object k;</span><br><span class="line">        <span class="keyword">if</span> (e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k))) {</span><br><span class="line">            <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e.value;</span><br><span class="line">            e.value = value;</span><br><span class="line">            e.recordAccess(<span class="built_in">this</span>);</span><br><span class="line">            <span class="keyword">return</span> oldValue;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">    modCount++;<span class="comment">//保证并发访问时，若HashMap内部结构发生变化，快速响应失败</span></span><br><span class="line">    addEntry(hash, key, value, i);<span class="comment">//新增一个entry</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>&nbsp;先来看看 inflateTable 这个方法</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">void</span> <span class="title function_">inflateTable</span><span class="params">(<span class="type">int</span> toSize)</span> {</span><br><span class="line">        <span class="type">int</span> <span class="variable">capacity</span> <span class="operator">=</span> roundUpToPowerOf2(toSize);<span class="comment">//capacity一定是2的次幂</span></span><br><span class="line">        threshold = (<span class="type">int</span>) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + <span class="number">1</span>);<span class="comment">//此处为threshold赋值，取capacity*loadFactor和MAXIMUM_CAPACITY+1的最小值，capaticy一定不会超过MAXIMUM_CAPACITY，除非loadFactor大于1</span></span><br><span class="line">        table = <span class="keyword">new</span> <span class="title class_">Entry</span>[capacity];</span><br><span class="line">        initHashSeedAsNeeded(capacity);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>inflateTable 这个方法用于为主干数组 table 在内存中分配存储空间，通过 roundUpToPowerOf2 (toSize) 可以确保 capacity 为大于或等于 toSize 的最接近 toSize 的二次幂，比如 toSize=13, 则 capacity=16;to_size=16,capacity=16;to_size=17,capacity=32.</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">roundUpToPowerOf2</span><span class="params">(<span class="type">int</span> number)</span> {</span><br><span class="line">       <span class="comment">// assert number &gt;= 0 : "number must be non-negative";</span></span><br><span class="line">       <span class="keyword">return</span> number &gt;= MAXIMUM_CAPACITY</span><br><span class="line">               ? MAXIMUM_CAPACITY</span><br><span class="line">               : (number &gt; <span class="number">1</span>) ? Integer.highestOneBit((number - <span class="number">1</span>) &lt;&lt; <span class="number">1</span>) : <span class="number">1</span>;</span><br><span class="line">   }</span><br></pre></td></tr></tbody></table></figure><p>roundUpToPowerOf2 中的这段处理使得数组长度一定为 2 的次幂，Integer.highestOneBit 是用来获取最左边的 bit（其他 bit 位为 0）所代表的数值. hash 函数</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//这是一个神奇的函数，用了很多的异或，移位等运算，对key的hashcode进一步进行计算以及二进制位的调整等来保证最终获取的存储位置尽量分布均匀</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="title function_">hash</span><span class="params">(Object k)</span> {</span><br><span class="line">        <span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> hashSeed;</span><br><span class="line">        <span class="keyword">if</span> (<span class="number">0</span> != h &amp;&amp; k <span class="keyword">instanceof</span> String) {</span><br><span class="line">            <span class="keyword">return</span> sun.misc.Hashing.stringHash32((String) k);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        h ^= k.hashCode();</span><br><span class="line"></span><br><span class="line">        h ^= (h &gt;&gt;&gt; <span class="number">20</span>) ^ (h &gt;&gt;&gt; <span class="number">12</span>);</span><br><span class="line">        <span class="keyword">return</span> h ^ (h &gt;&gt;&gt; <span class="number">7</span>) ^ (h &gt;&gt;&gt; <span class="number">4</span>);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>以上 hash 函数计算出的值，通过 indexFor 进一步处理来获取实际的存储位置</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 返回数组下标</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">indexFor</span><span class="params">(<span class="type">int</span> h, <span class="type">int</span> length)</span> {</span><br><span class="line">      <span class="keyword">return</span> h &amp; (length-<span class="number">1</span>);</span><br><span class="line">  }</span><br></pre></td></tr></tbody></table></figure><p>h&amp;（length-1）保证获取的 index 一定在数组范围内，举个例子，默认容量 16，length-1=15，h=18, 转换成二进制计算为</p><figure class="highlight tap"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">   <span class="number"> 1 </span><span class="number"> 0 </span><span class="number"> 0 </span><span class="number"> 1 </span> 0</span><br><span class="line">&amp;  <span class="number"> 0 </span><span class="number"> 1 </span><span class="number"> 1 </span><span class="number"> 1 </span> 1</span><br><span class="line">__________________</span><br><span class="line">   <span class="number"> 0 </span><span class="number"> 0 </span><span class="number"> 0 </span><span class="number"> 1 </span><span class="number"> 0 </span>   = 2</span><br></pre></td></tr></tbody></table></figure><p>最终计算出的 index=2。有些版本的对于此处的计算会使用 取模运算，也能保证 index 一定在数组范围内，不过位运算对计算机来说，性能更高一些（HashMap 中有大量位运算） 所以最终存储位置的确定流程是这样的：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139205925453.png"><br>再来看看 addEntry 的实现：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">addEntry</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">int</span> bucketIndex)</span> {</span><br><span class="line">        <span class="keyword">if</span> ((size &gt;= threshold) &amp;&amp; (<span class="literal">null</span> != table[bucketIndex])) {</span><br><span class="line">            resize(<span class="number">2</span> * table.length);<span class="comment">//当size超过临界阈值threshold，并且即将发生哈希冲突时进行扩容</span></span><br><span class="line">            hash = (<span class="literal">null</span> != key) ? hash(key) : <span class="number">0</span>;</span><br><span class="line">            bucketIndex = indexFor(hash, table.length);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        createEntry(hash, key, value, bucketIndex);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>通过以上代码能够得知，当发生哈希冲突并且 size 大于阈值的时候，需要进行数组扩容，扩容时，需要新建一个长度为之前数组 2 倍的新的数组，然后将当前的 Entry 数组中的元素全部传输过去，扩容后的新数组长度为之前的 2 倍，所以扩容相对来说是个耗资源的操作。</p><h1 id="三、为何HashMap的数组长度一定是2的次幂？"><a href="#三、为何HashMap的数组长度一定是2的次幂？" class="headerlink" title="三、为何HashMap的数组长度一定是2的次幂？"></a>三、为何 HashMap 的数组长度一定是 2 的次幂？</h1><p>我们来继续看上面提到的 resize 方法</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">resize</span><span class="params">(<span class="type">int</span> newCapacity)</span> {</span><br><span class="line">       <span class="type">Entry</span>[] <span class="variable">oldTable</span> <span class="operator">=</span> table;</span><br><span class="line">       <span class="type">int</span> <span class="variable">oldCapacity</span> <span class="operator">=</span> oldTable.length;</span><br><span class="line">       <span class="keyword">if</span> (oldCapacity == MAXIMUM_CAPACITY) {</span><br><span class="line">           threshold = Integer.MAX_VALUE;</span><br><span class="line">           <span class="keyword">return</span>;</span><br><span class="line">       }</span><br><span class="line">    </span><br><span class="line">       <span class="type">Entry</span>[] <span class="variable">newTable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Entry</span>[newCapacity];</span><br><span class="line">       transfer(newTable, initHashSeedAsNeeded(newCapacity));</span><br><span class="line">       table = newTable;</span><br><span class="line">       threshold = (<span class="type">int</span>)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + <span class="number">1</span>);</span><br><span class="line">   }</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>如果数组进行扩容，数组长度发生变化，而存储位置 index = h&amp;(length-1),index 也可能会发生变化，需要重新计算 index，我们先来看看 transfer 这个方法</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">transfer</span><span class="params">(Entry[] newTable, <span class="type">boolean</span> rehash)</span> {</span><br><span class="line">        <span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> newTable.length;</span><br><span class="line">　　　　　<span class="comment">//for循环中的代码，逐个遍历链表，重新计算索引位置，将老数组数据复制到新数组中去（数组不存储实际数据，所以仅仅是拷贝引用而已）</span></span><br><span class="line">        <span class="keyword">for</span> (Entry&lt;K,V&gt; e : table) {</span><br><span class="line">            <span class="keyword">while</span>(<span class="literal">null</span> != e) {</span><br><span class="line">                <span class="type">Entry</span>&lt;K,V&gt; <span class="variable">next</span> <span class="operator">=</span> e.next;</span><br><span class="line">                <span class="keyword">if</span> (rehash) {</span><br><span class="line">                    e.hash = <span class="literal">null</span> == e.key ? <span class="number">0</span> : hash(e.key);</span><br><span class="line">                }</span><br><span class="line">                <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> indexFor(e.hash, newCapacity);</span><br><span class="line">　　　　　　　　　 <span class="comment">//将当前entry的next链指向新的索引位置,newTable[i]有可能为空，有可能也是个entry链，如果是entry链，直接在链表头部插入。</span></span><br><span class="line">                e.next = newTable[i];</span><br><span class="line">                newTable[i] = e;</span><br><span class="line">                e = next;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>这个方法将老数组中的数据逐个链表地遍历，扔到新的扩容后的数组中，我们的数组索引位置的计算是通过 对 key 值的 hashcode 进行 hash 扰乱运算后，再通过和 length-1 进行位运算得到最终数组索引位置。 hashMap 的数组长度一定保持 2 的次幂，比如 16 的二进制表示为 10000，那么 length-1 就是 15，二进制为 01111，同理扩容后的数组长度为 32，二进制表示为 100000，length-1 为 31，二进制表示为 011111。从下图可以我们也能看到这样会保证低位全为 1，而扩容后只有一位差异，也就是多出了最左位的 1，这样在通过 h&amp;(length-1) 的时候，只要 h 对应的最左边的那一个差异位为 0，就能保证得到的新的数组索引和老数组索引一致 (大大减少了之前已经散列良好的老数组的数据位置重新调换)，个人理解。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139206201278.png"><br> 还有，数组长度保持 2 的次幂，length-1 的低位都为 1，会使得获得的数组索引 index 更加均匀，比如：<br> <img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139206294167.png"></p><p>我们看到，上面的 &amp; 运算，高位是不会对结果产生影响的（hash 函数采用各种位运算可能也是为了使得低位更加散列），我们只关注低位 bit，如果低位全部为 1，那么对于 h 低位部分来说，任何一位的变化都会对结果产生影响，也就是说，要得到 index=21 这个存储位置，h 的低位只有这一种组合。这也是数组长度设计为必须为 2 的次幂的原因。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139206391451.png"><br> 如果不是 2 的次幂，也就是低位不是全为 1 此时，要使得 index=21，h 的低位部分不再具有唯一性了，哈希冲突的几率会变的更大，同时，index 对应的这个 bit 位无论如何不会等于 1 了，而对应的那些数组位置也就被白白浪费了。 get 方法</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">V</span> <span class="title function_">get</span><span class="params">(Object key)</span> {</span><br><span class="line">    　　　　 <span class="comment">//如果key为null,则直接去table[0]处去检索即可。</span></span><br><span class="line">            <span class="keyword">if</span> (key == <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">return</span> getForNullKey();</span><br><span class="line">            <span class="type">Entry</span>&lt;K,V&gt; <span class="variable">entry</span> <span class="operator">=</span> getEntry(key);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span> == entry ? <span class="literal">null</span> : entry.getValue();</span><br><span class="line">     }</span><br></pre></td></tr></tbody></table></figure><p>get 方法通过 key 值返回对应 value，如果 key 为 null，直接去 table [0] 处检索。我们再看一下 getEntry 这个方法</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">Entry</span>&lt;K,V&gt; <span class="title function_">getEntry</span><span class="params">(Object key)</span> {</span><br><span class="line">            </span><br><span class="line">        <span class="keyword">if</span> (size == <span class="number">0</span>) {</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">//通过key的hashcode值计算hash值</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> (key == <span class="literal">null</span>) ? <span class="number">0</span> : hash(key);</span><br><span class="line">        <span class="comment">//indexFor (hash&amp;length-1) 获取最终数组索引，然后遍历链表，通过equals方法比对找出对应记录</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">Entry</span>&lt;K,V&gt; <span class="variable">e</span> <span class="operator">=</span> table[indexFor(hash, table.length)];</span><br><span class="line">             e != <span class="literal">null</span>;</span><br><span class="line">             e = e.next) {</span><br><span class="line">            Object k;</span><br><span class="line">            <span class="keyword">if</span> (e.hash == hash &amp;&amp; </span><br><span class="line">                ((k = e.key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">                <span class="keyword">return</span> e;</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>可以看出，get 方法的实现相对简单，key (hashcode)–&gt;hash–&gt;indexFor–&gt; 最终索引位置，找到对应位置 table [i]，再查看是否有链表，遍历链表，通过 key 的 equals 方法比对查找对应的记录。要注意的是，有人觉得上面在定位到数组位置之后然后遍历链表的时候，e.hash == hash 这个判断没必要，仅通过 equals 判断就可以。其实不然，试想一下，如果传入的 key 对象重写了 equals 方法却没有重写 hashCode，而恰巧此对象定位到这个数组位置，如果仅仅用 equals 判断可能是相等的，但其 hashCode 和当前对象不一致，这种情况，根据 Object 的 hashCode 的约定，不能返回当前对象，而应该返回 null，后面的例子会做出进一步解释。</p><h1 id="四、重写equals方法需同时重写hashCode方法"><a href="#四、重写equals方法需同时重写hashCode方法" class="headerlink" title="四、重写equals方法需同时重写hashCode方法"></a>四、重写 equals 方法需同时重写 hashCode 方法</h1><p>关于 HashMap 的源码分析就介绍到这儿了，最后我们再聊聊老生常谈的一个问题，各种资料上都会提到，“重写 equals 时也要同时覆盖 hashcode”，我们举个小例子来看看，如果重写了 equals 而不重写 hashcode 会发生什么样的问题</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by chengxiao on 2016/11/15.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyTest</span> {</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Person</span>{</span><br><span class="line">        <span class="type">int</span> idCard;</span><br><span class="line">        String name;</span><br><span class="line"></span><br><span class="line">        <span class="type">public</span> <span class="title function_">Person</span><span class="params">(<span class="type">int</span> idCard, String name)</span> {</span><br><span class="line">            <span class="built_in">this</span>.idCard = idCard;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">        }</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> {</span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">this</span> == o) {</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            }</span><br><span class="line">            <span class="keyword">if</span> (o == <span class="literal">null</span> || getClass() != o.getClass()){</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            }</span><br><span class="line">            <span class="type">Person</span> <span class="variable">person</span> <span class="operator">=</span> (Person) o;</span><br><span class="line">            <span class="comment">//两个对象是否等值，通过idCard来确定</span></span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">this</span>.idCard == person.idCard;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String []args)</span>{</span><br><span class="line">        <span class="type">HashMap</span>&lt;Person,String&gt; <span class="variable">map</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;Person, String&gt;();</span><br><span class="line">        <span class="type">Person</span> <span class="variable">person</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="number">1234</span>,<span class="string">"乔峰"</span>);</span><br><span class="line">        <span class="comment">//put到hashmap中去</span></span><br><span class="line">        map.put(person,<span class="string">"天龙八部"</span>);</span><br><span class="line">        <span class="comment">//get取出，从逻辑上讲应该能输出“天龙八部”</span></span><br><span class="line">        System.out.println(<span class="string">"结果:"</span>+map.get(<span class="keyword">new</span> <span class="title class_">Person</span>(<span class="number">1234</span>,<span class="string">"萧峰"</span>)));</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>实际输出结果：</p><p>结果：null</p><p>如果我们已经对 HashMap 的原理有了一定了解，这个结果就不难理解了。尽管我们在进行 get 和 put 操作的时候，使用的 key 从逻辑上讲是等值的（通过 equals 比较是相等的），但由于没有重写 hashCode 方法，所以 put 操作时，key (hashcode1)–&gt;hash–&gt;indexFor–&gt; 最终索引位置 ，而通过 key 取出 value 的时候 key (hashcode1)–&gt;hash–&gt;indexFor–&gt; 最终索引位置，由于 hashcode1 不等于 hashcode2，导致没有定位到一个数组位置而返回逻辑上错误的值 null（也有可能碰巧定位到一个数组位置，但是也会判断其 entry 的 hash 值是否相等，上面 get 方法中有提到。） 所以，在重写 equals 的方法的时候，必须注意重写 hashCode 方法，同时还要保证通过 equals 判断相等的两个对象，调用 hashCode 方法要返回同样的整数值。而如果 equals 判断不相等的两个对象，其 hashCode 可以相同（只不过会发生哈希冲突，应尽量避免）。</p><h1 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h1><p>本文描述了 HashMap 的实现原理，并结合源码做了进一步的分析，也涉及到一些源码细节设计缘由，最后简单介绍了为什么重写 equals 的时候需要重写 hashCode 方法。希望本篇文章能帮助到大家，同时也欢迎讨论指正，谢谢支持！</p><blockquote><p>作者： <span class="exturl" data-url="aHR0cDovL3d3dy5jbmJsb2dzLmNvbS9jaGVuZ3hpYW8v">dreamcatcher-cx<i class="fa fa-external-link-alt"></i></span><br>出处： <a href="http://www.cnblogs.com/chengxiao/"></a><span class="exturl" data-url="aHR0cDovL3d3dy5jbmJsb2dzLmNvbS9jaGVuZ3hpYW8v">http://www.cnblogs.com/chengxiao/<i class="fa fa-external-link-alt"></i></span><br>本文版权归作者和博客园共有，欢迎转载，但未经作者同意必须保留此段声明，且在页面明显位置给出原文链接。</p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/35205.html</id>
    <link href="https://www.bytelife.net/articles/35205.html"/>
    <published>2017-03-12T10:28:32.000Z</published>
    <summary>
      <![CDATA[<p>哈希表（hash table）也叫散列表，是一种非常重要的数据结构，应用场景及其丰富，许多缓存技术（比如 memcached）的核心其实就是在内存中维护一张大的哈希表，而 HashMap 的实现原理也常常出现在各类的面试题中，重要性可见一斑。本文会对 java 集合框架中的对应实现 HashMap 的实现原理进行讲解，然后会对 JDK7 的 HashMap 源码进行分析。</p>]]>
    </summary>
    <title>HashMap 实现原理及源码分析</title>
    <updated>2017-03-12T10:28:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="类加载" scheme="https://www.bytelife.net/tags/%E7%B1%BB%E5%8A%A0%E8%BD%BD/"/>
    <content>
      <![CDATA[<p>Java 虚拟机类加载过程中的 “加载” 阶段第一步就是 “通过一个类的全限定名来获取描述此类的二级制字节流”，这个动作由 Java 虚拟机外部实现，以便让应用程序自己决定如何去获取所需要的类，实现这个动作的模块叫做 “类加载器”。</p><span id="more"></span><h2 id="类与类加载器"><a href="#类与类加载器" class="headerlink" title="类与类加载器"></a>类与类加载器</h2><p>类加载器虽然只用于实现类的加载动作，但它在 Java 程序中的作用远不限于此。</p><blockquote><p>对于任意一个类，都需要由<strong>加载它的类加载器</strong>和这个<strong>类本身</strong>一同确立其在 Java 虚拟机中的唯一性，每一个类加载器，都拥有一个独立的类命名空间。</p></blockquote><p>换言之，比较两个类 “相等”，只有在两个类是由<strong>同一个类加载器</strong>加载的前提下才有意义，否则，即使这两个类来源于同一个 Class 文件，被同一个虚拟机加载，只要加载它们的类加载器不同，那这两个类就必定不相等。</p><h2 id="Java中的类加载器"><a href="#Java中的类加载器" class="headerlink" title="Java中的类加载器"></a>Java 中的类加载器</h2><p>Java 虚拟机只有两种不同的类加载器：</p><ul><li><strong>启动类加载器（Bootstrap ClassLoader）</strong>：使用 C++ 语言（HotSpot）实现，是虚拟机的一部分，该类加载器实例无法被用户获取；</li><li><strong>所有其它的类加载器</strong>：均由 Java 语言实现，独立于虚拟机外部，并且全部继承自抽象类 java.lang.ClassLoader；</li></ul><p>从 Java 程序员的角度，类加载器还可以继续细化，绝大部分 Java 程序都会使用到以下 3 种类加载器。</p><ul><li><strong>启动类加载器 （Bootstrap ClassLoader）</strong>：这个类加载器负责将存放在<code>&lt;JAVA_HOME&gt;\lib</code>目录中的，或者被<code>-Xbootclasspath</code>参数所指定的目录中的，并且是虚拟机识别的（仅按照文件名识别，例如 rt.jar）类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用，用户在编写自定义加载器时，如果需要把加载请求委托给引导类加载器，直接使用 null 代替即可。</li><li><strong>扩展类加载器（Extension ClassLoader）</strong>：这个加载器由<code>sun.misc.Launcher$ExtClassLoader</code>实现，他负责加载<code>&lt;JAVA_HOME&gt;\lib\ext</code>目录中的，或者被<code>java.ext.dirs</code>系统变量所指定的路径中的所有类库，开发者可以直接使用扩展类加载器。</li><li><strong>应用程序类加载器（Application ClassLoader）</strong>：这个类加载器由<code>sun.misc.Launcher$AppClassLoader</code>实现。该类是 ClassLoader 中的<code>getSystemClassLoader()</code>方法的返回值，因此也称作 “<strong>系统类加载器</strong>”。它负责用户类路径（ClassPath）上所指定的类库，开发者可以直接使用这个类加载器，如果应用程序中没有定义过自己的类加载器，一般情况下这个就是程序的默认类加载器。</li></ul><p>应用程序一般由这 3 中类加载器相互配合加载，如果有必要，还可以加入自己定义的类加载器，集成。</p><h3 id="自定义类加载器"><a href="#自定义类加载器" class="headerlink" title="自定义类加载器"></a>自定义类加载器</h3><p>自定义类加载器可以直接或间接继承自类<code>java.lang.ClassLoader</code>。在<code>java.lang.ClassLoader</code>类的常用方法中，一般来说，自己开发的类加载器只需要覆写 <code>findClass(String name)</code>方法即可。 java.lang.ClassLoader 类的方法 loadClass () 封装了代理模式的实现。</p><ul><li>该方法会首先调用 findLoadedClass () 方法来检查该类是否已经被加载过；</li><li>如果没有加载过的话，会调用父类加载器的 loadClass () 方法来尝试加载该类；</li><li>如果父类加载器无法加载该类的话，就调用 findClass () 方法来查找该类。</li></ul><p>因此，为了保证类加载器都正确实现代理模式，在开发自己的类加载器时，最好不要覆写 loadClass () 方法，而是覆写 findClass () 方法。 下面是一个文件系统类加载器的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FileSystemClassLoader</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span> { </span><br><span class="line">    <span class="keyword">private</span> String rootDir; </span><br><span class="line">    <span class="type">public</span> <span class="title function_">FileSystemClassLoader</span><span class="params">(String rootDir)</span> { </span><br><span class="line">        <span class="built_in">this</span>.rootDir = rootDir; </span><br><span class="line">    } </span><br><span class="line">    <span class="keyword">protected</span> <span class="type">Class</span>&lt;?&gt; <span class="title function_">findClass</span><span class="params">(String name)</span> <span class="keyword">throws</span> ClassNotFoundException { </span><br><span class="line">        <span class="type">byte</span>[] <span class="variable">classData</span> <span class="operator">=</span> getClassData(name); </span><br><span class="line">        <span class="keyword">if</span> (classData == <span class="literal">null</span>) { </span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ClassNotFoundException</span>(); </span><br><span class="line">        } </span><br><span class="line">        <span class="keyword">else</span> { </span><br><span class="line">            <span class="keyword">return</span> defineClass(name, classData, <span class="number">0</span>, classData.length); </span><br><span class="line">        } </span><br><span class="line">    } </span><br><span class="line">    <span class="keyword">private</span> <span class="type">byte</span>[] <span class="title function_">getClassData</span><span class="params">(String className)</span> { </span><br><span class="line">        <span class="type">String</span> <span class="variable">path</span> <span class="operator">=</span> classNameToPath(className); </span><br><span class="line">        <span class="keyword">try</span> { </span><br><span class="line">            <span class="type">InputStream</span> <span class="variable">ins</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(path); </span><br><span class="line">            <span class="type">ByteArrayOutputStream</span> <span class="variable">baos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayOutputStream</span>(); </span><br><span class="line">            <span class="type">int</span> <span class="variable">bufferSize</span> <span class="operator">=</span> <span class="number">4096</span>; </span><br><span class="line">            <span class="type">byte</span>[] <span class="variable">buffer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">byte</span>[bufferSize]; </span><br><span class="line">            <span class="type">int</span> <span class="variable">bytesNumRead</span> <span class="operator">=</span> <span class="number">0</span>; </span><br><span class="line">            <span class="keyword">while</span> ((bytesNumRead = ins.read(buffer)) != -<span class="number">1</span>) { </span><br><span class="line">                baos.write(buffer, <span class="number">0</span>, bytesNumRead); </span><br><span class="line">            } </span><br><span class="line">            <span class="keyword">return</span> baos.toByteArray(); </span><br><span class="line">        } <span class="keyword">catch</span> (IOException e) { </span><br><span class="line">            e.printStackTrace(); </span><br><span class="line">        } </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>; </span><br><span class="line">    } </span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="title function_">classNameToPath</span><span class="params">(String className)</span> { </span><br><span class="line">        <span class="keyword">return</span> rootDir + File.separatorChar </span><br><span class="line">                + className.replace(<span class="string">'.'</span>, File.separatorChar) + <span class="string">".class"</span>; </span><br><span class="line">    } </span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><h2 id="双亲委派模型"><a href="#双亲委派模型" class="headerlink" title="双亲委派模型"></a>双亲委派模型</h2><p>下图展示的类加载器之间的层次关系，称为类加载器的 “双亲委派模型”。双亲委派模型要求除了顶层的启动类加载器外，其它类加载器必须有自己的父加载器。</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139184079219.png"></p><blockquote><p>这里的类加载器之间的父子关系一般不通过继承（Inheritance）来实现，而是通过组合（Composition）关系来服用父加载器代码。 双亲委派模型并不是一个强制性约束，而是 Java 设计者推荐给开发者的一种类加载实现方式。</p></blockquote><h3 id="双亲委派模型的工作过程"><a href="#双亲委派模型的工作过程" class="headerlink" title="双亲委派模型的工作过程"></a>双亲委派模型的工作过程</h3><ul><li>如果一个类加载器收到了类加载的请求，它不会先自己尝试处理这个请求，而是委派给它的父类加载器，所有的请求最终都会传送到顶层的启动类加载器</li><li>只有当父类反馈自己无法完成该请求（它的搜索范围中没有找到所需的类，即抛出 ClassNotFoundException）时，子加载器才会尝试自己加载。</li></ul><h3 id="为什么使用双亲委派模型？"><a href="#为什么使用双亲委派模型？" class="headerlink" title="为什么使用双亲委派模型？"></a>为什么使用双亲委派模型？</h3><p>使用双亲委派模型可以使得 Java 类随着它的类加载器一起具备了一种<strong>带有优先级的层次关系</strong>。 例如类<code>java.lang.Object</code>，它存放在 rt.jar 中，无论哪一个类加载器要加载这个类，最终都是委派给处于模型最顶端的启动类加载器加载，因此 Objcet 类在程序中的各种类加载器环境中都是同一个类。 如果没有使用双亲委派模型，那么如果用户自己写了一个称为<code>“java.lang.Object”</code>的类，并放在程序的 classpath 中，那么系统将产生多个不同的 Object 类，可想而知，程序将一片混乱。</p><h3 id="双亲委派模型的实现"><a href="#双亲委派模型的实现" class="headerlink" title="双亲委派模型的实现"></a>双亲委派模型的实现</h3><p>双亲委派模型的实现非常简单，几乎所有的代码仅在<code>loadClass()</code>方法中实现，下面是一个简单的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//双亲委派模型的实现源码  </span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">synchronized</span> <span class="type">Class</span>&lt;?&gt; <span class="title function_">loadClass</span><span class="params">(String name, Boolean resolve)</span>  <span class="keyword">throws</span> ClassNotFoundException{  </span><br><span class="line">    <span class="comment">// 1、首先检查请求的类是否已经被加载过  </span></span><br><span class="line">    <span class="type">Class</span> <span class="variable">c</span> <span class="operator">=</span> findLoadedClass(name);  </span><br><span class="line">    <span class="keyword">if</span>(c == <span class="literal">null</span>){  </span><br><span class="line">        <span class="keyword">try</span>{  </span><br><span class="line">            <span class="keyword">if</span>(parent != <span class="literal">null</span>){    <span class="comment">// 2、如果没有则调用父加载器的loadClass()方法  </span></span><br><span class="line">                c = parent.loadClass(name, <span class="literal">false</span>);  </span><br><span class="line"></span><br><span class="line">            <span class="comment">// 3、如果父加载器为空则默认使用启动类加载器作为父加载器  </span></span><br><span class="line">            } <span class="keyword">else</span>{  </span><br><span class="line">                c = findBootstrapClassOrNull(name);  </span><br><span class="line">            }  </span><br><span class="line">        }<span class="keyword">catch</span>(ClassNotFoundException e){  </span><br><span class="line">            <span class="comment">// 4、如果父类加载器加载失败，则先抛出ClassNotFoundException  </span></span><br><span class="line">        }  </span><br><span class="line">        <span class="comment">// 5、然后再调用自己的findClass()方法进行加载  </span></span><br><span class="line">        <span class="keyword">if</span>(c == <span class="literal">null</span>){  </span><br><span class="line">            c = findClass(name);  </span><br><span class="line">        }  </span><br><span class="line">    }  </span><br><span class="line">    <span class="keyword">if</span>(resolve){  </span><br><span class="line">        resolveClass(c);  </span><br><span class="line">    }  </span><br><span class="line">    <span class="keyword">return</span> c;  </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.bytelife.net/articles/44598.html</id>
    <link href="https://www.bytelife.net/articles/44598.html"/>
    <published>2017-03-12T08:34:51.000Z</published>
    <summary>
      <![CDATA[<p>Java 虚拟机类加载过程中的 “加载” 阶段第一步就是 “通过一个类的全限定名来获取描述此类的二级制字节流”，这个动作由 Java 虚拟机外部实现，以便让应用程序自己决定如何去获取所需要的类，实现这个动作的模块叫做 “类加载器”。</p>]]>
    </summary>
    <title>Java 类加载机制 - 类加载器（ClassLoader）与双亲委派模型</title>
    <updated>2017-03-12T08:34:51.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="类加载" scheme="https://www.bytelife.net/tags/%E7%B1%BB%E5%8A%A0%E8%BD%BD/"/>
    <content>
      <![CDATA[<h2 id="类加载的时机"><a href="#类加载的时机" class="headerlink" title="类加载的时机"></a>类加载的时机</h2><p>类从被加载到虚拟机开始，到卸载为止，生命周期如图所示：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139181881678.png"><br>其中，加载、验证、准备、初始化、卸载这五个阶段的顺序是确定的，类的加载过程必须按照这种顺序按部就班的开始（开始不代表完成，通常为交叉混合运行），而解析阶段不一定：它在某些情况下可以在初始化阶段之后再开始。 </p><span id="more"></span><p>Java 虚拟机规定有且只有 5 种情况必须立即对类进行 “初始化”：</p><ol><li>遇到<code>new</code>、<code>getstatic</code>、<code>putstatic</code>或<code>invokestatic</code>这 4 条字节码指令（new 实例化对象、读取或设置静态字段、调用静态方法）；</li><li>使用<code>java.lang.reflect</code>包的方法进行<strong>反射</strong>调用；</li><li>初始化一个类时，其父类如未初始化，则需要先初始化其<strong>父类</strong>；</li><li>虚拟机启动时，用户指定的要执行的<strong>主类</strong>（包含 main 方法的那个类）；</li><li>使用 JDK1.7 的动态语言支持时，如果一个<code>java.lang.invoke.MethodHandle</code>实例最后的解析结果<code>REF\_getStatic</code>、<code>REF\_putStatic</code>、<code>REF_invokeStatic</code>的方法句柄，并且句柄所对应的类没有进行初始化；</li></ol><h2 id="类加载的过程"><a href="#类加载的过程" class="headerlink" title="类加载的过程"></a>类加载的过程</h2><h3 id="加载"><a href="#加载" class="headerlink" title="加载"></a>加载</h3><p>“加载” 阶段，虚拟机需要完成下面 3 件事情：</p><ol><li>通过一个类的全限定名来获取定义此类的二进制字节流</li><li>将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构</li><li>在内存中生成一个代表该类的 java.lang.Class 对象，作为方法区这个类的各种数据的访问入口</li></ol><blockquote><p>注意：加载阶段尚未完成，连接阶段可能已经开始。</p></blockquote><h3 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h3><p>验证是连接阶段的第一步，目的是确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求，并且不会威胁虚拟机自身的安全。 验证阶段大致上会完成下面 4 个阶段的检验动作：文件格式验证、元数据验证、字节码验证、符号引用验证。</p><ol><li>文件格式验证：验证字节流是否符合 Class 文件格式的规范，并且能被当前版本的虚拟机处理，通过验证后字节流进入方法区；</li><li>元数据验证：对字节码描述的信息进行语义分析，保证其描述的信息符合 Java 语言规范的要求；</li><li>字节码验证：最复杂，通过数据流和控制流分析确定程序语义是否合法、符合逻辑；</li><li>符号引用验证：该验证发生在虚拟机将符号引用转化为直接引用时，该动作发生在解析阶段。可以看做是对类自身以外的信息进行匹配性校验；</li></ol><h3 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h3><p>正式为类变量（static 修饰的变量）分配内存并设置初始值的阶段，这些变量所使用的内存将在方法区中进行分配。 通常情况下是对应类型的 “零值”，真正的用户定义的初值要在初始化阶段完成。例如： <code>public static int value = 123;</code> 变量 value 在准备阶段过后的初始值将是 0 而不是 123，因为这是并未执行任何 java 方法，而是把 value 赋值为 123 的 putstatic 指令是程序编译后，存放于类构造器<code>&lt;clinit&gt;()</code>方法中，因此该动作在初始化阶段才会执行。</p><blockquote><p>注意：这里是指 “通常情况”，如果字段的字段属性表中存在 ConstantValue 属性，那在准备阶段变量就将被赋予 ConstantValue 属性所指的值，例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> <span class="number">123</span>;</span><br></pre></td></tr></tbody></table></figure><p>编译的时候 javac 会为 value 生成 ConstantValue 属性，在准备阶段虚拟机会根据 ConstantValue 的设置将 value 赋值为 123.</p></blockquote><h3 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h3><p>解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。</p><ul><li>符号引用：是一组符号来描述所引用的目标，其字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中；</li><li>直接引用：可以是直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。</li></ul><h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p>类初始化阶段是类加载过程的最后一步，在准备阶段变量已经被附过一次系统要求的初始值（通常为 “零值”）, 而在初始化阶段，则根据程序员通过程序制定的主观计划去初始化类变量和其它资源。换言之，初始化阶段就是<strong>执行类构造器<clinit> () 方法</clinit></strong>的过程。</p><ul><li><code>&lt;clinit&gt;()</code>方法是由编译器自动收集类中的所有变量的赋值动作和静态语句块（<code>static{}</code>块）中的语句合并产生的，编译器收集的顺序是语句在源文件中出现的顺序决定的，静态语句块中只能访问到定义在静态语句块之前的变量，定义在它之后的变量在之前可以访问但不能赋值；</li><li><code>&lt;clinit&gt;()</code>方法与类构造函数（或者说实例构造器<code>&lt;init&gt;()</code>方法）不同，它不需要显示调用父类构造器，虚拟机会保证在子类的<code>&lt;clinit&gt;()</code>方法执行之前父类的已经执行完毕，因此虚拟机中第一个被执行的<code>&lt;clinit&gt;()</code>方法一定是<code>java.lang.Object</code>；</li><li>由于父类的<code>&lt;clinit&gt;()</code>方法先执行，意味着父类中定义的静态语句块要优先于子类的变量赋值操作；</li><li><code>&lt;clinit&gt;()</code>方法对于类或者接口不是必须的，如果一个类没有静态语句块和变量的赋值操作，那编译器就可以不为这个类生成<code>&lt;clinit&gt;()</code>方法；</li><li>接口中不能使用静态语句块，但仍然有变量初始化的赋值操作，因此接口也会生成<code>&lt;clinit&gt;()</code>方法。但接口不需要先执行父接口的<code>&lt;clinti&gt;()</code>方法，只有当父接口中定义的变量使用时，父接口才会初始化。接口的实现类初始化时一样不会执行接口的<code>&lt;clinit&gt;()</code>方法；</li><li>虚拟机会保证一个类的<code>&lt;clinit&gt;()</code>方法在多线程环境中被正确的加锁、同步。如果多线程同时执行某一个类的<code>&lt;clinit&gt;()</code>方法，那么只会有一个线程去执行，其它线程将会阻塞等待，直到活动线程执行完毕，需要注意的是，当活动线程执行完毕后，其它线程唤醒之后并不会再次进入<code>&lt;clinit&gt;()</code>方法。同一个类加载器下，一个类型只会被初始化一次。</li></ul>]]>
    </content>
    <id>https://www.bytelife.net/articles/49511.html</id>
    <link href="https://www.bytelife.net/articles/49511.html"/>
    <published>2017-03-12T05:33:28.000Z</published>
    <summary>
      <![CDATA[<h2 id="类加载的时机"><a href="#类加载的时机" class="headerlink" title="类加载的时机"></a>类加载的时机</h2><p>类从被加载到虚拟机开始，到卸载为止，生命周期如图所示：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139181881678.png"><br>其中，加载、验证、准备、初始化、卸载这五个阶段的顺序是确定的，类的加载过程必须按照这种顺序按部就班的开始（开始不代表完成，通常为交叉混合运行），而解析阶段不一定：它在某些情况下可以在初始化阶段之后再开始。 </p>]]>
    </summary>
    <title>Java 类加载机制 - 类加载的时机和过程</title>
    <updated>2017-03-12T05:33:28.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="JVM" scheme="https://www.bytelife.net/tags/JVM/"/>
    <content>
      <![CDATA[<h3 id="判断对象是否可回收的常见方法"><a href="#判断对象是否可回收的常见方法" class="headerlink" title="判断对象是否可回收的常见方法"></a>判断对象是否可回收的常见方法</h3><h4 id="引用计数算法（Reference-Counting）"><a href="#引用计数算法（Reference-Counting）" class="headerlink" title="引用计数算法（Reference Counting）"></a>引用计数算法（Reference Counting）</h4><p>给对象中添加一个引用计数器，每当有一个地方引用时，计数器加 1；当引用失效时，计数器减 1；任何时刻计数器为 0 的对象就是不可能再被使用的。</p><ul><li>优点：实现简单，判定效率高；</li><li>缺点：很难解决对象之间的循环引用问题；</li></ul><p>JVM 并不使用这种算法。</p><span id="more"></span><h4 id="可达性分析算法（Reachability-Analysis）"><a href="#可达性分析算法（Reachability-Analysis）" class="headerlink" title="可达性分析算法（Reachability Analysis）"></a>可达性分析算法（Reachability Analysis）</h4><p>通过一些列称为 “GC Roots” 的对象作为起始点，从这些节点开始向下搜索，搜索所走过的路径叫做引用链，当一个对象到 GC Roots 没有任何引用链与之相连时，则证明该对象是不可用的。 </p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139187407339.png"></p><p>在 Java 语言中，可作为 GC Roots 的对象包括：</p><ul><li>虚拟机栈（栈帧中的本地变量表）中引用的对象；</li><li>方法区中静态属性引用的对象；</li><li>方法区中常量引用的对象；</li><li>本地方法栈中 JNI（Native 方法）引用的对象；</li></ul><h4 id="二次标记"><a href="#二次标记" class="headerlink" title="二次标记"></a>二次标记</h4><p>如果一个对象真正宣告 “死亡”，至少要经过两次标记过程：</p><ul><li><p>如果一个对象经过可达性分析算法判定为可回收对象，则它将会被第一次标记并进行一次筛选，筛选的条件是判断该对象是否有必要执行 finalize () 方法。当对象没有覆盖 finalize () 方法或者已经执行过一次 finalize () 方法，则虚拟将将判定为 “没有必要执行”。如果判定为 “有必要执行 finalize () 方法”，那么这个对象将被置入 F-Queue 队列，并在稍后由一个虚拟机自动建立的、低优先级的 Finalizer 线程区执行（仅仅是触发）它。</p></li><li><p>finalize () 方法是逃脱死亡的最后机会，稍后 GC 将堆 F-Queue 队列进行再一次的小规模标记，如果对象在 finalize () 方法中重新与引用链上的任何一个对象建立关联，那么在第二次标记的时候将会被移除 “即将回收” 的集合；如果对象这时候还没有逃脱，那么基本上对象宣告 “死亡” 并将被回收。</p></li></ul><h3 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h3><h4 id="标记-清除算法（Mark-Sweep）"><a href="#标记-清除算法（Mark-Sweep）" class="headerlink" title="标记-清除算法（Mark-Sweep）"></a>标记 - 清除算法（Mark-Sweep）</h4><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139187593904.png"></p><p>算法分为 “标记” 和 “清除” 两个阶段：首先标记出所有需要回收的对象，在标记完成后统一回收掉所有被标记的对象。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139187670216.png"></p><p>主要缺点：</p><ul><li>效率问题，标记和清除过程的<strong>效率都不高</strong>；</li><li>空间问题，标记清除之后会产生大量<strong>不连续的内存碎片</strong>，空间碎片太多可能会导致，当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作；</li></ul><h4 id="复制算法（Copying）"><a href="#复制算法（Copying）" class="headerlink" title="复制算法（Copying）"></a>复制算法（Copying）</h4><p>它将可用内存按容量划分为大小相等的两块，每次只使用其中的一块。当这一块的内存用完了，就将还存活着的对象复制到另外一块上面，然后再把已使用过的内存空间一次清理掉。</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139187775430.png"></p><p>这样使得每次都是对其中的一块进行内存回收，内存分配时也就不用考虑内存碎片等复杂情况，只要移动堆顶指针，按顺序分配内存即可，实现简单，运行高效。只是这种算法的代价是将内存缩小为原来的一半，未免太高了一点。</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139187837665.png"></p><p>现在的商业虚拟机都采用这种收集算法来<strong>回收新生代</strong>，新生代中的对象 98% 是朝生夕死的，所以并不需要按照 1∶1 的比例来划分内存空间，而是将内存分为<strong>一块较大的 Eden 空间</strong>和<strong>两块较小的 Survivor 空间</strong>，每次使用 Eden 和其中的一块 Survivor。</p><p>当回收时，将 Eden 和 Survivor 中还存活着的对象一次性地拷贝到另外一块 Survivor 空间上，最后清理掉 Eden 和刚才用过的 Survivor 的空间。HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例是 8∶1，也就是每次新生代中可用内存空间为整个新生代容量的 90%（80%+10%），只有 10% 的内存是会被 “浪费” 的。</p><p>当然，98% 的对象可回收只是一般场景下的数据，我们没有办法保证每次回收都只有不多于 10% 的对象存活，当 Survivor 空间不够用时，需要依赖其他内存（这里指老年代）进行分配担保（Handle Promotion）。如果另外一块 Survivor 空间没有足够的空间存放上一次新生代收集下来的存活对象，这些对象将直接通过分配担保机制进入老年代。</p><h4 id="标记整理算法（Mark-Compact）"><a href="#标记整理算法（Mark-Compact）" class="headerlink" title="标记整理算法（Mark-Compact）"></a>标记整理算法（Mark-Compact）</h4><p>复制收集算法在对象存活率较高时就要执行较多的复制操作，效率将会变低。更关键的是，如果不想浪费 50% 的空间，就需要有额外的空间进行分配担保，以应对被使用的内存中所有对象都 100% 存活的极端情况，所以在老年代一般不能直接选用这种算法。</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139187988140.png"></p><p>根据<strong>老年代</strong>的特点，有人提出了另外一种 “<strong>标记 - 整理</strong>”（Mark-Compact）算法，标记过程仍然与 “标记 - 清除” 算法一样，但后续步骤不是直接对可回收对象进行清理，而是让所有存活的对象都向一端移动，然后直接清理掉端边界以外的内存。</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139188054228.png"></p><h4 id="分代收集算法（Generational-Collection）"><a href="#分代收集算法（Generational-Collection）" class="headerlink" title="分代收集算法（Generational Collection）"></a>分代收集算法（Generational Collection）</h4><p>当前商业虚拟机的垃圾收集都采用 “分代收集”（Generational Collection）算法，这种算法并没有什么新的思想，只是根据对象的存活周期的不同将内存划分为几块。 一般是把 Java 堆分为<strong>新生代</strong>和<strong>老年代</strong>，这样就可以根据各个年代的特点采用最适当的收集算法。</p><ul><li><p>新生代中，每次垃圾收集时都发现有大批对象死去，只有少量存活，那就选用复制算法，只需要付出少量存活对象的复制成本就可以完成收集。</p></li><li><p>老年代中，因为对象存活率高、没有额外空间对它进行分配担保，就必须使用 “标记 - 清理” 或 “标记 - 整理” 算法来进行回收。</p></li></ul><h3 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h3><p>Java 虚拟机规范中对垃圾收集器应该如何实现并没有任何规定，因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别，并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。</p><ul><li>**Serial 收集器（复制算法)：** 新生代单线程收集器，标记和清理都是单线程，优点是简单高效。</li><li>**Serial Old 收集器 (标记 - 整理算法)：** 老年代单线程收集器，Serial 收集器的老年代版本。</li><li>**ParNew 收集器 (停止 - 复制算法)：** 新生代收集器，可以认为是 Serial 收集器的多线程版本, 在多核 CPU 环境下有着比 Serial 更好的表现。</li><li>**Parallel Scavenge 收集器 (停止 - 复制算法)：** 并行收集器，追求高吞吐量，高效利用 CPU。吞吐量一般为 99%， 吞吐量 = 用户线程时间/(用户线程时间 + GC 线程时间)。适合后台应用等对交互相应要求不高的场景。</li><li>**Parallel Old 收集器 (停止 - 复制算法)：**Parallel Scavenge 收集器的老年代版本，并行收集器，吞吐量优先</li><li>*<em>CMS (Concurrent Mark Sweep) 收集器（标记 - 清理算法）</em>***：** 高并发、低停顿，追求最短 GC 回收停顿时间，cpu 占用比较高，响应时间快，停顿时间短，多核 cpu 追求高响应时间的选择</li></ul><p>&nbsp;<img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139188175513.png"></p><h4 id="1-Serial-Garbage-Collector"><a href="#1-Serial-Garbage-Collector" class="headerlink" title="1. Serial Garbage Collector"></a>1. Serial Garbage Collector</h4><p>Serial Garbage Collector 通过暂停所有应用的线程来工作。它是为单线程工作环境而设计的。它中使用一个线程来进行垃圾回收。这种暂停应用线程来进行垃圾回收的方式可能不太适应服务器环境。它最适合简单的命令行程序。通过 -XX:+UseSerialGC 参数来选用 Serial Garbage Collector。</p><h4 id="2-Parallel-Garbage-Collector"><a href="#2-Parallel-Garbage-Collector" class="headerlink" title="2. Parallel Garbage Collector"></a>2. Parallel Garbage Collector</h4><p>Parallel Garbage Collector 也被称为吞吐量收集器（throughput collector）。它是 Java 虚拟机的默认垃圾收集器。与 Serial Garbage Collector 不同，Parallel Garbage Collector 使用多个线程进行垃圾回收。与 Serial Garbage Collector 相似的地方时，它也是暂停所有的应用线程来进行垃圾回收。</p><h4 id="3-CMS-Garbage-Collector"><a href="#3-CMS-Garbage-Collector" class="headerlink" title="3. CMS Garbage Collector"></a>3. CMS Garbage Collector</h4><p>Concurrent Mark Sweep (CMS) Garbage Collector 使用多个线程来扫描堆内存来标记需要回收的实例，然后再清除被标记的实例。CMS Garbage Collector 只有在如下两种情景才会暂停所有的应用线程：</p><ul><li>当标记永久代内存空间中的对象时；</li><li>当进行垃圾回收时，堆内存同步发生了一些变化。</li></ul><p>相比 Parallel Garbage Collector，CMS Garbage Collector 使用更多的 CPU 资源来确保应用有一个更好的吞吐量。如果分配更多的 CPU 资源可以获得更好的性能，那么 CMS Garbage Collector 是一个更好的选择。 通过 XX:+USeParNewGC 参数来选用 CMS Garbage Collector。</p><h4 id="4-G1-Garbage-Collector"><a href="#4-G1-Garbage-Collector" class="headerlink" title="4. G1 Garbage Collector"></a>4. G1 Garbage Collector</h4><p>G1 Garbage Collector 用于大的堆内存区域。它将堆内存分割成多个独立区域（Region），然后并发地对它们进行垃圾回收。在释放内存后，G1 还可以压缩空闲的堆内存。但是，CMS Garbage Collector 是通过 “Stop The World (STW)” 来进行内存压缩的。G1 优先收集可回收更多内存的区域。Java 8 的改进：在用 G1 Garbage Collector 时，可以开启 -XX:+UseStringDeduplication 参数。它通过将重复的字符串移动到同一个 char 数组中来优化堆内存的使用。该选项在 Java 8u20 时引用进来。 通过 –XX:+UseG1GC 参数来选用 G1 Garbage Collector。</p><h3 id="Java虚拟机中的垃圾回收选项配置"><a href="#Java虚拟机中的垃圾回收选项配置" class="headerlink" title="Java虚拟机中的垃圾回收选项配置"></a>Java 虚拟机中的垃圾回收选项配置</h3><p>下面是与 Java 收集器相关的 Java 虚拟机选项。</p><h4 id="垃圾收集器选择"><a href="#垃圾收集器选择" class="headerlink" title="垃圾收集器选择"></a>垃圾收集器选择</h4><table><thead><tr><th><strong>Option</strong></th><th><strong>Description</strong></th></tr></thead><tbody><tr><td>-XX:+UseSerialGC</td><td>Serial Garbage Collector</td></tr><tr><td>-XX:+UseParallelGC</td><td>Parallel Garbage Collector</td></tr><tr><td>-XX:+UseConcMarkSweepGC</td><td>CMS Garbage Collector</td></tr><tr><td>-XX:ParallelCMSThreads=</td><td>CMS Collector – 使用的线程数</td></tr><tr><td>-XX:+UseG1GC</td><td>G1 Gargbage Collector</td></tr></tbody></table><h4 id="垃圾回收优化选项"><a href="#垃圾回收优化选项" class="headerlink" title="垃圾回收优化选项"></a>垃圾回收优化选项</h4><table><thead><tr><th><strong>Option</strong></th><th><strong>Description</strong></th></tr></thead><tbody><tr><td>-Xms</td><td>堆内存初始化尺寸</td></tr><tr><td>-Xmx</td><td>堆内存最大尺寸</td></tr><tr><td>-Xmn</td><td>新生代（Young Generation）的尺寸</td></tr><tr><td>-XX:PermSize</td><td>永久代（Permanent Generation）初始化尺寸</td></tr><tr><td>-XX:MaxPermSize</td><td>永久代（Permanent Generation）最大尺寸</td></tr></tbody></table>]]>
    </content>
    <id>https://www.bytelife.net/articles/46172.html</id>
    <link href="https://www.bytelife.net/articles/46172.html"/>
    <published>2017-03-11T12:38:18.000Z</published>
    <summary>
      <![CDATA[<h3 id="判断对象是否可回收的常见方法"><a href="#判断对象是否可回收的常见方法" class="headerlink" title="判断对象是否可回收的常见方法"></a>判断对象是否可回收的常见方法</h3><h4 id="引用计数算法（Reference-Counting）"><a href="#引用计数算法（Reference-Counting）" class="headerlink" title="引用计数算法（Reference Counting）"></a>引用计数算法（Reference Counting）</h4><p>给对象中添加一个引用计数器，每当有一个地方引用时，计数器加 1；当引用失效时，计数器减 1；任何时刻计数器为 0 的对象就是不可能再被使用的。</p>
<ul>
<li>优点：实现简单，判定效率高；</li>
<li>缺点：很难解决对象之间的循环引用问题；</li>
</ul>
<p>JVM 并不使用这种算法。</p>]]>
    </summary>
    <title>Java 垃圾回收机制（GC）</title>
    <updated>2017-03-11T12:38:18.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="JVM" scheme="https://www.bytelife.net/tags/JVM/"/>
    <content>
      <![CDATA[<h3 id="verbose"><a href="#verbose" class="headerlink" title="-verbose"></a>-verbose</h3><p>java -verbose [:class|gc|jni] 在输出设备上显示虚拟机运行信息。</p><ul><li>-verbose:class：监视输出运行期间的类加载情况</li><li>-verbose:gc：监视输出运行期间的内存回收 GC 情况</li><li>-verbose:jni：监视输出 native 方法调用的相关情况，一般用于诊断 jni 调用错误信息。</li></ul><span id="more"></span><h3 id="堆设置"><a href="#堆设置" class="headerlink" title="堆设置"></a>堆设置</h3><ul><li>**-Xms:** 初始堆大小</li><li>**-Xmx:** 最大堆大小</li><li>**-XX:NewSize=n:** 设置年轻代大小</li><li>**-XX:NewRatio=n:** 设置年轻代和年老代的比值。如: 为 3，表示年轻代与年老代比值为 1：3，年轻代占整个年轻代年老代和的 1/4</li><li>**-XX:SurvivorRatio=n:** 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。如：3，表示 Eden：Survivor=3：2，一个 Survivor 区占整个年轻代的 1/5</li><li>**-XX:MaxPermSize=n:** 设置持久代大小</li></ul><h3 id="收集器设置"><a href="#收集器设置" class="headerlink" title="收集器设置"></a>收集器设置</h3><ul><li>**-XX:+UseSerialGC:** 设置串行收集器</li><li>**-XX:+UseParallelGC:** 设置并行收集器</li><li>**-XX:+UseParalledlOldGC:** 设置并行年老代收集器</li><li>**-XX:+UseConcMarkSweepGC:** 设置并发收集器</li></ul><h3 id="垃圾回收统计信息"><a href="#垃圾回收统计信息" class="headerlink" title="垃圾回收统计信息"></a>垃圾回收统计信息</h3><ul><li><strong>-XX:+PrintGC</strong></li><li><strong>-XX:+PrintGCDetails</strong></li><li><strong>-XX:+PrintGCTimeStamps</strong></li><li><strong>-Xloggc:filename</strong></li></ul><h3 id="并行收集器设置"><a href="#并行收集器设置" class="headerlink" title="并行收集器设置"></a>并行收集器设置</h3><ul><li>**-XX:ParallelGCThreads=n:** 设置并行收集器收集时使用的 CPU 数。并行收集线程数。</li><li>**-XX:MaxGCPauseMillis=n:** 设置并行收集最大暂停时间</li><li>**-XX:GCTimeRatio=n:** 设置垃圾回收时间占程序运行时间的百分比。公式为 1/(1+n)</li></ul><h3 id="并发收集器设置"><a href="#并发收集器设置" class="headerlink" title="并发收集器设置"></a>并发收集器设置</h3><ul><li>**-XX:+CMSIncrementalMode:** 设置为增量模式。适用于单 CPU 情况。</li><li>**-XX:ParallelGCThreads=n:** 设置并发收集器年轻代收集方式为并行收集时，使用的 CPU 数。并行收集线程数。</li></ul>]]>
    </content>
    <id>https://www.bytelife.net/articles/23756.html</id>
    <link href="https://www.bytelife.net/articles/23756.html"/>
    <published>2017-03-11T10:20:57.000Z</published>
    <summary>
      <![CDATA[<h3 id="verbose"><a href="#verbose" class="headerlink" title="-verbose"></a>-verbose</h3><p>java -verbose [:class|gc|jni] 在输出设备上显示虚拟机运行信息。</p>
<ul>
<li>-verbose:class：监视输出运行期间的类加载情况</li>
<li>-verbose:gc：监视输出运行期间的内存回收 GC 情况</li>
<li>-verbose:jni：监视输出 native 方法调用的相关情况，一般用于诊断 jni 调用错误信息。</li>
</ul>]]>
    </summary>
    <title>常用 JVM 启动参数</title>
    <updated>2017-03-11T10:20:57.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="JVM" scheme="https://www.bytelife.net/tags/JVM/"/>
    <content>
      <![CDATA[<p>Java 虚拟机内存规范所管理的内存包括如下运行时数据区域：程序计数器、虚拟机栈、本地方法栈、Java 堆、方法区等。</p><span id="more"></span><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139204181232.png"></p><h3 id="程序计数器（Program-Counter-Register）"><a href="#程序计数器（Program-Counter-Register）" class="headerlink" title="程序计数器（Program Counter Register）"></a>程序计数器（Program Counter Register）</h3><p>可以看做是当前线程所执行的<strong>字节码行号指示器</strong>，每条线程都有一个独立的程序计数器（线程私有），此内存区是 Java 虚拟机规范中唯一没有任何 OutOfMemoryError 情况的区域。</p><h3 id="Java虚拟机栈（Java-Virtual-Machine-Stack）"><a href="#Java虚拟机栈（Java-Virtual-Machine-Stack）" class="headerlink" title="Java虚拟机栈（Java Virtual Machine Stack）"></a>Java 虚拟机栈（Java Virtual Machine Stack）</h3><p>线程私有，描述 Java 方法执行的内存模型：每个方法执行时，都会创建一个栈帧用于存放<strong>局部变量表、操作数栈、动态链接、方法出口</strong>等信息。既每一个方法的执行到完成的过程，对应着栈帧的入栈和出栈的过程。</p><h4 id="局部变量表"><a href="#局部变量表" class="headerlink" title="局部变量表"></a>局部变量表</h4><p>局部变量表存放了编译器可知的各种<strong>基本数据类型、对象引用和 returnAdress 类型</strong>。</p><ul><li>其中 double 和 long 类型的数据会占用 2 个局部变量空间（slot），其余的类型均占用 1 个。</li><li>局部变量表的空间在编译器即完成分配，大小完全确定，方法运行期间不会改变局部变量表的大小。</li></ul><h4 id="两种异常情况"><a href="#两种异常情况" class="headerlink" title="两种异常情况"></a>两种异常情况</h4><p>如果线程请求的栈深度大于虚拟机所允许的深度，将抛出 StackOverFlowError 异常。</p><p>如果虚拟机栈可以动态扩展（大部分 Java 虚拟机都可以），如果扩展时无法申请到足够的内存，就会抛出 OutOfMemoryError 异常。</p><h3 id="本地方法栈（Native-Method-Stack）"><a href="#本地方法栈（Native-Method-Stack）" class="headerlink" title="本地方法栈（Native Method Stack）"></a>本地方法栈（Native Method Stack）</h3><p>与虚拟机栈的发挥作用类似，区别如下：</p><ul><li>虚拟机栈为虚拟机执行 Java 方法（字节码）服务。</li><li>本地方发栈为虚拟机使用到的 Native 方法服务。</li></ul><p>本地方法栈在不同的虚拟机有不同的实现方式，甚至 Sun HotSpot 虚拟机将本地方法栈和虚拟机栈合二为一。</p><p>本地方法栈同样会出现虚拟机栈存在的两种异常情况。</p><h3 id="Java堆（Java-Heap）"><a href="#Java堆（Java-Heap）" class="headerlink" title="Java堆（Java Heap）"></a>Java 堆（Java Heap）</h3><p>线程共享，在虚拟机启动时创建，唯一目的是存放对象实例。几乎所有的对象实例都在堆上分配。Java 堆是 GC 的主要区域，因此也常称为 “GC 堆”。</p><ul><li>从垃圾回收的角度看，由于现在的 GC 基本都采用分代的收集算法，因此还可以将 Java 堆细分为：<strong>新生代</strong>和<strong>老年代</strong>。再细致划分的话，有：Eden 空间、From Survivor 空间、To Suvivor 空间。</li><li>从内存分配的角度看，线程共享的 Java 堆还可能划分成多个线程私有的分配缓冲区（TLAB）。</li></ul><p>但无论如何划分，无论哪个区域，Java 堆中存储的都是对象实例，进一步划分是为了更好的回收和分配内存。</p><p>Java 堆要求逻辑上连续，但物理上可以不连续。主流虚拟机在 Java 堆的实现上大多采用可以扩展（通过 - Xms 和 - Xmx 控制）的实现。</p><p>如果 Java 堆中没有内存完成实例分配，并且堆也无法再扩展时，将会抛出 OutOfMemoryError 异常。</p><h3 id="方法区（Method-Area）"><a href="#方法区（Method-Area）" class="headerlink" title="方法区（Method Area）"></a>方法区（Method Area）</h3><p>线程共享，用于存储已被 Java 虚拟机<strong>加载的类信息、常量、静态变量、即时编译器编译后的代码</strong>等数据。Java 虚拟机规范将其描述为堆的一个逻辑部分，但它却有个别名叫做 Non-Heap（非堆），目的将其与 Java 堆分开。</p><ul><li>对于 HotSpot 虚拟机来说，通常将方法区成为 “永久代”（Permanent Generation），但这种描述并不准确，只是因为 HotSpot 的设计团队将 GC 分代算法扩展到了方法区，目的是省去专门为方法区编写内存管理代码的工作。对于其它虚拟机来说是不存在永久代的概念的。通常在方法区发生的 GC 是比较少的，这区域的主要目标是针对常量池的回收和对类型的卸载。</li><li>HotSpot 团队官方发布的路线图信息，现在也有放弃永久代并逐步改为 Native Memory 来实现方法区的规划。在 JDK1.7 的 HotSpot 中，已经把原本放在永久代中的字符串常量池移出。</li></ul><p>当方法区无法满足内存的分配需求时，将会抛出 OutOfMemoryError 异常。</p><h3 id="运行时常量池（Runtime-Constant-Pool）"><a href="#运行时常量池（Runtime-Constant-Pool）" class="headerlink" title="运行时常量池（Runtime Constant Pool）"></a>运行时常量池（Runtime Constant Pool）</h3><p>运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息以外，还有一项信息是常量池，用于存放编译期生成的各种字面量和符号引用，这部分内容在类加载后进入方法区的运行时常量池中存放。</p><ul><li>对于运行时常量池，Java 虚拟机规范中没有做任何细节的要求，不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过，一般来说，除了存储 Class 文件中描述的符号引用外，还会把翻译出来的直接引用也存储在运行时常量池中。</li><li>运行时常量池相对于 Class 文件常量池的另外一个重要特性是具备动态性。Java 语言不要求敞亮一定要在编译期产生，也就是说并非一定要预置入 Class 文件中常量池的内容才能进入运行时常量池，运行期间也可能将新的敞亮放入池中，这种特性被开发人员利用的比较多的便是 String 类的 intern () 方法。</li></ul><p>当运行时常量池无法满足内存的分配需求，将会抛出 OutOfMemoryError 异常。</p><h3 id="直接内存（Direct-Memory）"><a href="#直接内存（Direct-Memory）" class="headerlink" title="直接内存（Direct Memory）"></a>直接内存（Direct Memory）</h3><p>直接内存不是虚拟机运行时数据区的一部分，也不是 Java 虚拟机规范中的内存区域，但却被频繁使用，也有可能导致 OutOfMemoryError 异常。</p><p>在 NIO 中，引入了一种基于通道（Channel）与缓冲区（Buffer）的 I/O 方式，可以利用 Native 函数库直接分配堆外内存，通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能，避免了在 Java 堆和 Native 堆中来回复制数据。</p><p>直接内存不受到 Java 堆大小的限制，但是仍然受到本机 RAM 和 SWAP 区（或分页文件）大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时，常常忽略直接内存而设置 - Xmx 等信息，终而导致各个内存区的总大小大于物理内存限制，从而导致动态扩展时出现的 OutOfMemoryError 异常。</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/55127.html</id>
    <link href="https://www.bytelife.net/articles/55127.html"/>
    <published>2017-03-11T05:11:49.000Z</published>
    <summary>
      <![CDATA[<p>Java 虚拟机内存规范所管理的内存包括如下运行时数据区域：程序计数器、虚拟机栈、本地方法栈、Java 堆、方法区等。</p>]]>
    </summary>
    <title>JVM 内存划分</title>
    <updated>2017-03-11T05:11:49.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="软件技巧" scheme="https://www.bytelife.net/categories/software/"/>
    <category term="Linux" scheme="https://www.bytelife.net/tags/Linux/"/>
    <content>
      <![CDATA[<p>锐速（serverspeeder），是一款 TCP 加速程序，能够增强 VPS / 服务器连接的稳定性，且有效的提高服务器的带宽利用率，进而提高访问速度。</p><span id="more"></span><p>老左经常看到论坛、群里有用户提到锐速这款软件可以提高 VPS 的访问速度、加速应用效率，于是今天就在 VULTR VPS 中进行尝试安装使用。 大概 2 月份的时候锐速宣布不再免费，原来宣传的永久免费 20M 也没了。一时间让广大 vps 爱好者陷入低谷，不过各路破解高手也是不断抛砖引玉，从开始的改 MAC 方法到后来的算 lic 方法，现在连一键安装包都出来了，算是比较成熟了吧。这里转载一个用的比较多的一键安装包，亲测可用，由 91yun.org 博主带来的。 锐速官方支持的 Linux 发行版列表：<span class="exturl" data-url="aHR0cDovL2RsLnNlcnZlcnNwZWVkZXIuY29tL2xzLmRvP209YXZhaWxhYmxlcw==">http://dl.serverspeeder.com/ls.do?m=availables<i class="fa fa-external-link-alt"></i></span> 脚本已托管在 Github：<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tLzkxeXVuL3NlcnZlcnNwZWVkZXI=">https://github.com/91yun/serverspeeder<i class="fa fa-external-link-alt"></i></span> </p><p>锐速破解版安装方法： </p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -N --no-check-certificate https://raw.githubusercontent.com/91yun/serverspeeder/master/serverspeeder-all.sh &amp;&amp; bash serverspeeder-all.sh</span><br></pre></td></tr></tbody></table></figure><p>锐速破解版卸载方法： </p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chattr -i /serverspeeder/etc/apx* &amp;&amp; /serverspeeder/bin/serverSpeeder.sh uninstall -f</span><br></pre></td></tr></tbody></table></figure><p>查看锐速启动状态</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/serverspeeder/bin/serverSpeeder.sh status</span><br></pre></td></tr></tbody></table></figure><p>锐速破解版功能：<br>如果内核完全匹配就会自动下载安装。 如果没有完全匹配的内核，会在界面提示可选内核，可以手动选个最接近的尝试 自动下载授权文件 自动修改配置文件 已 chattr +i /serverspeeder/etc/apx * 禁止修改配置文件，可以不用加 hosts 了 目前只支持 CentOS，ubuntu 和 debian。<br>PS：<strong>锐速不支持 openvz 架构</strong>。所以你查看 vps 的 mac 地址都是 0 的就别试了。</p><blockquote><p>转载自老左博客</p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/8183.html</id>
    <link href="https://www.bytelife.net/articles/8183.html"/>
    <published>2016-10-30T07:46:21.000Z</published>
    <summary>
      <![CDATA[<p>锐速（serverspeeder），是一款 TCP 加速程序，能够增强 VPS / 服务器连接的稳定性，且有效的提高服务器的带宽利用率，进而提高访问速度。</p>]]>
    </summary>
    <title>TCP 加速锐速 SS（ServerSpeeder）破解版一键安装</title>
    <updated>2016-10-30T07:46:21.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="Spring" scheme="https://www.bytelife.net/tags/Spring/"/>
    <content>
      <![CDATA[<p>在默认情况下，Spring 的应用上下文中所有的 bean 都是单例的形式创建的。也就是说，不管给定的一个 bean 被注入到其它 bean 多少次，每次注入的都是同一个实例。</p><p>在大多数情况下，单例 bean 是非常理想的方案。初始化和垃圾回收对象实例所带来的成本只留给一些小规模任务，在这些任务中，让对象保持无状态并且在应用中反复重用这些对象可能并不合理。</p><p>有时候你所使用的类可能是易变的，它们会保持一些状态，比如我们在 Web 购物商城中常见的购物车功能，不同的用户不可能同时使用同一个购物车实例，因此重用是不安全的。</p><span id="more"></span><h3 id="（一）Spring中的作用域"><a href="#（一）Spring中的作用域" class="headerlink" title="（一）Spring中的作用域"></a>（一）Spring 中的作用域</h3><p>Spring 提供了多种作用域，可以基于这些作用域来创建 bean，包括：</p><ul><li>单例（Singleton）：在整个应用中，只创建 bean 的一个实例；</li><li>原型（Prototype）：每次注入或者通过 Spring 上下文获取的时候，都会创建一个新的 bean 实例；</li><li>会话（Session）：在 Web 应用中，为每个会话创建一个 bean 实例；</li><li>请求（Request）：在 Web 应用中，为每次请求创建一个 bean 实例；</li></ul><p>如果需要自定义 bean 的作用域，需要使用 @Scope 注解，他可以与 @Component 或 @Bean 组合使用：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Cake</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里使用 ConfigurableBeanFactory 类的 SCOPE_PROTOTYPE 常亮设置为原型作用域。当然你也可以使用下面这种方式：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Scope("prototype")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Cake</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>但是尽可能使用 ConfigurableBeanFactory.SCOPE_PROTOTYPE，这更不容易出错。</p><p>当然也可以在 Java 配置中将作用域设置为原型 bean，例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">Dessert</span> <span class="title function_">cake</span><span class="params">()</span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Cake</span>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>同样，也可以在 XML 中配置，应用<bean>元素的 scope 属性：</bean></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"cake"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Cake"</span> <span class="attr">scope</span>=<span class="string">"prototype"</span>&gt;</span><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><h3 id="（二）使用会话和请求作用域"><a href="#（二）使用会话和请求作用域" class="headerlink" title="（二）使用会话和请求作用域"></a>（二）使用会话和请求作用域</h3><p>在 Web 应用中，我们经常需要操作两种作用域：会话和请求。</p><p>就像前面所说，在购物商城的购物车实例上，单例和原型作用域自然不能满足我们的需求，我们希望为每一个会话都创建一个购物车，那么这里会话作用域就是最完美的选择。</p><p>下面来简单模拟一下购物车的作用域场景：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@Scope(</span></span><br><span class="line"><span class="meta">    value = WebApplicationContext.SCOPE_SESSION,</span></span><br><span class="line"><span class="meta">    proxyMode = ScopedProxyMode.INTERFACES)</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">ShoppingCart</span> <span class="title function_">cart</span><span class="params">()</span>{</span><br><span class="line">   <span class="comment">// ....</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里，将 value 值设置成了 WebApplicationContext 中的 SCOPTE_SESSION 常量（值为 session）。这会告诉 Spring 为 Web 应用中的每个会话创建一个 ShoppingCart。对于每一个会话来说，这个 bean 实际上相当于是单例的。</p><p>这里需要注意，@Scope 还有一个 proxyMode 属性，它被设置为 ScopedProxyMode.INTERFACES。我们先不考虑这个属性，先来理解一下对 Spring 作用域的理解。</p><p>现在假设我们要将 ShoppingCart bean 注入到单例 StoreService bean 的 Setter 中，如下所示：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StoreService</span> {</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> ShoppingCart shoppingCart ;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">public</span>  <span class="type">void</span> <span class="title function_">setShoppingCart</span><span class="params">(ShoppingCart shoppingCart)</span>{</span><br><span class="line">        <span class="built_in">this</span>.shoppingCart = shoppingCart;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>因为 StoreService 是一个单例 bean，会在 Spring 上下文加载的时候创建，当它创建的时候，Spring 会试图将 ShoppingCart bean 注入到 setShoppingCart () 方法中。但是 ShoppingCart bean 是会话作用域的，此时并不存在。直到某个用户进入系统，创建了会话以后，才会出现 ShoppingCart 实例。</p><p>另外，系统中将会存在多个 ShoppingCart 实例：每个用户一个。我们并不想让 Spring 注入到某个胡定的 ShoppingCart 实例到 StoreService 中。我们希望的是当 StoreService 处理购物车功能时，它所使用的 ShoppingCart 实例恰好是当前会话所对应的那一个。</p><p>Spring 并不会将实际的 ShoppingCart bean 注入到 StoreService 中，Spring 会注入一个到 ShoppingCart bean 的代理。这个代理会暴露与 ShoppingCart 相同的方法，所以 StoreService 会认为他就是一个购物车。但是，当 StoreService 调用 ShoppingCart 的方法时，代理会对其进行懒解析并将调用委托给作用域内真正的 ShoppingCart bean。如下图所示：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139181440326.png"></p><p>现在我们来讨论一下 proxyMode 属性。我们将 proxyMode 属性设置为了 ScopedProxyMode.INTERFACES，这表明这个代理要实现 ShoppingCart 接口，并将调用委托给实现 bean。</p><p>这里我们的 ShoppingCart 是接口而不是具体的类，这当然是可以的（也是最理想的代理模式）。但如果 ShoppingCart 是具体的实现类而不是接口的话，Spring 就没办法创建基于接口的代理了。此时必须使用 CGLib 来生成基于类的代理。所以，如果 bean 类型是具体的类的话，我们必须要将 proxyMode 设置为 ScopedProxyMode.TARGET_CLASS，以此来表明要以生成目标类扩展的方式创建代理。</p><p>请求作用域与会话作用域十分类似，也应该以作用域代理的方式进行注入，再次不做赘述。</p><h3 id="（三）在XML中声明作用域代理"><a href="#（三）在XML中声明作用域代理" class="headerlink" title="（三）在XML中声明作用域代理"></a>（三）在 XML 中声明作用域代理</h3><p>在 XML 中设置作用域代理需要使用 Spring aop 命名空间的一个元素：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"cart"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.ShoppingCart"</span> <span class="attr">scope</span>=<span class="string">"session"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">aop:scoped-proxy</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>当然了，在使用 aop 命名空间之前一定要在 xml 的顶部进行对命名空间进行声明：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:aop</span>=<span class="string">"http://www.springframework.org/schema/aop"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"</span></span></span><br><span class="line"><span class="string"><span class="tag">       http://www.springframework.org/schema/beans</span></span></span><br><span class="line"><span class="string"><span class="tag">       http://www.springframework.org/schema/beans/spring-beans.xsd</span></span></span><br><span class="line"><span class="string"><span class="tag">       http://www.springframework.org/schema/aop</span></span></span><br><span class="line"><span class="string"><span class="tag">       http://www.springframework.org/schema/aop/spring-aop.xsd"</span>&gt;</span></span><br><span class="line">    ......</span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>注意：在使用 Spring 开发 web 项目时，需要在 web.xml 中加入如下内容（web2.4 以上）：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">web-app</span>&gt;</span></span><br><span class="line">   ...</span><br><span class="line">  <span class="tag">&lt;<span class="name">listener</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">listener-class</span>&gt;</span>org.springframework.web.context.request.RequestContextListener<span class="tag">&lt;/<span class="name">listener-class</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">listener</span>&gt;</span></span><br><span class="line">   ...</span><br><span class="line"><span class="tag">&lt;/<span class="name">web-app</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>web 2.4 以下版本需要加入：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">web-app</span>&gt;</span></span><br><span class="line"> ..</span><br><span class="line"> <span class="tag">&lt;<span class="name">filter</span>&gt;</span> </span><br><span class="line">    <span class="tag">&lt;<span class="name">filter-name</span>&gt;</span>requestContextFilter<span class="tag">&lt;/<span class="name">filter-name</span>&gt;</span> </span><br><span class="line">    <span class="tag">&lt;<span class="name">filter-class</span>&gt;</span>org.springframework.web.filter.RequestContextFilter<span class="tag">&lt;/<span class="name">filter-class</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">filter</span>&gt;</span> </span><br><span class="line"> <span class="tag">&lt;<span class="name">filter-mapping</span>&gt;</span> </span><br><span class="line">    <span class="tag">&lt;<span class="name">filter-name</span>&gt;</span>requestContextFilter<span class="tag">&lt;/<span class="name">filter-name</span>&gt;</span> </span><br><span class="line">    <span class="tag">&lt;<span class="name">url-pattern</span>&gt;</span>/*<span class="tag">&lt;/<span class="name">url-pattern</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">filter-mapping</span>&gt;</span></span><br><span class="line">   ...</span><br><span class="line"><span class="tag">&lt;/<span class="name">web-app</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>另外，&lt;aop:scoped-proxy /&gt; 是与 @Scope 注解的 proxyMode 属性功能相同的 Spring XML 配置元素。它会告诉 Spring 为 bean 创建一个作用域代理。默认情况下，它会使用 CGLib 创建目标类的代理。但是我们也可以将 proxy-target-class 属性设置为 false，进而要求它生成基于接口的代理：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"cart"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.ShoppingCart"</span> <span class="attr">scope</span>=<span class="string">"session"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">aop:scoped-proxy</span> <span class="attr">proxy-target-class</span>=<span class="string">"false"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.bytelife.net/articles/8890.html</id>
    <link href="https://www.bytelife.net/articles/8890.html"/>
    <published>2016-10-20T09:31:11.000Z</published>
    <summary>
      <![CDATA[<p>在默认情况下，Spring 的应用上下文中所有的 bean 都是单例的形式创建的。也就是说，不管给定的一个 bean 被注入到其它 bean 多少次，每次注入的都是同一个实例。</p>
<p>在大多数情况下，单例 bean 是非常理想的方案。初始化和垃圾回收对象实例所带来的成本只留给一些小规模任务，在这些任务中，让对象保持无状态并且在应用中反复重用这些对象可能并不合理。</p>
<p>有时候你所使用的类可能是易变的，它们会保持一些状态，比如我们在 Web 购物商城中常见的购物车功能，不同的用户不可能同时使用同一个购物车实例，因此重用是不安全的。</p>]]>
    </summary>
    <title>Spring 学习笔记之 Bean 的作用域</title>
    <updated>2016-10-20T09:31:11.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="Spring" scheme="https://www.bytelife.net/tags/Spring/"/>
    <content>
      <![CDATA[<p>之前的文章中已经看到了 Spring 的自动装配有很大的用处，它可以帮助我们快速的装配 bean，但是这里存在一个问题，在之前的装配中，仅有一个 bean 匹配所需的结果时，自动装配才是有效的。如果不仅只有一个 bean 能够匹配结果的话，这就会导致 Spring 不知道该装配哪个 bean 从而导致装配失败，例如下面这个例子，我们定义了一个 Dessert 接口，并且有三个类实现了这个接口，分别为 Cake、Cookies 和 IceCream：</p><span id="more"></span><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Cookies</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Cake</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IceCream</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这三个类均使用了 @Component 注解，在组件扫描的时候，能够发现他们并将其创建为 Spring 上下文中的 bean。下面是测试代码：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> cn.javacodes.spring.beans.Dessert;</span><br><span class="line"><span class="keyword">import</span> cn.javacodes.spring.configuration.SpringConfig;</span><br><span class="line"><span class="keyword">import</span> org.junit.runner.RunWith;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.test.context.ContextConfiguration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.test.context.junit4.SpringJUnit4ClassRunner;</span><br><span class="line"><span class="keyword">import</span> <span class="keyword">static</span> org.junit.Assert.assertNotNull;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by Eric on 2016/10/20.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = SpringConfig.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> {</span><br><span class="line">    <span class="keyword">private</span> Dessert dessert ;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setDessert</span><span class="params">(Dessert dessert)</span>{</span><br><span class="line">        <span class="built_in">this</span>.dessert = dessert;</span><br><span class="line">    }</span><br><span class="line">    <span class="meta">@org</span>.junit.Test</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">test</span><span class="params">()</span>{</span><br><span class="line">        assertNotNull(dessert);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>当 Spring 试图自动装配 setDessert () 中的 Dessert 参数时，它并没有唯一、无歧义的可选值。所以 Spring 会抛出一个异常：</p><blockquote><p>org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘Test’: Unsatisfied dependency expressed through method ‘setDessert’ parameter 0: No qualifying bean of type [cn.javacodes.spring.beans.Dessert] is defined: expected single matching bean but found 3: cake,cookies,iceCream; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [cn.javacodes.spring.beans.Dessert] is defined: expected single matching bean but found 3: cake,cookies,iceCream</p></blockquote><p>为了解决这个问题，Spring 提供了多种解决方案，标示首选的 bean（primary）和使用限定符（qualifier）。</p><h3 id="一、标示首选的bean"><a href="#一、标示首选的bean" class="headerlink" title="一、标示首选的bean"></a>一、标示首选的 bean</h3><p>在声明 bean 的时候，我们可以通过将其中一个可选的 bean 设置为首选（primary） bean，这样就可以避免歧义性了，使用方式如下，例如我们想将 IceCream 作为首选 bean：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Primary</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IceCream</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>当然了，你也可以在显式声明 bean 的时候将其设置为首选 bean，比如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@Primary</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">Dessert</span> <span class="title function_">IceCream</span><span class="params">()</span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">IceCream</span>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>当然，如果你喜欢使用 XML 来配置 Bean，那么其方法如下：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"iceCream"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.IceCream"</span></span></span><br><span class="line"><span class="tag">      <span class="attr">primary</span>=<span class="string">"true"</span> /&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>使用哪种方式告诉 Spring 首选 bean 的效果都是一样的，不过，如果你标示了两个或更多的首选 bean，那么它就无法工作了，因为这又会带来歧义性的问题。</p><p>当然我们可以使用另一种更为强大的机制（限定符）来解决这个问题。</p><h3 id="二、使用限定符"><a href="#二、使用限定符" class="headerlink" title="二、使用限定符"></a>二、使用限定符</h3><h4 id="（一）-Qualifier注解"><a href="#（一）-Qualifier注解" class="headerlink" title="（一）@Qualifier注解"></a>（一）@Qualifier 注解</h4><p>使用 @Primary 无法将可选方案的范围限定到唯一一个无歧义的选项，它只能标示一个优先的可选选项。当首选 bean 的数量超过 1 个时，我们并没有其它的办法将其限定到唯一的选项上。</p><p>Spring 提供的限定符可以解决这个问题，@Qualifier 注解是使用限定符的主要方式。它可以与 @Autowired 或 @Inject 协同使用。例如，我们想确保 IceCream 注入到 setDessert (）之中：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="meta">@Qualifier("iceCream")</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setDessert</span><span class="params">(Dessert dessert)</span>{</span><br><span class="line">    <span class="built_in">this</span>.dessert = dessert;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这是使用限定符最简单的例子了。为 @Qualifier 注解所设置的参数就是想要注入的 bean 的 ID。所有使用 @Component 注解声明的类都会创建为 bean，并且 bean 的 ID 为首字母变为小写的类名，因此这个例子中使用 iceCream 作为参数指向组件扫描时所创建的 IceCream bean。</p><p>实际上，更准确的讲，@Qualifier (“iceCream”) 所引用的 bean 要具有 String 类型的 “iceCream” 作为限定符。如果没有制定其他的限定符，那么所有的 bean 都会有一个默认的限定符，它的值为 bean 的 ID。因此框架会将具有 “iceCream” 限定符的 bean 注入到 setDessert () 方法中。这恰巧就是 ID 为 iceCream 的 bean。</p><p>基于默认的限定符看起来是很简单的，不过这里面存在一个问题，如果日后我们进行重构的时候，如果更改了 IceCream 类的类名比如更改为 Gelato 的话，那么自动创建的 bean 的 ID 就会变为 “gelato”，这就无法匹配我们之前所写的限定符了，导致自动装配失败。</p><p>所以在这里 setDessert () 方法上所指定的限定符与要注入的 bean 的名称是紧耦合的。对类名称的任意改动都会造成限定符失效。</p><h4 id="（二）创建自定义的限定符"><a href="#（二）创建自定义的限定符" class="headerlink" title="（二）创建自定义的限定符"></a>（二）创建自定义的限定符</h4><p>我们可以为 bean 设置自己的限定符，而不是依赖与将 bean ID 作为限定符。例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Qualifier("cold")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IceCream</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这样就解决了之前耦合类名的问题，然后就可以在需要的地方使用这个限定符了，例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="meta">@Qualifier("cold")</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setDessert</span><span class="params">(Dessert dessert)</span>{</span><br><span class="line">    <span class="built_in">this</span>.dessert = dessert;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>当然，@Qualifier 注解也可以与显式装配 Bean 的 @Bean 注解组合使用，再次不做赘述。</p><h4 id="（三）使用自定义限定符的注解"><a href="#（三）使用自定义限定符的注解" class="headerlink" title="（三）使用自定义限定符的注解"></a>（三）使用自定义限定符的注解</h4><p>上面的例子中使用了 “cold” 作为 IceCream 的限定符，在这里 “cold” 更像是一种特性来描述这个 bean，当然，面向特性的限定符比 bean ID 更好一些，但是如果多个 bean 都具有相同的特性怎么办？</p><p>比如我们新加入一个类：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Qualifier("cold")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Popsicle</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>现在我们有了两个带有 “cold” 的限定符，自动装配的时候我们再次遇到了歧义性的问题，需要更多的限定符来将其可选范围缩小，现在我们可能想到的解决办法可能是类似像下面这种方式，使用多个 @Qualifier 注解：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Qualifier("cold")</span></span><br><span class="line"><span class="meta">@Qualifier("creamy")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IceCream</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>但是这种方式是不行的，Java 语言不允许在同一个条目上重复出现相同类型的注解（Java 8 允许出现重复的注解，但是这个注解本身必须在定义的时候带有 @Repeatable，可是 Spring 的 @Qualifier 注解并没有在定义时加入 @Repeatable），为了解决这个问题，我们可以创建一个自定义的限定符注解，它本身需要使用 @Qualifier 注解来标注，例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Qualifier</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Cold {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>同样你可以在创建一个 Creamy 注解来替代 @Qualifier (“creamy”)：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Qualifier</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Creamy {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>同样的原理，你还可以创建类似 @Soft、@Crispy 等等其它注解。通过在定义注解的时候添加 @Qualifier，这些注解就具有了 Qualifier 的特性，他们本身世界上就是一个限定符。现在我们重新编辑一下 IceCream：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Cold</span></span><br><span class="line"><span class="meta">@Creamy</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IceCream</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>类似的，Popsicle 类可以添加 @Cold、@Fruity 注解：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Fruity</span></span><br><span class="line"><span class="meta">@Cold</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Popsicle</span> <span class="keyword">implements</span> <span class="title class_">Dessert</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>最终，在注入点，我们使用必要的限定符注解进行任意组合即可：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="meta">@Cold</span></span><br><span class="line"><span class="meta">@Creamy</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setDessert</span><span class="params">(Dessert dessert)</span>{</span><br><span class="line">    <span class="built_in">this</span>.dessert = dessert;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这样我们就可以随心所欲的使用自定义限定符注解来缩小匹配范围啦！当然，还是希望 Spring 可以尽快在新的版本中将 @Qualifier 注解中加入 @Repeatable 注解，这样就不用这么麻烦了（我估计要很久，因为 Spring 还需要保证在相对旧的 Java 版本上做兼容，Java 8 的这一特性估计不会这么快被支持的）！</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/22338.html</id>
    <link href="https://www.bytelife.net/articles/22338.html"/>
    <published>2016-10-20T09:23:41.000Z</published>
    <summary>
      <![CDATA[<p>之前的文章中已经看到了 Spring 的自动装配有很大的用处，它可以帮助我们快速的装配 bean，但是这里存在一个问题，在之前的装配中，仅有一个 bean 匹配所需的结果时，自动装配才是有效的。如果不仅只有一个 bean 能够匹配结果的话，这就会导致 Spring 不知道该装配哪个 bean 从而导致装配失败，例如下面这个例子，我们定义了一个 Dessert 接口，并且有三个类实现了这个接口，分别为 Cake、Cookies 和 IceCream：</p>]]>
    </summary>
    <title>Spring 学习笔记之处理自动装配的歧义性</title>
    <updated>2016-10-20T09:23:41.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="Spring" scheme="https://www.bytelife.net/tags/Spring/"/>
    <content>
      <![CDATA[<h3 id="一、c命名空间"><a href="#一、c命名空间" class="headerlink" title="一、c命名空间"></a>一、c 命名空间</h3><p>通过构造器注入 Bean 的时候通常需要使用到<constructor-arg>元素，作为替代的方案，可以使用 Spring 的 c- 命名空间。c- 命名空间是在 Spring 3.0 中加入的，它是在 XML 中更为简洁地描述构造器参数的方式。要使用 c- 命名空间，必须要在 XML 的顶部声明其模式，如下所示：</constructor-arg></p><span id="more"></span><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:c</span>=<span class="string">"http://www.springframework.org/schema/c"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>在 c- 命名空间和模式声明后，就可以使用它来声明构造器参数了，如下所示：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"compactDisc"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.Transfer"</span>&gt;</span><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line">    </span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"cdPlayer"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.CDPlayer"</span></span></span><br><span class="line"><span class="tag">          <span class="attr">c:cd-ref</span>=<span class="string">"compactDisc"</span> /&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>可以看到上面的方式也有一些问题，c:cd-ref 中的 cd 即为构造器参数的名称，这种方式对于后期代码的重构是非常不友好的，那么为了解决这个问题，可以使用参数的索引来替代参数名称，如下所示：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"compactDisc"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.Transfer"</span>&gt;</span><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"cdPlayer"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.CDPlayer"</span></span></span><br><span class="line"><span class="tag">      <span class="attr">c:_0-ref</span>=<span class="string">"compactDisc"</span> /&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>当然如果构造器只有一个参数，可以连索引一起略去：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"compactDisc"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.Transfer"</span>&gt;</span><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"cdPlayer"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.CDPlayer"</span></span></span><br><span class="line"><span class="tag">      <span class="attr">c:_-ref</span>=<span class="string">"compactDisc"</span> /&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>不过在我的测试中，这种方式在 idea 中会报错。</p><h3 id="二、装配集合"><a href="#二、装配集合" class="headerlink" title="二、装配集合"></a>二、装配集合</h3><h4 id="（一）装配List和Set"><a href="#（一）装配List和Set" class="headerlink" title="（一）装配List和Set"></a>（一）装配 List 和 Set</h4><p>BlankDisc 类的代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem.collections;</span><br><span class="line"><span class="keyword">import</span> cn.javacodes.spring.beans.soundsystem.CompactDisc;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by Eric on 2016/10/16.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BlankDisc</span> <span class="keyword">implements</span> <span class="title class_">CompactDisc</span> {</span><br><span class="line">    <span class="keyword">private</span> String title;</span><br><span class="line">    <span class="keyword">private</span> String artist;</span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; tracks;</span><br><span class="line">    <span class="type">public</span> <span class="title function_">BlankDisc</span><span class="params">(String title, String artist, List&lt;String&gt; tracks)</span> {</span><br><span class="line">        <span class="built_in">this</span>.title = title;</span><br><span class="line">        <span class="built_in">this</span>.artist = artist;</span><br><span class="line">        <span class="built_in">this</span>.tracks = tracks;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">play</span><span class="params">()</span> {</span><br><span class="line">        System.out.println(<span class="string">"正在播放"</span> + artist + <span class="string">"的专辑："</span> + title);</span><br><span class="line">        <span class="keyword">for</span> (String track : tracks) {</span><br><span class="line">            System.out.println(<span class="string">"-Track："</span> + track);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在 Spring 中配置这个类的 bean 时需要提供一个 list，当然，你可以给它传递为空值：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"compactDisc"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.collections.BlankDisc"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"Transfer"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"周传雄/小刚"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span>&gt;</span><span class="tag">&lt;<span class="name">null</span>/&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>不过如果传递 null 的话，当调用 play () 方法时，必然会产生 NullPointerException 异常，通常情况下，我们更多的给它装配一个 List。</p><p>我们有很多种解决方案，首先，可以使用<list>元素将其声明为一个列表：</list></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"compactDisc"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.collections.BlankDisc"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"Transfer"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"周传雄/小刚"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">list</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>忘记<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>出卖<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>寂寞轰炸<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>游戏爱情<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>心结<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>记事本<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>啤酒泡泡<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>黄昏<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>末班车<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>心血来潮<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">value</span>&gt;</span>时间<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">list</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>当然，也可以使用<ref>来替代<value>达到引用其它 bean 的目的，再次不做赘述。</value></ref></p><p>在我们的例子中构造器参数类型为 List，因此使用<list>元素进行注入是合情合理的，尽管如此，我们仍然可以使用<set>元素来进行注入：</set></list></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"compactDisc"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.collections.BlankDisc"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"Transfer"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"周传雄/小刚"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">set</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>忘记<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>出卖<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>寂寞轰炸<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>游戏爱情<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>心结<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>记事本<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>啤酒泡泡<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>黄昏<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>末班车<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>心血来潮<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">value</span>&gt;</span>时间<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">set</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p><set>和<list>元素的区别并不大，其中最重要的区别在于 Spring 创建要装配的集合时，所创建的是 Set 还是 List。如果是 Set 的话，所有重复的值都会被忽略掉，并且存放的顺序也不会得到保证，这个相信大家对 Set 和 List 的区别都有明确的认识。不过无论哪种情况下，<set>和<list>都可以用来装配 List、Set 甚至数组。</list></set></list></set></p><h4 id="（二）、装配Map"><a href="#（二）、装配Map" class="headerlink" title="（二）、装配Map"></a>（二）、装配 Map</h4><p>我们将上面的 BlankDisc 稍作改动，将原有的 List 改为 Map：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> String title;</span><br><span class="line"><span class="keyword">private</span> String artist;</span><br><span class="line"><span class="keyword">private</span> Map&lt;String,String&gt; tracks;</span><br><span class="line"><span class="type">public</span> <span class="title function_">BlankDisc</span><span class="params">(String title, String artist, Map&lt;String, String&gt; tracks)</span> {</span><br><span class="line">    <span class="built_in">this</span>.title = title;</span><br><span class="line">    <span class="built_in">this</span>.artist = artist;</span><br><span class="line">    <span class="built_in">this</span>.tracks = tracks;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>装配 Map 的方式如下：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"compactDisc"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.collections.BlankDisc"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"Transfer"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"周传雄/小刚"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">map</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">entry</span> <span class="attr">key</span>=<span class="string">"忘记"</span> <span class="attr">value</span>=<span class="string">"5:12"</span>/&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">entry</span> <span class="attr">key</span>=<span class="string">"出卖"</span> <span class="attr">value</span>=<span class="string">"4:54"</span>/&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">entry</span> …… </span></span><br><span class="line"><span class="tag">        &lt;/<span class="attr">map</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>当然<key>和<value>元素可以分别用<key-ref>和<value-ref>元素替代用于引用其它 Bean。</value-ref></key-ref></value></key></p><h4 id="（三）、装配Properties"><a href="#（三）、装配Properties" class="headerlink" title="（三）、装配Properties"></a>（三）、装配 Properties</h4><p>Properties 与 Map 非常类似，不过区别主要在于 Map 的 key 和 Value 可以是任意类型的对象，而 Properties 要求 key 和 value 必须都是 String 类型，而很多场景下我们的 key 和 value 的确都是 String 类型，比如数据库连接信息的配置。</p><p>为了演示 Properties 的相关配置，我们创建一个类 DataSource：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.properties;</span><br><span class="line"><span class="keyword">import</span> java.util.Properties;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by Eric on 2016/10/16.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataSource</span> {</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> Properties properties;</span><br><span class="line">    <span class="type">public</span> <span class="title function_">DataSource</span><span class="params">(Properties properties)</span> {</span><br><span class="line">        <span class="built_in">this</span>.properties = properties;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">Properties</span> <span class="title function_">getProperties</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> properties;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setProperties</span><span class="params">(Properties properties)</span> {</span><br><span class="line">        <span class="built_in">this</span>.properties = properties;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在 Spring XML 配置如下：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"dataSource"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.properties.DataSource"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">props</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">prop</span> <span class="attr">key</span>=<span class="string">"user"</span>&gt;</span>root<span class="tag">&lt;/<span class="name">prop</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">prop</span> <span class="attr">key</span>=<span class="string">"password"</span>&gt;</span>123456<span class="tag">&lt;/<span class="name">prop</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">prop</span> <span class="attr">key</span>=<span class="string">"url"</span>&gt;</span>jdbc:mysql:///test<span class="tag">&lt;/<span class="name">prop</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">prop</span> <span class="attr">key</span>=<span class="string">"driver"</span>&gt;</span>com.mysql.jdbc.Driver<span class="tag">&lt;/<span class="name">prop</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">props</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><h3 id="三、p命名空间"><a href="#三、p命名空间" class="headerlink" title="三、p命名空间"></a>三、p 命名空间</h3><p>我们知道配置 bean 的属性时需要使用<property>元素，为了简化这种方式，与 c 命名空间一样，Spring 为我们提供了 p 命名空间。为了使用 p- 命名空间，必须在 xml 顶部进行声明：</property></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:p</span>=<span class="string">"http://www.springframework.org/schema/p"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>&gt;</span></span><br><span class="line">……</span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>然后就可以使用 p- 命名空间了，使用方法如下：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"cdPlayer"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.CDPlayer"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">p:cd-ref</span>=<span class="string">"compactDisc"</span>/&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>属性的名称以 - ref 结尾的，代表 Spring 要进行装配的是 bean 的引用而不是字面值（String）。</p><h3 id="四、util命名空间"><a href="#四、util命名空间" class="headerlink" title="四、util命名空间"></a>四、util 命名空间</h3><p>p- 命名空间和 c- 命名空间都不能对集合类型进行装配，不过我们可以使用 util 命名空间来简化对集合的装配，声明如下：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:p</span>=<span class="string">"http://www.springframework.org/schema/p"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:c</span>=<span class="string">"http://www.springframework.org/schema/c"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:util</span>=<span class="string">"http://www.springframework.org/schema/util"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>现在就可以使用 util 命名空间了，例如：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">util:list</span> <span class="attr">id</span>=<span class="string">"trackList"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>忘记<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>出卖<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>寂寞轰炸<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>游戏爱情<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>心结<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>记事本<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>啤酒泡泡<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>黄昏<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>末班车<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>心血来潮<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">value</span>&gt;</span>时间<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">util:list</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>然后就可以像引用其它 Bean 一样将 list 轻松引用，如下所示：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"compactDisc"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem.collections.BlankDisc"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">c:title</span>=<span class="string">"Transfer"</span> <span class="attr">c:artist</span>=<span class="string">"周传雄/小刚"</span> <span class="attr">c:tracks-ref</span>=<span class="string">"trackList"</span>/&gt;</span></span><br></pre></td></tr></tbody></table></figure><p><span class="exturl" data-url="dXRpbDpsaXN0">util:list<i class="fa fa-external-link-alt"></i></span> 只是 util - 命名空间提供的众多元素之一，下表列出了 util - 命名空间提供的所有元素：</p><table><thead><tr><th>元素</th><th>描述</th></tr></thead><tbody><tr><td><span class="exturl" data-url="dXRpbDpjb25zdGFudA==">util:constant<i class="fa fa-external-link-alt"></i></span></td><td>引用某个类型的 public static 域，并墙漆暴露为 bean</td></tr><tr><td><span class="exturl" data-url="dXRpbDpsaXN0">util:list<i class="fa fa-external-link-alt"></i></span></td><td>创建一个 java.util.List 类型的 bean，其中包含值或引用</td></tr><tr><td><span class="exturl" data-url="dXRpbDptYXA=">util:map<i class="fa fa-external-link-alt"></i></span></td><td>创建一个 java.util.Map 类型的 bean，其中包含值或引用</td></tr><tr><td><span class="exturl" data-url="dXRpbDpwcm9wZXJ0aWVz">util:properties<i class="fa fa-external-link-alt"></i></span></td><td>创建一个 java.util.Properties 类型的 bean</td></tr><tr><td><span class="exturl" data-url="dXRpbDpwcm9wZXJ0eS1wYXRo">util:property-path<i class="fa fa-external-link-alt"></i></span></td><td>引用一个 bean 的属性（或内嵌属性），并将其暴露为 bean</td></tr><tr><td><span class="exturl" data-url="dXRpbDpzZXQ=">util:set<i class="fa fa-external-link-alt"></i></span></td><td>创建一个 java.util.set 类型的 bean，其中包含值或引用</td></tr></tbody></table>]]>
    </content>
    <id>https://www.bytelife.net/articles/36596.html</id>
    <link href="https://www.bytelife.net/articles/36596.html"/>
    <published>2016-10-20T09:13:13.000Z</published>
    <summary>
      <![CDATA[<h3 id="一、c命名空间"><a href="#一、c命名空间" class="headerlink" title="一、c命名空间"></a>一、c 命名空间</h3><p>通过构造器注入 Bean 的时候通常需要使用到<constructor-arg>元素，作为替代的方案，可以使用 Spring 的 c- 命名空间。c- 命名空间是在 Spring 3.0 中加入的，它是在 XML 中更为简洁地描述构造器参数的方式。要使用 c- 命名空间，必须要在 XML 的顶部声明其模式，如下所示：</constructor-arg></p>]]>
    </summary>
    <title>Spring 学习笔记之通过 XML 装配 Bean 的一些细节</title>
    <updated>2016-10-20T09:13:13.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="Spring" scheme="https://www.bytelife.net/tags/Spring/"/>
    <content>
      <![CDATA[<p>虽然很多场景下可以使用组件扫描和自动装配来实现 Spring 的自动化配置，但有些时候自动化配置的方案是行不通的，因此需要明确配置 Spring。比如你想讲第三方库中的组件装配到你的应用中，在这种情况下，你必须要显式装配。在进行显式配置的时候，有两种方案，Java 和 XML。</p><p>下面介绍通过 Java 代码显式装配 Bean。</p><span id="more"></span><h3 id="一、创建配置类"><a href="#一、创建配置类" class="headerlink" title="一、创建配置类"></a>一、创建配置类</h3><p>创建配置类的关键在于 @Configuration 注解，@Configuration 注解表明该类是一个配置类，该类包含了在 Spring 上下文中如何创建 Bean 的细节。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.configuration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by Eric on 2016/10/16.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayerConfig</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="二、声明简单的Bean"><a href="#二、声明简单的Bean" class="headerlink" title="二、声明简单的Bean"></a>二、声明简单的 Bean</h3><p>要在 JavaConfig 中声明 Bean，我们需要写一个方法，这个方法会创建所需类型的实例，然后给这个方法添加 @Bean 注解。比如下面的代码声明了 CompactDisc Bean：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">CompactDisc</span> <span class="title function_">transfer</span><span class="params">()</span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Transfer</span>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>@Bean 注解会告诉 Spring 这个方法会返回一个对象，该对象要注册为 Spring 应用上下文中的 bean。方法体中包含了最终产生 bean 对象的逻辑。</p><p>默认情况下，bean 的 ID 于带有 @Bean 注解方法的名称是一样的。在上面的例子中，bean 的名字将会是 “transfer”。如果你想为它设置一个不同的名字，可以通过 name 属性指定它的 ID：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean(name = "transfer-cd")</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">CompactDisc</span> <span class="title function_">transfer</span><span class="params">()</span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Transfer</span>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>另外，方法中的方法体可以是任何你想创建 Bean 的逻辑，比如你想在一组 CD 中随机选取一个 CD 进行播放也是可以的。</p><h3 id="三、借助JavaConfig实现注入"><a href="#三、借助JavaConfig实现注入" class="headerlink" title="三、借助JavaConfig实现注入"></a>三、借助 JavaConfig 实现注入</h3><p>前面声明的 CompactDisc Bean 是非常简单的，因为它没有任何依赖，那么现在想要声明一个 CDPlayer Bean，它依赖于 CompactDisc。</p><h4 id="（一）引用创建Bean的方法"><a href="#（一）引用创建Bean的方法" class="headerlink" title="（一）引用创建Bean的方法"></a>（一）引用创建 Bean 的方法</h4><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">CDPlayer</span> <span class="title function_">cdPlayer</span><span class="params">()</span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CDPlayer</span>(transfer());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里同样使用了 @Bean 注解，并在构造 CDPlayer 时将 CompactDisc 作为 CDPlayer 构造器的参数传入。</p><p>注意：这里虽然看起来是通过调用 transfer () 方法得到 CompactDisc，但由于 transfer 方法使用了 @Bean 注解，实际上 Spring 会拦截所有对它的调用，并确保直接返回该方法所创建的 Spring 上下文中的 bean，而不是每次都对其进行实际的调用。</p><p>比如说，假设你引入了另一个其他的 CDPlayer Bean，它和之前的那个 bean 完全一样。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">CDPlayer</span> <span class="title function_">cdPlayer</span><span class="params">()</span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CDPlayer</span>(transfer());</span><br><span class="line">}</span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">CDPlayer</span> <span class="title function_">anotherCDPlayer</span><span class="params">()</span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CDPlayer</span>(transfer());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>默认情况下，Spring 中的 bean 都是单例的，我们并没有必要为第二个 CDPlayer Bean 创建完全相同的 CompactDisc 实例。Spring 会拦截对 @Bean 注解标注方法的调用并确保返回的是 Spring 创建的 Bean。</p><h4 id="（二）将依赖作为参数"><a href="#（二）将依赖作为参数" class="headerlink" title="（二）将依赖作为参数"></a>（二）将依赖作为参数</h4><p>可以看到通过引用创建 Bean 的方法来解决这个问题有些让人疑惑，下面这种方式看起来更为容易理解：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">CDPlayer</span> <span class="title function_">cdPlayer</span><span class="params">(CompactDisc compactDisc)</span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CDPlayer</span>(compactDisc);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在这里，cdPlayer () 方法请求一个 CompactDisc 作为参数。当 Spring 调用 cdPlayer () 方法创建 bean 的时候，它会自动装配一个 CompactDisc 到配置方法中。这样方法体就可以在合适的位置使用它，通过这种方式可以避免明确的调用 @Bean 方法。</p><p>通过这种方式来引用其它 Bean 是最佳的选择，因为它不要求将依赖声明到同一个配置类当中，甚至它不要求必须要在 JavaConfig 中声明，实际上它可以通过组件扫描功能来自动发现或者通过 XML 来进行配置。</p><p>本例中使用了 CDPlayer 的构造器实现了 DI（依赖注入），但是完全可以使用其它风格的 DI，比如，你可以使用 setter 的方式进行注入。</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/2868.html</id>
    <link href="https://www.bytelife.net/articles/2868.html"/>
    <published>2016-10-20T09:03:27.000Z</published>
    <summary>
      <![CDATA[<p>虽然很多场景下可以使用组件扫描和自动装配来实现 Spring 的自动化配置，但有些时候自动化配置的方案是行不通的，因此需要明确配置 Spring。比如你想讲第三方库中的组件装配到你的应用中，在这种情况下，你必须要显式装配。在进行显式配置的时候，有两种方案，Java 和 XML。</p>
<p>下面介绍通过 Java 代码显式装配 Bean。</p>]]>
    </summary>
    <title>Spring 学习笔记之通过 Java 代码装配 Bean</title>
    <updated>2016-10-20T09:03:27.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="算法" scheme="https://www.bytelife.net/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>做数据处理的人可能经常用到一些比如求中位数、众数、均值等等的常用数学方法，下面整理一些算法，由于都比较简单，不做过多的解释了，直接拿去用就好啦！</p><span id="more"></span><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.utils;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.Iterator;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 数据统计工具类</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> 胡湛霏</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2016-09-27</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataStatisticsUtils</span> {</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求和</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getSum</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">double</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">double</span> num : arr) {</span><br><span class="line">            sum += num;</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> sum;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求均值</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getMean</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="keyword">return</span> getSum(arr) / arr.length;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求众数</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getMode</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">Map</span>&lt;Double, Integer&gt; <span class="variable">map</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;Double, Integer&gt;();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; arr.length; i++) {</span><br><span class="line">            <span class="keyword">if</span> (map.containsKey(arr[i])) {</span><br><span class="line">                map.put(arr[i], map.get(arr[i]) + <span class="number">1</span>);</span><br><span class="line">            } <span class="keyword">else</span> {</span><br><span class="line">                map.put(arr[i], <span class="number">1</span>);</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        <span class="type">int</span> <span class="variable">maxCount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">double</span> <span class="variable">mode</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">        <span class="type">Iterator</span>&lt;Double&gt; <span class="variable">iter</span> <span class="operator">=</span> map.keySet().iterator();</span><br><span class="line">        <span class="keyword">while</span> (iter.hasNext()) {</span><br><span class="line">            <span class="type">double</span> <span class="variable">num</span> <span class="operator">=</span> iter.next();</span><br><span class="line">            <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> map.get(num);</span><br><span class="line">            <span class="keyword">if</span> (count &gt; maxCount) {</span><br><span class="line">                maxCount = count;</span><br><span class="line">                mode = num;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> mode;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求中位数</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getMedian</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">double</span>[] <span class="variable">tempArr</span> <span class="operator">=</span> Arrays.copyOf(arr, arr.length);</span><br><span class="line">        Arrays.sort(tempArr);</span><br><span class="line">        <span class="keyword">if</span> (tempArr.length % <span class="number">2</span> == <span class="number">0</span>) {</span><br><span class="line">            <span class="keyword">return</span> (tempArr[tempArr.length &gt;&gt; <span class="number">1</span>] + tempArr[(tempArr.length &gt;&gt; <span class="number">1</span>) - <span class="number">1</span>]) / <span class="number">2</span>;</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            <span class="keyword">return</span> tempArr[(tempArr.length &gt;&gt; <span class="number">1</span>)];</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求中列数</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getMidrange</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">double</span> <span class="variable">max</span> <span class="operator">=</span> arr[<span class="number">0</span>], min = arr[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; arr.length; i++) {</span><br><span class="line">            <span class="keyword">if</span> (arr[i] &gt; max) {</span><br><span class="line">                max = arr[i];</span><br><span class="line">            }</span><br><span class="line">            <span class="keyword">if</span> (arr[i] &lt; min) {</span><br><span class="line">                min = arr[i];</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> (min + max) / <span class="number">2</span>;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求四分位数</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 存放三个四分位数的数组</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span>[] <span class="title function_">getQuartiles</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">double</span>[] <span class="variable">tempArr</span> <span class="operator">=</span> Arrays.copyOf(arr, arr.length);</span><br><span class="line">        Arrays.sort(tempArr);</span><br><span class="line">        <span class="type">double</span>[] <span class="variable">quartiles</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">double</span>[<span class="number">3</span>];</span><br><span class="line">        <span class="comment">// 第二四分位数（中位数）</span></span><br><span class="line">        quartiles[<span class="number">1</span>] = getMedian(tempArr);</span><br><span class="line">        <span class="comment">// 求另外两个四分位数</span></span><br><span class="line">        <span class="keyword">if</span> (tempArr.length % <span class="number">2</span> == <span class="number">0</span>) {</span><br><span class="line">            quartiles[<span class="number">0</span>] = getMedian(Arrays.copyOfRange(tempArr, <span class="number">0</span>, tempArr.length / <span class="number">2</span>));</span><br><span class="line">            quartiles[<span class="number">2</span>] = getMedian(Arrays.copyOfRange(tempArr, tempArr.length / <span class="number">2</span>, tempArr.length));</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            quartiles[<span class="number">0</span>] = getMedian(Arrays.copyOfRange(tempArr, <span class="number">0</span>, tempArr.length / <span class="number">2</span>));</span><br><span class="line">            quartiles[<span class="number">2</span>] = getMedian(Arrays.copyOfRange(tempArr, tempArr.length / <span class="number">2</span> + <span class="number">1</span>, tempArr.length));</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> quartiles;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求极差</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getRange</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">double</span> <span class="variable">max</span> <span class="operator">=</span> arr[<span class="number">0</span>], min = arr[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; arr.length; i++) {</span><br><span class="line">            <span class="keyword">if</span> (arr[i] &gt; max) {</span><br><span class="line">                max = arr[i];</span><br><span class="line">            }</span><br><span class="line">            <span class="keyword">if</span> (arr[i] &lt; min) {</span><br><span class="line">                min = arr[i];</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> max - min;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求四分位数极差</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getQuartilesRange</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="keyword">return</span> getRange(getQuartiles(arr));</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求截断均值</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr 求值数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> p   截断量p，例如p的值为20，则截断20%（高10%，低10%）</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getTrimmedMean</span><span class="params">(<span class="type">double</span>[] arr, <span class="type">int</span> p)</span> {</span><br><span class="line">        <span class="type">int</span> <span class="variable">tmp</span> <span class="operator">=</span> arr.length * p / <span class="number">100</span>;</span><br><span class="line">        <span class="type">double</span>[] <span class="variable">tempArr</span> <span class="operator">=</span> Arrays.copyOfRange(arr, tmp, arr.length + <span class="number">1</span> - tmp);</span><br><span class="line">        <span class="keyword">return</span> getMean(tempArr);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求方差</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getVariance</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">double</span> <span class="variable">variance</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">double</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>, sum2 = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; arr.length; i++) {</span><br><span class="line">            sum += arr[i];</span><br><span class="line">            sum2 += arr[i] * arr[i];</span><br><span class="line">        }</span><br><span class="line">        variance = sum2 / arr.length - (sum / arr.length) * (sum / arr.length);</span><br><span class="line">        <span class="keyword">return</span> variance;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求绝对平均偏差(AAD)</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getAbsoluteAverageDeviation</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">double</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">double</span> <span class="variable">mean</span> <span class="operator">=</span> getMean(arr);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; arr.length; i++) {</span><br><span class="line">            sum += Math.abs(arr[i] - mean);</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> sum / arr.length;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求中位数绝对偏差(MAD)</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getMedianAbsoluteDeviation</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">double</span>[] <span class="variable">tempArr</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">double</span>[arr.length];</span><br><span class="line">        <span class="type">double</span> <span class="variable">median</span> <span class="operator">=</span> getMedian(arr);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; arr.length; i++) {</span><br><span class="line">            tempArr[i] = Math.abs(arr[i] - median);</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> getMedian(tempArr);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 求标准差</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span> <span class="title function_">getStandardDevition</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="type">double</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">double</span> <span class="variable">mean</span> <span class="operator">=</span> getMean(arr);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; arr.length; i++) {</span><br><span class="line">            sum += Math.sqrt((arr[i] - mean) * (arr[i] - mean));</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> (sum / (arr.length - <span class="number">1</span>));</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>下面还有一些做数据规范化的代码整理：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.utils;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 数据变化\-规范化工具类</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> 胡湛霏</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 2016-09-27</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DataConversionUtils</span> {</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 最小\-最大规范化</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 规范化后的数组</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span>[] <span class="title function_">minMaxNormalize</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="comment">// 拷贝数组</span></span><br><span class="line">        <span class="type">double</span>[] <span class="variable">tempArr</span> <span class="operator">=</span> Arrays.copyOf(arr, arr.length);</span><br><span class="line">        <span class="comment">// 找到最大值和最小值</span></span><br><span class="line">        <span class="type">double</span> <span class="variable">max</span> <span class="operator">=</span> tempArr[<span class="number">0</span>], min = tempArr[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; tempArr.length; i++) {</span><br><span class="line">            <span class="keyword">if</span> (tempArr[i] &gt; max) {</span><br><span class="line">                max = tempArr[i];</span><br><span class="line">            }</span><br><span class="line">            <span class="keyword">if</span> (tempArr[i] &lt; min) {</span><br><span class="line">                min = tempArr[i];</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 规范化</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; tempArr.length; i++) {</span><br><span class="line">            tempArr[i] = (tempArr[i] - min) / (max - min);</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> tempArr;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Z-score规范化</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 规范化后的数组</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span>[] <span class="title function_">zScoreNormalize</span><span class="params">(<span class="type">double</span>[] arr)</span> {</span><br><span class="line">        <span class="comment">// 拷贝数组</span></span><br><span class="line">        <span class="type">double</span>[] <span class="variable">tempArr</span> <span class="operator">=</span> Arrays.copyOf(arr, arr.length);</span><br><span class="line">        <span class="comment">// 求均值</span></span><br><span class="line">        <span class="type">double</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">double</span> num : tempArr) {</span><br><span class="line">            sum += num;</span><br><span class="line">        }</span><br><span class="line">        <span class="type">double</span> <span class="variable">mean</span> <span class="operator">=</span> sum / tempArr.length;</span><br><span class="line">        <span class="comment">// 求标准差</span></span><br><span class="line">        <span class="type">double</span> <span class="variable">sum2</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; tempArr.length; i++) {</span><br><span class="line">            sum2 += Math.sqrt((tempArr[i] - mean) * (tempArr[i] - mean));</span><br><span class="line">        }</span><br><span class="line">        <span class="type">double</span> <span class="variable">standardDivition</span> <span class="operator">=</span> sum2 / (tempArr.length - <span class="number">1</span>);</span><br><span class="line">        <span class="comment">// 标准化</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; tempArr.length; i++) {</span><br><span class="line">            tempArr[i] = (tempArr[i] - mean) / standardDivition;</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> tempArr;</span><br><span class="line"></span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 小数定标规范化</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arr</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 规范化后的数组</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">double</span>[] <span class="title function_">decimalsNormalize</span><span class="params">(<span class="type">double</span>[] arr)</span>{</span><br><span class="line">        <span class="comment">// 拷贝数组</span></span><br><span class="line">        <span class="type">double</span>[] <span class="variable">tempArr</span> <span class="operator">=</span> Arrays.copyOf(arr, arr.length);</span><br><span class="line">        <span class="comment">// 找到最大值</span></span><br><span class="line">        <span class="type">double</span> <span class="variable">max</span> <span class="operator">=</span> tempArr[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; tempArr.length; i++) {</span><br><span class="line">            <span class="keyword">if</span> (tempArr[i] &gt; max) {</span><br><span class="line">                max = tempArr[i];</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 确定j的值（j为使max(|v'|)&lt;1的最小整数）</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (Math.abs(max/Math.pow(<span class="number">10</span>,j))&gt;=<span class="number">1</span>){</span><br><span class="line">            j++;</span><br><span class="line">        }</span><br><span class="line">        <span class="comment">// 规范化</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; tempArr.length; i++) {</span><br><span class="line">            tempArr[i] = tempArr[i] / Math.pow(<span class="number">10</span>,j);</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> tempArr;</span><br><span class="line"></span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.bytelife.net/articles/35577.html</id>
    <link href="https://www.bytelife.net/articles/35577.html"/>
    <published>2016-09-27T15:28:29.000Z</published>
    <summary>
      <![CDATA[<p>做数据处理的人可能经常用到一些比如求中位数、众数、均值等等的常用数学方法，下面整理一些算法，由于都比较简单，不做过多的解释了，直接拿去用就好啦！</p>]]>
    </summary>
    <title>Java 实现数据统计的常用算法</title>
    <updated>2016-09-27T15:28:29.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="软件技巧" scheme="https://www.bytelife.net/categories/software/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <content>
      <![CDATA[<p>Ctrl+Shift + Enter，语句完成 “！”，否定完成，输入表达式时按 “！” 键<br>Ctrl+E，最近的文件<br>Ctrl+Shift+E，最近更改的文件 Shift+Click，可以关闭文件<br>Ctrl+[OR]，可以跑到大括号的开头与结尾<br>Ctrl+F12，可以显示当前文件的结构<br>Ctrl+F7，可以查询当前元素在当前文件中的引用，然后按 F3 可以选择<br>Ctrl+N，可以快速打开类 </p><span id="more"></span><p>Ctrl+Shift+N，可以快速打开文件 Alt+Q，可以看到当前方法的声明<br>Ctrl+P，可以显示参数信息<br>Ctrl+Shift+Insert，可以选择剪贴板内容并插入 Alt+Insert，可以生成构造器/Getter/Setter 等<br>Ctrl+Alt+V，可以引入变量。例如：new String (); 自动导入变量定义<br>Ctrl+Alt+T，可以把代码包在一个块内，例如：try/catch<br>Ctrl+Enter，导入包，自动修正<br>Ctrl+Alt+L，格式化代码<br>Ctrl+Alt+I，将选中的代码进行自动缩进编排，这个功能在编辑 JSP 文件时也可以工作<br>Ctrl+Alt+O，优化导入的类和包<br>Ctrl+R，替换文本<br>Ctrl+F，查找文本<br>Ctrl+Shift+Space，自动补全代码<br>Ctrl+ 空格，代码提示（与系统输入法快捷键冲突）<br>Ctrl+Shift+Alt+N，查找类中的方法或变量 Alt+Shift+C，最近的更改 Alt+Shift+Up/Down，上/下移一行 Shift+F6，重构 – 重命名<br>Ctrl+X，删除行<br>Ctrl+D，复制行<br>Ctrl+/或<br>Ctrl+Shift+/，注释（//或者/**/）<br>Ctrl+J，自动代码（例如：serr）<br>Ctrl+Alt+J，用动态模板环绕<br>Ctrl+H，显示类结构图（类的继承层次）<br>Ctrl+Q，显示注释文档 Alt+F1，查找代码所在位置 Alt+1，快速打开或隐藏工程面板<br>Ctrl+Alt+left/right，返回至上次浏览的位置 Alt+left/right，切换代码视图 Alt+Up/Down，在方法间快速移动定位<br>Ctrl+Shift+Up/Down，向上/下移动语句 F2 或 Shift+F2，高亮错误或警告快速定位 Tab，代码标签输入完成后，按 Tab，生成代码<br>Ctrl+Shift+F7，高亮显示所有该文本，按 Esc 高亮消失 Alt+F3，逐个往下查找相同文本，并高亮显示<br>Ctrl+Up/Down，光标中转到第一行或最后一行下<br>Ctrl+B/<br>Ctrl+Click，快速打开光标处的类或方法（跳转到定义处）<br>Ctrl+Alt+B，跳转到方法实现处<br>Ctrl+Shift+Backspace，跳转到上次编辑的地方<br>Ctrl+O，重写方法<br>Ctrl+Alt+Space，类名自动完成<br>Ctrl+Alt+Up/Down，快速跳转搜索结果<br>Ctrl+Shift+J，整合两行 Alt+F8，计算变量值<br>Ctrl+Shift+V，可以将最近使用的剪贴板内容选择插入到文本<br>Ctrl+Alt+Shift+V，简单粘贴 Shift+Esc，不仅可以把焦点移到编辑器上，而且还可以隐藏当前（或最后活动的）工具窗口 F12，把焦点从编辑器移到最近使用的工具窗口 Shift+F1，要打开编辑器光标字符处使用的类或者方法 Java 文档的浏览器<br>Ctrl+W，可以选择单词继而语句继而行继而函数<br>Ctrl+Shift+W，取消选择光标所在词 Alt+F7，查找整个工程中使用地某一个类、方法或者变量的位置<br>Ctrl+I，实现方法<br>Ctrl+Shift+U，大小写转化<br>Ctrl+Y，删除当前行 Shift+Enter，向下插入新行 psvm/sout，main/System.out.println();<br>Ctrl+J，查看更多<br>Ctrl+Shift+F，全局查找<br>Ctrl+F，查找/Shift+F3，向上查找/F3，向下查找<br>Ctrl+Shift+S，高级搜索<br>Ctrl+U，转到父类<br>Ctrl+Alt+S，打开设置对话框 Alt+Shift+Inert，开启 / 关闭列选择模式<br>Ctrl+Alt+Shift+S，打开当前项目 / 模块属性<br>Ctrl+G，定位行 Alt+Home，跳转到导航栏<br>Ctrl+Enter，上插一行<br>Ctrl+Backspace，按单词删除<br>Ctrl+”+/-”，当前方法展开、折叠<br>Ctrl+Shift+”+/-”，全部展开、折叠 <strong>【调试部分、编译】</strong><br>Ctrl+F2，停止 Alt+Shift+F9，选择 Debug Alt+Shift+F10，选择 Run<br>Ctrl+Shift+F9，编译<br>Ctrl+Shift+F10，运行<br>Ctrl+Shift+F8，查看断点 F8，步过 F7，步入 Shift+F7，智能步入 Shift+F8，步出 Alt+Shift+F8，强制步过 Alt+Shift+F7，强制步入 Alt+F9，运行至光标处<br>Ctrl+Alt+F9，强制运行至光标处 F9，恢复程序 Alt+F10，定位到断点<br>Ctrl+F8，切换行断点<br>Ctrl+F9，生成项目 Alt+1，项目 Alt+2，收藏 Alt+6，TODO Alt+7，结构<br>Ctrl+Shift+C，复制路径<br>Ctrl+Alt+Shift+C，复制引用，必须选择类名<br>Ctrl+Alt+Y，同步<br>Ctrl+~，快速切换方案（界面外观、代码风格、快捷键映射等菜单） Shift+F12，还原默认布局<br>Ctrl+Shift+F12，隐藏 / 恢复所有窗口<br>Ctrl+F4，关闭<br>Ctrl+Shift+F4，关闭活动选项卡<br>Ctrl+Tab，转到下一个拆分器<br>Ctrl+Shift+Tab，转到上一个拆分器 <strong>【重构】</strong><br>Ctrl+Alt+Shift+T，弹出重构菜单 Shift+F6，重命名 F6，移动 F5，复制 Alt+Delete，安全删除<br>Ctrl+Alt+N，内联 <strong>【查找】</strong><br>Ctrl+F，查找<br>Ctrl+R，替换 F3，查找下一个 Shift+F3，查找上一个<br>Ctrl+Shift+F，在路径中查找<br>Ctrl+Shift+R，在路径中替换<br>Ctrl+Shift+S，搜索结构<br>Ctrl+Shift+M，替换结构 Alt+F7，查找用法<br>Ctrl+Alt+F7，显示用法<br>Ctrl+F7，在文件中查找用法<br>Ctrl+Shift+F7，在文件中高亮显示用法</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/33262.html</id>
    <link href="https://www.bytelife.net/articles/33262.html"/>
    <published>2016-09-26T16:36:21.000Z</published>
    <summary>
      <![CDATA[<p>Ctrl+Shift + Enter，语句完成 “！”，否定完成，输入表达式时按 “！” 键<br>Ctrl+E，最近的文件<br>Ctrl+Shift+E，最近更改的文件 Shift+Click，可以关闭文件<br>Ctrl+[OR]，可以跑到大括号的开头与结尾<br>Ctrl+F12，可以显示当前文件的结构<br>Ctrl+F7，可以查询当前元素在当前文件中的引用，然后按 F3 可以选择<br>Ctrl+N，可以快速打开类 </p>]]>
    </summary>
    <title>IntelliJ Idea 常用快捷键列表</title>
    <updated>2016-09-26T16:36:21.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="Spring" scheme="https://www.bytelife.net/tags/Spring/"/>
    <content>
      <![CDATA[<p>在 Spring 中可以使用 Java 代码、XML 和自动化装配三种方式来装配 Bean。从便利性角度来说，最强大的还是 Spring 的自动化配置，如果 Spring 能够进行自动化装配的话，那何苦还要显式的将这些 Bean 装配在一起呢？ Spring 从两个角度来实现自动化装配： </p><ul><li>组件扫描：Spring 会自动发现应用上下文中所创建的 Bean； </li><li>自动装配：Spring 自动满足 bean 之间的依赖。</li></ul><p>为了阐述组件扫描和装配，我们需要创建几个 Bean，它们代表了一个音响系统中的组件。</p><span id="more"></span><h3 id="一、创建可被发现的bean"><a href="#一、创建可被发现的bean" class="headerlink" title="一、创建可被发现的bean"></a>一、创建可被发现的 bean</h3><p>定义 CD 的一个接口：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">CompactDisc</span> {</span><br><span class="line">    <span class="type">void</span> <span class="title function_">play</span><span class="params">()</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>CompactDisc 接口定义了 CD 播放器对一盘 CD 所能进行的操作。它将 CD 播放器的任意实现与 CD 本身的耦合降低到了最小的程度。 下面创建一个 CompactDisc 的实现：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Transfer</span> <span class="keyword">implements</span> <span class="title class_">CompactDisc</span> {</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">title</span> <span class="operator">=</span> <span class="string">"transfer"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">artist</span> <span class="operator">=</span> <span class="string">"周传雄/小刚"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">play</span><span class="params">()</span> {</span><br><span class="line">        System.out.println(<span class="string">"正在播放"</span>+artist+<span class="string">"的专辑："</span> + title);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里需要注意的是该类使用了 @Component 注解，表明该类会作为组件类，并告知 Spring 要为这个组件创建 bean。但是在这之前，由于默认组件扫描是不启用的。我们还需要显式配置一下 Spring，从而命令它去寻找带有 @Component 注解的类，并为其创建 bean。 下面的这个类展现了完成这件事情的最简介配置方式：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.ComponentScan;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayerConfig</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>如果没有其他配置的话，@ComponentScan 默认会扫描与配置类相同的包。因为 CDPlayerConfig 类位于 cn.javacodes.spring.beans.soundsystem 包中，因此 Spring 将会扫描这个包以及这个包下的所有子包，查找带有 @Component 注解标示的类，并在 Spring 中自动为其创建一个 bean。 当然，如果你更加倾向于使用 XML 来启用组件扫描的话，那么可以使用 Spring context 命名空间的 <span class="exturl" data-url="Y29udGV4dDpjb21wb25lbnQtc2Nhbg==">context:component-scan<i class="fa fa-external-link-alt"></i></span> 元素。</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:configurator</span>=<span class="string">"http://www.springframework.org/schema/c"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:avalon</span>=<span class="string">"http://www.springframework.org/schema/p"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:context</span>=<span class="string">"http://www.springframework.org/schema/context"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">"cn.javacodes.spring.beans.soundsystem"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>尽管我们可以使用 XML 的方案来启用组件扫描，但在后面的讨论中，更多的还是会使用基于 Java 的配置。 下面我们创建一个简单的 JUnit 测试，它会创建 Spring 上下文，并判断 CompactDisc 是不是真的创建出来了。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem;</span><br><span class="line"><span class="keyword">import</span> org.junit.Test;</span><br><span class="line"><span class="keyword">import</span> org.junit.runner.RunWith;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.test.context.ContextConfiguration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.test.context.junit4.SpringJUnit4ClassRunner;</span><br><span class="line"><span class="keyword">import</span> <span class="keyword">static</span> org.junit.Assert.assertNotNull;</span><br><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = CDPlayerConfig.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayerTest</span> {</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CompactDisc cd;</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">cdShouldNotBeNull</span><span class="params">()</span>{</span><br><span class="line">        assertNotNull(cd);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>该类使用了 Spring 的 SpringJUnit4ClassRunner，以便在测试开始的时候自动创建 Spring 的上下文。注解 @ContextConfiguration 会告诉它需要在 CDPlayerConfig 中加载配置。因为 CDPlayerConfig 类中包含了 @ComponentScan 注解，因此最终的应用上下文中应该包含 CompactDisc bean。 为了证明这一点，在测试代码中有一个 CompactDisc 属性，并且这个属性带有 @Autowired 注解，以便于将 CompactDisc bean 注入到测试代码中，有关与 @Autowired 注解的更多内容将在后面讲述。最后，有一个简单的测试方法断言 cd 属性不为 null。如果它不为 null 的话，就意味着 Spring 能够发现 CompactDisc 类，自动在 Spring 上下文中将其创建为 bean 并将其注入到了测试代码中。 这个代码应该能够通过测试，并以测试成功的颜色显示。</p><h3 id="二、为组件扫描的bean命名"><a href="#二、为组件扫描的bean命名" class="headerlink" title="二、为组件扫描的bean命名"></a>二、为组件扫描的 bean 命名</h3><p>Spring 上下文中所有的 bean 都有一个 id。在前面的例子中，即使我们并没有明确的给定 Transfer bean 一个 id，但 Spring 会根据类名为其给定一个 id。具体来讲，Spring 会默认给定一个将类名首字母变为小写的 id，例如上例中将给定的 id 为 transfer。 如果想为这个 bean 给定不同的 id，你需要做的就是将你所想要给定的 id 作为参数传递给 @Component 注解。例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"><span class="meta">@Component("transfer")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Transfer</span> <span class="keyword">implements</span> <span class="title class_">CompactDisc</span> {</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">title</span> <span class="operator">=</span> <span class="string">"transfer"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">artist</span> <span class="operator">=</span> <span class="string">"周传雄/小刚"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">play</span><span class="params">()</span> {</span><br><span class="line">        System.out.println(<span class="string">"正在播放"</span>+artist+<span class="string">"的专辑："</span> + title);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>还有另外一种为 bean 命名的方式，使用 Java 依赖注入规范中提供的 @Named 注解来为 bean 设置 id：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem;</span><br><span class="line"><span class="keyword">import</span> javax.inject.Named;</span><br><span class="line"><span class="meta">@Named("transfer")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Transfer</span> <span class="keyword">implements</span> <span class="title class_">CompactDisc</span> {</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">title</span> <span class="operator">=</span> <span class="string">"transfer"</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">artist</span> <span class="operator">=</span> <span class="string">"周传雄/小刚"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">play</span><span class="params">()</span> {</span><br><span class="line">        System.out.println(<span class="string">"正在播放"</span>+artist+<span class="string">"的专辑："</span> + title);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>Spring 支持将 @Named 作为 @Component 注解的替代方案。两者之间有一些细微的差别，不过大多数场景种它们使可以相互替换的。但是推荐使用 @Component 而不是 @Named，因为 @Component 注解看起来更加能够知道它是干什么的。</p><h3 id="三、设置组件扫描的基础包"><a href="#三、设置组件扫描的基础包" class="headerlink" title="三、设置组件扫描的基础包"></a>三、设置组件扫描的基础包</h3><p>现在我们已经知道，默认情况下 @ComponentScan 注解会扫描当前配置类所在的包及其子包，但我们可能更希望将配置类与其它类放在不同的包中，那么为了指定不同的基础包，可以将指定的包名作为参数传递给 @ComponentScan 注解即可：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan("cn.javacodes.spring.beans.soundsystem")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayerConfig</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>当然也可以更加清晰的指明其是基础包，使用 basePackages 属性：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(basePackages = "cn.javacodes.spring.beans.soundsystem")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayerConfig</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里我们发现 basePackages 属性是复数形式，我们猜测它是否可以指定多个基础包呢？答案是正确的，如果想要指定多个包，那么只需要将要扫描的包放到一个数组中即可：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(basePackages = {"cn.javacodes.spring.beans.soundsystem", "cn.javacodes.spring.beans.video"})</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayerConfig</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>上面的方式中，包名以简单的字符串进行表示，当然这是可以的。但是如果我们日后对代码进行重构，很有可能就会出现问题，所以这种通过简单的字符串来配置基础包的方式是不安全的。为了解决这个问题，我们可以将其指定为包中所包含的类或接口：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.configuration;</span><br><span class="line"><span class="keyword">import</span> cn.javacodes.spring.beans.soundsystem.CDPlayer;</span><br><span class="line"><span class="keyword">import</span> cn.javacodes.spring.beans.video.DVDPlayer;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.ComponentScan;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayerConfig</span> {</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>注意：这里不再使用 basePackages 属性，取而代之的是 basePackageClasses 属性。我们不再使用 String 类型的包名来指定包，而是为 basePackageClasses 属性设置的数组中包含了类。这些类所在的包会作为组件扫描的基础包。 当然，使用组件类直接给 basePackageClasses 属性并不是很好的方式，我们可以考虑在包中创建一个用来进行扫描的空标记接口。通过标记接口的方式，你依然能够保持对重构友好的接口引用，但是可以避免引用任何实际的应用程序代码。</p><h3 id="四、通过为bean添加注解实现自动装配"><a href="#四、通过为bean添加注解实现自动装配" class="headerlink" title="四、通过为bean添加注解实现自动装配"></a>四、通过为 bean 添加注解实现自动装配</h3><p>简单来说，自动装配就是让 Spring 自动满足 bean 依赖的一种方法，在满足依赖的过程中，会在 Spring 应用上下文中寻找匹配某个 bean 需求的其它 bean。为了声明要进行自动装配，我们可以考虑使用 Spring 的 @Autowired 注解。 比如下面的 CDPlayer 类，它的构造器使用了 @Autowired 注解，表明当 Spring 创建 CDPlayer bean 的时候，会通过这个构造器来进行实例化并会传入一个可以设置给 CompactDisc 类型的 bean：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayer</span> {</span><br><span class="line">    <span class="keyword">private</span> CompactDisc cd;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="type">public</span> <span class="title function_">CDPlayer</span><span class="params">(CompactDisc cd)</span> {</span><br><span class="line">        <span class="built_in">this</span>.cd = cd;</span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">play</span><span class="params">()</span>{</span><br><span class="line">        cd.play();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>@Autowired 属性不仅可以用在构造器上，还可以用在属性的 Setter 方法上。比如说，如果 CDPlayer 有一个 setCompactDisc () 方法，那么可以采用下面的方式来进行自动装配：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setCompactDisc</span><span class="params">(CompactDisc cd)</span>{</span><br><span class="line">    <span class="built_in">this</span>.cd = cd;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在 Spring 完成初始化 bean 之后，它会尽可能的去满足 bean 的依赖。实际上，Setter 方法并没有什么特殊之处，@Autowired 可以出现在任何方法上。 假如有且只有一个 bean 匹配依赖需求的话，那么这个 bean 将会被封装起来。 如果没有匹配的 bean，那么在应用上下文创建的时候，Spring 将会抛出一个异常。为了避免异常，可以将 @Autowired 的 required 属性设置为 false，Spring 会尝试执行自动匹配，但是如果没有匹配的 bean 的话，Spring 会让这个 bean 处于未装配的状态：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired(required = false)</span></span><br><span class="line"><span class="type">public</span> <span class="title function_">CDPlayer</span><span class="params">(CompactDisc cd)</span> {</span><br><span class="line">    <span class="built_in">this</span>.cd = cd;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>但是，把 required 属性设置为 false 的时候你需要注意，如果你的代码中没有 null 检查的话，这个处于未装配状态的属性有可能会出现空指针异常（NullPointerException）。 如果有多个 bean 都能满足依赖关系的话，Spring 会抛出一个异常，表明没有明确指定要选择哪个 bean 进行装配，有关于 Spring 自动化装配的歧义性的问题，我会在后续的文章中进行说明。 @Autowired 是 Spring 特有的注解，如果你不希望在代码中到处使用 Spring 特有的注解的话，那么可以考虑使用 @Inject 注解对其进行替换，例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem;</span><br><span class="line"><span class="keyword">import</span> javax.inject.Inject;</span><br><span class="line"><span class="keyword">import</span> javax.inject.Named;</span><br><span class="line"><span class="meta">@Named</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayer</span> {</span><br><span class="line">    <span class="keyword">private</span> CompactDisc cd;</span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="type">public</span> <span class="title function_">CDPlayer</span><span class="params">(CompactDisc cd)</span> {</span><br><span class="line">        <span class="built_in">this</span>.cd = cd;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">play</span><span class="params">()</span>{</span><br><span class="line">        cd.play();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>@Inject 注解来源于 Java 依赖注入规范，同 @Named 注解一样，@Inject 注解与 @Autowired 注解存在一些细微的差别，但大多数情况下它们可以进行相互替换。</p><h3 id="五、验证自动装配"><a href="#五、验证自动装配" class="headerlink" title="五、验证自动装配"></a>五、验证自动装配</h3><p>我们修改一下测试类 CDPlayerTest，使其能够借助 CDPlayer bean 播放 CD：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans.soundsystem;</span><br><span class="line"><span class="keyword">import</span> cn.javacodes.spring.beans.MediaPlayer;</span><br><span class="line"><span class="keyword">import</span> cn.javacodes.spring.configuration.CDPlayerConfig;</span><br><span class="line"><span class="keyword">import</span> org.junit.Rule;</span><br><span class="line"><span class="keyword">import</span> org.junit.Test;</span><br><span class="line"><span class="keyword">import</span> org.junit.contrib.java.lang.system.StandardOutputStreamLog;</span><br><span class="line"><span class="keyword">import</span> org.junit.runner.RunWith;</span><br><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.annotation.Autowired;</span><br><span class="line"><span class="keyword">import</span> org.springframework.test.context.ContextConfiguration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.test.context.junit4.SpringJUnit4ClassRunner;</span><br><span class="line"><span class="keyword">import</span> <span class="keyword">static</span> org.junit.Assert.assertEquals;</span><br><span class="line"><span class="keyword">import</span> <span class="keyword">static</span> org.junit.Assert.assertNotNull;</span><br><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = CDPlayerConfig.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CDPlayerTest</span> {</span><br><span class="line">    <span class="meta">@Rule</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">StandardOutputStreamLog</span> <span class="variable">log</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StandardOutputStreamLog</span>();</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> MediaPlayer player;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> CompactDisc cd;</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">cdShouldNotBeNull</span><span class="params">()</span> {</span><br><span class="line">        assertNotNull(cd);</span><br><span class="line">    }</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">play</span><span class="params">()</span> {</span><br><span class="line">        player.play();</span><br><span class="line">        assertEquals(<span class="string">"正在播放周传雄/小刚的专辑：transfern"</span>, log.getLog());</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>该类中，除了注入 CompactDisc，还将 CDPlayer bean 注入到了测试代码中（更为通用的 MediaPlayer 类型）。在 play () 方法中，我们可以调用 CDPlayer 的 play () 方法并断言它的行为与你的预期是否一致。 自动化装配 Bean 还有更多的细节，我会在后续的文章中进行阐述。</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/33415.html</id>
    <link href="https://www.bytelife.net/articles/33415.html"/>
    <published>2016-09-17T16:14:00.000Z</published>
    <summary>
      <![CDATA[<p>在 Spring 中可以使用 Java 代码、XML 和自动化装配三种方式来装配 Bean。从便利性角度来说，最强大的还是 Spring 的自动化配置，如果 Spring 能够进行自动化装配的话，那何苦还要显式的将这些 Bean 装配在一起呢？ Spring 从两个角度来实现自动化装配： </p>
<ul>
<li>组件扫描：Spring 会自动发现应用上下文中所创建的 Bean； </li>
<li>自动装配：Spring 自动满足 bean 之间的依赖。</li>
</ul>
<p>为了阐述组件扫描和装配，我们需要创建几个 Bean，它们代表了一个音响系统中的组件。</p>]]>
    </summary>
    <title>Spring 学习笔记之自动化装配 Bean</title>
    <updated>2016-09-17T16:14:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="软件技巧" scheme="https://www.bytelife.net/categories/software/"/>
    <category term="Linux" scheme="https://www.bytelife.net/tags/Linux/"/>
    <content>
      <![CDATA[<p>现在的笔记本显卡多为 Intel 核心显卡 + Nvidia 显卡的组合模式，如果没能正确的管理 Linux 操作系统的双显卡驱动，可能会导致发热和性能差的问题。由于我们在 Linux 中极少运行对显卡要求特别高的 3D 程序，因此并不需要一直开启 Nvidia 独立显卡，只在需要它的时候再开启就好，Bumblebee 提供了一个很棒的解决方案。 本文以 Fedora24 为例：</p><span id="more"></span><h3 id="一、确认硬件环境"><a href="#一、确认硬件环境" class="headerlink" title="一、确认硬件环境"></a>一、确认硬件环境</h3><p>当然，首先你得确定你的电脑确实是 Intel+Nvidia 的双显卡组合模式（optimus），查看方法很简单，只需要一条命令：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lspci | egrep <span class="string">'VGA|3D'</span></span><br></pre></td></tr></tbody></table></figure><p>如果你的电脑是双显卡，将会输出类似如下的信息：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139211553705.png"></p><h3 id="二、禁用Nouveau"><a href="#二、禁用Nouveau" class="headerlink" title="二、禁用Nouveau"></a>二、禁用 Nouveau</h3><p>Nouveau 是一个大多数 Linux 发行版都会自带的 Nvidia 第三方驱动程序，不过 Nouveau 只支持 2D，不支持 3D，可想而知其性能有多差。安装 Bumblebee 之前必须禁用 Nouveau，否则会出现问题，方法如下： 运行命令：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> gedit /etc/modprobe.d/blacklist.conf</span><br></pre></td></tr></tbody></table></figure><p>在打开的窗口中加上如下内容：</p><blockquote><p>blacklist nouveau options nouveau modeset=0</p></blockquote><p>保存并重新启动操作系统。</p><h3 id="三、安装Bumblebee"><a href="#三、安装Bumblebee" class="headerlink" title="三、安装Bumblebee"></a>三、安装 Bumblebee</h3><p>不同版本的 Fedora 稍有不同 开源驱动（Nouveau）命令如下（如果安装此版本，则无需执行上面的第二步）：<br><strong>fedora 22:</strong></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf -y --nogpgcheck install http://install.linux.ncsu.edu/pub/yum/itecs/public/bumblebee/fedora22/noarch/bumblebee-release-1.2-1.noarch.rpm</span><br></pre></td></tr></tbody></table></figure><p><strong>fedora 23:</strong></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf -y --nogpgcheck install http://install.linux.ncsu.edu/pub/yum/itecs/public/bumblebee/fedora23/noarch/bumblebee-release-1.2-1.noarch.rpm</span><br></pre></td></tr></tbody></table></figure><p><strong>fedora 24:</strong></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf -y --nogpgcheck install http://install.linux.ncsu.edu/pub/yum/itecs/public/bumblebee/fedora24/noarch/bumblebee-release-1.2-1.noarch.rpm</span><br></pre></td></tr></tbody></table></figure><p>闭源驱动（Nvidia）命令如下：（推荐） <strong>fedora 22:</strong></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf -y --nogpgcheck install http://install.linux.ncsu.edu/pub/yum/itecs/public/bumblebee-nonfree/fedora22/noarch/bumblebee-nonfree-release-1.2-1.noarch.rpm</span><br></pre></td></tr></tbody></table></figure><p><strong>fedora 23:</strong></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf -y --nogpgcheck install http://install.linux.ncsu.edu/pub/yum/itecs/public/bumblebee-nonfree/fedora23/noarch/bumblebee-nonfree-release-1.2-1.noarch.rpm</span><br></pre></td></tr></tbody></table></figure><p><strong>fedora 24:</strong></p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf -y --nogpgcheck install http://install.linux.ncsu.edu/pub/yum/itecs/public/bumblebee-nonfree/fedora24/noarch/bumblebee-nonfree-release-1.2-1.noarch.rpm</span><br></pre></td></tr></tbody></table></figure><h3 id="四、安装Bumblebee-nvidia"><a href="#四、安装Bumblebee-nvidia" class="headerlink" title="四、安装Bumblebee-nvidia"></a>四、安装 Bumblebee-nvidia</h3><p>接下来执行（根据系统选择）： 1、32 位系统：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf install bumblebee-nvidia bbswitch-dkms primus kernel-devel</span><br></pre></td></tr></tbody></table></figure><p>2、64 位系统：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf install bumblebee-nvidia bbswitch-dkms VirtualGL.x86\_64 VirtualGL.i686 primus.x86\_64 primus.i686 kernel-devel</span><br></pre></td></tr></tbody></table></figure><p>耐心的等待下载并安装完成，重新启动操作系统。</p><h3 id="五、检查是否成功"><a href="#五、检查是否成功" class="headerlink" title="五、检查是否成功"></a>五、检查是否成功</h3><p>1、运行如下命令再次查看显卡 pci 信息：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lspci | egrep <span class="string">'VGA|3D'</span></span><br></pre></td></tr></tbody></table></figure><p>检查 nvidia 显卡信息后面是否是 (rev ff)，如果是，则证明 Bumblebee 已经运行； 2、运行如下命令比较帧数： 首先运行：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">glxgears</span><br></pre></td></tr></tbody></table></figure><p>查看终端中输出的 FPS 数值，此处的数值为使用核心显卡时的数值：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139212547585.png"><br>运行：</p><p>optirun glxgears</p><p>查看终端 FPS 信息，此时如果能够正常运行，则为 Nvidia 显卡在工作：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139212622535.png"><br> 明显看出 FPS 从 60 飙升到 2000，证明 NVIDIA 生效；</p><h3 id="六、使用Bumblebee软件"><a href="#六、使用Bumblebee软件" class="headerlink" title="六、使用Bumblebee软件"></a>六、使用 Bumblebee 软件</h3><p>常规用法：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">optirun [options] application [application-parameters]</span><br></pre></td></tr></tbody></table></figure><p>下面举几个例子： </p><p>1、使用独立显卡运行 Windows（通过 Wine）应用程序：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ optirun wine application.exe</span><br></pre></td></tr></tbody></table></figure><p>我就是用这种方式来运行魔兽世界的，虽然不总玩，哈哈，谁说 Linux 不可以玩游戏！ </p><p>2、打开 nvidia 显卡设置</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ optirun -b none nvidia-settings -c :8</span><br></pre></td></tr></tbody></table></figure><p>3、使用独立显卡运行 Java 程序</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ optirun -b primus java -jar /PATH/TO/Minecraft.jar</span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.bytelife.net/articles/6310.html</id>
    <link href="https://www.bytelife.net/articles/6310.html"/>
    <published>2016-09-13T04:09:05.000Z</published>
    <summary>
      <![CDATA[<p>现在的笔记本显卡多为 Intel 核心显卡 + Nvidia 显卡的组合模式，如果没能正确的管理 Linux 操作系统的双显卡驱动，可能会导致发热和性能差的问题。由于我们在 Linux 中极少运行对显卡要求特别高的 3D 程序，因此并不需要一直开启 Nvidia 独立显卡，只在需要它的时候再开启就好，Bumblebee 提供了一个很棒的解决方案。 本文以 Fedora24 为例：</p>]]>
    </summary>
    <title>Fedora24（Linux）Nvidia 双显卡驱动解决方案（Bumblebee）</title>
    <updated>2016-09-13T04:09:05.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="建站" scheme="https://www.bytelife.net/categories/website/"/>
    <category term="Wordpress" scheme="https://www.bytelife.net/tags/Wordpress/"/>
    <content>
      <![CDATA[<blockquote><p>原文链接：<span class="exturl" data-url="aHR0cDovL3h1aGVodWFuLmNvbS8=">蔓草札记<i class="fa fa-external-link-alt"></i></span> » <span class="exturl" data-url="aHR0cDovL3h1aGVodWFuLmNvbS8yMDI3Lmh0bWw=">WordPress 博客同步到 CSDN 插件<i class="fa fa-external-link-alt"></i></span></p></blockquote><p>在没用 WordPress 博客前就在 CSDN 写了一些日志，后来自己搭建博客后，就希望能够将博客同步到 CSDN 中，无奈 CSDN 在 2011 年 7 月宣布暂不支持 Windows&nbsp;Live&nbsp;Writer 发布博文，直到在 2014 年 5 月 29 日才宣布重新让插件同步成为可能，不过目前网上搜到的插件现在都已经失效了。 </p><span id="more"></span><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139163582293.png"></p><p>这两天闲时测试了下之前润物无声写的，也在网上查了不少关于 MetaWeblog 和 xmlrpc 的说明，但对解决问题都没什么大的帮助，最后只能一点点调试了。功夫不负有心人，终于在今天下午的时候，调试成功了。在 wp2metaweblog 插件的基础上做了些修改，并整理了下代码，防止和原 wp2metaweblog 插件冲突，改名为 WP2CSDNBlog 插件。 WP2CSDNBlog 插件是同步 WordPress 博客日志到 CSDN 博客的，理论上也可用在所有支持 Metaweblog API 的博客系统中，安装后需要到设置页面填下同步需要的信息。 </p><p><strong>Version 1.1 支持功能：</strong> </p><ol><li>支持发布 wordpress 的博文到 CSDN 博客 </li><li>支持将 WordPress 中文章链接发布到 CSDN 博客 </li><li>支持发布文章的自动归类</li></ol><p><strong>Version 1.2 支持功能：</strong> </p><ol><li>同时支持发布 wordpress 的博文到网易和新浪博客</li></ol><p><strong>Version 1.3 支持功能：</strong> </p><ol><li>支持将 wordpress 的定时发布文章发布到设置的博客</li></ol><p><strong>Version 1.4 支持功能：</strong> </p><ol><li>完善对定时发布功能的支持 </li><li>完善同步文章排版格式问题</li></ol><p><strong>Version 1.5 支持功能：</strong> </p><ol><li>修改 CSDN 博客同步文章排版格式</li></ol><p><strong>安装使用：</strong> </p><ol><li>上传整个文件夹 wp2csdnblog 到 /wp-content/plugins/ 目录 </li><li>后台控制面板 “插件 (Plugins)” 中激活 wp2csdnblog 插件 </li><li>后台控制面板 “配置 (Settings)-&gt;wp2csdnblog” 中配置 CSDN 账户信息</li></ol><p><strong>软件配置界面（V1.1 版本）:</strong><br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139164713418.png"></p><p><strong>代码地址：</strong><br><span class="exturl" data-url="aHR0cHM6Ly9naXRjYWZlLmNvbS94aGhqaW4vV1AyQ1NETkJsb2cvdHJlZS9tYXN0ZXI=">https://gitcafe.com/xhhjin/WP2CSDNBlog/tree/master<i class="fa fa-external-link-alt"></i></span> </p><p><strong>代码地址：</strong><br><span class="exturl" data-url="aHR0cHM6Ly9jb2RpbmcubmV0L3UveGhoamluL3AvV1AyQ1NETkJsb2c=">https://coding.net/u/xhhjin/p/WP2CSDNBlog<i class="fa fa-external-link-alt"></i></span> </p><p><strong>百度云下载：</strong><br><span class="exturl" data-url="aHR0cDovL3Bhbi5iYWlkdS5jb20vcy8xYzBkSHFUeQ==">http://pan.baidu.com/s/1c0dHqTy<i class="fa fa-external-link-alt"></i></span>（V1.1 版本） </p><p>建议通过代码地址下载，百度云下载后续不一定更新。 WP2CSDNBlog 插件，开始同步吧 :） </p><p><strong>2015.05.12 更新：</strong><br>插件已上传 WordPress plugins，可以在 WordPress 后台安装插件页面直接搜索 “wp2csdnblog” 安装。 WordPress plugins 地址：<span class="exturl" data-url="aHR0cHM6Ly93b3JkcHJlc3Mub3JnL3BsdWdpbnMvd3AyY3NkbmJsb2cv">https://wordpress.org/plugins/wp2csdnblog/<i class="fa fa-external-link-alt"></i></span> </p><p><strong>2015.06.14 更新：</strong><br>更新插件到 V1.2 版本，支持同时同步 WordPress 博客内容到新浪博客和网易博客。 新浪博客设置的用户名是博客的登录名，如果登录名是邮箱，不要忘了邮箱后缀。 网易博客设置的用户名根据邮箱区分。例如：您的邮箱帐号是 163 的：<span class="exturl" data-url="bWFpbHRvOmJsb2dfYWRtaW5AMTYzLmNvbQ==">blog_admin@163.com<i class="fa fa-external-link-alt"></i></span>，则用户名是 blog_admin，如果你的邮箱账号是 126 的：<span class="exturl" data-url="bWFpbHRvOmJsb2dfYWRtaW5AMTI2LmNvbQ==">blog_admin@126.com<i class="fa fa-external-link-alt"></i></span>，则用户名是 blog_admin@126，其余 yeah.net、188 等非 163 邮箱用户，请参考 126 邮箱的格式。 </p><p><strong>2015.06.22 更新：</strong><br>更新插件到 V1.3 版本，支持将 wordpress 的定时发布文章发布到设置的博客。 </p><p><strong>2015.07.05 更新：</strong><br>更新插件到 V1.4 版本，完善对定时发布文章功能的支持，改善同步文章的排版格式。 </p><p><strong>2016.05.04 更新：</strong><br>更新插件到 V1.5 版本，修改 CSDN 博客同步文章排版格式。 最近一段时间同步 CSDN 和网易博客的文章都没有成功，是 CSDN 和网易服务器返回错误导致的。大家可以用 Windows Live Writer 或者 Word 来发文章测试，如果能发布成功，则说明是插件的问题，反之很可能是服务器的问题，然后建议将服务器返回的错误在下面对应文章下留言。</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/49539.html</id>
    <link href="https://www.bytelife.net/articles/49539.html"/>
    <published>2016-09-10T01:49:10.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>原文链接：<a href="http://xuhehuan.com/">蔓草札记</a> » <a href="http://xuhehuan.com/2027.html">WordPress 博客同步到 CSDN 插件</a></p>
</blockquote>
<p>在没用 WordPress 博客前就在 CSDN 写了一些日志，后来自己搭建博客后，就希望能够将博客同步到 CSDN 中，无奈 CSDN 在 2011 年 7 月宣布暂不支持 Windows&nbsp;Live&nbsp;Writer 发布博文，直到在 2014 年 5 月 29 日才宣布重新让插件同步成为可能，不过目前网上搜到的插件现在都已经失效了。 </p>]]>
    </summary>
    <title>WordPress 博客同步到 CSDN 插件</title>
    <updated>2017-03-15T15:34:01.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <content>
      <![CDATA[<p>在 Web 开发过程中，利用 JSON 可以帮助我们更加方便的开发我们的应用。那么在 Java 语言中，如何实现 Java 实例与 JSON 之间的相互转换（序列化与反序列化）呢？目前流行的 JSON 第三方类库有 Jackson、Gson、Fastjson 等，本文将简单介绍如何使用 Jackson 进行 JSON 的解析与序列化。</p><span id="more"></span><h3 id="一、获取Jackson"><a href="#一、获取Jackson" class="headerlink" title="一、获取Jackson"></a>一、获取 Jackson</h3><p>获取 Jackson 可以通过 Maven 或直接下载 jar 包两种方式，通常我们只需要下载 Jackson 的 jackson-core 核心包即可，如果希望使用更多功能（例如注解），还需要下载另外的 jar 包。Jackson 为我们提供了以下 jar 包：</p><ol><li>jackson-core.jar——核心包（必须），提供基于 “流模式” 解析的 API。</li><li>jackson-databind——数据绑定包（可选），提供基于 “对象绑定” 和 “树模型” 相关 API。</li><li>jackson-annotations——注解包（可选），提供注解功能。</li></ol><p>目前 Jackson 的最新版本为 2.8.2。</p><h4 id="1、通过Maven获取"><a href="#1、通过Maven获取" class="headerlink" title="1、通过Maven获取"></a>1、通过 Maven 获取</h4><p>使用 Maven 获取 Jackson 十分方便，只需要在 pom.xml 中加入如下依赖即可：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.fasterxml.jackson.core<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jackson-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.8.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.fasterxml.jackson.core<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jackson-annotations<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.8.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.fasterxml.jackson.core<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jackson-databind<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.8.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><h4 id="2、直接下载jar包"><a href="#2、直接下载jar包" class="headerlink" title="2、直接下载jar包"></a>2、直接下载 jar 包</h4><p>官方为我们提供了两种直接下载 jar 包的途径：</p><ol><li>Central Maven repository：<span class="exturl" data-url="aHR0cDovL3JlcG8xLm1hdmVuLm9yZy9tYXZlbjIvY29tL2Zhc3RlcnhtbC9qYWNrc29uL2NvcmUv">http://repo1.maven.org/maven2/com/fasterxml/jackson/core/<i class="fa fa-external-link-alt"></i></span></li><li>Wiki：<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL0Zhc3RlclhNTC9qYWNrc29uLWNvcmUvd2lraQ==">https://github.com/FasterXML/jackson-core/wiki<i class="fa fa-external-link-alt"></i></span></li></ol><h3 id="二、用于测试的Java类"><a href="#二、用于测试的Java类" class="headerlink" title="二、用于测试的Java类"></a>二、用于测试的 Java 类</h3><p>为了方便我们学习和测试 Jackson，我们首先准备一个 Java 类 User，代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//JSON序列化和反序列化使用的User类</span></span><br><span class="line"><span class="keyword">import</span> java.util.Date;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> {</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> Integer age;</span><br><span class="line">    <span class="keyword">private</span> Date birthday;</span><br><span class="line">    <span class="keyword">private</span> String email;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">String</span> <span class="title function_">getName</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> {</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">Integer</span> <span class="title function_">getAge</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> age;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setAge</span><span class="params">(Integer age)</span> {</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">Date</span> <span class="title function_">getBirthday</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> birthday;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setBirthday</span><span class="params">(Date birthday)</span> {</span><br><span class="line">        <span class="built_in">this</span>.birthday = birthday;</span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">String</span> <span class="title function_">getEmail</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> email;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setEmail</span><span class="params">(String email)</span> {</span><br><span class="line">        <span class="built_in">this</span>.email = email;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="三、JSON序列化（Java对象转JSON）"><a href="#三、JSON序列化（Java对象转JSON）" class="headerlink" title="三、JSON序列化（Java对象转JSON）"></a>三、JSON 序列化（Java 对象转 JSON）</h3><p>在使用 Jackson 之前，我们先来了解一下 Jackson 中的一个核心类：ObjectMapper，我们几乎所有的操作都在使用该类的 API。 ObjectMapper 有多个 JSON 序列化的方法，可以把 JSON 字符串保存 File、OutputStream 等不同的介质中。</p><ul><li>writeValue (File&nbsp;arg0,&nbsp;Object&nbsp;arg1) 把 arg1 转成 json 序列，并保存到 arg0 文件中。</li><li>writeValue (OutputStream&nbsp;arg0,&nbsp;Object&nbsp;arg1) 把 arg1 转成 json 序列，并保存到 arg0 输出流中。</li><li>writeValueAsBytes (Object&nbsp;arg0) 把 arg0 转成 json 序列，并把结果输出成字节数组。</li><li>writeValueAsString (Object&nbsp;arg0) 把 arg0 转成 json 序列，并把结果输出成字符串。</li></ul><p>这些方法使用起来都十分简单，为了简洁直观的介绍 Jackson 的使用方法，我们只介绍 writeValueAsString (Object obj) 的使用方法，用于将 Java 对象转换为一个 JSON 字符串，代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.jackson.test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.core.JsonProcessingException;</span><br><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.databind.ObjectMapper;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.text.ParseException;</span><br><span class="line"><span class="keyword">import</span> java.text.SimpleDateFormat;</span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by huzha on 2016-09-07.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JacksonTest</span> {</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> JsonProcessingException, ParseException {</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">        user.setName(<span class="string">"Jeffrey"</span>);</span><br><span class="line">        user.setEmail(<span class="string">"xxx@xxx.com"</span>);</span><br><span class="line">        user.setAge(<span class="number">20</span>);</span><br><span class="line">        user.setBirthday(<span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">"yyyy-MM-dd"</span>).parse(<span class="string">"1995-08-23"</span>));</span><br><span class="line"></span><br><span class="line">        </span><br><span class="line">        <span class="type">ObjectMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line">        <span class="comment">//User类转JSON</span></span><br><span class="line">        <span class="comment">//输出结果：{"name":"Jeffrey","age":20,"birthday":809107200000,"email":"xxx@xxx.com"}</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> mapper.writeValueAsString(user);</span><br><span class="line">        System.out.println(json);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//Java集合转JSON</span></span><br><span class="line">        <span class="comment">//输出结果：[{"name":"Jeffrey","age":20,"birthday":809107200000,"email":"xxx@xxx.com"}]</span></span><br><span class="line">        <span class="type">List</span>&lt;User&gt; <span class="variable">users</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;User&gt;();</span><br><span class="line">        users.add(user);</span><br><span class="line">        <span class="type">String</span> <span class="variable">jsonlist</span> <span class="operator">=</span> mapper.writeValueAsString(users);</span><br><span class="line">        System.out.println(jsonlist);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="四、JSON反序列化（JSON转Java对象）"><a href="#四、JSON反序列化（JSON转Java对象）" class="headerlink" title="四、JSON反序列化（JSON转Java对象）"></a>四、JSON 反序列化（JSON 转 Java 对象）</h3><p>Jackson 为我们提供了许多 JSON 反序列化的方法，其中比较常用的方法如下：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139161048675.png"></p><p> 我们可以将文件、URL、字符串、流、字节数组等作为数据源进行解析，废话不多说，看例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.jackson.test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.fasterxml.jackson.databind.ObjectMapper;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.text.ParseException;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by huzha on 2016-09-07.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JacksonTest</span> {</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException, ParseException {</span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> <span class="string">"{"</span>name<span class="string">":"</span>Jeffrey<span class="string">","</span>age<span class="string">":20,"</span>birthday<span class="string">":809107200000,"</span>email<span class="string">":"</span>xxx<span class="meta">@xxx</span>.com<span class="string">"}"</span>;</span><br><span class="line">        <span class="type">ObjectMapper</span> <span class="variable">mapper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line">        <span class="type">User</span> <span class="variable">user</span> <span class="operator">=</span> mapper.readValue(json,User.class);</span><br><span class="line">        <span class="comment">// 输出结果：User{name='Jeffrey', age=20, birthday=Wed Aug 23 00:00:00 CST 1995, email='xxx@xxx.com'}</span></span><br><span class="line">        System.out.println(user.toString());</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="五、JSON注解"><a href="#五、JSON注解" class="headerlink" title="五、JSON注解"></a>五、JSON 注解</h3><p>Jackson 提供了一系列注解，方便对 JSON 序列化和反序列化进行控制，下面介绍一些常用的注解。 @JsonIgnore 此注解用于属性上，作用是进行 JSON 操作时忽略该属性。 @JsonFormat 此注解用于属性上，作用是把 Date 类型直接转化为想要的格式，如 @JsonFormat (pattern = “yyyy-MM-dd HH-mm-ss”)。 @JsonProperty 此注解用于属性上，作用是把该属性的名称序列化为另外一个名称，如把 trueName 属性序列化为 name，@JsonProperty (“name”)。 例如我们对 User 类进行一些修改，修改部分代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//序列化时忽略此属性</span></span><br><span class="line"><span class="meta">@JsonIgnore</span></span><br><span class="line"><span class="keyword">private</span> Integer age;</span><br><span class="line"></span><br><span class="line"><span class="comment">//将日期进行格式化</span></span><br><span class="line"><span class="meta">@JsonFormat(pattern = "yyyy-MM-dd")</span></span><br><span class="line"><span class="keyword">private</span> Date birthday;</span><br><span class="line"></span><br><span class="line"><span class="comment">//将email序列化为e-mail</span></span><br><span class="line"><span class="meta">@JsonProperty("e-mail")</span></span><br><span class="line"><span class="keyword">private</span> String email;</span><br></pre></td></tr></tbody></table></figure><p>再次通过上述序列化方法进行转换，查看输出结果为：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span><span class="attr">"name"</span><span class="punctuation">:</span><span class="string">"Jeffrey"</span><span class="punctuation">,</span><span class="attr">"birthday"</span><span class="punctuation">:</span><span class="string">"1995-08-22"</span><span class="punctuation">,</span><span class="attr">"e-mail"</span><span class="punctuation">:</span><span class="string">"xxx@xxx.com"</span><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>可以看到注解已经起了效果。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>在 Java 中使用 Jackson 解析和序列化 JSON 是十分方便的，而且 Jackson 在性能上也是十分出色的。使用 Jackson 操作 JSON 的核心是 ObjectMapper 类，我们几乎所有的操作都是通过这个类的实例来进行的。当然，如果你有兴趣，也可以尝试使用 Gson 或 FastJson 操作 JSON，他们的使用方法大致上是相同的。</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/47343.html</id>
    <link href="https://www.bytelife.net/articles/47343.html"/>
    <published>2016-09-07T14:12:29.000Z</published>
    <summary>
      <![CDATA[<p>在 Web 开发过程中，利用 JSON 可以帮助我们更加方便的开发我们的应用。那么在 Java 语言中，如何实现 Java 实例与 JSON 之间的相互转换（序列化与反序列化）呢？目前流行的 JSON 第三方类库有 Jackson、Gson、Fastjson 等，本文将简单介绍如何使用 Jackson 进行 JSON 的解析与序列化。</p>]]>
    </summary>
    <title>使用 Jackson 解析 JSON 和序列化</title>
    <updated>2016-09-07T14:12:29.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="面试" scheme="https://www.bytelife.net/tags/%E9%9D%A2%E8%AF%95/"/>
    <content>
      <![CDATA[<p>作为程序员在面试的时候碰到的问题还是很多的，不光要掌握像数组、链表、栈、队列和树这样基本的数据结构，在面试中还会碰到数据结构和算法问题。所以小编在这里告诉大家，下面的面试题目很有可能被碰到，一起来看看吧！ </p><span id="more"></span><h2 id="1-如何只扫描一遍就找到位于一个链表正中间的元素？"><a href="#1-如何只扫描一遍就找到位于一个链表正中间的元素？" class="headerlink" title="1. 如何只扫描一遍就找到位于一个链表正中间的元素？"></a>1. 如何只扫描一遍就找到位于一个链表正中间的元素？</h2><p>这是最受欢迎的算法题之一，经常在电话面试中被问到。很多程序员会想，要知道链表的长度，就要先扫描一遍链表，然后在第二遍中取其正中的元素。所以被要求只扫描一遍就解决问题的时候他们就会很困惑。要解决这个问题，你要维护两个指针。一个每次往下走一个结点，而另一个每次走两个结点。那么当走的快的指针到达链表末尾时，另一个指针就正好指在链表的正中间。 </p><h2 id="2-如何确定一个链表中是否存在环？"><a href="#2-如何确定一个链表中是否存在环？" class="headerlink" title="2. 如何确定一个链表中是否存在环？"></a>2. 如何确定一个链表中是否存在环？</h2><p>这跟第一题有点类似。同样地维护两个指针，分别以每步一个结点和每步两个结点的速度走，那么如果当某一步结束以后两个指针指向同一个结点，就说明我们找到了一个环。 </p><h2 id="3-如何只扫描一遍就找到一个链表倒数第三个元素？"><a href="#3-如何只扫描一遍就找到一个链表倒数第三个元素？" class="headerlink" title="3. 如何只扫描一遍就找到一个链表倒数第三个元素？"></a>3. 如何只扫描一遍就找到一个链表倒数第三个元素？</h2><p>这也是一个很经典的链表题，也可以用两个指针的方法来解决。我们让第一个指针先走，当它走出三步以后再让第二个指针开始走。那么当第一个指针到达链表的末尾时，第二个指针就正好指向链表中的倒数第三个元素。 </p><h2 id="4-如何找到一个含有-1-100-的数组中唯一出现两次的元素？"><a href="#4-如何找到一个含有-1-100-的数组中唯一出现两次的元素？" class="headerlink" title="4. 如何找到一个含有 1~100 的数组中唯一出现两次的元素？"></a>4. 如何找到一个含有 1~100 的数组中唯一出现两次的元素？</h2><p>这是一个挺简单的算法题。你可以把数组里面所有元素加起来，再减去 1~100 的和，就能得到那个重复的元素。当然你也可以暴力地去比对每一对元素，不过这样的复杂度是 O (N^2)。 </p><h2 id="5-如何在-Java-中反转一个字符串？"><a href="#5-如何在-Java-中反转一个字符串？" class="headerlink" title="5. 如何在 Java 中反转一个字符串？"></a>5. 如何在 Java 中反转一个字符串？</h2><p>这是我最喜欢的问题之一。因为 String 是编程中最重要的类型之一，所以在技术面试中你会遇到许多跟字符串有关的问题。Java 和其他编程语言都有许多不同的方式可以反转一个字符串，所以面试官往往会禁止你使用某些特性，比如 reverse () 和 StringBuffer。之后他还可能会问如何用递归来反转字符串。 </p><h2 id="6-如何用冒泡排序对数组进行排序？"><a href="#6-如何用冒泡排序对数组进行排序？" class="headerlink" title="6. 如何用冒泡排序对数组进行排序？"></a>6. 如何用冒泡排序对数组进行排序？</h2><p>在技术面试中我总是会问一些关于搜索和排序的问题。冒泡排序是最简单的排序算法之一，但如果你让一个候选人现场写冒泡排序，这能够很好地衡量他的编程技能。 </p><h2 id="7-栈和队列这两种数据结构之间的区别是什么？"><a href="#7-栈和队列这两种数据结构之间的区别是什么？" class="headerlink" title="7. 栈和队列这两种数据结构之间的区别是什么？"></a>7. 栈和队列这两种数据结构之间的区别是什么？</h2><p>这是一个经典的数据结构问题，应该没有人不知道的吧？不管怎么说，最主要的区别在于栈是后进先出（LIFO），而队列是先进先出（FIFO）的。 </p><h2 id="8-如何找到一个数组中所有重复出现的元素？"><a href="#8-如何找到一个数组中所有重复出现的元素？" class="headerlink" title="8. 如何找到一个数组中所有重复出现的元素？"></a>8. 如何找到一个数组中所有重复出现的元素？</h2><p>这个问题有时会作为第 4 题的后续出现。解决这个问题的一种途径是使用哈希表。遍历整个数组并将元素和出现次数存到哈希表中。最后你就可以统计那些出现超过一次的元素作为答案。在 Java 中，当一个元素已经在 HashMap 中时，调用 get (index) 就可以得到对应的值，否则会返回 Null。这个性质可以用来插入和更新 HashMap 中的值。 </p><h2 id="9-单向链表和双向链表之间有什么差别？"><a href="#9-单向链表和双向链表之间有什么差别？" class="headerlink" title="9. 单向链表和双向链表之间有什么差别？"></a>9. 单向链表和双向链表之间有什么差别？</h2><p>这又是一个经常在电话面试中被问到的数据结构题。单向链表和双向链表之间主要的差别在于它们的可遍历性不同。在单向链表中，每个结点只有指向下一个结点的指针，而没有指向上一个结点的指针，所以你就不能倒回去遍历。而双向链表的结点则维护了两个指针，往两个方向都可以遍历。 </p><h2 id="10-如何打印斐波那契数列？"><a href="#10-如何打印斐波那契数列？" class="headerlink" title="10. 如何打印斐波那契数列？"></a>10. 如何打印斐波那契数列？</h2><p>这是一个在面试中经常出现的编程问题。斐波那契数列是这样的一种数列：它的每一项都等于前两项之和，例如：1，1，2，3，5，8，13，21。面试官往往会关心这两件事情：一个能够返回斐波那契数列第 n 项的函数，以及如何在 Java 中用递归解决这个问题。虽然这个问题很简单，但递归的部分可能会困惑一些初学者。 </p><h2 id="11-如何判断一个整数是不是回文数？"><a href="#11-如何判断一个整数是不是回文数？" class="headerlink" title="11. 如何判断一个整数是不是回文数？"></a>11. 如何判断一个整数是不是回文数？</h2><p>跟前一题一样，这也不是一个算法题，但在面试中的出现率也很高。当一个数反过来写还是等于本身时，我们称之为回文数。面试官往往会要求你不使用 Java API 和第三方库来解这个问题。当然，问题本身不难。你只要会用 / 和 % 就能解决它。/ 能去掉最后一位，而 % 能得到最后一位。比如 1234 / 10 会得到 123，而 1234 % 10 则能得到 4。 </p><h2 id="12-什么是二叉查找树？"><a href="#12-什么是二叉查找树？" class="headerlink" title="12. 什么是二叉查找树？"></a>12. 什么是二叉查找树？</h2><p>这是一个关于树的数据结构问题。二叉查找树满足一些特殊的性质，比如说每个结点的左子树中的值都要比自身小，而右子树中的值都要比自身大，等等。除了定义以外，面试官还可能让你在 Java 中实现一棵二叉树并询问关于树的遍历的问题。比如中序遍历、先序遍历和后序遍历之间的区别。 </p><h2 id="13-如何分别用递归和非递归方式倒置一个链表？"><a href="#13-如何分别用递归和非递归方式倒置一个链表？" class="headerlink" title="13. 如何分别用递归和非递归方式倒置一个链表？"></a>13. 如何分别用递归和非递归方式倒置一个链表？</h2><p>这是一个非常好的数据结构题，所以我希望你自己去思考。当然你也能轻易地在网上找到许多解法。 </p><h2 id="14-如何用-Java-实现一个栈？"><a href="#14-如何用-Java-实现一个栈？" class="headerlink" title="14. 如何用 Java 实现一个栈？"></a>14. 如何用 Java 实现一个栈？</h2><p>你可以用数组或者链表来实现栈。这个问题期望你实现栈所提供的标准接口，也就是 push () 和 pop ()。这两个操作都应该发生在栈的顶部。当然，如果你能实现 contains () 和&nbsp;isEmpty () 这样的功能就更好了。顺便一提，你可以利用 JDK 提供的 java.util.Stack 来学习怎么实现一个栈。</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/32899.html</id>
    <link href="https://www.bytelife.net/articles/32899.html"/>
    <published>2016-09-07T07:48:16.000Z</published>
    <summary>
      <![CDATA[<p>作为程序员在面试的时候碰到的问题还是很多的，不光要掌握像数组、链表、栈、队列和树这样基本的数据结构，在面试中还会碰到数据结构和算法问题。所以小编在这里告诉大家，下面的面试题目很有可能被碰到，一起来看看吧！ </p>]]>
    </summary>
    <title>Java 程序员最可能被考到的面试题</title>
    <updated>2016-09-07T07:48:16.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <content>
      <![CDATA[<blockquote><p>本文转载自：QQ 兴趣部落：<strong>java 学习（部落）</strong> <span class="exturl" data-url="aHR0cDovL2J1bHVvLnFxLmNvbS9wL2RldGFpbC5odG1sP2JpZD0xMTE0OCZwaWQ9MTA2NDc2NS0xNDcyMzc2NDA2JmZyb209c2hhcmVfcXE=">原文地址：点击访问<i class="fa fa-external-link-alt"></i></span></p></blockquote><p>实现方式：JDK 实现，CC，BC JDK 提供比较基础的底层的实现；CC 提供一些简化的操作；BC 提供补充 </p><h2 id="一、Base64加密"><a href="#一、Base64加密" class="headerlink" title="一、Base64加密"></a>一、Base64 加密</h2><p>非常简单，加密解密就一个函数。 </p><span id="more"></span><p>代码如下：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139189571991.jpg"></p><h2 id="二、消息摘要算法加密————主要用于验证数据完整性。"><a href="#二、消息摘要算法加密————主要用于验证数据完整性。" class="headerlink" title="二、消息摘要算法加密————主要用于验证数据完整性。"></a>二、消息摘要算法加密————主要用于验证数据完整性。</h2><p> MD（消息摘要）：<br> <img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139189978006.jpg"><br> SHA（安全散列） JDK 实现和 MD 一样。 BC 实现如下：（Digest 类）<br> <img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139190043769.jpg"><br> CC 实现最简单（就是一个 DigestUtils 的静态方法）：<br> <img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139190106650.png"><br>MAC（消息认证码）<br>含有密钥的散列函数算法，兼容 MD 和 SHA 的特性，但加入了密钥。<br>主要 JDK 和 Bouncy Castle 实现。 JDK 实现：获取或定义密钥（byte [] 数组），Mac 类 实例化、初始化、执行。 BC 实现：Hmac 类 实例化、初始化、执行。 </p><h2 id="三、对称加解密"><a href="#三、对称加解密" class="headerlink" title="三、对称加解密"></a>三、对称加解密</h2><p> 对称加密指加密和解密使用相同密钥的加密算法。这里将介绍 DES、3 重 DES、AES 和 PBE 几种常见的对称加密算法在 Java 中的实现。 DES、3DES、AES（密钥）、PBE（口令和盐） DES： JDK 实现：生成并转换 Key；Cipher 类的实例化（getInstance）、初始化（init 选择模式与 Key）；执行（doFinal）加解密。 推荐 BC 实现方式：Security.addProvider(new BouncyCastleProvider ()); 然后剩下的代码就可以和 JDK 实现代码基本一样了。 3DES、AES 实现基本一样，所以就不一一介绍。。。 PBE（基于口令的加密） 特点：通过 salt + 口令 实现：初始化盐；生成口令；Cipher 类加解密。 </p><h2 id="四、非对称加解密"><a href="#四、非对称加解密" class="headerlink" title="四、非对称加解密"></a>四、非对称加解密</h2><p>非对称加密算法是一种基于密钥的保密方法，需要公开密钥和私有密钥，在文件加密、尤其是网银中应用广泛。这里主要介绍非对称加密算法的实现过程，DH、RSA 和 ELGamal 等几种常见的非对称加密算法的在 Java 中的应用。<br>概念：公钥、私钥； DH (密钥交换算法)： 代码实现有些麻烦<br>——初始化发送方密钥<br>-KeyPairGenerator ：能产生 KeyPair<br>-KeyPair ：常用的密钥载体，称为密钥对，分为公钥 PublicKey 与私钥 PrivateKey。 -PublicKey<br>——初始化接收方密钥<br>    - KeyFactory ：密钥工厂，生成密钥，通过某种密钥的规范来还原密钥 -X509EncodedKeySpec ：根据 ASN.1 进行密钥编码<br>    - DHPublicKey ：<br>    - DHParameterSpec ：遵从 DH 算法发参数的集合 -KeyPairGenerator ：<br>    - PrivateKey ：<br>——双发根据公布的对方的 PublicKey 构建本地密钥，<br>——构建出来的本地密钥是一致的 -KeyAgreement ：用来提供密钥一致性协议 -SecretKey ：秘密密钥，对称 -KeyFactory -X509EncodedKeySpec -PublicKey<br>——加密、解密（利用本地密钥） -Cipher ：为加密和解密提供密码功能的类<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139191275449.jpg"></p><p>解释： 首先，发送方产生密钥对，并公开 公钥；接收方根据这个公钥产生密钥对，然后也公开自己的 公钥。 然后，发送方根据接收方的 公钥 产生自己本地的密钥（本地密钥一般是采用对称密钥），接收方也根据发送方的 公钥 产生自己本地的密钥。其实，这样双方产生的本地密钥是相同的。 最后，双方就可以利用本地密钥进行加解密了。 RSA（基于因子分解）：代码实现较为简单 初始化密钥（包含公钥、密钥）。 可以利用公钥加密，私钥解密；也可以私钥加密，公钥解密 使用过程：双方分别掌握公钥与私钥中的一种，然后就可以加密并传输数据了。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139191732818.jpg"><br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139191678581.jpg"><br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139191628420.jpg"><br>EIGamal（基于离散因数） JDK 没有实现，只能利用 BC 实现。 首先，Security.addProvider(new BouncyCastleProvider ()); 之后实现类似于 RSA。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139191572820.jpg"></p><h2 id="五．Java实现数字签名"><a href="#五．Java实现数字签名" class="headerlink" title="五．Java实现数字签名"></a>五．Java 实现数字签名</h2><p>数字签名用于鉴别数字信息，公钥和私钥，私钥对数据签名，公钥用于检验。 过程为：首先初始化一个密钥对，在密钥对的基础上进行签名与验证。<br>1、RSA：既可以加解密，也可以数字签名。 初始化密钥对：KeyPairGenerator、KeyPair、RSAPublic、PSAPrivate； 执行签名；Signature 类 验证签名；Signature 类<br>2、DSA（数字签名算法） 初始化密钥对，公钥、私钥； 执行签名，用私钥签名； 验证签名，用公钥验证；<br>3、ECDSA 微软序列号便是采用的 ECDSA 算法进行的签名。速度快，强度高，签名短。 初始化密钥对； 执行签名； 验证签名</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/17609.html</id>
    <link href="https://www.bytelife.net/articles/17609.html"/>
    <published>2016-09-07T07:41:36.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>本文转载自：QQ 兴趣部落：<strong>java 学习（部落）</strong> <a href="http://buluo.qq.com/p/detail.html?bid=11148&amp;pid=1064765-1472376406&amp;from=share_qq">原文地址：点击访问</a></p>
</blockquote>
<p>实现方式：JDK 实现，CC，BC JDK 提供比较基础的底层的实现；CC 提供一些简化的操作；BC 提供补充 </p>
<h2 id="一、Base64加密"><a href="#一、Base64加密" class="headerlink" title="一、Base64加密"></a>一、Base64 加密</h2><p>非常简单，加密解密就一个函数。 </p>]]>
    </summary>
    <title>Java 加解密与数字签名</title>
    <updated>2016-09-07T07:41:36.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="Spring" scheme="https://www.bytelife.net/tags/Spring/"/>
    <content>
      <![CDATA[<p>通常，JavaBean 的属性值是私有的，同时拥有一组存取器方法，以 setXxx () 和 getXxx () 的形式存在。Spring 通过 Java 的反射机制借助属性的 set 方法来配置属性的值，以实现 setter 方式的注入。 </p><span id="more"></span><p>下面定义一个 Person 类来演示 Spring Bean 的属性注入：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by eric on 16-8-31.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Person</span> {</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line">    <span class="keyword">private</span> String sex;</span><br><span class="line">    <span class="keyword">private</span> String resume;</span><br><span class="line">    <span class="keyword">private</span> Car car;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">String</span> <span class="title function_">getName</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> {</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> age;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setAge</span><span class="params">(<span class="type">int</span> age)</span> {</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">String</span> <span class="title function_">getSex</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> sex;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setSex</span><span class="params">(String sex)</span> {</span><br><span class="line">        <span class="built_in">this</span>.sex = sex;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">String</span> <span class="title function_">getResume</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> resume;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setResume</span><span class="params">(String resume)</span> {</span><br><span class="line">        <span class="built_in">this</span>.resume = resume;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">Car</span> <span class="title function_">getCar</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> car;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setCar</span><span class="params">(Car car)</span> {</span><br><span class="line">        <span class="built_in">this</span>.car = car;</span><br><span class="line">    }</span><br><span class="line">    <span class="type">public</span> <span class="title function_">Person</span><span class="params">(String name, <span class="type">int</span> age, String sex, String resume)</span> {</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">        <span class="built_in">this</span>.sex = sex;</span><br><span class="line">        <span class="built_in">this</span>.resume = resume;</span><br><span class="line">    }</span><br><span class="line">    <span class="type">public</span> <span class="title function_">Person</span><span class="params">(String name, <span class="type">int</span> age, String sex, String resume, Car car)</span> {</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">        <span class="built_in">this</span>.sex = sex;</span><br><span class="line">        <span class="built_in">this</span>.resume = resume;</span><br><span class="line">        <span class="built_in">this</span>.car = car;</span><br><span class="line">    }</span><br><span class="line">    <span class="type">public</span> <span class="title function_">Person</span><span class="params">()</span> {</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>Person 类中包含一个无参的构造方法，因此可以在 Spring 中使用下面的 XML 声明一个<bean>：</bean></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"person"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Person"</span>&gt;</span><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>通过上面的 XML 生命的<bean>没有对任何属性进行赋值，下面逐步讲解如何为属性进行赋值。</bean></p><h3 id="一、注入简单值（字面值）"><a href="#一、注入简单值（字面值）" class="headerlink" title="一、注入简单值（字面值）"></a>一、注入简单值（字面值）</h3><h4 id="1、通过value属性注入"><a href="#1、通过value属性注入" class="headerlink" title="1、通过value属性注入"></a>1、通过 value 属性注入</h4><p>在 Spring 中可以使用<property>元素配置 Bean 的属性。例如现在想给 bean person 的姓名、年龄、性别、简历等属性赋值，那么可以使用下面的方式：</property></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"person"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Person"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"name"</span> <span class="attr">value</span>=<span class="string">"张三"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"age"</span> <span class="attr">value</span>=<span class="string">"16"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"sex"</span> <span class="attr">value</span>=<span class="string">"男"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"resume"</span> <span class="attr">value</span>=<span class="string">"疯狂的Java码农"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>一旦 Person 被实例化，Spring 就会调用<property>元素所指定属性的 setter 方法为该属性注入值。在本实例中，name、sex、resume 属性均为 String 类型的字符串，而我们发现 int 类型的 age 属性也可以成功注入简单值，这是因为<property>元素并没有限制只能注入 String 类型的值，value 属性同样可以指定为基本数据类型（包括数值型和布尔型等）。</property></property></p><h4 id="2、通过子元素注入"><a href="#2、通过子元素注入" class="headerlink" title="2、通过子元素注入"></a>2、通过<value>子元素注入</value></h4><p>那么我们想象一下，如果我们想注入的值中包含 XML 的特殊字符怎么办，上述方法是否仍然奏效？不妨尝试一下，例如我想将 person 的 resume 属性注入值 “&lt;Coder!&gt;”，那么在这个值中，“&lt;&gt;” 在 XML 中拥有特殊的含义，如果仍然使用 value 属性进行赋值，将会出现异常！</p><blockquote><p><code>Exception in thread "main" org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 11 in XML document from class path resource [applicationContext.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 11; columnNumber: 40; 与元素类型 "property" 相关联的 "value" 属性值不能包含 '&lt;' 字符。</code></p></blockquote><p>那么如何解决这个问题呢？<property>元素中可以添加一个<value>子元素，其作用与 value 属性基本相同，而当我们想要注入包含特殊字符的字面值时，可以在<value>子元素中使用 CDATA 节来解决，例如：</value></value></property></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"person"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Person"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"name"</span> <span class="attr">value</span>=<span class="string">"张三"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"age"</span> <span class="attr">value</span>=<span class="string">"16"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"sex"</span> <span class="attr">value</span>=<span class="string">"男"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"resume"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>&lt;![CDATA[&lt;Coder!&gt;]]&gt;<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><h3 id="二、引用其他Bean"><a href="#二、引用其他Bean" class="headerlink" title="二、引用其他Bean"></a>二、引用其他 Bean</h3><p>上面使用 value 属性或<value>子元素注入简单值只是实现简单的硬编码，而在实际开发过程中，经常需要为某一个属性赋值为一个对象，通常为另一个 bean。那么如何在 Spring 中为属性引用其他 bean 呢？</value></p><p>例如本例中 Person 实例拥有一个 Car 类型的属性 car，可以使用<property>元素的 ref 属性为其进行赋值。类 Car 的源码如下：</property></p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by huzha on 2016-09-06.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Car</span> {</span><br><span class="line">    <span class="keyword">private</span> String brand;</span><br><span class="line">    <span class="keyword">private</span> String model;</span><br><span class="line">    <span class="keyword">private</span> String color;</span><br><span class="line">    <span class="type">public</span> <span class="title function_">Car</span><span class="params">()</span> {</span><br><span class="line">    }</span><br><span class="line">    <span class="type">public</span> <span class="title function_">Car</span><span class="params">(String brand, String model, String color)</span> {</span><br><span class="line">        <span class="built_in">this</span>.brand = brand;</span><br><span class="line">        <span class="built_in">this</span>.model = model;</span><br><span class="line">        <span class="built_in">this</span>.color = color;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">String</span> <span class="title function_">getBrand</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> brand;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setBrand</span><span class="params">(String brand)</span> {</span><br><span class="line">        <span class="built_in">this</span>.brand = brand;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">String</span> <span class="title function_">getModel</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> model;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setModel</span><span class="params">(String model)</span> {</span><br><span class="line">        <span class="built_in">this</span>.model = model;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">String</span> <span class="title function_">getColor</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> color;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setColor</span><span class="params">(String color)</span> {</span><br><span class="line">        <span class="built_in">this</span>.color = color;</span><br><span class="line">    }</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">String</span> <span class="title function_">toString</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Car{"</span> +</span><br><span class="line">                <span class="string">"brand='"</span> + brand + <span class="string">''</span><span class="string">' +</span></span><br><span class="line"><span class="string">                ", model='</span><span class="string">" + model + ''' +</span></span><br><span class="line"><span class="string">                "</span>, color=<span class="string">'" + color + '</span><span class="string">''</span> +</span><br><span class="line">                <span class="string">'}'</span>;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>我们首先在 XML 中声明一个 Car 类型的<bean>，源码如下：</bean></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"car"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Car"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"brand"</span> <span class="attr">value</span>=<span class="string">"奥迪"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"model"</span> <span class="attr">value</span>=<span class="string">"A6"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"color"</span> <span class="attr">value</span>=<span class="string">"黑色"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>那么可以在 person bean 中引用这个 bean，源码如下：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"person"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Person"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"name"</span> <span class="attr">value</span>=<span class="string">"张三"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"age"</span> <span class="attr">value</span>=<span class="string">"16"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"sex"</span> <span class="attr">value</span>=<span class="string">"男"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"resume"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>&lt;![CDATA[&lt;Coder!&gt;]]&gt;<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"car"</span> <span class="attr">ref</span>=<span class="string">"car"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><h3 id="三、注入内部Bean"><a href="#三、注入内部Bean" class="headerlink" title="三、注入内部Bean"></a>三、注入内部 Bean</h3><p>我们知道，在 Spring 中 bean 的默认作用域是单例的，通过上述方式注入 bean，意味着这个 bean 还可能被其他 bean 引用。例如，可能有另外一个 Person 类型的 bean person1，其 car 属性同样引用的是 id 为 car 的 bean，那么意味着这两个人同时拥有同一台奥迪 A6，这通常是不现实的。那么如何为一个 Bean 创建一个独有的内部 bean 呢？</p><p>在 Spring 中<bean>元素中还可以嵌套其它的<bean>元素，我们称嵌套在内部的<bean>为内部 bean，这个概念与 Java 的内部类十分相似。</bean></bean></bean></p><p>我们修改一下 person 的<bean>声明，代码如下：</bean></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"person"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Person"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"name"</span> <span class="attr">value</span>=<span class="string">"张三"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"age"</span> <span class="attr">value</span>=<span class="string">"16"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"sex"</span> <span class="attr">value</span>=<span class="string">"男"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"resume"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>&lt;![CDATA[&lt;Coder!&gt;]]&gt;<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"car"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Car"</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"brand"</span> <span class="attr">value</span>=<span class="string">"奥迪"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"model"</span> <span class="attr">value</span>=<span class="string">"A6"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"color"</span> <span class="attr">value</span>=<span class="string">"黑色"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>这样我们就成功的注入了一个内部 Bean。细心的朋友可能会发现，在这里内部 bean 没有给定其 id 属性，因为在这里其 id 属性似乎并不是那么必要，因为我们永远不会通过 id 来引用内部 bean。这也突出了内部 Bean 的最大缺点：它们不能被复用。内部 Bean 仅适用于一次注入，而且也不能被其它 Bean 所引用。</p><p>内部 Bean 并不局限于在<property>元素中使用，我们同样可以在<constructor-arg>中使用，例如我们修改 person 的<bean>声明如下：</bean></constructor-arg></property></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"person"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Person"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"张三"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"16"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">"sex"</span> <span class="attr">value</span>=<span class="string">"男"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> &gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">value</span>&gt;</span>&lt;![CDATA[&lt;Coder!&gt;]]&gt;<span class="tag">&lt;/<span class="name">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Car"</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">"brand"</span> <span class="attr">value</span>=<span class="string">"奥迪"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">"model"</span> <span class="attr">value</span>=<span class="string">"A6"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">name</span>=<span class="string">"color"</span> <span class="attr">value</span>=<span class="string">"黑色"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.bytelife.net/articles/61060.html</id>
    <link href="https://www.bytelife.net/articles/61060.html"/>
    <published>2016-09-06T15:29:06.000Z</published>
    <summary>
      <![CDATA[<p>通常，JavaBean 的属性值是私有的，同时拥有一组存取器方法，以 setXxx () 和 getXxx () 的形式存在。Spring 通过 Java 的反射机制借助属性的 set 方法来配置属性的值，以实现 setter 方式的注入。 </p>]]>
    </summary>
    <title>Spring 学习笔记之 Bean 的属性注入</title>
    <updated>2016-09-06T15:29:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="Spring" scheme="https://www.bytelife.net/tags/Spring/"/>
    <content>
      <![CDATA[<h3 id="一、在Spring的IOC容器中配置Bean"><a href="#一、在Spring的IOC容器中配置Bean" class="headerlink" title="一、在Spring的IOC容器中配置Bean"></a>一、在 Spring 的 IOC 容器中配置 Bean</h3><p>可以通过 Spring 的 XML 配置文件来配置 Bean，例如：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 配置Bean --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"helloWorld"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.HelloWorld"</span>&gt;</span><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><span id="more"></span><p>其中：</p><ul><li>id 属性定义了 Bean 的名字，也作为该 Bean 在 Spring 容器中的引用；</li><li>class 属性定义了该 Bean 的类型</li></ul><p>注意：</p><ul><li>id 属性在 IOC 容器中是唯一的</li><li>若 id 没有指定，Spring 自动将类名作为 Bean 的名字</li><li>id 可以指定多个名字，名字之间可用逗号、分号、空格分隔</li></ul><h3 id="二、Spring容器"><a href="#二、Spring容器" class="headerlink" title="二、Spring容器"></a>二、Spring 容器</h3><p>在 Spring IOC 容器读取 Bean 配置并创建 Bean 实例之前，必须对它进行实例化。只有容器实例化之后，才可以从 IOC 容器中获取 Bean 实例并使用。 Spring 提供了两种类型的 IOC 容器实现，无论使用哪种方式，配置文件时完全相同：</p><ul><li>BeanFactory：IOC 容器的基本（底层）实现。BeanFactory 是 Spring 框架的基础设施，面向 Spring 本身；</li><li>ApplicationContext：提供了更多的高级特性，是 BeanFactory 的子接口。ApplicationContext 面向使用 Spring 框架的开发者，几乎所有的应用场合都直接使用 ApplicationContext 而非底层的 BeanFactory。</li></ul><h3 id="三、ApplicationContext"><a href="#三、ApplicationContext" class="headerlink" title="三、ApplicationContext"></a>三、ApplicationContext</h3><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139171751520.png"></p><p>ApplicationContext 有两个主要的实现类：</p><ul><li>ClassPathXmlApplicationContext：从类路径下加载配置文件</li><li>FileSystemXmlApplicationContext：从文件系统中加载配置文件</li></ul><p>ConfigurableApplicationContext 是 ApplicationContext 的子接口，增加了 refresh ()、close () 等方法，让 ApplicationContext 具有了启动、刷新、关闭上下文的能力。</p><p>注意：</p><ul><li>ApplicationContext 在初始化上下文时就会实例化所有的单例 Bean，及其 scope（作用域）属性为 singleton 的 Bean。</li><li>WebApplicationContext 是专门为 Web 应用而准备的，它允许从相对于 Web 根目录的路径中完成初始化工作。</li></ul><h3 id="四、依赖注入"><a href="#四、依赖注入" class="headerlink" title="四、依赖注入"></a>四、依赖注入</h3><p>Spring 支持 3 种依赖注入的方式，分别是：</p><ul><li>属性注入（Setter 注入）</li><li>构造器注入</li><li>工厂方法注入</li></ul><h4 id="1、属性注入"><a href="#1、属性注入" class="headerlink" title="1、属性注入"></a>1、属性注入</h4><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"helloWorld"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.HelloWorld"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"name"</span> <span class="attr">value</span>=<span class="string">"Spring 4.0"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><ul><li>属性注入即通过 setter 方法注入 Bean 的属性值或依赖的对象；</li><li>属性注入使用<property>元素，使用 name 指定 Bean 的属性名称，value 属性或<value>子节点指定属性值，使用 ref 属性指定依赖的对象；</value></property></li><li>属性注入是最常用的注入方式；</li></ul><h4 id="2、构造方法注入"><a href="#2、构造方法注入" class="headerlink" title="2、构造方法注入"></a>2、构造方法注入</h4><p>通过构造方法注入 Bean 的属性值或依赖的对象，它保证了 Bean 在实例化后就可以使用。 构造器注入在<constructor-arg>元素里声明属性，<constructor-arg>中常用的属性有 value、 index、type 等，注意：没有 name 属性。 （1）按索引匹配入参</constructor-arg></constructor-arg></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 构造器注入-通过索引 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"car0"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Car"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"A6L"</span> <span class="attr">index</span>=<span class="string">"0"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"奥迪"</span> <span class="attr">index</span>=<span class="string">"1"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"15648"</span> <span class="attr">index</span>=<span class="string">"2"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"240"</span> <span class="attr">index</span>=<span class="string">"3"</span>&gt;</span><span class="tag">&lt;/<span class="name">constructor-arg</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>（2）按类型匹配入参</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 构造器注入-通过类型 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"car1"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Car"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"卡宴"</span> <span class="attr">type</span>=<span class="string">"java.lang.String"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"保时捷"</span> <span class="attr">type</span>=<span class="string">"java.lang.String"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"260"</span> <span class="attr">type</span>=<span class="string">"double"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><h4 id="3、工厂方法注入"><a href="#3、工厂方法注入" class="headerlink" title="3、工厂方法注入"></a>3、工厂方法注入</h4><p>有时候静态工厂方法是实例化对象的唯一方法。Spring 支持通过<bean>元素的 factory-method 属性类装配工厂创建的 bean。 例如如下是一个 Stage 单例类：</bean></p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by eric on 16-8-31.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Stage</span> {</span><br><span class="line">    <span class="type">private</span> <span class="title function_">Stage</span><span class="params">()</span> {</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">StageSingletonHolder</span> {</span><br><span class="line">        <span class="keyword">static</span> <span class="type">Stage</span> <span class="variable">instance</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Stage</span>();</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">Stage</span> <span class="title function_">getInstance</span><span class="params">()</span> {</span><br><span class="line">        <span class="keyword">return</span> StageSingletonHolder.instance;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>我们注意到该类只有一个 private 的构造方法，而并未提供公开的构造方法，因此在 Spring 中不能直接通过之前所述的方式来获得该类的 bean 对象。为了解决这个问题，Spirng 的<bean>标签提供了一个 factory-method 属性，配置如下所示：</bean></p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 静态工厂方法注入--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"theStage"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.Stage"</span> <span class="attr">factory-method</span>=<span class="string">"getInstance"</span> /&gt;</span></span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.bytelife.net/articles/27638.html</id>
    <link href="https://www.bytelife.net/articles/27638.html"/>
    <published>2016-09-06T15:22:59.000Z</published>
    <summary>
      <![CDATA[<h3 id="一、在Spring的IOC容器中配置Bean"><a href="#一、在Spring的IOC容器中配置Bean" class="headerlink" title="一、在Spring的IOC容器中配置Bean"></a>一、在 Spring 的 IOC 容器中配置 Bean</h3><p>可以通过 Spring 的 XML 配置文件来配置 Bean，例如：</p>
<figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 配置Bean --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"helloWorld"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.HelloWorld"</span>&gt;</span><span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>]]>
    </summary>
    <title>Spring 学习笔记之配置 Bean</title>
    <updated>2016-09-06T15:22:59.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="Spring" scheme="https://www.bytelife.net/tags/Spring/"/>
    <content>
      <![CDATA[<h3 id="一、IOC与DI"><a href="#一、IOC与DI" class="headerlink" title="一、IOC与DI"></a>一、IOC 与 DI</h3><ul><li>IOC (Inversion of Control)：其思想是反转资源获取的方向. 传统的资源查找方式要求组件向容器发起请求查找资源. 作为回应, 容器适时的返回资源. 而应用了 IOC 之后, 则是容器主动地将资源推送给它所管理的组件, 组件所要做的仅是选择一种合适的方式来接受资源. 这种行为也被称为查找的被动形式</li><li>DI (Dependency Injection) — IOC 的另一种表述方式：即组件以一些预先定义好的方式 (例如: setter 方法) 接受来自如容器的资源注入. 相对于 IOC 而言，这种表述更直接</li></ul><span id="more"></span><h3 id="二、案例分析"><a href="#二、案例分析" class="headerlink" title="二、案例分析"></a>二、案例分析</h3><h4 id="1、需求"><a href="#1、需求" class="headerlink" title="1、需求"></a>1、需求</h4><p>从容器中获取 B 对象，并使 B 对象的 a 属性被赋值为容器 A 对象的引用。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139179370237.png"></p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>{}</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>{</span><br><span class="line">    <span class="keyword">private</span> A a;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setA</span><span class="params">(A a)</span>{</span><br><span class="line">        <span class="built_in">this</span>.a = a;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="2、传统处理方式"><a href="#2、传统处理方式" class="headerlink" title="2、传统处理方式"></a>2、传统处理方式</h4><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139179473252.png"></p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">A</span> <span class="variable">a</span> <span class="operator">=</span> getA();</span><br><span class="line"><span class="type">B</span> <span class="variable">b</span> <span class="operator">=</span> getB();</span><br><span class="line">b.setA(a);</span><br></pre></td></tr></tbody></table></figure><h4 id="3、IOC反转资源方向"><a href="#3、IOC反转资源方向" class="headerlink" title="3、IOC反转资源方向"></a>3、IOC 反转资源方向</h4><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139179596070.png"></p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">B</span> <span class="variable">b</span> <span class="operator">=</span> getB();</span><br></pre></td></tr></tbody></table></figure><h3 id="三、IOC发展历程"><a href="#三、IOC发展历程" class="headerlink" title="三、IOC发展历程"></a>三、IOC 发展历程</h3><p>需求: 生成 HTML 或 PDF 格式的不同类型的报表.</p><h4 id="1、IOC前生–分离接口与实现"><a href="#1、IOC前生–分离接口与实现" class="headerlink" title="1、IOC前生–分离接口与实现"></a>1、IOC 前生–分离接口与实现</h4><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139179860396.png"></p><h4 id="2、IOC前生–采用工厂设计模式"><a href="#2、IOC前生–采用工厂设计模式" class="headerlink" title="2、IOC前生–采用工厂设计模式"></a>2、IOC 前生–采用工厂设计模式</h4><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139180092072.png"></p><h4 id="3、IOC–采用反转控制"><a href="#3、IOC–采用反转控制" class="headerlink" title="3、IOC–采用反转控制"></a>3、IOC–采用反转控制</h4><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139180140240.png"></p>]]>
    </content>
    <id>https://www.bytelife.net/articles/31708.html</id>
    <link href="https://www.bytelife.net/articles/31708.html"/>
    <published>2016-09-06T14:54:01.000Z</published>
    <summary>
      <![CDATA[<h3 id="一、IOC与DI"><a href="#一、IOC与DI" class="headerlink" title="一、IOC与DI"></a>一、IOC 与 DI</h3><ul>
<li>IOC (Inversion of Control)：其思想是反转资源获取的方向. 传统的资源查找方式要求组件向容器发起请求查找资源. 作为回应, 容器适时的返回资源. 而应用了 IOC 之后, 则是容器主动地将资源推送给它所管理的组件, 组件所要做的仅是选择一种合适的方式来接受资源. 这种行为也被称为查找的被动形式</li>
<li>DI (Dependency Injection) — IOC 的另一种表述方式：即组件以一些预先定义好的方式 (例如: setter 方法) 接受来自如容器的资源注入. 相对于 IOC 而言，这种表述更直接</li>
</ul>]]>
    </summary>
    <title>Spring 学习笔记之 IOC 与 DI 概述</title>
    <updated>2016-09-06T14:54:01.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="Spring" scheme="https://www.bytelife.net/tags/Spring/"/>
    <content>
      <![CDATA[<h3 id="一、下载Spring"><a href="#一、下载Spring" class="headerlink" title="一、下载Spring"></a>一、下载 Spring</h3><p>官方网站：<span class="exturl" data-url="aHR0cDovL3Byb2plY3RzLnNwcmluZy5pby9zcHJpbmctZnJhbWV3b3JrLw==">http://projects.spring.io/spring-framework/<i class="fa fa-external-link-alt"></i></span> 下载方式：Maven、Gradle Maven 依赖描述：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-context<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>4.3.2.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><span id="more"></span><h3 id="二、Spring概述"><a href="#二、Spring概述" class="headerlink" title="二、Spring概述"></a>二、Spring 概述</h3><p>Spring 是一个开元框架，为简化企业级应用开发而生。使用 Spring 可以使简单的 JavaBean 实现以前只有 EJB 才能实现的功能。Spring 是一个 IOC 反转控制（DI 依赖注入）和 AOP（面向切面编程）容器框架。</p><ul><li>轻量级：Spring 是非侵入性的 - 基于 Spring 开发的应用中对象可以不依赖 Spring 的 API；</li><li>依赖注入（DI, Dependency Injection、IOC）；</li><li>面向切面编程（AOP, Aspect Oriented Programming）；</li><li>容器：Spring 是一个容器，因为它包含并且管理应用对象的生命周期；</li><li>框架：Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象；</li><li>一站式：在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库（实际上 Spring 自身也提供了表现层的 Spring MVC 和持久层的 Spring JDBC）。</li></ul><h3 id="三、Spring模块"><a href="#三、Spring模块" class="headerlink" title="三、Spring模块"></a>三、Spring 模块</h3><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139178503892.png"></p><h3 id="四、安装Spring-Tool-Suite"><a href="#四、安装Spring-Tool-Suite" class="headerlink" title="四、安装Spring Tool Suite"></a>四、安装 Spring Tool Suite</h3><ol><li>SPRING TOOL SUITE 是一个 Eclipse 插件，利用该插件可以更方便的在 Eclipse 平台上开发基于 Spring 的应用。</li><li>安装方法说明（springsource-tool-suite-3.4.0.RELEASE-e4.3.1-updatesite.zip）：</li></ol><ul><li>Help –&gt; Install New Software…</li><li>Click Add…</li><li>In dialog Add Site dialog, click Archive…</li><li>Navigate to springsource-tool-suite-3.4.0.RELEASE-e4.3.1-updatesite.zip &nbsp;and click &nbsp;Open</li><li>Clicking OK in the Add Site dialog will bring you back to the dialog ‘Install’</li><li>Select the xxx/Spring IDE that has appeared</li><li>Click Next &nbsp;and then Finish</li><li>Approve the license</li><li>Restart eclipse when that is asked</li></ul><h3 id="五、搭建Spring开发环境"><a href="#五、搭建Spring开发环境" class="headerlink" title="五、搭建Spring开发环境"></a>五、搭建 Spring 开发环境</h3><ul><li>将以下 jar 包加入到工程的 classpath 下：</li></ul><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139178676232.png"></p><ul><li>Spring 的配置文件：一个典型的 Spring 项目需要创建一个或多个 Bean 配置文件，这些配置文件用于在 Spring IOC 容器中配置 Bean。Bean 的配置文件可以放在 classpath 下，也可以放在其它目录下。</li></ul><h3 id="六、创建Spring项目"><a href="#六、创建Spring项目" class="headerlink" title="六、创建Spring项目"></a>六、创建 Spring 项目</h3><ul><li>cn.javacodes.spring.beans.HelloWorld</li></ul><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorld</span> {</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">setName</span><span class="params">(String name)</span>{</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">hello</span><span class="params">()</span>{</span><br><span class="line">        System.out.println(<span class="string">"hello: "</span> + name);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li>cn.javacodes.spring.beans.Main</li></ul><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.spring.beans;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.ApplicationContext;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.support.ClassPathXmlApplicationContext;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> {</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line">        <span class="comment">// 1\. 创建Spring的IOC容器对象</span></span><br><span class="line">        <span class="type">ApplicationContext</span> <span class="variable">ctx</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>(<span class="string">"applicationContext.xml"</span>);</span><br><span class="line">        <span class="comment">// 2\. 从IOC中获取Bean实例</span></span><br><span class="line">        <span class="type">HelloWorld</span> <span class="variable">helloWorld</span> <span class="operator">=</span> (HelloWorld) ctx.getBean(<span class="string">"helloWorld"</span>);</span><br><span class="line">        <span class="comment">// 3.调用hello方法</span></span><br><span class="line">        helloWorld.hello();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><ul><li>applicationContext.xml</li></ul><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>&gt;</span></span><br><span class="line">    &lt;!\-\- 配置Bean --&gt;</span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"helloWorld"</span> <span class="attr">class</span>=<span class="string">"cn.javacodes.spring.beans.HelloWorld"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"name"</span> <span class="attr">value</span>=<span class="string">"Spring"</span>&gt;</span><span class="tag">&lt;/<span class="name">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.bytelife.net/articles/1737.html</id>
    <link href="https://www.bytelife.net/articles/1737.html"/>
    <published>2016-09-06T14:41:38.000Z</published>
    <summary>
      <![CDATA[<h3 id="一、下载Spring"><a href="#一、下载Spring" class="headerlink" title="一、下载Spring"></a>一、下载 Spring</h3><p>官方网站：<a href="http://projects.spring.io/spring-framework/">http://projects.spring.io/spring-framework/</a> 下载方式：Maven、Gradle Maven 依赖描述：</p>
<figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-context<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>4.3.2.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>]]>
    </summary>
    <title>Spring 学习笔记之 Spring HelloWorld</title>
    <updated>2016-09-06T14:41:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="JavaScript" scheme="https://www.bytelife.net/tags/JavaScript/"/>
    <content>
      <![CDATA[<blockquote><p>前言：Ajax 是当今 Web 应用程序开发过程中必不可少的一种技术，使用它可以通过异步处理的方式极大的提高用户体验，这篇文章将以简单易懂的方式介绍 AJAX 技术。</p></blockquote><h3 id="AJAX技术简介"><a href="#AJAX技术简介" class="headerlink" title="AJAX技术简介"></a>AJAX 技术简介</h3><p>AJAX 即 “Asynchronous Javascript And XML”（异步 JavaScript 和 XML），音译为 “阿贾克斯”，是指一种创建交互式网页应用的网页开发技术。AJAX 通过在后台服务器进行少量的数据交换，实现对网页的异步刷新，即可以在不重新加载整个网页的情况下，对网页的局部进行更新。 Ajax 不是一个新的技术，事实上，它是一些旧有的成熟的技术以一种全新的更加强大的方式整合在一起 Ajax 的关键技术：</p><ul><li>使用 XHTML (HTML) 和 CSS 构建标准化的展示层</li><li>使用 DOM 进行动态显示和交互</li><li>使用 XML 和 XSLT 进行数据交换和操纵</li><li>使用 XMLHttpRequest 异步获取数据</li><li>使用 JavaScript 将所有元素绑定在一起</li></ul><span id="more"></span><h3 id="传统Web应用的同步处理方式"><a href="#传统Web应用的同步处理方式" class="headerlink" title="传统Web应用的同步处理方式"></a>传统 Web 应用的同步处理方式</h3><p>在传统的 Web 应用中，如果我们需要更新网页内容，必须重新加载整个页面，传统 Web 应用同步处理方式模型图如下：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139214124956.png"></p><p>我们可以看到，在传统的 Web 应用模型下，客户机 （浏览器或者本地机器上运行的代码）向服务器发出请求。该请求是同步的，客户机等待服务器的响应。当客户机等待的时候，会用某种形式通知您正在处理：</p><ul><li>沙漏（特别是 Windows 上）</li><li>旋转皮球（通常在 Mac 机器上）</li><li>应用程序基本上冻结了，然后过一段时间光标变化了</li></ul><p>这正是传统 Web 应用程序让人感到笨拙或缓慢的原因：缺乏真正的交互性。 按下按钮时，应用程序实际上变得不能使用，直到刚刚触发的请求得到响应。如果请求需要大量服务器处理，那么等待的时间可能很长。这对用户体验是非常不友好的。</p><h3 id="AJAX应用的异步处理方式"><a href="#AJAX应用的异步处理方式" class="headerlink" title="AJAX应用的异步处理方式"></a>AJAX 应用的异步处理方式</h3><p>Ajax 应用通过在用户和服务器之间引入一个媒介（Ajax engine）来异步发送请求，消除了传统的发送请求－等待－发送请求－等待的特性，极大的提高了用户体验。 Ajax 应用的异步处理方式模型图如下：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139214211186.png"><br> 我们在图中可以看到，当用户在浏览器中进行一定的操作（比如点击按钮）请求更新网页内容，浏览器将请求交由 AJAX 处理，AJAX 引擎收到将请求提交给服务器，此时浏览器端无需刷新当前页面，待服务器处理完成请求后，将响应传给 AJAX 引擎，由 AJAX 来更新网页的部分内容。 这样的处理在浏览器端用户完全无需等待，完全由 AJAX 来完成整个处理。这种处理方式就是异步处理。</p><h3 id="传统Web应用与Ajax应用的比较"><a href="#传统Web应用与Ajax应用的比较" class="headerlink" title="传统Web应用与Ajax应用的比较"></a>传统 Web 应用与 Ajax 应用的比较</h3><p>废话不多说，直接看图：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139214366717.png"></p><h3 id="创建AJAX核心对象"><a href="#创建AJAX核心对象" class="headerlink" title="创建AJAX核心对象"></a>创建 AJAX 核心对象</h3><p>想要使用 AJAX 技术必然逃不掉 AJAX 引擎，其实说起引擎可能觉得这个概念有点过于庞大，其实在 AJAX 中，这个引擎不过就是一个对象！对，你没有看错，只是一个对象而已！这个对象就是 XMLHttpRequest。 XMLHttpRequest（简称 XHR）是一个 JavaScript 对象。这个对象产生于浏览器中，它是 AJAX 的核心。 在不同的浏览器中创建这个对象的方法不同，其实主要的原因当然来自万恶的 IE，没办法，毕竟 IE 拥有着庞大的用户群，我们在开发过程中不得不考虑兼容性问题。 那么为了解决兼容性问题，通常创建 XHR 对象的代码如下：</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">ajaxFunction</span>(<span class="params"></span>)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">var</span> xmlHttp;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">try</span></span><br><span class="line">    {</span><br><span class="line">   <span class="comment">// Firefox, Opera 8.0+, Safari,IE7及以上版本</span></span><br><span class="line">    xmlHttp=<span class="keyword">new</span> <span class="title class_">XMLHttpRequest</span>();</span><br><span class="line">    }</span><br><span class="line"> <span class="keyword">catch</span> (e)</span><br><span class="line">    {</span><br><span class="line">  <span class="comment">// Internet Explorer</span></span><br><span class="line">   <span class="keyword">try</span></span><br><span class="line">      {</span><br><span class="line">      xmlHttp=<span class="keyword">new</span> <span class="title class_">ActiveXObject</span>(<span class="string">"Msxml2.XMLHTTP"</span>);<span class="comment">//IE较新版本</span></span><br><span class="line">      }</span><br><span class="line">   <span class="keyword">catch</span> (e)</span><br><span class="line">      {</span><br><span class="line"></span><br><span class="line">      <span class="keyword">try</span></span><br><span class="line">         {</span><br><span class="line">         xmlHttp=<span class="keyword">new</span> <span class="title class_">ActiveXObject</span>(<span class="string">"Microsoft.XMLHTTP"</span>);<span class="comment">//IE较老版本</span></span><br><span class="line">         }</span><br><span class="line">      <span class="keyword">catch</span> (e)</span><br><span class="line">         {</span><br><span class="line">         <span class="title function_">alert</span>(<span class="string">"您的浏览器不支持AJAX！"</span>);</span><br><span class="line">         <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">         }</span><br><span class="line">      }</span><br><span class="line">    }</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><p>注意不要被这些花括号迷住了眼睛，下面分别介绍每一步: 增加 try/catch 块：</p><ul><li>尝试创建 XMLHttpRequest 对象</li><li>如果失败, 先尝试使用较新版本的 Microsoft 浏览器创建 Microsoft 兼容的对象（Msxml2.XMLHTTP），如果失败（尝试使用较老版本的 Microsoft 浏览器创建 Microsoft 兼容的对象（Microsoft.XMLHTTP）</li></ul><p>如果全部失败，则使用 JavaScript 警告通知用户出现了问题并返回 false。</p><h3 id="XMLHttpRequest常用方法及属性"><a href="#XMLHttpRequest常用方法及属性" class="headerlink" title="XMLHttpRequest常用方法及属性"></a>XMLHttpRequest 常用方法及属性</h3><h4 id="onreadystatechange-属性"><a href="#onreadystatechange-属性" class="headerlink" title="onreadystatechange 属性"></a>onreadystatechange 属性</h4><p>该属性存有处理服务器响应的函数。下面的代码定义了一个空的函数并赋值给 onreadystatechange 属性：</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">xmlHttp.<span class="property">onreadystatechange</span>=<span class="keyword">function</span>(<span class="params"></span>)</span><br><span class="line">  {</span><br><span class="line">  <span class="comment">// 我们需要在这里写一些代码</span></span><br><span class="line">  }</span><br></pre></td></tr></tbody></table></figure><h4 id="readyState属性"><a href="#readyState属性" class="headerlink" title="readyState属性"></a>readyState 属性</h4><p>readyState 属性存有服务器响应的状态信息。每当 readyState 改变时，onreadystatechange 函数就会被执行。 这是 readyState 属性可能的值： AJAX - XMLHttpRequest 对象</p><table><thead><tr><th>状态</th><th>描述</th></tr></thead><tbody><tr><td>0</td><td>请求未初始化（在调用 open () 之前）</td></tr><tr><td>1</td><td>请求已提出（调用 send () 之前）</td></tr><tr><td>2</td><td>请求已发送（这里通常可以从响应得到内容头部）</td></tr><tr><td>3</td><td>请求处理中（响应中通常有部分数据可用，但是服务器还没有完成响应）</td></tr><tr><td>4</td><td>请求已完成（可以访问服务器响应并使用它）</td></tr></tbody></table><p>我们要向这个 onreadystatechange 函数添加一条 If 语句，来测试我们的响应是否已完成（意味着可获得数据）：</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">xmlHttp.<span class="property">onreadystatechange</span>=<span class="keyword">function</span>(<span class="params"></span>)</span><br><span class="line">  {</span><br><span class="line">  <span class="keyword">if</span>(xmlHttp.<span class="property">readyState</span>==<span class="number">4</span>)</span><br><span class="line">    {</span><br><span class="line">    <span class="comment">// 从服务器的response获得数据</span></span><br><span class="line">    }</span><br><span class="line">  }</span><br></pre></td></tr></tbody></table></figure><h4 id="responseText-属性"><a href="#responseText-属性" class="headerlink" title="responseText 属性"></a>responseText 属性</h4><p>可以通过 responseText 属性来取回由服务器返回的数据。</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">xmlHttp.<span class="property">onreadystatechange</span>=<span class="keyword">function</span>(<span class="params"></span>)</span><br><span class="line">  {</span><br><span class="line">  <span class="keyword">if</span>(xmlHttp.<span class="property">readyState</span>==<span class="number">4</span>)</span><br><span class="line">    {</span><br><span class="line">    <span class="comment">//例如将某一元素内的HTML代码更换为服务器发送的响应字符串</span></span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">"某一元素id"</span>).<span class="property">innerHTML</span> = xmlHttp.<span class="property">responseText</span>&lt;/code&gt;;</span><br><span class="line">    }</span><br><span class="line">  }</span><br></pre></td></tr></tbody></table></figure><h4 id="open-函数"><a href="#open-函数" class="headerlink" title="open()函数"></a>open () 函数</h4><p>使用 XMLHttpRequest 对象的 open () 方法来建立请求。该方法有五个参数：</p><ul><li>request-type：发送请求的类型。典型的值是 GET 或 POST，但也可以发送 HEAD 请求</li><li>url：要连接的 URL</li><li>asynch：如果希望使用异步连接则为 true，否则为 false。该参数是可选的，默认为 true</li><li>username：如果需要身份验证，则可以在此指定用户名。该可选参数没有默认值</li><li>password：如果需要身份验证，则可以在此指定口令。该可选参数没有默认值</li></ul><p>通常使用其中的前三个参数。事实上，即使需要异步连接，通常指定第三个参数为 “true”，这样更容易理解。</p><h4 id="send-函数"><a href="#send-函数" class="headerlink" title="send()函数"></a>send () 函数</h4><p>一旦请求用 open () 配置好之后，就可以使用 send () 方法发送请求了，send () 方法只有一个参数，就是要发送的内容。通常在 open () 方法中已经通过 url 设置好了参数内容，因此一般 send 方法中的参数传入 null 即可。 open () 和 send () 函数的具体使用方法将通过后文中的一个简单实例来进行说明。</p><h3 id="实例：通过邮编自动获取城市名称"><a href="#实例：通过邮编自动获取城市名称" class="headerlink" title="实例：通过邮编自动获取城市名称"></a>实例：通过邮编自动获取城市名称</h3><h4 id="前端页面"><a href="#前端页面" class="headerlink" title="前端页面"></a>前端页面</h4><p>网页内容如下图所示：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139221653767.png"><br> 当用户在邮编文本框中输入邮编时，则城市文本框内容自动改变为对应城市，我们知道，在传统 web 应用程序中如果要更新网站内容必须重新加载页面，而本实例要求只是更改城市文本框的内容，因此 AJAX 的异步处理方式最适合本实例的要求。 首先我们先准备一个 HTML 表单页面，代码如下：</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">title</span>&gt;</span>AJAX实例-通过邮政编码获取城市名称<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"content-type"</span> <span class="attr">content</span>=<span class="string">"text/html; charset=UTF-8"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    邮编：</span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"text_zipcode"</span> <span class="attr">name</span>=<span class="string">"zipcode"</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">br</span> /&gt;</span> <span class="tag">&lt;<span class="name">br</span> /&gt;</span></span><br><span class="line">    城市：</span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"text_city"</span> <span class="attr">name</span>=<span class="string">"city"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>这个页面很简单，body 中只有两个 input 文本框标签。</p><h4 id="AJAX代码"><a href="#AJAX代码" class="headerlink" title="AJAX代码"></a>AJAX 代码</h4><p>下面我们来编写 AJAX 代码，通常实现一个 AJAX 功能需要四个简单的步骤：</p><ol><li>创建 XMLHttpRequest 对象；</li><li>通过 open () 函数建立连接；</li><li>设置 onreadystatechange 属性并编写回调（callback）函数；</li><li>发送请求 send (null)；</li></ol><p>那么我们按照上面的几个步骤逐步实现： 首先我们需要创建 XHR 对象，该步骤上文已经详细介绍，在此不在赘述； 然后通过 open () 函数建立连接：open () 函数在上文中我们已经知道其可以传入 5 个参数，但实际开发中通常只需要传入三个参数即可，分别是：</p><ul><li>request-type：请求类型。通常有 get 和 post 两种，在本例中由于我们只需要传递一个参数，且该参数无需加密，因此我们采用 get 方式即可，get 与 post 请求方式的区别请自行查阅相关资料；</li><li>url：请求链接地址及参数；</li><li>asynch：连接方式，分为异步连接和同步连接。当值为 true 时为异步连接，当 false 时为同步连接，默认值为 true。通常我们使用异步连接方式。</li></ul><p>那么在本例中，open 函数的代码书写如下：</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xhr.<span class="title function_">open</span>(<span class="string">"get"</span>, <span class="string">"zipcode.do?zipcode="</span>+$(<span class="string">"text_zipcode"</span>).<span class="property">value</span>, <span class="literal">true</span>);</span><br></pre></td></tr></tbody></table></figure><p>在代码中，我们使用 get 方式建立与 zipcode.do 建立连接并传递 zipcode 参数。 接下来我们需要书写回调函数并赋值给 onreadystatechanged 属性，这个回调函数的意义是当 ajax 的 readyState 属性发生改变时所执行的函数，通常在这个函数中我们只关心服务器处理完成 ajax 的请求后并返回响应时我们需要做的操作，因此我们需要在这个函数中加入一个 if 判断，判断是否 readyState 是否为 4，代码如下：</p><figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">xhr.<span class="property">onreadystatechange</span> = <span class="keyword">function</span>(<span class="params"></span>){</span><br><span class="line">    <span class="comment">// 判断ajax状态码（4为成功接受服务端响应）</span></span><br><span class="line">    <span class="keyword">if</span> (xhr.<span class="property">readyState</span> == <span class="number">4</span>) {    </span><br><span class="line">        <span class="comment">// 判断响应状态码（200即正确响应）</span></span><br><span class="line">        <span class="keyword">if</span> (xhr.<span class="property">status</span> == <span class="number">200</span>) {</span><br><span class="line">            <span class="comment">// 更新页面部分内容</span></span><br><span class="line">            $(<span class="string">"text_city"</span>).<span class="property">value</span> = xhr.<span class="property">responseText</span>;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>最后我们书写发送请求代码：send (null)； 那么根据上面的步骤，我们已经实现好了 ajax 的全部代码，整合起来代码如下：</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">title</span>&gt;</span>AJAX实例-通过邮政编码获取城市名称<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"content-type"</span> <span class="attr">content</span>=<span class="string">"text/html; charset=UTF-8"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">window</span>.<span class="property">onload</span> = <span class="keyword">function</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">        $(<span class="string">"text_zipcode"</span>).<span class="property">onkeyup</span> = <span class="keyword">function</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// 1、获取XHR对象</span></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">var</span> xhr = <span class="title function_">createXHR</span>();</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// 判断是否成功获取XHR对象</span></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">if</span> (xhr != <span class="literal">false</span>) {</span></span><br><span class="line"><span class="language-javascript">                <span class="comment">// 2、建立连接</span></span></span><br><span class="line"><span class="language-javascript">                xhr.<span class="title function_">open</span>(<span class="string">"get"</span>, <span class="string">"zipcode.do?zipcode="</span>+$(<span class="string">"text_zipcode"</span>).<span class="property">value</span>, <span class="literal">true</span>);</span></span><br><span class="line"><span class="language-javascript">                <span class="comment">// 3、设置回调函数</span></span></span><br><span class="line"><span class="language-javascript">                xhr.<span class="property">onreadystatechange</span> = <span class="keyword">function</span>(<span class="params"></span>){</span></span><br><span class="line"><span class="language-javascript">                    <span class="comment">// 判断ajax状态码（4为成功接受服务端响应）</span></span></span><br><span class="line"><span class="language-javascript">                    <span class="keyword">if</span> (xhr.<span class="property">readyState</span> == <span class="number">4</span>) {    </span></span><br><span class="line"><span class="language-javascript">                        <span class="comment">// 判断响应状态码（200即正确响应）</span></span></span><br><span class="line"><span class="language-javascript">                        <span class="keyword">if</span> (xhr.<span class="property">status</span> == <span class="number">200</span>) {</span></span><br><span class="line"><span class="language-javascript">                            <span class="comment">// 更新页面部分内容</span></span></span><br><span class="line"><span class="language-javascript">                            $(<span class="string">"text_city"</span>).<span class="property">value</span> = xhr.<span class="property">responseText</span>;</span></span><br><span class="line"><span class="language-javascript">                        }</span></span><br><span class="line"><span class="language-javascript">                    }</span></span><br><span class="line"><span class="language-javascript">                }</span></span><br><span class="line"><span class="language-javascript">                <span class="comment">// 4、发送请求</span></span></span><br><span class="line"><span class="language-javascript">                xhr.<span class="title function_">send</span>(<span class="literal">null</span>);</span></span><br><span class="line"><span class="language-javascript">            }</span></span><br><span class="line"><span class="language-javascript">        };</span></span><br><span class="line"><span class="language-javascript">    };</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">function</span> <span class="title function_">createXHR</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">var</span> xmlHttp = <span class="literal">false</span>;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">try</span> {</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// Firefox, Opera 8.0+, Safari,IE7及以上版本</span></span></span><br><span class="line"><span class="language-javascript">            xmlHttp = <span class="keyword">new</span> <span class="title class_">XMLHttpRequest</span>();</span></span><br><span class="line"><span class="language-javascript">        } <span class="keyword">catch</span> (e) {</span></span><br><span class="line"><span class="language-javascript">            <span class="comment">// Internet Explorer</span></span></span><br><span class="line"><span class="language-javascript">            <span class="keyword">try</span> {</span></span><br><span class="line"><span class="language-javascript">                xmlHttp = <span class="keyword">new</span> <span class="title class_">ActiveXObject</span>(<span class="string">"Msxml2.XMLHTTP"</span>);<span class="comment">//IE较新版本</span></span></span><br><span class="line"><span class="language-javascript">            } <span class="keyword">catch</span> (e) {</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">                <span class="keyword">try</span> {</span></span><br><span class="line"><span class="language-javascript">                    xmlHttp = <span class="keyword">new</span> <span class="title class_">ActiveXObject</span>(<span class="string">"Microsoft.XMLHTTP"</span>);<span class="comment">//IE较老版本</span></span></span><br><span class="line"><span class="language-javascript">                } <span class="keyword">catch</span> (e) {</span></span><br><span class="line"><span class="language-javascript">                    <span class="title function_">alert</span>(<span class="string">"您的浏览器不支持AJAX！"</span>);</span></span><br><span class="line"><span class="language-javascript">                    xmlHttp = <span class="literal">false</span>;</span></span><br><span class="line"><span class="language-javascript">                }</span></span><br><span class="line"><span class="language-javascript">            }</span></span><br><span class="line"><span class="language-javascript">        }</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">return</span> xmlHttp;</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">function</span> <span class="title function_">$</span>(<span class="params">id</span>) {</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">return</span> <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(id);</span></span><br><span class="line"><span class="language-javascript">    };</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    邮编：</span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"text_zipcode"</span> <span class="attr">name</span>=<span class="string">"zipcode"</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">br</span> /&gt;</span>    <span class="tag">&lt;<span class="name">br</span> /&gt;</span> </span><br><span class="line">    城市：</span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"text_city"</span> <span class="attr">name</span>=<span class="string">"city"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>&nbsp;</p><h4 id="后端代码"><a href="#后端代码" class="headerlink" title="后端代码"></a>后端代码</h4><p>现在我们已经写好整个前端页面并实现 ajax，我们来看一下后端代码示例，由于本篇文章主要介绍 AJAX，因此后端实现方法不做过多解释，实现语言为 Java，代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.servlet;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.io.PrintWriter;</span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.ServletException;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServlet;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequest;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletResponse;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ZipcodeServlet</span> <span class="keyword">extends</span> <span class="title class_">HttpServlet</span> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">serialVersionUID</span> <span class="operator">=</span> <span class="number">1L</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">Map</span>&lt;String, String&gt; <span class="variable">map</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, String&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">init</span><span class="params">()</span> {</span><br><span class="line">        map.put(<span class="string">"150000"</span>, <span class="string">"哈尔滨"</span>);</span><br><span class="line">        map.put(<span class="string">"110000"</span>, <span class="string">"北京"</span>);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">doGet</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span></span><br><span class="line">            <span class="keyword">throws</span> ServletException, IOException {</span><br><span class="line"></span><br><span class="line">        response.setContentType(<span class="string">"text/html"</span>);</span><br><span class="line">        response.setCharacterEncoding(<span class="string">"UTF-8"</span>);</span><br><span class="line">        <span class="type">PrintWriter</span> <span class="variable">out</span> <span class="operator">=</span> response.getWriter();</span><br><span class="line">        <span class="type">String</span> <span class="variable">zipcode</span> <span class="operator">=</span> request.getParameter(<span class="string">"zipcode"</span>);</span><br><span class="line">        <span class="keyword">if</span> (map.containsKey(zipcode)) {</span><br><span class="line">            out.println(map.get(zipcode));</span><br><span class="line">        }</span><br><span class="line">        out.flush();</span><br><span class="line">        out.close();</span><br><span class="line"></span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">doPost</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span></span><br><span class="line">            <span class="keyword">throws</span> ServletException, IOException {</span><br><span class="line"></span><br><span class="line">        doGet(request, response);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Ajax 应用的开发确实有些繁琐，实际上，现在有很多成熟稳定的 Ajax 工具葙（例如 JQuery）封装了以上诸多细节，使得 Ajax 编程更加容易。但是如果不知道应用程序在做什么，就很难发现其中的问题。XMLHttpRequest 对象是 Ajax 应用的核心，必须非常熟悉。 Ajax 应用的基本流程：</p><ul><li>创建 XMLHttpRequest 对象</li><li>从 Web 表单中获取需要的数据</li><li>设置要连接的 URL</li><li>建立到服务器的连接</li><li>设置服务器在完成后要运行的回调函数</li><li>发送请求</li></ul><p>至于如何使用 AJAX 操作 XML、JSON 等，我会在以后的文章中在做讨论。</p>]]>
    </content>
    <id>https://www.bytelife.net/articles/30550.html</id>
    <link href="https://www.bytelife.net/articles/30550.html"/>
    <published>2016-07-22T17:46:11.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>前言：Ajax 是当今 Web 应用程序开发过程中必不可少的一种技术，使用它可以通过异步处理的方式极大的提高用户体验，这篇文章将以简单易懂的方式介绍 AJAX 技术。</p>
</blockquote>
<h3 id="AJAX技术简介"><a href="#AJAX技术简介" class="headerlink" title="AJAX技术简介"></a>AJAX 技术简介</h3><p>AJAX 即 “Asynchronous Javascript And XML”（异步 JavaScript 和 XML），音译为 “阿贾克斯”，是指一种创建交互式网页应用的网页开发技术。AJAX 通过在后台服务器进行少量的数据交换，实现对网页的异步刷新，即可以在不重新加载整个网页的情况下，对网页的局部进行更新。 Ajax 不是一个新的技术，事实上，它是一些旧有的成熟的技术以一种全新的更加强大的方式整合在一起 Ajax 的关键技术：</p>
<ul>
<li>使用 XHTML (HTML) 和 CSS 构建标准化的展示层</li>
<li>使用 DOM 进行动态显示和交互</li>
<li>使用 XML 和 XSLT 进行数据交换和操纵</li>
<li>使用 XMLHttpRequest 异步获取数据</li>
<li>使用 JavaScript 将所有元素绑定在一起</li>
</ul>]]>
    </summary>
    <title>AJAX 技术入门详解</title>
    <updated>2016-07-22T17:46:11.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="建站" scheme="https://www.bytelife.net/categories/website/"/>
    <category term="Linux" scheme="https://www.bytelife.net/tags/Linux/"/>
    <content>
      <![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138522621984.jpg"></p><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><ol><li><p>配置防火墙，开启 80 端口、3306 端口、如需使用 SSH 还应开启 22 端口、使用 FTP 需开启 21 端口、如果网站使用 SSL 访问需开启 443 端口： 删除原有的 iptables , 添加合适的配置</p><p>rm -rf /etc/sysconfig/iptables<br>vi /etc/sysconfig/iptables</p><span id="more"></span><p>添加如下内容 :</p><p>################################ 添加好之后防火墙规则如下所示 ################################</p><h1 id="Firewall-configuration-written-by-system-config-firewall"><a href="#Firewall-configuration-written-by-system-config-firewall" class="headerlink" title="Firewall configuration written by system-config-firewall"></a>Firewall configuration written by system-config-firewall</h1><h1 id="Manual-customization-of-this-file-is-not-recommended"><a href="#Manual-customization-of-this-file-is-not-recommended" class="headerlink" title="Manual customization of this file is not recommended."></a>Manual customization of this file is not recommended.</h1><p>*filter<br>:INPUT ACCEPT [0:0]<br>:FORWARD ACCEPT [0:0]<br>:OUTPUT ACCEPT [0:0]<br>-A INPUT -m state –state ESTABLISHED,RELATED -j ACCEPT<br>-A INPUT -p icmp -j ACCEPT<br>-A INPUT -i lo -j ACCEPT<br>-A INPUT -m state –state NEW -m tcp -p tcp –dport 22 -j ACCEPT<br>-A INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT<br>-A INPUT -m state –state NEW -m tcp -p tcp –dport 8080 -j ACCEPT<br>-A INPUT -m state –state NEW -m tcp -p tcp –dport 8081 -j ACCEPT<br>-A INPUT -m state –state NEW -m tcp -p tcp –dport 8082 -j ACCEPT<br>-A INPUT -m state –state NEW -m tcp -p tcp –dport 3306 -j ACCEPT<br>-A INPUT -j REJECT –reject-with icmp-host-prohibited<br>-A FORWARD -j REJECT –reject-with icmp-host-prohibited<br>COMMIT<br>#######################################################################################</p><p><code>:wq</code>保存退出, 重启防火墙使配置生效</p><p>/etc/init.d/iptables restart</p></li><li><p>关闭 SELINUX</p><p>rm -rf  /etc/selinux/config<br>vi /etc/selinux/config</p><p>添加一行内容:</p><p>SELINUX=disabled</p><p><code>:wq</code>保存退出</p><p>#重启系统<br>shutdown -r now</p></li><li><p>安装第三方 yum 源</p><p>#安装下载工具<br>yum install wget<br>#下载<br>wget <span class="exturl" data-url="aHR0cDovL3d3dy5hdG9taWNvcnAuY29tL2luc3RhbGxlcnMvYXRvbWlj">http://www.atomicorp.com/installers/atomic<i class="fa fa-external-link-alt"></i></span><br>#安装<br>sh ./atomic<br>#更新 yum 源<br>yum check-update</p></li></ol><h2 id="开始安装"><a href="#开始安装" class="headerlink" title="开始安装"></a>开始安装</h2><h3 id="一-安装nginx"><a href="#一-安装nginx" class="headerlink" title="一. 安装nginx"></a>一. 安装 nginx</h3><p>#删除系统自带的软件包<br>yum remove httpd* php*<br>#安装 nginx<br>yum install -y nginx<br>#设置 nginx 开机启动<br>chkconfig nginx on<br>#启动 nginx<br>service nginx start</p><h3 id="二-安装PHP"><a href="#二-安装PHP" class="headerlink" title="二. 安装PHP"></a>二. 安装 PHP</h3><ol><li><p>检查当前安装的 PHP 包</p><p>yum list installed | grep php</p><p>如果有安装的 PHP 包，先删除他们, 如:</p><p>yum remove php.x86_64 php-cli.x86_64 php-common.x86_64</p></li><li><p>配置安装包源:</p><h1 id="Centos-5-X"><a href="#Centos-5-X" class="headerlink" title="Centos 5.X"></a>Centos 5.X</h1><p>rpm -Uvh <span class="exturl" data-url="aHR0cDovL21pcnJvci53ZWJ0YXRpYy5jb20veXVtL2VsNS9sYXRlc3QucnBt">http://mirror.webtatic.com/yum/el5/latest.rpm<i class="fa fa-external-link-alt"></i></span></p><h1 id="CentOs-6-x"><a href="#CentOs-6-x" class="headerlink" title="CentOs 6.x"></a>CentOs 6.x</h1><p>rpm -Uvh <span class="exturl" data-url="aHR0cDovL21pcnJvci53ZWJ0YXRpYy5jb20veXVtL2VsNi9sYXRlc3QucnBt">http://mirror.webtatic.com/yum/el6/latest.rpm<i class="fa fa-external-link-alt"></i></span></p><h1 id="CentOs-7-X"><a href="#CentOs-7-X" class="headerlink" title="CentOs 7.X"></a>CentOs 7.X</h1><p>rpm -Uvh <span class="exturl" data-url="aHR0cHM6Ly9taXJyb3Iud2VidGF0aWMuY29tL3l1bS9lbDcvZXBlbC1yZWxlYXNlLnJwbQ==">https://mirror.webtatic.com/yum/el7/epel-release.rpm<i class="fa fa-external-link-alt"></i></span><br>rpm -Uvh <span class="exturl" data-url="aHR0cHM6Ly9taXJyb3Iud2VidGF0aWMuY29tL3l1bS9lbDcvd2VidGF0aWMtcmVsZWFzZS5ycG0=">https://mirror.webtatic.com/yum/el7/webtatic-release.rpm<i class="fa fa-external-link-alt"></i></span></p><p>如果想删除上面安装的包，重新安装</p><p>rpm -qa | grep webstatic<br>rpm -e  [上面搜索到的包即可]</p></li><li><p>执行安装</p><p>yum -y install php56w.x86_64<br>yum -y –enablerepo=webtatic install php56w-devel<br>yum -y install php56w-xml.x86_64 php56w-gd.x86_64 php56w-ldap.x86_64 php56w-mbstring.x86_64 php56w-mcrypt.x86_64 php56w-mysql.x86_64 php56w-pdo.x86_64 php56w-opcache.x86_64</p></li><li><p>安装 PHP FPM</p><p>yum -y install php56w-fpm<br>#设置 php-fpm 开机启动<br>chkconfig php-fpm on<br>#启动 php-fpm<br>/etc/init.d/php-fpm start</p><p>注：如果想更换到 php5.5 或 5.4 版本, 直接把上面的 56w 换成 55w 或者 54w 就可以了</p></li></ol><h3 id="三-安装-MySQL"><a href="#三-安装-MySQL" class="headerlink" title="三. 安装 MySQL"></a>三. 安装 MySQL</h3><ol><li><p>安装</p><p>yum install -y mysql mysql-server<br>#启动 MySQL<br>/etc/init.d/mysqld start<br>#设为开机启动<br>chkconfig mysqld on<br>#拷贝配置文件（注意：如果 /etc 目录下面默认有一个 my.cnf，直接覆盖即可）<br>cp /usr/share/mysql/my-medium.cnf /etc/my.cnf</p></li><li><p>为 root 账户设置密码</p><p>mysql_secure_installation</p><h1 id="回车，根据提示输入Y，输入2次密码，回车，根据提示一路输入Y，最后出现：Thanks-for-using-MySQL"><a href="#回车，根据提示输入Y，输入2次密码，回车，根据提示一路输入Y，最后出现：Thanks-for-using-MySQL" class="headerlink" title="回车，根据提示输入Y，输入2次密码，回车，根据提示一路输入Y，最后出现：Thanks for using MySQL!"></a>回车，根据提示输入 Y，输入 2 次密码，回车，根据提示一路输入 Y，最后出现：Thanks for using MySQL!</h1><h1 id="MySql密码设置完成，重新启动-MySQL："><a href="#MySql密码设置完成，重新启动-MySQL：" class="headerlink" title="MySql密码设置完成，重新启动 MySQL："></a>MySql 密码设置完成，重新启动 MySQL：</h1><p>#重启<br>/etc/init.d/mysqld restart<br>#停止<br>/etc/init.d/mysqld stop<br>#启动<br>/etc/init.d/mysqld start</p></li></ol><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><h3 id="1-配置nginx"><a href="#1-配置nginx" class="headerlink" title="1. 配置nginx"></a>1. 配置 nginx</h3><p>rm -rf /etc/nginx/conf.d/*<br>vi /etc/nginx/conf.d/default.conf</p><p>添加如下内容 :</p><p>server{<br>    listen      80;<br>    server_name _;<br>    index index.php index.html index.htm;<br>    root  /var/www;</p><pre><code>location ~ .*.(php|php5)?${        #fastcgi_pass  unix:/tmp/php-cgi.sock;        fastcgi_pass  127.0.0.1:9000;        fastcgi_index index.php;        include fastcgi.conf;}location / {    try\_files $uri $uri/ /index.php?$query\_string;}</code></pre><p>}</p><blockquote><p><em>说明: <code>/var/www</code> 为 web 根目录, <code>location / ...</code> 为 url 的 rewrite, 隐藏 <code>index.php</code></em></p></blockquote><h3 id="2-配置php-fpm"><a href="#2-配置php-fpm" class="headerlink" title="2. 配置php-fpm"></a>2. 配置 php-fpm</h3><p>vi /etc/php-fpm.d/<span class="exturl" data-url="aHR0cDovL3d3dy5jb25mLw==">www.conf<i class="fa fa-external-link-alt"></i></span></p><p>将用户和用户组设置为 nginx, 默认为 Apache, 如:</p><p>#修改用户为 nginx<br>user = nginx<br> #修改组为 nginx<br>group = nginx</p><h2 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h2><p>启动命令:</p><h1 id="nginx-重启-启动-停止"><a href="#nginx-重启-启动-停止" class="headerlink" title="nginx 重启 | 启动 | 停止"></a>nginx 重启 | 启动 | 停止</h1><p>service nginx restart | start | stop </p><h1 id="php-fpm-重启-启动-停止"><a href="#php-fpm-重启-启动-停止" class="headerlink" title="php-fpm 重启 | 启动 | 停止"></a>php-fpm 重启 | 启动 | 停止</h1><p>service php-fpm restart | start | stop </p><h1 id="mysql-重启-启动-停止"><a href="#mysql-重启-启动-停止" class="headerlink" title="mysql 重启 | 启动 | 停止"></a>mysql 重启 | 启动 | 停止</h1><p>service mysqld restart | start | stop</p><p>一些文件的目录:</p><h1 id="nginx-conf"><a href="#nginx-conf" class="headerlink" title="nginx.conf"></a>nginx.conf</h1><p>/etc/nginx/nginx.conf</p><h1 id="php-ini"><a href="#php-ini" class="headerlink" title="php.ini"></a>php.ini</h1><p>/etc/php.ini</p><h1 id="my-cnf"><a href="#my-cnf" class="headerlink" title="my.cnf"></a>my.cnf</h1><p>/etc/my.cnf</p><h1 id="项目根目录"><a href="#项目根目录" class="headerlink" title="项目根目录"></a>项目根目录</h1><p>/var/www</p><h2 id="开始测试"><a href="#开始测试" class="headerlink" title="开始测试"></a>开始测试</h2><p>vi /var/www/index.php</p><p>添加以下代码</p><!--?phpphpinfo();?--><p><code>:wq!</code> 保存退出</p><p>#设置权限<br>chown nginx.nginx /var/www -R<br>#重启 nginx<br>service nginx restart<br>#重启 php-fpm<br>service php-fpm restart</p><p>在客户端浏览器输入服务器 IP 地址 (如: 127.0.0.1)，可以看到相关的配置信息！ 说明 lnmp 配置成功！ &nbsp;</p><blockquote><p>本文参考自：<span class="exturl" data-url="aHR0cDovL3d3dy5qaWFuc2h1LmNvbS9wLzQ0N2UwMmQ3OTUxZA==">点击进入<i class="fa fa-external-link-alt"></i></span></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/19465.html</id>
    <link href="https://www.bytelife.net/articles/19465.html"/>
    <published>2016-07-18T04:28:53.000Z</published>
    <summary>
      <![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16138522621984.jpg"></p>
<h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><ol>
<li><p>配置防火墙，开启 80 端口、3306 端口、如需使用 SSH 还应开启 22 端口、使用 FTP 需开启 21 端口、如果网站使用 SSL 访问需开启 443 端口： 删除原有的 iptables , 添加合适的配置</p>
<p>rm -rf /etc/sysconfig/iptables<br>vi /etc/sysconfig/iptables</p>]]>
    </summary>
    <title>完美配置 CentOS+Nginx+MySQL+PHP（LNMP）网站环境</title>
    <updated>2016-07-18T04:28:53.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <content>
      <![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139162607170.jpg"></p><h3 id="XPath路径表达式"><a href="#XPath路径表达式" class="headerlink" title="XPath路径表达式"></a>XPath 路径表达式</h3><p>XPath 即为 XML 路径语言，它是一种用来确定 XML（标准通用标记语言的子集）文档中某部分位置的语言。XPath 基于 XML 的树状结构，提供在数据结构树中找寻节点的能力。XPath 使用路径表达式去确定 XML 文档中的节点。</p><span id="more"></span><h3 id="示例XML文档"><a href="#示例XML文档" class="headerlink" title="示例XML文档"></a>示例 XML 文档</h3><p>我们将利用下面的 XML 文档描述 XPath 语法</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"GB2312"</span>?&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">order</span>&gt;</span></span><br><span class="line">&nbsp; <span class="tag">&lt;<span class="name">item</span> <span class="attr">catalog</span>=<span class="string">"parts"</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">itemNumber</span>&gt;</span>C2688-67037<span class="tag">&lt;/<span class="name">itemNumber</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">description</span>&gt;</span>LCD液晶显示器<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">quantity</span>&gt;</span>1<span class="tag">&lt;/<span class="name">quantity</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">price</span>&gt;</span>358.00<span class="tag">&lt;/<span class="name">price</span>&gt;</span></span><br><span class="line">&nbsp; <span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">&nbsp;<span class="tag">&lt;<span class="name">item</span> <span class="attr">catalog</span>=<span class="string">"parts"</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">itemNumber</span>&gt;</span>C2688-67061<span class="tag">&lt;/<span class="name">itemNumber</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">description</span>&gt;</span>音箱<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">quantity</span>&gt;</span>1<span class="tag">&lt;/<span class="name">quantity</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">price</span>&gt;</span>16.50<span class="tag">&lt;/<span class="name">price</span>&gt;</span></span><br><span class="line">&nbsp; <span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;<span class="tag">&lt;<span class="name">item</span> <span class="attr">catalog</span>=<span class="string">"parts"</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">itemNumber</span>&gt;</span>C2688-67010<span class="tag">&lt;/<span class="name">itemNumber</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">description</span>&gt;</span>鼠标<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">quantity</span>&gt;</span>1<span class="tag">&lt;/<span class="name">quantity</span>&gt;</span></span><br><span class="line">&nbsp;&nbsp;&nbsp; <span class="tag">&lt;<span class="name">price</span>&gt;</span>8.50<span class="tag">&lt;/<span class="name">price</span>&gt;</span></span><br><span class="line">&nbsp; <span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">order</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><h3 id="从根节点选取“-”"><a href="#从根节点选取“-”" class="headerlink" title="从根节点选取“/”"></a>从根节点选取 “/”</h3><p>XML 文挡可以表示为树结构节点形式 XPath 使用模式表达式识别 XML 文档的节点。 一个 XPath 的模式是使用反斜杠 “/” 分开子元素名称描述路径 下面的 XPath 表达式选择元素 order 下元素 item 中的所有 price 元素</p><blockquote><p>/order/item/price</p></blockquote><p>注释：用 “/” 路径开始代表元素的绝对路径，不用 “/” 路径开始代表元素的相对路径</p><blockquote><p>item/price</p></blockquote><h3 id="从整个文档选取“-”"><a href="#从整个文档选取“-”" class="headerlink" title="从整个文档选取“//”"></a>从整个文档选取 “//”</h3><p>用 “//” 路径开始代表整个文档满足条件的所有元素，不考虑它们的位置 下面的 XPath 表达式选择文档中所有的 item 元素</p><blockquote><p>//item</p></blockquote><h3 id="选择未知元素“-”"><a href="#选择未知元素“-”" class="headerlink" title="选择未知元素“*”"></a>选择未知元素 “*”</h3><p>通配符 “*” 可用于选择未知 XML 元素 下面的 XPath 表达式选择元素 order 中的所有 item 元素所属的子元素</p><blockquote><p>/order/item/*</p></blockquote><p>下面的 XPath 表达式选择元素 order 下所有孙子辈的 price 元素</p><blockquote><p>/order/*/price</p></blockquote><p>下面的 XPath 表达式选择所有具有两个祖先的 price 元素</p><blockquote><p>/<em>/</em>/price</p></blockquote><p>下面的 XPath 表达式选择文档所有元素</p><blockquote><p>//*</p></blockquote><h3 id="选择分支“-”（谓语）"><a href="#选择分支“-”（谓语）" class="headerlink" title="选择分支“[]”（谓语）"></a>选择分支 “[]”（谓语）</h3><p>使用方括号 [] 可以指定特定的元素 下面的 XPath 表达式选择元素 order 中的第一个 item 的子元素</p><blockquote><p>/order/item[1]</p></blockquote><p>下面的 XPath 表达式选择元素 order 中的最后一个 item 的子元素</p><blockquote><p>/order/item[last()]</p></blockquote><p>下面的 XPath 表达式选择元素 order 中具有 price 元素的 item 元素</p><blockquote><p>/order/item[price]</p></blockquote><p>下面的 XPath 表达式, 从元素 order 中选择具有 price 等于 12.60 元素的 item 元素</p><blockquote><p>/order/item[price=16.50]</p></blockquote><p>下面的 XPath 表达式, 从隶属于元素 order 的 item 元素中选择具有 price 等于 12.60 元素的 price 元素</p><blockquote><p>/order/item[price=16.50]/price</p></blockquote><h3 id="选择几个路径“-”"><a href="#选择几个路径“-”" class="headerlink" title="选择几个路径“|”"></a>选择几个路径 “|”</h3><p>在 XPath 表达式中，使用 “|” 运算符可以选择几个路径 。实质上是逻辑 “与” 操作 下面的 XPath 表达式, 从隶属于 order 的 item 元素中选择所有 itemNumber 和 description 元素</p><blockquote><p>/order/item/itemNumber | /order/item/description</p></blockquote><p>下面的 XPath 表达式, 从文档中选择所有 itemNumber 和 description 元素</p><blockquote><p>//itemNumber | //description</p></blockquote><p>下面的 XPath 表达式, 从文档中选择所有 itemNumber ，description 和 price 元素</p><blockquote><p>//itemNumber | //description | //price</p></blockquote><p>下面的 XPath 表达式, 选取属于 order 中 item 下所有 itemNumber 元素和从文档中选择所有 description 元素</p><blockquote><p>/order/item/itemnumber | //description</p></blockquote><h3 id="选择属性“-”"><a href="#选择属性“-”" class="headerlink" title="选择属性“@”"></a>选择属性 “@”</h3><p>在 XPath 中，所有属性使用 @前缀 下面的 XPath 表达式, 选取所有名为 catalog 的属性</p><blockquote><p>//@catalog</p></blockquote><p>下面的 XPath 表达式, 选取所有具有 catalog 属性的 item 元素</p><blockquote><p>//item[@catalog]</p></blockquote><p>下面的 XPath 表达式, 选取所有具有任何属性的 item 元素</p><blockquote><p>//item[@*]</p></blockquote><p>下面的 XPath 表达式, 选取所有具有 catalog 等于 ”parts” 属性的 item 元素</p><blockquote><p>//item[@catalog=”parts”]</p></blockquote><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>（1）选取节点 下面列出了最有用的路径表达式：</p><table><thead><tr><th>表达式</th><th>描述</th></tr></thead><tbody><tr><td>nodename</td><td>选取此节点的所有子节点。</td></tr><tr><td>/</td><td>从根节点选取。</td></tr><tr><td>//</td><td>从匹配选择的当前节点选择文档中的节点，而不考虑它们的位置。</td></tr><tr><td>.</td><td>选取当前节点。</td></tr><tr><td>..</td><td>选取当前节点的父节点。</td></tr><tr><td>@</td><td>选取属性。</td></tr></tbody></table><p>（2）谓语（Predicates） 谓语用来查找某个特定的节点或者包含某个指定的值的节点。 谓语被嵌在方括号 [] 中。 （3）选取未知节点 XPath 通配符可用来选取未知的 XML 元素。</p><table><thead><tr><th>通配符</th><th>描述</th></tr></thead><tbody><tr><td>*</td><td>匹配任何元素节点。</td></tr><tr><td>@*</td><td>匹配任何属性节点。</td></tr><tr><td>node()</td><td>匹配任何类型的节点。</td></tr></tbody></table><p>（4）选取若干路径 通过在路径表达式中使用 “|” 运算符，您可以选取若干个路径。 更多有关 XPath 语法规则请访问 <span class="exturl" data-url="aHR0cDovL3d3dy53M3NjaG9vbC5jb20uY24veHBhdGgveHBhdGhfc3ludGF4LmFzcA==">W3CSchool<i class="fa fa-external-link-alt"></i></span>。 </p>]]>
    </content>
    <id>https://www.bytelife.net/articles/780.html</id>
    <link href="https://www.bytelife.net/articles/780.html"/>
    <published>2016-07-17T02:09:55.000Z</published>
    <summary>
      <![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139162607170.jpg"></p>
<h3 id="XPath路径表达式"><a href="#XPath路径表达式" class="headerlink" title="XPath路径表达式"></a>XPath 路径表达式</h3><p>XPath 即为 XML 路径语言，它是一种用来确定 XML（标准通用标记语言的子集）文档中某部分位置的语言。XPath 基于 XML 的树状结构，提供在数据结构树中找寻节点的能力。XPath 使用路径表达式去确定 XML 文档中的节点。</p>]]>
    </summary>
    <title>XPath 表达式语法详解</title>
    <updated>2016-07-17T02:09:55.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <content>
      <![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139161438607.png"></p><blockquote><p>引言：XML（可扩展标记语言）在软件开发工程中取得了广泛的应用。在 Java 语言中操作 XML 有许多方法，最常用的方法就是使用 JDom、Dom4j 等第三方组件。本文将简单介绍使用 Dom4j 操作 XML 的基本方法。</p></blockquote><span id="more"></span><p>本文采用的 Dom4j 版本为 1.6.1，下载地址见文章结尾。 废话不多说，先来看一下本文使用的 XML 文件内容：</p><figure class="highlight xml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">class</span> <span class="attr">id</span>=<span class="string">"1"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">student</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">num</span>&gt;</span>0001<span class="tag">&lt;/<span class="name">num</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">name</span>&gt;</span>张三<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">age</span>&gt;</span>19<span class="tag">&lt;/<span class="name">age</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">student</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">student</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">num</span>&gt;</span>0002<span class="tag">&lt;/<span class="name">num</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">name</span>&gt;</span>李四<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">age</span>&gt;</span>21<span class="tag">&lt;/<span class="name">age</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">hobby</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">name</span>&gt;</span>足球<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">name</span>&gt;</span>篮球<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">hobby</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">student</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">teacher</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">name</span>&gt;</span>王老师<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">age</span>&gt;</span>40<span class="tag">&lt;/<span class="name">age</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">course</span>&gt;</span>Java<span class="tag">&lt;/<span class="name">course</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">teacher</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">class</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure><p>在这个 XML 文件中，可以看到根节点为 class，它有 student 和 teacher 子节点，而 student 子节点中又包含 num、name、age、hobby 等孙子辈节点，teacher 子节点中包含 name、age、course 等孙子辈节点。 下面就使用 dom4j 来操作这个 xml 文件。</p><h3 id="解析XML"><a href="#解析XML" class="headerlink" title="解析XML"></a>解析 XML</h3><p>当使用 dom4j 操作 xml 时，你想做的第一件事可能就是解析一个 Xml 文档，这个操作在 dom4j 中十分容易，使用下面的代码即可以轻松的解析 xml 文件并返回一个 Document 对象。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.javacodes.dom4j;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.dom4j.Document;</span><br><span class="line"><span class="keyword">import</span> org.dom4j.Element;</span><br><span class="line"><span class="keyword">import</span> org.dom4j.io.SAXReader;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestDom4j</span> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line">        <span class="comment">// 获取SAX阅读器</span></span><br><span class="line">        <span class="type">SAXReader</span> <span class="variable">reader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SAXReader</span>();</span><br><span class="line">        <span class="comment">// 获取Document对象</span></span><br><span class="line">        <span class="type">Document</span> <span class="variable">doc</span> <span class="operator">=</span> reader.read(<span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">"d:/DemoXML.xml"</span>));</span><br><span class="line">        <span class="comment">// 获取根节点</span></span><br><span class="line">        <span class="type">Element</span> <span class="variable">root</span> <span class="operator">=</span> doc.getRootElement();</span><br><span class="line">        <span class="comment">// 输出测试</span></span><br><span class="line">        System.out.println(<span class="string">"根节点："</span> + root.getName() + <span class="string">",id="</span></span><br><span class="line">                \+ root.attributeValue(<span class="string">"id"</span>));</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出结果：</p><blockquote><p>根节点：class,id=1</p></blockquote><h3 id="使用迭代器Iterator"><a href="#使用迭代器Iterator" class="headerlink" title="使用迭代器Iterator"></a>使用迭代器 Iterator</h3><p>一个 Element 对象可以通过几个方法来返回一个标准的 Java 迭代器： （1）迭代所有子元素</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 迭代root元素的所有子元素</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">Iterator</span> <span class="variable">i</span> <span class="operator">=</span> root.elementIterator(); i.hasNext(); ) {</span><br><span class="line">      <span class="type">Element</span> <span class="variable">element</span> <span class="operator">=</span> (Element) i.next();</span><br><span class="line">      System.out.println(element.getName());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出结果：</p><blockquote><p>student student teacher</p></blockquote><p>（2）通过元素名称迭代</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通过元素名称“student”迭代子元素</span></span><br><span class="line"><span class="keyword">for</span> ( <span class="type">Iterator</span> <span class="variable">i</span> <span class="operator">=</span> root.elementIterator( <span class="string">"student"</span> ); i.hasNext(); ) {</span><br><span class="line">    <span class="type">Element</span> <span class="variable">foo</span> <span class="operator">=</span> (Element) i.next();</span><br><span class="line">    System.out.println(foo.getName());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出结果：</p><blockquote><p>student student</p></blockquote><p>（3）迭代所有属性</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 迭代root元素的所有属性</span></span><br><span class="line"><span class="keyword">for</span> ( <span class="type">Iterator</span> <span class="variable">i</span> <span class="operator">=</span> root.attributeIterator(); i.hasNext(); ) {</span><br><span class="line">      <span class="type">Attribute</span> <span class="variable">attribute</span> <span class="operator">=</span> (Attribute) i.next();</span><br><span class="line">      System.out.println(attribute.getName() + <span class="string">":"</span> + attribute.getValue());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出结果：</p><blockquote><p>id:1</p></blockquote><h3 id="获取元素值"><a href="#获取元素值" class="headerlink" title="获取元素值"></a>获取元素值</h3><p>通常我们都需要获取 xml 元素标签内部的文本，也就是元素值，下面一个简单的例子递归显示所有的元素值：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span>  <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">showAllElementText</span><span class="params">(Element e)</span>{</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">Iterator</span> <span class="variable">i</span> <span class="operator">=</span> e.elementIterator(); i.hasNext(); ) {</span><br><span class="line">           <span class="type">Element</span> <span class="variable">element</span> <span class="operator">=</span> (Element) i.next();</span><br><span class="line">           <span class="keyword">if</span> (!element.elements().isEmpty()) {</span><br><span class="line">                showAllElementText(element);</span><br><span class="line">           } <span class="keyword">else</span> {</span><br><span class="line">                System.out.println(element.getName()+<span class="string">"="</span>+element.getTextTrim());</span><br><span class="line">           }            </span><br><span class="line">      }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出结果</p><blockquote><p>num=0001 name = 张三 age=19 num=0002 name = 李四 age=21 name = 足球 name = 篮球 name = 王老师 age=40 course=Java</p></blockquote><h3 id="使用XPath表达式"><a href="#使用XPath表达式" class="headerlink" title="使用XPath表达式"></a>使用 XPath 表达式</h3><p>在 Dom4j 中使用 XPath 表达式可以更加轻松的操作 XML 文档，使用 XPath 表达式可以使用仅一行代码来进行复杂的操作，在 Dom4j 中使用 XPath 的几个简单示例代码如下： （1）查询单个节点（默认查找第一个）：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取SAX阅读器</span></span><br><span class="line"><span class="type">SAXReader</span> <span class="variable">reader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SAXReader</span>();</span><br><span class="line"><span class="comment">// 获取Document对象</span></span><br><span class="line"><span class="type">Document</span> <span class="variable">doc</span> <span class="operator">=</span> reader.read(<span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">"d:/DemoXML.xml"</span>));</span><br><span class="line"><span class="comment">// 获取student元素的name节点</span></span><br><span class="line"><span class="type">Node</span> <span class="variable">node</span> <span class="operator">=</span> doc.selectSingleNode(<span class="string">"//student/name"</span>);</span><br><span class="line"><span class="comment">// 输出测试</span></span><br><span class="line">System.out.println(node.getName() + <span class="string">"="</span> + node.getText());</span><br></pre></td></tr></tbody></table></figure><p>（2）查询多个节点</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取所有student元素的name节点</span></span><br><span class="line"><span class="type">List</span>&lt;Node&gt; <span class="variable">list</span> <span class="operator">=</span> doc.selectNodes(<span class="string">"//student"</span>);</span><br><span class="line"><span class="comment">// 输出测试</span></span><br><span class="line"><span class="keyword">for</span> (Node node : list) {</span><br><span class="line">&nbsp;&nbsp; &nbsp;System.out.println(node.getName() </span><br><span class="line">                 \+ <span class="string">":"</span> \+ node.valueOf(<span class="string">"name"</span>));</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>以上为两种经常使用的方法，另外如果你想在一个 XHTML 文档中查找到所有的超文本链接，可以使用下面这个窍门轻松实现：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">void</span> <span class="title function_">findLinks</span><span class="params">(Document document)</span> <span class="keyword">throws</span> DocumentException {</span><br><span class="line">    <span class="type">List</span> <span class="variable">list</span> <span class="operator">=</span> document.selectNodes( <span class="string">"//a/@href"</span> );</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">Iterator</span> <span class="variable">iter</span> <span class="operator">=</span> list.iterator(); iter.hasNext(); ) {</span><br><span class="line">        <span class="type">Attribute</span> <span class="variable">attribute</span> <span class="operator">=</span> (Attribute) iter.next();</span><br><span class="line">        <span class="type">String</span> <span class="variable">url</span> <span class="operator">=</span> attribute.getValue();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>如果你需要任何有关学习 XPah 表达式语言的帮助，你可以访问 <span class="exturl" data-url="aHR0cDovL3d3dy56dm9uLm9yZy94eGwvWFBhdGhUdXRvcmlhbC9HZW5lcmFsL2V4YW1wbGVzLmh0bWw=" title="External Link">Zvon tutorial<i class="fa fa-external-link-alt"></i></span> 进行学习，这里可以通过各种各样的例子帮助你学习。</p><h3 id="快速循环"><a href="#快速循环" class="headerlink" title="快速循环"></a>快速循环</h3><p>如果你需要操作一个十分庞大的 XML 文档，那么你应该使用快速循环的方法以避免在每次循环都创建 Iterator 对象，下面是一个简单的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">void</span> <span class="title function_">treeWalk</span><span class="params">(Document document)</span> {</span><br><span class="line">    treeWalk( document.getRootElement() );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="type">void</span> <span class="title function_">treeWalk</span><span class="params">(Element element)</span> {</span><br><span class="line">    <span class="keyword">for</span> ( <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>, size = element.nodeCount(); i &lt; size; i++ ) {</span><br><span class="line">        <span class="type">Node</span> <span class="variable">node</span> <span class="operator">=</span> element.node(i);</span><br><span class="line">        <span class="keyword">if</span> ( node <span class="keyword">instanceof</span> Element ) {</span><br><span class="line">            treeWalk( (Element) node );</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">else</span> {</span><br><span class="line">            <span class="comment">// 这里写你想要做的操作</span></span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="创建XML-Document对象"><a href="#创建XML-Document对象" class="headerlink" title="创建XML Document对象"></a>创建 XML Document 对象</h3><p>在使用 Dom4j 时经常需要创建一个新的 document，下面是一个简单的示例：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.dom4j.Document;</span><br><span class="line"><span class="keyword">import</span> org.dom4j.DocumentHelper;</span><br><span class="line"><span class="keyword">import</span> org.dom4j.Element;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Foo</span> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">Document</span> <span class="title function_">createDocument</span><span class="params">()</span> {</span><br><span class="line">        <span class="type">Document</span> <span class="variable">document</span> <span class="operator">=</span> DocumentHelper.createDocument();</span><br><span class="line">        <span class="type">Element</span> <span class="variable">root</span> <span class="operator">=</span> document.addElement( <span class="string">"root"</span> );</span><br><span class="line"></span><br><span class="line">        <span class="type">Element</span> <span class="variable">author1</span> <span class="operator">=</span> root.addElement( <span class="string">"author"</span> )</span><br><span class="line">            .addAttribute( <span class="string">"name"</span>, <span class="string">"James"</span> )</span><br><span class="line">            .addAttribute( <span class="string">"location"</span>, <span class="string">"UK"</span> )</span><br><span class="line">            .addText( <span class="string">"James Strachan"</span> );</span><br><span class="line">        </span><br><span class="line">        <span class="type">Element</span> <span class="variable">author2</span> <span class="operator">=</span> root.addElement( <span class="string">"author"</span> )</span><br><span class="line">            .addAttribute( <span class="string">"name"</span>, <span class="string">"Bob"</span> )</span><br><span class="line">            .addAttribute( <span class="string">"location"</span>, <span class="string">"US"</span> )</span><br><span class="line">            .addText( <span class="string">"Bob McWhirter"</span> );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> document;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="写入XML文件"><a href="#写入XML文件" class="headerlink" title="写入XML文件"></a>写入 XML 文件</h3><p>使用 Dom4j 将 Document 对象写入到 XML 文件十分简单，你只需要 1 行代码即可解决：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">document.write( <span class="keyword">new</span> <span class="title class_">FileWriter</span>( <span class="string">"foo.xml"</span> ));</span><br></pre></td></tr></tbody></table></figure><p>如果你想修改输出的格式，例如更易读的排版或者压缩（紧凑）的排版，再或者你想通过 Writer 或 OutputStream 进行输出，那么你可以使用 XMLWriter 类：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.dom4j.Document;</span><br><span class="line"><span class="keyword">import</span> org.dom4j.io.OutputFormat;</span><br><span class="line"><span class="keyword">import</span> org.dom4j.io.XMLWriter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Foo</span> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">write</span><span class="params">(Document document)</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 写入到一个文件</span></span><br><span class="line">        <span class="type">XMLWriter</span> <span class="variable">writer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">XMLWriter</span>(</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">FileWriter</span>( <span class="string">"output.xml"</span> )</span><br><span class="line">        );</span><br><span class="line">        writer.write( document );</span><br><span class="line">        writer.close();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 更加美观的排版</span></span><br><span class="line">        <span class="type">OutputFormat</span> <span class="variable">format</span> <span class="operator">=</span> OutputFormat.createPrettyPrint();</span><br><span class="line">        writer = <span class="keyword">new</span> <span class="title class_">XMLWriter</span>( System.out, format );</span><br><span class="line">        writer.write( document );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 更加紧凑的排版</span></span><br><span class="line">        format = OutputFormat.createCompactFormat();</span><br><span class="line">        writer = <span class="keyword">new</span> <span class="title class_">XMLWriter</span>( System.out, format );</span><br><span class="line">        writer.write( document );</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="Document对象与XML代码互转"><a href="#Document对象与XML代码互转" class="headerlink" title="Document对象与XML代码互转"></a>Document 对象与 XML 代码互转</h3><p>如果你想通过一个 Document 对象或其他任何节点对象（例如 Attribute 或 Element），你可以通过 asXML () 方法将它转换为 XML 文本字符串，例如：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Document</span> <span class="variable">document</span> <span class="operator">=</span> ...;</span><br><span class="line"><span class="type">String</span> <span class="variable">text</span> <span class="operator">=</span> document.asXML();</span><br></pre></td></tr></tbody></table></figure><p>如果你想从一个 XML 文本字符串转为一个 Document 对象，你可以使用 DocumentHelper.parseText() 方法进行解析：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">text</span> <span class="operator">=</span> <span class="string">"&lt;person&gt; &lt;name&gt;James&lt;/name&gt; &lt;/person&gt;"</span>;</span><br><span class="line"><span class="type">Document</span> <span class="variable">document</span> <span class="operator">=</span> DocumentHelper.parseText(text);</span><br></pre></td></tr></tbody></table></figure><h3 id="XSLT"><a href="#XSLT" class="headerlink" title="XSLT"></a>XSLT</h3><p>通过 Sum 公司提供的 JAXP API 在一个 Document 上应用 XSLT 十分简单。这里有一个使用 JAXP 创建一个 transformer 并应用到 Document 上的例子：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> javax.xml.transform.Transformer;</span><br><span class="line"><span class="keyword">import</span> javax.xml.transform.TransformerFactory;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.dom4j.Document;</span><br><span class="line"><span class="keyword">import</span> org.dom4j.io.DocumentResult;</span><br><span class="line"><span class="keyword">import</span> org.dom4j.io.DocumentSource;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Foo</span> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">Document</span> <span class="title function_">styleDocument</span><span class="params">(</span></span><br><span class="line"><span class="params">        Document document, </span></span><br><span class="line"><span class="params">        String stylesheet</span></span><br><span class="line"><span class="params">    )</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 使用JAXP加载transformer</span></span><br><span class="line">        <span class="type">TransformerFactory</span> <span class="variable">factory</span> <span class="operator">=</span> TransformerFactory.newInstance();</span><br><span class="line">        <span class="type">Transformer</span> <span class="variable">transformer</span> <span class="operator">=</span> factory.newTransformer( </span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">StreamSource</span>( stylesheet ) </span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 样式化document</span></span><br><span class="line">        <span class="type">DocumentSource</span> <span class="variable">source</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DocumentSource</span>( document );</span><br><span class="line">        <span class="type">DocumentResult</span> <span class="variable">result</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DocumentResult</span>();</span><br><span class="line">        transformer.transform( source, result );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 返回转换后的document</span></span><br><span class="line">        <span class="type">Document</span> <span class="variable">transformedDoc</span> <span class="operator">=</span> result.getDocument();</span><br><span class="line">        <span class="keyword">return</span> transformedDoc;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.bytelife.net/articles/47460.html</id>
    <link href="https://www.bytelife.net/articles/47460.html"/>
    <published>2016-07-16T04:17:32.000Z</published>
    <summary>
      <![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139161438607.png"></p>
<blockquote>
<p>引言：XML（可扩展标记语言）在软件开发工程中取得了广泛的应用。在 Java 语言中操作 XML 有许多方法，最常用的方法就是使用 JDom、Dom4j 等第三方组件。本文将简单介绍使用 Dom4j 操作 XML 的基本方法。</p>
</blockquote>]]>
    </summary>
    <title>使用 Dom4j 操作 XML</title>
    <updated>2016-07-17T02:09:55.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="网络协议" scheme="https://www.bytelife.net/tags/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/"/>
    <content>
      <![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139158212312.png"></p><blockquote><p>引言：作为一名软件工程 Web 专业学生，对于 HTTP 的熟悉掌握是必不可少的，特此做记录，打造自己的 HTTP 栈。</p></blockquote><h2 id="URL与URI"><a href="#URL与URI" class="headerlink" title="URL与URI"></a>URL 与 URI</h2><p>我们经常接触到的就是 URL 了，它就是我们访问 web 的一个字符串地址，那么 URI 是什么呢？他们是什么关系呢？ URL：uniform resource location 统一资源定位符 URI：uniform resource identifier 统一资源标识符 这也就是说，URI 是一种资源的标识；而 URL 也是一种 URI，也是一种资源的标识，但它也指明了如何定位 Locate 到这个资源。 URI 是一种抽象的资源标识，<strong>既可以是绝对的，也可以是相对的</strong>。但是 URL 是一种 URI，它指明了定位的信息，必须是绝对的。</p><span id="more"></span><h2 id="报文-通信的桥梁"><a href="#报文-通信的桥梁" class="headerlink" title="报文-通信的桥梁"></a>报文 - 通信的桥梁</h2><p>客户端和服务器端通过相互发送<strong>报文</strong>进行通信，要深刻理解 HTTP 协议，就需要理解报文的格式和内容。</p><h3 id="报文的组成"><a href="#报文的组成" class="headerlink" title="报文的组成"></a>报文的组成</h3><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139158333934.png"></p><p>无论是请求报文还是响应报文都需要有报文首部，当然报文主体并不是必需的。 一般来说，请求报文的格式如下：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139158959712.png"></p><p>看一下百度网站的请求报文：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139159047548.png"></p><p>简单的报文形式：</p><pre><code>GET / HTTP/1.1    //请求行，包含用于请求的方法，请求的URI，HTTP版本//以下为各种首部字段Host: www.baidu.comConnection: keep-aliveUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0...Accept-Encoding: gzip, deflate, sdchAccept-Language: zh-CN,zh;q=0.8</code></pre><p>响应报文的格式如下：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139159187474.png"></p><p>看一下百度网站的响应报文：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139159283970.png"></p><pre><code>HTTP/1.1 200 OK   //状态行，包含表明响应结果的状态码，原因短语和HTTP版本//以下为各种首部字段Server: bfe/1.0.8.5Date: Tue, 06 Oct 2015 14:48:28 GMTContent-Type: text/html;charset=utf-8Transfer-Encoding: chunkedConnection: keep-aliveCache-Control: private</code></pre><h2 id="告知服务器意图的HTTP方法"><a href="#告知服务器意图的HTTP方法" class="headerlink" title="告知服务器意图的HTTP方法"></a>告知服务器意图的 HTTP 方法</h2><p>发送 HTTP 的方法有许多种，最常用的便是 GET 和 POST，下面就这两种进行详细地说明。</p><ol><li><strong>GET</strong> GET 方法用来请求访问 URI 所指定的资源，**（我想访问你的某个资源）** 并不对服务器上的内容产生任何作用结果；每次 GET 的内容都是相同的。GET 方式把请求所需要的参数放到<code>URL</code>中，直接就可以在 URL 中看见，有大小限制。</li><li><strong>POST</strong> POST 方法用来传输实体主体，目的并不是获取响应的主体内容，<strong>（我要把这条信息告诉你）</strong>，POST 方式则是把内容放在<code>报文内容</code>中，因此只要报文的内容没有限制，它的大小就没有限制。</li><li><strong>总结</strong> GET 用于获取某个内容，POST 用于提交某种数据请求。 按照使用场景来说，一般用户注册的内容属于私密的，这应该使用 POST 方式；而针对某一内容的查询，为了快速的响应，可以使用 GET 方式。</li></ol><h2 id="无状态协议与Cookie"><a href="#无状态协议与Cookie" class="headerlink" title="无状态协议与Cookie"></a>无状态协议与 Cookie</h2><p>HTTP 是一种无状态协议，也就是每一次发送都是一次新的开始，服务器并不知道也没有必要知道当前连接的客户端是否之前有过交集，那么当需要进行保存用户登录状态时，则出现了麻烦，这个时候使用 Cookie 来保存状态。 Cookie 会根据服务器端发送的响应报文内的一个叫做<strong> Set-Cookie</strong> 的首部字段，通知客户端保存 Cookie（保存在自己的电脑里），当下次客户端发送请求时，<strong>Cookie 值会被添加到请求报文中发送出去。</strong></p><h2 id="持久连接"><a href="#持久连接" class="headerlink" title="持久连接"></a>持久连接</h2><p>使用浏览器浏览一个包含多张图片的 HTML 页面时，浏览器会发起多次请求，如图所示：</p><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139159401214.png"></p><p>显而易见每次请求会造成<strong>无谓的 TCP 连接建立和断开，增加通信量的开销。</strong></p><h4 id="引入持久连接"><a href="#引入持久连接" class="headerlink" title="引入持久连接"></a>引入持久连接</h4><p>持久连接的特点是，只要任意一端没有明确提出断开连接，则保持 TCP 连接状态。目前 HTTP/1.1 中默认为持久连接。</p><pre><code>Connection:keep-alive</code></pre><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139159486033.png"></p><h2 id="管线化"><a href="#管线化" class="headerlink" title="管线化"></a>管线化</h2><p>管线化可以同时并行发送多个请求，不需要一个一个等待响应了。</p><h2 id="常见的状态码"><a href="#常见的状态码" class="headerlink" title="常见的状态码"></a>常见的状态码</h2><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139159684270.png"></p><h2 id="确保安全的HTTPS"><a href="#确保安全的HTTPS" class="headerlink" title="确保安全的HTTPS"></a>确保安全的 HTTPS</h2><p>HTTP+ 加密 + 认证 + 完整性保护 = HTTPS 一些登陆界面和购物结算界面使用 HTTPS 通信，也就是改用<code>https://</code>，HTTPS 说简单点就是它的通信接口部分被 SSL 和 TLS 协议代替而已。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139159772511.png"></p><h2 id="身份认证"><a href="#身份认证" class="headerlink" title="身份认证"></a>身份认证</h2><p>有一些网址或者服务需要用户的身份信息，因此需要随时知道这些消息，但是肯定不能每次都让用户输入用户密码，因此关于认证就有下面几种方式：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139159849465.png"></p><p>在这里主要说一下 FormBase 认证，也就是<strong>表单认证</strong>。</p><h4 id="使用Cookie来管理Session"><a href="#使用Cookie来管理Session" class="headerlink" title="使用Cookie来管理Session"></a>使用 Cookie 来管理 Session</h4><ol><li>客户端把用户 IE 和密码等登录信息放入报文的实体部分，以<strong> POST</strong> 方式发送给服务器。</li><li>服务器进行身份认证，产生 SessionID，加入到 Set-Cookie 内，返回给客户端。</li><li>客户端接收到 SessionID 后，将其加入 Cookie，下次请求时，浏览器会自动发送 Cookie。</li></ol><blockquote><p>在传输过程中，一种安全地保存密码方式是，先利用给密码加盐的方式增加额外信息，再使用散列 hash 函数计算出散列值后保存。</p></blockquote><p>书籍推荐：《图解 HTTP》，轻松理解更全面的 HTTP 知识。</p><blockquote><p>文／LuckyJing（简书作者） 原文链接：<span class="exturl" data-url="aHR0cDovL3d3dy5qaWFuc2h1LmNvbS9wLzgxNjMyZmVhMzI3Yw==">http://www.jianshu.com/p/81632fea327c<i class="fa fa-external-link-alt"></i></span></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/56430.html</id>
    <link href="https://www.bytelife.net/articles/56430.html"/>
    <published>2016-07-15T16:09:08.000Z</published>
    <summary>
      <![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139158212312.png"></p>
<blockquote>
<p>引言：作为一名软件工程 Web 专业学生，对于 HTTP 的熟悉掌握是必不可少的，特此做记录，打造自己的 HTTP 栈。</p>
</blockquote>
<h2 id="URL与URI"><a href="#URL与URI" class="headerlink" title="URL与URI"></a>URL 与 URI</h2><p>我们经常接触到的就是 URL 了，它就是我们访问 web 的一个字符串地址，那么 URI 是什么呢？他们是什么关系呢？ URL：uniform resource location 统一资源定位符 URI：uniform resource identifier 统一资源标识符 这也就是说，URI 是一种资源的标识；而 URL 也是一种 URI，也是一种资源的标识，但它也指明了如何定位 Locate 到这个资源。 URI 是一种抽象的资源标识，<strong>既可以是绝对的，也可以是相对的</strong>。但是 URL 是一种 URI，它指明了定位的信息，必须是绝对的。</p>]]>
    </summary>
    <title>全面理解 HTTP</title>
    <updated>2016-07-15T16:09:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="网络协议" scheme="https://www.bytelife.net/tags/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/"/>
    <content>
      <![CDATA[<p>状态码的职责是当客户端向服务器端发送请求时，描述返回请求结果。借助状态码，用户可以知道服务器端是正常处理了请求，还是出现了什么错误。 RFC2616 定义的状态码，由 3 位数字和原因短信组成。 数字中的第一位指定了响应类别，后两位无分类。响应类别有以下 5 种：</p><span id="more"></span><table><thead><tr><th>Type</th><th>Reason-phrase</th><th>Note</th></tr></thead><tbody><tr><td>1XX</td><td>Informational</td><td>信息性状态码，表示接受的请求正在处理</td></tr><tr><td>2XX</td><td>Success</td><td>成功状态码，表示请求正常处理完毕</td></tr><tr><td>3XX</td><td>Redirection</td><td>重定向状态码，表示需要客户端需要进行附加操作</td></tr><tr><td>4XX</td><td>Client Error</td><td>客户端错误状态码，表示服务器无法处理请求</td></tr><tr><td>5XX</td><td>Server Error</td><td>服务器错误状态码，表示服务器处理请求出错</td></tr></tbody></table><p>RFC2616 记录的 HTTP 状态码有 37 种，再加上「WebDAV」(RFC4918、5842) 和「Additional HTTP Status Codes」(RFC6585)，数量就达到 60 多种。 然并卵，这么多种 HTTP 状态码，其实常用的大概只有 14 种，本文就讲讲这 14 种状态码。</p><h2 id="2XX-Success"><a href="#2XX-Success" class="headerlink" title="2XX Success"></a>2XX Success</h2><blockquote><p>This class of status code indicates that the client’s request was successfully received, understood, and accepted.</p></blockquote><p>2xx 响应结果表示从客户端发来的请求在服务器端被正常处理了。</p><h3 id="200-OK"><a href="#200-OK" class="headerlink" title="200 OK"></a>200 OK</h3><p>请求被成功处理，服务器会根据不同的请求方法返回结果： <strong>GET</strong>：请求的对应资源会作为响应返回。 <strong>HEAD</strong>：请求的对应资源的响应头 (entity-header) 会作为响应返回, 不包括响应体 (message-body)。 <strong>POST</strong>：返回处理对应请求的结果。</p><h3 id="204-No-Content"><a href="#204-No-Content" class="headerlink" title="204 No Content"></a>204 No Content</h3><p>该状态码表示服务器接收到的请求已经处理完毕，但是服务器不需要返回响应体. 比如，客户端是浏览器的话，发出的请求返回 204 响应，那么浏览器显示的页面不会发生更新。</p><h3 id="206-Partial-Content"><a href="#206-Partial-Content" class="headerlink" title="206 Partial Content"></a>206 Partial Content</h3><p>该状态码表示客户端进行了范围请求，而服务器成功执行了这部分的 GET 请求。 客户端发起的请求，必须在请求头中包含<code>Range</code>字段。服务端响应报文中，必须包含由<code>Content-Range</code>指定范围的实体内容 (entity-bodies)</p><h2 id="3XX-Redirection"><a href="#3XX-Redirection" class="headerlink" title="3XX Redirection"></a>3XX Redirection</h2><blockquote><p>This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request.</p></blockquote><p>3XX 响应结果表明浏览器需要执行某些特殊的处理以完成请求。</p><h3 id="301-Moved-Permanently"><a href="#301-Moved-Permanently" class="headerlink" title="301 Moved Permanently"></a>301 Moved Permanently</h3><p>永久性重定向。该状态码表示请求的资源已经被分配了新的 URI，并且以后使用资源现在所指的 URI。并且根据请求的方法有不同的处理方式：<br><strong>HEAD</strong>：必须在响应头部<code>Location</code>字段中指明新的永久性的 URI。<br><strong>GET</strong>：除了有<code>Location</code>字段以外，还需要在响应体中附上永久性 URI 的超链接文本。<br><strong>POST</strong>：客户端在发送 POST 请求，受到 301 响应之后，不应该自动跳转 URI，应当让用户确认跳转。 比如，如果一个 URI 已经在浏览器中被收藏为书签，这时应该按照<code>Location</code>首部字段提示的 URI 重新保存。 </p><h3 id="302-Found"><a href="#302-Found" class="headerlink" title="302 Found"></a>302 Found</h3><p>临时性重定向。该状态码表示请求的资源已被分配了新的 URI，希望用户本次能使用新的 URI 访问。 和 301 Moved Permanently 状态码相似，但 302 状态码代表的资源不是被永久移动，只是临时性质的。 如果，用户把一个 URI 收藏为书签，302 响应是不会像 301 那样去更新书签。</p><h3 id="303-See-Other"><a href="#303-See-Other" class="headerlink" title="303 See Other"></a>303 See Other</h3><p>该状态码表示由于请求对应的资源存在另一个 URI，应使用 GET 方法定向获取请求的资源。303 与 302 不同之处在于，302 是不会改变请求的方法，如果请求方法是 POST 的话，重定向的请求也应该是 POST。而对于 303，使用 POST 请求的话，重定向的请求应该是 GET 请求。 但是有一点是需要注意的，许多 HTTP/1.1 版以前的浏览器不能正确理解 303 状态码，很多现存的浏览器讲 302 响应视为 303 响应，并且使用 GET 方式访问<code>Location</code>中规定的的 URI，而无视原先请求的方法。 在 RFC2616 中有相关的这样一段原文：</p><blockquote><p>Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.</p></blockquote><h3 id="304-Not-Modified"><a href="#304-Not-Modified" class="headerlink" title="304 Not Modified"></a>304 Not Modified</h3><p>该状态码表示客户端发送附带条件请求时，服务器端允许请求访问资源，但未满足条件的情况。304 状态码返回时，不包含任何响应的主题部分。附带条件的请求指的是采用 GET 方法的请求头中包含：<code>If-Match</code>、<code>If-Modified-Since</code>、<code>If-None-Match</code>、<code>If-Range</code>、<code>If-Unmodified-Since</code>中任一首部。</p><h3 id="307-Temporary-Redirect"><a href="#307-Temporary-Redirect" class="headerlink" title="307 Temporary Redirect"></a>307 Temporary Redirect</h3><p>临时重定向。该状态码与 302 和 303 的有着类似的含义，不同之处在于，307 状态码并不会指定客户端要用什么样的请求方法请求重定向地址。(302 指定使用原有请求方法，303 指定使用 GET 方法)</p><h2 id="4XX-Client-Error"><a href="#4XX-Client-Error" class="headerlink" title="4XX Client Error"></a>4XX Client Error</h2><blockquote><p>The 4xx class of status code is intended for cases in which the client seems to have erred.</p></blockquote><p>4XX 的响应结果表明客户端是发生错误的原因所在</p><h3 id="400-Bad-Request"><a href="#400-Bad-Request" class="headerlink" title="400 Bad Request"></a>400 Bad Request</h3><p>表示该请求报文中存在语法错误，导致服务器无法理解该请求。客户端需要修改请求的内容后再次发送请求。</p><h3 id="401-Unauthorized"><a href="#401-Unauthorized" class="headerlink" title="401 Unauthorized"></a>401 Unauthorized</h3><p>该状态码表示发送的请求需要有通过 HTTP 认证 (Basic 认证，Digest 认证) 的认证信息。返回含有 401 的响应，必须在头部包含<code>WWW-Authenticate</code>以指明服务器需要哪种方式的认证。 当客户端再次请求该资源的时候，需要在请求头中的<code>Authorization</code>包含认证信息。 更多关于认证授权的信息关注 RFC2617</p><h3 id="403-Forbidden"><a href="#403-Forbidden" class="headerlink" title="403 Forbidden"></a>403 Forbidden</h3><p>该状态码表明对请求资源的访问被服务器拒绝了。服务器没有必要给出拒绝的详细理由，但如果想做说明的话，可以在实体的主体部分原因进行描述，这样就能让用户看到了。 未获得文件系统的访问权限，访问权限出现某些问题，从未授权的发送源 IP 地址试图访问等情况都可能发生 403 响应。</p><h3 id="404-Not-Found"><a href="#404-Not-Found" class="headerlink" title="404 Not Found"></a>404 Not Found</h3><p>该状态码表明服务器上无法找到指定的资源。通常被用于服务器不想透露拒绝请求的原因，或者没有其他的响应可提供。</p><h2 id="5XX-Server-Error"><a href="#5XX-Server-Error" class="headerlink" title="5XX Server Error"></a>5XX Server Error</h2><blockquote><p>Response status codes beginning with the digit “5” indicate cases in which the server is aware that it has erred or is incapable of performing the request.</p></blockquote><p>5XX 的响应结果表明服务器本身发生错误，或者没有足够的能力来处理请求。</p><h3 id="500-Internal-Server-Error"><a href="#500-Internal-Server-Error" class="headerlink" title="500 Internal Server Error"></a>500 Internal Server Error</h3><p>该状态码表明服务器端在执行请求时发生了错误。也有可能是 Web 应用存在的 BUG 或某些临时的故障。</p><h3 id="503-Service-Unavailable"><a href="#503-Service-Unavailable" class="headerlink" title="503 Service Unavailable"></a>503 Service Unavailable</h3><p>该状态码表明服务器暂时处于超负载或正在进行停机维护，现在无法处理请求。如果事先得知解除以上需要的时间，最好写入<code>Retry-After</code>首部字段再返回给客户端。</p><blockquote><p>转载自：<span class="exturl" data-url="aHR0cDovL2h1YW5nLWplcnJ5Yy5jb20vMjAxNi8wNS8yMi8lRTMlODAlOEMlRTclOTAlODYlRTglQTclQTNIVFRQJUUzJTgwJThEJUU0JUI5JThCJUU1JUI4JUI4JUU4JUE3JTgxJUU3JTlBJTg0JUU3JThBJUI2JUU2JTgwJTgxJUU3JUEwJTgxLw==">点击进入<i class="fa fa-external-link-alt"></i></span></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/54533.html</id>
    <link href="https://www.bytelife.net/articles/54533.html"/>
    <published>2016-07-14T09:49:34.000Z</published>
    <summary>
      <![CDATA[<p>状态码的职责是当客户端向服务器端发送请求时，描述返回请求结果。借助状态码，用户可以知道服务器端是正常处理了请求，还是出现了什么错误。 RFC2616 定义的状态码，由 3 位数字和原因短信组成。 数字中的第一位指定了响应类别，后两位无分类。响应类别有以下 5 种：</p>]]>
    </summary>
    <title>HTTP 常见状态码解释</title>
    <updated>2016-07-14T09:49:34.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <category term="并发" scheme="https://www.bytelife.net/tags/%E5%B9%B6%E5%8F%91/"/>
    <content>
      <![CDATA[<h2 id="并发问题的症状"><a href="#并发问题的症状" class="headerlink" title="并发问题的症状"></a>并发问题的症状</h2><h3 id="多线程put后可能导致get死循环"><a href="#多线程put后可能导致get死循环" class="headerlink" title="多线程put后可能导致get死循环"></a>多线程 put 后可能导致 get 死循环</h3><p>从前我们的 Java 代码因为一些原因使用了 HashMap 这个东西，但是当时的程序是单线程的，一切都没有问题。后来，我们的程序性能有问题，所以需要变成多线程的，于是，变成多线程后到了线上，发现程序经常占了 100% 的 CPU，查看堆栈，你会发现程序都 Hang 在了 HashMap.get() 这个方法上了，重启程序后问题消失。但是过段时间又会来。而且，这个问题在测试环境里可能很难重现。 </p><span id="more"></span><p>我们简单的看一下我们自己的代码，我们就知道 HashMap 被多个线程操作。而 Java 的文档说 HashMap 是非线程安全的，应该用 ConcurrentHashMap。但是在这里我们可以来研究一下原因。简单代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.king.hashmap;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestLock</span> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">HashMap</span> <span class="variable">map</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>();</span><br><span class="line"></span><br><span class="line">    <span class="type">public</span> <span class="title function_">TestLock</span><span class="params">()</span> {</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.put(<span class="keyword">new</span> <span class="title class_">Integer</span>(i), i);</span><br><span class="line">                }</span><br><span class="line">                System.out.println(<span class="string">"t1 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.put(<span class="keyword">new</span> <span class="title class_">Integer</span>(i), i);</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                System.out.println(<span class="string">"t2 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.put(<span class="keyword">new</span> <span class="title class_">Integer</span>(i), i);</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                System.out.println(<span class="string">"t3 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t4</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.put(<span class="keyword">new</span> <span class="title class_">Integer</span>(i), i);</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                System.out.println(<span class="string">"t4 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t5</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.put(<span class="keyword">new</span> <span class="title class_">Integer</span>(i), i);</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                System.out.println(<span class="string">"t5 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t6</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.get(<span class="keyword">new</span> <span class="title class_">Integer</span>(i));</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                System.out.println(<span class="string">"t6 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t7</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.get(<span class="keyword">new</span> <span class="title class_">Integer</span>(i));</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                System.out.println(<span class="string">"t7 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t8</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.get(<span class="keyword">new</span> <span class="title class_">Integer</span>(i));</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                System.out.println(<span class="string">"t8 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t9</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.get(<span class="keyword">new</span> <span class="title class_">Integer</span>(i));</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                System.out.println(<span class="string">"t9 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t10</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>() {</span><br><span class="line">            <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">50000</span>; i++) {</span><br><span class="line">                    map.get(<span class="keyword">new</span> <span class="title class_">Integer</span>(i));</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                System.out.println(<span class="string">"t10 over"</span>);</span><br><span class="line">            }</span><br><span class="line">        };</span><br><span class="line"></span><br><span class="line">        t1.start();</span><br><span class="line">        t2.start();</span><br><span class="line">        t3.start();</span><br><span class="line">        t4.start();</span><br><span class="line">        t5.start();</span><br><span class="line"></span><br><span class="line">        t6.start();</span><br><span class="line">        t7.start();</span><br><span class="line">        t8.start();</span><br><span class="line">        t9.start();</span><br><span class="line">        t10.start();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">TestLock</span>();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>就是启了 10 个线程，不断的往一个非线程安全的 HashMap 中 put 内容 / get 内容，put 的内容很简单，key 和 value 都是从 0 自增的整数（这个 put 的内容做的并不好，以致于后来干扰了我分析问题的思路）。对 HashMap 做并发写操作，我原以为只不过会产生脏数据的情况，但反复运行这个程序，会出现线程 t1、t2 被 hang 住的情况，多数情况下是一个线程被 hang 住另一个成功结束，偶尔会 10 个线程都被 hang 住。 产生这个死循环的根源在于对一个未保护的共享变量 — 一个 ”HashMap” 数据结构的操作。当在所有操作的方法上加了 ”synchronized” 后，一切恢复了正常。这算 jvm 的 bug 吗？应该说不是的，这个现象很早以前就报告出来了。Sun 的工程师并不认为这是 bug，而是建议在这样的场景下应采用 ”ConcurrentHashMap”， CPU 利用率过高一般是因为出现了出现了死循环，导致部分线程一直运行，占用 cpu 时间。问题原因就是 HashMap 是非线程安全的，多个线程 put 的时候造成了某个 key 值 Entry key List 的死循环，问题就这么产生了。 当另外一个线程 get 这个 Entry List 死循环的 key 的时候，这个 get 也会一直执行。最后结果是越来越多的线程死循环，最后导致服务器 dang 掉。我们一般认为 HashMap 重复插入某个值的时候，会覆盖之前的值，这个没错。但是对于多线程访问的时候，由于其内部实现机制 (在多线程环境且未作同步的情况下，对同一个 HashMap 做 put 操作可能导致两个或以上线程同时做 rehash 动作，就可能导致循环键表出现，一旦出现线程将无法终止，持续占用 CPU，导致 CPU 使用率居高不下)，就可能出现安全问题了。 使用 jstack 工具 dump 出问题的那台服务器的栈信息。死循环的话，首先查找 RUNNABLE 的线程，找到问题代码如下：</p><blockquote><p>java.lang.Thread.State:RUNNABLE at java.util.HashMap.get(HashMap.java:303) at com.sohu.twap.service.logic.TransformTweeter.doTransformTweetT5(TransformTweeter.java:183) 共出现了 23 次。 java.lang.Thread.State:RUNNABLE at java.util.HashMap.put(HashMap.java:374) at com.sohu.twap.service.logic.TransformTweeter.transformT5(TransformTweeter.java:816) 共出现了 3 次。</p></blockquote><p><strong>注意</strong>：不合理使用 HashMap 导致出现的是死循环而不是死锁。</p><h3 id="多线程put的时候可能导致元素丢失"><a href="#多线程put的时候可能导致元素丢失" class="headerlink" title="多线程put的时候可能导致元素丢失"></a>多线程 put 的时候可能导致元素丢失</h3><p>主要问题出在 addEntry 方法的 new Entry&lt;K,V&gt;(hash, key, value, e)，如果两个线程都同时取得了 e, 则他们下一个元素都是 e，然后赋值给 table 元素的时候有一个成功有一个丢失。</p><h3 id="put非null元素后get出来的却是null"><a href="#put非null元素后get出来的却是null" class="headerlink" title="put非null元素后get出来的却是null"></a>put 非 null 元素后 get 出来的却是 null</h3><p>在 transfer 方法中代码如下：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">transfer</span><span class="params">(Entry[] newTable)</span> {</span><br><span class="line">    <span class="type">Entry</span>[] <span class="variable">src</span> <span class="operator">=</span> table;</span><br><span class="line">    <span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> newTable.length;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; src.length; j++) {</span><br><span class="line">        <span class="type">Entry</span> <span class="variable">e</span> <span class="operator">=</span> src[j];</span><br><span class="line">        <span class="keyword">if</span> (e != <span class="literal">null</span>) {</span><br><span class="line">            src[j] = <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">do</span> {</span><br><span class="line">                <span class="type">Entry</span> <span class="variable">next</span> <span class="operator">=</span> e.next;</span><br><span class="line">                <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> indexFor(e.hash, newCapacity);</span><br><span class="line">                e.next = newTable[i];</span><br><span class="line">                newTable[i] = e;</span><br><span class="line">                e = next;</span><br><span class="line">            } <span class="keyword">while</span> (e != <span class="literal">null</span>);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在这个方法里，将旧数组赋值给 src，遍历 src，当 src 的元素非 null 时，就将 src 中的该元素置 null，即将旧数组中的元素置 null 了，也就是这一句：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (e != <span class="literal">null</span>) {</span><br><span class="line">        src[j] = <span class="literal">null</span>;</span><br></pre></td></tr></tbody></table></figure><p>此时若有 get 方法访问这个 key，它取得的还是旧数组，当然就取不到其对应的 value 了。 <strong>总结：HashMap 未同步时在并发程序中会产生许多微妙的问题，难以从表层找到原因。所以使用 HashMap 出现了违反直觉的现象，那么可能就是并发导致的了。</strong></p><h2 id="HashMap数据结构"><a href="#HashMap数据结构" class="headerlink" title="HashMap数据结构"></a>HashMap 数据结构</h2><p>我需要简单地说一下 HashMap 这个经典的数据结构。 HashMap 通常会用一个指针数组（假设为 table []）来做分散所有的 key，当一个 key 被加入时，会通过 Hash 算法通过 key 算出这个数组的下标 i，然后就把这个 &lt;key, value&gt; 插到 table [i] 中，如果有两个不同的 key 被算在了同一个 i，那么就叫冲突，又叫碰撞，这样会在 table [i] 上形成一个链表。 我们知道，如果 table [] 的尺寸很小，比如只有 2 个，如果要放进 10 个 keys 的话，那么碰撞非常频繁，于是一个 O (1) 的查找算法，就变成了链表遍历，性能变成了 O (n)，这是 Hash 表的缺陷。 所以，Hash 表的尺寸和容量非常的重要。一般来说，Hash 表这个容器当有数据要插入时，都会检查容量有没有超过设定的 thredhold，如果超过，需要增大 Hash 表的尺寸，但是这样一来，整个 Hash 表里的元素都需要被重算一遍。这叫 rehash，这个成本相当的大。</p><h2 id="HashMap的rehash源代码"><a href="#HashMap的rehash源代码" class="headerlink" title="HashMap的rehash源代码"></a>HashMap 的 rehash 源代码</h2><p>下面，我们来看一下 Java 的 HashMap 的源代码。Put 一个 Key,Value 对到 Hash 表中：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">V</span> <span class="title function_">put</span><span class="params">(K key, V value)</span></span><br><span class="line">{</span><br><span class="line">    ......</span><br><span class="line">    <span class="comment">//算Hash值</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> hash(key.hashCode());</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> indexFor(hash, table.length);</span><br><span class="line">    <span class="comment">//如果该key已被插入，则替换掉旧的value （链接操作）</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">Entry</span>&lt;K,V&gt; <span class="variable">e</span> <span class="operator">=</span> table[i]; e != <span class="literal">null</span>; e = e.next) {</span><br><span class="line">        Object k;</span><br><span class="line">        <span class="keyword">if</span> (e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k))) {</span><br><span class="line">            <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e.value;</span><br><span class="line">            e.value = value;</span><br><span class="line">            e.recordAccess(<span class="built_in">this</span>);</span><br><span class="line">            <span class="keyword">return</span> oldValue;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">    modCount++;</span><br><span class="line">    <span class="comment">//该key不存在，需要增加一个结点</span></span><br><span class="line">    addEntry(hash, key, value, i);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>检查容量是否超标：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">addEntry</span><span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">int</span> bucketIndex)</span></span><br><span class="line">{</span><br><span class="line">    <span class="type">Entry</span>&lt;K,V&gt; <span class="variable">e</span> <span class="operator">=</span> table[bucketIndex];</span><br><span class="line">    table[bucketIndex] = <span class="keyword">new</span> <span class="title class_">Entry</span>&lt;K,V&gt;(hash, key, value, e);</span><br><span class="line">    <span class="comment">//查看当前的size是否超过了我们设定的阈值threshold，如果超过，需要resize</span></span><br><span class="line">    <span class="keyword">if</span> (size++ &gt;= threshold)</span><br><span class="line">        resize(<span class="number">2</span> * table.length);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>新建一个更大尺寸的 hash 表，然后把数据从老的 Hash 表中迁移到新的 Hash 表中。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">resize</span><span class="params">(<span class="type">int</span> newCapacity)</span></span><br><span class="line">{</span><br><span class="line">    <span class="type">Entry</span>[] <span class="variable">oldTable</span> <span class="operator">=</span> table;</span><br><span class="line">    <span class="type">int</span> <span class="variable">oldCapacity</span> <span class="operator">=</span> oldTable.length;</span><br><span class="line">    ......</span><br><span class="line">    <span class="comment">//创建一个新的Hash Table</span></span><br><span class="line">    <span class="type">Entry</span>[] <span class="variable">newTable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Entry</span>[newCapacity];</span><br><span class="line">    <span class="comment">//将Old Hash Table上的数据迁移到New Hash Table上</span></span><br><span class="line">    transfer(newTable);</span><br><span class="line">    table = newTable;</span><br><span class="line">    threshold = (<span class="type">int</span>)(newCapacity * loadFactor);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>迁移的源代码，注意高亮处：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">transfer</span><span class="params">(Entry[] newTable)</span></span><br><span class="line">{</span><br><span class="line">    <span class="type">Entry</span>[] <span class="variable">src</span> <span class="operator">=</span> table;</span><br><span class="line">    <span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> newTable.length;</span><br><span class="line">    <span class="comment">//下面这段代码的意思是：</span></span><br><span class="line">    <span class="comment">//  从OldTable里摘一个元素出来，然后放到NewTable中</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; src.length; j++) {</span><br><span class="line">        <span class="type">Entry</span>&lt;K,V&gt; <span class="variable">e</span> <span class="operator">=</span> src[j];</span><br><span class="line">        <span class="keyword">if</span> (e != <span class="literal">null</span>) {</span><br><span class="line">            src[j] = <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">do</span> {</span><br><span class="line">                <span class="type">Entry</span>&lt;K,V&gt; <span class="variable">next</span> <span class="operator">=</span> e.next;</span><br><span class="line">                <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> indexFor(e.hash, newCapacity);</span><br><span class="line">                e.next = newTable[i];</span><br><span class="line">                newTable[i] = e;</span><br><span class="line">                e = next;</span><br><span class="line">            } <span class="keyword">while</span> (e != <span class="literal">null</span>);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>好了，这个代码算是比较正常的。而且没有什么问题。</p><h2 id="正常的ReHash过程"><a href="#正常的ReHash过程" class="headerlink" title="正常的ReHash过程"></a>正常的 ReHash 过程</h2><p>画了个图做了个演示。</p><ol><li>我假设了我们的 hash 算法就是简单的用 key mod 一下表的大小（也就是数组的长度）。</li><li>最上面的是 old hash 表，其中的 Hash 表的 size=2, 所以 key = 3, 7, 5，在 mod 2 以后都冲突在 table<span class="exturl" data-url="aHR0cDovL3N0YXRpYy5vc2NoaW5hLm5ldC91cGxvYWRzL3NwYWNlLzIwMTUvMDMzMS8xNDI5MjlfVmF3cl8xMjAxNjYuanBn">1<i class="fa fa-external-link-alt"></i></span> 这里了。</li><li>接下来的三个步骤是 Hash 表 resize 成 4，然后所有的 &lt;key,value&gt; 重新 rehash 的过程。</li></ol><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139208354778.jpg"></p><h2 id="并发的Rehash过程"><a href="#并发的Rehash过程" class="headerlink" title="并发的Rehash过程"></a>并发的 Rehash 过程</h2><p>**（1）假设我们有两个线程。** 我用红色和浅蓝色标注了一下。我们再回头看一下我们的 transfer 代码中的这个细节：</p><p>do {<br>    Entry&lt;K,V&gt; next = e.next; // &lt;–假设线程一执行到这里就被调度挂起了<br>    int i = indexFor(e.hash, newCapacity);<br>    e.next = newTable[i];<br>    newTable[i] = e;<br>    e = next;<br>} while (e != null);</p><p>而我们的线程二执行完成了。于是我们有下面的这个样子。<img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139208447113.jpg"></p><p> 注意：因为 Thread1 的 e 指向了 key (3)，而 next 指向了 key (7)，其在线程二 rehash 后，指向了线程二重组后的链表。我们可以看到链表的顺序被反转后。 </p><p> <strong>（2）线程一被调度回来执行。</strong></p><ol><li>先是执行 newTalbe [i] = e。</li><li>然后是 e = next，导致了 e 指向了 key (7)。</li><li>而下一次循环的 next = e.next 导致了 next 指向了 key (3)。</li></ol><p><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139208582710.jpg"></p><p> <strong>（3）一切安好。</strong> 线程一接着工作。把 key (7) 摘下来，放到 newTable [i] 的第一个，然后把 e 和 next 往下移。<br> <img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139208661671.jpg"><br> <strong>（4）环形链接出现。</strong> e.next = newTable [i] 导致 key (3).next 指向了 key (7)。注意：此时的 key (7).next 已经指向了 key (3)， 环形链表就这样出现了。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139208765666.jpg"><br> 于是，当我们的线程一调用到，HashTable.get(11) 时，悲剧就出现了——Infinite Loop。</p><h2 id="三种解决方案"><a href="#三种解决方案" class="headerlink" title="三种解决方案"></a>三种解决方案</h2><h3 id="Hashtable替换HashMap"><a href="#Hashtable替换HashMap" class="headerlink" title="Hashtable替换HashMap"></a>Hashtable 替换 HashMap</h3><p>Hashtable 是同步的，但由迭代器返回的 Iterator 和由所有 Hashtable 的 “collection 视图方法” 返回的 Collection 的 listIterator 方法都是快速失败的：在创建 Iterator 之后，如果从结构上对 Hashtable 进行修改，除非通过 Iterator 自身的移除或添加方法，否则在任何时间以任何方式对其进行修改，Iterator 都将抛出 ConcurrentModificationException。因此，面对并发的修改，Iterator 很快就会完全失败，而不冒在将来某个不确定的时间发生任意不确定行为的风险。由 Hashtable 的键和值方法返回的 Enumeration 不是快速失败的。 注意，迭代器的快速失败行为无法得到保证，因为一般来说，不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此，为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误做法：迭代器的快速失败行为应该仅用于检测程序错误。</p><h3 id="Collections-synchronizedMap将HashMap包装起来"><a href="#Collections-synchronizedMap将HashMap包装起来" class="headerlink" title="Collections.synchronizedMap将HashMap包装起来"></a>Collections.synchronizedMap 将 HashMap 包装起来</h3><p>返回由指定映射支持的同步（线程安全的）映射。为了保证按顺序访问，必须通过返回的映射完成对底层映射的所有访问。在返回的映射或其任意 collection 视图上进行迭代时，强制用户手工在返回的映射上进行同步：</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Map</span> <span class="variable">m</span> <span class="operator">=</span> Collections.synchronizedMap(<span class="keyword">new</span> <span class="title class_">HashMap</span>());</span><br><span class="line">...</span><br><span class="line"><span class="type">Set</span> <span class="variable">s</span> <span class="operator">=</span> m.keySet();  <span class="comment">// Needn't be in synchronized block</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">synchronized</span>(m) {  <span class="comment">// Synchronizing on m, not s!</span></span><br><span class="line"><span class="type">Iterator</span> <span class="variable">i</span> <span class="operator">=</span> s.iterator(); <span class="comment">// Must be in synchronized block</span></span><br><span class="line">    <span class="keyword">while</span> (i.hasNext())</span><br><span class="line">        foo(i.next());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>不遵从此建议将导致无法确定的行为。如果指定映射是可序列化的，则返回的映射也将是可序列化的。</p><h3 id="ConcurrentHashMap替换HashMap"><a href="#ConcurrentHashMap替换HashMap" class="headerlink" title="ConcurrentHashMap替换HashMap"></a>ConcurrentHashMap 替换 HashMap</h3><p>支持检索的完全并发和更新的所期望可调整并发的哈希表。此类遵守与 Hashtable 相同的功能规范，并且包括对应于 Hashtable 的每个方法的方法版本。不过，尽管所有操作都是线程安全的，但检索操作不必锁定，并且不支持以某种防止所有访问的方式锁定整个表。此类可以通过程序完全与 Hashtable 进行互操作，这取决于其线程安全，而与其同步细节无关。 检索操作（包括 get）通常不会受阻塞，因此，可能与更新操作交迭（包括 put 和 remove）。检索会影响最近完成的更新操作的结果。对于一些聚合操作，比如 putAll 和 clear，并发检索可能只影响某些条目的插入和移除。类似地，在创建迭代器 / 枚举时或自此之后，Iterators 和 Enumerations 返回在某一时间点上影响哈希表状态的元素。它们不会抛出 ConcurrentModificationException。不过，迭代器被设计成每次仅由一个线程使用。</p><blockquote><p>来自：陶邦仁的个人空间 - 开源中国社区<br>作者：陶邦仁<br>链接：<span class="exturl" data-url="aHR0cDovL215Lm9zY2hpbmEubmV0L3hpYW5nZ2FvL2Jsb2cvMzkzOTkw">http://my.oschina.net/xianggao/blog/393990<i class="fa fa-external-link-alt"></i></span></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/65492.html</id>
    <link href="https://www.bytelife.net/articles/65492.html"/>
    <published>2016-07-05T04:45:25.000Z</published>
    <summary>
      <![CDATA[<h2 id="并发问题的症状"><a href="#并发问题的症状" class="headerlink" title="并发问题的症状"></a>并发问题的症状</h2><h3 id="多线程put后可能导致get死循环"><a href="#多线程put后可能导致get死循环" class="headerlink" title="多线程put后可能导致get死循环"></a>多线程 put 后可能导致 get 死循环</h3><p>从前我们的 Java 代码因为一些原因使用了 HashMap 这个东西，但是当时的程序是单线程的，一切都没有问题。后来，我们的程序性能有问题，所以需要变成多线程的，于是，变成多线程后到了线上，发现程序经常占了 100% 的 CPU，查看堆栈，你会发现程序都 Hang 在了 HashMap.get() 这个方法上了，重启程序后问题消失。但是过段时间又会来。而且，这个问题在测试环境里可能很难重现。 </p>]]>
    </summary>
    <title>HashMap 多线程并发问题分析</title>
    <updated>2016-07-05T04:45:25.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="Java" scheme="https://www.bytelife.net/tags/Java/"/>
    <content>
      <![CDATA[<p>这篇文章主要是 java8 中新的 Date 和 Time API 的实战。新的 Date 和 Time 类是 java 开发者社区千呼万唤始出来的。Java8 之前存在的 Date 类一直都受人诟病，很多人都会选择使用第三方的 date 库 <span class="exturl" data-url="aHR0cDovL3d3dy5qb2RhLm9yZy9qb2RhLXRpbWUv">joda-time<i class="fa fa-external-link-alt"></i></span>。Java8 中的 date 和 time api 是 jodatime 的作者参与开发的，实现了 JSR310 的全部内容。这些新的 api 都在包 java.time 下。 既然第三方的 joda-time,date4j 都已经足够强大了，为什么 java8 还要重新实现他呢，一部分的原因是这些第三方的库是存在兼容问题的，比如标准的 JSF 日期转化器与 joda-time api，就不兼容，每次使用都需要编写自己的转换器，所以标准化 api 是必须的，就有了 JSR310，java8 中就实现了他全部的规定内容。</p><span id="more"></span><h2 id="新Date类和Time类背后的设计原则："><a href="#新Date类和Time类背后的设计原则：" class="headerlink" title="新Date类和Time类背后的设计原则："></a>新 Date 类和 Time 类背后的设计原则：</h2><p><strong>不可变类</strong><br>java8 之前，Date 类都是可变类。当我们在多线程环境下使用它，编程人员应该确认 Date 对象的线程安全。Java8 的 Date 和 Time API 提供了线程安全的不可变类。编程人员不用考虑并发的问题。<br><strong>领域模型驱动设计方法</strong><br>新的日期和时间的类别遵循 “域驱动设计”。对于开发者来说，理解方法和类的功能是很容易的。</p><h2 id="接下来让我们来看看新Date和Time-API"><a href="#接下来让我们来看看新Date和Time-API" class="headerlink" title="接下来让我们来看看新Date和Time API:"></a>接下来让我们来看看新 Date 和 Time API:</h2><p><strong>java.time.LocalDate:</strong><br>LocalDate 只提供日期不提供时间信息。它是不可变类且线程安全的。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.smarttechie;</span><br><span class="line"><span class="keyword">import</span> java.time.LocalDate;</span><br><span class="line"><span class="keyword">import</span> java.time.temporal.ChronoUnit;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* This class demonstrates JAVA 8 data and time API</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> Siva Prasad Rao Janapati</span></span><br><span class="line"><span class="comment">* */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DateTimeDemonstration</span> {</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* <span class="doctag">@param</span> args</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line">   <span class="comment">//Create date LocalDate localDate = LocalDate.now();</span></span><br><span class="line">    System.out.println(<span class="string">"The local date is :: "</span> + localDate); </span><br><span class="line">   <span class="comment">//Find the length of the month. That is, how many days are there for this month.</span></span><br><span class="line">   System.out.println(<span class="string">"The number of days available for this month:: "</span> + localDate.lengthOfMonth()); </span><br><span class="line">   <span class="comment">//Know the month name</span></span><br><span class="line">   System.out.println(<span class="string">"What is the month name? :: "</span> + localDate.getMonth().name()); </span><br><span class="line">   <span class="comment">//add 2 days to the today's date.</span></span><br><span class="line">   System.out.println(localDate.plus(<span class="number">2</span>, ChronoUnit.DAYS)); </span><br><span class="line">   <span class="comment">//substract 2 days from today</span></span><br><span class="line">   System.out.println(localDate.minus(<span class="number">2</span>, ChronoUnit.DAYS)); </span><br><span class="line">   <span class="comment">//Convert the string to date</span></span><br><span class="line">   System.out.println(localDate.parse(<span class="string">"2017-04-07"</span>));</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><strong>java.time.LocalTime:</strong><br>LocalTime 只提供时间而不提供日期信息，它是不可变类且线程安全的。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.smarttechie;</span><br><span class="line"><span class="keyword">import</span> java.time.LocalTime;</span><br><span class="line"><span class="keyword">import</span> java.time.temporal.ChronoUnit;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* This class demonstrates JAVA 8 data and time API</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> Siva Prasad Rao Janapati</span></span><br><span class="line"><span class="comment">* */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DateTimeDemonstration</span> {</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* <span class="doctag">@param</span> args</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line">   <span class="comment">//Get local time</span></span><br><span class="line">   <span class="type">LocalTime</span> <span class="variable">localTime</span> <span class="operator">=</span> LocalTime.now();</span><br><span class="line">   System.out.println(localTime);</span><br><span class="line">  <span class="comment">//Get the hour of the day</span></span><br><span class="line">  System.out.println(<span class="string">"The hour of the day:: "</span> + localTime.getHour());</span><br><span class="line">  <span class="comment">//add 2 hours to the time.</span></span><br><span class="line">  System.out.println(localTime.plus(<span class="number">2</span>, ChronoUnit.HOURS));</span><br><span class="line">  <span class="comment">//add 6 minutes to the time.</span></span><br><span class="line">  System.out.println(localTime.plusMinutes(<span class="number">6</span>));</span><br><span class="line">  <span class="comment">//substract 2 hours from current time</span></span><br><span class="line">  System.out.println(localTime.minus(<span class="number">2</span>, ChronoUnit.HOURS));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><strong>java.time.LocalDateTime:</strong><br>LocalDateTime 提供时间和日期的信息，它是不可变类且线程安全的</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> orr.smarttechie;</span><br><span class="line"><span class="keyword">import</span> java.time.LocalDateTime;</span><br><span class="line"><span class="keyword">import</span> java.time.temporal.ChronoUnit;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* This class demonstrates JAVA 8 data and time API</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> Siva Prasad Rao Janapati</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DateTimeDemonstration</span> {</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* <span class="doctag">@param</span> args</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line">    <span class="comment">//Get LocalDateTime object</span></span><br><span class="line">    <span class="type">LocalDateTime</span> <span class="variable">localDateTime</span> <span class="operator">=</span> LocalDateTime.now();</span><br><span class="line">    System.out.println(localDateTime);</span><br><span class="line">    <span class="comment">//Find the length of month. That is, how many days are there for this month.</span></span><br><span class="line">    System.out.println(<span class="string">"The number of days available for this month:: "</span> + localDateTime.getMonth().length(<span class="literal">true</span>));</span><br><span class="line">    <span class="comment">//Know the month name</span></span><br><span class="line">    System.out.println(<span class="string">"What is the month name? :: "</span> + localDateTime.getMonth().name());</span><br><span class="line">    <span class="comment">//add 2 days to today's date.</span></span><br><span class="line">    System.out.println(localDateTime.plus(<span class="number">2</span>, ChronoUnit.DAYS));</span><br><span class="line">    <span class="comment">//substract 2 days from today</span></span><br><span class="line">    System.out.println(localDateTime.minus(<span class="number">2</span>, ChronoUnit.DAYS));</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><strong>java.time.Year:</strong><br>Year 提供年的信息，它是不可变类且线程安全的。</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> orr.smarttechie;</span><br><span class="line"><span class="keyword">import</span> java.time.Year;</span><br><span class="line"><span class="keyword">import</span> java.time.temporal.ChronoUnit;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* This class demonstrates JAVA 8 data and time API</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> Siva Prasad Rao Janapati</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DateTimeDemonstration</span> {</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* <span class="doctag">@param</span> args</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line">   <span class="comment">//Get year</span></span><br><span class="line">   <span class="type">Year</span> <span class="variable">year</span> <span class="operator">=</span> Year.now();</span><br><span class="line">   System.out.println(<span class="string">"Year ::"</span> + year);</span><br><span class="line">   <span class="comment">//know the year is leap year or not</span></span><br><span class="line">   System.out.println(<span class="string">"Is year["</span> +year+<span class="string">"] leap year?"</span>+ year.isLeap());</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><strong>java.time.Duration:</strong><br>Duration 是用来计算两个给定的日期之间包含多少秒，多少毫秒，它是不可变类且线程安全的 </p><p><strong>java.time.Period:</strong> Period 是用来计算两个给定的日期之间包含多少天，多少月或者多少年，它是不可变类且线程安全的</p><figure class="highlight java"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> orr.smarttechie;</span><br><span class="line"><span class="keyword">import</span> java.time.LocalDate;</span><br><span class="line"><span class="keyword">import</span> java.time.Period;</span><br><span class="line"><span class="keyword">import</span> java.time.temporal.ChronoUnit;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* This class demonstrates JAVA 8 data and time API</span></span><br><span class="line"><span class="comment">* <span class="doctag">@author</span> Siva Prasad Rao Janapati</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DateTimeDemonstration</span> {</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* <span class="doctag">@param</span> args</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line">   <span class="type">LocalDate</span> <span class="variable">localDate</span> <span class="operator">=</span> LocalDate.now();</span><br><span class="line">   <span class="type">Period</span> <span class="variable">period</span> <span class="operator">=</span> Period.between(localDate, localDate.plus(<span class="number">2</span>, ChronoUnit.DAYS));</span><br><span class="line">   System.out.println(period.getDays());</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><blockquote><p>来自：码农网<br>译文链接：<span class="exturl" data-url="aHR0cDovL3d3dy5jb2RlY2VvLmNvbS9hcnRpY2xlL2phdmEtOC1kYXRlLXRpbWUtZ3VpZGUuaHRtbA==">http://www.codeceo.com/article/java-8-date-time-guide.html<i class="fa fa-external-link-alt"></i></span><br>英文原文：<span class="exturl" data-url="aHR0cHM6Ly9kem9uZS5jb20vYXJ0aWNsZXMvamF2YS04LW5ldy1kYXRhLWFuZC10aW1lLW92ZXJ2aWV3">https://dzone.com/articles/java-8-new-data-and-time-overview<i class="fa fa-external-link-alt"></i></span><br>翻译作者：码农网 – 栗子蜀黍</p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/20740.html</id>
    <link href="https://www.bytelife.net/articles/20740.html"/>
    <published>2016-07-05T04:38:48.000Z</published>
    <summary>
      <![CDATA[<p>这篇文章主要是 java8 中新的 Date 和 Time API 的实战。新的 Date 和 Time 类是 java 开发者社区千呼万唤始出来的。Java8 之前存在的 Date 类一直都受人诟病，很多人都会选择使用第三方的 date 库 <a href="http://www.joda.org/joda-time/">joda-time</a>。Java8 中的 date 和 time api 是 jodatime 的作者参与开发的，实现了 JSR310 的全部内容。这些新的 api 都在包 java.time 下。 既然第三方的 joda-time,date4j 都已经足够强大了，为什么 java8 还要重新实现他呢，一部分的原因是这些第三方的库是存在兼容问题的，比如标准的 JSF 日期转化器与 joda-time api，就不兼容，每次使用都需要编写自己的转换器，所以标准化 api 是必须的，就有了 JSR310，java8 中就实现了他全部的规定内容。</p>]]>
    </summary>
    <title>Java 8 中新的 Date 和 Time 类入门详解</title>
    <updated>2016-07-05T04:38:48.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Jeffrey</name>
    </author>
    <category term="开发技能" scheme="https://www.bytelife.net/categories/development/"/>
    <category term="网络协议" scheme="https://www.bytelife.net/tags/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/"/>
    <content>
      <![CDATA[<h3 id="误解七：HTTPS无法缓存"><a href="#误解七：HTTPS无法缓存" class="headerlink" title="误解七：HTTPS无法缓存"></a><strong>误解七：HTTPS 无法缓存</strong></h3><p>许多人以为，出于安全考虑，浏览器不会在本地保存 HTTPS 缓存。实际上，只要在 HTTP 头中使用特定命令，HTTPS 是可以缓存的。 微软的 IE 项目经理 Eric Lawrence 写道：</p><blockquote><p>“说来也许令人震惊，只要 HTTP 头允许这样做，所有版本的 IE 都缓存 HTTPS 内容。比如，如果头命令是 Cache-Control: max-age=600，那么这个网页就将被 IE 缓存 10 分钟。IE 的缓存策略，与是否使用 HTTPS 协议无关。（其他浏览器在这方面的行为不一致，取决于你使用的版本，所以这里不加以讨论。）”</p></blockquote><span id="more"></span><p>Firefox 默认只在内存中缓存 HTTPS。但是，只要头命令中有 Cache-Control: Public，缓存就会被写到硬盘上。下面的图片显示，Firefox 的硬盘缓存中有 HTTPS 内容，头命令正是 Cache-Control:Public。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139210436054.png"></p><h3 id="误解六：SSL证书很贵"><a href="#误解六：SSL证书很贵" class="headerlink" title="误解六：SSL证书很贵"></a><strong>误解六：SSL 证书很贵</strong></h3><p>如果你在网上搜一下，就会发现很多便宜的 SSL 证书，大概 10 美元一年，这和一个.com 域名的年费差不多。而且事实上，还能找到免费的 SSL 证书。 在效力上，便宜的证书当然会比大机构颁发的证书差一点，但是几乎所有的主流浏览器都接受这些证书。 &nbsp;</p><h3 id="误解五：HTTPS站点必须有独享的IP地址"><a href="#误解五：HTTPS站点必须有独享的IP地址" class="headerlink" title="误解五：HTTPS站点必须有独享的IP地址"></a><strong>误解五：HTTPS 站点必须有独享的 IP 地址</strong></h3><p>由于 IPv4 将要分配完毕，所以很多人关心这个问题。每个 IP 地址只能安装一张 SSL 证书，这是毫无疑问的。但是，如果你使用子域名通配符 SSL 证书（wildcard SSL certificate，价格大约是每年 125 美元），就能在一个 IP 地址上部署多个 HTTPS 子域名。比如，<span class="exturl" data-url="aHR0cHM6Ly93d3cuaHR0cHdhdGNoLmNvbeWSjGh0dHBzLy9zdG9yZS5odHRwd2F0Y2guY29tJUVGJUJDJThDJUU1JUIwJUIxJUU1JTg1JUIxJUU0JUJBJUFCJUU1JTkwJThDJUU0JUI4JTgwJUU0JUI4JUFBSVAlRTUlOUMlQjAlRTUlOUQlODAlRTMlODAlODI=">https://www.httpwatch.com 和 https://store.httpwatch.com，就共享同一个 IP 地址。<i class="fa fa-external-link-alt"></i></span><br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139210533569.png"><br> 另外，UCC（统一通信证书，Unified Communications Certificate）支持一张证书同时匹配多个站点，可以是完全不同的域名。SNI（服务器名称指示，Server Name Indication）允许一个 IP 地址上多个域名安装多张证书。服务器端，Apache 和 Nginx 支持该技术，IIS 不支持；客户端，IE 7+、Firefox 2.0+、Chrome 6+、Safari 2.1+ 和 Opera 8.0+ 支持。 &nbsp;</p><h3 id="误解四：转移服务器时要购买新证书"><a href="#误解四：转移服务器时要购买新证书" class="headerlink" title="误解四：转移服务器时要购买新证书"></a><strong>误解四：转移服务器时要购买新证书</strong></h3><p>部署 SSL 证书，需要这样几步：</p><blockquote><ol><li>在你的服务器上，生成一个 CSR 文件（SSL 证书请求文件，SSL Certificate Signing Request）。 </li><li>使用 CSR 文件，购买 SSL 证书。 3. 安装 SSL 证书。</li></ol></blockquote><p>这些步骤都经过精心设计，保证传输的安全，防止有人截取或非法获得证书。结果就是，你在第二步得到的证书不能用在另一台服务器上。如果你需要这样做，就必须以其他格式输出证书。 比如，IIS 的做法是生成一个可以转移的.pfx 文件，并加以密码保护。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139210741414.png"><br>将这个文件传入其他服务器，将可以继续使用原来的 SSL 证书了。 &nbsp;</p><h3 id="误解三：HTTPS太慢"><a href="#误解三：HTTPS太慢" class="headerlink" title="误解三：HTTPS太慢"></a><strong>误解三：HTTPS 太慢</strong></h3><p>使用 HTTPS 不会使你的网站变得更快（实际上有可能，请看下文），但是有一些技巧可以大大减少额外开销。 首先，只要压缩文本内容，就会降低解码耗用的 CPU 资源。不过，对于当代 CPU 来说，这点开销不值一提。 其次，建立 HTTPS 连接，要求额外的 TCP 往返，因此会新增一些发送和接收的字节。但是，从下图可以看到，新增的字节是很少的。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139210834526.png"><br> 第一次打开网页的时候，HTTPS 协议会比 HTTP 协议慢一点，这是因为读取和验证 SSL 证书的时间。下面是一张 HTTP 网页打开时间的瀑布图。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139210883485.png"><br>同一张网页使用 HTTPS 协议之后，打开时间变长了。 建立连接的部分，大约慢了 10%。但是，一旦有效的 HTTPS 连接建立起来，再刷新网页，两种协议几乎没有区别。先是 HTTP 协议的刷新表现：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139210937090.png"><br> 然后是 HTTPS 协议：<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139211049326.png"><br> 某些用户可能发现，HTTPS 比 HTTP 更快一点。这会发生在一些大公司的内部局域网，因为通常情况下，公司的网关会截取并分析所有的网络通信。但是，当它遇到 HTTPS 连接时，它就只能直接放行，因为 HTTPS 无法被解读。正是因为少了这个解读的过程，所以 HTTPS 变得比较快。 &nbsp;</p><h3 id="误解二：有了HTTPS，Cookie和查询字符串就安全了"><a href="#误解二：有了HTTPS，Cookie和查询字符串就安全了" class="headerlink" title="误解二：有了HTTPS，Cookie和查询字符串就安全了"></a><strong>误解二：有了 HTTPS，Cookie 和查询字符串就安全了</strong></h3><p>虽然无法直接从 HTTPS 数据中读取 Cookie 和查询字符串，但是你仍然需要使它们的值变得难以预测。 比如，曾经有一家英国银行，直接使用顺序排列的数值表示 session id:<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139211144205.png"><br> 黑客可以先注册一个账户，找到这个 cookie，看到这个值的表示方法。然后，改动 cookie，从而劫持其他人的 session id。至于查询字符串，也可以通过类似方式泄漏。 &nbsp;</p><h3 id="误解一：只有注册登录页，才需要HTTPS"><a href="#误解一：只有注册登录页，才需要HTTPS" class="headerlink" title="误解一：只有注册登录页，才需要HTTPS"></a><strong>误解一：只有注册登录页，才需要 HTTPS</strong></h3><p>这种想法很普遍。人们觉得，HTTPS 可以保护用户的密码，此外就不需要了。Firefox 浏览器新插件 Firesheep，证明了这种想法是错的。我们可以看到，在 Twitter 和 Facebook 上，劫持其他人的 session 是非常容易的。 咖啡馆的免费 WiFi，就是一个很理想的劫持环境，因为两个原因：</p><blockquote><ol><li>这种 WiFi 通常不会加密，所以很容易监控所有流量。 </li><li>WiFi 通常使用 NAT 进行外网和内网的地址转换，所有内网客户端都共享一个外网地址。这意味着，被劫持的 session，看上去很像来自原来的登录者。</li></ol></blockquote><p>以 Twitter 为例，它的登录页使用了 HTTPS，但是登录以后，其他页面就变成了 HTTP。这时，它的 cookie 里的 session 值就暴露了。<br><img src="https://cdn.jsdelivr.net/gh/huzhanfei/static@main/blog/images/2021/03/16139211218521.png"><br><img src="https://cdn.javacodes.cn/files/2016/07/2016-07-04_15-58-23.png"> 也就是说，这些 cookie 是在 HTTPS 环境下建立的，但是却在 HTTP 环境下传输。如果有人劫持到这些 cookie，那他就能以你的身份在 Twitter 上发言了。 （完） &nbsp;</p><blockquote><p><strong>来自：阮一峰的网络日志</strong><br><strong>译者：<strong>阮一峰</strong></strong><br><strong>链接：<span class="exturl" data-url="aHR0cDovL3d3dy5ydWFueWlmZW5nLmNvbS9ibG9nLzIwMTEvMDIvc2V2ZW4vX215dGhzL19hYm91dF9odHRwcy5odG1s">http://www.ruanyifeng.com/blog/2011/02/seven\_myths\_about_https.html<i class="fa fa-external-link-alt"></i></span></strong><br><strong> 原文：<span class="exturl" data-url="aHR0cDovL2Jsb2cuaHR0cHdhdGNoLmNvbS8yMDExLzAxLzI4L3RvcC03LW15dGhzLWFib3V0LWh0dHBzLw==">http://blog.httpwatch.com/2011/01/28/top-7-myths-about-https/<i class="fa fa-external-link-alt"></i></span></strong></p></blockquote>]]>
    </content>
    <id>https://www.bytelife.net/articles/7124.html</id>
    <link href="https://www.bytelife.net/articles/7124.html"/>
    <published>2016-07-04T16:02:57.000Z</published>
    <summary>
      <![CDATA[<h3 id="误解七：HTTPS无法缓存"><a href="#误解七：HTTPS无法缓存" class="headerlink" title="误解七：HTTPS无法缓存"></a><strong>误解七：HTTPS 无法缓存</strong></h3><p>许多人以为，出于安全考虑，浏览器不会在本地保存 HTTPS 缓存。实际上，只要在 HTTP 头中使用特定命令，HTTPS 是可以缓存的。 微软的 IE 项目经理 Eric Lawrence 写道：</p>
<blockquote>
<p>“说来也许令人震惊，只要 HTTP 头允许这样做，所有版本的 IE 都缓存 HTTPS 内容。比如，如果头命令是 Cache-Control: max-age=600，那么这个网页就将被 IE 缓存 10 分钟。IE 的缓存策略，与是否使用 HTTPS 协议无关。（其他浏览器在这方面的行为不一致，取决于你使用的版本，所以这里不加以讨论。）”</p>
</blockquote>]]>
    </summary>
    <title>HTTPS 的 7 个误解</title>
    <updated>2016-07-04T16:02:57.000Z</updated>
  </entry>
</feed>
