532 lines
1.2 MiB
532 lines
1.2 MiB
<?xml version="1.0" encoding="utf-8"?>
|
||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||
<title>一只攻城狮的学习之旅</title>
|
||
|
||
<subtitle>一只攻城狮的学习之旅</subtitle>
|
||
<link href="https://zyjblogs.cn/atom.xml" rel="self"/>
|
||
|
||
<link href="https://zyjblogs.cn/"/>
|
||
<updated>2024-03-19T06:44:27.132Z</updated>
|
||
<id>https://zyjblogs.cn/</id>
|
||
|
||
<author>
|
||
<name>逝水无痕</name>
|
||
|
||
</author>
|
||
|
||
<generator uri="https://hexo.io/">Hexo</generator>
|
||
|
||
<entry>
|
||
<title>C++编码优化之减少冗余拷贝或赋值</title>
|
||
<link href="https://zyjblogs.cn/posts/97da918c.html"/>
|
||
<id>https://zyjblogs.cn/posts/97da918c.html</id>
|
||
<published>2024-03-19T06:32:03.211Z</published>
|
||
<updated>2024-03-19T06:44:27.132Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="C-编码优化之减少冗余拷贝或赋值"><a href="#C-编码优化之减少冗余拷贝或赋值" class="headerlink" title="C++编码优化之减少冗余拷贝或赋值"></a>C++编码优化之减少冗余拷贝或赋值</h1><h2 id="临时变量"><a href="#临时变量" class="headerlink" title="临时变量"></a>临时变量</h2><p>目前遇到的一些产生临时变量的情况:函数实参、函数返回值、隐式类型转换、多余的拷贝。</p><h3 id="1-函数实参"><a href="#1-函数实参" class="headerlink" title="1. 函数实参"></a>1. 函数实参</h3><p>这点应该比较容易理解,函数参数,如果是实参传递的话,函数体里的修改并不会影响调用时传入的参数的值。那么函数体里操作的对象肯定是函数调用的过程中产生出来的。</p><p>那么这种情况我们该怎么办呢?</p><p>如果 <code>callee</code> 中确实要修改这个对象,但是 <code>caller</code> 又不想 <code>callee</code> 的修改影响到原来的值,那么这个临时变量就是必须的了,不需要也没办法避免。</p><p>如果 <code>callee</code>中根本没有修改这个对象,或者 <code>callee</code> 中这个参数本身就是 <code>const</code> 型的,那么将实参传递改为引用传递是个不错的选择(如果是基本类型的函数实参,则没有必要改为引用),可以减少一个临时变量而且不会带来任何损失。</p><p>另外,推荐一个静态代码检查工具 <code>cppcheck</code>,这个工具可以提示非基本类型的 <code>const</code> 实参改为引用。</p><h3 id="2-函数返回值(返回对象)"><a href="#2-函数返回值(返回对象)" class="headerlink" title="2. 函数返回值(返回对象)"></a>2. 函数返回值(返回对象)</h3><p>函数返回值的情况比较复杂,因为编译器在这方面做了很多优化,编译器优化到何种程度我也没追根究底研究过。</p><p>在没开任何优化选项的时候,<code>gcc</code> 也优化了一些简单的返回对象的情况。</p><p>先看一段代码:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">A createA(int a)</span><br><span class="line">{</span><br><span class="line"> A tmp; </span><br><span class="line"> tmp._a=a;</span><br><span class="line"> return tmp;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>抛开所有优化不谈,函数中 <code>createA</code> 应该有一个构造操作(<code>tmp</code> 对象生成)和一个拷贝构造操作(<code>tmp</code> 对象返回时)。</p><p>于是有些编译器尝试对函数返回时的拷贝构造进行优化:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">A createA(int a)</span><br><span class="line">{</span><br><span class="line"> return A(a);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>第一步可以被优化的拷贝构造就是上面的这种情况,即 <code>RVO(return value optimization)</code>,这时候只能在函数返回一个未命名变量的时候进行优化。</p><p>后来更进一步,可以在函数返回命名变量的时候也进行优化了,这就是 <code>NRVO(named return value optimization)</code>。</p><p>但是这时候,还有一种情况不能优化的情况是:如果 <code>createA</code>函数内部不同的分支返回不同的对象。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">A createA(int a)</span><br><span class="line">{</span><br><span class="line"> if(a%2==0)</span><br><span class="line"> {</span><br><span class="line"> A tmp; </span><br><span class="line"> tmp._a = 2;</span><br><span class="line"> return tmp;</span><br><span class="line"> }</span><br><span class="line"> else </span><br><span class="line"> {</span><br><span class="line"> A tmp; </span><br><span class="line"> tmp._a = 1;</span><br><span class="line"> return tmp;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>比如上面这段代码,我在 <code>gcc 3.4.5</code> 的情况下测试,发现这种情况是不能优化的。</p><p>但是也不排除 <code>gcc</code> 更高的版本或者某些在这方面做得更优秀的编译器已经可以优化这种情况。</p><h3 id="3-隐式类型转换"><a href="#3-隐式类型转换" class="headerlink" title="3. 隐式类型转换"></a>3. 隐式类型转换</h3><p>代码中的一些类型的隐式转换也会产生临时变量,比如:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">class A</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> A(int a=0):_a(a)</span><br><span class="line"> {</span><br><span class="line"> cout<<"constructor"<<endl;</span><br><span class="line"> }</span><br><span class="line"> A(const A &a)</span><br><span class="line"> {</span><br><span class="line"> cout<<"copy constructor"<<endl;</span><br><span class="line"> this->_a = a._a; </span><br><span class="line"> }</span><br><span class="line"> A& operator=(const A&a)</span><br><span class="line"> {</span><br><span class="line"> cout<<"operator="<<endl;</span><br><span class="line"> this->_a = a._a; </span><br><span class="line"> return *this;</span><br><span class="line"> }</span><br><span class="line"> int _a; </span><br><span class="line">};</span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> A a1;</span><br><span class="line"> a1 = 3; </span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 a1 = 3 执行时会首先调用 <code>A(3)</code> 产生一个临时对象,然后调用<code>operator=(const A& a)</code>。</p><p>这种情况下,我们只要实现一个<code>A::operator=(int)</code>函数就可以避免这个临时对象的产生了。</p><p>当然,这只是一个最简单的例子,不过思路是差不多的。</p><h3 id="4-多余的拷贝"><a href="#4-多余的拷贝" class="headerlink" title="4. 多余的拷贝"></a>4. 多余的拷贝</h3><p>这种情况应该比较少,也比较简单,个人感觉,这种情况主要是疏忽引起的。</p><p>是这样一种情况:</p><p>有个线程级的结构体<code>thread_data_t *pthread_data</code>,里面包含请求包的各种数据,在几处使用的使用使用了<code>const A a = pthread_data->getA()</code>。</p><p><code>getA()</code>的实现简单来说是返回了<code>thread_data_t</code>内部的A的成员。</p><p>因为在一次请求的处理过程中<code>thread_data_t</code>内部的 A 的成员不会改变,调用者用<code>const A a</code>来接收这个对象就表明调用者也不会改变返回的 A 成员。</p><p>因此,其实完全可以让<code>getA()</code>返回A成员的引用,调用者同样用引用来接收:<code>const A & a = pthread_data->getA()</code>。</p><p>这样就完全就避免了一次多余的拷贝。</p><h2 id="非临时变量"><a href="#非临时变量" class="headerlink" title="非临时变量"></a>非临时变量</h2><p>遇到的一些非临时变量情况有:<code>stl vector</code> 的增长引起拷贝构造、<code>vector</code> 的赋值、<code>std::sort</code> 操作</p><h3 id="1-vector的增长"><a href="#1-vector的增长" class="headerlink" title="1. vector的增长"></a>1. vector的增长</h3><p>先简单介绍一下<code>vector</code>的增长机制:每次<code>push_back</code>时,如果发现原来<code>vector</code>的空间用完,会把<code>vector</code>调整到原来的 2 倍( sgi 的实现,<code>visual studio</code> 的实现好像是 1.5 倍)。因为 <code>vector</code> 空间是连续存储的,这里就有一个问题,如果原来 <code>vector</code> 地址后面空余的空间没有被使用,那么<code>vector</code>继续把后面的地址申请来就可以扩展其空间了。但是如果后面的空间不够了呢?那就要重新申请一个<code>2*current_size</code>大小的空间,然后把<code>vector</code>当前,也就是<code>current_size</code>的内容拷贝到刚申请的那块空间中去,这时就引起了对象的拷贝操作了。</p><p>假设<code>vector</code>初始大小是 0,我们通过<code>push_back</code>加入了 10 个对象,以<code>sgi</code>实现的两倍增长为例,再假设每次调整<code>vector</code>空间的时候都需要调整地址,一共引入了多少次无用的拷贝?</p><p>因为<code>vector</code>空间是<code>1->2->4->8->16</code>增长的,拷贝的次数一共是四次,每次拷贝对象分别是<code>1、2、4、8</code>个。所以答案是<code>1+2+4+8=15</code>。</p><p>很容易看出规律,拷贝对象的个数等于最终<code>vector</code>空间大小减一。</p><p>那么如果<code>vector</code>大小最终会涨到 1000,1W 呢?数据就很可观了。</p><p>我接触过好几个服务,最终<code>vector</code>可能会增长到 10W 左右的。如果<code>vector</code>要放入 10W 个元素,那么就会开辟<code>131072</code>的空间,也就是说最多会引入 13W 次的对象拷贝,而这个拷贝操作是无效的、是可以避免的。</p><p>其实要避免<code>vector</code>增长引入的拷贝也很简单,在<code>push_back</code>之前先调用<code>reserve</code>申请一个估算的最大空间。</p><p>比如我们之前优化的一些服务,预期<code>vector</code>最大可能会增长到 10W,那么直接调用<code>v.reserve(100000)</code>就可以了。</p><p>当然,这也许会引起一些内存使用的浪费,这就需要使用时注意权衡了。</p><p>但如果你的服务是一直运行的,而且这个<code>vecto</code>r对象也是常驻内存的,个人觉得完全可以<code>reserve</code>一个最大的空间。因为<code>vector</code>空间增长之后,就算调用<code>clear</code>清除所有元素,内存也是不会释放的。除非使用和空<code>vector</code>交换的方式强制释放它的内存。</p><h3 id="2-vector的赋值"><a href="#2-vector的赋值" class="headerlink" title="2. vector的赋值"></a>2. vector的赋值</h3><p>遇到过这样一种情况,在一个函数接受一个<code>vector &</code>作为输入,经过一系列处理得到一个临时的<code>vector</code>,并在函数返回前将这个临时的<code>vector</code>赋值给作为参数的<code>vector &</code>作为返回值。简化一下代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">void cal_result(vector<int> &input_ret)</span><br><span class="line">{</span><br><span class="line"> vector<int> tmp;</span><br><span class="line"> for(...)</span><br><span class="line"> {</span><br><span class="line"> ... // input_ret will be used </span><br><span class="line"> //fill tmp</span><br><span class="line"> }</span><br><span class="line"> input_ret = tmp;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里,我们可以注意到函数返回后 <code>tmp</code> 对象也就消失了,不会被继续使用,所以如果可以的话,我们根本不需要返回 <code>tmp</code>的拷贝,直接返回 <code>tmp</code> 占用的空间就可以了。</p><p>那么怎么可以直接返回 <code>tmp</code> 而不引起拷贝呢?是不是可以这样想,我们把 <code>tmp</code>这个<code>vector</code>指向的地址赋值给<code>input_ret</code>,把<code>tmp</code>指向的空间和大小设置为 0 就可以了?</p><p>那么我们完全可以使用<code>vector</code>的<code>swap</code>操作。它只是将两个<code>vector</code>指向空间等等信息交换了一下,而不会引起元素的拷贝,它的操作是常数级的,和交互对象中元素数目无关。</p><p>因此将上述代码改为:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">void cal_result(vector<int> &input_ret)</span><br><span class="line">{</span><br><span class="line"> vector<int> tmp;</span><br><span class="line"> for(...)</span><br><span class="line"> {</span><br><span class="line"> ... // input_ret will be used </span><br><span class="line"> //fill tmp</span><br><span class="line"> }</span><br><span class="line"> input_ret.swap(tmp);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以减少<code>tmp</code>元素的拷贝操作,大大提高了该函数的处理效率。(提高多少,要看<code>tmp</code>中所有元素拷贝的代价多大)</p><h3 id="3-std-sort操作"><a href="#3-std-sort操作" class="headerlink" title="3. std::sort操作"></a>3. std::sort操作</h3><p>在为一个模块做性能优化的时候,发现一个<code>vector</code>的<code>sort</code>的操作十分消耗性能,占了整个模块消耗<code>CPU 10%</code>以上。</p><p>使用<code>gperftools</code>的<code>cpu profiler</code>分析了一下数据,发现<code>sort</code>操作调用了元素的拷贝构造和赋值函数,这才是消耗性能的原因。</p><p>进一步分析,<code>vector</code>中的元素对象特别庞大,对象中又嵌套了其他对象且嵌套了好几层,因此函数的拷贝和赋值的操作代价会比较大。</p><p>而<code>std::sort</code>采用的是内省排序+插入排序的方式( sgi 的实现),不可避免地会引入对象的交换和移动。(其实不管怎么排序都避免不了交换和移动的吧…)</p><p>因此,要优化这句<code>std::sort</code>操作,还需要减少对象交换或者提高交换的效率上入手。</p><ol><li>减少对象的交换</li></ol><p>我们采用的减少对象交换的方式是:先使用<code>index</code>的方式进行排序,排序好了之后,把原来的<code>vector</code>中的对象按照<code>index</code>排序的结果最终做一次拷贝,拷贝到这个对象排序后应该在的位置。</p><ol><li>提高交换的效率</li></ol><p>如果对象的实现是如下这样的:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">class A</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> A(const char* src)</span><br><span class="line"> {</span><br><span class="line"> _len = strlen(src);</span><br><span class="line"> _content = new char[_len];</span><br><span class="line"> memcpy(_content,src,_len);</span><br><span class="line"> }</span><br><span class="line"> A(const A &a)</span><br><span class="line"> {</span><br><span class="line"> *this = a;</span><br><span class="line"> }</span><br><span class="line"> A& operator=(const A&a)</span><br><span class="line"> {</span><br><span class="line"> _len = a._len; </span><br><span class="line"> _content = new char[_len];</span><br><span class="line"> memcpy(_content,src,_len);</span><br><span class="line"> }</span><br><span class="line">private:</span><br><span class="line"> char *_content;</span><br><span class="line"> int _len;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这里为了保持代码简短,省略了部分实现且没考虑一些安全性的校验。</p><p>那么在对象交换的时候,其实是没有必要调用拷贝构造函数和赋值函数的(<code>std::swap</code>的默认实现),直接交换两个对象的<code>_content</code>和<code>_len</code>值就好了。如果调用拷贝构造函数和赋值函数的话,不可避免还要引入<code>new、memcpy、strlen、delete</code>等等操作。</p><p>这种情况下,我们完全可以针对 A 的实现,重载全局的<code>swap</code>操作。这样<code>sort</code>的过程中就可以调用我们自己实现的高效的<code>swap</code>了。</p><p>如下代码可以重载我们 A 函数的<code>swap</code>实现:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">namespace std</span><br><span class="line">{</span><br><span class="line"> template<></span><br><span class="line"> void swap<A>(A &a1,A& a2)</span><br><span class="line"> {</span><br><span class="line"> cout<<"swap A"<<endl;</span><br><span class="line"> int tmp = a1._a;</span><br><span class="line"> a1._a = a2._a;</span><br><span class="line"> a2._a = tmp;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为调用堆精度问题和编译优化的问题,有时候也可能分析不到 <code>sort</code> 是因为调用了元素对象的拷贝构造和赋值函数所以才效率比较低。所以发现<code>sort</code>消耗性能的时候,可以看看是否是因为<code>sort</code>对象过大造成的,积累一个<code>common sense</code>吧。</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="C-编码优化之减少冗余拷贝或赋值"><a href="#C-编码优化之减少冗余拷贝或赋值" class="headerlink" title="C++编码优化之减少冗余拷贝或赋值"></a>C++编码优化之减少冗余拷贝或赋值</h1><h2 id="临时变量"><</summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>C语言中三块难啃的硬骨头</title>
|
||
<link href="https://zyjblogs.cn/posts/0.html"/>
|
||
<id>https://zyjblogs.cn/posts/0.html</id>
|
||
<published>2024-03-19T06:29:41.998Z</published>
|
||
<updated>2024-03-19T06:56:02.090Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="C语言中三块难啃的硬骨头"><a href="#C语言中三块难啃的硬骨头" class="headerlink" title="C语言中三块难啃的硬骨头"></a><a href="https://mp.weixin.qq.com/s/Ntr0cw7zeLNZbd-kdnjMsQ">C语言中三块难啃的硬骨头</a></h1><p>C语言在嵌入式学习中是必备的知识,审核大部分操作都要围绕C语言进行,而其中有三块“难啃的硬骨头”几乎是公认级别的。</p><p><img src="/posts/0/images/image-20220209155908670.png" alt="image-20220209155908670"></p><h1 id="0x01-指针"><a href="#0x01-指针" class="headerlink" title="0x01 指针"></a>0x01 指针</h1><p>指针公认最难理解的概念,也是让很多初学者选择放弃的直接原因。</p><p>指针之所以难理解,因为指针本身就是一个变量,是一个非常特殊的变量,专门存放地址的变量,这个地址需要给申请空间才能装东西,而且因为是个变量可以中间赋值,这么一倒腾很多人就开始犯晕了,绕不开弯了。C语言之所以被很多高手所喜欢,就是指针的魅力,中间可以灵活的切换,执行效率超高,这点也是让小白晕菜的地方。</p><p>指针是学习绕不过去的知识点,而且学完C语言,下一步紧接着切换到数据结构和算法,指针是切换的重点,指针搞不定下一步进行起来就很难,会让很多人放弃继续学习的勇气。</p><p>指针直接对接内存结构,常见的C语言里面的指针乱指,数组越界根本原因就是内存问题。在指针这个点有无穷无尽的发挥空间。很多编程的技巧都在此集结。</p><p>指针还涉及如何申请释放内存,如果释放不及时就会出现内存泄露的情况,指针是高效好用,但不彻底搞明白对于有些人来说简直就是噩梦。</p><p>在概念方面问题可以参见此前推文《对于C语言指针最详尽的讲解》,那么在指针方面可以参见一下大神的经验:</p><h2 id="复杂类型说明"><a href="#复杂类型说明" class="headerlink" title="复杂类型说明"></a><strong>复杂类型说明</strong></h2><p>要了解指针,多多少少会出现一些比较复杂的类型。所以先介绍一下如何完全理解一个复杂类型。</p><p>要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样。</p><p>所以笔者总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析。</p><p>下面让我们先从简单的类型开始慢慢分析吧。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int p;</span><br></pre></td></tr></table></figure><p>这是一个普通的整型变量</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int p;</span><br></pre></td></tr></table></figure><p>首先从P处开始,先与结合,所以说明P是一个指针。然后再与int结合,说明指针所指向的内容的类型为int型,所以P是一个返回整型数据的指针</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int p[3];</span><br></pre></td></tr></table></figure><p>首先从P处开始,先与[]结合,说明P是一个数组。然后与int结合,说明数组里的元素是整型的,所以P是一个由整型数据组成的数组。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int *p[3];</span><br></pre></td></tr></table></figure><p>首先从P处开始,先与[]结合,因为其优先级比高,所以P是一个数组。然后再与结合,说明数组里的元素是指针类型。之后再与int结合,说明指针所指向的内容的类型是整型的,所以P是一个由返回整型数据的指针所组成的数组。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int (*p)[3];</span><br></pre></td></tr></table></figure><p>首先从P处开始,先与结合,说明P是一个指针。然后再与[]结合(与”()”这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组。之后再与int结合,说明数组里的元素是整型的。所以P是一个指向由整型数据组成3个整数的指针。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int **p;</span><br></pre></td></tr></table></figure><p>首先从P开始,先与<em>结合,说明P是一个指针。然后再与</em>结合,说明指针所指向的元素是指针。之后再与int结合,说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int p(int);</span><br></pre></td></tr></table></figure><p>从P处起,先与()结合,说明P是一个函数。然后进入()里分析,说明该函数有一个整型变量的参数,之后再与外面的int结合,说明函数的返回值是一个整型数据。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int (*p)(int);</span><br></pre></td></tr></table></figure><p>从P处开始,先与指针结合,说明P是一个指针。然后与()结合,说明指针指向的是一个函数。之后再与()里的int结合,说明函数有一个int型的参数,再与最外层的int结合,说明函数的返回类型是整型,所以P是一个指向有一个整型参数且返回类型为整型的函数的指针。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int (p(int))[3];</span><br></pre></td></tr></table></figure><p>可以先跳过,不看这个类型,过于复杂。从P开始,先与()结合,说明P是一个函数。然后进入()里面,与int结合,说明函数有一个整型变量参数。然后再与外面的结合,说明函数返回的是一个指针。之后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组。接着再与结合,说明数组里的元素是指针,最后再与int结合,说明指针指向的内容是整型数据。所以P是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。</p><p>说到这里也就差不多了。理解了这几个类型,其它的类型对我们来说也是小菜了。不过一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用。这上面的几种类型已经足够我们用了。</p><h2 id="细说指针"><a href="#细说指针" class="headerlink" title="细说指针"></a><strong>细说指针</strong></h2><p>指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。</p><p>要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。让我们分别说明。</p><p>先声明几个指针放着做例子:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">(1)int*ptr;</span><br><span class="line"></span><br><span class="line">(2)char*ptr;</span><br><span class="line"></span><br><span class="line">(3)int**ptr;</span><br><span class="line"></span><br><span class="line">(4)int(*ptr)[3];</span><br><span class="line"></span><br><span class="line">(5)int*(*ptr)[4];</span><br></pre></td></tr></table></figure><h2 id="指针的类型"><a href="#指针的类型" class="headerlink" title="指针的类型"></a><strong>指针的类型</strong></h2><p>从语法的角度看,小伙伴们只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。</p><p>让我们看看上述例子中各个指针的类型:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">(1)intptr;//指针的类型是int</span><br><span class="line"></span><br><span class="line">(2)charptr;//指针的类型是char</span><br><span class="line"></span><br><span class="line">(3)intptr;//指针的类型是int</span><br><span class="line"></span><br><span class="line">(4)int(ptr)[3];//指针的类型是int()[3]</span><br><span class="line"></span><br><span class="line">(5)int*(ptr)[4];//指针的类型是int(*)[4]</span><br></pre></td></tr></table></figure><p>怎么样?找出指针的类型的方法是不是很简单?</p><h2 id="指针所指向的类型"><a href="#指针所指向的类型" class="headerlink" title="指针所指向的类型"></a><strong>指针所指向的类型</strong></h2><p>当通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。</p><p>从语法上看,小伙伴们只需把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。</p><p>上述例子中各个指针所指向的类型:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">(1)intptr; //指针所指向的类型是int</span><br><span class="line"></span><br><span class="line">(2)char*ptr; //指针所指向的的类型是char*</span><br><span class="line"></span><br><span class="line">(3)int*ptr; //指针所指向的的类型是int*</span><br><span class="line"></span><br><span class="line">(4)int(*ptr)[3]; //指针所指向的的类型是int(*)[3]</span><br><span class="line"></span><br><span class="line">(5)int*(*ptr)[4]; //指针所指向的的类型是int*(*)[4]</span><br></pre></td></tr></table></figure><p>在指针的算术运算中,指针所指向的类型有很大的作用。</p><p>指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当小伙伴们对C 越来越熟悉时,就会发现,把与指针搅和在一起的”类型”这个概念分成”指针的类型”和”指针所指向的类型”两个概念,是精通指针的关键点之一。</p><p>笔者看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。</p><h2 id="指针的值"><a href="#指针的值" class="headerlink" title="指针的值"></a><strong>指针的值</strong></h2><p>即指针所指向的内存区或地址。</p><p>指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。</p><p>在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。</p><p>以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。</p><p>指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。</p><p>以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?</p><h2 id="指针本身所占据的内存区"><a href="#指针本身所占据的内存区" class="headerlink" title="指针本身所占据的内存区"></a><strong>指针本身所占据的内存区</strong></h2><p>指针本身占了多大的内存?只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据4个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。</p><h1 id="0x02-函数"><a href="#0x02-函数" class="headerlink" title="0x02 函数"></a>0x02 函数</h1><p>面向过程对象模块的基本单位,以及对应各种组合,函数指针,指针函数</p><p>一个函数就是一个业务逻辑块,是面向过程,单元模块的最小单元,而且在函数的执行过程中,形参,实参如何交换数据,如何将数据传递出去,如何设计一个合理的函数,不单单是解决一个功能,还要看是不是能够复用,避免重复造轮子。</p><p>函数指针和指针函数,表面是两个字面意思的互换实际上含义截然不同,指针函数比较好理解,就是返回指针的一个函数,函数指针这个主要用在回调函数,很多人觉得函数都没还搞明白,回调函数更晕菜了。其实可以通俗的理解指向函数的指针,本身是一个指针变量,只不过在初始化的时候指向了函数,这又回到了指针层面。没搞明白指针再次深入的向前走特别难。</p><p><img src="/posts/0/images/image-20220209155933965.png" alt="image-20220209155933965"></p><p>C语言的开发者们为后来的开发者做了一些省力气的事情,他们编写了大量代码,将常见的基本功能都完成了,可以让别人直接拿来使用。但是那么多代码,如何从中找到自己需要的呢?将所有代码都拿来显然是不太现实。</p><p>但是这些代码,早已被早期的开发者们分门别类地放在了不同的文件中,并且每一段代码都有唯一的名字。所以其实学习C语言并没有那么难,尤其是可以在动手锻炼做项目中进行。使用代码时,只要在对应的名字后面加上( )就可以。这样的一段代码就是函数,函数能够独立地完成某个功能,一次编写完成后可以多次使用。</p><p>很多初学者可能都会把C语言中的函数和数学中的函数概念搞混淆。其实真相并没有那么复杂,C语言中的函数是有规律可循迹的,只要搞清楚了概念你会发现还挺有意思的。函数的英文名称是 Function,对应翻译过来的中文还有“功能”的意思。C语言中的函数也跟功能有着密切的关系。</p><p>我们来看一小段C语言代码:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">#include<stdio.h></span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line">puts("Hello World");</span><br><span class="line">return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>把目光放在第4行代码上,这行代码会在显示器上输出“Hello World”。前面我们已经讲过,puts 后面要带(),字符串也要放在()中。</p><p>在C语言中,有的语句使用时不能带括号,有的语句必须带括号。带括号的就是函数(Function)。</p><p>C语言提供了很多功能,我们只需要一句简单的代码就能够使用。但是这些功能的底层都比较复杂,通常是软件和硬件的结合,还要要考虑很多细节和边界,如果将这些功能都交给程序员去完成,那将极大增加程序员的学习成本,降低编程效率。</p><p>有了函数之后,C语言的编程效率就好像有了神器一样,开发者们只需要随时调用就可以了,像进程函数、操作函数、时间日期函数等都可以帮助我们直接实现C语言本身的功能。</p><p><strong>C语言函数是可以重复使用的</strong>。</p><p>函数的一个明显特征就是使用时必须带括号(),必要的话,括号中还可以包含待处理的数据。例如puts(“果果小师弟”)就使用了一段具有输出功能的代码,这段代码的名字是 puts,”尚观科技” 是要交给这段代码处理的数据。使用函数在编程中有专业的称呼,叫做函数调用(Function Call)。</p><p>如果函数需要处理多个数据,那么它们之间使用逗号,分隔,例如:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pow(10, 2);</span><br></pre></td></tr></table></figure><p>该函数用来求10的2次方。</p><p>好了,看到这里你有没有觉得其实C语言函数还是比较有意思的,而且并没有那么复杂困难。以后再遇到菜鸟小白的时候,你一口一个C语言的函数,说不定就能当场引来无数膜拜的目光。</p><h1 id="0x03-结构体、递归"><a href="#0x03-结构体、递归" class="headerlink" title="0x03 结构体、递归"></a>0x03 结构体、递归</h1><p>很多在大学学习C语言的,很多课程都没学完,结构体都没学到,因为从章节的安排来看好像,结构体学习放在教材的后半部分了,弄得很多学生觉得结构体不重要,如果只是应付学校的考试,或者就是为了混个毕业证,的确学的意义不大。</p><p>如果想从事编程这个行业,对这个概念还不了解,基本上无法构造数据模型,没有一个业务体是完全使用原生数据类型来完成的,很多高手在设计数据模型的时候,一般先把头文件中的结构体数据整理出来。然后设计好功能函数的参数,以及名字,然后才真正开始写c源码。</p><p>如果从节省空间考虑结构体里面的数据放的顺序不一样在内存中占用的空间也不一样,结构体与结构体之间赋值,结构体存在指针那么赋值要特别注意,需要进行深度的赋值。</p><p>递归一般用于从头到位统计或者罗列一些数据,在使用的时候很多初学者都觉得别扭,怎么还能自己调用自己?而且在使用的时候,一定设置好跳出的条件,不然无休止的进行下去,真就成无线死循环了。</p><p>对于结构体方面的知识,可以参见此前推送的文章《C语言结构体(struct)最全的讲解(万字干货)》。具体也可以参见大佬的经验:</p><p>相信大家对于结构体都不陌生。在此,分享出本人对C语言结构体的研究和学习的总结。如果你发现这个总结中有你以前所未掌握的,那本文也算是有点价值了。当然,水平有限,若发现不足之处恳请指出。代码文件test.c我放在下面。在此,我会围绕以下2个问题来分析和应用C语言结构体:</p><ol><li>C语言中的结构体有何作用</li><li>结构体成员变量内存对齐有何讲究(重点)</li></ol><p>对于一些概念的说明,我就不把C语言教材上的定义搬上来。我们坐下来慢慢聊吧。</p><h2 id="1-结构体有何作用"><a href="#1-结构体有何作用" class="headerlink" title="1. 结构体有何作用"></a><strong>1. 结构体有何作用</strong></h2><p>三个月前,教研室里一个学长在华为南京研究院的面试中就遇到这个问题。当然,这只是面试中最基础的问题。如果问你你怎么回答?我的理解是这样的,C语言中结构体至少有以下三个作用:</p><p>(1) 有机地组织了对象的属性。</p><p>比如,在STM32的RTC开发中,我们需要数据来表示日期和时间,这些数据通常是年、月、日、时、分、秒。如果我们不用结构体,那么就需要定义6个变量来表示。这样的话程序的数据结构是松散的,我们的数据结构最好是“高内聚,低耦合”的。所以,用一个结构体来表示更好,无论是从程序的可读性还是可移植性还是可维护性皆是:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">typedef struct //公历日期和时间结构体</span><br><span class="line">{</span><br><span class="line">vu16 year;</span><br><span class="line">vu8 month;</span><br><span class="line">vu8 date;</span><br><span class="line">vu8 hour;</span><br><span class="line">vu8 min;</span><br><span class="line">vu8 sec;</span><br><span class="line">}_calendar_obj;</span><br><span class="line">_calendar_obj calendar; //定义结构体变量</span><br></pre></td></tr></table></figure><p>(2) 以修改结构体成员变量的方法代替了函数(入口参数)的重新定义。</p><p>如果说结构体有机地组织了对象的属性表示结构体“中看”,那么以修改结构体成员变量的方法代替函数(入口参数)的重新定义就表示了结构体“中用”。继续以上面的结构体为例子,我们来分析。假如现在我有如下函数来显示日期和时间:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">void DsipDateTime( _calendar_obj DateTimeVal)</span><br></pre></td></tr></table></figure><p>那么我们只要将一个_calendar_obj这个结构体类型的变量作为实参调用DsipDateTime()即可,DsipDateTime()通过DateTimeVal的成变量来实现内容的显示。如果不用结构体,我们很可能需要写这样的一个函数:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">void DsipDateTime( vu16 year,vu8 month,vu8 date,vu8 hour,vu8 min,vu8 sec)</span><br></pre></td></tr></table></figure><p>显然这样的形参很不可观,数据结构管理起来也很繁琐。如果某个函数的返回值得是一个表示日期和时间的数据,那就更复杂了。这只是一方面。</p><p>另一方面,如果用户需要表示日期和时间的数据中还要包含星期(周),这个时候,如果之前没有用机构体,那么应该在DsipDateTime()函数中在增加一个形参vu8 week:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">void DsipDateTime( vu16 year,vu8 month,vu8 date,vu8 week,vu8 hour,vu8 min,vu8 sec)</span><br></pre></td></tr></table></figure><p>可见这种方法来传递参数非常繁琐。所以以结构体作为函数的入口参数的好处之一就是函数的声明void DsipDateTime(_calendar_obj DateTimeVal)不需要改变,只需要增加结构体的成员变量,然后在函数的内部实现上对calendar.week作相应的处理即可。这样,在程序的修改、维护方面作用显著。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">typedef struct //公历日期和时间结构体</span><br><span class="line">{</span><br><span class="line">vu16 year;</span><br><span class="line">vu8 month;</span><br><span class="line">vu8 date;</span><br><span class="line">vu8 week;</span><br><span class="line">vu8 hour;</span><br><span class="line">vu8 min;</span><br><span class="line">vu8 sec;</span><br><span class="line">}_calendar_obj;</span><br><span class="line">_calendar_obj calendar; //定义结构体变量</span><br></pre></td></tr></table></figure><p>(3) 结构体的内存对齐原则可以提高CPU对内存的访问速度(以空间换取时间)。</p><p>并且,结构体成员变量的地址可以根据基地址(以偏移量offset)计算。我们先来看看下面的一段简单的程序,对于此程序的分析会在第2部分结构体成员变量内存对齐中详细说明。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">#include<stdio.h></span><br><span class="line"></span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> struct //声明结构体char_short_long</span><br><span class="line"> {</span><br><span class="line"> char c;</span><br><span class="line"> short s;</span><br><span class="line"> long l;</span><br><span class="line"> }char_short_long;</span><br><span class="line"></span><br><span class="line"> struct //声明结构体long_short_char</span><br><span class="line"> {</span><br><span class="line"> long l;</span><br><span class="line"> short s;</span><br><span class="line"> char c;</span><br><span class="line"> }long_short_char;</span><br><span class="line"></span><br><span class="line"> struct //声明结构体char_long_short</span><br><span class="line"> {</span><br><span class="line"> char c;</span><br><span class="line"> long l;</span><br><span class="line"> short s;</span><br><span class="line"> }char_long_short;</span><br><span class="line"></span><br><span class="line">printf(" \n");</span><br><span class="line">printf(" Size of char = %d bytes\n",sizeof(char));</span><br><span class="line">printf(" Size of shrot = %d bytes\n",sizeof(short));</span><br><span class="line">printf(" Size of long = %d bytes\n",sizeof(long));</span><br><span class="line">printf(" \n"); //char_short_long</span><br><span class="line">printf(" Size of char_short_long = %d bytes\n",sizeof(char_short_long));</span><br><span class="line">printf(" Addr of char_short_long.c = 0x%p (10进制:%d)\n",&char_short_long.c,&char_short_long.c);</span><br><span class="line">printf(" Addr of char_short_long.s = 0x%p (10进制:%d)\n",&char_short_long.s,&char_short_long.s);</span><br><span class="line">printf(" Addr of char_short_long.l = 0x%p (10进制:%d)\n",&char_short_long.l,&char_short_long.l);</span><br><span class="line">printf(" \n");</span><br><span class="line"></span><br><span class="line">printf(" \n"); //long_short_char</span><br><span class="line">printf(" Size of long_short_char = %d bytes\n",sizeof(long_short_char));</span><br><span class="line">printf(" Addr of long_short_char.l = 0x%p (10进制:%d)\n",&long_short_char.l,&long_short_char.l);</span><br><span class="line">printf(" Addr of long_short_char.s = 0x%p (10进制:%d)\n",&long_short_char.s,&long_short_char.s);</span><br><span class="line">printf(" Addr of long_short_char.c = 0x%p (10进制:%d)\n",&long_short_char.c,&long_short_char.c);</span><br><span class="line">printf(" \n");</span><br><span class="line"></span><br><span class="line">printf(" \n"); //char_long_short</span><br><span class="line">printf(" Size of char_long_short = %d bytes\n",sizeof(char_long_short));</span><br><span class="line">printf(" Addr of char_long_short.c = 0x%p (10进制:%d)\n",&char_long_short.c,&char_long_short.c);</span><br><span class="line">printf(" Addr of char_long_short.l = 0x%p (10进制:%d)\n",&char_long_short.l,&char_long_short.l);</span><br><span class="line">printf(" Addr of char_long_short.s = 0x%p (10进制:%d)\n",&char_long_short.s,&char_long_short.s);</span><br><span class="line">printf(" \n");</span><br><span class="line">return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>程序的运行结果如下(注意:括号内的数据是成员变量的地址的十进制形式):</p><p><img src="/posts/0/images/640.webp" alt="图片"></p><h2 id="2-结构体成员变量内存对齐"><a href="#2-结构体成员变量内存对齐" class="headerlink" title="2. 结构体成员变量内存对齐"></a><strong>2. 结构体成员变量内存对齐</strong></h2><p>首先,我们来分析一下上面程序的运行结果。前三行说明在我的程序中,char型占1个字节,short型占2个字节,long型占4个字节。char_short_long、long_short_char和char_long_short是三个结构体成员相同但是成员变量的排列顺序不同。并且从程序的运行结果来看,</p><figure class="highlight plaintext"><table><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">Size of char_short_long = 8 bytes</span><br><span class="line">Size of long_short_char = 8 bytes</span><br><span class="line">Size of char_long_short = 12 bytes //比前两种情况大4 byte !</span><br></pre></td></tr></table></figure><p>并且,还要注意到,1 byte (char)+ 2 byte (short)+ 4 byte (long) = 7 byte,而不是8 byte。</p><p>所以,结构体成员变量的放置顺序影响着结构体所占的内存空间的大小。一个结构体变量所占内存的大小不一定等于其成员变量所占空间之和。如果一个用户程序或者操作系统(比如uC/OS-II)中存在大量结构体变量时,这种内存占用必须要进行优化,也就是说,结构体内部成员变量的排列次序是有讲究的。</p><p>结构体成员变量到底是如何存放的呢?</p><p>在这里,我就不卖关子了,直接给出如下结论,在没有#pragma pack宏的情况下:</p><ul><li>原则1 结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。</li><li>原则2 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。</li><li>原则3 结构体作为成员时,结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素时,那么b应该从8的整数倍地址处开始存储,因为sizeof(double) = 8 bytes)</li></ul><p>这里,我们结合上面的程序来分析(暂时不讨论原则3)。</p><p>先看看char_short_long和long_short_char这两个结构体,从它们的成员变量的地址可以看出来,这两个结构体符合原则1和原则2。注意,在 char_short_long的成员变量的地址中,char_short_long.s的地址是1244994,也就是说,1244993是“空的”,只是被“占位”了!</p><table><thead><tr><th align="left">成员变量</th><th align="left">成员变量十六进制地址</th><th align="left">成员变量十进制地址</th></tr></thead><tbody><tr><td align="left">char_long_short.c</td><td align="left">0x0012FF2C</td><td align="left">1244972</td></tr><tr><td align="left">char_long_short.l</td><td align="left">0x0012FF30</td><td align="left">1244976</td></tr><tr><td align="left">char_long_short.s</td><td align="left">0x0012FF34</td><td align="left">1244980</td></tr></tbody></table><p>可见,其内存分布图如下,共12 bytes:</p><p><img src="/posts/0/images/640.webp" alt="图片"></p><p>首先,1244972能被1整除,所以char_long_short.c放在1244972处没有问题(其实,就char型成员变量自身来说,其放在任何地址单元处都没有问题),根据原则1,在之后的1244973~1244975中都没有能被4(因为sizeof(long)=4bytes)整除的,1244976能被4整除,所以char_long_short.l应该放在1244976处,那么同理,最后一个.s(sizeof(short)=2 bytes)是应该放在1244980处。</p><p>是不是这样就结束了?不是,还有原则2。根据原则2的要求,char_long_short这个结构体所占的空间大小应该是其占内存空间最大的成员变量的大小的整数倍。如果我们到此就结束了,那么char_long_short所占的内存空间是1244972<del>1244981共计10bytes,不符合原则2,所以,必须在最后补齐2个 bytes(1244982</del>1244983)。</p><p>至此,一个结构体的内存布局完成了。</p><p>下面我们按照上述原则,来验证这样的分析是不是正确。按上面的分析,地址单元1244973、1244974、1244975以及1244982、1244983都是空的(至少char_long_short未用到,只是“占位”了)。如果我们的分析是正确的,那么,定义这样一个结构体,其所占内存也应该是12 bytes:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">struct //声明结构体char_long_short_new</span><br><span class="line">{</span><br><span class="line">char c;</span><br><span class="line">char add1; //补齐空间</span><br><span class="line">char add2; //补齐空间</span><br><span class="line">char add3; //补齐空间</span><br><span class="line">long l;</span><br><span class="line">short s;</span><br><span class="line">char add4; //补齐空间</span><br><span class="line">char add5; //补齐空间</span><br><span class="line">}char_long_short_new;</span><br></pre></td></tr></table></figure><p>可见,我们的分析是正确的。至于原则3,大家可以自己编程验证,这里就不再讨论了。</p><p>所以,无论你是在VC6.0还是Keil C51,还是Keil MDK中,当你需要定义一个结构体时,只要你稍微留心结构体成员变量内存对齐这一现象,就可以在很大程度上节约MCU的RAM。这一点不仅仅应用于实际编程,在很多大型公司,比如IBM、微软、百度、华为的笔试和面试中,也是常见的。</p><p>这三大块硬骨头是学习C语言的绊脚石,下功夫拿掉基本上C语言的大动脉就打通了,那么再去学习别的内容就相对比较简单了。编程学习过程中越是痛苦的时候,学到的东西就会越多,克服过去就会自己的技能,放弃了前面的付出的时间都将清零。越是难学的语言在入门之后,在入门之后越觉得过瘾,而且还容易上瘾。你上瘾了没?还是放弃了?</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="C语言中三块难啃的硬骨头"><a href="#C语言中三块难啃的硬骨头" class="headerlink" title="C语言中三块难啃的硬骨头"></a><a href="https://mp.weixin.qq.com/s/Ntr0cw7zeLNZbd</summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
<category term="指针" scheme="https://zyjblogs.cn/tags/%E6%8C%87%E9%92%88/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>一起探索C++类内存分布</title>
|
||
<link href="https://zyjblogs.cn/posts/97623f3c.html"/>
|
||
<id>https://zyjblogs.cn/posts/97623f3c.html</id>
|
||
<published>2024-03-19T06:28:04.302Z</published>
|
||
<updated>2024-03-19T06:58:31.098Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="一起探索C-类内存分布"><a href="#一起探索C-类内存分布" class="headerlink" title="一起探索C++类内存分布"></a>一起探索C++类内存分布</h1><p>C++ 类中内存分布具体是怎么样,尤其是C++中含有继承、虚函数、虚拟继承以及菱形继承等等情况下。</p><p>由于在<code>linux</code>下没有<code>windows</code>下显示直观,我们采用<code>vs2015</code>进行调试。</p><hr><ul><li><p><strong>部署环境</strong></p><p>我们在 <code>属性->C/C++ ->命令行 -> /d1 reportSingleClassLayoutXXX</code> ,XXX表示类名;</p></li></ul><p><img src="/posts/97623f3c/images/640.png" alt="图片"></p><hr><ul><li><p><strong>单个基础类</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">class Base</span><br><span class="line">{</span><br><span class="line">private:</span><br><span class="line"> int a;</span><br><span class="line"> int b;</span><br><span class="line">public:</span><br><span class="line"> void test();</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>内存分布:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">class Base size(8):</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | a</span><br><span class="line"> 4 | b</span><br><span class="line"> +-- -</span><br></pre></td></tr></table></figure><p><strong>总结</strong>:我们发现普通类的内存分布是根据声明的顺序进行的,成员函数不占用内存。</p></li></ul><hr><ul><li><p><strong>基础类+继承类</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">class Base</span><br><span class="line">{</span><br><span class="line"> int a;</span><br><span class="line"> int b;</span><br><span class="line">public:</span><br><span class="line"> void test();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Divide :public Base</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> void run();</span><br><span class="line">private:</span><br><span class="line"> int c;</span><br><span class="line"> int d;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>内存分布:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">class Divide size(16) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | +-- - (base class Base)</span><br><span class="line"> 0 | | a</span><br><span class="line"> 4 | | b</span><br><span class="line"> | +-- -</span><br><span class="line"> 8 | c</span><br><span class="line"> 12 | d</span><br><span class="line"> +-- -</span><br></pre></td></tr></table></figure><p><strong>总结</strong>:根据内存分布,我们发现普通继承类,内存分布也是按照声明的顺序进行的,成员函数不占用内存;类的顺序是先基类,后子类。</p></li></ul><hr><ul><li><p><strong>含有虚函数的基类</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">class Base</span><br><span class="line">{</span><br><span class="line"> int a;</span><br><span class="line"> int b;</span><br><span class="line">public:</span><br><span class="line"> void test();</span><br><span class="line"> virtual void run();</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>内存分布:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">class Base size(12) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | {vfptr}</span><br><span class="line"> 4 | a</span><br><span class="line"> 8 | b</span><br><span class="line"> +-- -</span><br><span class="line"></span><br><span class="line"> Base::$vftable@:</span><br><span class="line"> | &Base_meta</span><br><span class="line"> | 0</span><br><span class="line"> 0 | &Base::run</span><br></pre></td></tr></table></figure><p><strong>总结</strong>:带有虚函数的内存分布分为两部分,一部分是内存分布,一部分是虚表;我们从最上面发现,<code>vfptr</code>是放在了内存开始处,然后才是成员变量;虚函数<code>run</code>前面表示这个虚函数的序号为<code>0</code>。</p></li></ul><hr><ul><li><p><strong>含有虚函数的基类+继承子类</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">class Base</span><br><span class="line">{</span><br><span class="line"> int a;</span><br><span class="line"> int b;</span><br><span class="line">public:</span><br><span class="line"> void test();</span><br><span class="line"> virtual void run();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Divide :public Base</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> void DivideFun();</span><br><span class="line"> virtual void run();</span><br><span class="line">private:</span><br><span class="line"> int c;</span><br><span class="line"> int d;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>内存分布:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">class Divide size(20) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | +-- - (base class Base)</span><br><span class="line"> 0 | | {vfptr}</span><br><span class="line"> 4 | | a</span><br><span class="line"> 8 | | b</span><br><span class="line"> | +-- -</span><br><span class="line"> 12 | c</span><br><span class="line"> 16 | d</span><br><span class="line"> +-- -</span><br><span class="line"></span><br><span class="line"> Divide::$vftable@:</span><br><span class="line"> | &Divide_meta</span><br><span class="line"> | 0</span><br><span class="line"> 0 | &Divide::run</span><br></pre></td></tr></table></figure><p><strong>总结</strong>:我们发现继承类,虚表只有一个,还是在内存开始处,内存排布顺序与普通继承类是一致的;</p></li></ul><hr><ul><li><p><strong>含有虚函数的基类+继承子类(多增加一个虚函数)</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">class Base</span><br><span class="line">{</span><br><span class="line"> int a;</span><br><span class="line"> int b;</span><br><span class="line">public:</span><br><span class="line"> void test();</span><br><span class="line"> virtual void run();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Divide :public Base</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> void DivideFun();</span><br><span class="line"> virtual void run();</span><br><span class="line"> virtual void DivideRun();</span><br><span class="line">private:</span><br><span class="line"> int c;</span><br><span class="line"> int d;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>内存分布:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">class Divide size(20) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | +-- - (base class Base)</span><br><span class="line"> 0 | | {vfptr}</span><br><span class="line"> 4 | | a</span><br><span class="line"> 8 | | b</span><br><span class="line"> | +-- -</span><br><span class="line"> 12 | c</span><br><span class="line"> 16 | d</span><br><span class="line"> +-- -</span><br><span class="line"></span><br><span class="line"> Divide::$vftable@:</span><br><span class="line"> | &Divide_meta</span><br><span class="line"> | 0</span><br><span class="line"> 0 | &Divide::run</span><br><span class="line"> 1 | &Divide::DivideRun</span><br></pre></td></tr></table></figure><p><strong>总结</strong>:虚表还是继承于基类,在虚表部分多了<code>DivideRun</code>序号为<code>1</code>的虚函数;</p></li></ul><hr><ul><li><p><strong>多重继承</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">class Base</span><br><span class="line">{</span><br><span class="line"> int a;</span><br><span class="line"> int b;</span><br><span class="line">public:</span><br><span class="line"> virtual void run();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Divide1 :public Base</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual void run();</span><br><span class="line">private:</span><br><span class="line"> int c;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Divide2 :public Base</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual void run();</span><br><span class="line">private:</span><br><span class="line"> int d;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Divide :public Divide1, Divide2</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual void run();</span><br><span class="line">private:</span><br><span class="line"> int d;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>内存分布:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">class Divide1 size(16) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | +-- - (base class Base)</span><br><span class="line"> 0 | | {vfptr}</span><br><span class="line"> 4 | | a</span><br><span class="line"> 8 | | b</span><br><span class="line">| +-- -</span><br><span class="line"> 12 | c</span><br><span class="line"> +-- -</span><br><span class="line"></span><br><span class="line"> Divide1::$vftable@:</span><br><span class="line">| &Divide1_meta</span><br><span class="line">| 0</span><br><span class="line"> 0 | &Divide1::run</span><br><span class="line"></span><br><span class="line"> Divide1::run this adjustor: 0</span><br><span class="line"></span><br><span class="line"> class Divide2 size(16) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | +-- - (base class Base)</span><br><span class="line"> 0 | | {vfptr}</span><br><span class="line"> 4 | | a</span><br><span class="line"> 8 | | b</span><br><span class="line">| +-- -</span><br><span class="line"> 12 | d</span><br><span class="line"> +-- -</span><br><span class="line"></span><br><span class="line"> Divide2::$vftable@:</span><br><span class="line">| &Divide2_meta</span><br><span class="line">| 0</span><br><span class="line"> 0 | &Divide2::run</span><br><span class="line"></span><br><span class="line"> Divide2::run this adjustor: 0</span><br><span class="line"></span><br><span class="line"> class Divide size(36) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | +-- - (base class Divide1)</span><br><span class="line"> 0 | | +-- - (base class Base)</span><br><span class="line"> 0 | | | {vfptr}</span><br><span class="line"> 4 | | | a</span><br><span class="line"> 8 | | | b</span><br><span class="line">| | +-- -</span><br><span class="line"> 12 | | c</span><br><span class="line">| +-- -</span><br><span class="line"> | +-- - (base class Divide2)</span><br><span class="line"> | | +-- - (base class Base)</span><br><span class="line"> | | | {vfptr}</span><br><span class="line"> | | | a</span><br><span class="line"> | | | b</span><br><span class="line">| | +-- -</span><br><span class="line"> | | d</span><br><span class="line">| +-- -</span><br><span class="line"> | d</span><br><span class="line"> +-- -</span><br><span class="line"></span><br><span class="line"> Divide::$vftable@Divide1@:</span><br><span class="line">| &Divide_meta</span><br><span class="line">| 0</span><br><span class="line"> 0 | &Divide::run</span><br><span class="line"></span><br><span class="line"> Divide::$vftable@Divide2@:</span><br><span class="line">| -16</span><br><span class="line"> 0 | &thunk: this -= 16; goto Divide::run</span><br><span class="line"></span><br><span class="line"> Divide::run this adjustor: 0</span><br></pre></td></tr></table></figure><p>总结:主要看最后一个<code>Divide</code>类,内存排列顺序先是Divide1,后是Divide2,在Divide1和Divide2中各有一份虚表;</p></li></ul><hr><ul><li><p><strong>虚拟继承(菱形继承)</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">class Base</span><br><span class="line">{</span><br><span class="line"> int a;</span><br><span class="line"> int b;</span><br><span class="line">public:</span><br><span class="line"> virtual void run();</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Divide1 :virtual public Base</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual void run();</span><br><span class="line">private:</span><br><span class="line"> int c;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Divide2 :virtual public Base</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual void run();</span><br><span class="line">private:</span><br><span class="line"> int d;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Divide :public Divide1, Divide2</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual void run();</span><br><span class="line">private:</span><br><span class="line"> int d;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p><strong>内存分布:</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line">class Divide1 size(20) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | {vbptr}</span><br><span class="line"> 4 | c</span><br><span class="line"> +-- -</span><br><span class="line"> +-- - (virtual base Base)</span><br><span class="line"> 8 | {vfptr}</span><br><span class="line"> 12 | a</span><br><span class="line"> 16 | b</span><br><span class="line"> +-- -</span><br><span class="line"></span><br><span class="line"> Divide1::$vbtable@:</span><br><span class="line"> 0 | 0</span><br><span class="line"> 1 | 8 (Divide1d(Divide1 + 0)Base)</span><br><span class="line"></span><br><span class="line"> Divide1::$vftable@:</span><br><span class="line"> | -8</span><br><span class="line"> 0 | &Divide1::run</span><br><span class="line"></span><br><span class="line"> Divide1::run this adjustor: 8</span><br><span class="line"> vbi: class offset o.vbptr o.vbte fVtorDisp</span><br><span class="line"> Base 8 0 4 0</span><br><span class="line"></span><br><span class="line"> class Divide2 size(20) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | {vbptr}</span><br><span class="line"> 4 | d</span><br><span class="line"> +-- -</span><br><span class="line"> +-- - (virtual base Base)</span><br><span class="line"> 8 | {vfptr}</span><br><span class="line"> 12 | a</span><br><span class="line"> 16 | b</span><br><span class="line"> +-- -</span><br><span class="line"></span><br><span class="line"> Divide2::$vbtable@:</span><br><span class="line"> 0 | 0</span><br><span class="line"> 1 | 8 (Divide2d(Divide2 + 0)Base)</span><br><span class="line"></span><br><span class="line"> Divide2::$vftable@:</span><br><span class="line"> | -8</span><br><span class="line"> 0 | &Divide2::run</span><br><span class="line"></span><br><span class="line"> Divide2::run this adjustor: 8</span><br><span class="line"> vbi: class offset o.vbptr o.vbte fVtorDisp</span><br><span class="line"> Base 8 0 4 0</span><br><span class="line"></span><br><span class="line"> class Divide size(32) :</span><br><span class="line"> +-- -</span><br><span class="line"> 0 | +-- - (base class Divide1)</span><br><span class="line"> 0 | | {vbptr}</span><br><span class="line"> 4 | | c</span><br><span class="line"> | +-- -</span><br><span class="line"> 8 | +-- - (base class Divide2)</span><br><span class="line"> 8 | | {vbptr}</span><br><span class="line"> 12 | | d</span><br><span class="line"> | +-- -</span><br><span class="line"> 16 | d</span><br><span class="line"> +-- -</span><br><span class="line"> +-- - (virtual base Base)</span><br><span class="line"> 20 | {vfptr}</span><br><span class="line"> 24 | a</span><br><span class="line"> 28 | b</span><br><span class="line"> +-- -</span><br><span class="line"></span><br><span class="line"> Divide::$vbtable@Divide1@:</span><br><span class="line"> 0 | 0</span><br><span class="line"> 1 | 20 (Divided(Divide1 + 0)Base)</span><br><span class="line"></span><br><span class="line"> Divide::$vbtable@Divide2@:</span><br><span class="line"> 0 | 0</span><br><span class="line"> 1 | 12 (Divided(Divide2 + 0)Base)</span><br><span class="line"></span><br><span class="line"> Divide::$vftable@:</span><br><span class="line"> | -20</span><br><span class="line"> 0 | &Divide::run</span><br></pre></td></tr></table></figure><p>总结:通过内存分布可知,<code>Divide1</code>和<code>Divide2</code>都是两个虚表,Divide中却是成了3个虚表,只有一份base;所以说:<strong>虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(增加了更多的需指针)</strong></p></li></ul>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="一起探索C-类内存分布"><a href="#一起探索C-类内存分布" class="headerlink" title="一起探索C++类内存分布"></a>一起探索C++类内存分布</h1><p>C++ 类中内存分布具体是怎么样,尤其是C++中含有继承、虚函数</summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
<category term="memory" scheme="https://zyjblogs.cn/tags/memory/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>C++内存管理</title>
|
||
<link href="https://zyjblogs.cn/posts/b57ba5ed.html"/>
|
||
<id>https://zyjblogs.cn/posts/b57ba5ed.html</id>
|
||
<published>2024-03-19T06:27:19.919Z</published>
|
||
<updated>2024-03-19T06:47:01.461Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="C-内存管理"><a href="#C-内存管理" class="headerlink" title="C++内存管理"></a>C++内存管理</h1><p>内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++程序中都会发生,因此要想成为C++高手,内存管理一关是必须要过的,除非放弃C++,转到Java或者C#,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能。本期专题将从内存管理、内存泄漏、内存回收这三个方面来探讨C++内存管理问题。</p><h2 id="1-内存管理"><a href="#1-内存管理" class="headerlink" title="1. 内存管理"></a>1. 内存管理</h2><p>伟大的Bill Gates 曾经失言:</p><p>640K ought to be enough for everybody — Bill Gates 1981</p><p>程序员们经常编写内存管理程序,往往提心吊胆。如果不想触雷,唯一的解决办法就是发现所有潜伏的地雷并且排除它们,躲是躲不了的。本文的内容比一般教科书的要深入得多,读者需细心阅读,做到真正地通晓内存管理。</p><h2 id="1-1-C-内存管理详解"><a href="#1-1-C-内存管理详解" class="headerlink" title="1.1 C++内存管理详解"></a>1.1 C++内存管理详解</h2><h3 id="1-1-1-内存分配方式"><a href="#1-1-1-内存分配方式" class="headerlink" title="1.1.1 内存分配方式"></a>1.1.1 内存分配方式</h3><h4 id="1-1-1-1-分配方式简介"><a href="#1-1-1-1-分配方式简介" class="headerlink" title="1.1.1.1 分配方式简介"></a>1.1.1.1 分配方式简介</h4><p>在C++中,内存分成5个区,他们分别是栈、堆、自由存储区、全局/静态存储区和常量存储区。</p><p>栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。</p><p>堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。</p><p>自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。</p><p>全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。</p><p>常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。</p><h4 id="1-1-1-2-明确区分堆与栈"><a href="#1-1-1-2-明确区分堆与栈" class="headerlink" title="1.1.1.2 明确区分堆与栈"></a>1.1.1.2 明确区分堆与栈</h4><p>在bbs上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决定拿他第一个开刀。</p><p>首先,我们举一个例子:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">()</span> </span>{ <span class="type">int</span>* p=<span class="keyword">new</span> <span class="type">int</span>[<span class="number">5</span>]; }</span><br></pre></td></tr></table></figure><p>这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC下的汇编代码如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">00401028 push 14h</span><br><span class="line">0040102A call operator new (00401060)</span><br><span class="line">0040102F add esp,4</span><br><span class="line">00401032 mov dword ptr [ebp-8],eax</span><br><span class="line">00401035 mov eax,dword ptr [ebp-8]</span><br><span class="line">00401038 mov dword ptr [ebp-4],eax</span><br></pre></td></tr></table></figure><p>这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是delete p么?澳,错了,应该是delete []p,这是为了告诉编译器:我删除的是一个数组,VC就会根据相应的Cookie信息去进行释放内存的工作。</p><h4 id="1-1-1-3-堆和栈究竟有什么区别?"><a href="#1-1-1-3-堆和栈究竟有什么区别?" class="headerlink" title="1.1.1.3 堆和栈究竟有什么区别?"></a>1.1.1.3 堆和栈究竟有什么区别?</h4><p>好了,我们回到我们的主题:堆和栈究竟有什么区别?</p><p>主要的区别由以下几点:</p><p>1、管理方式不同;</p><p>2、空间大小不同;</p><p>3、能否产生碎片不同;</p><p>4、生长方向不同;</p><p>5、分配方式不同;</p><p>6、分配效率不同;</p><p>管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。</p><p>空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:</p><p>打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。</p><p>注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。</p><p>碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。</p><p>生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。</p><p>分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。</p><p>分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。</p><p>从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。</p><p>虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。</p><p>无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候debug可是相当困难的:)</p><h3 id="1-1-2-控制C-的内存分配"><a href="#1-1-2-控制C-的内存分配" class="headerlink" title="1.1.2 控制C++的内存分配"></a>1.1.2 控制C++的内存分配</h3><p>在嵌入式系统中使用C++的一个常见问题是内存分配,即对new 和 delete 操作符的失控。</p><p>具有讽刺意味的是,问题的根源却是C++对内存的管理非常的容易而且安全。具体地说,当一个对象被消除时,它的析构函数能够安全的释放所分配的内存。</p><p>这当然是个好事情,但是这种使用的简单性使得程序员们过度使用new 和 delete,而不注意在嵌入式C++环境中的因果关系。并且,在嵌入式系统中,由于内存的限制,频繁的动态分配不定大小的内存会引起很大的问题以及堆破碎的风险。</p><p>作为忠告,保守的使用内存分配是嵌入式环境中的第一原则。</p><p>但当你必须要使用new 和delete时,你不得不控制C++中的内存分配。你需要用一个全局的new 和delete来代替系统的内存分配符,并且一个类一个类的重载new 和delete。</p><p>一个防止堆破碎的通用方法是从不同固定大小的内存持中分配不同类型的对象。对每个类重载new 和delete就提供了这样的控制。</p><h4 id="1-1-2-1-重载全局的new和delete操作符"><a href="#1-1-2-1-重载全局的new和delete操作符" class="headerlink" title="1.1.2.1 重载全局的new和delete操作符"></a>1.1.2.1 重载全局的new和delete操作符</h4><p>可以很容易地重载new 和 delete 操作符,如下所示:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> * <span class="keyword">operator</span> <span class="title">new</span><span class="params">(<span class="type">size_t</span> size)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">void</span> *p = <span class="built_in">malloc</span>(size);</span><br><span class="line"> <span class="keyword">return</span> (p);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="keyword">operator</span> <span class="title">delete</span><span class="params">(<span class="type">void</span> *p)</span></span>;</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">free</span>(p);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这段代码可以代替默认的操作符来满足内存分配的请求。出于解释C++的目的,我们也可以直接调用malloc() 和free()。</p><p>也可以对单个类的new 和 delete 操作符重载。这是你能灵活的控制对象的内存分配。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TestClass</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> * <span class="keyword">operator</span> <span class="title">new</span><span class="params">(<span class="type">size_t</span> size)</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="keyword">operator</span> <span class="title">delete</span><span class="params">(<span class="type">void</span> *p)</span></span>;</span><br><span class="line"> <span class="comment">// .. other members here ...</span></span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">void</span> *TestClass::<span class="keyword">operator</span> <span class="title">new</span><span class="params">(<span class="type">size_t</span> size)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">void</span> *p = <span class="built_in">malloc</span>(size); <span class="comment">// Replace this with alternative allocator</span></span><br><span class="line"> <span class="keyword">return</span> (p);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> TestClass::<span class="keyword">operator</span> <span class="title">delete</span><span class="params">(<span class="type">void</span> *p)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">free</span>(p); <span class="comment">// Replace this with alternative de-allocator</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所有TestClass 对象的内存分配都采用这段代码。更进一步,任何从TestClass 继承的类也都采用这一方式,除非它自己也重载了new 和 delete 操作符。通过重载new 和 delete 操作符的方法,你可以自由地采用不同的分配策略,从不同的内存池中分配不同的类对象。</p><h4 id="1-1-2-2-为单个的类重载-new-和delete"><a href="#1-1-2-2-为单个的类重载-new-和delete" class="headerlink" title="1.1.2.2 为单个的类重载 new[ ]和delete[ ]"></a>1.1.2.2 为单个的类重载 new[ ]和delete[ ]</h4><p>必须小心对象数组的分配。你可能希望调用到被你重载过的new 和 delete 操作符,但并不如此。内存的请求被定向到全局的new[ ]和delete[ ] 操作符,而这些内存来自于系统堆。</p><p>C++将对象数组的内存分配作为一个单独的操作,而不同于单个对象的内存分配。为了改变这种方式,你同样需要重载new[ ] 和 delete[ ]操作符。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TestClass</span> {</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="type">void</span> * <span class="keyword">operator</span> <span class="keyword">new</span>[ ](<span class="type">size_t</span> size);</span><br><span class="line"> <span class="type">void</span> <span class="keyword">operator</span> <span class="keyword">delete</span>[ ](<span class="type">void</span> *p);</span><br><span class="line"> <span class="comment">// .. other members here ..</span></span><br><span class="line">};</span><br><span class="line"><span class="type">void</span> *TestClass::<span class="keyword">operator</span> <span class="keyword">new</span>[ ](<span class="type">size_t</span> size)</span><br><span class="line">{</span><br><span class="line"> <span class="type">void</span> *p = <span class="built_in">malloc</span>(size);</span><br><span class="line"> <span class="keyword">return</span> (p);</span><br><span class="line">}</span><br><span class="line"><span class="type">void</span> TestClass::<span class="keyword">operator</span> <span class="keyword">delete</span>[ ](<span class="type">void</span> *p)</span><br><span class="line">{</span><br><span class="line"> <span class="built_in">free</span>(p);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> TestClass *p = <span class="keyword">new</span> TestClass[<span class="number">10</span>];</span><br><span class="line"> <span class="comment">// ... etc ...</span></span><br><span class="line"> <span class="keyword">delete</span>[ ] p;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>但是注意:对于多数C++的实现,new[]操作符中的个数参数是数组的大小加上额外的存储对象数目的一些字节。在你的内存分配机制重要考虑的这一点。你应该尽量避免分配对象数组,从而使你的内存分配策略简单。</p><h3 id="1-1-3-常见的内存错误及其对策"><a href="#1-1-3-常见的内存错误及其对策" class="headerlink" title="1.1.3 常见的内存错误及其对策"></a>1.1.3 常见的内存错误及其对策</h3><p>发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。 常见的内存错误及其对策如下:</p><p>* 内存分配未成功,却使用了它。</p><p>编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行</p><p>检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。</p><p>* 内存分配虽然成功,但是尚未初始化就引用它。</p><p>犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。 内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。</p><p>* 内存分配成功并且已经初始化,但操作越过了内存的边界。</p><p>例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。</p><p>* 忘记了释放内存,造成内存泄露。</p><p>含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。</p><p>动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。</p><p>* 释放了内存却继续使用它。</p><p>有三种情况:</p><p>(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。</p><p>(2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。</p><p>(3)使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。</p><p>【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。</p><p>【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。</p><p>【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。</p><p>【规则4】动态内存的申请与释放必须配对,防止内存泄漏。</p><p>【规则5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。</p><h3 id="1-1-4-指针与数组的对比"><a href="#1-1-4-指针与数组的对比" class="headerlink" title="1.1.4 指针与数组的对比"></a>1.1.4 指针与数组的对比</h3><p>C++/C程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。</p><p>数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。</p><p>指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。</p><p>下面以字符串为例比较指针与数组的特性。</p><h4 id="1-1-4-1-修改内容"><a href="#1-1-4-1-修改内容" class="headerlink" title="1.1.4.1 修改内容"></a>1.1.4.1 修改内容</h4><p>下面示例中,字符数组a的容量是6个字符,其内容为hello。a的内容可以改变,如a[0]= ‘X’。指针p指向常量字符串“world”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span> a[] = “hello”;</span><br><span class="line">a[<span class="number">0</span>] = ‘X’;</span><br><span class="line">cout << a << endl;</span><br><span class="line"><span class="type">char</span> *p = “world”; <span class="comment">// 注意p指向常量字符串</span></span><br><span class="line">p[<span class="number">0</span>] = ‘X’; <span class="comment">// 编译器不能发现该错误</span></span><br><span class="line">cout << p << endl;</span><br></pre></td></tr></table></figure><h4 id="1-1-4-2-内容复制与比较"><a href="#1-1-4-2-内容复制与比较" class="headerlink" title="1.1.4.2 内容复制与比较"></a>1.1.4.2 内容复制与比较</h4><p>不能对数组名进行直接复制与比较。若想把数组a的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy进行复制。同理,比较b和a的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。</p><p>语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,可以先用库函数malloc为p申请一块容量为strlen(a)+1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 数组…</span></span><br><span class="line"><span class="type">char</span> a[] = <span class="string">"hello"</span>;</span><br><span class="line"><span class="type">char</span> b[<span class="number">10</span>];</span><br><span class="line"><span class="built_in">strcpy</span>(b, a); <span class="comment">// 不能用 b = a;</span></span><br><span class="line"><span class="keyword">if</span>(<span class="built_in">strcmp</span>(b, a) == <span class="number">0</span>) <span class="comment">// 不能用 if (b == a)</span></span><br><span class="line">…</span><br><span class="line"><span class="comment">// 指针…</span></span><br><span class="line"><span class="type">int</span> len = <span class="built_in">strlen</span>(a);</span><br><span class="line"><span class="type">char</span> *p = (<span class="type">char</span> *)<span class="built_in">malloc</span>(<span class="built_in">sizeof</span>(<span class="type">char</span>)*(len+<span class="number">1</span>));</span><br><span class="line"><span class="built_in">strcpy</span>(p,a); <span class="comment">// 不要用 p = a;</span></span><br><span class="line"><span class="keyword">if</span>(<span class="built_in">strcmp</span>(p, a) == <span class="number">0</span>) <span class="comment">// 不要用 if (p == a)</span></span><br><span class="line">…</span><br></pre></td></tr></table></figure><h4 id="1-1-4-3-计算内存容量"><a href="#1-1-4-3-计算内存容量" class="headerlink" title="1.1.4.3 计算内存容量"></a>1.1.4.3 计算内存容量</h4><p>用运算符sizeof可以计算出数组的容量(字节数)。如下示例中,sizeof(a)的值是12(注意别忘了’’)。指针p指向a,但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span> a[] = <span class="string">"hello world"</span>;</span><br><span class="line"><span class="type">char</span> *p = a;</span><br><span class="line">cout<< <span class="built_in">sizeof</span>(a) << endl; <span class="comment">// 12字节</span></span><br><span class="line">cout<< <span class="built_in">sizeof</span>(p) << endl; <span class="comment">// 4字节</span></span><br></pre></td></tr></table></figure><p>注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。如下示例中,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Func</span><span class="params">(<span class="type">char</span> a[<span class="number">100</span>])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> cout<< <span class="built_in">sizeof</span>(a) << endl; <span class="comment">// 4字节而不是100字节</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="1-1-5-指针参数是如何传递内存的?"><a href="#1-1-5-指针参数是如何传递内存的?" class="headerlink" title="1.1.5 指针参数是如何传递内存的?"></a>1.1.5 指针参数是如何传递内存的?</h3><p>如果函数的参数是一个指针,不要指望用该指针去申请动态内存。如下示例中,Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">GetMemory</span><span class="params">(<span class="type">char</span> *p, <span class="type">int</span> num)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> p = (<span class="type">char</span> *)<span class="built_in">malloc</span>(<span class="built_in">sizeof</span>(<span class="type">char</span>) * num);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Test</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> *str = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="built_in">GetMemory</span>(str, <span class="number">100</span>); <span class="comment">// str 仍然为 NULL</span></span><br><span class="line"> <span class="built_in">strcpy</span>(str, <span class="string">"hello"</span>); <span class="comment">// 运行错误</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。</p><p>如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见示例:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">GetMemory2</span><span class="params">(<span class="type">char</span> **p, <span class="type">int</span> num)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> *p = (<span class="type">char</span> *)<span class="built_in">malloc</span>(<span class="built_in">sizeof</span>(<span class="type">char</span>) * num);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Test2</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> *str = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="built_in">GetMemory2</span>(&str, <span class="number">100</span>); <span class="comment">// 注意参数是 &str,而不是str</span></span><br><span class="line"> <span class="built_in">strcpy</span>(str, <span class="string">"hello"</span>);</span><br><span class="line"> cout<< str << endl;</span><br><span class="line"> <span class="built_in">free</span>(str);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单,见示例:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">char</span> *<span class="title">GetMemory3</span><span class="params">(<span class="type">int</span> num)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> *p = (<span class="type">char</span> *)<span class="built_in">malloc</span>(<span class="built_in">sizeof</span>(<span class="type">char</span>) * num);</span><br><span class="line"> <span class="keyword">return</span> p;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Test3</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> *str = <span class="literal">NULL</span>;</span><br><span class="line"> str = <span class="built_in">GetMemory3</span>(<span class="number">100</span>);</span><br><span class="line"> <span class="built_in">strcpy</span>(str, <span class="string">"hello"</span>);</span><br><span class="line"> cout<< str << endl;</span><br><span class="line"> <span class="built_in">free</span>(str);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,见示例:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">char</span> *<span class="title">GetString</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> p[] = <span class="string">"hello world"</span>;</span><br><span class="line"> <span class="keyword">return</span> p; <span class="comment">// 编译器将提出警告</span></span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Test4</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> *str = <span class="literal">NULL</span>;</span><br><span class="line"> str = <span class="built_in">GetString</span>(); <span class="comment">// str 的内容是垃圾</span></span><br><span class="line"> cout<< str << endl;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>用调试器逐步跟踪Test4,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。</p><p>如果把上述示例改写成如下示例,会怎么样?</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">char</span> *<span class="title">GetString2</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> *p = <span class="string">"hello world"</span>;</span><br><span class="line"> <span class="keyword">return</span> p;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Test5</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> *str = <span class="literal">NULL</span>;</span><br><span class="line"> str = <span class="built_in">GetString2</span>();</span><br><span class="line"> cout<< str << endl;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。</p><h3 id="1-1-6-杜绝“野指针”"><a href="#1-1-6-杜绝“野指针”" class="headerlink" title="1.1.6 杜绝“野指针”"></a>1.1.6 杜绝“野指针”</h3><p>“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的成因主要有两种:</p><p>(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如</p><figure class="highlight cpp"><table><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">char</span> *p = <span class="literal">NULL</span>;</span><br><span class="line"><span class="type">char</span> *str = (<span class="type">char</span> *) <span class="built_in">malloc</span>(<span class="number">100</span>);</span><br></pre></td></tr></table></figure><p>(2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。</p><p>(3)指针操作超越了变量的作用域范围。这种情况让人防不胜防,示例程序如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></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><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Func</span><span class="params">(<span class="type">void</span>)</span></span>{ cout << “Func of <span class="keyword">class</span> <span class="title class_">A</span>” << endl; }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Test</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> A *p;</span><br><span class="line"> {</span><br><span class="line"> A a;</span><br><span class="line"> p = &a; <span class="comment">// 注意 a 的生命期</span></span><br><span class="line"> }</span><br><span class="line"> p-><span class="built_in">Func</span>(); <span class="comment">// p是“野指针”</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。</p><h3 id="1-1-7-有了malloc-free为什么还要new-delete?"><a href="#1-1-7-有了malloc-free为什么还要new-delete?" class="headerlink" title="1.1.7 有了malloc/free为什么还要new/delete?"></a>1.1.7 有了malloc/free为什么还要new/delete?</h3><p>malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。</p><p>对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。</p><p>因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。我们先看一看malloc/free和new/delete如何实现对象的动态内存管理,见示例:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Obj</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span> :</span><br><span class="line"> <span class="built_in">Obj</span>(<span class="type">void</span>){ cout << “Initialization” << endl; }</span><br><span class="line"> ~<span class="built_in">Obj</span>(<span class="type">void</span>){ cout << “Destroy” << endl; }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Initialize</span><span class="params">(<span class="type">void</span>)</span></span>{ cout << “Initialization” << endl; }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Destroy</span><span class="params">(<span class="type">void</span>)</span></span>{ cout << “Destroy” << endl; }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">UseMallocFree</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Obj *a = (obj *)<span class="built_in">malloc</span>(<span class="built_in">sizeof</span>(obj)); <span class="comment">// 申请动态内存</span></span><br><span class="line"> a-><span class="built_in">Initialize</span>(); <span class="comment">// 初始化</span></span><br><span class="line"> <span class="comment">//…</span></span><br><span class="line"> a-><span class="built_in">Destroy</span>(); <span class="comment">// 清除工作</span></span><br><span class="line"> <span class="built_in">free</span>(a); <span class="comment">// 释放内存</span></span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">UseNewDelete</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Obj *a = <span class="keyword">new</span> Obj; <span class="comment">// 申请动态内存并且初始化</span></span><br><span class="line"> <span class="comment">//…</span></span><br><span class="line"> <span class="keyword">delete</span> a; <span class="comment">// 清除并且释放内存</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>类Obj的函数Initialize模拟了构造函数的功能,函数Destroy模拟了析构函数的功能。函数UseMallocFree中,由于malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作。函数UseNewDelete则简单得多。</p><p>所以我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。</p><p>既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。</p><p>如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。</p><h3 id="1-1-8-内存耗尽怎么办?"><a href="#1-1-8-内存耗尽怎么办?" class="headerlink" title="1.1.8 内存耗尽怎么办?"></a>1.1.8 内存耗尽怎么办?</h3><p>如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。</p><p>(1)判断指针是否为NULL,如果是则马上用return语句终止本函数。例如:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Func</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> A *a = <span class="keyword">new</span> A;</span><br><span class="line"> <span class="keyword">if</span>(a == <span class="literal">NULL</span>)</span><br><span class="line"> {</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><br></pre></td></tr></table></figure><p>(2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。例如:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Func</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> A *a = <span class="keyword">new</span> A;</span><br><span class="line"> <span class="keyword">if</span>(a == <span class="literal">NULL</span>)</span><br><span class="line"> {</span><br><span class="line"> cout << “Memory Exhausted” << endl;</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> …</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>(3)为new和malloc设置异常处理函数。例如Visual C++可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,也可以让malloc享用与new相同的异常处理函数。详细内容请参考C++使用手册。</p><p>上述(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。</p><p>很多人不忍心用exit(1),问:“不编写出错处理程序,让操作系统自己解决行不行?”</p><p>不行。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果不用exit(1) 把坏程序杀死,它可能会害死操作系统。道理如同:如果不把歹徒击毙,歹徒在老死之前会犯下更多的罪。</p><p>有一个很重要的现象要告诉大家。对于32位以上的应用程序而言,无论怎样使用malloc与new,几乎不可能导致“内存耗尽”。我在Windows 98下用Visual C++编写了测试程序,见示例7。这个程序会无休止地运行下去,根本不会终止。因为32位操作系统支持“虚存”,内存用完了,自动用硬盘空间顶替。我只听到硬盘嘎吱嘎吱地响,Window 98已经累得对键盘、鼠标毫无反应。</p><p>我可以得出这么一个结论:对于32位以上的应用程序,“内存耗尽”错误处理程序毫无用处。这下可把Unix和Windows程序员们乐坏了:反正错误处理程序不起作用,我就不写了,省了很多麻烦。</p><p>我不想误导读者,必须强调:不加错误处理将导致程序的质量很差,千万不可因小失大。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">main</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">float</span> *p = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="keyword">while</span>(TRUE)</span><br><span class="line"> {</span><br><span class="line"> p = <span class="keyword">new</span> <span class="type">float</span>[<span class="number">1000000</span>];</span><br><span class="line"> cout << “eat memory” << endl;</span><br><span class="line"> <span class="keyword">if</span>(p==<span class="literal">NULL</span>)</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="1-1-9-malloc-free的使用要点"><a href="#1-1-9-malloc-free的使用要点" class="headerlink" title="1.1.9 malloc/free的使用要点"></a>1.1.9 malloc/free的使用要点</h3><p>函数malloc的原型如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> * <span class="title">malloc</span><span class="params">(<span class="type">size_t</span> size)</span></span>;</span><br></pre></td></tr></table></figure><p>用malloc申请一块长度为length的整数类型的内存,程序如下:</p><figure class="highlight cpp"><table><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> *p = (<span class="type">int</span> *) <span class="built_in">malloc</span>(<span class="built_in">sizeof</span>(<span class="type">int</span>) * length);</span><br></pre></td></tr></table></figure><p>我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。</p><p>* malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。</p><p>* malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住int, float等数据类型的变量的确切字节数。例如int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节。最好用以下程序作一次测试:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">cout << <span class="built_in">sizeof</span>(<span class="type">char</span>) << endl;</span><br><span class="line">cout << <span class="built_in">sizeof</span>(<span class="type">int</span>) << endl;</span><br><span class="line">cout << <span class="built_in">sizeof</span>(<span class="type">unsigned</span> <span class="type">int</span>) << endl;</span><br><span class="line">cout << <span class="built_in">sizeof</span>(<span class="type">long</span>) << endl;</span><br><span class="line">cout << <span class="built_in">sizeof</span>(<span class="type">unsigned</span> <span class="type">long</span>) << endl;</span><br><span class="line">cout << <span class="built_in">sizeof</span>(<span class="type">float</span>) << endl;</span><br><span class="line">cout << <span class="built_in">sizeof</span>(<span class="type">double</span>) << endl;</span><br><span class="line">cout << <span class="built_in">sizeof</span>(<span class="type">void</span> *) << endl;</span><br></pre></td></tr></table></figure><p>在malloc的“()”中使用sizeof运算符是良好的风格,但要当心有时我们会昏了头,写出 p = malloc(sizeof(p))这样的程序来。</p><p>函数free的原型如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">void free( void * memblock );</span><br></pre></td></tr></table></figure><p>为什么free函数不象malloc函数那样复杂呢?这是因为指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p是NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。</p><h3 id="1-1-10-new-delete的使用要点"><a href="#1-1-10-new-delete的使用要点" class="headerlink" title="1.1.10 new/delete的使用要点"></a>1.1.10 new/delete的使用要点</h3><p>运算符new使用起来要比函数malloc简单得多,例如:</p><figure class="highlight cpp"><table><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> *p1 = (<span class="type">int</span> *)<span class="built_in">malloc</span>(<span class="built_in">sizeof</span>(<span class="type">int</span>) * length);</span><br><span class="line"><span class="type">int</span> *p2 = <span class="keyword">new</span> <span class="type">int</span>[length];</span><br></pre></td></tr></table></figure><p>这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式。例如</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Obj</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span> :</span><br><span class="line"> <span class="built_in">Obj</span>(<span class="type">void</span>); <span class="comment">// 无参数的构造函数</span></span><br><span class="line"> <span class="built_in">Obj</span>(<span class="type">int</span> x); <span class="comment">// 带一个参数的构造函数</span></span><br><span class="line"> …</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Test</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Obj *a = <span class="keyword">new</span> Obj;</span><br><span class="line"> Obj *b = <span class="keyword">new</span> <span class="built_in">Obj</span>(<span class="number">1</span>); <span class="comment">// 初值为1</span></span><br><span class="line"> …</span><br><span class="line"> <span class="keyword">delete</span> a;</span><br><span class="line"> <span class="keyword">delete</span> b;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果用new创建对象数组,那么只能使用对象的无参数构造函数。例如:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Obj *objects = <span class="keyword">new</span> Obj[<span class="number">100</span>]; <span class="comment">// 创建100个动态对象</span></span><br></pre></td></tr></table></figure><p>不能写成:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Obj *objects = <span class="keyword">new</span> Obj[<span class="number">100</span>](<span class="number">1</span>);<span class="comment">// 创建100个动态对象的同时赋初值1</span></span><br></pre></td></tr></table></figure><p>在用delete释放对象数组时,留意不要丢了符号‘[]’。例如:</p><figure class="highlight cpp"><table><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">delete</span> []objects; <span class="comment">// 正确的用法</span></span><br><span class="line"><span class="keyword">delete</span> objects; <span class="comment">// 错误的用法</span></span><br></pre></td></tr></table></figure><p>后者有可能引起程序崩溃和内存泄漏。</p><h2 id="1-2-C-中的健壮指针和资源管理"><a href="#1-2-C-中的健壮指针和资源管理" class="headerlink" title="1.2 C++中的健壮指针和资源管理"></a>1.2 C++中的健壮指针和资源管理</h2><p>我最喜欢的对资源的定义是:”任何在你的程序中获得并在此后释放的东西”。内存是一个相当明显的资源的例子。它需要用new来获得,用delete来释放。同时也有许多其它类型的资源文件句柄、重要的片断、Windows中的GDI资源,等等。将资源的概念推广到程序中创建、释放的所有对象也是十分方便的,无论对象是在堆中分配的还是在栈中或者是在全局作用于内生命的。</p><p>对于给定的资源的拥有着,是负责释放资源的一个对象或者是一段代码。所有权分立为两种级别——自动的和显式的(automatic and explicit),如果一个对象的释放是由语言本身的机制来保证的,这个对象的就是被自动地所有。例如,一个嵌入在其他对象中的对象,他的清除需要其他对象来在清除的时候保证。外面的对象被看作嵌入类的所有者。 类似地,每个在栈上创建的对象(作为自动变量)的释放(破坏)是在控制流离开了对象被定义的作用域的时候保证的。这种情况下,作用于被看作是对象的所有者。注意所有的自动所有权都是和语言的其他机制相容的,包括异常。无论是如何退出作用域的——正常流程控制退出、一个break语句、一个return、一个goto、或者是一个throw——自动资源都可以被清除。</p><p>到目前为止,一切都很好!问题是在引入指针、句柄和抽象的时候产生的。如果通过一个指针访问一个对象的话,比如对象在堆中分配,C++不自动地关注它的释放。程序员必须明确的用适当的程序方法来释放这些资源。比如说,如果一个对象是通过调用new来创建的,它需要用delete来回收。一个文件是用CreateFile(Win32 API)打开的,它需要用CloseHandle来关闭。用EnterCritialSection进入的临界区(Critical Section)需要LeaveCriticalSection退出,等等。一个”裸”指针,文件句柄,或者临界区状态没有所有者来确保它们的最终释放。基本的资源管理的前提就是确保每个资源都有他们的所有者。</p><h3 id="1-2-1-第一条规则(RAII)"><a href="#1-2-1-第一条规则(RAII)" class="headerlink" title="1.2.1 第一条规则(RAII)"></a>1.2.1 第一条规则(RAII)</h3><p>一个指针,一个句柄,一个临界区状态只有在我们将它们封装入对象的时候才会拥有所有者。这就是我们的第一规则:在构造函数中分配资源,在析构函数中释放资源。</p><p>当你按照规则将所有资源封装的时候,你可以保证你的程序中没有任何的资源泄露。这点在当封装对象(Encapsulating Object)在栈中建立或者嵌入在其他的对象中的时候非常明显。但是对那些动态申请的对象呢?不要急!任何动态申请的东西都被看作一种资源,并且要按照上面提到的方法进行封装。这一对象封装对象的链不得不在某个地方终止。它最终终止在最高级的所有者,自动的或者是静态的。这些分别是对离开作用域或者程序时释放资源的保证。</p><p>下面是资源封装的一个经典例子。在一个多线程的应用程序中,线程之间共享对象的问题是通过用这样一个对象联系临界区来解决的。每一个需要访问共享资源的客户需要获得临界区。例如,这可能是Win32下临界区的实现方法。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CritSect</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">friend</span> <span class="keyword">class</span> <span class="title class_">Lock</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">CritSect</span> () { <span class="built_in">InitializeCriticalSection</span> (&_critSection); }</span><br><span class="line"> ~<span class="built_in">CritSect</span> () { <span class="built_in">DeleteCriticalSection</span> (&_critSection); }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Acquire</span> <span class="params">()</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="built_in">EnterCriticalSection</span> (&_critSection);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Release</span> <span class="params">()</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="built_in">LeaveCriticalSection</span> (&_critSection);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> CRITICAL_SECTION _critSection;</span><br><span class="line"></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这里聪明的部分是我们确保每一个进入临界区的客户最后都可以离开。”进入”临界区的状态是一种资源,并应当被封装。封装器通常被称作一个锁(lock)。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Lock</span></span><br><span class="line">{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Lock</span> (CritSect& critSect) : _critSect (critSect)</span><br><span class="line"> {</span><br><span class="line"> _critSect.<span class="built_in">Acquire</span> ();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ~<span class="built_in">Lock</span> ()</span><br><span class="line"> {</span><br><span class="line"> _critSect.<span class="built_in">Release</span> ();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span></span><br><span class="line"> CritSect & _critSect;</span><br><span class="line"></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>锁一般的用法如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Shared::Act</span> <span class="params">()</span> <span class="title">throw</span> <span class="params">(<span class="type">char</span> *)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="function">Lock <span class="title">lock</span> <span class="params">(_critSect)</span></span>;</span><br><span class="line"> <span class="comment">// perform action —— may throw</span></span><br><span class="line"> <span class="comment">// automatic destructor of lock</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意无论发生什么,临界区都会借助于语言的机制保证释放。</p><p>还有一件需要记住的事情——每一种资源都需要被分别封装。这是因为资源分配是一个非常容易出错的操作,是要资源是有限提供的。我们会假设一个失败的资源分配会导致一个异常——事实上,这会经常的发生。所以如果你想试图用一个石头打两只鸟的话,或者在一个构造函数中申请两种形式的资源,你可能就会陷入麻烦。只要想想在一种资源分配成功但另一种失败抛出异常时会发生什么。因为构造函数还没有全部完成,析构函数不可能被调用,第一种资源就会发生泄露。</p><p>这种情况可以非常简单的避免。无论何时你有一个需要两种以上资源的类时,写两个小的封装器将它们嵌入你的类中。每一个嵌入的构造都可以保证删除,即使包装类没有构造完成。</p><h3 id="1-2-2-Smart-Pointers"><a href="#1-2-2-Smart-Pointers" class="headerlink" title="1.2.2 Smart Pointers"></a>1.2.2 Smart Pointers</h3><p>我们至今还没有讨论最常见类型的资源——用操作符new分配,此后用指针访问的一个对象。我们需要为每个对象分别定义一个封装类吗?(事实上,C++标准模板库已经有了一个模板类,叫做auto_ptr,其作用就是提供这种封装。我们一会儿在回到auto_ptr。)让我们从一个极其简单、呆板但安全的东西开始。看下面的Smart Pointer模板类,它十分坚固,甚至无法实现。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SmartPointer</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> ~<span class="built_in">SmartPointer</span> () { <span class="keyword">delete</span> _p; }</span><br><span class="line"> T * <span class="keyword">operator</span>->() { <span class="keyword">return</span> _p; }</span><br><span class="line"> T <span class="type">const</span> * <span class="keyword">operator</span>->() <span class="type">const</span> { <span class="keyword">return</span> _p; }</span><br><span class="line"> <span class="keyword">protected</span>:</span><br><span class="line"> <span class="built_in">SmartPointer</span> (): _p (<span class="number">0</span>) {}</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">SmartPointer</span> <span class="params">(T* p)</span>: _p (p) {</span>}</span><br><span class="line"> T * _p;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>为什么要把SmartPointer的构造函数设计为protected呢?如果我需要遵守第一条规则,那么我就必须这样做。资源——在这里是class T的一个对象——必须在封装器的构造函数中分配。但是我不能只简单的调用new T,因为我不知道T的构造函数的参数。因为,在原则上,每一个T都有一个不同的构造函数;我需要为他定义个另外一个封装器。模板的用处会很大,为每一个新的类,我可以通过继承SmartPointer定义一个新的封装器,并且提供一个特定的构造函数。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SmartItem</span>: <span class="keyword">public</span> SmartPointer<Item></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">SmartItem</span> <span class="params">(<span class="type">int</span> i)</span></span></span><br><span class="line"><span class="function"> : SmartPointer<Item> (new Item (i)) {</span>}</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>为每一个类提供一个Smart Pointer真的值得吗?说实话——不!他很有教学的价值,但是一旦你学会如何遵循第一规则的话,你就可以放松规则并使用一些高级的技术。这一技术是让SmartPointer的构造函数成为public,但是只是是用它来做资源转换(Resource Transfer)我的意思是用new操作符的结果直接作为SmartPointer的构造函数的参数,像这样:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">SmartPointer<Item> <span class="title">item</span> <span class="params">(<span class="keyword">new</span> Item (i))</span></span>;</span><br></pre></td></tr></table></figure><p>这个方法明显更需要自控性,不只是你,而且包括你的程序小组的每个成员。他们都必须发誓除了做资源转换外不把构造函数用在任意其他用途。幸运的是,这条规矩很容易得以加强。只需要在源文件中查找所有的new即可。</p><h3 id="1-2-3-Resource-Transfer"><a href="#1-2-3-Resource-Transfer" class="headerlink" title="1.2.3 Resource Transfer"></a>1.2.3 Resource Transfer</h3><p>到目前为止,我们所讨论的一直是生命周期在一个单独的作用域内的资源。现在我们要解决一个困难的问题——如何在不同的作用域间安全的传递资源。这一问题在当你处理容器的时候会变得十分明显。你可以动态的创建一串对象,将它们存放至一个容器中,然后将它们取出,并且在最终安排它们。为了能够让这安全的工作——没有泄露——对象需要改变其所有者。</p><p>这个问题的一个非常显而易见的解决方法是使用Smart Pointer,无论是在加入容器前还是还找到它们以后。这是他如何运作的,你加入Release方法到Smart Pointer中:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line">T * SmartPointer<T>::<span class="built_in">Release</span> ()</span><br><span class="line">{</span><br><span class="line"> T * pTmp = _p;</span><br><span class="line"> _p = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">return</span> pTmp;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意在Release调用以后,Smart Pointer就不再是对象的所有者了——它内部的指针指向空。现在,调用了Release都必须是一个负责的人并且迅速隐藏返回的指针到新的所有者对象中。在我们的例子中,容器调用了Release,比如这个Stack的例子:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Stack::Push</span> <span class="params">(SmartPointer <Item> & item)</span> <span class="title">throw</span> <span class="params">(<span class="type">char</span> *)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (_top == maxStack)</span><br><span class="line"> <span class="keyword">throw</span> <span class="string">"Stack overflow"</span>;</span><br><span class="line"> _arr [_top++] = item.<span class="built_in">Release</span> ();</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>同样的,你也可以再你的代码中用加强Release的可靠性。</p><p>相应的Pop方法要做些什么呢?他应该释放了资源并祈祷调用它的是一个负责的人而且立即作一个资源传递它到一个Smart Pointer?这听起来并不好。</p><h3 id="1-2-4-Strong-Pointers"><a href="#1-2-4-Strong-Pointers" class="headerlink" title="1.2.4 Strong Pointers"></a>1.2.4 Strong Pointers</h3><p>资源管理在内容索引(Windows NT Server上的一部分,现在是Windows 2000)上工作,并且,我对这十分满意。然后我开始想……这一方法是在这样一个完整的系统中形成的,如果可以把它内建入语言的本身岂不是一件非常好?我提出了强指针(Strong Pointer)和弱指针(Weak Pointer)。一个Strong Pointer会在许多地方和我们这个SmartPointer相似–它在超出它的作用域后会清除他所指向的对象。资源传递会以强指针赋值的形式进行。也可以有Weak Pointer存在,它们用来访问对象而不需要所有对象–比如可赋值的引用。</p><p>任何指针都必须声明为Strong或者Weak,并且语言应该来关注类型转换的规定。例如,你不可以将Weak Pointer传递到一个需要Strong Pointer的地方,但是相反却可以。Push方法可以接受一个Strong Pointer并且将它转移到Stack中的Strong Pointer的序列中。Pop方法将会返回一个Strong Pointer。把Strong Pointer的引入语言将会使垃圾回收成为历史。</p><p>这里还有一个小问题–修改C++标准几乎和竞选美国总统一样容易。当我将我的注意告诉给Bjarne Stroutrup的时候,他看我的眼神好像是我刚刚要向他借一千美元一样。</p><p>然后我突然想到一个念头。我可以自己实现Strong Pointers。毕竟,它们都很想Smart Pointers。给它们一个拷贝构造函数并重载赋值操作符并不是一个大问题。事实上,这正是标准库中的auto_ptr有的。重要的是对这些操作给出一个资源转移的语法,但是这也不是很难。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line">SmartPointer<T>::<span class="built_in">SmartPointer</span> (SmartPointer<T> & ptr)</span><br><span class="line">{</span><br><span class="line"> _p = ptr.<span class="built_in">Release</span> ();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line"><span class="type">void</span> SmartPointer<T>::<span class="keyword">operator</span> = (SmartPointer<T> & ptr)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (_p != ptr._p)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">delete</span> _p;</span><br><span class="line"> _p = ptr.<span class="built_in">Release</span> ();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使这整个想法迅速成功的原因之一是我可以以值方式传递这种封装指针!我有了我的蛋糕,并且也可以吃了。看这个Stack的新的实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Stack</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">enum</span> { maxStack = <span class="number">3</span> };</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Stack</span> ()</span><br><span class="line"> : _top (<span class="number">0</span>)</span><br><span class="line"> {}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Push</span> <span class="params">(SmartPointer<Item> & item)</span> <span class="title">throw</span> <span class="params">(<span class="type">char</span> *)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">if</span> (_top >= maxStack)</span><br><span class="line"> <span class="keyword">throw</span> <span class="string">"Stack overflow"</span>;</span><br><span class="line"> _arr [_top++] = item;</span><br><span class="line"> }</span><br><span class="line"> <span class="function">SmartPointer<Item> <span class="title">Pop</span> <span class="params">()</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">if</span> (_top == <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">SmartPointer</span><Item> ();</span><br><span class="line"> <span class="keyword">return</span> _arr [--_top];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">private</span></span><br><span class="line"> <span class="type">int</span> _top;</span><br><span class="line"> SmartPointer<Item> _arr [maxStack];</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>Pop方法强制客户将其返回值赋给一个Strong Pointer,SmartPointer<Item>。任何试图将他对一个普通指针的赋值都会产生一个编译期错误,因为类型不匹配。此外,因为Pop以值方式返回一个Strong Pointer(在Pop的声明时SmartPointer<Item>后面没有&符号),编译器在return时自动进行了一个资源转换。他调用了operator =来从数组中提取一个Item,拷贝构造函数将他传递给调用者。调用者最后拥有了指向Pop赋值的Strong Pointer指向的一个Item。</p><p>我马上意识到我已经在某些东西之上了。我开始用了新的方法重写原来的代码。</p><h3 id="1-2-5-Parser"><a href="#1-2-5-Parser" class="headerlink" title="1.2.5 Parser"></a>1.2.5 Parser</h3><p>我过去有一个老的算术操作分析器,是用老的资源管理的技术写的。分析器的作用是在分析树中生成节点,节点是动态分配的。例如分析器的Expression方法生成一个表达式节点。我没有时间用Strong Pointer去重写这个分析器。我令Expression、Term和Factor方法以传值的方式将Strong Pointer返回到Node中。看下面的Expression方法的实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">SmartPointer<Node> <span class="title">Parser::Expression</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// Parse a term</span></span><br><span class="line"> SmartPointer<Node> pNode = <span class="built_in">Term</span> ();</span><br><span class="line"> EToken token = _scanner.<span class="built_in">Token</span>();</span><br><span class="line"> <span class="keyword">if</span> ( token == tPlus || token == tMinus )</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// Expr := Term { ('+' | '-') Term }</span></span><br><span class="line"> SmartPointer<MultiNode> pMultiNode = <span class="keyword">new</span> <span class="built_in">SumNode</span> (pNode);</span><br><span class="line"> <span class="keyword">do</span></span><br><span class="line"> {</span><br><span class="line"> _scanner.<span class="built_in">Accept</span>();</span><br><span class="line"> SmartPointer<Node> pRight = <span class="built_in">Term</span> ();</span><br><span class="line"> pMultiNode-><span class="built_in">AddChild</span> (pRight, (token == tPlus));</span><br><span class="line"> token = _scanner.<span class="built_in">Token</span>();</span><br><span class="line"> } <span class="keyword">while</span> (token == tPlus || token == tMinus);</span><br><span class="line"> pNode = <span class="built_in">up_cast</span><Node, MultiNode> (pMultiNode);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// otherwise Expr := Term</span></span><br><span class="line"> <span class="keyword">return</span> pNode; <span class="comment">// by value!</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最开始,Term方法被调用。他传值返回一个指向Node的Strong Pointer并且立刻把它保存到我们自己的Strong Pointer,pNode中。如果下一个符号不是加号或者减号,我们就简单的把这个SmartPointer以值返回,这样就释放了Node的所有权。另外一方面,如果下一个符号是加号或者减号,我们创建一个新的SumMode并且立刻(直接传递)将它储存到MultiNode的一个Strong Pointer中。这里,SumNode是从MultiMode中继承而来的,而MulitNode是从Node继承而来的。原来的Node的所有权转给了SumNode。</p><p>只要是他们在被加号和减号分开的时候,我们就不断的创建terms,我们将这些term转移到我们的MultiNode中,同时MultiNode得到了所有权。最后,我们将指向MultiNode的Strong Pointer向上映射为指向Mode的Strong Pointer,并且将他返回调用着。</p><p>我们需要对Strong Pointers进行显式的向上映射,即使指针是被隐式的封装。例如,一个MultiNode是一个Node,但是相同的is-a关系在SmartPointer<MultiNode>和SmartPointer<Node>之间并不存在,因为它们是分离的类(模板实例)并不存在继承关系。up-cast模板是像下面这样定义的:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">class</span> To, <span class="keyword">class</span> From></span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> SmartPointer<To> <span class="title">up_cast</span> <span class="params">(SmartPointer<From> & from)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">SmartPointer</span><To> (from.<span class="built_in">Release</span> ());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果你的编译器支持新加入标准的成员模板(member template)的话,你可以为SmartPointer<T>定义一个新的构造函数用来从接受一个class U。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">U</span>> SmartPointer<T>::<span class="built_in">SmartPointer</span> (SPrt<U> & uptr)</span><br><span class="line"> : _p (uptr.<span class="built_in">Release</span> ())</span><br><span class="line"> {}</span><br></pre></td></tr></table></figure><p>这里的这个花招是模板在U不是T的子类的时候就不会编译成功(换句话说,只在U is-a T的时候才会编译)。这是因为uptr的缘故。Release()方法返回一个指向U的指针,并被赋值为_p,一个指向T的指针。所以如果U不是一个T的话,赋值会导致一个编译时刻错误。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">std::auto_ptr</span><br></pre></td></tr></table></figure><p>后来我意识到在STL中的auto_ptr模板,就是我的Strong Pointer。在那时候还有许多的实现差异(auto_ptr的Release方法并不将内部的指针清零–你的编译器的库很可能用的就是这种陈旧的实现),但是最后在标准被广泛接受之前都被解决了。</p><h3 id="1-2-6-Transfer-Semantics"><a href="#1-2-6-Transfer-Semantics" class="headerlink" title="1.2.6 Transfer Semantics"></a>1.2.6 Transfer Semantics</h3><p>目前为止,我们一直在讨论在C++程序中资源管理的方法。宗旨是将资源封装到一些轻量级的类中,并由类负责它们的释放。特别的是,所有用new操作符分配的资源都会被储存并传递进Strong Pointer(标准库中的auto_ptr)的内部。</p><p>这里的关键词是传递(passing)。一个容器可以通过传值返回一个Strong Pointer来安全的释放资源。容器的客户只能够通过提供一个相应的Strong Pointer来保存这个资源。任何一个将结果赋给一个”裸”指针的做法都立即会被编译器发现。</p><figure class="highlight cpp"><table><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">auto_ptr<Item> item = stack.<span class="built_in">Pop</span> (); <span class="comment">// ok</span></span><br><span class="line">Item * p = stack.<span class="built_in">Pop</span> (); <span class="comment">// Error! Type mismatch.</span></span><br></pre></td></tr></table></figure><p>以传值方式被传递的对象有value semantics 或者称为 copy semantics。Strong Pointers是以值方式传递的–但是我们能说它们有copy semantics吗?不是这样的!它们所指向的对象肯定没有被拷贝过。事实上,传递过后,源auto_ptr不在访问原有的对象,并且目标auto_ptr成为了对象的唯一拥有者(但是往往auto_ptr的旧的实现即使在释放后仍然保持着对对象的所有权)。自然而然的我们可以将这种新的行为称作Transfer Semantics。</p><p>拷贝构造函数(copy construcor)和赋值操作符定义了auto_ptr的Transfer Semantics,它们用了非const的auto_ptr引用作为它们的参数。</p><figure class="highlight cpp"><table><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">auto_ptr</span> (auto_ptr<T> & ptr);</span><br><span class="line">auto_ptr & <span class="keyword">operator</span> = (auto_ptr<T> & ptr);</span><br></pre></td></tr></table></figure><p>这是因为它们确实改变了他们的源–剥夺了对资源的所有权。</p><p>通过定义相应的拷贝构造函数和重载赋值操作符,你可以将Transfer Semantics加入到许多对象中。例如,许多Windows中的资源,比如动态建立的菜单或者位图,可以用有Transfer Semantics的类来封装。</p><h3 id="1-2-7-Strong-Vectors"><a href="#1-2-7-Strong-Vectors" class="headerlink" title="1.2.7 Strong Vectors"></a>1.2.7 Strong Vectors</h3><p>标准库只在auto_ptr中支持资源管理。甚至连最简单的容器也不支持ownership semantics。你可能想将auto_ptr和标准容器组合到一起可能会管用,但是并不是这样的。例如,你可能会这样做,但是会发现你不能够用标准的方法来进行索引。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vector< auto_ptr<Item> > autoVector;</span><br></pre></td></tr></table></figure><p>这种建造不会编译成功;</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Item * item = autoVector [<span class="number">0</span>];</span><br></pre></td></tr></table></figure><p>另一方面,这会导致一个从autoVect到auto_ptr的所有权转换:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">auto_ptr<Item> item = autoVector [<span class="number">0</span>];</span><br></pre></td></tr></table></figure><p>我们没有选择,只能够构造我们自己的Strong Vector。最小的接口应该如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">auto_vector</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">auto_vector</span> <span class="params">(<span class="type">size_t</span> capacity = <span class="number">0</span>)</span></span>;</span><br><span class="line"> T <span class="type">const</span> * <span class="keyword">operator</span> [] (<span class="type">size_t</span> i) <span class="type">const</span>;</span><br><span class="line"> T * <span class="keyword">operator</span> [] (<span class="type">size_t</span> i);</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">assign</span> <span class="params">(<span class="type">size_t</span> i, auto_ptr<T> & p)</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">assign_direct</span> <span class="params">(<span class="type">size_t</span> i, T * p)</span></span>;</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">push_back</span> <span class="params">(auto_ptr<T> & p)</span></span>;</span><br><span class="line"> <span class="function">auto_ptr<T> <span class="title">pop_back</span> <span class="params">()</span></span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>你也许会发现一个非常防御性的设计态度。我决定不提供一个对vector的左值索引的访问,取而代之,如果你想设定(set)一个值的话,你必须用assign或者assign_direct方法。我的观点是,资源管理不应该被忽视,同时,也不应该在所有的地方滥用。在我的经验里,一个strong vector经常被许多push_back方法充斥着。</p><p>Strong vector最好用一个动态的Strong Pointers的数组来实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">auto_vector</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">grow</span> <span class="params">(<span class="type">size_t</span> reqCapacity)</span></span>;</span><br><span class="line"> auto_ptr<T> *_arr;</span><br><span class="line"> <span class="type">size_t</span> _capacity;</span><br><span class="line"> <span class="type">size_t</span> _end;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>grow方法申请了一个很大的auto_ptr<T>的数组,将所有的东西从老的书组类转移出来,在其中交换,并且删除原来的数组。</p><p>auto_vector的其他实现都是十分直接的,因为所有资源管理的复杂度都在auto_ptr中。例如,assign方法简单的利用了重载的赋值操作符来删除原有的对象并转移资源到新的对象:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">assign</span> <span class="params">(<span class="type">size_t</span> i, auto_ptr<T> & p)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> _arr [i] = p;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我已经讨论了push_back和pop_back方法。push_back方法传值返回一个auto_ptr,因为它将所有权从auto_vector转换到auto_ptr中。</p><p>对auto_vector的索引访问是借助auto_ptr的get方法来实现的,get简单的返回一个内部指针。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">T * <span class="keyword">operator</span> [] (<span class="type">size_t</span> i)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">return</span> _arr [i].<span class="built_in">get</span> ();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>没有容器可以没有iterator。我们需要一个iterator让auto_vector看起来更像一个普通的指针向量。特别是,当我们废弃iterator的时候,我们需要的是一个指针而不是auto_ptr。我们不希望一个auto_vector的iterator在无意中进行资源转换。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">auto_iterator</span>: <span class="keyword">public</span></span><br><span class="line">iterator<random_access_iterator_tag, T *></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">auto_iterator</span> () : _pp (<span class="number">0</span>) {}</span><br><span class="line"> <span class="built_in">auto_iterator</span> (auto_ptr<T> * pp) : _pp (pp) {}</span><br><span class="line"> <span class="type">bool</span> <span class="keyword">operator</span> != (auto_iterator<T> <span class="type">const</span> & it) <span class="type">const</span></span><br><span class="line"> { <span class="keyword">return</span> it._pp != _pp; }</span><br><span class="line"> auto_iterator <span class="type">const</span> & <span class="keyword">operator</span>++ (<span class="type">int</span>) { <span class="keyword">return</span> _pp++; }</span><br><span class="line"> auto_iterator <span class="keyword">operator</span>++ () { <span class="keyword">return</span> ++_pp; }</span><br><span class="line"> T * <span class="keyword">operator</span> * () { <span class="keyword">return</span> _pp-><span class="built_in">get</span> (); }</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> auto_ptr<T> * _pp;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>我们给auto_vect提供了标准的begin和end方法来找回iterator:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">auto_vector</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">typedef</span> auto_iterator<T> iterator;</span><br><span class="line"> <span class="function">iterator <span class="title">begin</span> <span class="params">()</span> </span>{ <span class="keyword">return</span> _arr; }</span><br><span class="line"> <span class="function">iterator <span class="title">end</span> <span class="params">()</span> </span>{ <span class="keyword">return</span> _arr + _end; }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>你也许会问我们是否要利用资源管理重新实现每一个标准的容器?幸运的是,不;事实是strong vector解决了大部分所有权的需求。当你把你的对象都安全的放置到一个strong vector中,你可以用所有其它的容器来重新安排(weak)pointer。</p><p>设想,例如,你需要对一些动态分配的对象排序的时候。你将它们的指针保存到一个strong vector中。然后你用一个标准的vector来保存从strong vector中获得的weak指针。你可以用标准的算法对这个vector进行排序。这种中介vector叫做permutation vector。相似的,你也可以用标准的maps, priority queues, heaps, hash tables等等。</p><h3 id="1-2-8-Code-Inspection"><a href="#1-2-8-Code-Inspection" class="headerlink" title="1.2.8 Code Inspection"></a>1.2.8 Code Inspection</h3><p>如果你严格遵照资源管理的条款,你就不会再资源泄露或者两次删除的地方遇到麻烦。你也降低了访问野指针的几率。同样的,遵循原有的规则,用delete删除用new申请的德指针,不要两次删除一个指针。你也不会遇到麻烦。但是,那个是更好的注意呢?</p><p>这两个方法有一个很大的不同点。就是和寻找传统方法的bug相比,找到违反资源管理的规定要容易的多。后者仅需要一个代码检测或者一个运行测试,而前者则在代码中隐藏得很深,并需要很深的检查。</p><p>设想你要做一段传统的代码的内存泄露检查。第一件事,你要做的就是grep所有在代码中出现的new,你需要找出被分配空间地指针都作了什么。你需要确定导致删除这个指针的所有的执行路径。你需要检查break语句,过程返回,异常。原有的指针可能赋给另一个指针,你对这个指针也要做相同的事。</p><p>相比之下,对于一段用资源管理技术实现的代码。你也用grep检查所有的new,但是这次你只需要检查邻近的调用:</p><p>● 这是一个直接的Strong Pointer转换,还是我们在一个构造函数的函数体中?</p><p>● 调用的返回知是否立即保存到对象中,构造函数中是否有可以产生异常的代码。?</p><p>● 如果这样的话析构函数中时候有delete?</p><p>下一步,你需要用grep查找所有的release方法,并实施相同的检查。</p><p>不同点是需要检查、理解单个执行路径和只需要做一些本地的检验。这难道不是提醒你非结构化的和结构化的程序设计的不同吗?原理上,你可以认为你可以应付goto,并且跟踪所有的可能分支。另一方面,你可以将你的怀疑本地化为一段代码。本地化在两种情况下都是关键所在。</p><p>在资源管理中的错误模式也比较容易调试。最常见的bug是试图访问一个释放过的strong pointer。这将导致一个错误,并且很容易跟踪。</p><h3 id="1-2-9-共享的所有权"><a href="#1-2-9-共享的所有权" class="headerlink" title="1.2.9 共享的所有权"></a>1.2.9 共享的所有权</h3><p>为每一个程序中的资源都找出或者指定一个所有者是一件很容易的事情吗?答案是出乎意料的,是!如果你发现了一些问题,这可能说明你的设计上存在问题。还有另一种情况就是共享所有权是最好的甚至是唯一的选择。</p><p>共享的责任分配给被共享的对象和它的客户(client)。一个共享资源必须为它的所有者保持一个引用计数。另一方面,所有者再释放资源的时候必须通报共享对象。最后一个释放资源的需要在最后负责free的工作。</p><p>最简单的共享的实现是共享对象继承引用计数的类RefCounted:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">RefCounted</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">RefCounted</span> () : _count (<span class="number">1</span>) {}</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">GetRefCount</span> <span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> _count; }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">IncRefCount</span> <span class="params">()</span> </span>{ _count++; }</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">DecRefCount</span> <span class="params">()</span> </span>{ <span class="keyword">return</span> --_count; }</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="type">int</span> _count;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>按照资源管理,一个引用计数是一种资源。如果你遵守它,你需要释放它。当你意识到这一事实的时候,剩下的就变得简单了。简单的遵循规则–再构造函数中获得引用计数,在析构函数中释放。甚至有一个RefCounted的smart pointer等价物:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RefPtr</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">RefPtr</span> (T * p) : _p (p) {}</span><br><span class="line"> <span class="built_in">RefPtr</span> (RefPtr<T> & p)</span><br><span class="line"> {</span><br><span class="line"> _p = p._p;</span><br><span class="line"> _p-><span class="built_in">IncRefCount</span> ();</span><br><span class="line"> }</span><br><span class="line"> ~<span class="built_in">RefPtr</span> ()</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> (_p-><span class="built_in">DecRefCount</span> () == <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">delete</span> _p;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> T * _p;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>注意模板中的T不比成为RefCounted的后代,但是它必须有IncRefCount和DecRefCount的方法。当然,一个便于使用的RefPtr需要有一个重载的指针访问操作符。在RefPtr中加入转换语义学(transfer semantics)是读者的工作。</p><h3 id="1-2-10-所有权网络"><a href="#1-2-10-所有权网络" class="headerlink" title="1.2.10 所有权网络"></a>1.2.10 所有权网络</h3><p>链表是资源管理分析中的一个很有意思的例子。如果你选择表成为链(link)的所有者的话,你会陷入实现递归的所有权。每一个link都是它的继承者的所有者,并且,相应的,余下的链表的所有者。下面是用smart pointer实现的一个表单元:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Link</span></span><br><span class="line">{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> auto_ptr<Link> _next;</span><br><span class="line">};</span><br><span class="line"><span class="comment">//最好的方法是,将连接控制封装到一个弄构进行资源转换的类中。</span></span><br><span class="line"><span class="comment">//对于双链表呢?安全的做法是指明一个方向,如forward:</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">DoubleLink</span></span><br><span class="line">{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> DoubleLink *_prev;</span><br><span class="line"> auto_ptr<DoubleLink> _next;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>注意不要创建环形链表。</p><p>这给我们带来了另外一个有趣的问题–资源管理可以处理环形的所有权吗?它可以,用一个mark-and-sweep的算法。这里是实现这种方法的一个例子:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span><<span class="keyword">class</span> <span class="title class_">T</span>></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CyclPtr</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">CyclPtr</span> (T * p)</span><br><span class="line"> :_p (p), _isBeingDeleted (<span class="literal">false</span>)</span><br><span class="line"> {}</span><br><span class="line"> ~<span class="built_in">CyclPtr</span> ()</span><br><span class="line"> {</span><br><span class="line"> _isBeingDeleted = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">if</span> (!_p-><span class="built_in">IsBeingDeleted</span> ())</span><br><span class="line"> <span class="keyword">delete</span> _p;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">Set</span> <span class="params">(T * p)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> _p = p;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">bool</span> <span class="title">IsBeingDeleted</span> <span class="params">()</span> <span class="type">const</span> </span>{ <span class="keyword">return</span> _isBeingDeleted; }</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> T * _p;</span><br><span class="line"> <span class="type">bool</span> _isBeingDeleted;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>注意我们需要用class T来实现方法IsBeingDeleted,就像从CyclPtr继承。对特殊的所有权网络普通化是十分直接的。</p><p>将原有代码转换为资源管理代码</p><p>如果你是一个经验丰富的程序员,你一定会知道找资源的bug是一件浪费时间的痛苦的经历。我不必说服你和你的团队花费一点时间来熟悉资源管理是十分值得的。你可以立即开始用这个方法,无论你是在开始一个新项目或者是在一个项目的中期。转换不必立即全部完成。下面是步骤。</p><p>(1)首先,在你的工程中建立基本的Strong Pointer。然后通过查找代码中的new来开始封装裸指针。</p><p>(2)最先封装的是在过程中定义的临时指针。简单的将它们替换为auto_ptr并且删除相应的delete。如果一个指针在过程中没有被删除而是被返回,用auto_ptr替换并在返回前调用release方法。在你做第二次传递的时候,你需要处理对release的调用。注意,即使是在这点,你的代码也可能更加”精力充沛”–你会移出代码中潜在的资源泄漏问题。</p><p>(3)下面是指向资源的裸指针。确保它们被独立的封装到auto_ptr中,或者在构造函数中分配在析构函数中释放。如果你有传递所有权的行为的话,需要调用release方法。如果你有容器所有对象,用Strong Pointers重新实现它们。</p><p>(4)接下来,找到所有对release的方法调用并且尽力清除所有,如果一个release调用返回一个指针,将它修改传值返回一个auto_ptr。</p><p>(5)重复着一过程,直到最后所有new和release的调用都在构造函数或者资源转换的时候发生。这样,你在你的代码中处理了资源泄漏的问题。对其他资源进行相似的操作。</p><p>(6)你会发现资源管理清除了许多错误和异常处理带来的复杂性。不仅仅你的代码会变得精力充沛,它也会变得简单并容易维护。</p><h1 id="2-内存泄漏"><a href="#2-内存泄漏" class="headerlink" title="2. 内存泄漏"></a>2. 内存泄漏</h1><h2 id="2-1-C-中动态内存分配引发问题的解决方案"><a href="#2-1-C-中动态内存分配引发问题的解决方案" class="headerlink" title="2.1 C++中动态内存分配引发问题的解决方案"></a>2.1 C++中动态内存分配引发问题的解决方案</h2><p>假设我们要开发一个String类,它可以方便地处理字符串数据。我们可以在类中声明一个数组,考虑到有时候字符串极长,我们可以把数组大小设为200,但一般的情况下又不需要这么多的空间,这样是浪费了内存。对了,我们可以使用new操作符,这样是十分灵活的,但在类中就会出现许多意想不到的问题,本文就是针对这一现象而写的。现在,我们先来开发一个String类,但它是一个不完善的类。的确,我们要刻意地使它出现各种各样的问题,这样才好对症下药。好了,我们开始吧!</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* String.h */</span></span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span> STRING_H_</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> STRING_H_</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">String</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> <span class="type">char</span> * str; <span class="comment">//存储数据</span></span><br><span class="line"> <span class="type">int</span> len; <span class="comment">//字符串长度</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">String</span>(<span class="type">const</span> <span class="type">char</span> * s); <span class="comment">//构造函数</span></span><br><span class="line"> <span class="built_in">String</span>(); <span class="comment">// 默认构造函数</span></span><br><span class="line"> ~<span class="built_in">String</span>(); <span class="comment">// 析构函数</span></span><br><span class="line"> <span class="keyword">friend</span> ostream & <span class="keyword">operator</span><<(ostream & os,<span class="type">const</span> String& st);</span><br><span class="line">};</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*String.cpp*/</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <iostream></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <cstring></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"String.h"</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line">String::<span class="built_in">String</span>(<span class="type">const</span> <span class="type">char</span> * s)</span><br><span class="line">{</span><br><span class="line"> len = <span class="built_in">strlen</span>(s);</span><br><span class="line"> str = <span class="keyword">new</span> <span class="type">char</span>[len + <span class="number">1</span>];</span><br><span class="line"> <span class="built_in">strcpy</span>(str, s);</span><br><span class="line">}<span class="comment">//拷贝数据</span></span><br><span class="line">String::<span class="built_in">String</span>()</span><br><span class="line">{</span><br><span class="line"> len =<span class="number">0</span>;</span><br><span class="line"> str = <span class="keyword">new</span> <span class="type">char</span>[len+<span class="number">1</span>];</span><br><span class="line"> str[<span class="number">0</span>]=<span class="string">'"0'</span>;</span><br><span class="line">}</span><br><span class="line">String::~<span class="built_in">String</span>()</span><br><span class="line">{</span><br><span class="line"> cout<<<span class="string">"这个字符串将被删除:"</span><<str<<<span class="string">'"n'</span>;<span class="comment">//为了方便观察结果,特留此行代码。</span></span><br><span class="line"> <span class="keyword">delete</span> [] str;</span><br><span class="line">}</span><br><span class="line">ostream & <span class="keyword">operator</span><<(ostream & os, <span class="type">const</span> String & st)</span><br><span class="line">{</span><br><span class="line"> os << st.str;</span><br><span class="line"> <span class="keyword">return</span> os;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/*test_right.cpp*/</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <iostream></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <stdlib.h></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"String.h"</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> cout<<temp<<<span class="string">'"n'</span>;</span><br><span class="line"> <span class="built_in">system</span>(<span class="string">"PAUSE"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果:</p><figure class="highlight bash"><table><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><br><span class="line">请按任意键继续. . .</span><br></pre></td></tr></table></figure><p>大家可以看到,以上程序十分正确,而且也是十分有用的。可是,我们不能被表面现象所迷惑!下面,请大家用test_String.cpp文件替换test_right.cpp文件进行编译,看看结果。有的编译器可能就是根本不能进行编译!</p><p>test_String.cpp:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <iostream></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <stdlib.h></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"String.h"</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">show_right</span><span class="params">(<span class="type">const</span> String&)</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">show_String</span><span class="params">(<span class="type">const</span> String)</span></span>;<span class="comment">//注意,参数非引用,而是按值传递。</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="function">String <span class="title">test1</span><span class="params">(<span class="string">"第一个范例。"</span>)</span></span>;</span><br><span class="line"> <span class="function">String <span class="title">test2</span><span class="params">(<span class="string">"第二个范例。"</span>)</span></span>;</span><br><span class="line"> <span class="function">String <span class="title">test3</span><span class="params">(<span class="string">"第三个范例。"</span>)</span></span>;</span><br><span class="line"> <span class="function">String <span class="title">test4</span><span class="params">(<span class="string">"第四个范例。"</span>)</span></span>;</span><br><span class="line"> cout<<<span class="string">"下面分别输入三个范例:"</span>n<span class="string">";</span></span><br><span class="line"><span class="string"> cout<<test1<<endl;</span></span><br><span class="line"><span class="string"> cout<<test2<<endl;</span></span><br><span class="line"><span class="string"> cout<<test3<<endl;</span></span><br><span class="line"><span class="string"> String* String1=new String(test1);</span></span><br><span class="line"><span class="string"> cout<<*String1<<endl;</span></span><br><span class="line"><span class="string"> delete String1;</span></span><br><span class="line"><span class="string"> cout<<test1<<endl; //在Dev-cpp上没有任何反应。</span></span><br><span class="line"><span class="string"> cout<<"</span>使用正确的函数:<span class="string">"<<endl;</span></span><br><span class="line"><span class="string"> show_right(test2);</span></span><br><span class="line"><span class="string"> cout<<test2<<endl;</span></span><br><span class="line"><span class="string"> cout<<"</span>使用错误的函数:<span class="string">"<<endl;</span></span><br><span class="line"><span class="string"> show_String(test2);</span></span><br><span class="line"><span class="string"> cout<<test2<<endl; //这一段代码出现严重的错误!</span></span><br><span class="line"><span class="string"> String String2(test3);</span></span><br><span class="line"><span class="string"> cout<<"</span>String2: <span class="string">"<<String2<<endl;</span></span><br><span class="line"><span class="string"> String String3;</span></span><br><span class="line"><span class="string"> String3=test4;</span></span><br><span class="line"><span class="string"> cout<<"</span>String3: <span class="string">"<<String3<<endl;</span></span><br><span class="line"><span class="string"> cout<<"</span>下面,程序结束,析构函数将被调用。<span class="string">"<<endl;</span></span><br><span class="line"><span class="string"> return 0;</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string">void show_right(const String& a)</span></span><br><span class="line"><span class="string">{</span></span><br><span class="line"><span class="string"> cout<<a<<endl;</span></span><br><span class="line"><span class="string">}</span></span><br><span class="line"><span class="string">void show_String(const String a)</span></span><br><span class="line"><span class="string">{</span></span><br><span class="line"><span class="string"> cout<<a<<endl;</span></span><br><span class="line"><span class="string">}</span></span><br></pre></td></tr></table></figure><p>运行结果:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">下面分别输入三个范例:</span><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><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><span class="line">第二个范例。</span><br><span class="line">这个字符串将被删除:第二个范例。</span><br><span class="line">这个字符串将被删除:?=</span><br><span class="line">?=</span><br><span class="line">String2: 第三个范例。</span><br><span class="line">String3: 第四个范例。</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">这个字符串将被删除:x =</span><br><span class="line">这个字符串将被删除:?=</span><br><span class="line">这个字符串将被删除:</span><br></pre></td></tr></table></figure><p>现在,请大家自己试试运行结果,或许会更加惨不忍睹呢!下面,我为大家一一分析原因。</p><p>首先,大家要知道,C++类有以下这些极为重要的函数:</p><p>一:复制构造函数。</p><p>二:赋值函数。</p><p>我们先来讲复制构造函数。什么是复制构造函数呢?比如,我们可以写下这样的代码:String test1(test2);这是进行初始化。我们知道,初始化对象要用构造函数。可这儿呢?按理说,应该有声明为这样的构造函数:String(const String &);可是,我们并没有定义这个构造函数呀?答案是,C++提供了默认的复制构造函数,问题也就出在这儿。</p><p>(1):什么时候会调用复制构造函数呢?(以String类为例。)</p><p>在我们提供这样的代码:String test1(test2)时,它会被调用;当函数的参数列表为按值传递,也就是没有用引用和指针作为类型时,如:void show_String(const String),它会被调用。其实,还有一些情况,但在这儿就不列举了。</p><p>(2):它是什么样的函数。</p><p>它的作用就是把两个类进行复制。拿String类为例,C++提供的默认复制构造函数是这样的:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">String</span>(<span class="type">const</span> String& a)</span><br><span class="line">{</span><br><span class="line"> str=a.str;</span><br><span class="line"> len=a.len;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在平时,这样并不会有任何的问题出现,但我们用了new操作符,涉及到了动态内存分配,我们就不得不谈谈浅复制和深复制了。以上的函数就是实行的浅复制,它只是复制了指针,而并没有复制指针指向的数据,可谓一点儿用也没有。打个比方吧!就像一个朋友让你把一个程序通过网络发给他,而你大大咧咧地把快捷方式发给了他,有什么用处呢?我们来具体谈谈:</p><p>假如,A对象中存储了这样的字符串:“C++”。它的地址为2000。现在,我们把A对象赋给B对象:String B=A。现在,A和B对象的str指针均指向2000地址。看似可以使用,但如果B对象的析构函数被调用时,则地址2000处的字符串“C++”已经被从内存中抹去,而A对象仍然指向地址2000。这时,如果我们写下这样的代码:cout<<A<<endl;或是等待程序结束,A对象的析构函数被调用时,A对象的数据能否显示出来呢?只会是乱码。而且,程序还会这样做:连续对地址2000处使用两次delete操作符,这样的后果是十分严重的!</p><p>本例中,有这样的代码:</p><figure class="highlight cpp"><table><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">String* String1=<span class="keyword">new</span> <span class="built_in">String</span>(test1);</span><br><span class="line">cout<<*String1<<endl;</span><br><span class="line"><span class="keyword">delete</span> String1;</span><br></pre></td></tr></table></figure><p>假设test1中str指向的地址为2000,而String中str指针同样指向地址2000,我们删除了2000处的数据,而test1对象呢?已经被破坏了。大家从运行结果上可以看到,我们使用cout<<test1时,一点反应也没有。而在test1的析构函数被调用时,显示是这样:“这个字符串将被删除:”。</p><p>再看看这段代码:</p><figure class="highlight cpp"><table><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">cout<<<span class="string">"使用错误的函数"</span><<endl;</span><br><span class="line"><span class="built_in">show_String</span>(test2);</span><br><span class="line">cout<<test2<<endl;<span class="comment">//这一段代码出现严重的错误!</span></span><br></pre></td></tr></table></figure><p>show_String函数的参数列表void show_String(const String a)是按值传递的,所以,我们相当于执行了这样的代码:String a=test2;函数执行完毕,由于生存周期的缘故,对象a被析构函数删除,我们马上就可以看到错误的显示结果了:这个字符串将被删除:?=。当然,test2也被破坏了。解决的办法很简单,当然是手工定义一个复制构造函数喽!人力可以胜天!</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">String::<span class="built_in">String</span>(<span class="type">const</span> String& a)</span><br><span class="line">{</span><br><span class="line"> len=a.len;</span><br><span class="line"> str=<span class="keyword">new</span> <span class="built_in">char</span>(len+<span class="number">1</span>);</span><br><span class="line"> <span class="built_in">strcpy</span>(str,a.str);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们执行的是深复制。这个函数的功能是这样的:假设对象A中的str指针指向地址2000,内容为“I am a C++ Boy!”。我们执行代码String B=A时,我们先开辟出一块内存,假设为3000。我们用strcpy函数将地址2000的内容拷贝到地址3000中,再将对象B的str指针指向地址3000。这样,就互不干扰了。</p><p>大家把这个函数加入程序中,问题就解决了大半,但还没有完全解决,问题在赋值函数上。我们的程序中有这样的段代码:</p><figure class="highlight cpp"><table><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">String String3;</span><br><span class="line">String3=test4;</span><br></pre></td></tr></table></figure><p>经过我前面的讲解,大家应该也会对这段代码进行寻根摸底:凭什么可以这样做:String3=test4???原因是,C++为了用户的方便,提供的这样的一个操作符重载函数:operator=。所以,我们可以这样做。大家应该猜得到,它同样是执行了浅复制,出了同样的毛病。比如,执行了这段代码后,析构函数开始大展神威^_^。由于这些变量是后进先出的,所以最后的String3变量先被删除:这个字符串将被删除:第四个范例。很正常。最后,删除到test4的时候,问题来了:这个字符串将被删除:?=。原因我不用赘述了,只是这个赋值函数怎么写,还有一点儿学问呢!大家请看:</p><p>平时,我们可以写这样的代码:x=y=z。(均为整型变量。)而在类对象中,我们同样要这样,因为这很方便。而对象A=B=C就是A.operator=(B.operator=(c))。而这个operator=函数的参数列表应该是:const String& a,所以,大家不难推出,要实现这样的功能,返回值也要是String&,这样才能实现A=B=C。我们先来写写看:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">String& String::<span class="keyword">operator</span>=(<span class="type">const</span> String& a)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">delete</span> [] str;<span class="comment">//先删除自身的数据</span></span><br><span class="line"> len=a.len;</span><br><span class="line"> str=<span class="keyword">new</span> <span class="type">char</span>[len+<span class="number">1</span>];</span><br><span class="line"> <span class="built_in">strcpy</span>(str,a.str);<span class="comment">//此三行为进行拷贝</span></span><br><span class="line"> <span class="keyword">return</span> *<span class="keyword">this</span>;<span class="comment">//返回自身的引用</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>是不是这样就行了呢?我们假如写出了这种代码:A=A,那么大家看看,岂不是把A对象的数据给删除了吗?这样可谓引发一系列的错误。所以,我们还要检查是否为自身赋值。只比较两对象的数据是不行了,因为两个对象的数据很有可能相同。我们应该比较地址。以下是完好的赋值函数:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">String& String::<span class="keyword">operator</span>=(<span class="type">const</span> String& a)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>==&a)</span><br><span class="line"> <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line"> <span class="keyword">delete</span> [] str;</span><br><span class="line"> len=a.len;</span><br><span class="line"> str=<span class="keyword">new</span> <span class="type">char</span>[len+<span class="number">1</span>];</span><br><span class="line"> <span class="built_in">strcpy</span>(str,a.str);</span><br><span class="line"> <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>把这些代码加入程序,问题就完全解决,下面是运行结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><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><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><span class="line"> 使用错误的函数:</span><br><span class="line">第二个范例。</span><br><span class="line">这个字符串将被删除:第二个范例。</span><br><span class="line">第二个范例。</span><br><span class="line">String2: 第三个范例。</span><br><span class="line">String3: 第四个范例。</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><span class="line">这个字符串将被删除:第二个范例。</span><br><span class="line">这个字符串将被删除:第一个范例。</span><br></pre></td></tr></table></figure><h2 id="2-2-如何对付内存泄漏?"><a href="#2-2-如何对付内存泄漏?" class="headerlink" title="2.2 如何对付内存泄漏?"></a>2.2 如何对付内存泄漏?</h2><p>写出那些不会导致任何内存泄漏的代码。很明显,当你的代码中到处充满了new 操作、delete操作和指针运算的话,你将会在某个地方搞晕了头,导致内存泄漏,指针引用错误,以及诸如此类的问题。这和你如何小心地对待内存分配工作其实完全没有关系:代码的复杂性最终总是会超过你能够付出的时间和努力。于是随后产生了一些成功的技巧,它们依赖于将内存分配(allocations)与重新分配(deallocation)工作隐藏在易于管理的类型之后。标准容器(standard containers)是一个优秀的例子。它们不是通过你而是自己为元素管理内存,从而避免了产生糟糕的结果。想象一下,没有string和vector的帮助,写出这个:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><vector></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><string></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><algorithm></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> <span class="comment">// small program messing around with strings</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> cout << <span class="string">"enter some whitespace-separated words:"</span>n<span class="string">";</span></span><br><span class="line"><span class="string"> vector<string> v;</span></span><br><span class="line"><span class="string"> string s;</span></span><br><span class="line"><span class="string"> while (cin>>s) v.push_back(s);</span></span><br><span class="line"><span class="string"> sort(v.begin(),v.end());</span></span><br><span class="line"><span class="string"> string cat;</span></span><br><span class="line"><span class="string"> typedef vector<string>::const_iterator Iter;</span></span><br><span class="line"><span class="string"> for (Iter p = v.begin(); p!=v.end(); ++p) cat += *p+"</span>+<span class="string">";</span></span><br><span class="line"><span class="string"> cout << cat << ’"</span>n’;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>你有多少机会在第一次就得到正确的结果?你又怎么知道你没有导致内存泄漏呢?</p><p>注意,没有出现显式的内存管理,宏,造型,溢出检查,显式的长度限制,以及指针。通过使用函数对象和标准算法(standard algorithm),我可以避免使用指针——例如使用迭代子(iterator),不过对于一个这么小的程序来说有点小题大作了。</p><p>这些技巧并不完美,要系统化地使用它们也并不总是那么容易。但是,应用它们产生了惊人的差异,而且通过减少显式的内存分配与重新分配的次数,你甚至可以使余下的例子更加容易被跟踪。早在1981年,我就指出,通过将我必须显式地跟踪的对象的数量从几万个减少到几打,为了使程序正确运行而付出的努力从可怕的苦工,变成了应付一些可管理的对象,甚至更加简单了。</p><p>如果你的程序还没有包含将显式内存管理减少到最小限度的库,那么要让你程序完成和正确运行的话,最快的途径也许就是先建立一个这样的库。</p><p>模板和标准库实现了容器、资源句柄以及诸如此类的东西,更早的使用甚至在多年以前。异常的使用使之更加完善。</p><p>如果你实在不能将内存分配/重新分配的操作隐藏到你需要的对象中时,你可以使用资源句柄(resource handle),以将内存泄漏的可能性降至最低。这里有个例子:我需要通过一个函数,在空闲内存中建立一个对象并返回它。这时候可能忘记释放这个对象。毕竟,我们不能说,仅仅关注当这个指针要被释放的时候,谁将负责去做。使用资源句柄,这里用了标准库中的auto_ptr,使需要为之负责的地方变得明确了。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><memory></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">S</span> {</span><br><span class="line"> <span class="built_in">S</span>() { cout << <span class="string">"make an S"</span>n<span class="string">"; }</span></span><br><span class="line"><span class="string"> ~S() { cout << "</span>destroy an S<span class="string">"n"</span>; }</span><br><span class="line"> <span class="built_in">S</span>(<span class="type">const</span> S&) { cout << <span class="string">"copy initialize an S"</span>n<span class="string">"; }</span></span><br><span class="line"><span class="string"> S& operator=(const S&) { cout << "</span>copy assign an S<span class="string">"n"</span>; }</span><br><span class="line">};</span><br><span class="line"><span class="function">S* <span class="title">f</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> S; <span class="comment">// 谁该负责释放这个S?</span></span><br><span class="line">};</span><br><span class="line"><span class="function">auto_ptr<S> <span class="title">g</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">auto_ptr</span><S>(<span class="keyword">new</span> S); <span class="comment">// 显式传递负责释放这个S</span></span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> cout << <span class="string">"start main"</span>n<span class="string">";</span></span><br><span class="line"><span class="string"> S* p = f();</span></span><br><span class="line"><span class="string"> cout << "</span><span class="function">after <span class="title">f</span><span class="params">()</span> before <span class="title">g</span><span class="params">()</span>"n"</span>;</span><br><span class="line"> <span class="comment">// S* q = g(); // 将被编译器捕捉</span></span><br><span class="line"> auto_ptr<S> q = <span class="built_in">g</span>();</span><br><span class="line"> cout << <span class="string">"exit main"</span>n<span class="string">";</span></span><br><span class="line"><span class="string"> // *p产生了内存泄漏</span></span><br><span class="line"><span class="string"> // *q被自动释放</span></span><br><span class="line"><span class="string">}</span></span><br></pre></td></tr></table></figure><p>在更一般的意义上考虑资源,而不仅仅是内存。</p><p>如果在你的环境中不能系统地应用这些技巧(例如,你必须使用别的地方的代码,或者你的程序的另一部分简直是原始人类(译注:原文是Neanderthals,尼安德特人,旧石器时代广泛分布在欧洲的猿人)写的,如此等等),那么注意使用一个内存泄漏检测器作为开发过程的一部分,或者插入一个垃圾收集器(garbage collector)。</p><h2 id="2-3浅谈C-C-内存泄漏及其检测工具"><a href="#2-3浅谈C-C-内存泄漏及其检测工具" class="headerlink" title="2.3浅谈C/C++内存泄漏及其检测工具"></a>2.3浅谈C/C++内存泄漏及其检测工具</h2><p>对于一个c/c++程序员来说,内存泄漏是一个常见的也是令人头疼的问题。已经有许多技术被研究出来以应对这个问题,比如Smart Pointer,Garbage Collection等。Smart Pointer技术比较成熟,STL中已经包含支持Smart Pointer的class,但是它的使用似乎并不广泛,而且它也不能解决所有的问题;Garbage Collection技术在Java中已经比较成熟,但是在c/c++领域的发展并不顺畅,虽然很早就有人思考在C++中也加入GC的支持。现实世界就是这样的,作为一个c/c++程序员,内存泄漏是你心中永远的痛。不过好在现在有许多工具能够帮助我们验证内存泄漏的存在,找出发生问题的代码。</p><h3 id="2-3-1-内存泄漏的定义"><a href="#2-3-1-内存泄漏的定义" class="headerlink" title="2.3.1 内存泄漏的定义"></a>2.3.1 内存泄漏的定义</h3><p>一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。以下这段小程序演示了堆内存发生泄漏的情形:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">MyFunction</span><span class="params">(<span class="type">int</span> nSize)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span>* p= <span class="keyword">new</span> <span class="type">char</span>[nSize];</span><br><span class="line"> <span class="keyword">if</span>( !<span class="built_in">GetStringFrom</span>( p, nSize ) ){</span><br><span class="line"> <span class="built_in">MessageBox</span>(“Error”);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> …<span class="comment">//using the string pointed by p;</span></span><br><span class="line"> <span class="keyword">delete</span> p;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当函数GetStringFrom()返回零的时候,指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存,但是c函数可以在任何地方退出,所以一旦有某个出口处没有释放应该释放的内存,就会发生内存泄漏。</p><p>广义的说,内存泄漏不仅仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),比如核心态HANDLE,GDI Object,SOCKET, Interface等,从根本上说这些由操作系统分配的对象也消耗内存,如果这些对象发生泄漏最终也会导致内存的泄漏。而且,某些对象消耗的是核心态内存,这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下,系统资源的泄漏比堆内存的泄漏更为严重。</p><p>GDI Object的泄漏是一种常见的资源泄漏:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">CMyView::OnPaint</span><span class="params">( CDC* pDC )</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> CBitmap bmp;</span><br><span class="line"> CBitmap* pOldBmp;</span><br><span class="line"> bmp.<span class="built_in">LoadBitmap</span>(IDB_MYBMP);</span><br><span class="line"> pOldBmp = pDC-><span class="built_in">SelectObject</span>( &bmp );</span><br><span class="line"> …</span><br><span class="line"> <span class="keyword">if</span>( <span class="built_in">Something</span>() ){</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> pDC-><span class="built_in">SelectObject</span>( pOldBmp );</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当函数Something()返回非零的时候,程序在退出前没有把pOldBmp选回pDC中,这会导致pOldBmp指向的HBITMAP对象发生泄漏。这个程序如果长时间的运行,可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来,因为Win9x的GDI堆比Win2k或NT的要小很多。</p><h3 id="2-3-2-内存泄漏的发生方式"><a href="#2-3-2-内存泄漏的发生方式" class="headerlink" title="2.3.2 内存泄漏的发生方式"></a>2.3.2 内存泄漏的发生方式</h3><p>以发生的方式来分类,内存泄漏可以分为4类:</p><ol><li><p>常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回True,那么pOldBmp指向的HBITMAP对象总是发生泄漏。</p></li><li><p>偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二,如果Something()函数只有在特定环境下才返回True,那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。</p></li><li><p>一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,但是因为这个类是一个Singleton,所以内存泄漏只会发生一次。另一个例子:</p></li></ol><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span>* g_lpszFileName = <span class="literal">NULL</span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">SetFileName</span><span class="params">( <span class="type">const</span> <span class="type">char</span>* lpcszFileName )</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span>( g_lpszFileName ){</span><br><span class="line"> <span class="built_in">free</span>( g_lpszFileName );</span><br><span class="line"> }</span><br><span class="line"> g_lpszFileName = <span class="built_in">strdup</span>( lpcszFileName );</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果程序在结束的时候没有释放g_lpszFileName指向的字符串,那么,即使多次调用SetFileName(),总会有一块内存,而且仅有一块内存发生泄漏。</p><ol start="4"><li>隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。举一个例子:</li></ol><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Connection</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Connection</span>( SOCKET s);</span><br><span class="line"> ~<span class="built_in">Connection</span>();</span><br><span class="line"> …</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> SOCKET _socket;</span><br><span class="line"> …</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConnectionManager</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">ConnectionManager</span>(){}</span><br><span class="line"> ~<span class="built_in">ConnectionManager</span>(){</span><br><span class="line"> list::iterator it;</span><br><span class="line"> <span class="keyword">for</span>( it = _connlist.<span class="built_in">begin</span>(); it != _connlist.<span class="built_in">end</span>(); ++it ){</span><br><span class="line"> <span class="keyword">delete</span> (*it);</span><br><span class="line"> }</span><br><span class="line"> _connlist.<span class="built_in">clear</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">OnClientConnected</span><span class="params">( SOCKET s )</span></span>{</span><br><span class="line"> Connection* p = <span class="keyword">new</span> <span class="built_in">Connection</span>(s);</span><br><span class="line"> _connlist.<span class="built_in">push_back</span>(p);</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">OnClientDisconnected</span><span class="params">( Connection* pconn )</span></span>{</span><br><span class="line"> _connlist.<span class="built_in">remove</span>( pconn );</span><br><span class="line"> <span class="keyword">delete</span> pconn;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line"> list _connlist;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>假设在Client从Server端断开后,Server并没有呼叫OnClientDisconnected()函数,那么代表那次连接的Connection对象就不会被及时的删除(在Server程序退出的时候,所有Connection对象会在ConnectionManager的析构函数里被删除)。当不断的有连接建立、断开时隐式内存泄漏就发生了。</p><p>从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。</p><h3 id="2-3-3-检测内存泄漏"><a href="#2-3-3-检测内存泄漏" class="headerlink" title="2.3.3 检测内存泄漏"></a>2.3.3 检测内存泄漏</h3><p>检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数,我们就能跟踪每一块内存的生命周期,比如,每当成功的分配一块内存后,就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这样,当程序结束的时候,list中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理,详细的算法可以参见Steve Maguire的<<Writing Solid Code>>。</p><p>如果要检测堆内存的泄漏,那么需要截获住malloc/realloc/free和new/delete就可以了(其实new/delete最终也是用malloc/free的,所以只要截获前面一组即可)。对于其他的泄漏,可以采用类似的方法,截获住相应的分配和释放函数。比如,要检测BSTR的泄漏,就需要截获SysAllocString/SysFreeString;要检测HMENU的泄漏,就需要截获CreateMenu/ DestroyMenu。(有的资源的分配函数有多个,释放函数只有一个,比如,SysAllocStringLen也可以用来分配BSTR,这时就需要截获多个分配函数)</p><p>在Windows平台下,检测内存泄漏的工具常用的一般有三种,MS C-Runtime Library内建的检测功能;外挂式的检测工具,诸如,Purify,BoundsChecker等;利用Windows NT自带的Performance Monitor。这三种工具各有优缺点,MS C-Runtime Library虽然功能上较之外挂式的工具要弱,但是它是免费的;Performance Monitor虽然无法标示出发生问题的代码,但是它能检测出隐式的内存泄漏的存在,这是其他两类工具无能为力的地方。</p><p>以下我们详细讨论这三种检测工具:</p><h4 id="2-3-3-1-VC下内存泄漏的检测方法"><a href="#2-3-3-1-VC下内存泄漏的检测方法" class="headerlink" title="2.3.3.1 VC下内存泄漏的检测方法"></a>2.3.3.1 VC下内存泄漏的检测方法</h4><p>用MFC开发的应用程序,在DEBUG版模式下编译后,都会自动加入内存泄漏的检测代码。在程序结束后,如果发生了内存泄漏,在Debug窗口中会显示出所有发生泄漏的内存块的信息,以下两行显示了一块被泄漏的内存块的信息:</p><p>E:”TestMemLeak”TestDlg.cpp(70) : {59} normal block at 0x00881710, 200 bytes long.</p><p>Data: <abcdefghijklmnop> 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70</p><p>第一行显示该内存块由TestDlg.cpp文件,第70行代码分配,地址在0x00881710,大小为200字节,{59}是指调用内存分配函数的Request Order,关于它的详细信息可以参见MSDN中_CrtSetBreakAlloc()的帮助。第二行显示该内存块前16个字节的内容,尖括号内是以ASCII方式显示,接着的是以16进制方式显示。</p><p>一般大家都误以为这些内存泄漏的检测功能是由MFC提供的,其实不然。MFC只是封装和利用了MS C-Runtime Library的Debug Function。非MFC程序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检测功能。MS C-Runtime Library在实现malloc/free,strdup等函数时已经内建了内存泄漏的检测功能。</p><p>注意观察一下由MFC Application Wizard生成的项目,在每一个cpp文件的头部都有这样一段宏定义:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifdef</span> _DEBUG</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> new DEBUG_NEW</span></span><br><span class="line"><span class="meta">#<span class="keyword">undef</span> THIS_FILE</span></span><br><span class="line"><span class="type">static</span> <span class="type">char</span> THIS_FILE[] = __FILE__;</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>有了这样的定义,在编译DEBUG版时,出现在这个cpp文件中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢?DEBUG_NEW也是一个宏,以下摘自afx.h,1632行</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> DEBUG_NEW new(THIS_FILE, __LINE__)</span></span><br></pre></td></tr></table></figure><p>所以如果有这样一行代码:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span>* p = <span class="keyword">new</span> <span class="type">char</span>[<span class="number">200</span>];</span><br></pre></td></tr></table></figure><p>经过宏替换就变成了:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span>* p = <span class="built_in">new</span>( THIS_FILE, __LINE__)<span class="type">char</span>[<span class="number">200</span>];</span><br></pre></td></tr></table></figure><p>根据C++的标准,对于以上的new的使用方法,编译器会去找这样定义的operator new:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span>* <span class="keyword">operator</span> <span class="title">new</span><span class="params">(<span class="type">size_t</span>, LPCSTR, <span class="type">int</span>)</span></span></span><br></pre></td></tr></table></figure><p>我们在afxmem.cpp 63行找到了一个这样的operator new 的实现</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span>* AFX_CDECL <span class="keyword">operator</span> <span class="title">new</span><span class="params">(<span class="type">size_t</span> nSize, LPCSTR lpszFileName, <span class="type">int</span> nLine)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">return</span> ::<span class="keyword">operator</span> <span class="built_in">new</span>(nSize, _NORMAL_BLOCK, lpszFileName, nLine);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span>* __cdecl <span class="keyword">operator</span> <span class="title">new</span><span class="params">(<span class="type">size_t</span> nSize, <span class="type">int</span> nType, LPCSTR lpszFileName, <span class="type">int</span> nLine)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> …</span><br><span class="line"> pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine);</span><br><span class="line"> <span class="keyword">if</span> (pResult != <span class="literal">NULL</span>)</span><br><span class="line"> <span class="keyword">return</span> pResult;</span><br><span class="line"> …</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>第二个operator new函数比较长,为了简单期间,我只摘录了部分。很显然最后的内存分配还是通过_malloc_dbg函数实现的,这个函数属于MS C-Runtime Library 的Debug Function。这个函数不但要求传入内存的大小,另外还有文件名和行号两个参数。文件名和行号就是用来记录此次分配是由哪一段代码造成的。如果这块内存在程序结束之前没有被释放,那么这些信息就会输出到Debug窗口里。</p><p>这里顺便提一下THIS_FILE,__FILE和__LINE__。__FILE__和__LINE__都是编译器定义的宏。当碰到__FILE__时,编译器会把__FILE__替换成一个字符串,这个字符串就是当前在编译的文件的路径名。当碰到__LINE__时,编译器会把__LINE__替换成一个数字,这个数字就是当前这行代码的行号。在DEBUG_NEW的定义中没有直接使用__FILE__,而是用了THIS_FILE,其目的是为了减小目标文件的大小。假设在某个cpp文件中有100处使用了new,如果直接使用__FILE__,那编译器会产生100个常量字符串,这100个字符串都是飧?/SPAN>cpp文件的路径名,显然十分冗余。如果使用THIS_FILE,编译器只会产生一个常量字符串,那100处new的调用使用的都是指向常量字符串的指针。</p><p>再次观察一下由MFC Application Wizard生成的项目,我们会发现在cpp文件中只对new做了映射,如果你在程序中直接使用malloc函数分配内存,调用malloc的文件名和行号是不会被记录下来的。如果这块内存发生了泄漏,MS C-Runtime Library仍然能检测到,但是当输出这块内存块的信息,不会包含分配它的的文件名和行号。</p><p>要在非MFC程序中打开内存泄漏的检测功能非常容易,你只要在程序的入口处加入以下几行代码:</p><figure class="highlight cpp"><table><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">int</span> tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );</span><br><span class="line">tmpFlag |= _CRTDBG_LEAK_CHECK_DF;</span><br><span class="line">_CrtSetDbgFlag( tmpFlag );</span><br></pre></td></tr></table></figure><p>这样,在程序结束的时候,也就是winmain,main或dllmain函数返回之后,如果还有内存块没有释放,它们的信息会被打印到Debug窗口里。</p><p>如果你试着创建了一个非MFC应用程序,而且在程序的入口处加入了以上代码,并且故意在程序中不释放某些内存块,你会在Debug窗口里看到以下的信息:</p><figure class="highlight cpp"><table><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="number">47</span>} normal block at <span class="number">0x00C91C90</span>, <span class="number">200</span> bytes <span class="type">long</span>.</span><br><span class="line">Data: < > <span class="number">00</span> <span class="number">01</span> <span class="number">02</span> <span class="number">03</span> <span class="number">04</span> <span class="number">05</span> <span class="number">06</span> <span class="number">07</span> <span class="number">08</span> <span class="number">09</span> <span class="number">0</span>A <span class="number">0B</span> <span class="number">0</span>C <span class="number">0</span>D <span class="number">0</span>E <span class="number">0F</span></span><br></pre></td></tr></table></figure><p>内存泄漏的确检测到了,但是和上面MFC程序的例子相比,缺少了文件名和行号。对于一个比较大的程序,没有这些信息,解决问题将变得十分困难。</p><p>为了能够知道泄漏的内存块是在哪里分配的,你需要实现类似MFC的映射功能,把new,maolloc等函数映射到_malloc_dbg函数上。这里我不再赘述,你可以参考MFC的源代码。</p><p>由于Debug Function实现在MS C-RuntimeLibrary中,所以它只能检测到堆内存的泄漏,而且只限于malloc,realloc或strdup等分配的内存,而那些系统资源,比如HANDLE,GDI Object,或是不通过C-Runtime Library分配的内存,比如VARIANT,BSTR的泄漏,它是无法检测到的,这是这种检测法的一个重大的局限性。另外,为了能记录内存块是在哪里分配的,源代码必须相应的配合,这在调试一些老的程序非常麻烦,毕竟修改源代码不是一件省心的事,这是这种检测法的另一个局限性。</p><p>对于开发一个大型的程序,MS C-Runtime Library提供的检测功能是远远不够的。接下来我们就看看外挂式的检测工具。我用的比较多的是BoundsChecker,一则因为它的功能比较全面,更重要的是它的稳定性。这类工具如果不稳定,反而会忙里添乱。到底是出自鼎鼎大名的NuMega,我用下来基本上没有什么大问题。</p><h4 id="2-3-3-2-使用BoundsChecker检测内存泄漏"><a href="#2-3-3-2-使用BoundsChecker检测内存泄漏" class="headerlink" title="2.3.3.2 使用BoundsChecker检测内存泄漏"></a>2.3.3.2 使用BoundsChecker检测内存泄漏</h4><p>BoundsChecker采用一种被称为 Code Injection的技术,来截获对分配内存和释放内存的函数的调用。简单地说,当你的程序开始运行时,BoundsChecker的DLL被自动载入进程的地址空间(这可以通过system-level的Hook实现),然后它会修改进程中对内存分配和释放的函数调用,让这些调用首先转入它的代码,然后再执行原来的代码。BoundsChecker在做这些动作的时,无须修改被调试程序的源代码或工程配置文件,这使得使用它非常的简便、直接。</p><p>这里我们以malloc函数为例,截获其他的函数方法与此类似。</p><p>需要被截获的函数可能在DLL中,也可能在程序的代码里。比如,如果静态连结C-Runtime Library,那么malloc函数的代码会被连结到程序里。为了截获住对这类函数的调用,BoundsChecker会动态修改这些函数的指令。</p><p>以下两段汇编代码,一段没有BoundsChecker介入,另一段则有BoundsChecker的介入:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">126</span>: <span class="function">_CRTIMP <span class="type">void</span> * __cdecl <span class="title">malloc</span> <span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"><span class="number">127</span>: <span class="type">size_t</span> nSize</span></span></span><br><span class="line"><span class="params"><span class="function"><span class="number">128</span>: )</span></span></span><br><span class="line"><span class="function">129: {</span></span><br><span class="line"><span class="number">00403</span>C10 push ebp</span><br><span class="line"><span class="number">00403</span>C11 mov ebp,esp</span><br><span class="line"><span class="number">130</span>: <span class="keyword">return</span> _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, <span class="literal">NULL</span>, <span class="number">0</span>);</span><br><span class="line"><span class="number">00403</span>C13 push <span class="number">0</span></span><br><span class="line"><span class="number">00403</span>C15 push <span class="number">0</span></span><br><span class="line"><span class="number">00403</span>C17 push <span class="number">1</span></span><br><span class="line"><span class="number">00403</span>C19 mov eax,[__newmode (<span class="number">0042376</span>c)]</span><br><span class="line"><span class="number">00403</span>C1E push eax</span><br><span class="line"><span class="number">00403</span>C1F mov ecx,dword ptr [nSize]</span><br><span class="line"><span class="number">00403</span>C22 push ecx</span><br><span class="line"><span class="number">00403</span>C23 call _nh_malloc_dbg (<span class="number">00403</span>c80)</span><br><span class="line"><span class="number">00403</span>C28 add esp,<span class="number">14</span>h</span><br><span class="line"><span class="number">131</span>: }</span><br></pre></td></tr></table></figure><p>以下这一段代码有BoundsChecker介入:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">126</span>: <span class="function">_CRTIMP <span class="type">void</span> * __cdecl <span class="title">malloc</span> <span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function"><span class="number">127</span>: <span class="type">size_t</span> nSize</span></span></span><br><span class="line"><span class="params"><span class="function"><span class="number">128</span>: )</span></span></span><br><span class="line"><span class="function">129: {</span></span><br><span class="line"><span class="number">00403</span>C10 jmp <span class="number">01F</span>41EC8</span><br><span class="line"><span class="number">00403</span>C15 push <span class="number">0</span></span><br><span class="line"><span class="number">00403</span>C17 push <span class="number">1</span></span><br><span class="line"><span class="number">00403</span>C19 mov eax,[__newmode (<span class="number">0042376</span>c)]</span><br><span class="line"><span class="number">00403</span>C1E push eax</span><br><span class="line"><span class="number">00403</span>C1F mov ecx,dword ptr [nSize]</span><br><span class="line"><span class="number">00403</span>C22 push ecx</span><br><span class="line"><span class="number">00403</span>C23 call _nh_malloc_dbg (<span class="number">00403</span>c80)</span><br><span class="line"><span class="number">00403</span>C28 add esp,<span class="number">14</span>h</span><br><span class="line"><span class="number">131</span>: }</span><br></pre></td></tr></table></figure><p>当BoundsChecker介入后,函数malloc的前三条汇编指令被替换成一条jmp指令,原来的三条指令被搬到地址01F41EC8处了。当程序进入malloc后先jmp到01F41EC8,执行原来的三条指令,然后就是BoundsChecker的天下了。大致上它会先记录函数的返回地址(函数的返回地址在stack上,所以很容易修改),然后把返回地址指向属于BoundsChecker的代码,接着跳到malloc函数原来的指令,也就是在00403c15的地方。当malloc函数结束的时候,由于返回地址被修改,它会返回到BoundsChecker的代码中,此时BoundsChecker会记录由malloc分配的内存的指针,然后再跳转到到原来的返回地址去。</p><p>如果内存分配/释放函数在DLL中,BoundsChecker则采用另一种方法来截获对这些函数的调用。BoundsChecker通过修改程序的DLL Import Table让table中的函数地址指向自己的地址,以达到截获的目的。</p><p>截获住这些分配和释放函数,BoundsChecker就能记录被分配的内存或资源的生命周期。接下来的问题是如何与源代码相关,也就是说当BoundsChecker检测到内存泄漏,它如何报告这块内存块是哪段代码分配的。答案是调试信息(Debug Information)。当我们编译一个Debug版的程序时,编译器会把源代码和二进制代码之间的对应关系记录下来,放到一个单独的文件里(.pdb)或者直接连结进目标程序,通过直接读取调试信息就能得到分配某块内存的源代码在哪个文件,哪一行上。使用Code Injection和Debug Information,使BoundsChecker不但能记录呼叫分配函数的源代码的位置,而且还能记录分配时的Call Stack,以及Call Stack上的函数的源代码位置。这在使用像MFC这样的类库时非常有用,以下我用一个例子来说明:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">ShowXItemMenu</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> …</span><br><span class="line"> CMenu menu;</span><br><span class="line"> menu.<span class="built_in">CreatePopupMenu</span>();</span><br><span class="line"> <span class="comment">//add menu items.</span></span><br><span class="line"> menu.<span class="built_in">TrackPropupMenu</span>();</span><br><span class="line"> …</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">ShowYItemMenu</span><span class="params">( )</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> …</span><br><span class="line"> CMenu menu;</span><br><span class="line"> menu.<span class="built_in">CreatePopupMenu</span>();</span><br><span class="line"> <span class="comment">//add menu items.</span></span><br><span class="line"> menu.<span class="built_in">TrackPropupMenu</span>();</span><br><span class="line"> menu.<span class="built_in">Detach</span>();<span class="comment">//this will cause HMENU leak</span></span><br><span class="line"> …</span><br><span class="line">}</span><br><span class="line"><span class="function">BOOL <span class="title">CMenu::CreatePopupMenu</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> …</span><br><span class="line"> hMenu = <span class="built_in">CreatePopupMenu</span>();</span><br><span class="line"> …</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当调用ShowYItemMenu()时,我们故意造成HMENU的泄漏。但是,对于BoundsChecker来说被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假设的你的程序有许多地方使用了CMenu的CreatePopupMenu()函数,如CMenu::CreatePopupMenu()造成的,你依然无法确认问题的根结到底在哪里,在ShowXItemMenu()中还是在ShowYItemMenu()中,或者还有其它的地方也使用了CreatePopupMenu()?有了Call Stack的信息,问题就容易了。BoundsChecker会如下报告泄漏的HMENU的信息:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Function</span><br><span class="line">File</span><br><span class="line">Line</span><br><span class="line">CMenu::CreatePopupMenu</span><br><span class="line">E:<span class="string">"8168"</span>vc98<span class="string">"mfc"</span>mfc<span class="string">"include"</span>afxwin1.inl</span><br><span class="line"><span class="number">1009</span></span><br><span class="line">ShowYItemMenu</span><br><span class="line">E:<span class="string">"testmemleak"</span>mytest.cpp</span><br><span class="line"><span class="number">100</span></span><br></pre></td></tr></table></figure><p>这里省略了其他的函数调用</p><p>如此,我们很容易找到发生问题的函数是ShowYItemMenu()。当使用MFC之类的类库编程时,大部分的API调用都被封装在类库的class里,有了Call Stack信息,我们就可以非常容易的追踪到真正发生泄漏的代码。</p><p>记录Call Stack信息会使程序的运行变得非常慢,因此默认情况下BoundsChecker不会记录Call Stack信息。可以按照以下的步骤打开记录Call Stack信息的选项开关:</p><ol><li><p>打开菜单:BoundsChecker|Setting…</p></li><li><p>在Error Detection页中,在Error Detection Scheme的List中选择Custom</p></li><li><p>在Category的Combox中选择 Pointer and leak error check</p></li><li><p>钩上Report Call Stack复选框</p></li><li><p>点击Ok</p></li></ol><p>基于Code Injection,BoundsChecker还提供了API Parameter的校验功能,memory over run等功能。这些功能对于程序的开发都非常有益。由于这些内容不属于本文的主题,所以不在此详述了。</p><p>尽管BoundsChecker的功能如此强大,但是面对隐式内存泄漏仍然显得苍白无力。所以接下来我们看看如何用Performance Monitor检测内存泄漏。</p><h4 id="2-3-3-3-使用Performance-Monitor检测内存泄漏"><a href="#2-3-3-3-使用Performance-Monitor检测内存泄漏" class="headerlink" title="2.3.3.3 使用Performance Monitor检测内存泄漏"></a>2.3.3.3 使用Performance Monitor检测内存泄漏</h4><p>NT的内核在设计过程中已经加入了系统监视功能,比如CPU的使用率,内存的使用情况,I/O操作的频繁度等都作为一个个Counter,应用程序可以通过读取这些Counter了解整个系统的或者某个进程的运行状况。Performance Monitor就是这样一个应用程序。</p><p>为了检测内存泄漏,我们一般可以监视Process对象的Handle Count,Virutal Bytes 和Working Set三个Counter。Handle Count记录了进程当前打开的HANDLE的个数,监视这个Counter有助于我们发现程序是否有Handle泄漏;Virtual Bytes记录了该进程当前在虚地址空间上使用的虚拟内存的大小,NT的内存分配采用了两步走的方法,首先,在虚地址空间上保留一段空间,这时操作系统并没有分配物理内存,只是保留了一段地址。然后,再提交这段空间,这时操作系统才会分配物理内存。所以,Virtual Bytes一般总大于程序的Working Set。监视Virutal Bytes可以帮助我们发现一些系统底层的问题; Working Set记录了操作系统为进程已提交的内存的总量,这个值和程序申请的内存总量存在密切的关系,如果程序存在内存的泄漏这个值会持续增加,但是Virtual Bytes却是跳跃式增加的。</p><p>监视这些Counter可以让我们了解进程使用内存的情况,如果发生了泄漏,即使是隐式内存泄漏,这些Counter的值也会持续增加。但是,我们知道有问题却不知道哪里有问题,所以一般使用Performance Monitor来验证是否有内存泄漏,而使用BoundsChecker来找到和解决。</p><p>当Performance Monitor显示有内存泄漏,而BoundsChecker却无法检测到,这时有两种可能:第一种,发生了偶发性内存泄漏。这时你要确保使用Performance Monitor和使用BoundsChecker时,程序的运行环境和操作方法是一致的。第二种,发生了隐式的内存泄漏。这时你要重新审查程序的设计,然后仔细研究Performance Monitor记录的Counter的值的变化图,分析其中的变化和程序运行逻辑的关系,找到一些可能的原因。这是一个痛苦的过程,充满了假设、猜想、验证、失败,但这也是一个积累经验的绝好机会。</p><h1 id="3-探讨C-内存回收"><a href="#3-探讨C-内存回收" class="headerlink" title="3. 探讨C++内存回收"></a>3. 探讨C++内存回收</h1><h2 id="3-1-C-内存对象大会战"><a href="#3-1-C-内存对象大会战" class="headerlink" title="3.1 C++内存对象大会战"></a>3.1 C++内存对象大会战</h2><p>如果一个人自称为程序高手,却对内存一无所知,那么我可以告诉你,他一定在吹牛。用C或C++写程序,需要更多地关注内存,这不仅仅是因为内存的分配是否合理直接影响着程序的效率和性能,更为主要的是,当我们操作内存的时候一不小心就会出现问题,而且很多时候,这些问题都是不易发觉的,比如内存泄漏,比如悬挂指针。笔者今天在这里并不是要讨论如何避免这些问题,而是想从另外一个角度来认识C++内存对象。</p><p>我们知道,C++将内存划分为三个逻辑区域:堆、栈和静态存储区。既然如此,我称位于它们之中的对象分别为堆对象,栈对象以及静态对象。那么这些不同的内存对象有什么区别了?堆对象和栈对象各有什么优劣了?如何禁止创建堆对象或栈对象了?这些便是今天的主题。</p><h3 id="3-1-1-基本概念"><a href="#3-1-1-基本概念" class="headerlink" title="3.1.1 基本概念"></a>3.1.1 基本概念</h3><p>先来看看栈。栈,一般用于存放局部变量或对象,如我们在函数定义中用类似下面语句声明的对象:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Type stack_object ;</span><br></pre></td></tr></table></figure><p>stack_object便是一个栈对象,它的生命期是从定义点开始,当所在函数返回时,生命结束。</p><p>另外,几乎所有的临时对象都是栈对象。比如,下面的函数定义:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Type fun(Type object);</span><br></pre></td></tr></table></figure><p>这个函数至少产生两个临时对象,首先,参数是按值传递的,所以会调用拷贝构造函数生成一个临时对象object_copy1 ,在函数内部使用的不是使用的不是object,而是object_copy1,自然,object_copy1是一个栈对象,它在函数返回时被释放;还有这个函数是值返回的,在函数返回时,如果我们不考虑返回值优化(NRV),那么也会产生一个临时对象object_copy2,这个临时对象会在函数返回后一段时间内被释放。比如某个函数中有如下代码:</p><figure class="highlight plaintext"><table><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">Type tt ,result ; //生成两个栈对象</span><br><span class="line">tt = fun(tt); //函数返回时,生成的是一个临时对象object_copy2</span><br></pre></td></tr></table></figure><p>上面的第二个语句的执行情况是这样的,首先函数fun返回时生成一个临时对象object_copy2 ,然后再调用赋值运算符执行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tt = object_copy2 ; //调用赋值运算符</span><br></pre></td></tr></table></figure><p>看到了吗?编译器在我们毫无知觉的情况下,为我们生成了这么多临时对象,而生成这些临时对象的时间和空间的开销可能是很大的,所以,你也许明白了,为什么对于“大”对象最好用const引用传递代替按值进行函数参数传递了。</p><p>接下来,看看堆。堆,又叫自由存储区,它是在程序执行的过程中动态分配的,所以它最大的特性就是动态性。在C++中,所有堆对象的创建和销毁都要由程序员负责,所以,如果处理不好,就会发生内存问题。如果分配了堆对象,却忘记了释放,就会产生内存泄漏;而如果已释放了对象,却没有将相应的指针置为NULL,该指针就是所谓的“悬挂指针”,再度使用此指针时,就会出现非法访问,严重时就导致程序崩溃。</p><p>那么,C++中是怎样分配堆对象的?唯一的方法就是用new(当然,用类malloc指令也可获得C式堆内存),只要使用new,就会在堆中分配一块内存,并且返回指向该堆对象的指针。</p><p>再来看看静态存储区。所有的静态对象、全局对象都于静态存储区分配。关于全局对象,是在main()函数执行前就分配好了的。其实,在main()函数中的显示代码执行之前,会调用一个由编译器生成的_main()函数,而_main()函数会进行所有全局对象的的构造及初始化工作。而在main()函数结束之前,会调用由编译器生成的exit函数,来释放所有的全局对象。比如下面的代码:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">void main(void)</span><br><span class="line">{</span><br><span class="line"> … …// 显式代码</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>实际上,被转化成这样:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">void main(void)</span><br><span class="line">{</span><br><span class="line"> _main(); //隐式代码,由编译器产生,用以构造所有全局对象</span><br><span class="line"> … … // 显式代码</span><br><span class="line"> … …</span><br><span class="line"> exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所以,知道了这个之后,便可以由此引出一些技巧,如,假设我们要在main()函数执行之前做某些准备工作,那么我们可以将这些准备工作写到一个自定义的全局对象的构造函数中,这样,在main()函数的显式代码执行之前,这个全局对象的构造函数会被调用,执行预期的动作,这样就达到了我们的目的。 刚才讲的是静态存储区中的全局对象,那么,局部静态对象了?局部静态对象通常也是在函数中定义的,就像栈对象一样,只不过,其前面多了个static关键字。局部静态对象的生命期是从其所在函数第一次被调用,更确切地说,是当第一次执行到该静态对象的声明代码时,产生该静态局部对象,直到整个程序结束时,才销毁该对象。</p><p>还有一种静态对象,那就是它作为class的静态成员。考虑这种情况时,就牵涉了一些较复杂的问题。</p><p>第一个问题是class的静态成员对象的生命期,class的静态成员对象随着第一个class object的产生而产生,在整个程序结束时消亡。也就是有这样的情况存在,在程序中我们定义了一个class,该类中有一个静态对象作为成员,但是在程序执行过程中,如果我们没有创建任何一个该class object,那么也就不会产生该class所包含的那个静态对象。还有,如果创建了多个class object,那么所有这些object都共享那个静态对象成员。</p><p>第二个问题是,当出现下列情况时:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">class Base</span><br><span class="line">{</span><br><span class="line"> public:</span><br><span class="line"> static Type s_object ;</span><br><span class="line">}</span><br><span class="line">class Derived1 : public Base / / 公共继承</span><br><span class="line">{</span><br><span class="line"> … …// other data</span><br><span class="line">}</span><br><span class="line">class Derived2 : public Base / / 公共继承</span><br><span class="line">{</span><br><span class="line"> … …// other data</span><br><span class="line">}</span><br><span class="line">Base example ;</span><br><span class="line">Derivde1 example1 ;</span><br><span class="line">Derivde2 example2 ;</span><br><span class="line">example.s_object = …… ;</span><br><span class="line">example1.s_object = …… ;</span><br><span class="line">example2.s_object = …… ;</span><br></pre></td></tr></table></figure><p>请注意上面标为黑体的三条语句,它们所访问的s_object是同一个对象吗?答案是肯定的,它们的确是指向同一个对象,这听起来不像是真的,是吗?但这是事实,你可以自己写段简单的代码验证一下。我要做的是来解释为什么会这样? 我们知道,当一个类比如Derived1,从另一个类比如Base继承时,那么,可以看作一个Derived1对象中含有一个Base型的对象,这就是一个subobject。一个Derived1对象的大致内存布局如下:</p><p>让我们想想,当我们将一个Derived1型的对象传给一个接受非引用Base型参数的函数时会发生切割,那么是怎么切割的呢?相信现在你已经知道了,那就是仅仅取出了Derived1型的对象中的subobject,而忽略了所有Derived1自定义的其它数据成员,然后将这个subobject传递给函数(实际上,函数中使用的是这个subobject的拷贝)。</p><p>所有继承Base类的派生类的对象都含有一个Base型的subobject(这是能用Base型指针指向一个Derived1对象的关键所在,自然也是多态的关键了),而所有的subobject和所有Base型的对象都共用同一个s_object对象,自然,从Base类派生的整个继承体系中的类的实例都会共用同一个s_object对象了。上面提到的example、example1、example2的对象布局如下图所示:</p><h3 id="3-1-2-三种内存对象的比较"><a href="#3-1-2-三种内存对象的比较" class="headerlink" title="3.1.2 三种内存对象的比较"></a>3.1.2 三种内存对象的比较</h3><p>栈对象的优势是在适当的时候自动生成,又在适当的时候自动销毁,不需要程序员操心;而且栈对象的创建速度一般较堆对象快,因为分配堆对象时,会调用operator new操作,operator new会采用某种内存空间搜索算法,而该搜索过程可能是很费时间的,产生栈对象则没有这么麻烦,它仅仅需要移动栈顶指针就可以了。但是要注意的是,通常栈空间容量比较小,一般是1MB~2MB,所以体积比较大的对象不适合在栈中分配。特别要注意递归函数中最好不要使用栈对象,因为随着递归调用深度的增加,所需的栈空间也会线性增加,当所需栈空间不够时,便会导致栈溢出,这样就会产生运行时错误。</p><p>堆对象,其产生时刻和销毁时刻都要程序员精确定义,也就是说,程序员对堆对象的生命具有完全的控制权。我们常常需要这样的对象,比如,我们需要创建一个对象,能够被多个函数所访问,但是又不想使其成为全局的,那么这个时候创建一个堆对象无疑是良好的选择,然后在各个函数之间传递这个堆对象的指针,便可以实现对该对象的共享。另外,相比于栈空间,堆的容量要大得多。实际上,当物理内存不够时,如果这时还需要生成新的堆对象,通常不会产生运行时错误,而是系统会使用虚拟内存来扩展实际的物理内存。</p><p>接下来看看static对象。</p><p>首先是全局对象。全局对象为类间通信和函数间通信提供了一种最简单的方式,虽然这种方式并不优雅。一般而言,在完全的面向对象语言中,是不存在全局对象的,比如C#,因为全局对象意味着不安全和高耦合,在程序中过多地使用全局对象将大大降低程序的健壮性、稳定性、可维护性和可复用性。C++也完全可以剔除全局对象,但是最终没有,我想原因之一是为了兼容C。</p><p>其次是类的静态成员,上面已经提到,基类及其派生类的所有对象都共享这个静态成员对象,所以当需要在这些class之间或这些class objects之间进行数据共享或通信时,这样的静态成员无疑是很好的选择。</p><p>接着是静态局部对象,主要可用于保存该对象所在函数被屡次调用期间的中间状态,其中一个最显著的例子就是递归函数,我们都知道递归函数是自己调用自己的函数,如果在递归函数中定义一个nonstatic局部对象,那么当递归次数相当大时,所产生的开销也是巨大的。这是因为nonstatic局部对象是栈对象,每递归调用一次,就会产生一个这样的对象,每返回一次,就会释放这个对象,而且,这样的对象只局限于当前调用层,对于更深入的嵌套层和更浅露的外层,都是不可见的。每个层都有自己的局部对象和参数。</p><p>在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。</p><h3 id="3-1-3-使用栈对象的意外收获"><a href="#3-1-3-使用栈对象的意外收获" class="headerlink" title="3.1.3 使用栈对象的意外收获"></a>3.1.3 使用栈对象的意外收获</h3><p>前面已经介绍到,栈对象是在适当的时候创建,然后在适当的时候自动释放的,也就是栈对象有自动管理功能。那么栈对象会在什么会自动释放了?第一,在其生命期结束的时候;第二,在其所在的函数发生异常的时候。你也许说,这些都很正常啊,没什么大不了的。是的,没什么大不了的。但是只要我们再深入一点点,也许就有意外的收获了。</p><p>栈对象,自动释放时,会调用它自己的析构函数。如果我们在栈对象中封装资源,而且在栈对象的析构函数中执行释放资源的动作,那么就会使资源泄漏的概率大大降低,因为栈对象可以自动的释放资源,即使在所在函数发生异常的时候。实际的过程是这样的:函数抛出异常时,会发生所谓的stack_unwinding(堆栈回滚),即堆栈会展开,由于是栈对象,自然存在于栈中,所以在堆栈回滚的过程中,栈对象的析构函数会被执行,从而释放其所封装的资源。除非,除非在析构函数执行的过程中再次抛出异常――而这种可能性是很小的,所以用栈对象封装资源是比较安全的。基于此认识,我们就可以创建一个自己的句柄或代理来封装资源了。智能指针(auto_ptr)中就使用了这种技术。在有这种需要的时候,我们就希望我们的资源封装类只能在栈中创建,也就是要限制在堆中创建该资源封装类的实例。</p><h3 id="3-1-4-禁止产生堆对象"><a href="#3-1-4-禁止产生堆对象" class="headerlink" title="3.1.4 禁止产生堆对象"></a>3.1.4 禁止产生堆对象</h3><p>上面已经提到,你决定禁止产生某种类型的堆对象,这时你可以自己创建一个资源封装类,该类对象只能在栈中产生,这样就能在异常的情况下自动释放封装的资源。</p><p>那么怎样禁止产生堆对象了?我们已经知道,产生堆对象的唯一方法是使用new操作,如果我们禁止使用new不就行了么。再进一步,new操作执行时会调用operator new,而operator new是可以重载的。方法有了,就是使new operator 为private,为了对称,最好将operator delete也重载为private。现在,你也许又有疑问了,难道创建栈对象不需要调用new吗?是的,不需要,因为创建栈对象不需要搜索内存,而是直接调整堆栈指针,将对象压栈,而operator new的主要任务是搜索合适的堆内存,为堆对象分配空间,这在上面已经提到过了。好,让我们看看下面的示例代码:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">#include <stdlib.h> //需要用到C式内存分配函数</span><br><span class="line">class Resource ; //代表需要被封装的资源类</span><br><span class="line">class NoHashObject</span><br><span class="line">{</span><br><span class="line"> private:</span><br><span class="line"> Resource* ptr ;//指向被封装的资源</span><br><span class="line"> ... ... //其它数据成员</span><br><span class="line"> void* operator new(size_t size) //非严格实现,仅作示意之用</span><br><span class="line"> {</span><br><span class="line"> return malloc(size) ;</span><br><span class="line"> }</span><br><span class="line"> void operator delete(void* pp) //非严格实现,仅作示意之用</span><br><span class="line"> {</span><br><span class="line"> free(pp) ;</span><br><span class="line"> }</span><br><span class="line"> public:</span><br><span class="line"> NoHashObject()</span><br><span class="line"> {</span><br><span class="line"> //此处可以获得需要封装的资源,并让ptr指针指向该资源</span><br><span class="line"> ptr = new Resource() ;</span><br><span class="line"> }</span><br><span class="line"> ~NoHashObject()</span><br><span class="line"> {</span><br><span class="line"> delete ptr ; //释放封装的资源</span><br><span class="line"> }</span><br><span class="line">}; </span><br><span class="line">//NoHashObject现在就是一个禁止堆对象的类了,如果你写下如下代码:</span><br><span class="line">NoHashObject* fp = new NoHashObject() ; //编译期错误!</span><br><span class="line">delete fp ;</span><br></pre></td></tr></table></figure><p>上面代码会产生编译期错误。好了,现在你已经知道了如何设计一个禁止堆对象的类了,你也许和我一样有这样的疑问,难道在类NoHashObject的定义不能改变的情况下,就一定不能产生该类型的堆对象了吗?不,还是有办法的,我称之为“暴力破解法”。C++是如此地强大,强大到你可以用它做你想做的任何事情。这里主要用到的是技巧是指针类型的强制转换。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">void main(void)</span><br><span class="line">{</span><br><span class="line"> char* temp = new char[sizeof(NoHashObject)] ;</span><br><span class="line"> //强制类型转换,现在ptr是一个指向NoHashObject对象的指针</span><br><span class="line"> NoHashObject* obj_ptr = (NoHashObject*)temp ;</span><br><span class="line"> temp = NULL ; //防止通过temp指针修改NoHashObject对象</span><br><span class="line"> //再一次强制类型转换,让rp指针指向堆中NoHashObject对象的ptr成员</span><br><span class="line"> Resource* rp = (Resource*)obj_ptr ;</span><br><span class="line"> //初始化obj_ptr指向的NoHashObject对象的ptr成员</span><br><span class="line"> rp = new Resource() ;</span><br><span class="line"> //现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了</span><br><span class="line"> ... ...</span><br><span class="line"> delete rp ;//释放资源</span><br><span class="line"> temp = (char*)obj_ptr ;</span><br><span class="line"> obj_ptr = NULL ;//防止悬挂指针产生</span><br><span class="line"> delete [] temp ;//释放NoHashObject对象所占的堆空间。</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的实现是麻烦的,而且这种实现方式几乎不会在实践中使用,但是我还是写出来路,因为理解它,对于我们理解C++内存对象是有好处的。对于上面的这么多强制类型转换,其最根本的是什么了?我们可以这样理解:</p><p>某块内存中的数据是不变的,而类型就是我们戴上的眼镜,当我们戴上一种眼镜后,我们就会用对应的类型来解释内存中的数据,这样不同的解释就得到了不同的信息。</p><p>所谓强制类型转换实际上就是换上另一副眼镜后再来看同样的那块内存数据。</p><p>另外要提醒的是,不同的编译器对对象的成员数据的布局安排可能是不一样的,比如,大多数编译器将NoHashObject的ptr指针成员安排在对象空间的头4个字节,这样才会保证下面这条语句的转换动作像我们预期的那样执行:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Resource* rp = (Resource*)obj_ptr ;</span><br></pre></td></tr></table></figure><p>但是,并不一定所有的编译器都是如此。</p><p>既然我们可以禁止产生某种类型的堆对象,那么可以设计一个类,使之不能产生栈对象吗?当然可以。</p><h3 id="3-1-5-禁止产生栈对象"><a href="#3-1-5-禁止产生栈对象" class="headerlink" title="3.1.5 禁止产生栈对象"></a>3.1.5 禁止产生栈对象</h3><p>前面已经提到了,创建栈对象时会移动栈顶指针以“挪出”适当大小的空间,然后在这个空间上直接调用对应的构造函数以形成一个栈对象,而当函数返回时,会调用其析构函数释放这个对象,然后再调整栈顶指针收回那块栈内存。在这个过程中是不需要operator new/delete操作的,所以将operator new/delete设置为private不能达到目的。当然从上面的叙述中,你也许已经想到了:将构造函数或析构函数设为私有的,这样系统就不能调用构造/析构函数了,当然就不能在栈中生成对象了。</p><p>这样的确可以,而且我也打算采用这种方案。但是在此之前,有一点需要考虑清楚,那就是,如果我们将构造函数设置为私有,那么我们也就不能用new来直接产生堆对象了,因为new在为对象分配空间后也会调用它的构造函数啊。所以,我打算只将析构函数设置为private。再进一步,将析构函数设为private除了会限制栈对象生成外,还有其它影响吗?是的,这还会限制继承。</p><p>如果一个类不打算作为基类,通常采用的方案就是将其析构函数声明为private。</p><p>为了限制栈对象,却不限制继承,我们可以将析构函数声明为protected,这样就两全其美了。如下代码所示:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">class NoStackObject</span><br><span class="line">{</span><br><span class="line"> protected:</span><br><span class="line"> ~NoStackObject() { }</span><br><span class="line"> public:</span><br><span class="line"> void destroy()</span><br><span class="line"> {</span><br><span class="line"> delete this ;//调用保护析构函数</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>接着,可以像这样使用NoStackObject类:</p><figure class="highlight plaintext"><table><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">NoStackObject* hash_ptr = new NoStackObject() ;</span><br><span class="line">... ... //对hash_ptr指向的对象进行操作</span><br><span class="line">hash_ptr->destroy() ;</span><br></pre></td></tr></table></figure><p>呵呵,是不是觉得有点怪怪的,我们用new创建一个对象,却不是用delete去删除它,而是要用destroy方法。很显然,用户是不习惯这种怪异的使用方式的。所以,我决定将构造函数也设为private或protected。这又回到了上面曾试图避免的问题,即不用new,那么该用什么方式来生成一个对象了?我们可以用间接的办法完成,即让这个类提供一个static成员函数专门用于产生该类型的堆对象。(设计模式中的singleton模式就可以用这种方式实现。)让我们来看看:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">class NoStackObject</span><br><span class="line">{</span><br><span class="line"> protected:</span><br><span class="line"> NoStackObject() { }</span><br><span class="line"> ~NoStackObject() { }</span><br><span class="line"> public:</span><br><span class="line"> static NoStackObject* creatInstance()</span><br><span class="line"> {</span><br><span class="line"> return new NoStackObject() ;//调用保护的构造函数</span><br><span class="line"> }</span><br><span class="line"> void destroy()</span><br><span class="line"> {</span><br><span class="line"> delete this ;//调用保护的析构函数</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>现在可以这样使用NoStackObject类了:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">NoStackObject* hash_ptr = NoStackObject::creatInstance() ;</span><br><span class="line">... ... //对hash_ptr指向的对象进行操作</span><br><span class="line">hash_ptr->destroy() ;</span><br><span class="line">hash_ptr = NULL ; //防止使用悬挂指针</span><br></pre></td></tr></table></figure><p>现在感觉是不是好多了,生成对象和释放对象的操作一致了。</p><h2 id="3-2-浅议C-中的垃圾回收方法"><a href="#3-2-浅议C-中的垃圾回收方法" class="headerlink" title="3.2 浅议C++ 中的垃圾回收方法"></a>3.2 浅议C++ 中的垃圾回收方法</h2><p>许多 C 或者 C++ 程序员对垃圾回收嗤之以鼻,认为垃圾回收肯定比自己来管理动态内存要低效,而且在回收的时候一定会让程序停顿在那里,而如果自己控制内存管理的话,分配和释放时间都是稳定的,不会导致程序停顿。最后,很多 C/C++ 程序员坚信在C/C++ 中无法实现垃圾回收机制。这些错误的观点都是由于不了解垃圾回收的算法而臆想出来的。</p><p>其实垃圾回收机制并不慢,甚至比动态内存分配更高效。因为我们可以只分配不释放,那么分配内存的时候只需要从堆上一直的获得新的内存,移动堆顶的指针就够了;而释放的过程被省略了,自然也加快了速度。现代的垃圾回收算法已经发展了很多,增量收集算法已经可以让垃圾回收过程分段进行,避免打断程序的运行了。而传统的动态内存管理的算法同样有在适当的时间收集内存碎片的工作要做,并不比垃圾回收更有优势。</p><p>而垃圾回收的算法的基础通常基于扫描并标记当前可能被使用的所有内存块,从已经被分配的所有内存中把未标记的内存回收来做的。C/C++ 中无法实现垃圾回收的观点通常基于无法正确扫描出所有可能还会被使用的内存块,但是,看似不可能的事情实际上实现起来却并不复杂。首先,通过扫描内存的数据,指向堆上动态分配出来内存的指针是很容易被识别出来的,如果有识别错误,也只能是把一些不是指针的数据当成指针,而不会把指针当成非指针数据。这样,回收垃圾的过程只会漏回收掉而不会错误的把不应该回收的内存清理。其次,如果回溯所有内存块被引用的根,只可能存在于全局变量和当前的栈内,而全局变量(包括函数内的静态变量)都是集中存在于 bss 段或 data段中。</p><p>垃圾回收的时候,只需要扫描 bss 段, data 段以及当前被使用着的栈空间,找到可能是动态内存指针的量,把引用到的内存递归扫描就可以得到当前正在使用的所有动态内存了。</p><p>如果肯为你的工程实现一个不错的垃圾回收器,提高内存管理的速度,甚至减少总的内存消耗都是可能的。如果有兴趣的话,可以搜索一下网上已有的关于垃圾回收的论文和实现了的库,开拓视野对一个程序员尤为重要。</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="C-内存管理"><a href="#C-内存管理" class="headerlink" title="C++内存管理"></a>C++内存管理</h1><p>内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自</summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
<category term="memory" scheme="https://zyjblogs.cn/tags/memory/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>详细分析JDK中Stream的实现原理</title>
|
||
<link href="https://zyjblogs.cn/posts/f5786ffa.html"/>
|
||
<id>https://zyjblogs.cn/posts/f5786ffa.html</id>
|
||
<published>2024-03-19T06:24:23.059Z</published>
|
||
<updated>2024-03-19T06:33:26.337Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="详细分析JDK中Stream的实现原理"><a href="#详细分析JDK中Stream的实现原理" class="headerlink" title="详细分析JDK中Stream的实现原理"></a><a href="https://www.cnblogs.com/throwable/p/15371609.html">详细分析JDK中Stream的实现原理</a></h1><h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a><a href="https://www.cnblogs.com/throwable/p/15371609.html#%E5%89%8D%E6%8F%90">前提</a></h2><p><code>Stream</code>是<code>JDK1.8</code>中首次引入的,距今已经过去了接近<code>8</code>年时间(<code>JDK1.8</code>正式版是<code>2013</code>年底发布的)。<code>Stream</code>的引入一方面极大地简化了某些开发场景,另一方面也可能降低了编码的可读性(确实有不少人说到<code>Stream</code>会降低代码的可读性,但是在笔者看来,熟练使用之后反而觉得代码的可读性提高了)。这篇文章会花巨量篇幅,详细分析<code>Stream</code>的底层实现原理,参考的源码是<code>JDK11</code>的源码,其他版本<code>JDK</code>可能不适用于本文中的源码展示和相关例子。</p><blockquote><p>这篇文章花费了极多时间和精力梳理和编写,希望能够帮助到本文的读者</p></blockquote><h2 id="Stream是如何做到向前兼容的"><a href="#Stream是如何做到向前兼容的" class="headerlink" title="Stream是如何做到向前兼容的"></a><a href="https://www.cnblogs.com/throwable/p/15371609.html#stream%E6%98%AF%E5%A6%82%E4%BD%95%E5%81%9A%E5%88%B0%E5%90%91%E5%89%8D%E5%85%BC%E5%AE%B9%E7%9A%84">Stream是如何做到向前兼容的</a></h2><p><code>Stream</code>是<code>JDK1.8</code>引入的,如要需要<code>JDK1.7</code>或者以前的代码也能在<code>JDK1.8</code>或以上运行,那么<code>Stream</code>的引入必定不能在原来已经发布的接口方法进行修改,否则必定会因为兼容性问题导致老版本的接口实现无法在新版本中运行(方法签名出现异常),猜测是基于这个问题引入了接口默认方法,也就是<code>default</code>关键字。查看源码可以发现,<code>ArrayList</code>的超类<code>Collection</code>和<code>Iterable</code>分别添加了数个<code>default</code>方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.Collection部分源码</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Collection</span><E> <span class="keyword">extends</span> <span class="title class_">Iterable</span><E> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">default</span> Spliterator<E> <span class="title function_">spliterator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> Spliterators.spliterator(<span class="built_in">this</span>, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> Stream<E> <span class="title function_">stream</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> StreamSupport.stream(spliterator(), <span class="literal">false</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> Stream<E> <span class="title function_">parallelStream</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> StreamSupport.stream(spliterator(), <span class="literal">true</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// java.lang.Iterable部分源码</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Iterable</span><T> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">forEach</span><span class="params">(Consumer<? <span class="built_in">super</span> T> action)</span> {</span><br><span class="line"> Objects.requireNonNull(action);</span><br><span class="line"> <span class="keyword">for</span> (T t : <span class="built_in">this</span>) {</span><br><span class="line"> action.accept(t);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> Spliterator<T> <span class="title function_">spliterator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> Spliterators.spliteratorUnknownSize(iterator(), <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从直觉来看,这些新增的方法应该就是<code>Stream</code>实现的关键方法(后面会印证这不是直觉,而是查看源码的结果)。接口默认方法在使用上和实例方法一致,在实现上可以直接在接口方法中编写方法体,有点静态方法的意味,但是子类可以覆盖其实现(也就是接口默认方法在本接口中的实现有点像静态方法,可以被子类覆盖,使用上和实例方法一致)。这种实现方式,有可能是一种突破,也有可能是一种妥协,但是无论是妥协还是突破,都实现了向前兼容:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// JDK1.7中的java.lang.Iterable</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Iterable</span><T> {</span><br><span class="line"></span><br><span class="line"> Iterator<T> <span class="title function_">iterator</span><span class="params">()</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// JDK1.7中的Iterable实现</span></span><br><span class="line"><span class="keyword">public</span> MyIterable<Long> <span class="keyword">implements</span> <span class="title class_">Iterable</span><Long>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> Iterator<Long> <span class="title function_">iterator</span><span class="params">()</span>{</span><br><span class="line"> ....</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如上,<code>MyIterable</code>在<code>JDK1.7</code>中定义,如果该类在<code>JDK1.8</code>中运行,那么调用其实例中的<code>forEach()</code>和<code>spliterator()</code>方法,相当于直接调用<code>JDK1.8</code>中的<code>Iterable</code>中的接口默认方法<code>forEach()</code>和<code>spliterator()</code>。当然受限于<code>JDK</code>版本,这里只能确保编译通过,旧功能正常使用,而无法在<code>JDK1.7</code>中使用<code>Stream</code>相关功能或者使用<code>default</code>方法关键字。总结这么多,就是想说明为什么使用<code>JDK7</code>开发和编译的代码可以在<code>JDK8</code>环境下运行。</p><h2 id="可拆分迭代器Spliterator"><a href="#可拆分迭代器Spliterator" class="headerlink" title="可拆分迭代器Spliterator"></a>可拆分迭代器Spliterator</h2><p><code>Stream</code>实现的基石是<code>Spliterator</code>,<code>Spliterator</code>是<code>splitable iterator</code>的缩写,意为”可拆分迭代器”,用于遍历指定数据源(例如数组、集合或者<code>IO Channel</code>等)中的元素,在设计上充分考虑了串行和并行的场景。上一节提到了<code>Collection</code>存在接口默认方法<code>spliterator()</code>,此方法会生成一个<code>Spliterator<E></code>实例,意为着<strong>所有的集合子类都具备创建<code>Spliterator</code>实例的能力</strong>。<code>Stream</code>的实现在设计上和<code>Netty</code>中的<code>ChannelHandlerContext</code>十分相似,本质是一个链表,<strong>而<code>Spliterator</code>就是这个链表的<code>Head</code>节点</strong>(<code>Spliterator</code>实例就是一个流实例的头节点,后面分析具体的源码时候再具体展开)。</p><h3 id="Spliterator接口方法"><a href="#Spliterator接口方法" class="headerlink" title="Spliterator接口方法"></a>Spliterator接口方法</h3><p>接着看<code>Spliterator</code>接口定义的方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Spliterator</span><T> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="type">boolean</span> <span class="title function_">tryAdvance</span><span class="params">(Consumer<? <span class="built_in">super</span> T> action)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">forEachRemaining</span><span class="params">(Consumer<? <span class="built_in">super</span> T> action)</span> {</span><br><span class="line"> <span class="keyword">do</span> { } <span class="keyword">while</span> (tryAdvance(action));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Spliterator<T> <span class="title function_">trySplit</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="type">long</span> <span class="title function_">estimateSize</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> <span class="type">long</span> <span class="title function_">getExactSizeIfKnown</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> (characteristics() & SIZED) == <span class="number">0</span> ? -<span class="number">1L</span> : estimateSize();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> <span class="title function_">characteristics</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> <span class="type">boolean</span> <span class="title function_">hasCharacteristics</span><span class="params">(<span class="type">int</span> characteristics)</span> {</span><br><span class="line"> <span class="keyword">return</span> (characteristics() & characteristics) == characteristics;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> Comparator<? <span class="built_in">super</span> T> getComparator() {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>tryAdvance</strong></p><ul><li>方法签名:<code>boolean tryAdvance(Consumer<? super T> action)</code></li><li>功能:如果<code>Spliterator</code>中存在剩余元素,则对其中的某个元素执行传入的<code>action</code>回调,并且返回<code>true</code>,否则返回<code>false</code>。如果<code>Spliterator</code>启用了<code>ORDERED</code>特性,会按照顺序(这里的顺序值可以类比为<code>ArrayList</code>中容器数组元素的下标,<code>ArrayList</code>中添加新元素是天然有序的,下标由零开始递增)处理下一个元素</li><li>例子:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> List<Integer> list = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> list.add(<span class="number">2</span>);</span><br><span class="line"> list.add(<span class="number">1</span>);</span><br><span class="line"> list.add(<span class="number">3</span>);</span><br><span class="line"> Spliterator<Integer> spliterator = list.stream().spliterator();</span><br><span class="line"> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">round</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">loop</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">while</span> (spliterator.tryAdvance(num -> System.out.printf(<span class="string">"第%d轮回调Action,值:%d\n"</span>, round.getAndIncrement(), num))) {</span><br><span class="line"> System.out.printf(<span class="string">"第%d轮循环\n"</span>, loop.getAndIncrement());</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="number">1</span>轮回调Action,值:<span class="number">2</span></span><br><span class="line">第<span class="number">1</span>轮循环</span><br><span class="line">第<span class="number">2</span>轮回调Action,值:<span class="number">1</span></span><br><span class="line">第<span class="number">2</span>轮循环</span><br><span class="line">第<span class="number">3</span>轮回调Action,值:<span class="number">3</span></span><br><span class="line">第<span class="number">3</span>轮循环</span><br></pre></td></tr></table></figure><p><strong>forEachRemaining</strong></p><ul><li>方法签名:<code>default void forEachRemaining(Consumer<? super T> action)</code></li><li>功能:如果<code>Spliterator</code>中存在剩余元素,则对其中的<strong>所有剩余元素</strong>在<strong>当前线程中</strong>执行传入的<code>action</code>回调。如果<code>Spliterator</code>启用了<code>ORDERED</code>特性,会按照顺序处理剩余所有元素。这是一个接口默认方法,方法体比较粗暴,直接是一个死循环包裹着<code>tryAdvance()</code>方法,直到<code>false</code>退出循环</li><li>例子:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> List<Integer> list = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> list.add(<span class="number">2</span>);</span><br><span class="line"> list.add(<span class="number">1</span>);</span><br><span class="line"> list.add(<span class="number">3</span>);</span><br><span class="line"> Spliterator<Integer> spliterator = list.stream().spliterator();</span><br><span class="line"> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">round</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">1</span>);</span><br><span class="line"> spliterator.forEachRemaining(num -> System.out.printf(<span class="string">"第%d轮回调Action,值:%d\n"</span>, round.getAndIncrement(), num));</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="number">1</span>轮回调Action,值:<span class="number">2</span></span><br><span class="line">第<span class="number">2</span>轮回调Action,值:<span class="number">1</span></span><br><span class="line">第<span class="number">3</span>轮回调Action,值:<span class="number">3</span></span><br></pre></td></tr></table></figure><p><strong>trySplit</strong></p><ul><li>方法签名:<code>Spliterator<T> trySplit()</code></li><li>功能:如果当前的<code>Spliterator</code>是可分区(可分割)的,那么此方法将会返回一个全新的<code>Spliterator</code>实例,这个全新的<code>Spliterator</code>实例里面的元素不会被当前<code>Spliterator</code>实例中的元素覆盖(这里是直译了<code>API</code>注释,实际要表达的意思是:当前的<code>Spliterator</code>实例<code>X</code>是可分割的,<code>trySplit()</code>方法会分割<code>X</code>产生一个全新的<code>Spliterator</code>实例<code>Y</code>,原来的<code>X</code>所包含的元素(范围)也会收缩,类似于<code>X = [a,b,c,d] => X = [a,b], Y = [c,d]</code>;如果当前的<code>Spliterator</code>实例<code>X</code>是不可分割的,此方法会返回<code>NULL</code>),<strong>具体的分割算法由实现类决定</strong></li><li>例子:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> List<Integer> list = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> list.add(<span class="number">2</span>);</span><br><span class="line"> list.add(<span class="number">3</span>);</span><br><span class="line"> list.add(<span class="number">4</span>);</span><br><span class="line"> list.add(<span class="number">1</span>);</span><br><span class="line"> Spliterator<Integer> first = list.stream().spliterator();</span><br><span class="line"> Spliterator<Integer> second = first.trySplit();</span><br><span class="line"> first.forEachRemaining(num -> {</span><br><span class="line"> System.out.printf(<span class="string">"first spliterator item: %d\n"</span>, num);</span><br><span class="line"> });</span><br><span class="line"> second.forEachRemaining(num -> {</span><br><span class="line"> System.out.printf(<span class="string">"second spliterator item: %d\n"</span>, num);</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">first spliterator item: <span class="number">4</span></span><br><span class="line">first spliterator item: <span class="number">1</span></span><br><span class="line">second spliterator item: <span class="number">2</span></span><br><span class="line">second spliterator item: <span class="number">3</span></span><br></pre></td></tr></table></figure><p><strong>estimateSize</strong></p><ul><li>方法签名:<code>long estimateSize()</code></li><li>功能:返回<code>forEachRemaining()</code>方法需要遍历的元素总量的估计值,如果样本个数是无限、计算成本过高或者未知,会直接返回<code>Long.MAX_VALUE</code></li><li>例子:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> List<Integer> list = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> list.add(<span class="number">2</span>);</span><br><span class="line"> list.add(<span class="number">3</span>);</span><br><span class="line"> list.add(<span class="number">4</span>);</span><br><span class="line"> list.add(<span class="number">1</span>);</span><br><span class="line"> Spliterator<Integer> spliterator = list.stream().spliterator();</span><br><span class="line"> System.out.println(spliterator.estimateSize());</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="number">4</span></span><br></pre></td></tr></table></figure><p><strong>getExactSizeIfKnown</strong></p><ul><li>方法签名:<code>default long getExactSizeIfKnown()</code></li><li>功能:如果当前的<code>Spliterator</code>具备<code>SIZED</code>特性(关于特性,下文再展开分析),那么直接调用<code>estimateSize()</code>方法,否则返回<code>-1</code></li><li>例子:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> List<Integer> list = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> list.add(<span class="number">2</span>);</span><br><span class="line"> list.add(<span class="number">3</span>);</span><br><span class="line"> list.add(<span class="number">4</span>);</span><br><span class="line"> list.add(<span class="number">1</span>);</span><br><span class="line"> Spliterator<Integer> spliterator = list.stream().spliterator();</span><br><span class="line"> System.out.println(spliterator.getExactSizeIfKnown());</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="number">4</span></span><br></pre></td></tr></table></figure><p><strong>int characteristics()</strong></p><ul><li>方法签名:<code>long estimateSize()</code></li><li>功能:当前的<code>Spliterator</code>具备的特性(集合),采用位运算,存储在<code>32</code>位整数中(关于特性,下文再展开分析)</li></ul><p><strong>hasCharacteristics</strong></p><ul><li>方法签名:<code>default boolean hasCharacteristics(int characteristics)</code></li><li>功能:判断当前的<code>Spliterator</code>是否具备传入的特性</li></ul><p><strong>getComparator</strong></p><ul><li>方法签名:<code>default Comparator<? super T> getComparator()</code></li><li>功能:如果当前的<code>Spliterator</code>具备<code>SORTED</code>特性,则需要返回一个<code>Comparator</code>实例;如果<code>Spliterator</code>中的元素是天然有序(例如元素实现了<code>Comparable</code>接口),则返回<code>NULL</code>;其他情况直接抛出<code>IllegalStateException</code>异常</li></ul><h3 id="Spliterator自分割"><a href="#Spliterator自分割" class="headerlink" title="Spliterator自分割"></a>Spliterator自分割</h3><p><code>Spliterator#trySplit()</code>可以把一个既有的<code>Spliterator</code>实例分割为两个<code>Spliterator</code>实例,笔者这里把这种方式称为<code>Spliterator</code>自分割,示意图如下:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-1.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-1.png" alt="img"></a></p><p>这里的分割在实现上可以采用两种方式:</p><ul><li>物理分割:对于<code>ArrayList</code>而言,把底层数组<strong>拷贝</strong>并且进行分割,用上面的例子来说相当于<code>X = [1,3,4,2] => X = [4,2], Y = [1,3]</code>,这样实现加上对于<code>ArrayList</code>中本身的元素容器数组,相当于多存了一份数据,显然不是十分合理</li><li>逻辑分割:对于<code>ArrayList</code>而言,由于元素容器数组天然有序,可以采用数组的索引(下标)进行分割,用上面的例子来说相当于<code>X = 索引表[0,1,2,3] => X = 索引表[2,3], Y = 索引表[0,1]</code>,这种方式是共享底层容器数组,只对元素索引进行分割,实现上比较简单而且相对合理</li></ul><p>参看<code>ArrayListSpliterator</code>的源码,可以分析其分割算法实现:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ArrayList#spliterator()</span></span><br><span class="line"><span class="keyword">public</span> Spliterator<E> <span class="title function_">spliterator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ArrayListSpliterator</span>(<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><br><span class="line"><span class="comment">// ArrayList中内部类ArrayListSpliterator</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ArrayListSpliterator</span> <span class="keyword">implements</span> <span class="title class_">Spliterator</span><E> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 当前的处理的元素索引值,其实是剩余元素的下边界值(包含),在tryAdvance()或者trySplit()方法中被修改,一般初始值为0</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> index;</span><br><span class="line"> <span class="comment">// 栅栏,其实是元素索引值的上边界值(不包含),一般初始化的时候为-1,使用时具体值为元素索引值上边界加1</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> fence;</span><br><span class="line"> <span class="comment">// 预期的修改次数,一般初始化值等于modCount</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> expectedModCount;</span><br><span class="line"></span><br><span class="line"> ArrayListSpliterator(<span class="type">int</span> origin, <span class="type">int</span> fence, <span class="type">int</span> expectedModCount) {</span><br><span class="line"> <span class="built_in">this</span>.index = origin;</span><br><span class="line"> <span class="built_in">this</span>.fence = fence;</span><br><span class="line"> <span class="built_in">this</span>.expectedModCount = expectedModCount;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 获取元素索引值的上边界值,如果小于0,则把hi和fence都赋值为(ArrayList中的)size,expectedModCount赋值为(ArrayList中的)modCount,返回上边界值</span></span><br><span class="line"> <span class="comment">// 这里注意if条件中有赋值语句hi = fence,也就是此方法调用过程中临时变量hi总是重新赋值为fence,fence是ArrayListSpliterator实例中的成员属性</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">getFence</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">int</span> hi;</span><br><span class="line"> <span class="keyword">if</span> ((hi = fence) < <span class="number">0</span>) {</span><br><span class="line"> expectedModCount = modCount;</span><br><span class="line"> hi = fence = size;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> hi;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Spliterator自分割,这里采用了二分法</span></span><br><span class="line"> <span class="keyword">public</span> ArrayListSpliterator <span class="title function_">trySplit</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">// hi等于当前ArrayListSpliterator实例中的fence变量,相当于获取剩余元素的上边界值</span></span><br><span class="line"> <span class="comment">// lo等于当前ArrayListSpliterator实例中的index变量,相当于获取剩余元素的下边界值</span></span><br><span class="line"> <span class="comment">// mid = (lo + hi) >>> 1,这里的无符号右移动1位运算相当于(lo + hi)/2</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">hi</span> <span class="operator">=</span> getFence(), lo = index, mid = (lo + hi) >>> <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 当lo >= mid的时候为不可分割,返回NULL,否则,以index = lo,fence = mid和expectedModCount = expectedModCount创建一个新的ArrayListSpliterator</span></span><br><span class="line"> <span class="comment">// 这里有个细节之处,在新的ArrayListSpliterator构造参数中,当前的index被重新赋值为index = mid,这一点容易看漏,老程序员都喜欢做这样的赋值简化</span></span><br><span class="line"> <span class="comment">// lo >= mid返回NULL的时候,不会创建新的ArrayListSpliterator,也不会修改当前ArrayListSpliterator中的参数</span></span><br><span class="line"> <span class="keyword">return</span> (lo >= mid) ? <span class="literal">null</span> : <span class="keyword">new</span> <span class="title class_">ArrayListSpliterator</span>(lo, index = mid, expectedModCount);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// tryAdvance实现</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">tryAdvance</span><span class="params">(Consumer<? <span class="built_in">super</span> E> action)</span> {</span><br><span class="line"> <span class="keyword">if</span> (action == <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="comment">// 获取迭代的上下边界</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">hi</span> <span class="operator">=</span> getFence(), i = index;</span><br><span class="line"> <span class="comment">// 由于前面分析下边界是包含关系,上边界是非包含关系,所以这里要i < hi而不是i <= hi</span></span><br><span class="line"> <span class="keyword">if</span> (i < hi) {</span><br><span class="line"> index = i + <span class="number">1</span>;</span><br><span class="line"> <span class="comment">// 这里的elementData来自ArrayList中,也就是前文经常提到的元素数组容器,这里是直接通过元素索引访问容器中的数据</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span> <span class="type">E</span> <span class="variable">e</span> <span class="operator">=</span> (E)elementData[i];</span><br><span class="line"> <span class="comment">// 对传入的Action进行回调</span></span><br><span class="line"> action.accept(e);</span><br><span class="line"> <span class="comment">// 并发修改异常判断</span></span><br><span class="line"> <span class="keyword">if</span> (modCount != expectedModCount)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ConcurrentModificationException</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// forEachRemaining实现,这里没有采用默认实现,而是完全覆盖实现一个新方法</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">forEachRemaining</span><span class="params">(Consumer<? <span class="built_in">super</span> E> action)</span> {</span><br><span class="line"> <span class="comment">// 这里会新建所需的中间变量,i为index的中间变量,hi为fence的中间变量,mc为expectedModCount的中间变量</span></span><br><span class="line"> <span class="type">int</span> i, hi, mc;</span><br><span class="line"> Object[] a;</span><br><span class="line"> <span class="keyword">if</span> (action == <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="comment">// 判断容器数组存在性</span></span><br><span class="line"> <span class="keyword">if</span> ((a = elementData) != <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// hi、fence和mc初始化</span></span><br><span class="line"> <span class="keyword">if</span> ((hi = fence) < <span class="number">0</span>) {</span><br><span class="line"> mc = modCount;</span><br><span class="line"> hi = size;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> mc = expectedModCount;</span><br><span class="line"> <span class="comment">// 这里就是先做参数合法性校验,再遍历临时数组容器a中中[i,hi)的剩余元素对传入的Action进行回调</span></span><br><span class="line"> <span class="comment">// 这里注意有一处隐蔽的赋值(index = hi),下界被赋值为上界,意味着每个ArrayListSpliterator实例只能调用一次forEachRemaining()方法</span></span><br><span class="line"> <span class="keyword">if</span> ((i = index) >= <span class="number">0</span> && (index = hi) <= a.length) {</span><br><span class="line"> <span class="keyword">for</span> (; i < hi; ++i) {</span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span> <span class="type">E</span> <span class="variable">e</span> <span class="operator">=</span> (E) a[i];</span><br><span class="line"> action.accept(e);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 这里校验ArrayList的modCount和mc是否一致,理论上在forEachRemaining()遍历期间,不能对数组容器进行元素的新增或者移除,一旦发生modCount更变会抛出异常</span></span><br><span class="line"> <span class="keyword">if</span> (modCount == mc)</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="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ConcurrentModificationException</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">long</span> <span class="title function_">estimateSize</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> getFence() - index;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 具备ORDERED、SIZED和SUBSIZED特性</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">characteristics</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在阅读源码的时候务必注意,老一辈的程序员有时候会采用比较<strong>隐蔽</strong>的赋值方式,笔者认为需要展开一下:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-2.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-2.png" alt="img"></a></p><p>第一处红圈位置在构建新的<code>ArrayListSpliterator</code>的时候,当前<code>ArrayListSpliterator</code>的<code>index</code>属性也被修改了,过程如下图:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-3.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-3.png" alt="img"></a></p><p>第二处红圈位置,在<code>forEachRemaining()</code>方法调用时候做参数校验,并且<code>if</code>分支里面把<code>index</code>(下边界值)赋值为<code>hi</code>(上边界值),<strong>那么一个<code>ArrayListSpliterator</code>实例中的<code>forEachRemaining()</code>方法的遍历操作必定只会执行一次</strong>。可以这样验证一下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> List<Integer> list = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> list.add(<span class="number">2</span>);</span><br><span class="line"> list.add(<span class="number">1</span>);</span><br><span class="line"> list.add(<span class="number">3</span>);</span><br><span class="line"> Spliterator<Integer> spliterator = list.stream().spliterator();</span><br><span class="line"> <span class="keyword">final</span> <span class="type">AtomicInteger</span> <span class="variable">round</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">1</span>);</span><br><span class="line"> spliterator.forEachRemaining(num -> System.out.printf(<span class="string">"[第一次遍历forEachRemaining]第%d轮回调Action,值:%d\n"</span>, round.getAndIncrement(), num));</span><br><span class="line"> round.set(<span class="number">1</span>);</span><br><span class="line"> spliterator.forEachRemaining(num -> System.out.printf(<span class="string">"[第二次遍历forEachRemaining]第%d轮回调Action,值:%d\n"</span>, round.getAndIncrement(), num));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 控制台输出</span></span><br><span class="line">[第一次遍历forEachRemaining]第<span class="number">1</span>轮回调Action,值:<span class="number">2</span></span><br><span class="line">[第一次遍历forEachRemaining]第<span class="number">2</span>轮回调Action,值:<span class="number">1</span></span><br><span class="line">[第一次遍历forEachRemaining]第<span class="number">3</span>轮回调Action,值:<span class="number">3</span></span><br></pre></td></tr></table></figure><p>对于<code>ArrayListSpliterator</code>的实现可以确认下面几点:</p><ul><li>一个新的<code>ArrayListSpliterator</code>实例中的<code>forEachRemaining()</code>方法只能调用一次</li><li><code>ArrayListSpliterator</code>实例中的<code>forEachRemaining()</code>方法遍历元素的边界是<code>[index, fence)</code></li><li><code>ArrayListSpliterator</code>自分割的时候,分割出来的新<code>ArrayListSpliterator</code>负责处理元素下标小的分段(类比<code>fork</code>的左分支),而原<code>ArrayListSpliterator</code>负责处理元素下标大的分段(类比<code>fork</code>的右分支)</li><li><code>ArrayListSpliterator</code>提供的<code>estimateSize()</code>方法得到的分段元素剩余数量是一个准确值</li></ul><p>如果把上面的例子继续分割,可以得到下面的过程:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-4.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-4.png" alt="img"></a></p><p><strong><code>Spliterator</code>自分割是并行流实现的基础</strong>,并行流计算过程其实就是<code>fork-join</code>的处理过程,<code>trySplit()</code>方法的实现决定了<code>fork</code>任务的粒度,每个<code>fork</code>任务进行计算的时候是并发安全的,这一点由线程封闭(线程栈封闭)保证,每一个<code>fork</code>任务计算完成最后的结果再由单个线程进行<code>join</code>操作,才能得到正确的结果。下面的例子是求整数<code>1 ~ 100</code>的和:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConcurrentSplitCalculateSum</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_">ForkTask</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> <span class="variable">result</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Spliterator<Integer> spliterator;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> CountDownLatch latch;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">ForkTask</span><span class="params">(Spliterator<Integer> spliterator,</span></span><br><span class="line"><span class="params"> CountDownLatch latch)</span> {</span><br><span class="line"> <span class="built_in">this</span>.spliterator = spliterator;</span><br><span class="line"> <span class="built_in">this</span>.latch = latch;</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"> spliterator.forEachRemaining(num -> result = result + num);</span><br><span class="line"> <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"> System.out.printf(<span class="string">"线程[%s]完成计算任务,当前段计算结果:%d,耗时:%d ms\n"</span>,</span><br><span class="line"> Thread.currentThread().getName(), result, end - start);</span><br><span class="line"> latch.countDown();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">result</span><span class="params">()</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><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">join</span><span class="params">(List<ForkTask> tasks)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">result</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (ForkTask task : tasks) {</span><br><span class="line"> result = result + task.result();</span><br><span class="line"> }</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">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">THREAD_NUM</span> <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> List<Integer> source = <span class="keyword">new</span> <span class="title class_">ArrayList</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">1</span>; i < <span class="number">101</span>; i++) {</span><br><span class="line"> source.add(i);</span><br><span class="line"> }</span><br><span class="line"> Spliterator<Integer> root = source.stream().spliterator();</span><br><span class="line"> List<Spliterator<Integer>> spliteratorList = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> Spliterator<Integer> x = root.trySplit();</span><br><span class="line"> Spliterator<Integer> y = x.trySplit();</span><br><span class="line"> Spliterator<Integer> z = root.trySplit();</span><br><span class="line"> spliteratorList.add(root);</span><br><span class="line"> spliteratorList.add(x);</span><br><span class="line"> spliteratorList.add(y);</span><br><span class="line"> spliteratorList.add(z);</span><br><span class="line"> List<ForkTask> tasks = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> <span class="type">CountDownLatch</span> <span class="variable">latch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(THREAD_NUM);</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 < THREAD_NUM; i++) {</span><br><span class="line"> <span class="type">ForkTask</span> <span class="variable">task</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ForkTask</span>(spliteratorList.get(i), latch);</span><br><span class="line"> task.setName(<span class="string">"fork-task-"</span> + (i + <span class="number">1</span>));</span><br><span class="line"> tasks.add(task);</span><br><span class="line"> }</span><br><span class="line"> tasks.forEach(Thread::start);</span><br><span class="line"> latch.await();</span><br><span class="line"> <span class="type">int</span> <span class="variable">result</span> <span class="operator">=</span> join(tasks);</span><br><span class="line"> System.out.println(<span class="string">"最终计算结果为:"</span> + result);</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">线程[fork-task-<span class="number">4</span>]完成计算任务,当前段计算结果:<span class="number">1575</span>,耗时:<span class="number">0</span> ms</span><br><span class="line">线程[fork-task-<span class="number">2</span>]完成计算任务,当前段计算结果:<span class="number">950</span>,耗时:<span class="number">1</span> ms</span><br><span class="line">线程[fork-task-<span class="number">3</span>]完成计算任务,当前段计算结果:<span class="number">325</span>,耗时:<span class="number">1</span> ms</span><br><span class="line">线程[fork-task-<span class="number">1</span>]完成计算任务,当前段计算结果:<span class="number">2200</span>,耗时:<span class="number">1</span> ms</span><br><span class="line">最终计算结果为:<span class="number">5050</span></span><br></pre></td></tr></table></figure><p>当然,最终并行流的计算用到了<code>ForkJoinPool</code>,并不像这个例子中这么粗暴地进行异步执行。关于并行流的实现下文会详细分析。</p><h3 id="Spliterator支持的特性"><a href="#Spliterator支持的特性" class="headerlink" title="Spliterator支持的特性"></a>Spliterator支持的特性</h3><p>某一个<code>Spliterator</code>实例支持的特性由方法<code>characteristics()</code>决定,这个方法返回的是一个<code>32</code>位数值,实际使用中会展开为<code>bit</code>数组,所有的特性分配在不同的位上,而<code>hasCharacteristics(int characteristics)</code>就是通过输入的具体特性值通过位运算判断该特性是否存在于<code>characteristics()</code>中。下面简化<code>characteristics</code>为<code>byte</code>分析一下这个技巧:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">假设:byte characteristics() => 也就是最多8个位用于表示特性集合,如果每个位只表示一种特性,那么可以总共表示8种特性</span><br><span class="line">特性X:0000 0001</span><br><span class="line">特性Y:0000 0010</span><br><span class="line">以此类推</span><br><span class="line">假设:characteristics = X | Y = 0000 0001 | 0000 0010 = 0000 0011</span><br><span class="line">那么:characteristics & X = 0000 0011 & 0000 0001 = 0000 0001</span><br><span class="line">判断characteristics是否包含X:(characteristics & X) == X</span><br></pre></td></tr></table></figure><p>上面推断的过程就是<code>Spliterator</code>中特性判断方法的处理逻辑:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回特性集合</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">characteristics</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 class="keyword">default</span> <span class="type">boolean</span> <span class="title function_">hasCharacteristics</span><span class="params">(<span class="type">int</span> characteristics)</span> {</span><br><span class="line"> <span class="keyword">return</span> (characteristics() & characteristics) == characteristics;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里可以验证一下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="keyword">class</span> <span class="title class_">CharacteristicsCheck</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> System.out.printf(<span class="string">"是否存在ORDERED特性:%s\n"</span>, hasCharacteristics(Spliterator.ORDERED));</span><br><span class="line"> System.out.printf(<span class="string">"是否存在SIZED特性:%s\n"</span>, hasCharacteristics(Spliterator.SIZED));</span><br><span class="line"> System.out.printf(<span class="string">"是否存在DISTINCT特性:%s\n"</span>, hasCharacteristics(Spliterator.DISTINCT));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">characteristics</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SORTED;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">hasCharacteristics</span><span class="params">(<span class="type">int</span> characteristics)</span> {</span><br><span class="line"> <span class="keyword">return</span> (characteristics() & characteristics) == characteristics;</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">是否存在ORDERED特性:<span class="literal">true</span></span><br><span class="line">是否存在SIZED特性:<span class="literal">true</span></span><br><span class="line">是否存在DISTINCT特性:<span class="literal">false</span></span><br></pre></td></tr></table></figure><p>目前<code>Spliterator</code>支持的特性一共有<code>8</code>个,如下:</p><table><thead><tr><th align="center">特性</th><th align="center">十六进制值</th><th align="center">二进制值</th><th align="center">功能</th></tr></thead><tbody><tr><td align="center"><code>DISTINCT</code></td><td align="center"><code>0x00000001</code></td><td align="center"><code>0000 0000 0000 0001</code></td><td align="center">去重,例如对于每对要处理的元素<code>(x,y)</code>,使用<code>!x.equals(y)</code>比较,<code>Spliterator</code>中去重实际上基于<code>Set</code>处理</td></tr><tr><td align="center"><code>ORDERED</code></td><td align="center"><code>0x00000010</code></td><td align="center"><code>0000 0000 0001 0000</code></td><td align="center">(元素)顺序处理,可以理解为<code>trySplit()</code>、<code>tryAdvance()</code>和<code>forEachRemaining()</code>方法对所有元素处理都保证一个严格的前缀顺序</td></tr><tr><td align="center"><code>SORTED</code></td><td align="center"><code>0x00000004</code></td><td align="center"><code>0000 0000 0000 0100</code></td><td align="center">排序,元素使用<code>getComparator()</code>方法提供的<code>Comparator</code>进行排序,如果定义了<code>SORTED</code>特性,则必须定义<code>ORDERED</code>特性</td></tr><tr><td align="center"><code>SIZED</code></td><td align="center"><code>0x00000040</code></td><td align="center"><code>0000 0000 0100 0000</code></td><td align="center">(元素)预估数量,启用此特性,那么<code>Spliterator</code>拆分或者迭代之前,<code>estimateSize()</code>返回的是元素的准确数量</td></tr><tr><td align="center"><code>NONNULL</code></td><td align="center"><code>0x00000040</code></td><td align="center"><code>0000 0001 0000 0000</code></td><td align="center">(元素)非<code>NULL</code>,数据源保证<code>Spliterator</code>需要处理的元素不能为<code>NULL</code>,最常用于并发容器中的集合、队列和<code>Map</code></td></tr><tr><td align="center"><code>IMMUTABLE</code></td><td align="center"><code>0x00000400</code></td><td align="center"><code>0000 0100 0000 0000</code></td><td align="center">(元素)不可变,数据源不可被修改,也就是处理过程中元素不能被添加、替换和移除(更新属性是允许的)</td></tr><tr><td align="center"><code>CONCURRENT</code></td><td align="center"><code>0x00001000</code></td><td align="center"><code>0001 0000 0000 0000</code></td><td align="center">(元素源)的修改是并发安全的,意味着多线程在数据源中添加、替换或者移除元素在不需要额外的同步条件下是并发安全的</td></tr><tr><td align="center"><code>SUBSIZED</code></td><td align="center"><code>0x00004000</code></td><td align="center"><code>0100 0000 0000 0000</code></td><td align="center">(子<code>Spliterator</code>元素)预估数量,启用此特性,意味着通过<code>trySplit()</code>方法分割出来的所有子<code>Spliterator</code>(当前<code>Spliterator</code>分割后也属于子<code>Spliterator</code>)都启用<code>SIZED</code>特性</td></tr></tbody></table><blockquote><p>细心点观察可以发现:所有特性采用32位的整数存储,使用了隔1位存储的策略,位下标和特性的映射是:(0 => DISTINCT)、(3 => SORTED)、(5 => ORDERED)、(7=> SIZED)、(9 => NONNULL)、(11 => IMMUTABLE)、(13 => CONCURRENT)、(15 => SUBSIZED)</p></blockquote><p>所有特性的功能这里只概括了核心的定义,还有一些小字或者特例描述限于篇幅没有完全加上,这一点可以参考具体的源码中的<code>API</code>注释。这些特性最终会转化为<code>StreamOpFlag</code>再提供给<code>Stream</code>中的操作判断使用,由于<code>StreamOpFlag</code>会更加复杂,下文再进行详细分析。</p><h2 id="流的实现原理以及源码分析"><a href="#流的实现原理以及源码分析" class="headerlink" title="流的实现原理以及源码分析"></a>流的实现原理以及源码分析</h2><p>由于流的实现是高度抽象的工程代码,所以在源码阅读上会有点困难。整个体系涉及到大量的接口、类和枚举,如下图:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-5.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-5.png" alt="img"></a></p><p>图中的顶层类结构图描述的就是流的流水线相关类继承关系,其中<code>IntStream</code>、<code>LongStream</code>和<code>DoubleStream</code>都是特化类型,分别针对于<code>Integer</code>、<code>Long</code>和<code>Double</code>三种类型,其他引用类型构建的<code>Pipeline</code>都是<code>ReferencePipeline</code>实例,因此笔者认为,<code>ReferencePipeline</code>(引用类型流水线)是流的核心数据结构,下面会基于<code>ReferencePipeline</code>的实现做深入分析。</p><h3 id="StreamOpFlag源码分析"><a href="#StreamOpFlag源码分析" class="headerlink" title="StreamOpFlag源码分析"></a>StreamOpFlag源码分析</h3><blockquote><p>注意,这一小节很烧脑,也有可能是笔者的位操作不怎么熟练,这篇文章大部分时间消耗在这一小节</p></blockquote><p><code>StreamOpFlag</code>是一个枚举,功能是存储<code>Stream</code>和操作的标志(<code>Flags corresponding to characteristics of streams and operations</code>,下称<code>Stream</code>标志),这些标志提供给<code>Stream</code>框架用于控制、定制化和优化计算。<code>Stream</code>标志可以用于描述与流相关联的若干不同实体的特征,这些实体包括:<code>Stream</code>的源、<code>Stream</code>的中间操作(<code>Op</code>)和<code>Stream</code>的终端操作(<code>Terminal Op</code>)。但是并非所有的<code>Stream</code>标志对所有的<code>Stream</code>实体都具备意义,目前这些实体和标志映射关系如下:</p><table><thead><tr><th align="center">Type(Stream Entity Type)</th><th align="center">DISTINCT</th><th align="center">SORTED</th><th align="center">ORDERED</th><th align="center">SIZED</th><th align="center">SHORT_CIRCUIT</th></tr></thead><tbody><tr><td align="center"><code>SPLITERATOR</code></td><td align="center">01</td><td align="center">01</td><td align="center">01</td><td align="center">01</td><td align="center">00</td></tr><tr><td align="center"><code>STREAM</code></td><td align="center">01</td><td align="center">01</td><td align="center">01</td><td align="center">01</td><td align="center">00</td></tr><tr><td align="center"><code>OP</code></td><td align="center">11</td><td align="center">11</td><td align="center">11</td><td align="center">10</td><td align="center">01</td></tr><tr><td align="center"><code>TERMINAL_OP</code></td><td align="center">00</td><td align="center">00</td><td align="center">10</td><td align="center">00</td><td align="center">01</td></tr><tr><td align="center"><code>UPSTREAM_TERMINAL_OP</code></td><td align="center">00</td><td align="center">00</td><td align="center">10</td><td align="center">00</td><td align="center">00</td></tr></tbody></table><p>其中:</p><ul><li>01:表示设置/注入</li><li>10:表示清除</li><li>11:表示保留</li><li>00:表示初始化值(默认填充值),这是一个关键点,<code>0</code>值表示绝对不会是某个类型的标志</li></ul><p><code>StreamOpFlag</code>的顶部注释中还有一个表格如下:</p><table><thead><tr><th align="center">-</th><th align="center">DISTINCT</th><th align="center">SORTED</th><th align="center">ORDERED</th><th align="center">SIZED</th><th align="center">SHORT_CIRCUIT</th></tr></thead><tbody><tr><td align="center">Stream source(<code>Stream</code>的源)</td><td align="center">Y</td><td align="center">Y</td><td align="center">Y</td><td align="center">Y</td><td align="center">N</td></tr><tr><td align="center">Intermediate operation(中间操作)</td><td align="center">PCI</td><td align="center">PCI</td><td align="center">PCI</td><td align="center">PC</td><td align="center">PI</td></tr><tr><td align="center">Terminal operation(终结操作)</td><td align="center">N</td><td align="center">N</td><td align="center">PC</td><td align="center">N</td><td align="center">PI</td></tr></tbody></table><p>标记 <code>-></code> 含义:</p><ul><li><code>Y</code>:允许</li><li><code>N</code>:非法</li><li><code>P</code>:保留</li><li><code>C</code>:清除</li><li><code>I</code>:注入</li><li>组合<code>PCI</code>:可以保留、清除或者注入</li><li>组合<code>PC</code>:可以保留或者清除</li><li>组合<code>PI</code>:可以保留或者注入</li></ul><p>两个表格其实是在描述同一个结论,可以相互对照和理解,但是<strong>最终实现参照于第一个表的定义</strong>。注意一点:这里的<code>preserved</code>(<code>P</code>)表示保留的意思,如果<code>Stream</code>实体某个标志被赋值为<code>preserved</code>,意味着该实体可以使用此标志代表的特性。例如此小节第一个表格中的<code>OP</code>的<code>DISTINCT</code>、<code>SORTED</code>和<code>ORDERED</code>都赋值为<code>11</code>(<code>preserved</code>),意味着<code>OP</code>类型的实体允许使用去重、自然排序和顺序处理特性。回到源码部分,先看<code>StreamOpFlag</code>的核心属性和构造器:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">StreamOpFlag</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 暂时忽略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 类型枚举,Stream相关实体类型</span></span><br><span class="line"> <span class="keyword">enum</span> <span class="title class_">Type</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// SPLITERATOR类型,关联所有和Spliterator相关的特性</span></span><br><span class="line"> SPLITERATOR,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// STREAM类型,关联所有和Stream相关的标志</span></span><br><span class="line"> STREAM,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// STREAM类型,关联所有和Stream中间操作相关的标志</span></span><br><span class="line"> OP,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// TERMINAL_OP类型,关联所有和Stream终结操作相关的标志</span></span><br><span class="line"> TERMINAL_OP,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// UPSTREAM_TERMINAL_OP类型,关联所有在最后一个有状态操作边界上游传播的终止操作标志</span></span><br><span class="line"> <span class="comment">// 这个类型的意义直译有点拗口,不过实际上在JDK11源码中,这个类型没有被流相关功能引用,暂时可以忽略</span></span><br><span class="line"> UPSTREAM_TERMINAL_OP</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 设置/注入标志的bit模式,二进制数0001,十进制数1</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SET_BITS</span> <span class="operator">=</span> <span class="number">0b01</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 清除标志的bit模式,二进制数0010,十进制数2</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CLEAR_BITS</span> <span class="operator">=</span> <span class="number">0b10</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 保留标志的bit模式,二进制数0011,十进制数3</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">PRESERVE_BITS</span> <span class="operator">=</span> <span class="number">0b11</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 掩码建造器工厂方法,注意这个方法用于实例化MaskBuilder</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> MaskBuilder <span class="title function_">set</span><span class="params">(Type t)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MaskBuilder</span>(<span class="keyword">new</span> <span class="title class_">EnumMap</span><>(Type.class)).set(t);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 私有静态内部类,掩码建造器,里面的map由上面的set(Type t)方法得知是EnumMap实例</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_">MaskBuilder</span> {</span><br><span class="line"> <span class="comment">// Type -> SET_BITS|CLEAR_BITS|PRESERVE_BITS|0</span></span><br><span class="line"> <span class="keyword">final</span> Map<Type, Integer> map;</span><br><span class="line"></span><br><span class="line"> MaskBuilder(Map<Type, Integer> map) {</span><br><span class="line"> <span class="built_in">this</span>.map = map;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 设置类型和对应的掩码</span></span><br><span class="line"> MaskBuilder <span class="title function_">mask</span><span class="params">(Type t, Integer i)</span> {</span><br><span class="line"> map.put(t, i);</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 对类型添加/inject</span></span><br><span class="line"> MaskBuilder <span class="title function_">set</span><span class="params">(Type t)</span> {</span><br><span class="line"> <span class="keyword">return</span> mask(t, SET_BITS);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> MaskBuilder <span class="title function_">clear</span><span class="params">(Type t)</span> {</span><br><span class="line"> <span class="keyword">return</span> mask(t, CLEAR_BITS);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> MaskBuilder <span class="title function_">setAndClear</span><span class="params">(Type t)</span> {</span><br><span class="line"> <span class="keyword">return</span> mask(t, PRESERVE_BITS);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这里的build方法对于类型中的NULL掩码填充为0,然后把map返回</span></span><br><span class="line"> Map<Type, Integer> <span class="title function_">build</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">for</span> (Type t : Type.values()) {</span><br><span class="line"> map.putIfAbsent(t, <span class="number">0b00</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> map;</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="keyword">private</span> <span class="keyword">final</span> Map<Type, Integer> maskTable;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// bit的起始偏移量,控制下面set、clear和preserve的起始偏移量</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> bitPosition;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// set/inject的bit set(map),其实准确来说应该是一个表示set/inject的bit map</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> set;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// clear的bit set(map),其实准确来说应该是一个表示clear的bit map</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> clear;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// preserve的bit set(map),其实准确来说应该是一个表示preserve的bit map</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> preserve;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">StreamOpFlag</span><span class="params">(<span class="type">int</span> position, MaskBuilder maskBuilder)</span> {</span><br><span class="line"> <span class="comment">// 这里会基于MaskBuilder初始化内部的EnumMap</span></span><br><span class="line"> <span class="built_in">this</span>.maskTable = maskBuilder.build();</span><br><span class="line"> <span class="comment">// Two bits per flag <= 这里会把入参position放大一倍</span></span><br><span class="line"> position *= <span class="number">2</span>;</span><br><span class="line"> <span class="built_in">this</span>.bitPosition = position;</span><br><span class="line"> <span class="built_in">this</span>.set = SET_BITS << position; <span class="comment">// 设置/注入标志的bit模式左移2倍position</span></span><br><span class="line"> <span class="built_in">this</span>.clear = CLEAR_BITS << position; <span class="comment">// 清除标志的bit模式左移2倍position</span></span><br><span class="line"> <span class="built_in">this</span>.preserve = PRESERVE_BITS << position; <span class="comment">// 保留标志的bit模式左移2倍position</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><br><span class="line"> <span class="comment">// 下面这些静态变量就是直接返回标志对应的set/injec、清除和保留的bit map</span></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bit value to set or inject {<span class="doctag">@link</span> #DISTINCT}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">IS_DISTINCT</span> <span class="operator">=</span> DISTINCT.set;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bit value to clear {<span class="doctag">@link</span> #DISTINCT}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">NOT_DISTINCT</span> <span class="operator">=</span> DISTINCT.clear;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bit value to set or inject {<span class="doctag">@link</span> #SORTED}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">IS_SORTED</span> <span class="operator">=</span> SORTED.set;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bit value to clear {<span class="doctag">@link</span> #SORTED}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">NOT_SORTED</span> <span class="operator">=</span> SORTED.clear;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bit value to set or inject {<span class="doctag">@link</span> #ORDERED}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">IS_ORDERED</span> <span class="operator">=</span> ORDERED.set;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bit value to clear {<span class="doctag">@link</span> #ORDERED}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">NOT_ORDERED</span> <span class="operator">=</span> ORDERED.clear;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bit value to set {<span class="doctag">@link</span> #SIZED}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">IS_SIZED</span> <span class="operator">=</span> SIZED.set;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bit value to clear {<span class="doctag">@link</span> #SIZED}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">NOT_SIZED</span> <span class="operator">=</span> SIZED.clear;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The bit value to inject {<span class="doctag">@link</span> #SHORT_CIRCUIT}.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">IS_SHORT_CIRCUIT</span> <span class="operator">=</span> SHORT_CIRCUIT.set;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>又因为<code>StreamOpFlag</code>是一个枚举,一个枚举成员是一个独立的标志,而一个标志会对多个<code>Stream</code>实体类型产生作用,所以它的一个成员描述的是上面实体和标志映射关系的一个列(竖着看):</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-7.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-7.png" alt="img"></a></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line">// 纵向看</span><br><span class="line">DISTINCT Flag:</span><br><span class="line">maskTable: {</span><br><span class="line"> SPLITERATOR: 0000 0001,</span><br><span class="line"> STREAM: 0000 0001,</span><br><span class="line"> OP: 0000 0011,</span><br><span class="line"> TERMINAL_OP: 0000 0000,</span><br><span class="line"> UPSTREAM_TERMINAL_OP: 0000 0000</span><br><span class="line">}</span><br><span class="line">position(input): 0</span><br><span class="line">bitPosition: 0</span><br><span class="line">set: 1 => 0000 0000 0000 0000 0000 0000 0000 0001</span><br><span class="line">clear: 2 => 0000 0000 0000 0000 0000 0000 0000 0010</span><br><span class="line">preserve: 3 => 0000 0000 0000 0000 0000 0000 0000 0011</span><br><span class="line"></span><br><span class="line">SORTED Flag:</span><br><span class="line">maskTable: {</span><br><span class="line"> SPLITERATOR: 0000 0001,</span><br><span class="line"> STREAM: 0000 0001,</span><br><span class="line"> OP: 0000 0011,</span><br><span class="line"> TERMINAL_OP: 0000 0000,</span><br><span class="line"> UPSTREAM_TERMINAL_OP: 0000 0000</span><br><span class="line">}</span><br><span class="line">position(input): 1 </span><br><span class="line">bitPosition: 2</span><br><span class="line">set: 4 => 0000 0000 0000 0000 0000 0000 0000 0100</span><br><span class="line">clear: 8 => 0000 0000 0000 0000 0000 0000 0000 1000</span><br><span class="line">preserve: 12 => 0000 0000 0000 0000 0000 0000 0000 1100</span><br><span class="line"></span><br><span class="line">ORDERED Flag:</span><br><span class="line">maskTable: {</span><br><span class="line"> SPLITERATOR: 0000 0001,</span><br><span class="line"> STREAM: 0000 0001,</span><br><span class="line"> OP: 0000 0011,</span><br><span class="line"> TERMINAL_OP: 0000 0010,</span><br><span class="line"> UPSTREAM_TERMINAL_OP: 0000 0010</span><br><span class="line">}</span><br><span class="line">position(input): 2</span><br><span class="line">bitPosition: 4 </span><br><span class="line">set: 16 => 0000 0000 0000 0000 0000 0000 0001 0000</span><br><span class="line">clear: 32 => 0000 0000 0000 0000 0000 0000 0010 0000</span><br><span class="line">preserve: 48 => 0000 0000 0000 0000 0000 0000 0011 0000</span><br><span class="line"></span><br><span class="line">SIZED Flag:</span><br><span class="line">maskTable: {</span><br><span class="line"> SPLITERATOR: 0000 0001,</span><br><span class="line"> STREAM: 0000 0001,</span><br><span class="line"> OP: 0000 0010,</span><br><span class="line"> TERMINAL_OP: 0000 0000,</span><br><span class="line"> UPSTREAM_TERMINAL_OP: 0000 0000</span><br><span class="line">}</span><br><span class="line">position(input): 3</span><br><span class="line">bitPosition: 6 </span><br><span class="line">set: 64 => 0000 0000 0000 0000 0000 0000 0100 0000</span><br><span class="line">clear: 128 => 0000 0000 0000 0000 0000 0000 1000 0000</span><br><span class="line">preserve: 192 => 0000 0000 0000 0000 0000 0000 1100 0000</span><br><span class="line"></span><br><span class="line">SHORT_CIRCUIT Flag:</span><br><span class="line">maskTable: {</span><br><span class="line"> SPLITERATOR: 0000 0000,</span><br><span class="line"> STREAM: 0000 0000,</span><br><span class="line"> OP: 0000 0001,</span><br><span class="line"> TERMINAL_OP: 0000 0001,</span><br><span class="line"> UPSTREAM_TERMINAL_OP: 0000 0000</span><br><span class="line">}</span><br><span class="line">position(input): 12</span><br><span class="line">bitPosition: 24 </span><br><span class="line">set: 16777216 => 0000 0001 0000 0000 0000 0000 0000 0000</span><br><span class="line">clear: 33554432 => 0000 0010 0000 0000 0000 0000 0000 0000 </span><br><span class="line">preserve: 50331648 => 0000 0011 0000 0000 0000 0000 0000 0000</span><br></pre></td></tr></table></figure><p>接着就用到按位与(<code>&</code>)和按位或(<code>|</code>)的操作,假设<code>A = 0001</code>、<code>B = 0010</code>、<code>C = 1000</code>,那么:</p><ul><li><code>A|B = A | B = 0001 | 0010 = 0011</code>(按位或,<code>1|0=1, 0|1=1,0|0 =0,1|1=1</code>)</li><li><code>A&B = A & B = 0001 | 0010 = 0000</code>(按位与,<code>1|0=0, 0|1=0,0|0 =0,1|1=1</code>)</li><li><code>MASK = A | B | C = 0001 | 0010 | 1000 = 1011</code></li><li>那么判断<code>A|B</code>是否包含<code>A</code>的条件为:<code>A == (A|B & A)</code></li><li>那么判断<code>MASK</code>是否包含<code>A</code>的条件为:<code>A == MASK & A</code></li></ul><p>这里把<code>StreamOpFlag</code>中的枚举套用进去分析:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="type">int</span> <span class="variable">DISTINCT_SET</span> <span class="operator">=</span> <span class="number">0b0001</span>;</span><br><span class="line"><span class="keyword">static</span> <span class="type">int</span> <span class="variable">SORTED_CLEAR</span> <span class="operator">=</span> <span class="number">0b1000</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> <span class="comment">// 支持DISTINCT标志和不支持SORTED标志</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">flags</span> <span class="operator">=</span> DISTINCT_SET | SORTED_CLEAR;</span><br><span class="line"> System.out.println(Integer.toBinaryString(flags));</span><br><span class="line"> System.out.printf(<span class="string">"支持DISTINCT标志:%s\n"</span>, DISTINCT_SET == (DISTINCT_SET & flags));</span><br><span class="line"> System.out.printf(<span class="string">"不支持SORTED标志:%s\n"</span>, SORTED_CLEAR == (SORTED_CLEAR & flags));</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="number">1001</span></span><br><span class="line">支持DISTINCT标志:<span class="literal">true</span></span><br><span class="line">不支持SORTED标志:<span class="literal">true</span></span><br></pre></td></tr></table></figure><p>由于<code>StreamOpFlag</code>的修饰符是默认,不能直接使用,可以把它的代码拷贝出来修改包名验证里面的功能:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">flags</span> <span class="operator">=</span> StreamOpFlag.DISTINCT.set | StreamOpFlag.SORTED.clear;</span><br><span class="line"> System.out.println(StreamOpFlag.DISTINCT.set == (StreamOpFlag.DISTINCT.set & flags));</span><br><span class="line"> System.out.println(StreamOpFlag.SORTED.clear == (StreamOpFlag.SORTED.clear & flags));</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><br><span class="line"><span class="literal">true</span></span><br><span class="line"><span class="literal">true</span></span><br></pre></td></tr></table></figure><p>下面这些方法就是基于这些运算特性而定义的:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">StreamOpFlag</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 暂时忽略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回当前StreamOpFlag的set/inject的bit map</span></span><br><span class="line"> <span class="type">int</span> <span class="title function_">set</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> set;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回当前StreamOpFlag的清除的bit map</span></span><br><span class="line"> <span class="type">int</span> <span class="title function_">clear</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> clear;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里判断当前StreamOpFlag类型->标记映射中Stream类型的标记,如果大于0说明不是初始化状态,那么当前StreamOpFlag就是Stream相关的标志</span></span><br><span class="line"> <span class="type">boolean</span> <span class="title function_">isStreamFlag</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> maskTable.get(Type.STREAM) > <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里就用到按位与判断输入的flags中是否设置当前StreamOpFlag(StreamOpFlag.set)</span></span><br><span class="line"> <span class="type">boolean</span> <span class="title function_">isKnown</span><span class="params">(<span class="type">int</span> flags)</span> {</span><br><span class="line"> <span class="keyword">return</span> (flags & preserve) == set;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里就用到按位与判断输入的flags中是否清除当前StreamOpFlag(StreamOpFlag.clear)</span></span><br><span class="line"> <span class="type">boolean</span> <span class="title function_">isCleared</span><span class="params">(<span class="type">int</span> flags)</span> {</span><br><span class="line"> <span class="keyword">return</span> (flags & preserve) == clear;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里就用到按位与判断输入的flags中是否保留当前StreamOpFlag(StreamOpFlag.clear)</span></span><br><span class="line"> <span class="type">boolean</span> <span class="title function_">isPreserved</span><span class="params">(<span class="type">int</span> flags)</span> {</span><br><span class="line"> <span class="keyword">return</span> (flags & preserve) == preserve;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 判断当前的Stream实体类型是否可以设置本标志,要求Stream实体类型的标志位为set或者preserve,按位与要大于0</span></span><br><span class="line"> <span class="type">boolean</span> <span class="title function_">canSet</span><span class="params">(Type t)</span> {</span><br><span class="line"> <span class="keyword">return</span> (maskTable.get(t) & SET_BITS) > <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><br></pre></td></tr></table></figure><p>这里有个特殊操作,位运算的时候采用了<code>(flags & preserve)</code>,理由是:同一个标志中的同一个<code>Stream</code>实体类型只可能存在<code>set/inject</code>、<code>clear</code>和<code>preserve</code>的其中一种,也就是同一个<code>flags</code>中不可能同时存在<code>StreamOpFlag.SORTED.set</code>和<code>StreamOpFlag.SORTED.clear</code>,从语义上已经矛盾,而<code>set/inject</code>、<code>clear</code>和<code>preserve</code>在<code>bit map</code>中的大小(为<code>2</code>位)和位置已经是固定的,<code>preserve</code>在设计的时候为<code>0b11</code>刚好<code>2</code>位取反,因此可以特化为(这个特化也让判断更加严谨):</p><figure class="highlight shell"><table><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">(flags & set) == set => (flags & preserve) == set</span><br><span class="line">(flags & clear) == clear => (flags & preserve) == clear</span><br><span class="line">(flags & preserve) == preserve => (flags & preserve) == preserve</span><br></pre></td></tr></table></figure><p>分析这么多,总的来说,就是想通过一个<code>32</code>位整数,每<code>2</code>位分别表示<code>3</code>种状态,那么一个完整的<code>Flags</code>(标志集合)一共可以表示<code>16</code>种标志(<code>position=[0,15]</code>,可以查看<code>API</code>注释,<code>[4,11]</code>和<code>[13,15]</code>的位置是未需实现或者预留的,属于<code>gap</code>)。接着分析掩码<code>Mask</code>的计算过程例子:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">// 横向看(位移动运算符优先级高于与或,例如<<的优先级比|高)</span><br><span class="line">SPLITERATOR_CHARACTERISTICS_MASK:</span><br><span class="line">mask(init) = 0</span><br><span class="line">mask(DISTINCT,SPLITERATOR[DISTINCT]=01,bitPosition=0) = 0000 0000 | 0000 0001 << 0 = 0000 0000 | 0000 0001 = 0000 0001</span><br><span class="line">mask(SORTED,SPLITERATOR[SORTED]=01,bitPosition=2) = 0000 0001 | 0000 0001 << 2 = 0000 0001 | 0000 0100 = 0000 0101</span><br><span class="line">mask(ORDERED,SPLITERATOR[ORDERED]=01,bitPosition=4) = 0000 0101 | 0000 0001 << 4 = 0000 0101 | 0001 0000 = 0001 0101</span><br><span class="line">mask(SIZED,SPLITERATOR[SIZED]=01,bitPosition=6) = 0001 0101 | 0000 0001 << 6 = 0001 0101 | 0100 0000 = 0101 0101</span><br><span class="line">mask(SHORT_CIRCUIT,SPLITERATOR[SHORT_CIRCUIT]=00,bitPosition=24) = 0101 0101 | 0000 0000 << 24 = 0101 0101 | 0000 0000 = 0101 0101</span><br><span class="line">mask(final) = 0000 0000 0000 0000 0000 0000 0101 0101(二进制)、85(十进制)</span><br><span class="line"></span><br><span class="line">STREAM_MASK:</span><br><span class="line">mask(init) = 0</span><br><span class="line">mask(DISTINCT,SPLITERATOR[DISTINCT]=01,bitPosition=0) = 0000 0000 | 0000 0001 << 0 = 0000 0000 | 0000 0001 = 0000 0001</span><br><span class="line">mask(SORTED,SPLITERATOR[SORTED]=01,bitPosition=2) = 0000 0001 | 0000 0001 << 2 = 0000 0001 | 0000 0100 = 0000 0101</span><br><span class="line">mask(ORDERED,SPLITERATOR[ORDERED]=01,bitPosition=4) = 0000 0101 | 0000 0001 << 4 = 0000 0101 | 0001 0000 = 0001 0101</span><br><span class="line">mask(SIZED,SPLITERATOR[SIZED]=01,bitPosition=6) = 0001 0101 | 0000 0001 << 6 = 0001 0101 | 0100 0000 = 0101 0101</span><br><span class="line">mask(SHORT_CIRCUIT,SPLITERATOR[SHORT_CIRCUIT]=00,bitPosition=24) = 0101 0101 | 0000 0000 << 24 = 0101 0101 | 0000 0000 = 0101 0101</span><br><span class="line">mask(final) = 0000 0000 0000 0000 0000 0000 0101 0101(二进制)、85(十进制)</span><br><span class="line"></span><br><span class="line">OP_MASK:</span><br><span class="line">mask(init) = 0</span><br><span class="line">mask(DISTINCT,SPLITERATOR[DISTINCT]=11,bitPosition=0) = 0000 0000 | 0000 0011 << 0 = 0000 0000 | 0000 0011 = 0000 0011</span><br><span class="line">mask(SORTED,SPLITERATOR[SORTED]=11,bitPosition=2) = 0000 0011 | 0000 0011 << 2 = 0000 0011 | 0000 1100 = 0000 1111</span><br><span class="line">mask(ORDERED,SPLITERATOR[ORDERED]=11,bitPosition=4) = 0000 1111 | 0000 0011 << 4 = 0000 1111 | 0011 0000 = 0011 1111</span><br><span class="line">mask(SIZED,SPLITERATOR[SIZED]=10,bitPosition=6) = 0011 1111 | 0000 0010 << 6 = 0011 1111 | 1000 0000 = 1011 1111</span><br><span class="line">mask(SHORT_CIRCUIT,SPLITERATOR[SHORT_CIRCUIT]=01,bitPosition=24) = 1011 1111 | 0000 0001 << 24 = 1011 1111 | 0100 0000 0000 0000 0000 0000 0000 = 0100 0000 0000 0000 0000 1011 1111</span><br><span class="line">mask(final) = 0000 0000 1000 0000 0000 0000 1011 1111(二进制)、16777407(十进制)</span><br><span class="line"></span><br><span class="line">TERMINAL_OP_MASK:</span><br><span class="line">mask(init) = 0</span><br><span class="line">mask(DISTINCT,SPLITERATOR[DISTINCT]=00,bitPosition=0) = 0000 0000 | 0000 0000 << 0 = 0000 0000 | 0000 0000 = 0000 0000</span><br><span class="line">mask(SORTED,SPLITERATOR[SORTED]=00,bitPosition=2) = 0000 0000 | 0000 0000 << 2 = 0000 0000 | 0000 0000 = 0000 0000</span><br><span class="line">mask(ORDERED,SPLITERATOR[ORDERED]=10,bitPosition=4) = 0000 0000 | 0000 0010 << 4 = 0000 0000 | 0010 0000 = 0010 0000</span><br><span class="line">mask(SIZED,SPLITERATOR[SIZED]=00,bitPosition=6) = 0010 0000 | 0000 0000 << 6 = 0010 0000 | 0000 0000 = 0010 0000</span><br><span class="line">mask(SHORT_CIRCUIT,SPLITERATOR[SHORT_CIRCUIT]=01,bitPosition=24) = 0010 0000 | 0000 0001 << 24 = 0010 0000 | 0001 0000 0000 0000 0000 0000 0000 = 0001 0000 0000 0000 0000 0010 0000</span><br><span class="line">mask(final) = 0000 0001 0000 0000 0000 0000 0010 0000(二进制)、16777248(十进制)</span><br><span class="line"></span><br><span class="line">UPSTREAM_TERMINAL_OP_MASK:</span><br><span class="line">mask(init) = 0</span><br><span class="line">mask(DISTINCT,SPLITERATOR[DISTINCT]=00,bitPosition=0) = 0000 0000 | 0000 0000 << 0 = 0000 0000 | 0000 0000 = 0000 0000</span><br><span class="line">mask(SORTED,SPLITERATOR[SORTED]=00,bitPosition=2) = 0000 0000 | 0000 0000 << 2 = 0000 0000 | 0000 0000 = 0000 0000</span><br><span class="line">mask(ORDERED,SPLITERATOR[ORDERED]=10,bitPosition=4) = 0000 0000 | 0000 0010 << 4 = 0000 0000 | 0010 0000 = 0010 0000</span><br><span class="line">mask(SIZED,SPLITERATOR[SIZED]=00,bitPosition=6) = 0010 0000 | 0000 0000 << 6 = 0010 0000 | 0000 0000 = 0010 0000</span><br><span class="line">mask(SHORT_CIRCUIT,SPLITERATOR[SHORT_CIRCUIT]=00,bitPosition=24) = 0010 0000 | 0000 0000 << 24 = 0010 0000 | 0000 0000 = 0010 0000</span><br><span class="line">mask(final) = 0000 0000 0000 0000 0000 0000 0010 0000(二进制)、32(十进制)</span><br></pre></td></tr></table></figure><p>相关的方法和属性如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">StreamOpFlag</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// SPLITERATOR类型的标志bit map</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SPLITERATOR_CHARACTERISTICS_MASK</span> <span class="operator">=</span> createMask(Type.SPLITERATOR);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// STREAM类型的标志bit map</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">STREAM_MASK</span> <span class="operator">=</span> createMask(Type.STREAM);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// OP类型的标志bit map</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">OP_MASK</span> <span class="operator">=</span> createMask(Type.OP);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// TERMINAL_OP类型的标志bit map</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TERMINAL_OP_MASK</span> <span class="operator">=</span> createMask(Type.TERMINAL_OP);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// UPSTREAM_TERMINAL_OP类型的标志bit map</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">UPSTREAM_TERMINAL_OP_MASK</span> <span class="operator">=</span> createMask(Type.UPSTREAM_TERMINAL_OP);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 基于Stream类型,创建对应类型填充所有标志的bit map</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">createMask</span><span class="params">(Type t)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">mask</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (StreamOpFlag flag : StreamOpFlag.values()) {</span><br><span class="line"> mask |= flag.maskTable.get(t) << flag.bitPosition;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> mask;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构造一个标志本身的掩码,就是所有标志都采用保留位表示,目前作为flags == 0时候的初始值</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">FLAG_MASK</span> <span class="operator">=</span> createFlagMask();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 构造一个包含全部标志中的preserve位的bit map,按照目前来看是暂时是一个固定值,二进制表示为0011 0000 0000 0000 0000 1111 1111</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">createFlagMask</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">mask</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (StreamOpFlag flag : StreamOpFlag.values()) {</span><br><span class="line"> mask |= flag.preserve;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> mask;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构造一个Stream类型包含全部标志中的set位的bit map,这里直接使用了STREAM_MASK,按照目前来看是暂时是一个固定值,二进制表示为0000 0000 0000 0000 0000 0000 0101 0101</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">FLAG_MASK_IS</span> <span class="operator">=</span> STREAM_MASK;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构造一个Stream类型包含全部标志中的clear位的bit map,按照目前来看是暂时是一个固定值,二进制表示为0000 0000 0000 0000 0000 0000 1010 1010</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">FLAG_MASK_NOT</span> <span class="operator">=</span> STREAM_MASK << <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 初始化操作的标志bit map,目前来看就是Stream的头节点初始化时候需要合并在flags里面的初始化值,照目前来看是暂时是一个固定值,二进制表示为0000 0000 0000 0000 0000 0000 1111 1111</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">INITIAL_OPS_VALUE</span> <span class="operator">=</span> FLAG_MASK_IS | FLAG_MASK_NOT;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>SPLITERATOR_CHARACTERISTICS_MASK</code>等<code>5</code>个成员(见上面的<code>Mask</code>计算例子)其实就是预先计算好对应的<code>Stream</code>实体类型的<strong>所有<code>StreamOpFlag</code>标志</strong>的<code>bit map</code>,也就是之前那个展示<code>Stream</code>的类型和标志的映射图的”横向”展示:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-8.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-8.png" alt="img"></a></p><p>前面的分析已经相对详细,过程非常复杂,但是更复杂的<code>Mask</code>应用还在后面的方法。<code>Mask</code>的初始化就是提供给标志的合并(<code>combine</code>)和转化(从<code>Spliterator</code>中的<code>characteristics</code>转化为<code>flags</code>)操作的,见下面的方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">StreamOpFlag</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这个方法完全没有注释,只使用在下面的combineOpFlags()方法中</span></span><br><span class="line"> <span class="comment">// 从源码来看</span></span><br><span class="line"> <span class="comment">// 入参flags == 0的时候,那么直接返回0011 0000 0000 0000 0000 1111 1111</span></span><br><span class="line"> <span class="comment">// 入参flags != 0的时候,那么会把当前flags的所有set/inject、clear和preserve所在位在bit map中全部置为0,然后其他位全部置为1</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">getMask</span><span class="params">(<span class="type">int</span> flags)</span> {</span><br><span class="line"> <span class="keyword">return</span> (flags == <span class="number">0</span>)</span><br><span class="line"> ? FLAG_MASK</span><br><span class="line"> : ~(flags | ((FLAG_MASK_IS & flags) << <span class="number">1</span>) | ((FLAG_MASK_NOT & flags) >> <span class="number">1</span>));</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 合并新的flags和前一个flags,这里还是用到老套路先和Mask按位与,再进行一次按位或</span></span><br><span class="line"> <span class="comment">// 作为Stream的头节点的时候,prevCombOpFlags必须为INITIAL_OPS_VALUE</span></span><br><span class="line"> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">combineOpFlags</span><span class="params">(<span class="type">int</span> newStreamOrOpFlags, <span class="type">int</span> prevCombOpFlags)</span> {</span><br><span class="line"> <span class="comment">// 0x01 or 0x10 nibbles are transformed to 0x11</span></span><br><span class="line"> <span class="comment">// 0x00 nibbles remain unchanged</span></span><br><span class="line"> <span class="comment">// Then all the bits are flipped</span></span><br><span class="line"> <span class="comment">// Then the result is logically or'ed with the operation flags.</span></span><br><span class="line"> <span class="keyword">return</span> (prevCombOpFlags & StreamOpFlag.getMask(newStreamOrOpFlags)) | newStreamOrOpFlags;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 通过合并后的flags,转换出Stream类型的flags</span></span><br><span class="line"> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">toStreamFlags</span><span class="params">(<span class="type">int</span> combOpFlags)</span> {</span><br><span class="line"> <span class="comment">// By flipping the nibbles 0x11 become 0x00 and 0x01 become 0x10</span></span><br><span class="line"> <span class="comment">// Shift left 1 to restore set flags and mask off anything other than the set flags</span></span><br><span class="line"> <span class="keyword">return</span> ((~combOpFlags) >> <span class="number">1</span>) & FLAG_MASK_IS & combOpFlags;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Stream的标志转换为Spliterator的characteristics</span></span><br><span class="line"> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">toCharacteristics</span><span class="params">(<span class="type">int</span> streamFlags)</span> {</span><br><span class="line"> <span class="keyword">return</span> streamFlags & SPLITERATOR_CHARACTERISTICS_MASK;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Spliterator的characteristics转换为Stream的标志,入参是Spliterator实例</span></span><br><span class="line"> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">fromCharacteristics</span><span class="params">(Spliterator<?> spliterator)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">characteristics</span> <span class="operator">=</span> spliterator.characteristics();</span><br><span class="line"> <span class="keyword">if</span> ((characteristics & Spliterator.SORTED) != <span class="number">0</span> && spliterator.getComparator() != <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// Do not propagate the SORTED characteristic if it does not correspond</span></span><br><span class="line"> <span class="comment">// to a natural sort order</span></span><br><span class="line"> <span class="keyword">return</span> characteristics & SPLITERATOR_CHARACTERISTICS_MASK & ~Spliterator.SORTED;</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> characteristics & SPLITERATOR_CHARACTERISTICS_MASK;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Spliterator的characteristics转换为Stream的标志,入参是Spliterator的characteristics</span></span><br><span class="line"> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">fromCharacteristics</span><span class="params">(<span class="type">int</span> characteristics)</span> {</span><br><span class="line"> <span class="keyword">return</span> characteristics & SPLITERATOR_CHARACTERISTICS_MASK;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的位运算很复杂,只展示简单的计算结果和相关功能:</p><ul><li><code>combineOpFlags()</code>:用于合并新的<code>flags</code>和上一个<code>flags</code>,因为<code>Stream</code>的数据结构是一个<code>Pipeline</code>,后继节点需要合并前驱节点的<code>flags</code>,例如前驱节点<code>flags</code>是<code>ORDERED.set</code>,当前新加入<code>Pipeline</code>的节点(后继节点)的新<code>flags</code>为<code>SIZED.set</code>,那么在后继节点中应该合并前驱节点的标志,简单想象为<code>SIZED.set | ORDERED.set</code>,如果是头节点,那么初始化头节点时候的<code>flags</code>要合并<code>INITIAL_OPS_VALUE</code>,这里举个例子:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> ORDERED.set | DISTINCT.set;</span><br><span class="line"><span class="type">int</span> <span class="variable">right</span> <span class="operator">=</span> SIZED.clear | SORTED.clear;</span><br><span class="line">System.out.println(<span class="string">"left:"</span> + Integer.toBinaryString(left));</span><br><span class="line">System.out.println(<span class="string">"right:"</span> + Integer.toBinaryString(right));</span><br><span class="line">System.out.println(<span class="string">"right mask:"</span> + Integer.toBinaryString(getMask(right)));</span><br><span class="line">System.out.println(<span class="string">"combine:"</span> + Integer.toBinaryString(combineOpFlags(right, left)));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出结果</span></span><br><span class="line">left:<span class="number">1010001</span></span><br><span class="line">right:<span class="number">10001000</span></span><br><span class="line">right mask:<span class="number">11111111111111111111111100110011</span></span><br><span class="line">combine:<span class="number">10011001</span></span><br></pre></td></tr></table></figure><ul><li><code>characteristics</code>的转化问题:<code>Spliterator</code>中的<code>characteristics</code>可以通过简单的按位与转换为<code>flags</code>的原因是<code>Spliterator</code>中的<code>characteristics</code>在设计时候本身就是和<code>StreamOpFlag</code>匹配的,准确来说就是<code>bit map</code>的位分布是匹配的,所以直接与<code>SPLITERATOR_CHARACTERISTICS_MASK</code>做按位与即可,见下面的例子:</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 这里简单点只展示8 bit</span><br><span class="line">SPLITERATOR_CHARACTERISTICS_MASK: 0101 0101</span><br><span class="line">Spliterator.ORDERED: 0001 0000</span><br><span class="line">StreamOpFlag.ORDERED.set: 0001 0000</span><br></pre></td></tr></table></figure><p>至此,已经分析完<code>StreamOpFlag</code>的完整实现,<code>Mask</code>相关的方法限于篇幅就不打算详细展开,下面会开始分析<code>Stream</code>中的”流水线”结构实现,因为习惯问题,下文的”标志”和”特性”两个词语会混用。</p><h2 id="ReferencePipeline源码分析"><a href="#ReferencePipeline源码分析" class="headerlink" title="ReferencePipeline源码分析"></a>ReferencePipeline源码分析</h2><p>既然<code>Stream</code>具备流的特性,那么就需要一个链式数据结构,让元素能够从<code>Source</code>一直往下”流动”和传递到每一个链节点,实现这种场景的常用数据结构就是双向链表(考虑需要回溯,单向链表不太合适),目前比较著名的实现有<code>AQS</code>和<code>Netty</code>中的<code>ChannelHandlerContext</code>。例如<code>Netty</code>中的流水线<code>ChannelPipeline</code>设计如下:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-6.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-6.png" alt="img"></a></p><p>对于这个双向链表的数据结构,<code>Stream</code>中对应的类就是<code>AbstractPipeline</code>,核心实现类在<code>ReferencePipeline</code>和<code>ReferencePipeline</code>的内部类。</p><h3 id="主要接口"><a href="#主要接口" class="headerlink" title="主要接口"></a>主要接口</h3><p>先简单展示<code>AbstractPipeline</code>的核心父类方法定义,主要接父类是<code>Stream</code>、<code>BaseStream</code>和<code>PipelineHelper</code>:</p><ul><li><code>Stream</code>代表一个支持串行和并行聚合操作集合的元素序列,此顶层接口提供了流中间操作、终结操作和一些静态工厂方法的定义(由于方法太多,这里不全部列举),这个接口本质是一个建造器类型接口(对接中间操作来说),可以构成一个多中间操作,单终结操作的链,例如:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Stream</span><T> <span class="keyword">extends</span> <span class="title class_">BaseStream</span><T, Stream<T>> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 忽略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 过滤Op</span></span><br><span class="line"> Stream<T> <span class="title function_">filter</span><span class="params">(Predicate<? <span class="built_in">super</span> T> predicate)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 映射Op</span></span><br><span class="line"> <R> Stream<R> <span class="title function_">map</span><span class="params">(Function<? <span class="built_in">super</span> T, ? extends R> mapper)</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 终结操作 - 遍历</span></span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">forEach</span><span class="params">(Consumer<? <span class="built_in">super</span> T> action)</span>;</span><br><span class="line"></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 class="comment">// init</span></span><br><span class="line"><span class="type">Stream</span> <span class="variable">x</span> <span class="operator">=</span> buildStream();</span><br><span class="line"><span class="comment">// chain: head -> filter(Op) -> map(Op) -> forEach(Terminal Op)</span></span><br><span class="line">x.filter().map().forEach()</span><br></pre></td></tr></table></figure><ul><li><code>BaseStream</code>:<code>Stream</code>的基础接口,定义流的迭代器、流的等效变体(并发处理变体、同步处理变体和不支持顺序处理元素变体)、并发和同步判断以及关闭相关方法</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// T是元素类型,S是BaseStream<T, S>类型</span></span><br><span class="line"><span class="comment">// 流的基础接口,这里的流指定的支持同步执行和异步执行的聚合操作的元素序列</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BaseStream</span><T, S <span class="keyword">extends</span> <span class="title class_">BaseStream</span><T, S>> <span class="keyword">extends</span> <span class="title class_">AutoCloseable</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回一个当前Stream实例中所有元素的迭代器</span></span><br><span class="line"> <span class="comment">// 这是一个终结操作</span></span><br><span class="line"> Iterator<T> <span class="title function_">iterator</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回一个当前Stream实例中所有元素的可拆分迭代器</span></span><br><span class="line"> Spliterator<T> <span class="title function_">spliterator</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 当前的Stream实例是否支持并发</span></span><br><span class="line"> <span class="type">boolean</span> <span class="title function_">isParallel</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回一个等效的同步处理的Stream实例</span></span><br><span class="line"> S <span class="title function_">sequential</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回一个等效的并发处理的Stream实例</span></span><br><span class="line"> S <span class="title function_">parallel</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回一个等效的不支持StreamOpFlag.ORDERED特性的Stream实例</span></span><br><span class="line"> <span class="comment">// 或者说支持StreamOpFlag.NOT_ORDERED的特性,也就返回的变体Stream在处理元素的时候不需要顺序处理</span></span><br><span class="line"> S <span class="title function_">unordered</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回一个添加了close处理器的Stream实例,close处理器会在下面的close方法中回调</span></span><br><span class="line"> S <span class="title function_">onClose</span><span class="params">(Runnable closeHandler)</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 关闭当前Stream实例,回调关联本Stream的所有close处理器</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">close</span><span class="params">()</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li><code>PipelineHelper</code>:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">PipelineHelper</span><P_OUT> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取流的流水线的数据源的"形状",其实就是数据源元素的类型</span></span><br><span class="line"> <span class="comment">// 主要有四种类型:REFERENCE(除了int、long和double之外的引用类型)、INT_VALUE、LONG_VALUE和DOUBLE_VALUE</span></span><br><span class="line"> <span class="keyword">abstract</span> StreamShape <span class="title function_">getSourceShape</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 class="comment">// 从实现上看是当前流管道节点合并前面所有节点和自身节点标志的所有标志</span></span><br><span class="line"> <span class="keyword">abstract</span> <span class="type">int</span> <span class="title function_">getStreamAndOpFlags</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果当前的流管道节点的合并标志集合支持SIZED,则调用Spliterator.getExactSizeIfKnown()返回数据源中的准确元素数量,否则返回-1</span></span><br><span class="line"> <span class="keyword">abstract</span><P_IN> <span class="type">long</span> <span class="title function_">exactOutputSizeIfKnown</span><span class="params">(Spliterator<P_IN> spliterator)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 相当于调用下面的方法组合:copyInto(wrapSink(sink), spliterator)</span></span><br><span class="line"> <span class="keyword">abstract</span><P_IN, S <span class="keyword">extends</span> <span class="title class_">Sink</span><P_OUT>> S <span class="title function_">wrapAndCopyInto</span><span class="params">(S sink, Spliterator<P_IN> spliterator)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 发送所有来自Spliterator中的元素到Sink中,如果支持SHORT_CIRCUIT标志,则会调用copyIntoWithCancel</span></span><br><span class="line"> <span class="keyword">abstract</span><P_IN> <span class="keyword">void</span> <span class="title function_">copyInto</span><span class="params">(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 发送所有来自Spliterator中的元素到Sink中,Sink处理完每个元素后会检查Sink#cancellationRequested()方法的状态去判断是否中断推送元素的操作</span></span><br><span class="line"> <span class="keyword">abstract</span> <P_IN> <span class="type">boolean</span> <span class="title function_">copyIntoWithCancel</span><span class="params">(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建接收元素类型为P_IN的Sink实例,实现PipelineHelper中描述的所有中间操作,用这个Sink去包装传入的Sink实例(传入的Sink实例的元素类型为PipelineHelper的输出类型P_OUT)</span></span><br><span class="line"> <span class="keyword">abstract</span><P_IN> Sink<P_IN> <span class="title function_">wrapSink</span><span class="params">(Sink<P_OUT> sink)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 包装传入的spliterator,从源码来看,在Stream链的头节点调用会直接返回传入的实例,如果在非头节点调用会委托到StreamSpliterators.WrappingSpliterator()方法进行包装</span></span><br><span class="line"> <span class="comment">// 这个方法在源码中没有API注释</span></span><br><span class="line"> <span class="keyword">abstract</span><P_IN> Spliterator<P_OUT> <span class="title function_">wrapSpliterator</span><span class="params">(Spliterator<P_IN> spliterator)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构造一个兼容当前Stream元素"形状"的Node.Builder实例</span></span><br><span class="line"> <span class="comment">// 从源码来看直接委托到Nodes.builder()方法</span></span><br><span class="line"> <span class="keyword">abstract</span> Node.Builder<P_OUT> <span class="title function_">makeNodeBuilder</span><span class="params">(<span class="type">long</span> exactSizeIfKnown,</span></span><br><span class="line"><span class="params"> IntFunction<P_OUT[]> generator)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Stream流水线所有阶段(节点)应用于数据源Spliterator,输出的元素作为结果收集起来转化为Node实例</span></span><br><span class="line"> <span class="comment">// 此方法应用于toArray()方法的计算,本质上是一个终结操作</span></span><br><span class="line"> <span class="keyword">abstract</span><P_IN> Node<P_OUT> <span class="title function_">evaluate</span><span class="params">(Spliterator<P_IN> spliterator,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> flatten,</span></span><br><span class="line"><span class="params"> IntFunction<P_OUT[]> generator)</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意一点(重复<code>3</code>次):</p><ul><li>这里把同步流称为同步处理|执行的流,”并行流”称为并发处理|执行的流,因为并行流有歧义,<strong>实际上只是并发执行,不是并行执行</strong></li><li>这里把同步流称为同步处理|执行的流,”并行流”称为并发处理|执行的流,因为并行流有歧义,<strong>实际上只是并发执行,不是并行执行</strong></li><li>这里把同步流称为同步处理|执行的流,”并行流”称为并发处理|执行的流,因为并行流有歧义,<strong>实际上只是并发执行,不是并行执行</strong></li></ul><h3 id="Sink和引用类型链"><a href="#Sink和引用类型链" class="headerlink" title="Sink和引用类型链"></a>Sink和引用类型链</h3><p><code>PipelineHelper</code>的几个方法中存在<code>Sink</code>这个接口,上一节没有分析,这一小节会详细展开。<code>Stream</code>在构建的时候虽然是一个双向链表的结构,但是在最终应用终结操作的时候,会把所有操作转化为引用类型链(<code>ChainedReference</code>),记得之前也提到过这种类似于多层包装器的编程模式,简化一下模型如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WrapperApp</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">interface</span> <span class="title class_">Wrapper</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">doAction</span><span class="params">()</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="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="type">AtomicInteger</span> <span class="variable">counter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);</span><br><span class="line"> <span class="type">Wrapper</span> <span class="variable">first</span> <span class="operator">=</span> () -> System.out.printf(<span class="string">"wrapper [depth => %d] invoke\n"</span>, counter.incrementAndGet());</span><br><span class="line"> <span class="type">Wrapper</span> <span class="variable">second</span> <span class="operator">=</span> () -> {</span><br><span class="line"> first.doAction();</span><br><span class="line"> System.out.printf(<span class="string">"wrapper [depth => %d] invoke\n"</span>, counter.incrementAndGet());</span><br><span class="line"> };</span><br><span class="line"> second.doAction();</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">wrapper [depth => <span class="number">1</span>] invoke</span><br><span class="line">wrapper [depth => <span class="number">2</span>] invoke</span><br></pre></td></tr></table></figure><p>上面的例子有点突兀,两个不同<code>Sink</code>的实现可以做到无感知融合,举另一个例子如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Sink</span><T> <span class="keyword">extends</span> <span class="title class_">Consumer</span><T> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">begin</span><span class="params">(<span class="type">long</span> size)</span> {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">end</span><span class="params">()</span> {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ChainedReference</span><T, OUT> <span class="keyword">implements</span> <span class="title class_">Sink</span><T> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">final</span> Sink<OUT> downstream;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">ChainedReference</span><span class="params">(Sink<OUT> downstream)</span> {</span><br><span class="line"> <span class="built_in">this</span>.downstream = downstream;</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="meta">@SuppressWarnings({"unchecked", "rawtypes"})</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReferenceChain</span><OUT, R> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * sink chain</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> List<Supplier<Sink<?>>> sinkBuilders = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * current sink</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> AtomicReference<Sink> sinkReference = <span class="keyword">new</span> <span class="title class_">AtomicReference</span><>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> ReferenceChain<OUT, R> <span class="title function_">filter</span><span class="params">(Predicate<OUT> predicate)</span> {</span><br><span class="line"> <span class="comment">//filter</span></span><br><span class="line"> sinkBuilders.add(() -> {</span><br><span class="line"> Sink<OUT> prevSink = (Sink<OUT>) sinkReference.get();</span><br><span class="line"> Sink.ChainedReference<OUT, OUT> currentSink = <span class="keyword">new</span> <span class="title class_">Sink</span>.ChainedReference<>(prevSink) {</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="keyword">void</span> <span class="title function_">accept</span><span class="params">(OUT out)</span> {</span><br><span class="line"> <span class="keyword">if</span> (predicate.test(out)) {</span><br><span class="line"> downstream.accept(out);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> sinkReference.set(currentSink);</span><br><span class="line"> <span class="keyword">return</span> currentSink;</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> ReferenceChain<OUT, R> <span class="title function_">map</span><span class="params">(Function<OUT, R> function)</span> {</span><br><span class="line"> <span class="comment">// map</span></span><br><span class="line"> sinkBuilders.add(() -> {</span><br><span class="line"> Sink<R> prevSink = (Sink<R>) sinkReference.get();</span><br><span class="line"> Sink.ChainedReference<OUT, R> currentSink = <span class="keyword">new</span> <span class="title class_">Sink</span>.ChainedReference<>(prevSink) {</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="keyword">void</span> <span class="title function_">accept</span><span class="params">(OUT in)</span> {</span><br><span class="line"> downstream.accept(function.apply(in));</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> sinkReference.set(currentSink);</span><br><span class="line"> <span class="keyword">return</span> currentSink;</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">forEachPrint</span><span class="params">(Collection<OUT> collection)</span> {</span><br><span class="line"> forEachPrint(collection, <span class="literal">false</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">forEachPrint</span><span class="params">(Collection<OUT> collection, <span class="type">boolean</span> reverse)</span> {</span><br><span class="line"> Spliterator<OUT> spliterator = collection.spliterator();</span><br><span class="line"> <span class="comment">// 这个是类似于terminal op</span></span><br><span class="line"> Sink<OUT> sink = System.out::println;</span><br><span class="line"> sinkReference.set(sink);</span><br><span class="line"> Sink<OUT> stage = sink;</span><br><span class="line"> <span class="comment">// 反向包装 -> 正向遍历</span></span><br><span class="line"> <span class="keyword">if</span> (reverse) {</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 <= sinkBuilders.size() - <span class="number">1</span>; i++) {</span><br><span class="line"> Supplier<Sink<?>> supplier = sinkBuilders.get(i);</span><br><span class="line"> stage = (Sink<OUT>) supplier.get();</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 class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> sinkBuilders.size() - <span class="number">1</span>; i >= <span class="number">0</span>; i--) {</span><br><span class="line"> Supplier<Sink<?>> supplier = sinkBuilders.get(i);</span><br><span class="line"> stage = (Sink<OUT>) supplier.get();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> Sink<OUT> finalStage = stage;</span><br><span class="line"> spliterator.forEachRemaining(finalStage);</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="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> List<Integer> list = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> list.add(<span class="number">1</span>);</span><br><span class="line"> list.add(<span class="number">2</span>);</span><br><span class="line"> list.add(<span class="number">3</span>);</span><br><span class="line"> list.add(<span class="number">12</span>);</span><br><span class="line"> ReferenceChain<Integer, Integer> chain = <span class="keyword">new</span> <span class="title class_">ReferenceChain</span><>();</span><br><span class="line"> <span class="comment">// filter -> map -> for each</span></span><br><span class="line"> chain.filter(item -> item > <span class="number">10</span>)</span><br><span class="line"> .map(item -> item * <span class="number">2</span>)</span><br><span class="line"> .forEachPrint(list);</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="number">24</span></span><br></pre></td></tr></table></figure><p>执行的流程如下:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-9.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-9.png" alt="img"></a></p><p>多层包装器的编程模式的核心要领就是:</p><ul><li>绝大部分操作可以转换为<code>java.util.function.Consumer</code>的实现,也就是实现<code>accept(T t)</code>方法完成对传入的元素进行处理</li><li>先处理的<code>Sink</code>总是以后处理的<code>Sink</code>为入参,在自身处理方法中判断和回调传入的<code>Sink</code>的处理方法回调,也就是构建引用链的时候,需要从后往前构建,这种方式的实现逻辑可以参考<code>AbstractPipeline#wrapSink()</code>,例如:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 目标顺序:filter -> map</span></span><br><span class="line"><span class="type">Sink</span> <span class="variable">mapSink</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Sink</span>(inputSink){</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Function mapper;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">accept</span><span class="params">(E ele)</span> {</span><br><span class="line"> inputSink.accept(mapper.apply(ele))</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"><span class="type">Sink</span> <span class="variable">filterSink</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Sink</span>(mapSink){</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Predicate predicate;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">accept</span><span class="params">(E ele)</span> {</span><br><span class="line"> <span class="keyword">if</span>(predicate.test(ele)){</span><br><span class="line"> mapSink.accept(ele);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>由上一点得知,一般来说,最后的终结操作会应用在引用链的第一个<code>Sink</code>上</li></ul><p>上面的代码并非笔者虚构出来,可见<code>java.util.stream.Sink</code>的源码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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="comment">// 继承自Consumer,主要是继承函数式接口方法void accept(T t)</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Sink</span><T> <span class="keyword">extends</span> <span class="title class_">Consumer</span><T> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 重置当前Sink的状态(为了接收一个新的数据集),传入的size是推送到downstream的准确数据量,无法评估数据量则传入-1</span></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">begin</span><span class="params">(<span class="type">long</span> size)</span> {}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// </span></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">end</span><span class="params">()</span> {}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回true的时候表示当前的Sink不会接收数据</span></span><br><span class="line"> <span class="keyword">default</span> <span class="type">boolean</span> <span class="title function_">cancellationRequested</span><span class="params">()</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 class="comment">// 特化方法,接受一个int类型的值</span></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">accept</span><span class="params">(<span class="type">int</span> value)</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">"called wrong accept method"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 特化方法,接受一个long类型的值</span></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">accept</span><span class="params">(<span class="type">long</span> value)</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">"called wrong accept method"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 特化方法,接受一个double类型的值</span></span><br><span class="line"> <span class="keyword">default</span> <span class="keyword">void</span> <span class="title function_">accept</span><span class="params">(<span class="type">double</span> value)</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">"called wrong accept method"</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 引用类型链,准确来说是Sink链</span></span><br><span class="line"> <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ChainedReference</span><T, E_OUT> <span class="keyword">implements</span> <span class="title class_">Sink</span><T> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 下一个Sink</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">final</span> Sink<? <span class="built_in">super</span> E_OUT> downstream;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">ChainedReference</span><span class="params">(Sink<? <span class="built_in">super</span> E_OUT> downstream)</span> {</span><br><span class="line"> <span class="built_in">this</span>.downstream = Objects.requireNonNull(downstream);</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">begin</span><span class="params">(<span class="type">long</span> size)</span> {</span><br><span class="line"> downstream.begin(size);</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">end</span><span class="params">()</span> {</span><br><span class="line"> downstream.end();</span><br><span class="line"> }</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_">cancellationRequested</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> downstream.cancellationRequested();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 暂时忽略Int、Long、Double的特化类型场景</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果用过<code>RxJava</code>或者<code>Project-Reactor</code>,<code>Sink</code>更像是<code>Subscriber</code>,多个<code>Subscriber</code>组成了<code>ChainedReference</code>(<code>Sink Chain</code>,可以理解为一个复合的<code>Subscriber</code>),而<code>Terminal Op</code>则类似于<code>Publisher</code>,只有在<code>Subscriber</code>订阅<code>Publisher</code>的时候才会进行数据的处理,这里是应用了<code>Reactive</code>编程模式。</p><h3 id="AbstractPipeline和ReferencePipeline的实现"><a href="#AbstractPipeline和ReferencePipeline的实现" class="headerlink" title="AbstractPipeline和ReferencePipeline的实现"></a>AbstractPipeline和ReferencePipeline的实现</h3><p><code>AbstractPipeline</code>和<code>ReferencePipeline</code>都是抽象类,<code>AbstractPipeline</code>用于构建<code>Pipeline</code>的数据结构,提供一些<code>Shape</code>相关的抽象方法给<code>ReferencePipeline</code>实现,而<code>ReferencePipeline</code>就是<code>Stream</code>中<code>Pipeline</code>的基础类型,从源码上看,<code>Stream</code>链式(管道式)结构的头节点和操作节点都是<code>ReferencePipeline</code>的子类。先看<code>AbstractPipeline</code>的成员变量和构造函数:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractPipeline</span><E_IN, E_OUT, S <span class="keyword">extends</span> <span class="title class_">BaseStream</span><E_OUT, S>></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">PipelineHelper</span><E_OUT> <span class="keyword">implements</span> <span class="title class_">BaseStream</span><E_OUT, S> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 流管道链式结构的头节点(只有当前的AbstractPipeline引用是头节点,此变量才会被赋值,非头节点为NULL)</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("rawtypes")</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> AbstractPipeline sourceStage;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 流管道链式结构的upstream,也就是上一个节点,如果是头节点此引用为NULL</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("rawtypes")</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> AbstractPipeline previousStage;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 合并数据源的标志和操作标志的掩码</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">final</span> <span class="type">int</span> sourceOrOpFlags;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 流管道链式结构的下一个节点,如果是头节点此引用为NULL</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("rawtypes")</span></span><br><span class="line"> <span class="keyword">private</span> AbstractPipeline nextStage;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 流的深度</span></span><br><span class="line"> <span class="comment">// 串行执行的流中,表示当前流管道实例中中间操作节点的个数(除去头节点和终结操作)</span></span><br><span class="line"> <span class="comment">// 并发执行的流中,表示当前流管道实例中中间操作节点和前一个有状态操作节点之间的节点个数</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> depth;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 合并了所有数据源的标志、操作标志和当前的节点(AbstractPipeline)实例的标志,也就是当前的节点可以基于此属性得知所有支持的标志</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> combinedFlags;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 数据源的Spliterator实例</span></span><br><span class="line"> <span class="keyword">private</span> Spliterator<?> sourceSpliterator;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 数据源的Spliterator实例封装的Supplier实例</span></span><br><span class="line"> <span class="keyword">private</span> Supplier<? <span class="keyword">extends</span> <span class="title class_">Spliterator</span><?>> sourceSupplier;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 标记当前的流节点是否被连接或者消费掉,不能重复连接或者重复消费</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">boolean</span> linkedOrConsumed;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 标记当前的流管道链式结构中是否存在有状态的操作节点,这个属性只会在头节点中有效</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">boolean</span> sourceAnyStateful;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 数据源关闭动作,这个属性只会在头节点中有效,由sourceStage持有</span></span><br><span class="line"> <span class="keyword">private</span> Runnable sourceCloseAction;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 标记当前流是否并发执行</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">boolean</span> parallel;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 流管道结构头节点的父构造方法,使用数据源的Spliterator实例封装的Supplier实例</span></span><br><span class="line"> AbstractPipeline(Supplier<? <span class="keyword">extends</span> <span class="title class_">Spliterator</span><?>> source,</span><br><span class="line"> <span class="type">int</span> sourceFlags, <span class="type">boolean</span> parallel) {</span><br><span class="line"> <span class="comment">// 头节点的前驱节点置为NULL</span></span><br><span class="line"> <span class="built_in">this</span>.previousStage = <span class="literal">null</span>;</span><br><span class="line"> <span class="built_in">this</span>.sourceSupplier = source;</span><br><span class="line"> <span class="built_in">this</span>.sourceStage = <span class="built_in">this</span>;</span><br><span class="line"> <span class="comment">// 合并传入的源标志和流标志的掩码</span></span><br><span class="line"> <span class="built_in">this</span>.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;</span><br><span class="line"> <span class="comment">// The following is an optimization of:</span></span><br><span class="line"> <span class="comment">// StreamOpFlag.combineOpFlags(sourceOrOpFlags, StreamOpFlag.INITIAL_OPS_VALUE);</span></span><br><span class="line"> <span class="comment">// 初始化合并标志集合为sourceOrOpFlags和所有流操作标志的初始化值</span></span><br><span class="line"> <span class="built_in">this</span>.combinedFlags = (~(sourceOrOpFlags << <span class="number">1</span>)) & StreamOpFlag.INITIAL_OPS_VALUE;</span><br><span class="line"> <span class="comment">// 深度设置为0</span></span><br><span class="line"> <span class="built_in">this</span>.depth = <span class="number">0</span>;</span><br><span class="line"> <span class="built_in">this</span>.parallel = parallel;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 流管道结构头节点的父构造方法,使用数据源的Spliterator实例</span></span><br><span class="line"> AbstractPipeline(Spliterator<?> source,</span><br><span class="line"> <span class="type">int</span> sourceFlags, <span class="type">boolean</span> parallel) {</span><br><span class="line"> <span class="comment">// 头节点的前驱节点置为NULL</span></span><br><span class="line"> <span class="built_in">this</span>.previousStage = <span class="literal">null</span>;</span><br><span class="line"> <span class="built_in">this</span>.sourceSpliterator = source;</span><br><span class="line"> <span class="built_in">this</span>.sourceStage = <span class="built_in">this</span>;</span><br><span class="line"> <span class="comment">// 合并传入的源标志和流标志的掩码</span></span><br><span class="line"> <span class="built_in">this</span>.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;</span><br><span class="line"> <span class="comment">// The following is an optimization of:</span></span><br><span class="line"> <span class="comment">// StreamOpFlag.combineOpFlags(sourceOrOpFlags, StreamOpFlag.INITIAL_OPS_VALUE);</span></span><br><span class="line"> <span class="comment">// 初始化合并标志集合为sourceOrOpFlags和所有流操作标志的初始化值</span></span><br><span class="line"> <span class="built_in">this</span>.combinedFlags = (~(sourceOrOpFlags << <span class="number">1</span>)) & StreamOpFlag.INITIAL_OPS_VALUE;</span><br><span class="line"> <span class="built_in">this</span>.depth = <span class="number">0</span>;</span><br><span class="line"> <span class="built_in">this</span>.parallel = parallel;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 流管道结构中间操作节点的父构造方法</span></span><br><span class="line"> AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, <span class="type">int</span> opFlags) {</span><br><span class="line"> <span class="keyword">if</span> (previousStage.linkedOrConsumed)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_STREAM_LINKED);</span><br><span class="line"> previousStage.linkedOrConsumed = <span class="literal">true</span>;</span><br><span class="line"> <span class="comment">// 设置前驱节点的后继节点引用为当前的AbstractPipeline实例</span></span><br><span class="line"> previousStage.nextStage = <span class="built_in">this</span>;</span><br><span class="line"> <span class="comment">// 设置前驱节点引用为传入的前驱节点实例</span></span><br><span class="line"> <span class="built_in">this</span>.previousStage = previousStage;</span><br><span class="line"> <span class="comment">// 合并传入的中间操作标志和流操作标志的掩码</span></span><br><span class="line"> <span class="built_in">this</span>.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;</span><br><span class="line"> <span class="comment">// 合并标志集合为传入的标志和前驱节点的标志集合</span></span><br><span class="line"> <span class="built_in">this</span>.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);</span><br><span class="line"> <span class="comment">// 赋值sourceStage为前驱节点的sourceStage</span></span><br><span class="line"> <span class="built_in">this</span>.sourceStage = previousStage.sourceStage;</span><br><span class="line"> <span class="keyword">if</span> (opIsStateful())</span><br><span class="line"> <span class="comment">// 标记当前的流存在有状态操作</span></span><br><span class="line"> sourceStage.sourceAnyStateful = <span class="literal">true</span>;</span><br><span class="line"> <span class="comment">// 深度设置为前驱节点深度加1</span></span><br><span class="line"> <span class="built_in">this</span>.depth = previousStage.depth + <span class="number">1</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><br></pre></td></tr></table></figure><p>至此,可以看出流管道的数据结构:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-10.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-10.png" alt="img"></a></p><p><code>Terminal Op</code>不参与管道链式结构的构建。接着看<code>AbstractPipeline</code>中的终结求值方法(<code>Terminal evaluation methods</code>):</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractPipeline</span><E_IN, E_OUT, S <span class="keyword">extends</span> <span class="title class_">BaseStream</span><E_OUT, S>></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">PipelineHelper</span><E_OUT> <span class="keyword">implements</span> <span class="title class_">BaseStream</span><E_OUT, S> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略其他方法</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 基于终结操作进行求值,这个是Stream执行的常用核心方法,常用于collect()这类终结操作</span></span><br><span class="line"> <span class="keyword">final</span> <R> R <span class="title function_">evaluate</span><span class="params">(TerminalOp<E_OUT, R> terminalOp)</span> {</span><br><span class="line"> <span class="keyword">assert</span> <span class="title function_">getOutputShape</span><span class="params">()</span> == terminalOp.inputShape();</span><br><span class="line"> <span class="comment">// 判断linkedOrConsumed,以防多次终结求值,也就是每个终结操作只能执行一次</span></span><br><span class="line"> <span class="keyword">if</span> (linkedOrConsumed)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_STREAM_LINKED);</span><br><span class="line"> linkedOrConsumed = <span class="literal">true</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 如果当前流支持并发执行,则委托到TerminalOp.evaluateParallel(),如果当前流只支持同步执行,则委托到TerminalOp.evaluateSequential()</span></span><br><span class="line"> <span class="comment">// 这里注意传入到TerminalOp中的方法参数分别是this(PipelineHelper类型)和数据源Spliterator</span></span><br><span class="line"> <span class="keyword">return</span> isParallel()</span><br><span class="line"> ? terminalOp.evaluateParallel(<span class="built_in">this</span>, sourceSpliterator(terminalOp.getOpFlags()))</span><br><span class="line"> : terminalOp.evaluateSequential(<span class="built_in">this</span>, sourceSpliterator(terminalOp.getOpFlags()));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 基于当前的流实例转换为最终的Node实例,传入的IntFunction用于创建数组实例</span></span><br><span class="line"> <span class="comment">// 此终结方法一般用于toArray()这类终结操作</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">final</span> Node<E_OUT> <span class="title function_">evaluateToArrayNode</span><span class="params">(IntFunction<E_OUT[]> generator)</span> {</span><br><span class="line"> <span class="keyword">if</span> (linkedOrConsumed)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_STREAM_LINKED);</span><br><span class="line"> linkedOrConsumed = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// If the last intermediate operation is stateful then</span></span><br><span class="line"> <span class="comment">// evaluate directly to avoid an extra collection step</span></span><br><span class="line"> <span class="comment">// 当前流支持并发执行,并且最后一个中间操作是有状态,则委托到opEvaluateParallel(),否则委托到evaluate(),这两个都是AbstractPipeline中的方法</span></span><br><span class="line"> <span class="keyword">if</span> (isParallel() && previousStage != <span class="literal">null</span> && opIsStateful()) {</span><br><span class="line"> <span class="comment">// Set the depth of this, last, pipeline stage to zero to slice the</span></span><br><span class="line"> <span class="comment">// pipeline such that this operation will not be included in the</span></span><br><span class="line"> <span class="comment">// upstream slice and upstream operations will not be included</span></span><br><span class="line"> <span class="comment">// in this slice</span></span><br><span class="line"> depth = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">return</span> opEvaluateParallel(previousStage, previousStage.sourceSpliterator(<span class="number">0</span>), generator);</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> evaluate(sourceSpliterator(<span class="number">0</span>), <span class="literal">true</span>, generator);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这个方法比较简单,就是获取当前流的数据源所在的Spliterator,并且确保流已经消费,一般用于forEach()这类终结操作</span></span><br><span class="line"> <span class="keyword">final</span> Spliterator<E_OUT> <span class="title function_">sourceStageSpliterator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">this</span> != sourceStage)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (linkedOrConsumed)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_STREAM_LINKED);</span><br><span class="line"> linkedOrConsumed = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (sourceStage.sourceSpliterator != <span class="literal">null</span>) {</span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> Spliterator<E_OUT> s = sourceStage.sourceSpliterator;</span><br><span class="line"> sourceStage.sourceSpliterator = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">return</span> s;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (sourceStage.sourceSupplier != <span class="literal">null</span>) {</span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> Spliterator<E_OUT> s = (Spliterator<E_OUT>) sourceStage.sourceSupplier.get();</span><br><span class="line"> sourceStage.sourceSupplier = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">return</span> s;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_CONSUMED);</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><br></pre></td></tr></table></figure><p><code>AbstractPipeline</code>中实现了<code>BaseStream</code>的方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractPipeline</span><E_IN, E_OUT, S <span class="keyword">extends</span> <span class="title class_">BaseStream</span><E_OUT, S>></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">PipelineHelper</span><E_OUT> <span class="keyword">implements</span> <span class="title class_">BaseStream</span><E_OUT, S> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略其他方法</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 设置头节点的parallel属性为false,返回自身实例,表示当前的流是同步执行的</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> S <span class="title function_">sequential</span><span class="params">()</span> {</span><br><span class="line"> sourceStage.parallel = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">return</span> (S) <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 设置头节点的parallel属性为true,返回自身实例,表示当前的流是并发执行的</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> S <span class="title function_">parallel</span><span class="params">()</span> {</span><br><span class="line"> sourceStage.parallel = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">return</span> (S) <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 流关闭操作,设置linkedOrConsumed为true,数据源的Spliterator相关引用置为NULL,置空并且回调sourceCloseAction钩子实例</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">close</span><span class="params">()</span> {</span><br><span class="line"> linkedOrConsumed = <span class="literal">true</span>;</span><br><span class="line"> sourceSupplier = <span class="literal">null</span>;</span><br><span class="line"> sourceSpliterator = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (sourceStage.sourceCloseAction != <span class="literal">null</span>) {</span><br><span class="line"> <span class="type">Runnable</span> <span class="variable">closeAction</span> <span class="operator">=</span> sourceStage.sourceCloseAction;</span><br><span class="line"> sourceStage.sourceCloseAction = <span class="literal">null</span>;</span><br><span class="line"> closeAction.run();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回一个添加了close处理器的Stream实例,close处理器会在下面的close方法中回调</span></span><br><span class="line"> <span class="comment">// 如果本来持有的引用sourceStage.sourceCloseAction非空,会使用传入的closeHandler与sourceStage.sourceCloseAction进行合并</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">public</span> S <span class="title function_">onClose</span><span class="params">(Runnable closeHandler)</span> {</span><br><span class="line"> <span class="keyword">if</span> (linkedOrConsumed)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_STREAM_LINKED);</span><br><span class="line"> Objects.requireNonNull(closeHandler);</span><br><span class="line"> <span class="type">Runnable</span> <span class="variable">existingHandler</span> <span class="operator">=</span> sourceStage.sourceCloseAction;</span><br><span class="line"> sourceStage.sourceCloseAction =</span><br><span class="line"> (existingHandler == <span class="literal">null</span>)</span><br><span class="line"> ? closeHandler</span><br><span class="line"> : Streams.composeWithExceptions(existingHandler, closeHandler);</span><br><span class="line"> <span class="keyword">return</span> (S) <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Primitive specialization use co-variant overrides, hence is not final</span></span><br><span class="line"> <span class="comment">// 返回当前流实例中所有元素的Spliterator实例</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">public</span> Spliterator<E_OUT> <span class="title function_">spliterator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (linkedOrConsumed)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_STREAM_LINKED);</span><br><span class="line"> <span class="comment">// 标记当前节点被链接或者消费</span></span><br><span class="line"> linkedOrConsumed = <span class="literal">true</span>;</span><br><span class="line"> <span class="comment">// 如果当前节点为头节点,那么返回sourceStage.sourceSpliterator或者延时加载的sourceStage.sourceSupplier(延时加载封装由lazySpliterator实现)</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">this</span> == sourceStage) {</span><br><span class="line"> <span class="keyword">if</span> (sourceStage.sourceSpliterator != <span class="literal">null</span>) {</span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> Spliterator<E_OUT> s = (Spliterator<E_OUT>) sourceStage.sourceSpliterator;</span><br><span class="line"> sourceStage.sourceSpliterator = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">return</span> s;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (sourceStage.sourceSupplier != <span class="literal">null</span>) {</span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> Supplier<Spliterator<E_OUT>> s = (Supplier<Spliterator<E_OUT>>) sourceStage.sourceSupplier;</span><br><span class="line"> sourceStage.sourceSupplier = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">return</span> lazySpliterator(s);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_CONSUMED);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 如果当前节点不是头节点,重新对sourceSpliterator进行包装,包装后的实例为WrappingSpliterator</span></span><br><span class="line"> <span class="keyword">return</span> wrap(<span class="built_in">this</span>, () -> sourceSpliterator(<span class="number">0</span>), isParallel());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 当前流实例是否并发执行,从头节点的parallel属性进行判断</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">isParallel</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> sourceStage.parallel;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从当前combinedFlags中获取数据源标志和所有流中间操作标志的集合</span></span><br><span class="line"> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">getStreamFlags</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> StreamOpFlag.toStreamFlags(combinedFlags);</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"> * Get the source spliterator for this pipeline stage. For a sequential or</span></span><br><span class="line"><span class="comment"> * stateless parallel pipeline, this is the source spliterator. For a</span></span><br><span class="line"><span class="comment"> * stateful parallel pipeline, this is a spliterator describing the results</span></span><br><span class="line"><span class="comment"> * of all computations up to and including the most recent stateful</span></span><br><span class="line"><span class="comment"> * operation.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">private</span> Spliterator<?> sourceSpliterator(<span class="type">int</span> terminalFlags) {</span><br><span class="line"> <span class="comment">// 从sourceStage.sourceSpliterator或者sourceStage.sourceSupplier中获取当前流实例中的Spliterator实例,确保必定存在,否则抛出IllegalStateException</span></span><br><span class="line"> Spliterator<?> spliterator = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (sourceStage.sourceSpliterator != <span class="literal">null</span>) {</span><br><span class="line"> spliterator = sourceStage.sourceSpliterator;</span><br><span class="line"> sourceStage.sourceSpliterator = <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (sourceStage.sourceSupplier != <span class="literal">null</span>) {</span><br><span class="line"> spliterator = (Spliterator<?>) sourceStage.sourceSupplier.get();</span><br><span class="line"> sourceStage.sourceSupplier = <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_CONSUMED);</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">// 这里只提一下计算过程,从头节点的后继节点开始遍历到当前节点,如果被遍历的节点时有状态的,那么对depth、combinedFlags和spliterator会进行重新计算</span></span><br><span class="line"> <span class="comment">// depth一旦出现有状态节点就会重置为0,然后从1重新开始增加</span></span><br><span class="line"> <span class="comment">// combinedFlags会重新合并sourceOrOpFlags、SHORT_CIRCUIT(如果sourceOrOpFlags支持)和Spliterator.SIZED</span></span><br><span class="line"> <span class="comment">// spliterator简单来看就是从并发执行的toArray()=>Array数组=>Spliterator实例</span></span><br><span class="line"> <span class="keyword">if</span> (isParallel() && sourceStage.sourceAnyStateful) {</span><br><span class="line"> <span class="comment">// Adapt the source spliterator, evaluating each stateful op</span></span><br><span class="line"> <span class="comment">// in the pipeline up to and including this pipeline stage.</span></span><br><span class="line"> <span class="comment">// The depth and flags of each pipeline stage are adjusted accordingly.</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">depth</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="meta">@SuppressWarnings("rawtypes")</span> <span class="type">AbstractPipeline</span> <span class="variable">u</span> <span class="operator">=</span> sourceStage, p = sourceStage.nextStage, e = <span class="built_in">this</span>;</span><br><span class="line"> u != e;</span><br><span class="line"> u = p, p = p.nextStage) {</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> <span class="variable">thisOpFlags</span> <span class="operator">=</span> p.sourceOrOpFlags;</span><br><span class="line"> <span class="keyword">if</span> (p.opIsStateful()) {</span><br><span class="line"> depth = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (StreamOpFlag.SHORT_CIRCUIT.isKnown(thisOpFlags)) {</span><br><span class="line"> <span class="comment">// Clear the short circuit flag for next pipeline stage</span></span><br><span class="line"> <span class="comment">// This stage encapsulates short-circuiting, the next</span></span><br><span class="line"> <span class="comment">// stage may not have any short-circuit operations, and</span></span><br><span class="line"> <span class="comment">// if so spliterator.forEachRemaining should be used</span></span><br><span class="line"> <span class="comment">// for traversal</span></span><br><span class="line"> thisOpFlags = thisOpFlags & ~StreamOpFlag.IS_SHORT_CIRCUIT;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> spliterator = p.opEvaluateParallelLazy(u, spliterator);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Inject or clear SIZED on the source pipeline stage</span></span><br><span class="line"> <span class="comment">// based on the stage's spliterator</span></span><br><span class="line"> thisOpFlags = spliterator.hasCharacteristics(Spliterator.SIZED)</span><br><span class="line"> ? (thisOpFlags & ~StreamOpFlag.NOT_SIZED) | StreamOpFlag.IS_SIZED</span><br><span class="line"> : (thisOpFlags & ~StreamOpFlag.IS_SIZED) | StreamOpFlag.NOT_SIZED;</span><br><span class="line"> }</span><br><span class="line"> p.depth = depth++;</span><br><span class="line"> p.combinedFlags = StreamOpFlag.combineOpFlags(thisOpFlags, u.combinedFlags);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果传入的terminalFlags标志不为0,则当前节点的combinedFlags会合并terminalFlags</span></span><br><span class="line"> <span class="keyword">if</span> (terminalFlags != <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// Apply flags from the terminal operation to last pipeline stage</span></span><br><span class="line"> combinedFlags = StreamOpFlag.combineOpFlags(terminalFlags, combinedFlags);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> spliterator;</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><br></pre></td></tr></table></figure><p><code>AbstractPipeline</code>中实现了<code>PipelineHelper</code>的方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractPipeline</span><E_IN, E_OUT, S <span class="keyword">extends</span> <span class="title class_">BaseStream</span><E_OUT, S>></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">PipelineHelper</span><E_OUT> <span class="keyword">implements</span> <span class="title class_">BaseStream</span><E_OUT, S> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略其他方法</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 获取数据源元素的类型,这里的类型包括引用、int、double和float</span></span><br><span class="line"> <span class="comment">// 其实实现上就是获取depth<=0的第一个节点的输出类型</span></span><br><span class="line"> <span class="meta">@Override</span> </span><br><span class="line"> <span class="keyword">final</span> StreamShape <span class="title function_">getSourceShape</span><span class="params">()</span> {</span><br><span class="line"> <span class="meta">@SuppressWarnings("rawtypes")</span></span><br><span class="line"> <span class="type">AbstractPipeline</span> <span class="variable">p</span> <span class="operator">=</span> AbstractPipeline.<span class="built_in">this</span>;</span><br><span class="line"> <span class="keyword">while</span> (p.depth > <span class="number">0</span>) {</span><br><span class="line"> p = p.previousStage;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> p.getOutputShape();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 基于当前节点的标志集合判断和返回流中待处理的元素数量,无法获取则返回-1</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN> <span class="type">long</span> <span class="title function_">exactOutputSizeIfKnown</span><span class="params">(Spliterator<P_IN> spliterator)</span> {</span><br><span class="line"> <span class="keyword">return</span> StreamOpFlag.SIZED.isKnown(getStreamAndOpFlags()) ? spliterator.getExactSizeIfKnown() : -<span class="number">1</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN, S <span class="keyword">extends</span> <span class="title class_">Sink</span><E_OUT>> S <span class="title function_">wrapAndCopyInto</span><span class="params">(S sink, Spliterator<P_IN> spliterator)</span> {</span><br><span class="line"> copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator);</span><br><span class="line"> <span class="keyword">return</span> sink;</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN> <span class="keyword">void</span> <span class="title function_">copyInto</span><span class="params">(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator)</span> {</span><br><span class="line"> Objects.requireNonNull(wrappedSink);</span><br><span class="line"> <span class="comment">// 当前节点不支持SHORT_CIRCUIT(短路)特性,则直接遍历元素引用链,不支持短路跳出</span></span><br><span class="line"> <span class="keyword">if</span> (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {</span><br><span class="line"> wrappedSink.begin(spliterator.getExactSizeIfKnown());</span><br><span class="line"> spliterator.forEachRemaining(wrappedSink);</span><br><span class="line"> wrappedSink.end();</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"> copyIntoWithCancel(wrappedSink, spliterator);</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="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN> <span class="type">boolean</span> <span class="title function_">copyIntoWithCancel</span><span class="params">(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator)</span> {</span><br><span class="line"> <span class="meta">@SuppressWarnings({"rawtypes","unchecked"})</span></span><br><span class="line"> <span class="type">AbstractPipeline</span> <span class="variable">p</span> <span class="operator">=</span> AbstractPipeline.<span class="built_in">this</span>;</span><br><span class="line"> <span class="comment">// 基于当前节点,获取流管道链式结构中第最后一个depth=0的前驱节点</span></span><br><span class="line"> <span class="keyword">while</span> (p.depth > <span class="number">0</span>) {</span><br><span class="line"> p = p.previousStage;</span><br><span class="line"> }</span><br><span class="line"> wrappedSink.begin(spliterator.getExactSizeIfKnown());</span><br><span class="line"> <span class="comment">// 委托到forEachWithCancel()进行遍历</span></span><br><span class="line"> <span class="type">boolean</span> <span class="variable">cancelled</span> <span class="operator">=</span> p.forEachWithCancel(spliterator, wrappedSink);</span><br><span class="line"> wrappedSink.end();</span><br><span class="line"> <span class="keyword">return</span> cancelled;</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">getStreamAndOpFlags</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> combinedFlags;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 当前节点标志集合中是否支持ORDERED</span></span><br><span class="line"> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">isOrdered</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> StreamOpFlag.ORDERED.isKnown(combinedFlags);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 构建元素引用链,生成一个多重包装的Sink(WrapSink),这里的逻辑可以看前面的分析章节</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN> Sink<P_IN> <span class="title function_">wrapSink</span><span class="params">(Sink<E_OUT> sink)</span> {</span><br><span class="line"> Objects.requireNonNull(sink);</span><br><span class="line"> <span class="comment">// 这里遍历的时候,总是从当前节点向前驱节点遍历,也就是传入的sink实例总是包裹在最里面一层执行</span></span><br><span class="line"> <span class="keyword">for</span> ( <span class="meta">@SuppressWarnings("rawtypes")</span> AbstractPipeline p=AbstractPipeline.<span class="built_in">this</span>; p.depth > <span class="number">0</span>; p=p.previousStage) {</span><br><span class="line"> sink = p.opWrapSink(p.previousStage.combinedFlags, sink);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (Sink<P_IN>) sink;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 包装数据源的Spliterator,如果depth=0,则直接返回sourceSpliterator,否则返回的是延迟加载的WrappingSpliterator</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN> Spliterator<E_OUT> <span class="title function_">wrapSpliterator</span><span class="params">(Spliterator<P_IN> sourceSpliterator)</span> {</span><br><span class="line"> <span class="keyword">if</span> (depth == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> (Spliterator<E_OUT>) sourceSpliterator;</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> wrap(<span class="built_in">this</span>, () -> sourceSpliterator, isParallel());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 计算Node实例,这个方法用于toArray()方法系列,是一个终结操作,下面会另开章节详细分析</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN> Node<E_OUT> <span class="title function_">evaluate</span><span class="params">(Spliterator<P_IN> spliterator,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> flatten,</span></span><br><span class="line"><span class="params"> IntFunction<E_OUT[]> generator)</span> {</span><br><span class="line"> <span class="keyword">if</span> (isParallel()) {</span><br><span class="line"> <span class="comment">// @@@ Optimize if op of this pipeline stage is a stateful op</span></span><br><span class="line"> <span class="keyword">return</span> evaluateToNode(<span class="built_in">this</span>, spliterator, flatten, generator);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> Node.Builder<E_OUT> nb = makeNodeBuilder(</span><br><span class="line"> exactOutputSizeIfKnown(spliterator), generator);</span><br><span class="line"> <span class="keyword">return</span> wrapAndCopyInto(nb, spliterator).build();</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><br></pre></td></tr></table></figure><p><code>AbstractPipeline</code>中剩余的待如<code>XXYYZZPipeline</code>等子类实现的抽象方法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractPipeline</span><E_IN, E_OUT, S <span class="keyword">extends</span> <span class="title class_">BaseStream</span><E_OUT, S>></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">PipelineHelper</span><E_OUT> <span class="keyword">implements</span> <span class="title class_">BaseStream</span><E_OUT, S> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 省略其他方法</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取当前流的输出"形状",REFERENCE、INT_VALUE、LONG_VALUE或者DOUBLE_VALUE</span></span><br><span class="line"> <span class="keyword">abstract</span> StreamShape <span class="title function_">getOutputShape</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 收集当前流的所有输出元素,转化为一个适配当前流输出"形状"的Node实例</span></span><br><span class="line"> <span class="keyword">abstract</span> <P_IN> Node<E_OUT> <span class="title function_">evaluateToNode</span><span class="params">(PipelineHelper<E_OUT> helper,</span></span><br><span class="line"><span class="params"> Spliterator<P_IN> spliterator,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> flattenTree,</span></span><br><span class="line"><span class="params"> IntFunction<E_OUT[]> generator)</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 包装Spliterator为WrappingSpliterator实例</span></span><br><span class="line"> <span class="keyword">abstract</span> <P_IN> Spliterator<E_OUT> <span class="title function_">wrap</span><span class="params">(PipelineHelper<E_OUT> ph,</span></span><br><span class="line"><span class="params"> Supplier<Spliterator<P_IN>> supplier,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> isParallel)</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 包装Spliterator为DelegatingSpliterator实例</span></span><br><span class="line"> <span class="keyword">abstract</span> <P_IN> Spliterator<E_OUT> <span class="title function_">wrap</span><span class="params">(PipelineHelper<E_OUT> ph,</span></span><br><span class="line"><span class="params"> Supplier<Spliterator<P_IN>> supplier,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> isParallel)</span>;</span><br><span class="line"> <span class="comment">// 基于Sink遍历Spliterator中的元素,支持取消操作,简单理解就是支持cancel的tryAdvance方法</span></span><br><span class="line"> <span class="keyword">abstract</span> <span class="type">boolean</span> <span class="title function_">forEachWithCancel</span><span class="params">(Spliterator<E_OUT> spliterator, Sink<E_OUT> sink)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回Node的建造器实例,用于toArray方法系列</span></span><br><span class="line"> <span class="keyword">abstract</span> Node.Builder<E_OUT> <span class="title function_">makeNodeBuilder</span><span class="params">(<span class="type">long</span> exactSizeIfKnown,</span></span><br><span class="line"><span class="params"> IntFunction<E_OUT[]> generator)</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 判断当前的操作(节点)是否有状态,如果是有状态的操作,必须覆盖opEvaluateParallel方法</span></span><br><span class="line"> <span class="keyword">abstract</span> <span class="type">boolean</span> <span class="title function_">opIsStateful</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 当前操作生成的结果会作为传入的Sink实例的入参,这是一个包装Sink的过程,通俗理解就是之前提到的元素引用链添加一个新的链节点,这个方法算是流执行的一个核心方法</span></span><br><span class="line"> <span class="keyword">abstract</span> Sink<E_IN> <span class="title function_">opWrapSink</span><span class="params">(<span class="type">int</span> flags, Sink<E_OUT> sink)</span>; </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 并发执行的操作节点求值</span></span><br><span class="line"> <P_IN> Node<E_OUT> <span class="title function_">opEvaluateParallel</span><span class="params">(PipelineHelper<E_OUT> helper,</span></span><br><span class="line"><span class="params"> Spliterator<P_IN> spliterator,</span></span><br><span class="line"><span class="params"> IntFunction<E_OUT[]> generator)</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>(<span class="string">"Parallel evaluation is not supported"</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="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <P_IN> Spliterator<E_OUT> <span class="title function_">opEvaluateParallelLazy</span><span class="params">(PipelineHelper<E_OUT> helper,</span></span><br><span class="line"><span class="params"> Spliterator<P_IN> spliterator)</span> {</span><br><span class="line"> <span class="keyword">return</span> opEvaluateParallel(helper, spliterator, i -> (E_OUT[]) <span class="keyword">new</span> <span class="title class_">Object</span>[i]).spliterator();</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><br></pre></td></tr></table></figure><p>这里提到的抽象方法<code>opWrapSink()</code>其实就是元素引用链的添加链节点的方法,它的实现逻辑见子类,这里只考虑非特化子类<code>ReferencePipeline</code>的部分源码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ReferencePipeline</span><P_IN, P_OUT></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">AbstractPipeline</span><P_IN, P_OUT, Stream<P_OUT>></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">Stream</span><P_OUT> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构造函数,用于头节点,传入基于Supplier封装的Spliterator实例作为数据源,数据源的标志集合和是否支持并发执行的判断标记</span></span><br><span class="line"> ReferencePipeline(Supplier<? <span class="keyword">extends</span> <span class="title class_">Spliterator</span><?>> source,</span><br><span class="line"> <span class="type">int</span> sourceFlags, <span class="type">boolean</span> parallel) {</span><br><span class="line"> <span class="built_in">super</span>(source, sourceFlags, parallel);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构造函数,用于头节点,传入Spliterator实例作为数据源,数据源的标志集合和是否支持并发执行的判断标记</span></span><br><span class="line"> ReferencePipeline(Spliterator<?> source,</span><br><span class="line"> <span class="type">int</span> sourceFlags, <span class="type">boolean</span> parallel) {</span><br><span class="line"> <span class="built_in">super</span>(source, sourceFlags, parallel);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构造函数,用于中间节点,传入上一个流管道节点的实例(前驱节点)和当前操作节点支持的标志集合</span></span><br><span class="line"> ReferencePipeline(AbstractPipeline<?, P_IN, ?> upstream, <span class="type">int</span> opFlags) {</span><br><span class="line"> <span class="built_in">super</span>(upstream, opFlags);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这里流的输出"形状"固定为REFERENCE</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> StreamShape <span class="title function_">getOutputShape</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> StreamShape.REFERENCE;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 转换当前流实例为Node实例,应用于toArray方法,后面详细分析终结操作的时候再展开</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN> Node<P_OUT> <span class="title function_">evaluateToNode</span><span class="params">(PipelineHelper<P_OUT> helper,</span></span><br><span class="line"><span class="params"> Spliterator<P_IN> spliterator,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> flattenTree,</span></span><br><span class="line"><span class="params"> IntFunction<P_OUT[]> generator)</span> {</span><br><span class="line"> <span class="keyword">return</span> Nodes.collect(helper, spliterator, flattenTree, generator);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 包装Spliterator=>WrappingSpliterator</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN> Spliterator<P_OUT> <span class="title function_">wrap</span><span class="params">(PipelineHelper<P_OUT> ph,</span></span><br><span class="line"><span class="params"> Supplier<Spliterator<P_IN>> supplier,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> isParallel)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">StreamSpliterators</span>.WrappingSpliterator<>(ph, supplier, isParallel);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 包装Spliterator=>DelegatingSpliterator,实现惰性加载</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> Spliterator<P_OUT> <span class="title function_">lazySpliterator</span><span class="params">(Supplier<? extends Spliterator<P_OUT>> supplier)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">StreamSpliterators</span>.DelegatingSpliterator<>(supplier);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 遍历Spliterator中的元素,基于传入的Sink实例进行处理,支持Cancel操作</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">forEachWithCancel</span><span class="params">(Spliterator<P_OUT> spliterator, Sink<P_OUT> sink)</span> {</span><br><span class="line"> <span class="type">boolean</span> cancelled;</span><br><span class="line"> <span class="keyword">do</span> { } <span class="keyword">while</span> (!(cancelled = sink.cancellationRequested()) && spliterator.tryAdvance(sink));</span><br><span class="line"> <span class="keyword">return</span> cancelled;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 构造Node建造器实例</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> Node.Builder<P_OUT> <span class="title function_">makeNodeBuilder</span><span class="params">(<span class="type">long</span> exactSizeIfKnown, IntFunction<P_OUT[]> generator)</span> {</span><br><span class="line"> <span class="keyword">return</span> Nodes.builder(exactSizeIfKnown, generator);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 基于当前流的Spliterator生成迭代器实例</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> Iterator<P_OUT> <span class="title function_">iterator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> Spliterators.iterator(spliterator());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 省略其他OP的代码</span></span><br><span class="line"> </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_">Head</span><E_IN, E_OUT> <span class="keyword">extends</span> <span class="title class_">ReferencePipeline</span><E_IN, E_OUT> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 构造函数,用于头节点,传入基于Supplier封装的Spliterator实例作为数据源,数据源的标志集合和是否支持并发执行的判断标记</span></span><br><span class="line"> Head(Supplier<? <span class="keyword">extends</span> <span class="title class_">Spliterator</span><?>> source,</span><br><span class="line"> <span class="type">int</span> sourceFlags, <span class="type">boolean</span> parallel) {</span><br><span class="line"> <span class="built_in">super</span>(source, sourceFlags, parallel);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 构造函数,用于头节点,传入Spliterator实例作为数据源,数据源的标志集合和是否支持并发执行的判断标记</span></span><br><span class="line"> Head(Spliterator<?> source,</span><br><span class="line"> <span class="type">int</span> sourceFlags, <span class="type">boolean</span> parallel) {</span><br><span class="line"> <span class="built_in">super</span>(source, sourceFlags, parallel);</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">opIsStateful</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 不支持包装Sink实例</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> Sink<E_IN> <span class="title function_">opWrapSink</span><span class="params">(<span class="type">int</span> flags, Sink<E_OUT> sink)</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 区分同步异步执行forEach,同步则简单理解为调用Spliterator.forEachRemaining,异步则调用终结操作forEach</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">forEach</span><span class="params">(Consumer<? <span class="built_in">super</span> E_OUT> action)</span> {</span><br><span class="line"> <span class="keyword">if</span> (!isParallel()) {</span><br><span class="line"> sourceStageSpliterator().forEachRemaining(action);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">super</span>.forEach(action);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 区分同步异步执行forEachOrdered,同步则简单理解为调用Spliterator.forEachRemaining,异步则调用终结操作forEachOrdered</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">forEachOrdered</span><span class="params">(Consumer<? <span class="built_in">super</span> E_OUT> action)</span> {</span><br><span class="line"> <span class="keyword">if</span> (!isParallel()) {</span><br><span class="line"> sourceStageSpliterator().forEachRemaining(action);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">super</span>.forEachOrdered(action);</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="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">StatelessOp</span><E_IN, E_OUT></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">ReferencePipeline</span><E_IN, E_OUT> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 基于上一个节点引用、输入元素"形状"和当前节点支持的标志集合创建StatelessOp实例</span></span><br><span class="line"> StatelessOp(AbstractPipeline<?, E_IN, ?> upstream,</span><br><span class="line"> StreamShape inputShape,</span><br><span class="line"> <span class="type">int</span> opFlags) {</span><br><span class="line"> <span class="built_in">super</span>(upstream, opFlags);</span><br><span class="line"> <span class="keyword">assert</span> upstream.getOutputShape() == inputShape;</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">opIsStateful</span><span class="params">()</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 class="comment">// 有状态操作节点的父类</span></span><br><span class="line"> <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">StatefulOp</span><E_IN, E_OUT></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">ReferencePipeline</span><E_IN, E_OUT> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 基于上一个节点引用、输入元素"形状"和当前节点支持的标志集合创建StatefulOp实例</span></span><br><span class="line"> StatefulOp(AbstractPipeline<?, E_IN, ?> upstream,</span><br><span class="line"> StreamShape inputShape,</span><br><span class="line"> <span class="type">int</span> opFlags) {</span><br><span class="line"> <span class="built_in">super</span>(upstream, opFlags);</span><br><span class="line"> <span class="keyword">assert</span> upstream.getOutputShape() == inputShape;</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">opIsStateful</span><span class="params">()</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><br><span class="line"> <span class="comment">// 前面也提到,节点操作异步求值的方法在无状态节点下必须覆盖,这里重新把这个方法抽象,子类必须实现</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">abstract</span> <P_IN> Node<E_OUT> <span class="title function_">opEvaluateParallel</span><span class="params">(PipelineHelper<E_OUT> helper,</span></span><br><span class="line"><span class="params"> Spliterator<P_IN> spliterator,</span></span><br><span class="line"><span class="params"> IntFunction<E_OUT[]> generator)</span>;</span><br><span class="line"> }</span><br><span class="line">} </span><br></pre></td></tr></table></figure><p>这里重重重点分析一下<code>ReferencePipeline</code>中的<code>wrapSink</code>方法实现:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <P_IN> Sink<P_IN> <span class="title function_">wrapSink</span><span class="params">(Sink<E_OUT> sink)</span> {</span><br><span class="line"> Objects.requireNonNull(sink);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> ( <span class="meta">@SuppressWarnings("rawtypes")</span> AbstractPipeline p=AbstractPipeline.<span class="built_in">this</span>; p.depth > <span class="number">0</span>; p=p.previousStage) {</span><br><span class="line"> sink = p.opWrapSink(p.previousStage.combinedFlags, sink);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> (Sink<P_IN>) sink;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>入参是一个<code>Sink</code>实例,返回值也是一个<code>Sink</code>实例,里面的<code>for</code>循环是基于当前的<code>AbstractPipeline</code>节点向前遍历,直到<code>depth</code>为<code>0</code>的节点跳出循环,而<code>depth</code>为<code>0</code>意味着该节点必定为头节点,也就是该循环是遍历当前节点到头节点的后继节点,<code>Sink</code>是”向前包装的”,也就是处于链后面的节点<code>Sink</code>总是会作为其前驱节点的<code>opWrapSink()</code>方法的入参,在同步执行流求值计算的时候,前驱节点的<code>Sink</code>处理完元素后就会通过<code>downstream</code>引用(其实就是后驱节点的<code>Sink</code>)调用其<code>accept()</code>把元素或者处理完的元素结果传递进去,激活下一个<code>Sink</code>,以此类推。另外,<code>ReferencePipeline</code>的三个内部类<code>Head</code>、<code>StatelessOp</code>和<code>StatefulOp</code>就是流的节点类,其中只有<code>Head</code>是非抽象类,代表流管道结构(或者说双向链表结构)的头节点,<code>StatelessOp</code>(无状态操作)和<code>StatefulOp</code>(有状态操作)的子类构成了流管道结构的操作节点或者是终结操作。在忽略是否有状态操作的前提下看<code>ReferencePipeline</code>,它只是流数据结构的承载体,表面上看到的双向链表结构在流的求值计算过程中并不会进行直接遍历每个节点进行求值,而是先转化成一个多层包装的<code>Sink</code>,也就是前文笔者提到的元素引用链后者前一句分析的<code>Sink</code>元素处理以及传递,正确来说应该是一个<code>Sink</code>栈或者<code>Sink</code>包装器,它的实现可以类比为现实生活中的洋葱,或者编程模式中的<code>AOP</code>编程模式。形象一点的描述如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">Head(Spliterator) -> Op(filter) -> Op(map) -> Op(sorted) -> Terminal Op(forEach)</span><br><span class="line"></span><br><span class="line">↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓</span><br><span class="line">forEach ele in Spliterator: </span><br><span class="line"> Sink[filter](ele){</span><br><span class="line"> if filter process == true: </span><br><span class="line"> Sink[map](ele){</span><br><span class="line"> ele = mapper(ele)</span><br><span class="line"> Sink[sorted](ele){</span><br><span class="line"></span><br><span class="line"> var array </span><br><span class="line"></span><br><span class="line"> begin: </span><br><span class="line"> </span><br><span class="line"> accept(ele):</span><br><span class="line"> add ele to array</span><br><span class="line"></span><br><span class="line"> end:</span><br><span class="line"> sort ele in array </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>终结操作<code>forEach</code>是目前分析源码中最简单的实现,下面会详细分析每种终结操作的实现细节。</p><h2 id="流中间操作的源码实现"><a href="#流中间操作的源码实现" class="headerlink" title="流中间操作的源码实现"></a>流中间操作的源码实现</h2><p>限于篇幅,这里只能挑选一部分的中间<code>Op</code>进行分析。流的中间操作基本都是由<code>BaseStream</code>接口定义,在<code>ReferencePipeline</code>中进行实现,这里挑选比较常用的<code>filter</code>、<code>map</code>和<code>sorted</code>进行分析。先看<code>filter</code>:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ReferencePipeline</span><P_IN, P_OUT></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">AbstractPipeline</span><P_IN, P_OUT, Stream<P_OUT>></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">Stream</span><P_OUT> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// filter操作,泛型参数Predicate类型接受一个任意类型(这里考虑到泛型擦除)的元素,输出布尔值,它是一个无状态操作</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> Stream<P_OUT> <span class="title function_">filter</span><span class="params">(Predicate<? <span class="built_in">super</span> P_OUT> predicate)</span> {</span><br><span class="line"> Objects.requireNonNull(predicate);</span><br><span class="line"> <span class="comment">// 这里注意到,StatelessOp的第一个参数是指upstream,也就是理解为上一个节点,这里使用了this,意味着upstream为当前的ReferencePipeline实例,元素"形状"为引用类型,操作标志位不支持SIZED</span></span><br><span class="line"> <span class="comment">// 在AbstractPipeline,previousStage指向了this,当前的节点就是StatelessOp[filter]实例,那么前驱节点this的后继节点引用nextStage就指向了StatelessOp[filter]实例</span></span><br><span class="line"> <span class="comment">// 也就是StatelessOp[filter].previousStage = this; this.nextStage = StatelessOp[filter]; ===> 也就是这个看起来简单的new StatelessOp()其实已经把自身加入到管道中</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">StatelessOp</span><P_OUT, P_OUT>(<span class="built_in">this</span>, StreamShape.REFERENCE,</span><br><span class="line"> StreamOpFlag.NOT_SIZED) {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> Sink<P_OUT> <span class="title function_">opWrapSink</span><span class="params">(<span class="type">int</span> flags, Sink<P_OUT> sink)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Sink</span>.ChainedReference<P_OUT, P_OUT>(sink) {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">begin</span><span class="params">(<span class="type">long</span> size)</span> {</span><br><span class="line"> <span class="comment">// 这里通知下一个节点的Sink.begin(),由于filter方法不感知元素数量,所以传值-1</span></span><br><span class="line"> downstream.begin(-<span class="number">1</span>);</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">accept</span><span class="params">(P_OUT u)</span> {</span><br><span class="line"> <span class="comment">// 基于输入的Predicate实例判断当前处理元素是否符合判断,只有判断结果为true才会把元素原封不动直接传递到下一个Sink</span></span><br><span class="line"> <span class="keyword">if</span> (predicate.test(u))</span><br><span class="line"> downstream.accept(u);</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><span class="line"></span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接着是<code>map</code>:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ReferencePipeline</span><P_IN, P_OUT></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">AbstractPipeline</span><P_IN, P_OUT, Stream<P_OUT>></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">Stream</span><P_OUT> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// map操作,基于传入的Function实例做映射转换(P_OUT->R),它是一个无状态操作</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> <R> Stream<R> <span class="title function_">map</span><span class="params">(Function<? <span class="built_in">super</span> P_OUT, ? extends R> mapper)</span> {</span><br><span class="line"> Objects.requireNonNull(mapper);</span><br><span class="line"> <span class="comment">// upstream为当前的ReferencePipeline实例,元素"形状"为引用类型,操作标志位不支持SORTED和DISTINCT</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">StatelessOp</span><P_OUT, R>(<span class="built_in">this</span>, StreamShape.REFERENCE,</span><br><span class="line"> StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> Sink<P_OUT> <span class="title function_">opWrapSink</span><span class="params">(<span class="type">int</span> flags, Sink<R> sink)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Sink</span>.ChainedReference<P_OUT, R>(sink) {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">accept</span><span class="params">(P_OUT u)</span> {</span><br><span class="line"> <span class="comment">// 基于传入的Function实例转换元素后把转换结果传递到下一个Sink</span></span><br><span class="line"> downstream.accept(mapper.apply(u));</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><span class="line"></span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后是<code>sorted</code>,<code>sorted</code>操作会相对复杂一点:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ReferencePipeline</span><P_IN, P_OUT></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">AbstractPipeline</span><P_IN, P_OUT, Stream<P_OUT>></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">Stream</span><P_OUT> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// sorted操作,基于传入的Comparator实例对处理的元素进行排序,从源码中看,它是一个有状态操作</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> Stream<P_OUT> <span class="title function_">sorted</span><span class="params">(Comparator<? <span class="built_in">super</span> P_OUT> comparator)</span> {</span><br><span class="line"> <span class="keyword">return</span> SortedOps.makeRef(<span class="built_in">this</span>, comparator);</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><br><span class="line"></span><br><span class="line"><span class="comment">// SortedOps工具类</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">SortedOps</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构建排序操作的链节点</span></span><br><span class="line"> <span class="keyword">static</span> <T> Stream<T> <span class="title function_">makeRef</span><span class="params">(AbstractPipeline<?, T, ?> upstream,</span></span><br><span class="line"><span class="params"> Comparator<? <span class="built_in">super</span> T> comparator)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">OfRef</span><>(upstream, comparator);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 有状态的排序操作节点</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">OfRef</span><T> <span class="keyword">extends</span> <span class="title class_">ReferencePipeline</span>.StatefulOp<T, T> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 是否自然排序,不定义Comparator实例的时候为true,否则为false</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">boolean</span> isNaturalSort;</span><br><span class="line"> <span class="comment">// 用于排序的Comparator实例</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Comparator<? <span class="built_in">super</span> T> comparator;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 自然排序情况下的构造方法,元素"形状"为引用类型,操作标志位不支持ORDERED和SORTED</span></span><br><span class="line"> OfRef(AbstractPipeline<?, T, ?> upstream) {</span><br><span class="line"> <span class="built_in">super</span>(upstream, StreamShape.REFERENCE,</span><br><span class="line"> StreamOpFlag.IS_ORDERED | StreamOpFlag.IS_SORTED);</span><br><span class="line"> <span class="built_in">this</span>.isNaturalSort = <span class="literal">true</span>;</span><br><span class="line"> <span class="comment">// Comparator实例赋值为Comparator.naturalOrder(),本质是基于Object中的equals或者子类覆盖Object中的equals方法进行元素排序</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> Comparator<? <span class="built_in">super</span> T> comp = (Comparator<? <span class="built_in">super</span> T>) Comparator.naturalOrder();</span><br><span class="line"> <span class="built_in">this</span>.comparator = comp;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 非自然排序情况下的构造方法,需要传入Comparator实例,元素"形状"为引用类型,操作标志位不支持ORDERED和SORTED</span></span><br><span class="line"> OfRef(AbstractPipeline<?, T, ?> upstream, Comparator<? <span class="built_in">super</span> T> comparator) {</span><br><span class="line"> <span class="built_in">super</span>(upstream, StreamShape.REFERENCE,</span><br><span class="line"> StreamOpFlag.IS_ORDERED | StreamOpFlag.NOT_SORTED);</span><br><span class="line"> <span class="built_in">this</span>.isNaturalSort = <span class="literal">false</span>;</span><br><span class="line"> <span class="built_in">this</span>.comparator = Objects.requireNonNull(comparator);</span><br><span class="line"> }</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> Sink<T> <span class="title function_">opWrapSink</span><span class="params">(<span class="type">int</span> flags, Sink<T> sink)</span> {</span><br><span class="line"> Objects.requireNonNull(sink);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// If the input is already naturally sorted and this operation</span></span><br><span class="line"> <span class="comment">// also naturally sorted then this is a no-op</span></span><br><span class="line"> <span class="comment">// 流中的所有元素本身已经按照自然顺序排序,并且没有定义Comparator实例,则不需要进行排序,所以no op就行</span></span><br><span class="line"> <span class="keyword">if</span> (StreamOpFlag.SORTED.isKnown(flags) && isNaturalSort)</span><br><span class="line"> <span class="keyword">return</span> sink;</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (StreamOpFlag.SIZED.isKnown(flags))</span><br><span class="line"> <span class="comment">// 知道要处理的元素的确切数量,使用数组进行排序</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SizedRefSortingSink</span><>(sink, comparator);</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="comment">// 不知道要处理的元素的确切数量,使用ArrayList进行排序</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RefSortingSink</span><>(sink, comparator);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这里是并行执行流中toArray方法的实现,暂不分析</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <P_IN> Node<T> <span class="title function_">opEvaluateParallel</span><span class="params">(PipelineHelper<T> helper,</span></span><br><span class="line"><span class="params"> Spliterator<P_IN> spliterator,</span></span><br><span class="line"><span class="params"> IntFunction<T[]> generator)</span> {</span><br><span class="line"> <span class="comment">// If the input is already naturally sorted and this operation</span></span><br><span class="line"> <span class="comment">// naturally sorts then collect the output</span></span><br><span class="line"> <span class="keyword">if</span> (StreamOpFlag.SORTED.isKnown(helper.getStreamAndOpFlags()) && isNaturalSort) {</span><br><span class="line"> <span class="keyword">return</span> helper.evaluate(spliterator, <span class="literal">false</span>, generator);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// @@@ Weak two-pass parallel implementation; parallel collect, parallel sort</span></span><br><span class="line"> T[] flattenedData = helper.evaluate(spliterator, <span class="literal">true</span>, generator).asArray(generator);</span><br><span class="line"> Arrays.parallelSort(flattenedData, comparator);</span><br><span class="line"> <span class="keyword">return</span> Nodes.node(flattenedData);</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">// 这里考虑到篇幅太长,SizedRefSortingSink和RefSortingSink的源码不复杂,只展开RefSortingSink进行分析</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 无法确认待处理元素确切数量时候用于元素排序的Sink实现</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">RefSortingSink</span><T> <span class="keyword">extends</span> <span class="title class_">AbstractRefSortingSink</span><T> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 临时ArrayList实例</span></span><br><span class="line"> <span class="keyword">private</span> ArrayList<T> list;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 构造函数,需要的参数为下一个Sink引用和Comparator实例</span></span><br><span class="line"> RefSortingSink(Sink<? <span class="built_in">super</span> T> sink, Comparator<? <span class="built_in">super</span> T> comparator) {</span><br><span class="line"> <span class="built_in">super</span>(sink, comparator);</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">begin</span><span class="params">(<span class="type">long</span> size)</span> {</span><br><span class="line"> <span class="keyword">if</span> (size >= Nodes.MAX_ARRAY_SIZE)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(Nodes.BAD_SIZE);</span><br><span class="line"> <span class="comment">// 基于传入的size是否大于0,大于等于0用于作为initialCapacity构建ArrayList,小于0则构建默认initialCapacity的ArrayList,赋值到临时变量list</span></span><br><span class="line"> list = (size >= <span class="number">0</span>) ? <span class="keyword">new</span> <span class="title class_">ArrayList</span><>((<span class="type">int</span>) size) : <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">end</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">// 临时的ArrayList实例基于Comparator实例进行潘旭</span></span><br><span class="line"> list.sort(comparator);</span><br><span class="line"> <span class="comment">// 下一个Sink节点的激活,区分是否支持取消操作</span></span><br><span class="line"> downstream.begin(list.size());</span><br><span class="line"> <span class="keyword">if</span> (!cancellationRequestedCalled) {</span><br><span class="line"> list.forEach(downstream::accept);</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> (T t : list) {</span><br><span class="line"> <span class="keyword">if</span> (downstream.cancellationRequested()) <span class="keyword">break</span>;</span><br><span class="line"> downstream.accept(t);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> downstream.end();</span><br><span class="line"> <span class="comment">// 激活下一个Sink完成后,临时的ArrayList实例置为NULL,便于GC回收</span></span><br><span class="line"> list = <span class="literal">null</span>;</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">accept</span><span class="params">(T t)</span> {</span><br><span class="line"> <span class="comment">// 当前Sink处理元素直接添加到临时的ArrayList实例</span></span><br><span class="line"> list.add(t);</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><br></pre></td></tr></table></figure><p><code>sorted</code>操作有个比较显著的特点,一般的<code>Sink</code>处理完自身的逻辑,会在<code>accept()</code>方法激活下一个<code>Sink</code>引用,但是它在<code>accept()</code>方法中只做元素的累积(<strong>元素富集</strong>),在<code>end()</code>方法进行最终的排序操作和模仿<code>Spliterator</code>的两个元素遍历方法向<code>downstream</code>推送待处理的元素。示意图如下:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-11.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-11.png" alt="img"></a></p><p>其他中间操作的实现逻辑是大致相同的。</p><h2 id="同步执行流终结操作的源码实现"><a href="#同步执行流终结操作的源码实现" class="headerlink" title="同步执行流终结操作的源码实现"></a>同步执行流终结操作的源码实现</h2><p>限于篇幅,这里只能挑选一部分的<code>Terminal Op</code>进行分析,<strong>简单起见只分析同步执行的场景</strong>,这里挑选最典型和最复杂的<code>froEach()</code>和<code>collect()</code>,还有比较独特的<code>toArray()</code>方法。先看<code>froEach()</code>方法的实现过程:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ReferencePipeline</span><P_IN, P_OUT></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">AbstractPipeline</span><P_IN, P_OUT, Stream<P_OUT>></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">Stream</span><P_OUT> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 遍历元素</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">forEach</span><span class="params">(Consumer<? <span class="built_in">super</span> P_OUT> action)</span> {</span><br><span class="line"> evaluate(ForEachOps.makeRef(action, <span class="literal">false</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><br><span class="line"> <span class="comment">// 基于终结操作的求值方法</span></span><br><span class="line"> <span class="keyword">final</span> <R> R <span class="title function_">evaluate</span><span class="params">(TerminalOp<E_OUT, R> terminalOp)</span> {</span><br><span class="line"> <span class="keyword">assert</span> <span class="title function_">getOutputShape</span><span class="params">()</span> == terminalOp.inputShape();</span><br><span class="line"> <span class="comment">// 确保只会执行一次,linkedOrConsumed是流管道结构最后一个节点的属性</span></span><br><span class="line"> <span class="keyword">if</span> (linkedOrConsumed)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_STREAM_LINKED);</span><br><span class="line"> linkedOrConsumed = <span class="literal">true</span>;</span><br><span class="line"> <span class="comment">// 这里暂且只分析同步执行的流的终结操作,终结操作节点的标志会合并到流最后一个节点的combinedFlags中,执行的关键就是evaluateSequential方法</span></span><br><span class="line"> <span class="keyword">return</span> isParallel()</span><br><span class="line"> ? terminalOp.evaluateParallel(<span class="built_in">this</span>, sourceSpliterator(terminalOp.getOpFlags()))</span><br><span class="line"> : terminalOp.evaluateSequential(<span class="built_in">this</span>, sourceSpliterator(terminalOp.getOpFlags()));</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><br><span class="line"></span><br><span class="line"><span class="comment">// ForEachOps类,TerminalOp接口的定义比较简单,这里不展开</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ForEachOps</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 构造变量元素的终结操作实例,传入的元素是T类型,结果是Void类型(返回NULL,或者说是没有返回值,毕竟是一个元素遍历过程)</span></span><br><span class="line"> <span class="comment">// 参数为一个Consumer接口实例和一个标记是否顺序处理元素的布尔值</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <T> TerminalOp<T, Void> <span class="title function_">makeRef</span><span class="params">(Consumer<? <span class="built_in">super</span> T> action,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> ordered)</span> {</span><br><span class="line"> Objects.requireNonNull(action);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ForEachOp</span>.OfRef<>(action, ordered);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 遍历元素操作的终结操作实现,同时它是一个适配器,适配TerminalSink(Sink)接口</span></span><br><span class="line"> <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ForEachOp</span><T></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">TerminalOp</span><T, Void>, TerminalSink<T, Void> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 标记是否顺序处理元素</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">boolean</span> ordered;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> <span class="title function_">ForEachOp</span><span class="params">(<span class="type">boolean</span> ordered)</span> {</span><br><span class="line"> <span class="built_in">this</span>.ordered = ordered;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// TerminalOp</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 终结操作节点的标志集合,如果ordered为true则返回0,否则返回StreamOpFlag.NOT_ORDERED,表示不支持顺序处理元素</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getOpFlags</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> ordered ? <span class="number">0</span> : StreamOpFlag.NOT_ORDERED;</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <S> Void <span class="title function_">evaluateSequential</span><span class="params">(PipelineHelper<T> helper,</span></span><br><span class="line"><span class="params"> Spliterator<S> spliterator)</span> {</span><br><span class="line"> <span class="comment">// 以当前的ForEachOp实例作为最后一个Sink添加到Sink链(也就是前面经常说的元素引用链),然后对Sink链进行遍历</span></span><br><span class="line"> <span class="keyword">return</span> helper.wrapAndCopyInto(<span class="built_in">this</span>, spliterator).get();</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <S> Void <span class="title function_">evaluateParallel</span><span class="params">(PipelineHelper<T> helper,</span></span><br><span class="line"><span class="params"> Spliterator<S> spliterator)</span> {</span><br><span class="line"> <span class="keyword">if</span> (ordered)</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">ForEachOrderedTask</span><>(helper, spliterator, <span class="built_in">this</span>).invoke();</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">ForEachTask</span><>(helper, spliterator, helper.wrapSink(<span class="built_in">this</span>)).invoke();</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><br><span class="line"> <span class="comment">// TerminalSink</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 实现TerminalSink的方法,实际上TerminalSink继承接口Supplier,这里是实现了Supplier接口的get()方法,由于PipelineHelper.wrapAndCopyInto()方法会返回最后一个Sink的引用,这里其实就是evaluateSequential()中的返回值</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Void <span class="title function_">get</span><span class="params">()</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><br><span class="line"> <span class="comment">// ForEachOp的静态内部类,引用类型的ForEachOp的最终实现,依赖入参遍历元素处理的最后一步回调Consumer实例</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">OfRef</span><T> <span class="keyword">extends</span> <span class="title class_">ForEachOp</span><T> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 最后的遍历回调的Consumer句柄</span></span><br><span class="line"> <span class="keyword">final</span> Consumer<? <span class="built_in">super</span> T> consumer;</span><br><span class="line"></span><br><span class="line"> OfRef(Consumer<? <span class="built_in">super</span> T> consumer, <span class="type">boolean</span> ordered) {</span><br><span class="line"> <span class="built_in">super</span>(ordered);</span><br><span class="line"> <span class="built_in">this</span>.consumer = consumer;</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">accept</span><span class="params">(T t)</span> {</span><br><span class="line"> <span class="comment">// 遍历元素回调操作</span></span><br><span class="line"> consumer.accept(t);</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></table></figure><p><code>forEach</code>终结操作实现上,自身这个操作并不会构成流的链式结构的一部分,也就是它不是一个<code>AbstractPipeline</code>的子类实例,而是构建一个回调<code>Consumer</code>实例操作的一个<code>Sink</code>实例(准确来说是<code>TerminalSink</code>)实例,这里暂且叫<code>forEach terminal sink</code>,通过流最后一个操作节点的<code>wrapSink()</code>方法,把<code>forEach terminal sink</code>添加到<code>Sink</code>链的尾部,通过流最后一个操作节点的<code>copyInto()</code>方法进行元素遍历,按照<code>copyInto()</code>方法的套路,只要多层包装的<code>Sink</code>方法在回调其实现方法的时候总是激活<code>downstream</code>的前提下,执行的顺序就是流链式结构定义的操作节点顺序,而<code>forEach</code>最后添加的<code>Consumer</code>实例一定就是最后回调的。</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-12.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-12.png" alt="img"></a></p><p>接着分析<code>collect()</code>方法的实现,先看<code>Collector</code>接口的定义:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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="comment">// T:需要进行reduce操作的输入元素类型</span></span><br><span class="line"><span class="comment">// A:reduce操作中可变累加对象的类型,可以简单理解为累加操作中,累加到Container<A>中的可变对象类型</span></span><br><span class="line"><span class="comment">// R:reduce操作结果类型</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Collector</span><T, A, R> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 注释中称为Container,用于承载最终结果的可变容器,而此方法的Supplier实例持有的是创建Container实例的get()方法实现,后面称为Supplier</span></span><br><span class="line"> <span class="comment">// 也就是一般使用如:Supplier<Container> supplier = () -> new Container();</span></span><br><span class="line"> Supplier<A> <span class="title function_">supplier</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Accumulator,翻译为累加器,用于处理值并且把处理结果传递(累加)到Container中,后面称为Accumulator</span></span><br><span class="line"> BiConsumer<A, T> <span class="title function_">accumulator</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Combiner,翻译为合并器,真实泛型类型为BinaryOperator<A,A,A>,BiFunction的子类,接收两个部分的结果并且合并为一个结果,后面称为Combiner</span></span><br><span class="line"> <span class="comment">// 这个方法可以把一个参数的状态转移到另一个参数,然后返回更新状态后的参数,例如:(arg1, arg2) -> {arg2.state = arg1.state; return arg2;}</span></span><br><span class="line"> <span class="comment">// 可以把一个参数的状态转移到另一个参数,然后返回一个新的容器,例如:(arg1, arg2) -> {arg2.state = arg1.state; return new Container(arg2);}</span></span><br><span class="line"> BinaryOperator<A> <span class="title function_">combiner</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Finisher,直接翻译感觉意义不合理,实际上就是做最后一步转换工作的处理器,后面称为Finisher</span></span><br><span class="line"> Function<A, R> <span class="title function_">finisher</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Collector支持的特性集合,见枚举Characteristics</span></span><br><span class="line"> Set<Characteristics> <span class="title function_">characteristics</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里忽略两个Collector的静态工厂方法,因为并不常用</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">enum</span> <span class="title class_">Characteristics</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 标记Collector支持并发执行,一般和并发容器相关</span></span><br><span class="line"> CONCURRENT,</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 标记Collector处理元素时候无序</span></span><br><span class="line"> UNORDERED,</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 标记Collector的输入和输出元素是同类型,也就是Finisher在实现上R -> A可以等效于A -> R,unchecked cast会成功(也就是类型强转可以成功)</span></span><br><span class="line"> <span class="comment">// 在这种场景下,对于Container来说其实类型强制转换也是等效的,也就是Supplier<A>和Supplier<R>得出的Container是同一种类型的Container</span></span><br><span class="line"> IDENTITY_FINISH</span><br><span class="line"> } </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Collector的实现Collectors.CollectorImpl</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Collectors</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这一大堆常量就是预设的多种特性组合,CH_NOID比较特殊,是空集合,也就是Collector三种特性都不支持</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> Set<Collector.Characteristics> CH_CONCURRENT_ID</span><br><span class="line"> = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT,</span><br><span class="line"> Collector.Characteristics.UNORDERED,</span><br><span class="line"> Collector.Characteristics.IDENTITY_FINISH));</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> Set<Collector.Characteristics> CH_CONCURRENT_NOID</span><br><span class="line"> = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT,</span><br><span class="line"> Collector.Characteristics.UNORDERED));</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> Set<Collector.Characteristics> CH_ID</span><br><span class="line"> = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> Set<Collector.Characteristics> CH_UNORDERED_ID</span><br><span class="line"> = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED,</span><br><span class="line"> Collector.Characteristics.IDENTITY_FINISH));</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> Set<Collector.Characteristics> CH_NOID = Collections.emptySet();</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> Set<Collector.Characteristics> CH_UNORDERED_NOID</span><br><span class="line"> = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">Collectors</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><span class="line"> <span class="comment">// 静态类,Collector的实现,实现其实就是Supplier、Accumulator、Combiner、Finisher和Characteristics集合的成员属性承载</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">CollectorImpl</span><T, A, R> <span class="keyword">implements</span> <span class="title class_">Collector</span><T, A, R> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Supplier<A> supplier;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> BiConsumer<A, T> accumulator;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> BinaryOperator<A> combiner;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Function<A, R> finisher;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Set<Characteristics> characteristics;</span><br><span class="line"></span><br><span class="line"> CollectorImpl(Supplier<A> supplier,</span><br><span class="line"> BiConsumer<A, T> accumulator,</span><br><span class="line"> BinaryOperator<A> combiner,</span><br><span class="line"> Function<A,R> finisher,</span><br><span class="line"> Set<Characteristics> characteristics) {</span><br><span class="line"> <span class="built_in">this</span>.supplier = supplier;</span><br><span class="line"> <span class="built_in">this</span>.accumulator = accumulator;</span><br><span class="line"> <span class="built_in">this</span>.combiner = combiner;</span><br><span class="line"> <span class="built_in">this</span>.finisher = finisher;</span><br><span class="line"> <span class="built_in">this</span>.characteristics = characteristics;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> CollectorImpl(Supplier<A> supplier,</span><br><span class="line"> BiConsumer<A, T> accumulator,</span><br><span class="line"> BinaryOperator<A> combiner,</span><br><span class="line"> Set<Characteristics> characteristics) {</span><br><span class="line"> <span class="built_in">this</span>(supplier, accumulator, combiner, castingIdentity(), characteristics);</span><br><span class="line"> }</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> BiConsumer<A, T> <span class="title function_">accumulator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> accumulator;</span><br><span class="line"> }</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> Supplier<A> <span class="title function_">supplier</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> supplier;</span><br><span class="line"> }</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> BinaryOperator<A> <span class="title function_">combiner</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> combiner;</span><br><span class="line"> }</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> Function<A, R> <span class="title function_">finisher</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> finisher;</span><br><span class="line"> }</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> Set<Characteristics> <span class="title function_">characteristics</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> characteristics;</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><br><span class="line"> <span class="comment">// IDENTITY_FINISH特性下,Finisher的实现,也就是之前提到的A->R和R->A等效,可以强转</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <I, R> Function<I, R> <span class="title function_">castingIdentity</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> i -> (R) 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><br></pre></td></tr></table></figure><p><code>collect()</code>方法的求值执行入口在<code>ReferencePipeline</code>中:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ReferencePipeline</span></span><br><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ReferencePipeline</span><P_IN, P_OUT></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">AbstractPipeline</span><P_IN, P_OUT, Stream<P_OUT>></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">Stream</span><P_OUT> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 基于Collector实例进行求值</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> <R, A> R <span class="title function_">collect</span><span class="params">(Collector<? <span class="built_in">super</span> P_OUT, A, R> collector)</span> {</span><br><span class="line"> A container;</span><br><span class="line"> <span class="comment">// 并发求值场景暂不考虑</span></span><br><span class="line"> <span class="keyword">if</span> (isParallel()</span><br><span class="line"> && (collector.characteristics().contains(Collector.Characteristics.CONCURRENT))</span><br><span class="line"> && (!isOrdered() || collector.characteristics().contains(Collector.Characteristics.UNORDERED))) {</span><br><span class="line"> container = collector.supplier().get();</span><br><span class="line"> BiConsumer<A, ? <span class="built_in">super</span> P_OUT> accumulator = collector.accumulator();</span><br><span class="line"> forEach(u -> accumulator.accept(container, u));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 这里就是同步执行场景下的求值过程,这里可以看出其实所有Collector的求值都是Reduce操作</span></span><br><span class="line"> container = evaluate(ReduceOps.makeRef(collector));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 如果Collector的Finisher输入类型和输出类型相同,所以Supplier<A>和Supplier<R>得出的Container是同一种类型的Container,可以直接类型转换,否则就要调用Collector中的Finisher进行最后一步处理</span></span><br><span class="line"> <span class="keyword">return</span> collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)</span><br><span class="line"> ? (R) container</span><br><span class="line"> : collector.finisher().apply(container);</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><br><span class="line"></span><br><span class="line"><span class="comment">// ReduceOps</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ReduceOps</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">ReduceOps</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><span class="line"> <span class="comment">// 引用类型Reduce操作创建TerminalOp实例</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <T, I> TerminalOp<T, I></span><br><span class="line"> <span class="title function_">makeRef</span><span class="params">(Collector<? <span class="built_in">super</span> T, I, ?> collector)</span> {</span><br><span class="line"> <span class="comment">// Supplier</span></span><br><span class="line"> Supplier<I> supplier = Objects.requireNonNull(collector).supplier();</span><br><span class="line"> <span class="comment">// Accumulator</span></span><br><span class="line"> BiConsumer<I, ? <span class="built_in">super</span> T> accumulator = collector.accumulator();</span><br><span class="line"> <span class="comment">// Combiner</span></span><br><span class="line"> BinaryOperator<I> combiner = collector.combiner();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 这里注意一点,ReducingSink是方法makeRef中的内部类,作用域只在方法内,它是封装为TerminalOp最终转化为Sink链中最后一个Sink实例的类型</span></span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">ReducingSink</span> <span class="keyword">extends</span> <span class="title class_">Box</span><I></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">AccumulatingSink</span><T, I, ReducingSink> {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">begin</span><span class="params">(<span class="type">long</span> size)</span> {</span><br><span class="line"> <span class="comment">// 这里把从Supplier创建的新Container实例存放在父类Box的状态属性中</span></span><br><span class="line"> state = supplier.get();</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">accept</span><span class="params">(T t)</span> {</span><br><span class="line"> <span class="comment">// 处理元素,Accumulator处理状态(容器实例)和元素,这里可以想象,如果state为一个ArrayList实例,这里的accept()实现可能为add(ele)操作</span></span><br><span class="line"> accumulator.accept(state, t);</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">combine</span><span class="params">(ReducingSink other)</span> {</span><br><span class="line"> <span class="comment">// Combiner合并两个状态(容器实例)</span></span><br><span class="line"> state = combiner.apply(state, other.state);</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_">ReduceOp</span><T, I, ReducingSink>(StreamShape.REFERENCE) {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> ReducingSink <span class="title function_">makeSink</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ReducingSink</span>();</span><br><span class="line"> }</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">int</span> <span class="title function_">getOpFlags</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> collector.characteristics().contains(Collector.Characteristics.UNORDERED)</span><br><span class="line"> ? StreamOpFlag.NOT_ORDERED</span><br><span class="line"> : <span class="number">0</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><br><span class="line"> <span class="comment">// 继承自接口TerminalSink,主要添加了combine()抽象方法,用于合并元素</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">interface</span> <span class="title class_">AccumulatingSink</span><T, R, K <span class="keyword">extends</span> <span class="title class_">AccumulatingSink</span><T, R, K>></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">TerminalSink</span><T, R> {</span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">combine</span><span class="params">(K other)</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 状态盒,用于持有和获取状态,状态属性的修饰符为default,包内的类实例都能修改</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Box</span><U> {</span><br><span class="line"> U state;</span><br><span class="line"></span><br><span class="line"> Box() {} <span class="comment">// Avoid creation of special accessor</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> U <span class="title function_">get</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> state;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ReduceOp的最终实现,这个就是Reduce操作终结操作的实现</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ReduceOp</span><T, R, S <span class="keyword">extends</span> <span class="title class_">AccumulatingSink</span><T, R, S>></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">TerminalOp</span><T, R> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 流输入元素"形状"</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> StreamShape inputShape;</span><br><span class="line"></span><br><span class="line"> ReduceOp(StreamShape shape) {</span><br><span class="line"> inputShape = shape;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 抽象方法,让子类生成终结操作的Sink</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">abstract</span> S <span class="title function_">makeSink</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 class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> StreamShape <span class="title function_">inputShape</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> inputShape;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 同步执行求值,还是相似的思路,使用wrapAndCopyInto()进行Sink链构建和元素遍历</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <P_IN> R <span class="title function_">evaluateSequential</span><span class="params">(PipelineHelper<T> helper,</span></span><br><span class="line"><span class="params"> Spliterator<P_IN> spliterator)</span> {</span><br><span class="line"> <span class="comment">// 以当前的ReduceOp实例的makeSink()返回的Sink实例作为最后一个Sink添加到Sink链(也就是前面经常说的元素引用链),然后对Sink链进行遍历</span></span><br><span class="line"> <span class="comment">// 这里向上一步一步推演思考,最终get()方法执行完毕拿到的结果就是ReducingSink父类Box中的state变量,也就是容器实例</span></span><br><span class="line"> <span class="keyword">return</span> helper.wrapAndCopyInto(makeSink(), spliterator).get();</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <P_IN> R <span class="title function_">evaluateParallel</span><span class="params">(PipelineHelper<T> helper,</span></span><br><span class="line"><span class="params"> Spliterator<P_IN> spliterator)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ReduceTask</span><>(<span class="built_in">this</span>, helper, spliterator).invoke().get();</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><br></pre></td></tr></table></figure><p>接着就看<code>Collector</code>的静态工厂方法,看一些常用的<code>Collector</code>实例是如何构建的,例如看<code>Collectors.toList()</code>:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Supplier => () -> new ArrayList<T>(); // 初始化ArrayList</span></span><br><span class="line"><span class="comment">// Accumulator => (list,number) -> list.add(number); // 往ArrayList中添加元素</span></span><br><span class="line"><span class="comment">// Combiner => (left, right) -> { left.addAll(right); return left;} // 合并ArrayList</span></span><br><span class="line"><span class="comment">// Finisher => X -> X; // 输入什么就返回什么,这里实际返回的是ArrayList</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <T></span><br><span class="line">Collector<T, ?, List<T>> toList() {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CollectorImpl</span><>((Supplier<List<T>>) ArrayList::<span class="keyword">new</span>, List::add,</span><br><span class="line"> (left, right) -> { left.addAll(right); <span class="keyword">return</span> left; },</span><br><span class="line"> CH_ID);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>把过程画成流程图如下:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-13.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-13.png" alt="img"></a></p><p>甚至可以更通俗地用伪代码表示<code>Collector</code>这类<code>Terminal Op</code>的执行过程(还是以<code>Collectors.toList()</code>为例):</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">[begin]</span><br><span class="line"><span class="type">Supplier</span> <span class="variable">supplier</span> <span class="operator">=</span> () -> <span class="keyword">new</span> <span class="title class_">ArrayList</span><T>();</span><br><span class="line"><span class="type">Container</span> <span class="variable">container</span> <span class="operator">=</span> supplier.get();</span><br><span class="line">Box.state = container;</span><br><span class="line"></span><br><span class="line">[accept]</span><br><span class="line">Box.state.add(element);</span><br><span class="line"></span><br><span class="line">[end]</span><br><span class="line"><span class="keyword">return</span> supplier.get(); (=> <span class="keyword">return</span> Box.state);</span><br><span class="line"></span><br><span class="line">↓↓↓↓↓↓↓↓↓甚至更加通俗的过程如下↓↓↓↓↓↓↓↓↓↓↓↓↓↓</span><br><span class="line"></span><br><span class="line">ArrayList<T> container = <span class="keyword">new</span> <span class="title class_">ArrayList</span><T>();</span><br><span class="line">loop:</span><br><span class="line"> container.add(element)</span><br><span class="line"><span class="keyword">return</span> container;</span><br></pre></td></tr></table></figure><p>也就是虽然工程化的代码看起来很复杂,最终的实现就是简单的:初始化<code>ArrayList</code>实例由<code>state</code>属性持有,遍历处理元素的时候把元素添加到<code>state</code>中,最终返回<code>state</code>。最后看<code>toArray()</code>的方法实现(下面的方法代码没有按照实际的位置贴出,笔者把零散的代码块放在一起方便分析):</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">ReferencePipeline</span><P_IN, P_OUT></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">AbstractPipeline</span><P_IN, P_OUT, Stream<P_OUT>></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">Stream</span><P_OUT> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 流的所有元素转换为数组,这里的IntFunction有一种比较特殊的用法,就是用于创建数组实例</span></span><br><span class="line"> <span class="comment">// 例如IntFunction<String[]> f = String::new; String[] arry = f.apply(2); 相当于String[] arry = new String[2];</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> <A> A[] toArray(IntFunction<A[]> generator) {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这里主动擦除了IntFunction的类型,只要保证求值的过程是正确,最终可以做类型强转</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("rawtypes")</span></span><br><span class="line"> <span class="type">IntFunction</span> <span class="variable">rawGenerator</span> <span class="operator">=</span> (IntFunction) generator;</span><br><span class="line"> <span class="comment">// 委托到evaluateToArrayNode()方法进行计算</span></span><br><span class="line"> <span class="keyword">return</span> (A[]) Nodes.flatten(evaluateToArrayNode(rawGenerator), rawGenerator)</span><br><span class="line"> .asArray(rawGenerator);</span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 流的所有元素转换为Object数组</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> Object[] toArray() {</span><br><span class="line"> <span class="keyword">return</span> toArray(Object[]::<span class="keyword">new</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 流元素求值转换为ArrayNode</span></span><br><span class="line"> <span class="keyword">final</span> Node<E_OUT> <span class="title function_">evaluateToArrayNode</span><span class="params">(IntFunction<E_OUT[]> generator)</span> {</span><br><span class="line"> <span class="comment">// 确保不会处理多次</span></span><br><span class="line"> <span class="keyword">if</span> (linkedOrConsumed)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(MSG_STREAM_LINKED);</span><br><span class="line"> linkedOrConsumed = <span class="literal">true</span>;</span><br><span class="line"> <span class="comment">// 并发执行暂时跳过</span></span><br><span class="line"> <span class="keyword">if</span> (isParallel() && previousStage != <span class="literal">null</span> && opIsStateful()) {</span><br><span class="line"> depth = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">return</span> opEvaluateParallel(previousStage, previousStage.sourceSpliterator(<span class="number">0</span>), generator);</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> evaluate(sourceSpliterator(<span class="number">0</span>), <span class="literal">true</span>, generator);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 最终的转换Node的方法</span></span><br><span class="line"> <span class="keyword">final</span> <P_IN> Node<E_OUT> <span class="title function_">evaluate</span><span class="params">(Spliterator<P_IN> spliterator,</span></span><br><span class="line"><span class="params"> <span class="type">boolean</span> flatten,</span></span><br><span class="line"><span class="params"> IntFunction<E_OUT[]> generator)</span> {</span><br><span class="line"> <span class="comment">// 并发执行暂时跳过</span></span><br><span class="line"> <span class="keyword">if</span> (isParallel()) {</span><br><span class="line"> <span class="comment">// @@@ Optimize if op of this pipeline stage is a stateful op</span></span><br><span class="line"> <span class="keyword">return</span> evaluateToNode(<span class="built_in">this</span>, spliterator, flatten, generator);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 兜兜转换还是回到了wrapAndCopyInto()方法,遍历Sink链,所以基本可以得知Node.Builder是Sink的一个实现</span></span><br><span class="line"> Node.Builder<E_OUT> nb = makeNodeBuilder(</span><br><span class="line"> exactOutputSizeIfKnown(spliterator), generator);</span><br><span class="line"> <span class="keyword">return</span> wrapAndCopyInto(nb, spliterator).build();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 获取Node的建造器实例</span></span><br><span class="line"> <span class="keyword">final</span> Node.Builder<P_OUT> <span class="title function_">makeNodeBuilder</span><span class="params">(<span class="type">long</span> exactSizeIfKnown, IntFunction<P_OUT[]> generator)</span> {</span><br><span class="line"> <span class="keyword">return</span> Nodes.builder(exactSizeIfKnown, generator);</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><br><span class="line"></span><br><span class="line"><span class="comment">// Node接口定义</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Node</span><T> {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 获取待处理的元素封装成的Spliterator实例</span></span><br><span class="line"> Spliterator<T> <span class="title function_">spliterator</span><span class="params">()</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 遍历当前Node实例中所有待处理的元素,回调到Consumer实例中</span></span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">forEach</span><span class="params">(Consumer<? <span class="built_in">super</span> T> consumer)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取当前Node实例的所有子Node的个数</span></span><br><span class="line"> <span class="keyword">default</span> <span class="type">int</span> <span class="title function_">getChildCount</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">// 获取当前Node实例的子Node实例,入参i是子Node的索引</span></span><br><span class="line"> <span class="keyword">default</span> Node<T> <span class="title function_">getChild</span><span class="params">(<span class="type">int</span> i)</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IndexOutOfBoundsException</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 分割当前Node实例的一个部分,生成一个新的sub Node,类似于ArrayList中的subList方法</span></span><br><span class="line"> <span class="keyword">default</span> Node<T> <span class="title function_">truncate</span><span class="params">(<span class="type">long</span> from, <span class="type">long</span> to, IntFunction<T[]> generator)</span> {</span><br><span class="line"> <span class="keyword">if</span> (from == <span class="number">0</span> && to == count())</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> Spliterator<T> spliterator = spliterator();</span><br><span class="line"> <span class="type">long</span> <span class="variable">size</span> <span class="operator">=</span> to - from;</span><br><span class="line"> Node.Builder<T> nodeBuilder = Nodes.builder(size, generator);</span><br><span class="line"> nodeBuilder.begin(size);</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 < from && spliterator.tryAdvance(e -> { }); i++) { }</span><br><span class="line"> <span class="keyword">if</span> (to == count()) {</span><br><span class="line"> spliterator.forEachRemaining(nodeBuilder);</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">i</span> <span class="operator">=</span> <span class="number">0</span>; i < size && spliterator.tryAdvance(nodeBuilder); i++) { }</span><br><span class="line"> }</span><br><span class="line"> nodeBuilder.end();</span><br><span class="line"> <span class="keyword">return</span> nodeBuilder.build();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 创建一个包含当前Node实例所有元素的元素数组视图</span></span><br><span class="line"> T[] asArray(IntFunction<T[]> generator);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="keyword">void</span> <span class="title function_">copyInto</span><span class="params">(T[] array, <span class="type">int</span> offset)</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回Node实例基于Stream的元素"形状"</span></span><br><span class="line"> <span class="keyword">default</span> StreamShape <span class="title function_">getShape</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> StreamShape.REFERENCE;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取当前Node实例包含的元素个数</span></span><br><span class="line"> <span class="type">long</span> <span class="title function_">count</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Node建造器,注意这个Node.Builder接口是继承自Sink,那么其子类实现都可以添加到Sink链中作为一个节点(终结节点)</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Builder</span><T> <span class="keyword">extends</span> <span class="title class_">Sink</span><T> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建Node实例</span></span><br><span class="line"> Node<T> <span class="title function_">build</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 基于Integer元素类型的特化类型Node.Builder</span></span><br><span class="line"> <span class="keyword">interface</span> <span class="title class_">OfInt</span> <span class="keyword">extends</span> <span class="title class_">Node</span>.Builder<Integer>, Sink.OfInt {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> Node.OfInt <span class="title function_">build</span><span class="params">()</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 基于Long元素类型的特化类型Node.Builder</span></span><br><span class="line"> <span class="keyword">interface</span> <span class="title class_">OfLong</span> <span class="keyword">extends</span> <span class="title class_">Node</span>.Builder<Long>, Sink.OfLong {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> Node.OfLong <span class="title function_">build</span><span class="params">()</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 基于Double元素类型的特化类型Node.Builder</span></span><br><span class="line"> <span class="keyword">interface</span> <span class="title class_">OfDouble</span> <span class="keyword">extends</span> <span class="title class_">Node</span>.Builder<Double>, Sink.OfDouble {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> Node.OfDouble <span class="title function_">build</span><span class="params">()</span>;</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><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里下面的方法来源于Nodes类</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">Nodes</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Node扁平化处理,如果传入的Node实例存在子Node实例,则使用fork-join对Node进行分割和并发计算,结果添加到IntFunction生成的数组中,如果不存在子Node,直接返回传入的Node实例</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> <T> Node<T> <span class="title function_">flatten</span><span class="params">(Node<T> node, IntFunction<T[]> generator)</span> {</span><br><span class="line"> <span class="keyword">if</span> (node.getChildCount() > <span class="number">0</span>) {</span><br><span class="line"> <span class="type">long</span> <span class="variable">size</span> <span class="operator">=</span> node.count();</span><br><span class="line"> <span class="keyword">if</span> (size >= MAX_ARRAY_SIZE)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(BAD_SIZE);</span><br><span class="line"> T[] array = generator.apply((<span class="type">int</span>) size);</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">ToArrayTask</span>.OfRef<>(node, array, <span class="number">0</span>).invoke();</span><br><span class="line"> <span class="keyword">return</span> node(array);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> node;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 创建Node的建造器实例</span></span><br><span class="line"> <span class="keyword">static</span> <T> Node.Builder<T> <span class="title function_">builder</span><span class="params">(<span class="type">long</span> exactSizeIfKnown, IntFunction<T[]> generator)</span> {</span><br><span class="line"> <span class="comment">// 当知道待处理元素的准确数量并且小于允许创建的数组的最大长度MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8),使用FixedNodeBuilder(固定长度数组Node建造器),否则使用SpinedNodeBuilder实例</span></span><br><span class="line"> <span class="keyword">return</span> (exactSizeIfKnown >= <span class="number">0</span> && exactSizeIfKnown < MAX_ARRAY_SIZE)</span><br><span class="line"> ? <span class="keyword">new</span> <span class="title class_">FixedNodeBuilder</span><>(exactSizeIfKnown, generator)</span><br><span class="line"> : builder();</span><br><span class="line"> } </span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建Node的建造器实例,使用SpinedNodeBuilder的实例,此SpinedNode支持元素添加,但是不支持元素移除</span></span><br><span class="line"> <span class="keyword">static</span> <T> Node.Builder<T> <span class="title function_">builder</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SpinedNodeBuilder</span><>();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 固定长度固定长度数组Node实现(也就是最终的Node实现是一个ArrayNode,最终的容器为一个T类型元素的数组T[])</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">FixedNodeBuilder</span><T></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">ArrayNode</span><T></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">Node</span>.Builder<T> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 基于size(元素个数,或者说创建数组的长度)和数组创建方法IntFunction构建FixedNodeBuilder实例</span></span><br><span class="line"> FixedNodeBuilder(<span class="type">long</span> size, IntFunction<T[]> generator) {</span><br><span class="line"> <span class="built_in">super</span>(size, generator);</span><br><span class="line"> <span class="keyword">assert</span> size < MAX_ARRAY_SIZE;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回当前FixedNodeBuilder实例,判断数组元素计数值curSize必须大于等于实际数组容器中元素的个数</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Node<T> <span class="title function_">build</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (curSize < array.length)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(String.format(<span class="string">"Current size %d is less than fixed size %d"</span>,</span><br><span class="line"> curSize, array.length));</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Sink的begin方法回调,传入的size必须和数组长度相等,因为后面的accept()方法会执行size此</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">begin</span><span class="params">(<span class="type">long</span> size)</span> {</span><br><span class="line"> <span class="keyword">if</span> (size != array.length)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(String.format(<span class="string">"Begin size %d is not equal to fixed size %d"</span>,</span><br><span class="line"> size, array.length));</span><br><span class="line"> <span class="comment">// 重置数组元素计数值为0</span></span><br><span class="line"> curSize = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Sink的accept方法回调,当数组元素计数值小于数组长度,直接向数组下标curSize++添加传入的元素</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">accept</span><span class="params">(T t)</span> {</span><br><span class="line"> <span class="keyword">if</span> (curSize < array.length) {</span><br><span class="line"> array[curSize++] = t;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(String.format(<span class="string">"Accept exceeded fixed size of %d"</span>,</span><br><span class="line"> array.length));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Sink的end方法回调,再次判断数组元素计数值curSize必须大于等于实际数组容器中元素的个数</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">end</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (curSize < array.length)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(String.format(<span class="string">"End size %d is less than fixed size %d"</span>,</span><br><span class="line"> curSize, array.length));</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回FixedNodeBuilder当前信息,当前处理的下标和当前数组中所有的元素</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> String.format(<span class="string">"FixedNodeBuilder[%d][%s]"</span>,</span><br><span class="line"> array.length - curSize, Arrays.toString(array));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Node实现,容器为一个固定长度的数组</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_">ArrayNode</span><T> <span class="keyword">implements</span> <span class="title class_">Node</span><T> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 数组容器</span></span><br><span class="line"> <span class="keyword">final</span> T[] array;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 数组容器中当前元素的个数,这个值是一个固定值,或者在FixedNodeBuilder的accept()方法回调中递增</span></span><br><span class="line"> <span class="type">int</span> curSize;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 基于size和数组创建的工厂IntFunction构建ArrayNode实例</span></span><br><span class="line"> <span class="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> ArrayNode(<span class="type">long</span> size, IntFunction<T[]> generator) {</span><br><span class="line"> <span class="keyword">if</span> (size >= MAX_ARRAY_SIZE)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(BAD_SIZE);</span><br><span class="line"> <span class="comment">// 创建szie长度的数组容器</span></span><br><span class="line"> <span class="built_in">this</span>.array = generator.apply((<span class="type">int</span>) size);</span><br><span class="line"> <span class="built_in">this</span>.curSize = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这个方法是基于一个现成的数组创建ArrayNode实例,直接改变数组的引用为array,元素个数curSize置为输入参数长度</span></span><br><span class="line"> ArrayNode(T[] array) {</span><br><span class="line"> <span class="built_in">this</span>.array = array;</span><br><span class="line"> <span class="built_in">this</span>.curSize = array.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Node - 接下来是Node接口的实现</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 基于数组实例,起始索引0和结束索引curSize构造一个全新的Spliterator实例</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Spliterator<T> <span class="title function_">spliterator</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> Arrays.spliterator(array, <span class="number">0</span>, curSize);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 拷贝array中的元素到外部传入的dest数组中</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">copyInto</span><span class="params">(T[] dest, <span class="type">int</span> destOffset)</span> {</span><br><span class="line"> System.arraycopy(array, <span class="number">0</span>, dest, destOffset, curSize);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回元素数组视图,这里直接返回array引用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> T[] asArray(IntFunction<T[]> generator) {</span><br><span class="line"> <span class="keyword">if</span> (array.length == curSize) {</span><br><span class="line"> <span class="keyword">return</span> array;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 获取array中的元素个数</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">count</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> curSize;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 遍历array,每个元素回调Consumer实例</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">forEach</span><span class="params">(Consumer<? <span class="built_in">super</span> T> consumer)</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 < curSize; i++) {</span><br><span class="line"> consumer.accept(array[i]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 返回ArrayNode当前信息,当前处理的下标和当前数组中所有的元素</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> String.format(<span class="string">"ArrayNode[%d][%s]"</span>,</span><br><span class="line"> array.length - curSize, Arrays.toString(array));</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><br></pre></td></tr></table></figure><p>很多集合容器的<code>Spliterator</code>其实并不支持<code>SIZED</code>特性,其实<code>Node</code>的最终实现很多情况下都是<code>Nodes.SpinedNodeBuilder</code>,因为<code>SpinedNodeBuilder</code>重实现实现了数组扩容和<code>Spliterator</code>基于数组进行分割的方法,源码相对复杂(特别是<code>spliterator()</code>方法),这里挑部分进行分析,由于<code>SpinedNodeBuilder</code>绝大部分方法都是使用父类<code>SpinedBuffer</code>中的实现,这里可以直接分析<code>SpinedBuffer</code>:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// SpinedBuffer的当前数组在超过了元素数量阈值之后,会拆分为多个数组块,存储到spine中,而curChunk引用指向的是当前处理的数组块</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SpinedBuffer</span><E></span><br><span class="line"> <span class="keyword">extends</span> <span class="title class_">AbstractSpinedBuffer</span></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">Consumer</span><E>, Iterable<E> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 当前的数组块</span></span><br><span class="line"> <span class="keyword">protected</span> E[] curChunk;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 所有数组块</span></span><br><span class="line"> <span class="keyword">protected</span> E[][] spine;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 构造函数,指定初始化容量</span></span><br><span class="line"> SpinedBuffer(<span class="type">int</span> initialCapacity) {</span><br><span class="line"> <span class="built_in">super</span>(initialCapacity);</span><br><span class="line"> curChunk = (E[]) <span class="keyword">new</span> <span class="title class_">Object</span>[<span class="number">1</span> << initialChunkPower];</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="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> SpinedBuffer() {</span><br><span class="line"> <span class="built_in">super</span>();</span><br><span class="line"> curChunk = (E[]) <span class="keyword">new</span> <span class="title class_">Object</span>[<span class="number">1</span> << initialChunkPower];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 拷贝当前SpinedBuffer中的数组元素到传入的数组实例</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">copyInto</span><span class="params">(E[] array, <span class="type">int</span> offset)</span> {</span><br><span class="line"> <span class="comment">// 计算最终的offset,区分单个chunk和多个chunk的情况</span></span><br><span class="line"> <span class="type">long</span> <span class="variable">finalOffset</span> <span class="operator">=</span> offset + count();</span><br><span class="line"> <span class="keyword">if</span> (finalOffset > array.length || finalOffset < offset) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IndexOutOfBoundsException</span>(<span class="string">"does not fit"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 单个chunk的情况,由curChunk最接拷贝</span></span><br><span class="line"> <span class="keyword">if</span> (spineIndex == <span class="number">0</span>)</span><br><span class="line"> System.arraycopy(curChunk, <span class="number">0</span>, array, offset, elementIndex);</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 多个chunk的情况,由遍历spine并且对每个chunk进行拷贝</span></span><br><span class="line"> <span class="comment">// full chunks</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i < spineIndex; i++) {</span><br><span class="line"> System.arraycopy(spine[i], <span class="number">0</span>, array, offset, spine[i].length);</span><br><span class="line"> offset += spine[i].length;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (elementIndex > <span class="number">0</span>)</span><br><span class="line"> System.arraycopy(curChunk, <span class="number">0</span>, array, offset, elementIndex);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回数组元素视图,基于IntFunction构建数组实例,使用copyInto()方法进行元素拷贝</span></span><br><span class="line"> <span class="keyword">public</span> E[] asArray(IntFunction<E[]> arrayFactory) {</span><br><span class="line"> <span class="type">long</span> <span class="variable">size</span> <span class="operator">=</span> count();</span><br><span class="line"> <span class="keyword">if</span> (size >= Nodes.MAX_ARRAY_SIZE)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(Nodes.BAD_SIZE);</span><br><span class="line"> E[] result = arrayFactory.apply((<span class="type">int</span>) size);</span><br><span class="line"> copyInto(result, <span class="number">0</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="comment">// 清空SpinedBuffer,清空分块元素和所有引用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">clear</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (spine != <span class="literal">null</span>) {</span><br><span class="line"> curChunk = spine[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i<curChunk.length; i++)</span><br><span class="line"> curChunk[i] = <span class="literal">null</span>;</span><br><span class="line"> spine = <span class="literal">null</span>;</span><br><span class="line"> priorElementCount = <span class="literal">null</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> i=<span class="number">0</span>; i<elementIndex; i++)</span><br><span class="line"> curChunk[i] = <span class="literal">null</span>;</span><br><span class="line"> }</span><br><span class="line"> elementIndex = <span class="number">0</span>;</span><br><span class="line"> spineIndex = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 遍历元素回调Consumer,分别遍历spine和curChunk</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">forEach</span><span class="params">(Consumer<? <span class="built_in">super</span> E> consumer)</span> {</span><br><span class="line"> <span class="comment">// completed chunks, if any</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 < spineIndex; j++)</span><br><span class="line"> <span class="keyword">for</span> (E t : spine[j])</span><br><span class="line"> consumer.accept(t);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// current chunk</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i<elementIndex; i++)</span><br><span class="line"> consumer.accept(curChunk[i]);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Consumer的accept实现,最终会作为Sink接口的accept方法调用</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">accept</span><span class="params">(E e)</span> {</span><br><span class="line"> <span class="comment">// 如果当前分块(第一个)的元素已经满了,就初始化spine,然后元素添加到spine[0]中</span></span><br><span class="line"> <span class="keyword">if</span> (elementIndex == curChunk.length) {</span><br><span class="line"> inflateSpine();</span><br><span class="line"> <span class="comment">// 然后元素添加到spine[0]中的元素已经满了,就新增spine[n],把元素放进spine[n]中</span></span><br><span class="line"> <span class="keyword">if</span> (spineIndex+<span class="number">1</span> >= spine.length || spine[spineIndex+<span class="number">1</span>] == <span class="literal">null</span>)</span><br><span class="line"> increaseCapacity();</span><br><span class="line"> elementIndex = <span class="number">0</span>;</span><br><span class="line"> ++spineIndex;</span><br><span class="line"> <span class="comment">// 当前的chunk更新为最新的chunk,就是spine中的最新一个chunk</span></span><br><span class="line"> curChunk = spine[spineIndex];</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 当前的curChunk添加元素</span></span><br><span class="line"> curChunk[elementIndex++] = e;</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><br></pre></td></tr></table></figure><p>源码已经基本分析完毕,下面还是用一个例子转化为流程图:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-14.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-14.png" alt="img"></a></p><h2 id="流并发执行的源码实现"><a href="#流并发执行的源码实现" class="headerlink" title="流并发执行的源码实现"></a>流并发执行的源码实现</h2><p>如果流实例调用了<code>parallel()</code>,注释中提到会返回一个异步执行流的变体,实际上并没有构造变体,只是把<code>sourceStage.parallel</code>标记为<code>true</code>,异步求值的基本过程是:构建流管道结构的时候和同步求值的过程一致,构建完<code>Sink</code>链之后,<code>Spliterator</code>会使用特定算法基于<code>trySplit()</code>进行自分割,自分割算法由具体的子类决定,例如<code>ArrayList</code>采用的就是二分法,分割完成后每个<code>Spliterator</code>持有所有元素中的一小部分,然后把每个<code>Spliterator</code>作为<code>sourceSpliterator</code>在<code>fork-join</code>线程池中执行<code>Sink</code>链,得到多个部分的结果在当前调用线程中聚合,得到最终结果。这里用到的技巧就是:线程封闭和<code>fork-join</code>。因为不同<code>Terminal Op</code>的并发求值过程大同小异,这里只分析<code>forEach</code>并发执行的实现。首先展示一个使用<code>fork-join</code>线程池的简单例子:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MapReduceApp</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="comment">// 数组中每个元素*2,再求和</span></span><br><span class="line"> <span class="type">Integer</span> <span class="variable">result</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MapReducer</span><>(<span class="keyword">new</span> <span class="title class_">Integer</span>[]{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>}, x -> x * <span class="number">2</span>, Integer::sum).invoke();</span><br><span class="line"> System.out.println(result);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">interface</span> <span class="title class_">Mapper</span><S, T> {</span><br><span class="line"></span><br><span class="line"> T <span class="title function_">apply</span><span class="params">(S source)</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">interface</span> <span class="title class_">Reducer</span><S, T> {</span><br><span class="line"></span><br><span class="line"> T <span class="title function_">apply</span><span class="params">(S first, S second)</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="keyword">class</span> <span class="title class_">MapReducer</span><T> <span class="keyword">extends</span> <span class="title class_">CountedCompleter</span><T> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> T[] array;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> Mapper<T, T> mapper;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> Reducer<T, T> reducer;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> <span class="type">int</span> lo, hi;</span><br><span class="line"></span><br><span class="line"> MapReducer<T> sibling;</span><br><span class="line"></span><br><span class="line"> T result;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">MapReducer</span><span class="params">(T[] array,</span></span><br><span class="line"><span class="params"> Mapper<T, T> mapper,</span></span><br><span class="line"><span class="params"> Reducer<T, T> reducer)</span> {</span><br><span class="line"> <span class="built_in">this</span>.array = array;</span><br><span class="line"> <span class="built_in">this</span>.mapper = mapper;</span><br><span class="line"> <span class="built_in">this</span>.reducer = reducer;</span><br><span class="line"> <span class="built_in">this</span>.lo = <span class="number">0</span>;</span><br><span class="line"> <span class="built_in">this</span>.hi = array.length;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">MapReducer</span><span class="params">(CountedCompleter<?> p,</span></span><br><span class="line"><span class="params"> T[] array,</span></span><br><span class="line"><span class="params"> Mapper<T, T> mapper,</span></span><br><span class="line"><span class="params"> Reducer<T, T> reducer,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> lo,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> hi)</span> {</span><br><span class="line"> <span class="built_in">super</span>(p);</span><br><span class="line"> <span class="built_in">this</span>.array = array;</span><br><span class="line"> <span class="built_in">this</span>.mapper = mapper;</span><br><span class="line"> <span class="built_in">this</span>.reducer = reducer;</span><br><span class="line"> <span class="built_in">this</span>.lo = lo;</span><br><span class="line"> <span class="built_in">this</span>.hi = hi;</span><br><span class="line"> }</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="keyword">void</span> <span class="title function_">compute</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">if</span> (hi - lo >= <span class="number">2</span>) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> (lo + hi) >> <span class="number">1</span>;</span><br><span class="line"> MapReducer<T> left = <span class="keyword">new</span> <span class="title class_">MapReducer</span><>(<span class="built_in">this</span>, array, mapper, reducer, lo, mid);</span><br><span class="line"> MapReducer<T> right = <span class="keyword">new</span> <span class="title class_">MapReducer</span><>(<span class="built_in">this</span>, array, mapper, reducer, mid, hi);</span><br><span class="line"> left.sibling = right;</span><br><span class="line"> right.sibling = left;</span><br><span class="line"> <span class="comment">// 创建子任务父任务的pending计数器加1</span></span><br><span class="line"> setPendingCount(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 提交右子任务</span></span><br><span class="line"> right.fork();</span><br><span class="line"> <span class="comment">// 在当前线程计算左子任务</span></span><br><span class="line"> left.compute();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (hi > lo) {</span><br><span class="line"> result = mapper.apply(array[lo]);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 叶子节点完成,尝试合并其他兄弟节点的结果,会调用onCompletion方法</span></span><br><span class="line"> tryComplete();</span><br><span class="line"> }</span><br><span class="line"> }</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> T <span class="title function_">getRawResult</span><span class="params">()</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="meta">@SuppressWarnings("unchecked")</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onCompletion</span><span class="params">(CountedCompleter<?> caller)</span> {</span><br><span class="line"> <span class="keyword">if</span> (caller != <span class="built_in">this</span>) {</span><br><span class="line"> MapReducer<T> child = (MapReducer<T>) caller;</span><br><span class="line"> MapReducer<T> sib = child.sibling;</span><br><span class="line"> <span class="comment">// 合并子任务结果,只有两个子任务</span></span><br><span class="line"> <span class="keyword">if</span> (Objects.isNull(sib) || Objects.isNull(sib.result)) {</span><br><span class="line"> result = child.result;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> result = reducer.apply(child.result, sib.result);</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></table></figure><p>这里简单使用了<code>fork-join</code>编写了一个简易的<code>MapReduce</code>应用,<code>main</code>方法中运行的是数组<code>[1,2,3,4]</code>中的所有元素先映射为<code>i -> i * 2</code>,再进行<code>reduce</code>(求和)的过程,代码中也是简单使用二分法对原始的<code>array</code>进行分割,当最终的任务只包含一个元素,也就是<code>lo < hi</code>且<code>hi - lo == 1</code>的时候,会基于单个元素调用<code>Mapper</code>的方法进行完成通知<code>tryComplete()</code>,任务完成会最终通知<code>onCompletion()</code>方法,<code>Reducer</code>就是在此方法中进行结果的聚合操作。对于流的并发求值来说,过程是类似的,<code>ForEachOp</code>中最终调用<code>ForEachOrderedTask</code>或者<code>ForEachTask</code>,这里挑选<code>ForEachTask</code>进行分析:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ForEachOp</span><T></span><br><span class="line"> <span class="keyword">implements</span> <span class="title class_">TerminalOp</span><T, Void>, TerminalSink<T, Void> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 暂时省略其他代码</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <S> Void <span class="title function_">evaluateParallel</span><span class="params">(PipelineHelper<T> helper,</span></span><br><span class="line"><span class="params"> Spliterator<S> spliterator)</span> {</span><br><span class="line"> <span class="keyword">if</span> (ordered)</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">ForEachOrderedTask</span><>(helper, spliterator, <span class="built_in">this</span>).invoke();</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> <span class="comment">// 最终是调用ForEachTask的invoke方法,invoke会阻塞到所有fork任务执行完,获取最终的结果</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">ForEachTask</span><>(helper, spliterator, helper.wrapSink(<span class="built_in">this</span>)).invoke();</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">// 暂时省略其他代码</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// ForEachOps类</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ForEachOps</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">ForEachOps</span><span class="params">()</span> { }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// forEach的fork-join任务实现,没有覆盖getRawResult()方法,最终只会返回NULL</span></span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ForEachTask</span><S, T> <span class="keyword">extends</span> <span class="title class_">CountedCompleter</span><Void> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Spliterator实例,如果是父任务则代表所有待处理的元素,如果是子任务则是一个分割后的新Spliterator实例</span></span><br><span class="line"> <span class="keyword">private</span> Spliterator<S> spliterator;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Sink链实例</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Sink<S> sink;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 流管道引用</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> PipelineHelper<T> helper;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 目标数量,其实是每个任务处理元素数量的建议值</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">long</span> targetSize;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这个构造器是提供给父(根)任务</span></span><br><span class="line"> ForEachTask(PipelineHelper<T> helper,</span><br><span class="line"> Spliterator<S> spliterator,</span><br><span class="line"> Sink<S> sink) {</span><br><span class="line"> <span class="built_in">super</span>(<span class="literal">null</span>);</span><br><span class="line"> <span class="built_in">this</span>.sink = sink;</span><br><span class="line"> <span class="built_in">this</span>.helper = helper;</span><br><span class="line"> <span class="built_in">this</span>.spliterator = spliterator;</span><br><span class="line"> <span class="built_in">this</span>.targetSize = <span class="number">0L</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 这个构造器是提供给子任务,所以需要父任务的引用和一个分割后的新Spliterator实例作为参数</span></span><br><span class="line"> ForEachTask(ForEachTask<S, T> parent, Spliterator<S> spliterator) {</span><br><span class="line"> <span class="built_in">super</span>(parent);</span><br><span class="line"> <span class="built_in">this</span>.spliterator = spliterator;</span><br><span class="line"> <span class="built_in">this</span>.sink = parent.sink;</span><br><span class="line"> <span class="built_in">this</span>.targetSize = parent.targetSize;</span><br><span class="line"> <span class="built_in">this</span>.helper = parent.helper;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Similar to AbstractTask but doesn't need to track child tasks</span></span><br><span class="line"> <span class="comment">// 实现compute方法,用于分割Spliterator成多个子任务,这里不需要跟踪所有子任务</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">compute</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">// 神奇的赋值,相当于Spliterator<S> rightSplit = spliterator; Spliterator<S> leftSplit;</span></span><br><span class="line"> <span class="comment">// rightSplit总是指向当前的spliterator实例</span></span><br><span class="line"> Spliterator<S> rightSplit = spliterator, leftSplit;</span><br><span class="line"> <span class="comment">// 这里也是神奇的赋值,相当于long sizeEstimate = rightSplit.estimateSize(); long sizeThreshold;</span></span><br><span class="line"> <span class="type">long</span> <span class="variable">sizeEstimate</span> <span class="operator">=</span> rightSplit.estimateSize(), sizeThreshold;</span><br><span class="line"> <span class="comment">// sizeThreshold赋值为targetSize</span></span><br><span class="line"> <span class="keyword">if</span> ((sizeThreshold = targetSize) == <span class="number">0L</span>)</span><br><span class="line"> <span class="comment">// 基于Spliterator分割后的右分支实例的元素数量重新赋值sizeThreshold和targetSize</span></span><br><span class="line"> <span class="comment">// 计算方式是待处理元素数量/(fork-join线程池并行度<<2)或者1(当前一个计算方式结果为0的时候)</span></span><br><span class="line"> targetSize = sizeThreshold = AbstractTask.suggestTargetSize(sizeEstimate);</span><br><span class="line"> <span class="comment">// 当前的流是否支持SHORT_CIRCUIT,也就是短路特性</span></span><br><span class="line"> <span class="type">boolean</span> <span class="variable">isShortCircuit</span> <span class="operator">=</span> StreamOpFlag.SHORT_CIRCUIT.isKnown(helper.getStreamAndOpFlags());</span><br><span class="line"> <span class="comment">// 当前的任务是否fork右分支</span></span><br><span class="line"> <span class="type">boolean</span> <span class="variable">forkRight</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"> <span class="comment">// taskSink作为Sink的临时变量</span></span><br><span class="line"> Sink<S> taskSink = sink;</span><br><span class="line"> <span class="comment">// 当前任务的临时变量</span></span><br><span class="line"> ForEachTask<S, T> task = <span class="built_in">this</span>;</span><br><span class="line"> <span class="comment">// Spliterator分割和创建新的fork任务ForEachTask,前提是不支持短路或者Sink不支持取消</span></span><br><span class="line"> <span class="keyword">while</span> (!isShortCircuit || !taskSink.cancellationRequested()) {</span><br><span class="line"> <span class="comment">// 当前的任务中的Spliterator(rightSplit)中的待处理元素小于等于每个任务应该处理的元素阈值或者再分割后得到NULL,则不需要再分割,直接基于rightSplit和Sink链执行循环处理元素</span></span><br><span class="line"> <span class="keyword">if</span> (sizeEstimate <= sizeThreshold || (leftSplit = rightSplit.trySplit()) == <span class="literal">null</span>) {</span><br><span class="line"> <span class="comment">// 这里就是遍历rightSplit元素回调Sink链的操作</span></span><br><span class="line"> task.helper.copyInto(taskSink, rightSplit);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// rightSplit还能分割,则基于分割后的leftSplit和以当前任务作为父任务创建一个新的fork任务</span></span><br><span class="line"> ForEachTask<S, T> leftTask = <span class="keyword">new</span> <span class="title class_">ForEachTask</span><>(task, leftSplit);</span><br><span class="line"> <span class="comment">// 待处理子任务加1</span></span><br><span class="line"> task.addToPendingCount(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 需要fork的任务实例临时变量</span></span><br><span class="line"> ForEachTask<S, T> taskToFork;</span><br><span class="line"> <span class="comment">// 因为rightSplit总是分割Spliterator后对应原来的Spliterator引用,而leftSplit总是trySplit()后生成的新的Spliterator</span></span><br><span class="line"> <span class="comment">// 所以这里leftSplit也需要作为rightSplit进行分割,通俗来说就是周星驰007那把梅花间足发射的枪</span></span><br><span class="line"> <span class="keyword">if</span> (forkRight) {</span><br><span class="line"> <span class="comment">// 这里交换leftSplit为rightSplit,所以forkRight设置为false,下一轮循环相当于fork left</span></span><br><span class="line"> forkRight = <span class="literal">false</span>;</span><br><span class="line"> rightSplit = leftSplit;</span><br><span class="line"> taskToFork = task;</span><br><span class="line"> <span class="comment">// 赋值下一轮的父Task为当前的fork task</span></span><br><span class="line"> task = leftTask;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> forkRight = <span class="literal">true</span>;</span><br><span class="line"> taskToFork = leftTask;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 添加fork任务到任务队列中</span></span><br><span class="line"> taskToFork.fork();</span><br><span class="line"> <span class="comment">// 其实这里是更新剩余待分割的Spliterator中的所有元素数量到sizeEstimate</span></span><br><span class="line"> sizeEstimate = rightSplit.estimateSize();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 置空spliterator实例并且传播任务完成状态,等待所有任务执行完成</span></span><br><span class="line"> task.spliterator = <span class="literal">null</span>;</span><br><span class="line"> task.propagateCompletion();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上面的源码分析看起来可能比较难理解,这里举个简单的例子:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> List<Integer> list = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> list.add(<span class="number">1</span>);</span><br><span class="line"> list.add(<span class="number">2</span>);</span><br><span class="line"> list.add(<span class="number">3</span>);</span><br><span class="line"> list.add(<span class="number">4</span>);</span><br><span class="line"> list.stream().parallel().forEach(System.out::println);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这段代码中最终转换成<code>ForEachTask</code>中评估后得到的<code>targetSize = sizeThreshold == 1</code>,当前调用线程会参与计算,会执行<code>3</code>次<code>fork</code>,也就是一共有<code>4</code>个处理流程实例(也就是原始的<code>Spliterator</code>实例最终会分割出<code>3</code>个全新的<code>Spliterator</code>实例,加上自身一个<code>4</code>个<code>Spliterator</code>实例),每个处理流程实例只处理<code>1</code>个元素,对应的流程图如下:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-16.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-16.png" alt="img"></a></p><p>最终的计算结果是调用<code>CountedCompleter.invoke()</code>方法获取的,此方法会阻塞直到所有子任务处理完成,当然<code>forEach</code>终结操作不需要返回值,所以没有实现<code>getRawResult()</code>方法,这里只是为了阻塞到所有任务执行完毕才解除调用线程的阻塞状态。</p><h2 id="状态操作与短路操作"><a href="#状态操作与短路操作" class="headerlink" title="状态操作与短路操作"></a>状态操作与短路操作</h2><p><code>Stream</code>中按照<strong>中间操作</strong>是否有状态可以把这些操作分为<strong>无状态操作</strong>和<strong>有状态操作</strong>。<code>Stream</code>中按照<strong>终结操作</strong>是否支持短路特性可以把这些操作分为<strong>非短路操作</strong>和<strong>短路操作</strong>。理解如下:</p><ul><li>无状态操作:当前操作节点处理元素完成后,在满足前提条件下直接把结果传递到下一个操作节点,也就是操作内部不存在状态也不需要保存状态,例如<code>filter</code>、<code>map</code>等操作</li><li>有状态操作:处理元素的时候,依赖于节点的内部状态对元素进行累积,当处理一个新的元素的时候,其实可以感知到所有处理过的元素的历史状态,这个”状态”其实更像是缓冲区的概念,例如<code>sort</code>、<code>limit</code>等操作,以<code>sort</code>操作为例,一般是把所有待处理的元素全部添加到一个容器如<code>ArrayList</code>,再进行所有元素的排序,然后再重新模拟<code>Spliterator</code>把元素推送到后一个节点</li><li>非短路(终结)操作:终结操作在处理元素时候不能基于短路条件提前中断处理并且返回,也就是必须处理所有的元素,如<code>forEach</code></li><li>短路(终结)操作:终结操作在处理元素时候允许基于短路条件提前中断处理并且返回,但是最终实现中是有可能遍历完所有的元素中,只是在处理方法中基于前置的短路条件跳过了实际的处理过程,如<code>anyMatch</code>(实际上<code>anyMatch</code>会遍历完所有的元素,不过在命中了短路条件下,元素回调<code>Sink.accept()</code>方法时候会基于<code>stop</code>短路标记跳过具体的处理流程)</li></ul><p>这里不展开源码进行分析,仅仅展示一个经常见到的<code>Stream</code>操作汇总表如下:</p><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-15.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202110/stream-source-15.png" alt="img"></a></p><p>这里还有两点要注意:</p><ul><li>从源码上看部分中间操作也是支持短路的,例如<code>slice</code>和<code>while</code>相关操作</li><li>从源码上看<code>find</code>相关终结操作中<code>findFirst</code>、<code>findAny</code>均支持和判断<code>StreamOpFlag.SHORT_CIRCUIT</code>,而<code>match</code>相关终结操作是通过内部的临时状态<code>stop</code>和<code>value</code>进行短路控制</li></ul><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>前前后后写了十多万字,其实也仅仅相对浅层次介绍了<code>Stream</code>的基本实现,笔者认为很多没分析到的中间操作实现和终结操作实现,特别是并发执行的终结操作实现是十分复杂的,多线程环境下需要进行一些想象和多处<code>DEBUG</code>定位执行位置和推演执行的过程。简单总结一下:</p><ul><li><code>JDK</code>中<code>Stream</code>的实现是精炼的高度工程化代码</li><li><code>Stream</code>的载体虽然是<code>AbstractPipeline</code>,管道结构,但是只用其形,实际求值操作之前会转化为一个多层包裹的<code>Sink</code>结构,也就是前文一直说的<code>Sink</code>链,从编程模式来看,应用的是<code>Reactor</code>编程模式</li><li><code>Stream</code>目前支持的固有求值执行结构一定是<code>Head(Source Spliterator) -> Op -> Op ... -> Terminal Op</code>的形式,这算是一个局限性,没有办法做到像<code>LINQ</code>那样可以灵活实现类似内存视图的功能</li><li><code>Stream</code>目前支持并发求值方案是针对<code>Source Spliterator</code>进行分割,封装<code>Terminal Op</code>和固定<code>Sink</code>链构造的<code>ForkJoinTask</code>进行并发计算,调用线程和<code>fork-join</code>线程池中的工作线程都可以参与求值过程,笔者认为这部分是<code>Stream</code>中除了那些标志集合位运算外最复杂的实现</li><li><code>Stream</code>实现的功能是一个突破,也有人说过此功能是一个”早产儿”,在此希望<code>JDK</code>能够在矛盾螺旋中前进和发展</li></ul>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="详细分析JDK中Stream的实现原理"><a href="#详细分析JDK中Stream的实现原理" class="headerlink" title="详细分析JDK中Stream的实现原理"></a><a href="https://www.cnblogs.</summary>
|
||
|
||
|
||
|
||
|
||
<category term="java" scheme="https://zyjblogs.cn/categories/java/"/>
|
||
|
||
|
||
<category term="java" scheme="https://zyjblogs.cn/tags/java/"/>
|
||
|
||
<category term="stream" scheme="https://zyjblogs.cn/tags/stream/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>HashMap简介</title>
|
||
<link href="https://zyjblogs.cn/posts/dbb6295a.html"/>
|
||
<id>https://zyjblogs.cn/posts/dbb6295a.html</id>
|
||
<published>2024-03-19T06:22:48.861Z</published>
|
||
<updated>2024-03-19T06:33:26.327Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="HashMap简介"><a href="#HashMap简介" class="headerlink" title="HashMap简介"></a>HashMap简介</h1><p><img src="https://cdn.nlark.com/yuque/0/2020/png/2758456/1603541776468-9e70e602-d3a7-4469-a2e9-ee3561d34e00.png" alt="img"></p><h4 id="HashMap核心数据结构"><a href="#HashMap核心数据结构" class="headerlink" title="HashMap核心数据结构"></a>HashMap核心数据结构</h4><p>Hash表 = 数组 + 线性链表 + 红黑树</p><h4 id="为什么初始容量是2的指数幂"><a href="#为什么初始容量是2的指数幂" class="headerlink" title="为什么初始容量是2的指数幂?"></a>为什么初始容量是2的指数幂?</h4><p>如果创建HashMap时指定的大小不是2的指数就会报错吗?</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Map map = new HashMap<>(13);</span><br></pre></td></tr></table></figure><p>这行代码在编译的时候也不会报错,那为什么说初始容量是2的指数呢?</p><p>看一下HashMap的构造器</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">public HashMap(int initialCapacity, float loadFactor) {</span><br><span class="line">if (initialCapacity < 0)</span><br><span class="line">throw new IllegalArgumentException("Illegal initial capacity: " +</span><br><span class="line"> initialCapacity);</span><br><span class="line">if (initialCapacity > MAXIMUM_CAPACITY)</span><br><span class="line">initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line">if (loadFactor <= 0 || Float.isNaN(loadFactor))</span><br><span class="line">throw new IllegalArgumentException("Illegal load factor: " +</span><br><span class="line"> loadFactor);</span><br><span class="line">this.loadFactor = loadFactor;</span><br><span class="line">// 调用了tableSizeFor()方法</span><br><span class="line">this.threshold = tableSizeFor(initialCapacity);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">static final int tableSizeFor(int cap) {</span><br><span class="line">int n = cap - 1;</span><br><span class="line">n |= n >>> 1;</span><br><span class="line">n |= n >>> 2;</span><br><span class="line">n |= n >>> 4;</span><br><span class="line">n |= n >>> 8;</span><br><span class="line">n |= n >>> 16;</span><br><span class="line">return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>tableSizeFor写的奇奇怪怪的嘞, 这一长串是干嘛呢?</p><p>因为initCapacity必定大于等于0, 所以在他的二进制数中,首位必然是1.而且initCapacity最大值又小于32位.<br>因此,先将他右移一位取或,结果的前两位必然也是1,依次将后续的所有位数全部变成1, 得到的就是他所在的,距离值最近的2^n-1. 最后将该值 +1 就得到了比initCapacity大,且距离最近的2的指数值.</p><p>那为什么呢? 为什么一定要将容量设置为2的指数呢?初始容量给多少就是多少不行吗?<br>先提一些题外话, 哈希值可以很大也可以很小,如何将这个很大范围的哈希值塞进很小的一个数组里呢?<br>很容易想到的方法就是对这个值取余,这样不管多大的数值,散布在这个数组各个索引的概率也差不多相等.<br>在HashMap中,计算索引的方法是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// n = table.length</span><br><span class="line">// hash = hash(key)</span><br><span class="line">i = (n - 1) & hash</span><br><span class="line"></span><br><span class="line">static final int hash(Object key) {</span><br><span class="line">int h;</span><br><span class="line">return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为n都被置为2的指数,n = 0000 0000 0100 0000, n - 1 = 0000 0000 0011 1111,这样做且运算时,hash值前面的位数和0做&计算都是0,直接取hash后几位就可以了,而且这个结果的范围就在0 ~ n-1之间.</p><h4 id="加载因子为什么是0-75"><a href="#加载因子为什么是0-75" class="headerlink" title="加载因子为什么是0.75?"></a>加载因子为什么是0.75?</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line"> * <p>As a general rule, the default load factor (.75) offers a good</span><br><span class="line"> * tradeoff between time and space costs. Higher values decrease the</span><br><span class="line"> * space overhead but increase the lookup cost (reflected in most of</span><br><span class="line"> * the operations of the <tt>HashMap</tt> class, including</span><br><span class="line"> * <tt>get</tt> and <tt>put</tt>). The expected number of entries in</span><br><span class="line"> * the map and its load factor should be taken into account when</span><br><span class="line"> * setting its initial capacity, so as to minimize the number of</span><br><span class="line"> * rehash operations. If the initial capacity is greater than the</span><br><span class="line"> * maximum number of entries divided by the load factor, no rehash</span><br><span class="line"> * operations will ever occur.</span><br><span class="line"> */</span><br></pre></td></tr></table></figure><p>从HashMap中摘下来的一段注释, 加载因子决定了当数组填充多少时,才开始扩容.<br>理论上来说数组的每个位置都是有均等的可能放入元素的,那是不是填个1,当所有的位置都占满了才去扩容呢?<br>理论是这样的,但是会有可能发生 有一个位置就是没有数据,其他格子下链的数据已经堆积起来了. 这样去get(key)的时候会花费更长的时间.<br>同样的道理,基于空间上考虑,在尽量数组装的差不多的时候才去考虑扩容.毕竟每个位置放入元素的机会都是均等的.<br>因此,<code>the default load factor (.75) offers a good tradeoff between time and space costs</code>.</p><h4 id="为什么链表长度为8的时候-会去转为红黑树"><a href="#为什么链表长度为8的时候-会去转为红黑树" class="headerlink" title="为什么链表长度为8的时候,会去转为红黑树?"></a>为什么链表长度为8的时候,会去转为红黑树?</h4><p>这里要引入一个泊松分布的概念<br><a href="http://www.ruanyifeng.com/blog/2015/06/poisson-distribution.html">泊松分布和指数分布:10分钟教程</a><br>在HashMap的源码中也有相应的概率显示</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">* 0: 0.60653066</span><br><span class="line">* 1: 0.30326533</span><br><span class="line">* 2: 0.07581633</span><br><span class="line">* 3: 0.01263606</span><br><span class="line">* 4: 0.00157952</span><br><span class="line">* 5: 0.00015795</span><br><span class="line">* 6: 0.00001316</span><br><span class="line">* 7: 0.00000094</span><br><span class="line">* 8: 0.00000006</span><br><span class="line">* more: less than 1 in ten million</span><br></pre></td></tr></table></figure><p>可以看到,在链表长度为8的时候概率已经非常小了, 已经小于千万分之一.所以即使在长度超过8的情况下链表会转成红黑树,树的出现依然很少见.<br><code>Because TreeNodes are about twice the size of regular nodes.</code></p><h4 id="JAVA7的HashMap扩容出现的问题"><a href="#JAVA7的HashMap扩容出现的问题" class="headerlink" title="JAVA7的HashMap扩容出现的问题"></a>JAVA7的HashMap扩容出现的问题</h4><p>JAVA7中扩容的代码主要是下面这段. 当然,中间还有一段重新计算索引的被我删掉, 考虑的是扩容后链表内存放的数据重新计算数组下标依然一样的情况.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">for (Entry<K, V> e : table){</span><br><span class="line">while (null != e){</span><br><span class="line">Entry<K, V> next = e.next;</span><br><span class="line"></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></pre></td></tr></table></figure><p>如果进行扩容,扩容后的结果<br><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603541805991-1fc4854e-2ea8-4f44-8ca7-fe22ada4b22f.jpeg" alt="img"></p><p>扩容前</p><p><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603541811313-451d4adb-4539-418c-acf4-e96925e1d1d8.jpeg" alt="img"></p><p>扩容后<br>可以看到,扩容后链表是顺序倒了过来.</p><p>如果是两个线程同时遇到扩容问题,<br>t1为线程1, e1,next1为t1中的e和next对象.<br>t2为线程2, e2,next2为t2中的e和next对象.<br>若t2在<code>Entry<K, V> next = e.next;</code>时挂起,由t1执行,t1执行结束后:</p><p><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603541846639-a0535b89-3f0d-45b9-aa76-0a2b2e7ecd8c.jpeg" alt="img"></p><p>t2挂起<br>根据我们上面看到的扩容后链表顺序返过来,</p><p><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603541854154-4b4ff4e0-92d6-42e9-b946-f5419c43f14f.jpeg" alt="img"></p><p>t1执行结束</p><p><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603541871903-7f48398a-0de5-497a-ae93-7e3cc25fd4ab.jpeg" alt="img"></p><p>t2第一次循环后</p><p><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603541885768-d4e1b75a-474e-441d-a539-3ba90f667f54.jpeg" alt="img"></p><p>t2第二次循环结束</p><p>第二次循环执行完毕, 在t1时,两次循环后就已经跳出循环. 但是在t2这里, e仍然非空,所以要继续执行.</p><p>第三次循环执行到<code>e.next = newTable[i];</code>时,出现了一个问题<br><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603541950001-8836bb13-2f70-441c-9dd2-60c1e7d289bf.jpeg" alt="img"></p><p>n1.next = n2; n2.next = n1;<br><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603541963251-7215981d-e39b-43a2-9e3a-a1c52ebd8a71.jpeg" alt="img"></p><p>第三次循环结束时<br>第三次循环结束时,e == null 结束循环.<br>但是t2线程中的链表已经形成了一个环状.</p><h4 id="JAVA8的HashMap扩容"><a href="#JAVA8的HashMap扩容" class="headerlink" title="JAVA8的HashMap扩容"></a>JAVA8的HashMap扩容</h4><p>JAVA8中,HashMap的扩容不再使用重新计算数组下标,挨个移动. 这样就避免了next的指来指去导致链表形成环状的情况.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">Node<K,V> loHead = null, loTail = null;</span><br><span class="line">Node<K,V> hiHead = null, hiTail = null;</span><br><span class="line">Node<K,V> next;</span><br><span class="line">do {</span><br><span class="line">next = e.next;</span><br><span class="line">if ((e.hash & oldCap) == 0) {</span><br><span class="line">if (loTail == null)</span><br><span class="line">loHead = e;</span><br><span class="line">else</span><br><span class="line">loTail.next = e;</span><br><span class="line">loTail = e;</span><br><span class="line">}</span><br><span class="line">else {</span><br><span class="line">if (hiTail == null)</span><br><span class="line">hiHead = e;</span><br><span class="line">else</span><br><span class="line">hiTail.next = e;</span><br><span class="line">hiTail = e;</span><br><span class="line">}</span><br><span class="line">} while ((e = next) != null);</span><br><span class="line">if (loTail != null) {</span><br><span class="line">loTail.next = null;</span><br><span class="line">newTab[j] = loHead;</span><br><span class="line">}</span><br><span class="line">if (hiTail != null) {</span><br><span class="line">hiTail.next = null;</span><br><span class="line">newTab[j + oldCap] = hiHead;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在JAVA8中, 使用的是四个指针,高低位指针,将链表直接分成两段. 低位将低位链表放入新数组的原索引位置, 高位将高位链表放入扩容出的新空间中,相应位置.<br>这样处理避免了挨个元素移动,并且将链表的长度减少.<br><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603541986020-f556fdfb-fc16-40b9-9519-5984df2ef318.jpeg" alt="img"><br>假设有这么一个数组, n1,n3计算结果为低位, n2,n4计算结果为高位.<br><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603542033717-fbad65c2-b3aa-44dd-b99a-b41fa61b2802.jpeg" alt="img"><br>将loHead放到原来的3位置,hiHead放入3+16位置<br><img src="https://cdn.nlark.com/yuque/0/2020/jpeg/2758456/1603542041454-05071865-6d69-45f8-837a-11ed0999f688.jpeg" alt="img"><br>这样就避免了环状的情况,因为hash值和容量做计算的时候,结果始终是一样的.</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="HashMap简介"><a href="#HashMap简介" class="headerlink" title="HashMap简介"></a>HashMap简介</h1><p><img src="https://cdn.nlark.com/yuque/0/20</summary>
|
||
|
||
|
||
|
||
|
||
<category term="数据结构" scheme="https://zyjblogs.cn/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
|
||
|
||
|
||
<category term="hashmap" scheme="https://zyjblogs.cn/tags/hashmap/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>HashMap的最大容量是多少</title>
|
||
<link href="https://zyjblogs.cn/posts/87ddd1f4.html"/>
|
||
<id>https://zyjblogs.cn/posts/87ddd1f4.html</id>
|
||
<published>2024-03-19T06:21:58.654Z</published>
|
||
<updated>2024-03-19T06:33:26.329Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h3 id="HashMap的最大容量是多少"><a href="#HashMap的最大容量是多少" class="headerlink" title="HashMap的最大容量是多少."></a>HashMap的最大容量是多少.</h3><p>首先, HashMap底层是数组+链表, 所以HashMap的容量约等于 <code>数组长度 * 链表长度</code>.<br>因为链表长度不固定,甚至可能链表会是树结构, 所以我们主要讨论数组长度.</p><p>那么, 数组的最大长度是多长呢? 仔细想想, 好像这么多年也没去看过数组的源码(笑).</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">一是规范隐含的限制。Java数组的length必须是非负的int,</span><br><span class="line">所以它的理论最大值就是java.lang.Integer.MAX_VALUE = 2^31-1 = 2147483647。</span><br><span class="line"></span><br><span class="line">二是具体的实现带来的限制。</span><br><span class="line">这会使得实际的JVM不一定能支持上面说的理论上的最大length。</span><br><span class="line">例如说如果有JVM使用uint32_t来记录对象大小的话,那可以允许的最大的数组长度(按元素的个数计算)就会是:</span><br><span class="line">(uint32_t的最大值 - 数组对象的对象头大小) / 数组元素大小</span><br></pre></td></tr></table></figure><p>嗯..数组长度理论上可以达到 2^31-1 这么长, 那么HashMap的最大长度也是这么了?</p><p>不, 在HashMap中规定HashMap底层数组的元素最大为 1<<30</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">static final int MAXIMUM_CAPACITY = 1 << 30;</span><br></pre></td></tr></table></figure><p>为啥呢? 理论上不是可以更长吗?</p><p>还记得我们以前提到过的HashMap会把容量定为输入容量的最近的2次幂.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">static final int tableSizeFor(int cap) {</span><br><span class="line"> int n = cap - 1;</span><br><span class="line"> n |= n >>> 1;</span><br><span class="line"> n |= n >>> 2;</span><br><span class="line"> n |= n >>> 4;</span><br><span class="line"> n |= n >>> 8;</span><br><span class="line"> n |= n >>> 16;</span><br><span class="line"> return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这串是干嘛呢?</p><p>现在想象一个场景, <code>new HashMap(9);</code><br>我想初始化一个长度为9的HashMap</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">cap: </span><br><span class="line">00000000 00000000 00000000 00001001</span><br><span class="line"></span><br><span class="line">int n = cap - 1:</span><br><span class="line">00000000 00000000 00000000 00001000</span><br><span class="line"></span><br><span class="line">n >>> 1:</span><br><span class="line">00000000 00000000 00000000 00000100</span><br><span class="line"></span><br><span class="line">n |= n >>> 1:</span><br><span class="line">00000000 00000000 00000000 00001100</span><br><span class="line"></span><br><span class="line">n >>> 2:</span><br><span class="line">00000000 00000000 00000000 00000011</span><br><span class="line"></span><br><span class="line">n |= n >>> 2:</span><br><span class="line">00000000 00000000 00000000 00001111</span><br><span class="line"></span><br><span class="line">n >>> 4:</span><br><span class="line">00000000 00000000 00000000 00001111</span><br><span class="line"></span><br><span class="line">n |= n >>> 4:</span><br><span class="line">00000000 00000000 00000000 00001111</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">n >>> 8:</span><br><span class="line">00000000 00000000 00000000 00001111</span><br><span class="line"></span><br><span class="line">n |= n >>> 8:</span><br><span class="line">00000000 00000000 00000000 00001111</span><br><span class="line"></span><br><span class="line">n >>> 16:</span><br><span class="line">00000000 00000000 00000000 00001111</span><br><span class="line"></span><br><span class="line">n |= n >>> 16:</span><br><span class="line">00000000 00000000 00000000 00001111</span><br></pre></td></tr></table></figure><p>这边计算了什么呢</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">00000000 00000000 00000000 00001111 = 15</span><br></pre></td></tr></table></figure><p>也就是将原本最高的一位后面全部变成1<br>也即, 变成了 <code>2^n -1</code><br>这样只要最后结果加1, 就会变成离他最近的2次幂.</p><p>那这些有什么用呢?</p><p><code>00000000 00000000 00000000 00000001</code> 左边不是31个位置吗? 为什么最大容量不是 1 << 31 ?</p><p>如果左移31, 就会变成<code>10000000 00000000 00000000 00000000</code>,<br>而最高位, 即最左边的位是符号位, 1为负数.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 运行这条</span><br><span class="line">System.out.println(0b10000000_00000000_00000000_00000000);</span><br><span class="line"></span><br><span class="line">// 输出</span><br><span class="line">-2147483648</span><br></pre></td></tr></table></figure><p>数组长度总不能是负数吧. 所以HashMap的数组长度最长是 1<<30</p><hr><p>尝试了一下添加1<<30个数进HashMap</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">public static void main(String[] args) {</span><br><span class="line"> int times = 1<<30;</span><br><span class="line"> Map<Integer, Integer> map = new HashMap<>();</span><br><span class="line"> for (int i = 0; i < times; i++) {</span><br><span class="line"> map.put(i, i);</span><br><span class="line"> System.out.println(i);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到, 我没有设置HashMap初始大小, 因此默认大小是16, 因为我们知道, HashMap在一定条件下会扩容, 扩容导致的问题就是数据迁移.</p><p>所以在运行到 1486699 的时候第一次出现明显卡顿,时间很短 大概一秒左右, 再往后的输出停顿时间越来越久.</p><p>因此小伙伴们如果预先知道要装多少数据, 或者大概数据, 不妨精心计算一下HashMap的初始大小.我认为 总数据量 / (3|4|5|6) 都可以.</p><p>因为按照同一节点下链表的数据多少规律, 同一个节点下挂载多个数据的概率是逐渐减少的.(而且没有哪个map会装这么多数据吧</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">0: 0.60653066</span><br><span class="line">1: 0.30326533</span><br><span class="line">2: 0.07581633</span><br><span class="line">3: 0.01263606</span><br><span class="line">4: 0.00157952</span><br><span class="line">5: 0.00015795</span><br><span class="line">6: 0.00001316</span><br><span class="line">7: 0.00000094</span><br><span class="line">8: 0.00000006</span><br></pre></td></tr></table></figure><p>在 23739181 的时候就OOM了, 或许下次把堆内存调大点再试试(逃</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h3 id="HashMap的最大容量是多少"><a href="#HashMap的最大容量是多少" class="headerlink" title="HashMap的最大容量是多少."></a>HashMap的最大容量是多少.</h3><p>首先, HashMap底层是数组</summary>
|
||
|
||
|
||
|
||
|
||
<category term="数据结构" scheme="https://zyjblogs.cn/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
|
||
|
||
|
||
<category term="hashmap" scheme="https://zyjblogs.cn/tags/hashmap/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>synchronized 实现原理</title>
|
||
<link href="https://zyjblogs.cn/posts/f11fd659.html"/>
|
||
<id>https://zyjblogs.cn/posts/f11fd659.html</id>
|
||
<published>2024-03-19T06:15:38.582Z</published>
|
||
<updated>2024-03-19T06:33:26.333Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h2 id="synchronized-实现原理"><a href="#synchronized-实现原理" class="headerlink" title="synchronized 实现原理"></a>synchronized 实现原理</h2><p>2020-03-24</p><h1 id="synchronized-实现原理-1"><a href="#synchronized-实现原理-1" class="headerlink" title="synchronized 实现原理"></a>synchronized 实现原理</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>众所周知 <code>synchronized</code> 锁在 <code>Java</code> 中经常使用它的源码是 <code>C++</code> 实现的,它的实现原理是怎样的呢?本文以 <code>OpenJDK 8</code> 为例探究以下内容。</p><ul><li>synchronized 是如何工作的</li><li>synchronized 锁升级过程</li><li>重量级锁的队列之间协作过程和策略</li></ul><h2 id="对象头"><a href="#对象头" class="headerlink" title="对象头"></a>对象头</h2><p>对象头的内容非常多这里我们只做简单介绍以引出后文。在 JVM 中对象布局分为三块区域:</p><ul><li>对象头</li><li>实例数据</li><li>对齐填充</li></ul><p><img src="https://xiaomi-info.github.io/2020/03/24/synchronized/object.png" alt="img"><br>当线程访问同步块时首先需要获得锁并把相关信息存储在对象头中。所以 <code>wait</code>、<code>notify</code>、<code>notifyAll</code> 这些方法为什么被设计在 <code>Object</code> 中或许你已经找到答案了。</p><p>Hotspot 有两种对象头:</p><ul><li>数组类型,使用 <code>arrayOopDesc</code> 来描述对象头</li><li>其它,使用 <code>instanceOopDesc</code> 来描述对象头</li></ul><p>对象头由两部分组成</p><ul><li>Mark Word:存储自身的运行时数据,例如 HashCode、GC 年龄、锁相关信息等内容。</li><li>Klass Pointer:类型指针指向它的类元数据的指针。</li></ul><p>64 位虚拟机 Mark Word 是 64bit 其结构如下:</p><p><img src="https://xiaomi-info.github.io/2020/03/24/synchronized/sync_1.png" alt="img"></p><p>在 JDK 6 中虚拟机团队对锁进行了重要改进,优化了其性能引入了 <code>偏向锁</code>、<code>轻量级锁</code>、<code>适应性自旋</code>、<code>锁消除</code>、<code>锁粗化</code>等实现,其中 <code>锁消除</code>和<code>锁粗化</code>本文不做详细讨论其余内容我们将对其进行逐一探究。</p><p>总体上来说锁状态升级流程如下:</p><p><img src="https://xiaomi-info.github.io/2020/03/24/synchronized/lock.png" alt="img"></p><h2 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a>偏向锁</h2><h3 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h3><p>当线程访问同步块并获取锁时处理流程如下:</p><ol><li>检查 <code>mark word</code> 的<code>线程 id</code> 。</li><li>如果为空则设置 CAS 替换当前线程 id。如果替换成功则获取锁成功,如果失败则撤销偏向锁。</li><li>如果不为空则检查 <code>线程 id</code>为是否为本线程。如果是则获取锁成功,如果失败则撤销偏向锁。</li></ol><p>持有偏向锁的线程以后每次进入这个锁相关的同步块时,只需比对一下 mark word 的线程 id 是否为本线程,如果是则获取锁成功。</p><p>如果发生线程竞争发生 2、3 步失败的情况则需要撤销偏向锁。</p><h3 id="偏向锁的撤销"><a href="#偏向锁的撤销" class="headerlink" title="偏向锁的撤销"></a>偏向锁的撤销</h3><ol><li>偏向锁的撤销动作必须等待全局安全点</li><li>暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态</li><li>撤销偏向锁恢复到无锁(标志位为 01)或轻量级锁(标志位为 00)的状态</li></ol><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><p>只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。</p><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><p>如果存在竞争会带来额外的锁撤销操作。</p><h2 id="轻量级锁"><a href="#轻量级锁" class="headerlink" title="轻量级锁"></a>轻量级锁</h2><h3 id="加锁"><a href="#加锁" class="headerlink" title="加锁"></a>加锁</h3><p>多个线程竞争偏向锁导致偏向锁升级为轻量级锁</p><ol><li>JVM 在当前线程的栈帧中创建 Lock Reocrd,并将对象头中的 Mark Word 复制到 Lock Reocrd 中。(Displaced Mark Word)</li><li>线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向 Lock Reocrd 的指针。如果成功则获得锁,如果失败则先检查对象的 Mark Word 是否指向当前线程的栈帧如果是则说明已经获取锁,否则说明其它线程竞争锁则膨胀为重量级锁。</li></ol><h3 id="解锁"><a href="#解锁" class="headerlink" title="解锁"></a>解锁</h3><ol><li>使用 CAS 操作将 Mark Word 还原</li><li>如果第 1 步执行成功则释放完成</li><li>如果第 1 步执行失败则膨胀为重量级锁。</li></ol><h3 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h3><p>其性能提升的依据是对于绝大部分的锁在整个生命周期内都是不会存在竞争。在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗。</p><h3 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h3><p>在有多线程竞争的情况下轻量级锁增加了额外开销。</p><h2 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a>自旋锁</h2><p>自旋是一种获取锁的机制并不是一个锁状态。在膨胀为重量级锁的过程中或重入时会多次尝试自旋获取锁以避免线程唤醒的开销,但是它会占用 CPU 的时间因此如果同步代码块执行时间很短自旋等待的效果就很好,反之则浪费了 CPU 资源。默认情况下自旋次数是 10 次用户可以使用参数 <code>-XX : PreBlockSpin</code> 来更改。那么如何优化来避免此情况发生呢?我们来看适应性自旋。</p><h3 id="适应性自旋锁"><a href="#适应性自旋锁" class="headerlink" title="适应性自旋锁"></a>适应性自旋锁</h3><p>JDK 6 引入了自适应自旋锁,意味着自旋的次数不在固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果对于某个锁很少自旋成功那么以后有可能省略掉自旋过程以避免资源浪费。有了自适应自旋随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虛拟机就会变得越来越“聪明”了。</p><h3 id="优点-2"><a href="#优点-2" class="headerlink" title="优点"></a>优点</h3><p>竞争的线程不会阻塞挂起,提高了程序响应速度。避免重量级锁引起的性能消耗。</p><h3 id="缺点-2"><a href="#缺点-2" class="headerlink" title="缺点"></a>缺点</h3><p>如果线程始终无法获取锁,自旋消耗 CPU 最终会膨胀为重量级锁。</p><h2 id="重量级锁"><a href="#重量级锁" class="headerlink" title="重量级锁"></a>重量级锁</h2><p>在重量级锁中没有竞争到锁的对象会 park 被挂起,退出同步块时 unpark 唤醒后续线程。唤醒操作涉及到操作系统调度会有额外的开销。</p><p><code>ObjectMonitor</code> 中包含一个同步队列(由 <code>_cxq</code> 和 <code>_EntryList</code> 组成)一个等待队列( <code>_WaitSet</code> )。</p><ul><li>被<code>notify</code>或 <code>notifyAll</code> 唤醒时根据 <code>policy</code> 策略选择加入的队列(policy 默认为 0)</li><li>退出同步块时根据 <code>QMode</code> 策略来唤醒下一个线程(QMode 默认为 0)</li></ul><p>这里稍微提及一下<strong>管程</strong>这个概念。synchronized 关键字及 <code>wait</code>、<code>notify</code>、<code>notifyAll</code> 这三个方法都是管程的组成部分。可以说管程就是一把解决并发问题的万能钥匙。有两大核心问题管程都是能够解决的:</p><ul><li><strong>互斥</strong>:即同一时刻只允许一个线程访问共享资源;</li><li><strong>同步</strong>:即线程之间如何通信、协作。</li></ul><p><code>synchronized</code> 的 <code>monitor</code>锁机制和 JDK 并发包中的 <code>AQS</code> 是很相似的,只不过 <code>AQS</code> 中是一个同步队列多个等待队列。熟悉 <code>AQS</code> 的同学可以拿来做个对比。</p><h3 id="队列协作流程图"><a href="#队列协作流程图" class="headerlink" title="队列协作流程图"></a>队列协作流程图</h3><p><img src="https://xiaomi-info.github.io/2020/03/24/synchronized/sync_2.png" alt="img"></p><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>在 HotSpot 中 monitor 是由 ObjectMonitor 实现的。其源码是用 c++来实现的源文件是 ObjectMonitor.hpp 主要数据结构如下所示:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">ObjectMonitor() {</span><br><span class="line"> _header = NULL;</span><br><span class="line"> _count = <span class="number">0</span>;</span><br><span class="line"> _waiters = <span class="number">0</span>, <span class="comment">// 等待中的线程数</span></span><br><span class="line"> _recursions = <span class="number">0</span>; <span class="comment">// 线程重入次数</span></span><br><span class="line"> _object = NULL; <span class="comment">// 存储该 monitor 的对象</span></span><br><span class="line"> _owner = NULL; <span class="comment">// 指向拥有该 monitor 的线程</span></span><br><span class="line"> _WaitSet = NULL; <span class="comment">// 等待线程 双向循环链表_WaitSet 指向第一个节点</span></span><br><span class="line"> _WaitSetLock = <span class="number">0</span> ;</span><br><span class="line"> _Responsible = NULL ;</span><br><span class="line"> _succ = NULL ;</span><br><span class="line"> _cxq = NULL ; <span class="comment">// 多线程竞争锁时的单向链表</span></span><br><span class="line"> FreeNext = NULL ;</span><br><span class="line"> _EntryList = NULL ; <span class="comment">// _owner 从该双向循环链表中唤醒线程,</span></span><br><span class="line"> _SpinFreq = <span class="number">0</span> ;</span><br><span class="line"> _SpinClock = <span class="number">0</span> ;</span><br><span class="line"> OwnerIsThread = <span class="number">0</span> ;</span><br><span class="line"> _previous_owner_tid = <span class="number">0</span>; <span class="comment">// 前一个拥有此监视器的线程 ID</span></span><br><span class="line"> }</span><br></pre></td></tr></table></figure><blockquote><ol><li>_owner:初始时为 NULL。当有线程占有该 monitor 时 owner 标记为该线程的 ID。当线程释放 monitor 时 owner 恢复为 NULL。owner 是一个临界资源 JVM 是通过 CAS 操作来保证其线程安全的。</li><li>_cxq:竞争队列所有请求锁的线程首先会被放在这个队列中(单向)。_cxq 是一个临界资源 JVM 通过 CAS 原子指令来修改_cxq 队列。<br>每当有新来的节点入队,它的 next 指针总是指向之前队列的头节点,而_cxq 指针会指向该新入队的节点,所以是后来居上。</li><li>_EntryList: _cxq 队列中有资格成为候选资源的线程会被移动到该队列中。</li><li>_WaitSet: 等待队列因为调用 wait 方法而被阻塞的线程会被放在该队列中。</li></ol></blockquote><h3 id="monitor-竞争过程"><a href="#monitor-竞争过程" class="headerlink" title="monitor 竞争过程"></a>monitor 竞争过程</h3><blockquote><ol><li>通过 CAS 尝试把 monitor 的 owner 字段设置为当前线程。</li><li>如果设置之前的 owner 指向当前线程,说明当前线程再次进入 monitor,即重入锁执行 recursions ++ , 记录重入的次数。</li><li>如果当前线程是第一次进入该 monitor, 设置 recursions 为 1,_owner 为当前线程,该线程成功获得锁并返回。</li><li>如果获取锁失败,则等待锁的释放。</li></ol></blockquote><p>执行 <code>monitorenter</code> 指令时 调用以下代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">IRT_ENTRY_NO_ASYNC(<span class="keyword">void</span>, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))</span><br><span class="line">#ifdef ASSERT</span><br><span class="line"> thread->last_frame().interpreter_frame_verify_monitor(elem);</span><br><span class="line">#endif</span><br><span class="line"> <span class="title function_">if</span> <span class="params">(PrintBiasedLockingStatistics)</span> {</span><br><span class="line"> Atomic::inc(BiasedLocking::slow_path_entry_count_addr());</span><br><span class="line"> }</span><br><span class="line"> Handle <span class="title function_">h_obj</span><span class="params">(thread, elem->obj()</span>);</span><br><span class="line"> <span class="keyword">assert</span>(Universe::heap()->is_in_reserved_or_null(h_obj()),<span class="string">"must be NULL or an object"</span>);</span><br><span class="line"><span class="comment">// 是否使用偏向锁 JVM 启动时设置的偏向锁-XX:-UseBiasedLocking=false/true</span></span><br><span class="line"> <span class="keyword">if</span> (UseBiasedLocking) {</span><br><span class="line"> <span class="comment">// Retry fast entry if bias is revoked to avoid unnecessary inflation</span></span><br><span class="line"> ObjectSynchronizer::fast_enter(h_obj, elem->lock(), <span class="literal">true</span>, CHECK);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 轻量级锁</span></span><br><span class="line"> ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">assert</span>(Universe::heap()->is_in_reserved_or_null(elem->obj()),</span><br><span class="line"> <span class="string">"must be NULL or an object"</span>);</span><br><span class="line">#ifdef ASSERT</span><br><span class="line"> thread->last_frame().interpreter_frame_verify_monitor(elem);</span><br><span class="line">#endif</span><br><span class="line">IRT_END</span><br></pre></td></tr></table></figure><blockquote><p><code>slow_enter</code> 方法主要是轻量级锁的一些操作,如果操作失败则会膨胀为重量级锁,过程前面已经描述比较清楚此处不在赘述。<code>enter</code> 方法则为重量级锁的入口源码如下</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> ATTR ObjectMonitor::enter(TRAPS) {</span><br><span class="line"> Thread * <span class="type">const</span> <span class="variable">Self</span> <span class="operator">=</span> THREAD ;</span><br><span class="line"> <span class="keyword">void</span> * cur ;</span><br><span class="line"> <span class="comment">// 省略部分代码</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 通过 CAS 操作尝试把 monitor 的_owner 字段设置为当前线程</span></span><br><span class="line"> cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;</span><br><span class="line"> <span class="keyword">if</span> (cur == NULL) {</span><br><span class="line"> <span class="keyword">assert</span> (_recursions == <span class="number">0</span> , <span class="string">"invariant"</span>) ;</span><br><span class="line"> <span class="keyword">assert</span> (_owner == Self, <span class="string">"invariant"</span>) ;</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="comment">// 线程重入,recursions++</span></span><br><span class="line"> <span class="keyword">if</span> (cur == Self) {</span><br><span class="line"> _recursions ++ ;</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="comment">// 如果当前线程是第一次进入该 monitor, 设置_recursions 为 1,_owner 为当前线程</span></span><br><span class="line"> <span class="keyword">if</span> (Self->is_lock_owned ((address)cur)) {</span><br><span class="line"> <span class="keyword">assert</span> (_recursions == <span class="number">0</span>, <span class="string">"internal state error"</span>);</span><br><span class="line"> _recursions = <span class="number">1</span> ;</span><br><span class="line"> _owner = Self ;</span><br><span class="line"> OwnerIsThread = <span class="number">1</span> ;</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="keyword">for</span> (;;) {</span><br><span class="line"> jt->set_suspend_equivalent();</span><br><span class="line"> <span class="comment">// 如果获取锁失败,则等待锁的释放;</span></span><br><span class="line"> EnterI (THREAD) ;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!ExitSuspendEquivalent(jt)) <span class="keyword">break</span> ;</span><br><span class="line"> _recursions = <span class="number">0</span> ;</span><br><span class="line"> _succ = NULL ;</span><br><span class="line"> exit (<span class="literal">false</span>, Self) ;</span><br><span class="line"></span><br><span class="line"> jt->java_suspend_self();</span><br><span class="line"> }</span><br><span class="line"> Self->set_current_pending_monitor(NULL);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="monitor-等待"><a href="#monitor-等待" class="headerlink" title="monitor 等待"></a>monitor 等待</h3><blockquote><ol><li>当前线程被封装成 ObjectWaiter 对象 node,状态设置成 ObjectWaiter::TS_CXQ。</li><li>for 循环通过 CAS 把 node 节点 push 到<code>_cxq</code>列表中,同一时刻可能有多个线程把自己的 node 节点 push 到<code>_cxq</code>列表中。</li><li>node 节点 push 到_cxq 列表之后,通过自旋尝试获取锁,如果还是没有获取到锁则通过 park 将当前线程挂起等待被唤醒。</li><li>当该线程被唤醒时会从挂起的点继续执行,通过 ObjectMonitor::TryLock 尝试获取锁。</li></ol></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 省略部分代码</span></span><br><span class="line"><span class="keyword">void</span> ATTR ObjectMonitor::EnterI (TRAPS) {</span><br><span class="line"> Thread * Self = THREAD ;</span><br><span class="line"> <span class="keyword">assert</span> (Self->is_Java_thread(), <span class="string">"invariant"</span>) ;</span><br><span class="line"> <span class="keyword">assert</span> (((JavaThread *) Self)->thread_state() == _thread_blocked , <span class="string">"invariant"</span>) ;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Try lock 尝试获取锁</span></span><br><span class="line"> <span class="keyword">if</span> (TryLock (Self) > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">assert</span> (_succ != Self , <span class="string">"invariant"</span>) ;</span><br><span class="line"> <span class="keyword">assert</span> (_owner == Self , <span class="string">"invariant"</span>) ;</span><br><span class="line"> <span class="keyword">assert</span> (_Responsible != Self , <span class="string">"invariant"</span>) ;</span><br><span class="line"> <span class="comment">// 如果获取成功则退出,避免 park unpark 系统调度的开销</span></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="comment">// 自旋获取锁</span></span><br><span class="line"> <span class="keyword">if</span> (TrySpin(Self) > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">assert</span> (_owner == Self, <span class="string">"invariant"</span>);</span><br><span class="line"> <span class="keyword">assert</span> (_succ != Self, <span class="string">"invariant"</span>);</span><br><span class="line"> <span class="keyword">assert</span> (_Responsible != Self, <span class="string">"invariant"</span>);</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="comment">// 当前线程被封装成 ObjectWaiter 对象 node, 状态设置成 ObjectWaiter::TS_CXQ</span></span><br><span class="line"> ObjectWaiter <span class="title function_">node</span><span class="params">(Self)</span> ;</span><br><span class="line"> Self->_ParkEvent->reset() ;</span><br><span class="line"> node._prev = (ObjectWaiter *) <span class="number">0xBAD</span> ;</span><br><span class="line"> node.TState = ObjectWaiter::TS_CXQ ;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 通过 CAS 把 node 节点 push 到_cxq 列表中</span></span><br><span class="line"> ObjectWaiter * nxt ;</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> node._next = nxt = _cxq ;</span><br><span class="line"> <span class="keyword">if</span> (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) <span class="keyword">break</span> ;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 再次 tryLock</span></span><br><span class="line"> <span class="keyword">if</span> (TryLock (Self) > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">assert</span> (_succ != Self , <span class="string">"invariant"</span>) ;</span><br><span class="line"> <span class="keyword">assert</span> (_owner == Self , <span class="string">"invariant"</span>) ;</span><br><span class="line"> <span class="keyword">assert</span> (_Responsible != Self , <span class="string">"invariant"</span>) ;</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><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> <span class="comment">// 本段代码的主要思想和 AQS 中相似可以类比来看</span></span><br><span class="line"> <span class="comment">// 再次尝试</span></span><br><span class="line"> <span class="keyword">if</span> (TryLock (Self) > <span class="number">0</span>) <span class="keyword">break</span> ;</span><br><span class="line"> <span class="keyword">assert</span> (_owner != Self, <span class="string">"invariant"</span>) ;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ((SyncFlags & <span class="number">2</span>) && _Responsible == NULL) {</span><br><span class="line"> Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 满足条件则 park self</span></span><br><span class="line"> <span class="keyword">if</span> (_Responsible == Self || (SyncFlags & <span class="number">1</span>)) {</span><br><span class="line"> TEVENT (Inflated enter - park TIMED) ;</span><br><span class="line"> Self->_ParkEvent->park ((jlong) RecheckInterval) ;</span><br><span class="line"> <span class="comment">// Increase the RecheckInterval, but clamp the value.</span></span><br><span class="line"> RecheckInterval *= <span class="number">8</span> ;</span><br><span class="line"> <span class="keyword">if</span> (RecheckInterval > <span class="number">1000</span>) RecheckInterval = <span class="number">1000</span> ;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> TEVENT (Inflated enter - park UNTIMED) ;</span><br><span class="line"> <span class="comment">// 通过 park 将当前线程挂起,等待被唤醒</span></span><br><span class="line"> Self->_ParkEvent->park() ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (TryLock(Self) > <span class="number">0</span>) <span class="keyword">break</span> ;</span><br><span class="line"> <span class="comment">// 再次尝试自旋</span></span><br><span class="line"> <span class="keyword">if</span> ((Knob_SpinAfterFutile & <span class="number">1</span>) && TrySpin(Self) > <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="monitor-释放"><a href="#monitor-释放" class="headerlink" title="monitor 释放"></a>monitor 释放</h3><blockquote><p>当某个持有锁的线程执行完同步代码块时,会释放锁并 <code>unpark</code> 后续线程(由于篇幅只保留重要代码)。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {</span><br><span class="line"> Thread * Self = THREAD ;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (_recursions != <span class="number">0</span>) {</span><br><span class="line"> _recursions--; <span class="comment">// this is simple recursive enter</span></span><br><span class="line"> TEVENT (Inflated exit - recursive) ;</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"> ObjectWaiter * w = NULL ;</span><br><span class="line"> <span class="type">int</span> <span class="variable">QMode</span> <span class="operator">=</span> Knob_QMode ;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 直接绕过 EntryList 队列,从 cxq 队列中获取线程用于竞争锁</span></span><br><span class="line"> <span class="keyword">if</span> (QMode == <span class="number">2</span> && _cxq != NULL) {</span><br><span class="line"> w = _cxq ;</span><br><span class="line"> <span class="keyword">assert</span> (w != NULL, <span class="string">"invariant"</span>) ;</span><br><span class="line"> <span class="keyword">assert</span> (w->TState == ObjectWaiter::TS_CXQ, <span class="string">"Invariant"</span>) ;</span><br><span class="line"> ExitEpilog (Self, w) ;</span><br><span class="line"> <span class="keyword">return</span> ;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// cxq 队列插入 EntryList 尾部</span></span><br><span class="line"> <span class="keyword">if</span> (QMode == <span class="number">3</span> && _cxq != NULL) {</span><br><span class="line"> w = _cxq ;</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> <span class="keyword">assert</span> (w != NULL, <span class="string">"Invariant"</span>) ;</span><br><span class="line"> ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;</span><br><span class="line"> <span class="keyword">if</span> (u == w) <span class="keyword">break</span> ;</span><br><span class="line"> w = u ;</span><br><span class="line"> }</span><br><span class="line"> ObjectWaiter * q = NULL ;</span><br><span class="line"> ObjectWaiter * p ;</span><br><span class="line"> <span class="keyword">for</span> (p = w ; p != NULL ; p = p->_next) {</span><br><span class="line"> guarantee (p->TState == ObjectWaiter::TS_CXQ, <span class="string">"Invariant"</span>) ;</span><br><span class="line"> p->TState = ObjectWaiter::TS_ENTER ;</span><br><span class="line"> p->_prev = q ;</span><br><span class="line"> q = p ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ObjectWaiter * Tail ;</span><br><span class="line"> <span class="keyword">for</span> (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;</span><br><span class="line"> <span class="keyword">if</span> (Tail == NULL) {</span><br><span class="line"> _EntryList = w ;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> Tail->_next = w ;</span><br><span class="line"> w->_prev = Tail ;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// cxq 队列插入到_EntryList 头部</span></span><br><span class="line"> <span class="keyword">if</span> (QMode == <span class="number">4</span> && _cxq != NULL) {</span><br><span class="line"> <span class="comment">// 把 cxq 队列放入 EntryList</span></span><br><span class="line"> <span class="comment">// 此策略确保最近运行的线程位于 EntryList 的头部</span></span><br><span class="line"> w = _cxq ;</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> <span class="keyword">assert</span> (w != NULL, <span class="string">"Invariant"</span>) ;</span><br><span class="line"> ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;</span><br><span class="line"> <span class="keyword">if</span> (u == w) <span class="keyword">break</span> ;</span><br><span class="line"> w = u ;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">assert</span> (w != NULL , <span class="string">"invariant"</span>) ;</span><br><span class="line"></span><br><span class="line"> ObjectWaiter * q = NULL ;</span><br><span class="line"> ObjectWaiter * p ;</span><br><span class="line"> <span class="keyword">for</span> (p = w ; p != NULL ; p = p->_next) {</span><br><span class="line"> guarantee (p->TState == ObjectWaiter::TS_CXQ, <span class="string">"Invariant"</span>) ;</span><br><span class="line"> p->TState = ObjectWaiter::TS_ENTER ;</span><br><span class="line"> p->_prev = q ;</span><br><span class="line"> q = p ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (_EntryList != NULL) {</span><br><span class="line"> q->_next = _EntryList ;</span><br><span class="line"> _EntryList->_prev = q ;</span><br><span class="line"> }</span><br><span class="line"> _EntryList = w ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> w = _EntryList ;</span><br><span class="line"> <span class="keyword">if</span> (w != NULL) {</span><br><span class="line"> <span class="keyword">assert</span> (w->TState == ObjectWaiter::TS_ENTER, <span class="string">"invariant"</span>) ;</span><br><span class="line"> ExitEpilog (Self, w) ;</span><br><span class="line"> <span class="keyword">return</span> ;</span><br><span class="line"> }</span><br><span class="line"> w = _cxq ;</span><br><span class="line"> <span class="keyword">if</span> (w == NULL) <span class="keyword">continue</span> ;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> <span class="keyword">assert</span> (w != NULL, <span class="string">"Invariant"</span>) ;</span><br><span class="line"> ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;</span><br><span class="line"> <span class="keyword">if</span> (u == w) <span class="keyword">break</span> ;</span><br><span class="line"> w = u ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (QMode == <span class="number">1</span>) {</span><br><span class="line"> <span class="comment">// QMode == 1 : 把 cxq 倾倒入 EntryList 逆序</span></span><br><span class="line"> ObjectWaiter * s = NULL ;</span><br><span class="line"> ObjectWaiter * t = w ;</span><br><span class="line"> ObjectWaiter * u = NULL ;</span><br><span class="line"> <span class="keyword">while</span> (t != NULL) {</span><br><span class="line"> guarantee (t->TState == ObjectWaiter::TS_CXQ, <span class="string">"invariant"</span>) ;</span><br><span class="line"> t->TState = ObjectWaiter::TS_ENTER ;</span><br><span class="line"> u = t->_next ;</span><br><span class="line"> t->_prev = u ;</span><br><span class="line"> t->_next = s ;</span><br><span class="line"> s = t;</span><br><span class="line"> t = u ;</span><br><span class="line"> }</span><br><span class="line"> _EntryList = s ;</span><br><span class="line"> <span class="keyword">assert</span> (s != NULL, <span class="string">"invariant"</span>) ;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// QMode == 0 or QMode == 2</span></span><br><span class="line"> _EntryList = w ;</span><br><span class="line"> ObjectWaiter * q = NULL ;</span><br><span class="line"> ObjectWaiter * p ;</span><br><span class="line"> <span class="comment">// 将单向链表构造成双向环形链表;</span></span><br><span class="line"> <span class="keyword">for</span> (p = w ; p != NULL ; p = p->_next) {</span><br><span class="line"> guarantee (p->TState == ObjectWaiter::TS_CXQ, <span class="string">"Invariant"</span>) ;</span><br><span class="line"> p->TState = ObjectWaiter::TS_ENTER ;</span><br><span class="line"> p->_prev = q ;</span><br><span class="line"> q = p ;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (_succ != NULL) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line"> w = _EntryList ;</span><br><span class="line"> <span class="keyword">if</span> (w != NULL) {</span><br><span class="line"> guarantee (w->TState == ObjectWaiter::TS_ENTER, <span class="string">"invariant"</span>) ;</span><br><span class="line"> ExitEpilog (Self, w) ;</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><br></pre></td></tr></table></figure><h3 id="notify-唤醒"><a href="#notify-唤醒" class="headerlink" title="notify 唤醒"></a>notify 唤醒</h3><blockquote><p>notify 或者 notifyAll 方法可以唤醒同一个锁监视器下调用 wait 挂起的线程,具体实现如下</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> ObjectMonitor::notify(TRAPS) {</span><br><span class="line"> CHECK_OWNER();</span><br><span class="line"> <span class="keyword">if</span> (_WaitSet == NULL) {</span><br><span class="line"> TEVENT (Empty - Notify);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> DTRACE_MONITOR_PROBE(notify, <span class="built_in">this</span>, object(), THREAD);</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> <span class="variable">Policy</span> <span class="operator">=</span> Knob_MoveNotifyee;</span><br><span class="line"></span><br><span class="line"> Thread::SpinAcquire(&_WaitSetLock, <span class="string">"WaitSet - notify"</span>);</span><br><span class="line"> ObjectWaiter *iterator = DequeueWaiter();</span><br><span class="line"> <span class="keyword">if</span> (iterator != NULL) {</span><br><span class="line"> <span class="comment">// 省略一些代码</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 头插 EntryList</span></span><br><span class="line"> <span class="keyword">if</span> (Policy == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (List == NULL) {</span><br><span class="line"> iterator->_next = iterator->_prev = NULL;</span><br><span class="line"> _EntryList = iterator;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> List->_prev = iterator;</span><br><span class="line"> iterator->_next = List;</span><br><span class="line"> iterator->_prev = NULL;</span><br><span class="line"> _EntryList = iterator;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (Policy == <span class="number">1</span>) { <span class="comment">// 尾插 EntryList</span></span><br><span class="line"> <span class="keyword">if</span> (List == NULL) {</span><br><span class="line"> iterator->_next = iterator->_prev = NULL;</span><br><span class="line"> _EntryList = iterator;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> ObjectWaiter *Tail;</span><br><span class="line"> <span class="keyword">for</span> (Tail = List; Tail->_next != NULL; Tail = Tail->_next);</span><br><span class="line"> <span class="keyword">assert</span> (Tail != NULL && Tail->_next == NULL, <span class="string">"invariant"</span>);</span><br><span class="line"> Tail->_next = iterator;</span><br><span class="line"> iterator->_prev = Tail;</span><br><span class="line"> iterator->_next = NULL;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (Policy == <span class="number">2</span>) { <span class="comment">// 头插 cxq</span></span><br><span class="line"> <span class="comment">// prepend to cxq</span></span><br><span class="line"> <span class="keyword">if</span> (List == NULL) {</span><br><span class="line"> iterator->_next = iterator->_prev = NULL;</span><br><span class="line"> _EntryList = iterator;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> iterator->TState = ObjectWaiter::TS_CXQ;</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> ObjectWaiter *Front = _cxq;</span><br><span class="line"> iterator->_next = Front;</span><br><span class="line"> <span class="keyword">if</span> (Atomic::cmpxchg_ptr(iterator, &_cxq, Front) == Front) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (Policy == <span class="number">3</span>) { <span class="comment">// 尾插 cxq</span></span><br><span class="line"> iterator->TState = ObjectWaiter::TS_CXQ;</span><br><span class="line"> <span class="keyword">for</span> (;;) {</span><br><span class="line"> ObjectWaiter *Tail;</span><br><span class="line"> Tail = _cxq;</span><br><span class="line"> <span class="keyword">if</span> (Tail == NULL) {</span><br><span class="line"> iterator->_next = NULL;</span><br><span class="line"> <span class="keyword">if</span> (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">while</span> (Tail->_next != NULL) Tail = Tail->_next;</span><br><span class="line"> Tail->_next = iterator;</span><br><span class="line"> iterator->_prev = Tail;</span><br><span class="line"> iterator->_next = NULL;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> ParkEvent *ev = iterator->_event;</span><br><span class="line"> iterator->TState = ObjectWaiter::TS_RUN;</span><br><span class="line"> OrderAccess::fence();</span><br><span class="line"> ev->unpark();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (Policy < <span class="number">4</span>) {</span><br><span class="line"> iterator->wait_reenter_begin(<span class="built_in">this</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"> Thread::SpinRelease(&_WaitSetLock);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {</span><br><span class="line"> ObjectMonitor::_sync_Notifications->inc();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文介绍了 <code>synchronized</code> 工作原理和锁升级的过程。其中锁队列的协作流程较复杂,本文配了详细的流程图可以参照。最后附上了一部分重要代码的解析,理解 <code>synchronized</code> 原理之后便于写出性能更高的代码。</p><p>简单的来说偏向锁通过对比 Mark Word thread id 解决加锁问题。而轻量级锁是通过用 CAS 操作 Mark Word 和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html">HotSpot Glossary of Terms</a></li><li><a href="https://www.oracle.com/technetwork/java/6-performance-137236.html">Java SE 6 Performance White Paper</a></li></ul>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h2 id="synchronized-实现原理"><a href="#synchronized-实现原理" class="headerlink" title="synchronized 实现原理"></a>synchronized 实现原理</h2><p>2020-03-24</summary>
|
||
|
||
|
||
|
||
|
||
<category term="多线程" scheme="https://zyjblogs.cn/categories/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
|
||
|
||
<category term="java" scheme="https://zyjblogs.cn/categories/%E5%A4%9A%E7%BA%BF%E7%A8%8B/java/"/>
|
||
|
||
|
||
<category term="java" scheme="https://zyjblogs.cn/tags/java/"/>
|
||
|
||
<category term="多线程" scheme="https://zyjblogs.cn/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>N皇后</title>
|
||
<link href="https://zyjblogs.cn/posts/bd497d25.html"/>
|
||
<id>https://zyjblogs.cn/posts/bd497d25.html</id>
|
||
<published>2024-03-18T16:00:00.000Z</published>
|
||
<updated>2024-03-19T06:04:07.899Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="N皇后"><a href="#N皇后" class="headerlink" title="N皇后"></a>N皇后</h1><p><a href="https://leetcode-cn.com/problems/n-queens/">51. N皇后</a></p><h3 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h3><blockquote><p>n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。</p></blockquote><p><img src="https://s2.ax1x.com/2019/03/26/ANUzjA.png" alt="ANUzjA.png"></p><blockquote></blockquote><p>上图为 8 皇后问题的一种解法。</p><blockquote></blockquote><p>给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。</p><blockquote></blockquote><p>每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。</p><p>示例:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">输入: 4</span><br><span class="line">输出: [</span><br><span class="line"> [".Q..", // 解法 1</span><br><span class="line"> "...Q",</span><br><span class="line"> "Q...",</span><br><span class="line"> "..Q."],</span><br><span class="line"></span><br><span class="line"> ["..Q.", // 解法 2</span><br><span class="line"> "Q...",</span><br><span class="line"> "...Q",</span><br><span class="line"> ".Q.."]</span><br><span class="line">]</span><br><span class="line">解释: 4 皇后问题存在两个不同的解法。</span><br></pre></td></tr></table></figure><h3 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h3><p>约束条件为每个棋子所在的行、列、对角线都不能有另一个棋子。</p><p>使用一维数组表示一种解法,下标(index)表示行,值(value)表示该行的Q(皇后)在哪一列。<br>每行只存储一个元素,然后递归到下一行,这样就不用判断行了,只需要判断列和对角线。</p><h3 id="Solution1"><a href="#Solution1" class="headerlink" title="Solution1"></a>Solution1</h3><p>当result[row] = column时,即row行的棋子在column列。</p><p>对于[0, row-1]的任意一行(i 行),若 row 行的棋子和 i 行的棋子在同一列,则有result[i] == column;<br>若 row 行的棋子和 i 行的棋子在同一对角线,等腰直角三角形两直角边相等,即 row - i == Math.abs(result[i] - column)</p><p>布尔类型变量 isValid 的作用是剪枝,减少不必要的递归。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> List<List<String>> <span class="title function_">solveNQueens</span><span class="params">(<span class="type">int</span> n)</span> {</span><br><span class="line"><span class="comment">// 下标代表行,值代表列。如result[0] = 3 表示第1行的Q在第3列</span></span><br><span class="line"><span class="type">int</span>[] result = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line">List<List<String>> resultList = <span class="keyword">new</span> <span class="title class_">LinkedList</span><>();</span><br><span class="line">dfs(resultList, result, <span class="number">0</span>, n);</span><br><span class="line"><span class="keyword">return</span> resultList;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">dfs</span><span class="params">(List<List<String>> resultList, <span class="type">int</span>[] result, <span class="type">int</span> row, <span class="type">int</span> n)</span> {</span><br><span class="line"> <span class="comment">// 递归终止条件</span></span><br><span class="line"><span class="keyword">if</span> (row == n) {</span><br><span class="line">List<String> list = <span class="keyword">new</span> <span class="title class_">LinkedList</span><>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">0</span>; x < n; ++x) {</span><br><span class="line"><span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> <span class="number">0</span>; y < n; ++y)</span><br><span class="line">sb.append(result[x] == y ? <span class="string">"Q"</span> : <span class="string">"."</span>);</span><br><span class="line">list.add(sb.toString());</span><br><span class="line">}</span><br><span class="line">resultList.add(list);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">column</span> <span class="operator">=</span> <span class="number">0</span>; column < n; ++column) {</span><br><span class="line"><span class="type">boolean</span> <span class="variable">isValid</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">result[row] = column;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 逐行往下考察每一行。同列,result[i] == column</span></span><br><span class="line"><span class="comment"> * 同对角线,row - i == Math.abs(result[i] - column)</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> row - <span class="number">1</span>; i >= <span class="number">0</span>; --i) {</span><br><span class="line"><span class="keyword">if</span> (result[i] == column || row - i == Math.abs(result[i] - column)) {</span><br><span class="line">isValid = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span> (isValid) dfs(resultList, result, row + <span class="number">1</span>, n);</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="Solution2"><a href="#Solution2" class="headerlink" title="Solution2"></a>Solution2</h3><p>使用LinkedList表示一种解法,下标(index)表示行,值(value)表示该行的Q(皇后)在哪一列。</p><p>解法二和解法一的不同在于,相同列以及相同对角线的校验。<br>将对角线抽象成【一次函数】这个简单的数学模型,根据一次函数的截距是常量这一特性进行校验。</p><p>这里,我将右上-左下对角线,简称为“\”对角线;左上-右下对角线简称为“/”对角线。</p><p>“/”对角线斜率为1,对应方程为y = x + b,其中b为截距。<br>对于线上任意一点,均有y - x = b,即row - i = b;<br>定义一个布尔类型数组anti_diag,将b作为下标,当anti_diag[b] = true时,表示相应对角线上已经放置棋子。<br>但row - i有可能为负数,负数不能作为数组下标,row - i 的最小值为-n(当row = 0,i = n时),可以加上n作为数组下标,即将row -i + n 作为数组下标。<br>row - i + n 的最大值为 2n(当row = n,i = 0时),故anti_diag的容量设置为 2n 即可。</p><p><img src="https://s2.ax1x.com/2019/03/26/ANXG79.png" alt="ANXG79.png"></p><p>“\”对角线斜率为-1,对应方程为y = -x + b,其中b为截距。<br>对于线上任意一点,均有y + x = b,即row + i = b;<br>同理,定义数组main_diag,将b作为下标,当main_diag[row + i] = true时,表示相应对角线上已经放置棋子。</p><p>有了两个校验对角线的数组,再来定义一个用于校验列的数组cols,这个太简单啦,不解释。</p><p><strong>解法二时间复杂度为O(n!),在校验相同列和相同对角线时,引入三个布尔类型数组进行判断。相比解法一,少了一层循环,用空间换时间。</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">List<List<String>> resultList = <span class="keyword">new</span> <span class="title class_">LinkedList</span><>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> List<List<String>> <span class="title function_">solveNQueens</span><span class="params">(<span class="type">int</span> n)</span> {</span><br><span class="line"><span class="type">boolean</span>[] cols = <span class="keyword">new</span> <span class="title class_">boolean</span>[n];</span><br><span class="line"><span class="type">boolean</span>[] main_diag = <span class="keyword">new</span> <span class="title class_">boolean</span>[<span class="number">2</span> * n];</span><br><span class="line"><span class="type">boolean</span>[] anti_diag = <span class="keyword">new</span> <span class="title class_">boolean</span>[<span class="number">2</span> * n];</span><br><span class="line">LinkedList<Integer> result = <span class="keyword">new</span> <span class="title class_">LinkedList</span><>();</span><br><span class="line">dfs(result, <span class="number">0</span>, cols, main_diag, anti_diag, n);</span><br><span class="line"><span class="keyword">return</span> resultList;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">dfs</span><span class="params">(LinkedList<Integer> result, <span class="type">int</span> row, <span class="type">boolean</span>[] cols, <span class="type">boolean</span>[] main_diag, <span class="type">boolean</span>[] anti_diag, <span class="type">int</span> n)</span> {</span><br><span class="line"><span class="keyword">if</span> (row == n) {</span><br><span class="line">List<String> list = <span class="keyword">new</span> <span class="title class_">LinkedList</span><>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">0</span>; x < n; ++x) {</span><br><span class="line"><span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> <span class="number">0</span>; y < n; ++y)</span><br><span class="line">sb.append(result.get(x) == y ? <span class="string">"Q"</span> : <span class="string">"."</span>);</span><br><span class="line">list.add(sb.toString());</span><br><span class="line">}</span><br><span class="line">resultList.add(list);</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">}</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 < n; ++i) {</span><br><span class="line"><span class="keyword">if</span> (cols[i] || main_diag[row + i] || anti_diag[row - i + n])</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line">result.add(i);</span><br><span class="line">cols[i] = <span class="literal">true</span>;</span><br><span class="line">main_diag[row + i] = <span class="literal">true</span>;</span><br><span class="line">anti_diag[row - i + n] = <span class="literal">true</span>;</span><br><span class="line">dfs(result, row + <span class="number">1</span>, cols, main_diag, anti_diag, n);</span><br><span class="line">result.removeLast();</span><br><span class="line">cols[i] = <span class="literal">false</span>;</span><br><span class="line">main_diag[row + i] = <span class="literal">false</span>;</span><br><span class="line">anti_diag[row - i + n] = <span class="literal">false</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="N皇后"><a href="#N皇后" class="headerlink" title="N皇后"></a>N皇后</h1><p><a href="https://leetcode-cn.com/problems/n-queens/">51. N皇后</a></</summary>
|
||
|
||
|
||
|
||
|
||
<category term="算法" scheme="https://zyjblogs.cn/categories/%E7%AE%97%E6%B3%95/"/>
|
||
|
||
|
||
<category term="算法" scheme="https://zyjblogs.cn/tags/%E7%AE%97%E6%B3%95/"/>
|
||
|
||
<category term="回溯" scheme="https://zyjblogs.cn/tags/%E5%9B%9E%E6%BA%AF/"/>
|
||
|
||
<category term="递归" scheme="https://zyjblogs.cn/tags/%E9%80%92%E5%BD%92/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>状态转移方程</title>
|
||
<link href="https://zyjblogs.cn/posts/92fa7813.html"/>
|
||
<id>https://zyjblogs.cn/posts/92fa7813.html</id>
|
||
<published>2024-03-18T16:00:00.000Z</published>
|
||
<updated>2024-03-19T06:47:50.492Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="状态转移方程"><a href="#状态转移方程" class="headerlink" title="状态转移方程"></a>状态转移方程</h1><h2 id="定义"><a href="#定义" class="headerlink" title="定义"></a><strong>定义</strong></h2><p><a href="https://baike.so.com/doc/6995222-7218096.html">动态规划</a>中本阶段的状态往往是上一阶段状态和上一阶段决策的结果。若给定了第K阶段的状态Sk以及决策uk(Sk),则第K+1阶段的状态Sk+1也就完全确定。也就是说Sk+1与Sk,uk之间存在一种明确的数量对应关系,记为Tk(Sk,uk),即有Sk+1= Tk(Sk,uk)。 这种用函数表示前后阶段关系的方程,称为状态转移方程。在上例中状态转移方程为 Sk+1= uk(Sk) 。</p><h2 id="设计"><a href="#设计" class="headerlink" title="设计"></a><strong>设计</strong></h2><p>适用条件</p><p>任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。同样,动态规划也并不是万能的。适用动态规划的问题必须满足最优化原理和无后效性。</p><p>1.<a href="https://baike.so.com/doc/1852557-1959037.html">最优化原理</a>(最优子结构性质) 最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。</p><p>2.无后效性将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。</p><p>3.子问题的重叠性 动态规划将原来具有指数级时间复杂度的<a href="https://baike.so.com/doc/6058609-6271658.html">搜索算法</a>改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。</p><p>如何设计动态转移方程</p><p>如果满足上述条件,一般可以按照以下步骤进行设计:</p><p>一、确定问题的<a href="https://baike.so.com/doc/8780581-9104627.html">决策对象</a></p><p>二、对决策对象划分阶段</p><p>三、对各阶段确定<a href="https://baike.so.com/doc/1032672-1092144.html">状态变量</a></p><p>四、根据状态变量确定费用函数和目标函数</p><p>五、建立各阶段的状态变量的转移方程,写出状态转移方程</p><p>六、编程实现</p><h2 id="状态转移方程的代码实现"><a href="#状态转移方程的代码实现" class="headerlink" title="状态转移方程的代码实现"></a><strong>状态转移方程的代码实现</strong></h2><p>假设列出了状态转移方程:d(i, j) = a(i, j) + max{d(i + 1, j), d(i + 1, j + 1)}。</p><h3 id="折叠递归计算"><a href="#折叠递归计算" class="headerlink" title="折叠递归计算"></a><a href="https://baike.so.com/doc/2649061-2797348.html#">折叠</a><strong>递归计算</strong></h3><figure class="highlight cpp"><table><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="function"><span class="type">int</span> <span class="title">d</span><span class="params">(<span class="type">int</span> i, <span class="type">int</span> j)</span></span>{</span><br><span class="line"><span class="keyword">return</span> a[i][j] + (i == n ? <span class="number">0</span> : (<span class="built_in">d</span>(i + <span class="number">1</span>, j) > <span class="built_in">d</span>(i + <span class="number">1</span>, j + <span class="number">1</span>) ? <span class="built_in">d</span>(i + <span class="number">1</span>, j) : <span class="built_in">d</span>(i + <span class="number">1</span>, j + <span class="number">1</span>)));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>递归方法的缺点是:效率比较低,首先在调用函数的嵌套时,函数不断的切换,由此降低了效率。其次是相同的子问题被重复求解,例如:d(2, 3), d(4, 2), d(4, 3)就是被重复求解了两次。</p><h3 id="折叠递推计算"><a href="#折叠递推计算" class="headerlink" title="折叠递推计算"></a><a href="https://baike.so.com/doc/2649061-2797348.html#">折叠</a><strong>递推计算</strong></h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> i, j;</span><br><span class="line"><span class="keyword">for</span>(j = <span class="number">1</span>; j <= n; ++j)</span><br><span class="line">d[n][j] = a[n][j];</span><br><span class="line"><span class="keyword">for</span>(i = n<span class="number">-1</span>; i >= <span class="number">1</span>; --i)</span><br><span class="line"><span class="keyword">for</span>(j = <span class="number">1</span>; j <= i; ++j)</span><br><span class="line">d[i][j] = a[i][j] + (d[i + <span class="number">1</span>][j] > d[i + <span class="number">1</span>][j + <span class="number">1</span>] ? d[i + <span class="number">1</span>][j] : d[i + <span class="number">1</span>][j + <span class="number">1</span>]);</span><br></pre></td></tr></table></figure><p>递推要注意边界的处理。</p><h3 id="折叠记忆化搜索"><a href="#折叠记忆化搜索" class="headerlink" title="折叠记忆化搜索"></a><a href="https://baike.so.com/doc/2649061-2797348.html#">折叠</a><strong>记忆化搜索</strong></h3><p>首先设置一个数组,目的是保存已经计算好的子问题的解,下次再计算相同子问题时,就不用重复求解了,如下设置一个st数组用来保存计算好的子问题的解,初始化st所有元素为-1。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">d</span><span class="params">(<span class="type">int</span> i, <span class="type">int</span> j)</span></span>{</span><br><span class="line"><span class="keyword">if</span>(st[i][j] > <span class="number">0</span>)</span><br><span class="line"><span class="keyword">return</span> st[i][j];</span><br><span class="line"><span class="keyword">return</span> st[i][j] = a[i][j] + (i == n ? <span class="number">0</span> : (<span class="built_in">d</span>(i + <span class="number">1</span>, j) > <span class="built_in">d</span>(i + <span class="number">1</span>, j + <span class="number">1</span>) ? <span class="built_in">d</span>(i + <span class="number">1</span>, j) : <span class="built_in">d</span>(i + <span class="number">1</span>, j + <span class="number">1</span>)));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>记忆化搜索用的也是递归的方法,目的是把子问题的解保存下来,避免重复计算的情况,这是它比纯递归更高效的原因。</p><p>记忆化搜索跟递推相比,它的优点是:它不必事先确定好各状态的计算顺序,但使用递推时必须事先确定好计算顺序。</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="状态转移方程"><a href="#状态转移方程" class="headerlink" title="状态转移方程"></a>状态转移方程</h1><h2 id="定义"><a href="#定义" class="headerlink" title="定义"><</summary>
|
||
|
||
|
||
|
||
|
||
<category term="算法" scheme="https://zyjblogs.cn/categories/%E7%AE%97%E6%B3%95/"/>
|
||
|
||
|
||
<category term="dp" scheme="https://zyjblogs.cn/tags/dp/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>Union-Find 算法</title>
|
||
<link href="https://zyjblogs.cn/posts/e88a2bb0.html"/>
|
||
<id>https://zyjblogs.cn/posts/e88a2bb0.html</id>
|
||
<published>2024-03-18T16:00:00.000Z</published>
|
||
<updated>2024-03-19T06:04:07.902Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="Union-Find-算法(并查集算法)"><a href="#Union-Find-算法(并查集算法)" class="headerlink" title="Union-Find 算法(并查集算法)"></a>Union-Find 算法(并查集算法)</h1><h2 id="⼀、问题介绍"><a href="#⼀、问题介绍" class="headerlink" title="⼀、问题介绍"></a>⼀、问题介绍</h2><p>简单说,动态连通性其实可以抽象成给⼀幅图连线。⽐如下⾯这幅图,总共</p><p>有 10 个节点,他们互不相连,分别⽤ 0~9 标记:</p><p><img src="/posts/e88a2bb0/images/%E5%B9%B6%E6%9F%A5%E9%9B%86%E7%AE%97%E6%B3%95/2022-07-28-09-41-27-image.png"></p><p>现在我们的 Union-Find 算法主要需要实现这两个 API:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UF</span> { </span><br><span class="line"> <span class="comment">/* 将 p 和 q 连接 */</span> </span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">union</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span>; </span><br><span class="line"> <span class="comment">/* 判断 p 和 q 是否连通 */</span> </span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">connected</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span>; </span><br><span class="line"> <span class="comment">/* 返回图中有多少个连通分量 */</span> </span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">count</span><span class="params">()</span>; </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这⾥所说的「连通」是⼀种等价关系,也就是说具有如下三个性质:</p><p>1、⾃反性:节点 p 和 p 是连通的。</p><p>2、对称性:如果节点 p 和 q 连通,那么 q 和 p 也连通。</p><p>3、传递性:如果节点 p 和 q 连通, q 和 r 连通,那么 p 和 r 也连通。</p><p>⽐如说之前那幅图,0〜9 任意两个不同的点都不连通,调⽤ connected 都</p><p>会返回 false,连通分量为 10 个。</p><p>如果现在调⽤ union(0, 1) ,那么 0 和 1 被连通,连通分量降为 9 个。</p><p>再调⽤ union(1, 2) ,这时 0,1,2 都被连通,调⽤ connected(0, 2) 也会返回</p><p>true,连通分量变为 8 个。</p><p><img src="/posts/e88a2bb0/images/%E5%B9%B6%E6%9F%A5%E9%9B%86%E7%AE%97%E6%B3%95/2022-07-28-09-42-58-image.png"></p><p>判断这种「等价关系」⾮常实⽤,⽐如说编译器判断同⼀个变量的不同引</p><p>⽤,⽐如社交⽹络中的朋友圈计算等等。</p><p>这样,你应该⼤概明⽩什么是动态连通性了,Union-Find 算法的关键就在</p><p>于 union 和 connected 函数的效率。那么⽤什么模型来表⽰这幅图的连通状</p><p>态呢?⽤什么数据结构来实现代码呢?</p><h2 id="⼆、基本思路"><a href="#⼆、基本思路" class="headerlink" title="⼆、基本思路"></a>⼆、基本思路</h2><p>注意我刚才把「模型」和具体的「数据结构」分开说,这么做是有原因的。</p><p>因为我们使⽤森林(若⼲棵树)来表⽰图的动态连通性,⽤数组来具体实现</p><p>这个森林。</p><p>怎么⽤森林来表⽰连通性呢?我们设定树的每个节点有⼀个指针指向其⽗节</p><p>点,如果是根节点的话,这个指针指向⾃⼰。⽐如说刚才那幅 10 个节点的</p><p>图,⼀开始的时候没有相互连通,就是这样:</p><p><img src="/posts/e88a2bb0/images/%E5%B9%B6%E6%9F%A5%E9%9B%86%E7%AE%97%E6%B3%95/2022-07-28-09-43-20-image.png"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UF</span> { </span><br><span class="line"> <span class="comment">// 记录连通分量 </span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> count; </span><br><span class="line"> <span class="comment">// 节点 x 的节点是 parent[x] </span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span>[] parent; </span><br><span class="line"> <span class="comment">/* 构造函数,n 为图的节点总数 */</span> </span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">UF</span><span class="params">(<span class="type">int</span> n)</span> { </span><br><span class="line"> <span class="comment">// ⼀开始互不连通 </span></span><br><span class="line"> <span class="built_in">this</span>.count = n; </span><br><span class="line"> <span class="comment">// ⽗节点指针初始指向⾃⼰ </span></span><br><span class="line"> parent = <span class="keyword">new</span> <span class="title class_">int</span>[n]; </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 < n; i++) {</span><br><span class="line"> parent[i] = 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><br></pre></td></tr></table></figure><p>如果某两个节点被连通,则让其中的(任意)⼀个节点的根节点接到另⼀个</p><p>节点的根节点上:</p><p><img src="/posts/e88a2bb0/images/%E5%B9%B6%E6%9F%A5%E9%9B%86%E7%AE%97%E6%B3%95/2022-07-28-09-44-44-image.png"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">union</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span> { </span><br><span class="line"> <span class="type">int</span> <span class="variable">rootP</span> <span class="operator">=</span> find(p); </span><br><span class="line"> <span class="type">int</span> <span class="variable">rootQ</span> <span class="operator">=</span> find(q); </span><br><span class="line"> <span class="keyword">if</span> (rootP == rootQ) </span><br><span class="line"> <span class="keyword">return</span>; </span><br><span class="line"> <span class="comment">// 将两棵树合并为⼀棵 </span></span><br><span class="line"> parent[rootP] = rootQ; </span><br><span class="line"> <span class="comment">// parent[rootQ] = rootP 也⼀样 </span></span><br><span class="line"> count--; <span class="comment">// 两个分量合⼆为⼀ </span></span><br><span class="line">}</span><br><span class="line"><span class="comment">/* 返回某个节点 x 的根节点 */</span> </span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">find</span><span class="params">(<span class="type">int</span> x)</span> { </span><br><span class="line"> <span class="comment">// 根节点的 parent[x] == x </span></span><br><span class="line"> <span class="keyword">while</span> (parent[x] != x) </span><br><span class="line"> x = parent[x]; </span><br><span class="line"> <span class="keyword">return</span> x; </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_">count</span><span class="params">()</span> { </span><br><span class="line"> <span class="keyword">return</span> count; </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样,如果节点 p 和 q 连通的话,它们⼀定拥有相同的根节点:</p><p><img src="/posts/e88a2bb0/images/%E5%B9%B6%E6%9F%A5%E9%9B%86%E7%AE%97%E6%B3%95/2022-07-28-09-46-42-image.png"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">connected</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span> { </span><br><span class="line"><span class="type">int</span> <span class="variable">rootP</span> <span class="operator">=</span> find(p); </span><br><span class="line"><span class="type">int</span> <span class="variable">rootQ</span> <span class="operator">=</span> find(q); </span><br><span class="line"><span class="keyword">return</span> rootP == rootQ;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>⾄此,Union-Find 算法就基本完成了。是不是很神奇?竟然可以这样使⽤数</p><p>组来模拟出⼀个森林,如此巧妙的解决这个⽐较复杂的问题!</p><p>那么这个算法的复杂度是多少呢?我们发现,主要</p><p>API connected 和 union 中的复杂度都是 find 函数造成的,所以说它们的</p><p>复杂度和 find ⼀样。</p><p>find 主要功能就是从某个节点向上遍历到树根,其时间复杂度就是树的⾼</p><p>度。我们可能习惯性地认为树的⾼度就是 logN ,但这并不⼀定。 logN 的</p><p>⾼度只存在于平衡⼆叉树,对于⼀般的树可能出现极端不平衡的情况,使得</p><p>「树」⼏乎退化成「链表」,树的⾼度最坏情况下可能变成 N 。</p><p><img src="/posts/e88a2bb0/images/%E5%B9%B6%E6%9F%A5%E9%9B%86%E7%AE%97%E6%B3%95/2022-07-28-09-47-17-image.png"></p><p>所以说上⾯这种解法, find , union , connected 的时间复杂度都是 O(N)。</p><p>这个复杂度很不理想的,你想图论解决的都是诸如社交⽹络这样数据规模巨</p><p>⼤的问题,对于 union 和 connected 的调⽤⾮常频繁,每次调⽤需要线性时</p><p>间完全不可忍受。</p><p>问题的关键在于,如何想办法避免树的不平衡呢?只需要略施⼩计即可。</p><h2 id="三、平衡性优化"><a href="#三、平衡性优化" class="headerlink" title="三、平衡性优化"></a>三、平衡性优化</h2><p>我们要知道哪种情况下可能出现不平衡现象,关键在于 union 过程:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">union</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span> { </span><br><span class="line"> <span class="type">int</span> <span class="variable">rootP</span> <span class="operator">=</span> find(p); </span><br><span class="line"> <span class="type">int</span> <span class="variable">rootQ</span> <span class="operator">=</span> find(q); </span><br><span class="line"> <span class="keyword">if</span> (rootP == rootQ) </span><br><span class="line"> <span class="keyword">return</span>; </span><br><span class="line"> <span class="comment">// 将两棵树合并为⼀棵 </span></span><br><span class="line"> parent[rootP] = rootQ; </span><br><span class="line"> <span class="comment">// parent[rootQ] = rootP 也可以 </span></span><br><span class="line"> count--;</span><br><span class="line"> <span class="comment">//......</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们⼀开始就是简单粗暴的把 p 所在的树接到 q 所在的树的根节点下⾯,</p><p>那么这⾥就可能出现「头重脚轻」的不平衡状况,⽐如下⾯这种局⾯:</p><p><img src="/posts/e88a2bb0/images/%E5%B9%B6%E6%9F%A5%E9%9B%86%E7%AE%97%E6%B3%95/2022-07-28-09-50-04-image.png"></p><p>⻓此以往,树可能⽣⻓得很不平衡。我们其实是希望,⼩⼀些的树接到⼤⼀</p><p>些的树下⾯,这样就能避免头重脚轻,更平衡⼀些。解决⽅法是额外使⽤⼀</p><p>个 size 数组,记录每棵树包含的节点数,我们不妨称为「重量」:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UF</span> { </span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> count; </span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span>[] parent; </span><br><span class="line"> <span class="comment">// 新增⼀个数组记录树的“重量” </span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span>[] size; </span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">UF</span><span class="params">(<span class="type">int</span> n)</span> { </span><br><span class="line"> <span class="built_in">this</span>.count = n; </span><br><span class="line"> parent = <span class="keyword">new</span> <span class="title class_">int</span>[n]; </span><br><span class="line"> <span class="comment">// 最初每棵树只有⼀个节点 </span></span><br><span class="line"> <span class="comment">// 重量应该初始化 1 </span></span><br><span class="line"> size = <span class="keyword">new</span> <span class="title class_">int</span>[n]; </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 < n; i++) { </span><br><span class="line"> parent[i] = i; size[i] = <span class="number">1</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><br></pre></td></tr></table></figure><p>⽐如说 size[3] = 5 表⽰,以节点 3 为根的那棵树,总共有 5 个节点。这</p><p>样我们可以修改⼀下 union ⽅法:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">union</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span> { </span><br><span class="line"> <span class="type">int</span> <span class="variable">rootP</span> <span class="operator">=</span> find(p); </span><br><span class="line"> <span class="type">int</span> <span class="variable">rootQ</span> <span class="operator">=</span> find(q); </span><br><span class="line"> <span class="keyword">if</span> (rootP == rootQ) </span><br><span class="line"> <span class="keyword">return</span>; </span><br><span class="line"> <span class="comment">// ⼩树接到⼤树下⾯,较平衡 </span></span><br><span class="line"> <span class="keyword">if</span> (size[rootP] > size[rootQ]) { </span><br><span class="line"> parent[rootQ] = rootP; size[rootP] += size[rootQ]; </span><br><span class="line"> } <span class="keyword">else</span> { </span><br><span class="line"> parent[rootP] = rootQ; size[rootQ] += size[rootP]; </span><br><span class="line"> }</span><br><span class="line"> count--; </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样,通过⽐较树的重量,就可以保证树的⽣⻓相对平衡,树的⾼度⼤致</p><p>在 logN 这个数量级,极⼤提升执⾏效率。</p><p>此时, find , union , connected 的时间复杂度都下降为 O(logN),即便数据</p><p>规模上亿,所需时间也⾮常少</p><h2 id="四、路径压缩"><a href="#四、路径压缩" class="headerlink" title="四、路径压缩"></a>四、路径压缩</h2><p>这步优化特别简单,所以⾮常巧妙。我们能不能进⼀步压缩每棵树的⾼度,</p><p>使树⾼始终保持为常数?</p><p><img src="/posts/e88a2bb0/images/%E5%B9%B6%E6%9F%A5%E9%9B%86%E7%AE%97%E6%B3%95/2022-07-28-09-52-40-image.png"></p><p>这样 find 就能以 O(1) 的时间找到某⼀节点的根节点,相应</p><p>的, connected 和 union 复杂度都下降为 O(1)。</p><p>要做到这⼀点,⾮常简单,只需要在 find 中加⼀⾏代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">find</span><span class="params">(<span class="type">int</span> x)</span> { </span><br><span class="line"> <span class="keyword">while</span> (parent[x] != x) { </span><br><span class="line"> <span class="comment">// 进⾏路径压缩 </span></span><br><span class="line"> parent[x] = parent[parent[x]]; </span><br><span class="line"> x = parent[x]; </span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> x; </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个操作有点匪夷所思,看个 GIF 就明⽩它的作⽤了(为清晰起⻅,这棵</p><p>树⽐较极端):</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_gif/gibkIz0MVqdHPaNWjlxCMeER7r1MqoLV5JRlgy2dSOiabum6z1OHcMqkzkP6MXf5DiclqDVR1OicMPhL99Jx3qQH7w/640?wx_fmt=gif&wxfrom=5&wx_lazy=1&wx_co=1" alt="图片"></p><p>用语言描述就是,每次 while 循环都会把一对儿父子节点改到同一层,这样每次调用<code>find</code>函数向树根遍历的同时,顺手就将树高缩短了,最终所有树高都会是一个常数,那么所有方法的复杂度也就都是 O(1)。</p><blockquote><p>PS:读者可能会问,这个 GIF 图的<code>find</code>过程完成之后,树高确实缩短了,但是如果更高的树,压缩后高度可能依然很大呀?不能这么想。因为这个 GIF 的情景是我编出来方便大家理解路径压缩的,但是实际中,每次<code>find</code>都会进行路径压缩,所以树本来就不可能增长到这么高,这种担心是多余的。</p></blockquote><p>路径压缩的第二种写法是这样:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第二种路径压缩的 find 方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">find</span><span class="params">(<span class="type">int</span> x)</span> {</span><br><span class="line"> <span class="keyword">if</span> (parent[x] != x) {</span><br><span class="line"> parent[x] = find(parent[x]);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> parent[x];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我一度认为这种递归写法和第一种迭代写法做的事情一样,但实际上是我大意了,有读者指出这种写法进行路径压缩的效率是高于上一种解法的。</p><p>这个递归过程有点不好理解,你可以自己手画一下递归过程。我把这个函数做的事情翻译成迭代形式,方便你理解它进行路径压缩的原理:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="keyword">public</span> <span class="type">int</span> <span class="title function_">find</span><span class="params">(<span class="type">int</span> x)</span> {</span><br><span class="line"> <span class="comment">// 先找到根节点</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">root</span> <span class="operator">=</span> x;</span><br><span class="line"> <span class="keyword">while</span> (parent[root] != root) {</span><br><span class="line"> root = parent[root];</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 然后把 x 到根节点之间的所有节点直接接到根节点下面</span></span><br><span class="line"> <span class="type">int</span> <span class="variable">old_parent</span> <span class="operator">=</span> parent[x];</span><br><span class="line"> <span class="keyword">while</span> (x != root) {</span><br><span class="line"> parent[x] = root;</span><br><span class="line"> x = old_parent;</span><br><span class="line"> old_parent = parent[old_parent];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> root;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这种路径压缩的效果如下:</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/gibkIz0MVqdHPaNWjlxCMeER7r1MqoLV5uqSibPic0joJayCeia7cCcNbCRAUgLEibt5661foYGvesxbOdxlKOtxAQA/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1" alt="图片"></p><p>比起第一种路径压缩,显然这种方法压缩得更彻底,直接把一整条树枝压平,一点意外都没有,所以从效率的角度来说,推荐你使用这种路径压缩算法。</p><p><strong>另外,如果路径压缩技巧将树高保持为常数了,那么<code>size</code>数组的平衡优化就不是特别必要了</strong>。</p><p>所以你一般看到的 Union Find 算法应该是如下实现:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UF</span> {</span><br><span class="line"> <span class="comment">// 连通分量个数</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> count;</span><br><span class="line"> <span class="comment">// 存储每个节点的父节点</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span>[] parent;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// n 为图中节点的个数</span></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">UF</span><span class="params">(<span class="type">int</span> n)</span> {</span><br><span class="line"> <span class="built_in">this</span>.count = n;</span><br><span class="line"> parent = <span class="keyword">new</span> <span class="title class_">int</span>[n];</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 < n; i++) {</span><br><span class="line"> parent[i] = i;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将节点 p 和节点 q 连通</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">union</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">rootP</span> <span class="operator">=</span> find(p);</span><br><span class="line"> <span class="type">int</span> <span class="variable">rootQ</span> <span class="operator">=</span> find(q);</span><br><span class="line"> <span class="keyword">if</span> (rootP == rootQ)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> parent[rootQ] = rootP;</span><br><span class="line"> <span class="comment">// 两个连通分量合并成一个连通分量</span></span><br><span class="line"> count--;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 判断节点 p 和节点 q 是否连通</span></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">connected</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">rootP</span> <span class="operator">=</span> find(p);</span><br><span class="line"> <span class="type">int</span> <span class="variable">rootQ</span> <span class="operator">=</span> find(q);</span><br><span class="line"> <span class="keyword">return</span> rootP == rootQ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">find</span><span class="params">(<span class="type">int</span> x)</span> {</span><br><span class="line"> <span class="keyword">if</span> (parent[x] != x) {</span><br><span class="line"> parent[x] = find(parent[x]);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> parent[x];</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_">count</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Union-Find 算法的复杂度可以这样分析:构造函数初始化数据结构需要 O(N) 的时间和空间复杂度;连通两个节点<code>union</code>、判断两个节点的连通性<code>connected</code>、计算连通分量<code>count</code>所需的时间复杂度均为 O(1)。</p><p>到这里,相信你已经掌握了 Union-Find 算法的核心逻辑,总结一下我们优化算法的过程:</p><p>1、用<code>parent</code>数组记录每个节点的父节点,相当于指向父节点的指针,所以<code>parent</code>数组内实际存储着一个森林(若干棵多叉树)。</p><p>2、用<code>size</code>数组记录着每棵树的重量,目的是让<code>union</code>后树依然拥有平衡性,保证各个 API 时间复杂度为 O(logN),而不会退化成链表影响操作效率。</p><p>3、在<code>find</code>函数中进行路径压缩,保证任意树的高度保持在常数,使得各个 API 时间复杂度为 O(1)。使用了路径压缩之后,可以不使用<code>size</code>数组的平衡优化。</p><p>下面我们看一些具体的并查集题目。</p><h2 id="五、题目实践"><a href="#五、题目实践" class="headerlink" title="五、题目实践"></a>五、题目实践</h2><p>力扣第 323 题「无向图中连通分量的数目」就是最基本的连通分量题目:</p><p>给你输入一个包含<code>n</code>个节点的图,用一个整数<code>n</code>和一个数组<code>edges</code>表示,其中<code>edges[i] = [ai, bi]</code>表示图中节点<code>ai</code>和<code>bi</code>之间有一条边。请你计算这幅图的连通分量个数。</p><p>函数签名如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">countComponents</span><span class="params">(<span class="type">int</span> n, <span class="type">int</span>[][] edges)</span></span><br></pre></td></tr></table></figure><p>这道题我们可以直接套用<code>UF</code>类来解决:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">countComponents</span><span class="params">(<span class="type">int</span> n, <span class="type">int</span>[][] edges)</span> {</span><br><span class="line"> <span class="type">UF</span> <span class="variable">uf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UF</span>(n);</span><br><span class="line"> <span class="comment">// 将每个节点进行连通</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span>[] e : edges) {</span><br><span class="line"> uf.union(e[<span class="number">0</span>], e[<span class="number">1</span>]);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 返回连通分量的个数</span></span><br><span class="line"> <span class="keyword">return</span> uf.count();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UF</span> {</span><br><span class="line"> <span class="comment">// 见上文</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>**<strong>另外,一些使用 DFS 深度优先算法解决的问题,也可以用 Union-Find 算法解决</strong>。</p><p>比如力扣第 130 题「被围绕的区域」:</p><p>给你一个 M×N 的二维矩阵,其中包含字符<code>X</code>和<code>O</code>,让你找到矩阵中<strong>四面</strong>被<code>X</code>围住的<code>O</code>,并且把它们替换成<code>X</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">solve</span><span class="params">(<span class="type">char</span>[][] board)</span>;</span><br></pre></td></tr></table></figure><p>注意哦,必须是四面被围的<code>O</code>才能被换成<code>X</code>,也就是说边角上的<code>O</code>一定不会被围,进一步,与边角上的<code>O</code>相连的<code>O</code>也不会被<code>X</code>围四面,也不会被替换。**</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/gibkIz0MVqdHPaNWjlxCMeER7r1MqoLV5YZuUia1cK3MWlz0ZMRR2GEFqWap5PrnozL1v7Ttofp8J7dBx9oFj6rw/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1" alt="图片"></p><blockquote><p>PS:这让我想起小时候玩的棋类游戏「黑白棋」,只要你用两个棋子把对方的棋子夹在中间,对方的子就被替换成你的子。可见,占据四角的棋子是无敌的,与其相连的边棋子也是无敌的(无法被夹掉)。</p></blockquote><p>其实这个问题应该归为 <a href="https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247492234&idx=1&sn=fef28b1ca7639e056104374ddc9fbf0b&scene=21#wechat_redirect">岛屿系列问题</a> 使用 DFS 算法解决:</p><p>先用 for 循环遍历棋盘的<strong>四边</strong>,用 DFS 算法把那些与边界相连的<code>O</code>换成一个特殊字符,比如<code>#</code>;然后再遍历整个棋盘,把剩下的<code>O</code>换成<code>X</code>,把<code>#</code>恢复成<code>O</code>。这样就能完成题目的要求,时间复杂度 O(MN)。</p><p>但这个问题也可以用 Union-Find 算法解决,虽然实现复杂一些,甚至效率也略低,但这是使用 Union-Find 算法的通用思想,值得一学。</p><p><strong>你可以把那些不需要被替换的<code>O</code>看成一个拥有独门绝技的门派,它们有一个共同「祖师爷」叫<code>dummy</code>,这些<code>O</code>和<code>dummy</code>互相连通,而那些需要被替换的<code>O</code>与<code>dummy</code>不连通</strong>。</p><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/gibkIz0MVqdHPaNWjlxCMeER7r1MqoLV5Pvaa0eolSR1OGaSia0YqnfGzz1n59icmBfTbxtdsbhfJiaGgUTQkdassA/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1" alt="图片"></p><p>这就是 Union-Find 的核心思路,明白这个图,就很容易看懂代码了。</p><p>首先要解决的是,根据我们的实现,Union-Find 底层用的是一维数组,构造函数需要传入这个数组的大小,而题目给的是一个二维棋盘。</p><p>这个很简单,二维坐标<code>(x,y)</code>可以转换成<code>x * n + y</code>这个数(<code>m</code>是棋盘的行数,<code>n</code>是棋盘的列数),<strong>敲黑板,这是将二维坐标映射到一维的常用技巧</strong>。</p><p>其次,我们之前描述的「祖师爷」是虚构的,需要给他老人家留个位置。索引<code>[0.. m*n-1]</code>都是棋盘内坐标的一维映射,那就让这个虚拟的<code>dummy</code>节点占据索引<code>m * n</code>好了。</p><p>看解法代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">solve</span><span class="params">(<span class="type">char</span>[][] board)</span> {</span><br><span class="line"> <span class="keyword">if</span> (board.length == <span class="number">0</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> <span class="variable">m</span> <span class="operator">=</span> board.length;</span><br><span class="line"> <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> board[<span class="number">0</span>].length;</span><br><span class="line"> <span class="comment">// 给 dummy 留一个额外位置</span></span><br><span class="line"> <span class="type">UF</span> <span class="variable">uf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UF</span>(m * n + <span class="number">1</span>);</span><br><span class="line"> <span class="type">int</span> <span class="variable">dummy</span> <span class="operator">=</span> m * n;</span><br><span class="line"> <span class="comment">// 将首列和末列的 O 与 dummy 连通</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 < m; i++) {</span><br><span class="line"> <span class="keyword">if</span> (board[i][<span class="number">0</span>] == <span class="string">'O'</span>)</span><br><span class="line"> uf.union(i * n, dummy);</span><br><span class="line"> <span class="keyword">if</span> (board[i][n - <span class="number">1</span>] == <span class="string">'O'</span>)</span><br><span class="line"> uf.union(i * n + n - <span class="number">1</span>, dummy);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 将首行和末行的 O 与 dummy 连通</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 < n; j++) {</span><br><span class="line"> <span class="keyword">if</span> (board[<span class="number">0</span>][j] == <span class="string">'O'</span>)</span><br><span class="line"> uf.union(j, dummy);</span><br><span class="line"> <span class="keyword">if</span> (board[m - <span class="number">1</span>][j] == <span class="string">'O'</span>)</span><br><span class="line"> uf.union(n * (m - <span class="number">1</span>) + j, dummy);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 方向数组 d 是上下左右搜索的常用手法</span></span><br><span class="line"> <span class="type">int</span>[][] d = <span class="keyword">new</span> <span class="title class_">int</span>[][]{{<span class="number">1</span>,<span class="number">0</span>}, {<span class="number">0</span>,<span class="number">1</span>}, {<span class="number">0</span>,-<span class="number">1</span>}, {-<span class="number">1</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">1</span>; i < m - <span class="number">1</span>; i++) </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 < n - <span class="number">1</span>; j++) </span><br><span class="line"> <span class="keyword">if</span> (board[i][j] == <span class="string">'O'</span>)</span><br><span class="line"> <span class="comment">// 将此 O 与上下左右的 O 连通</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 < <span class="number">4</span>; k++) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> i + d[k][<span class="number">0</span>];</span><br><span class="line"> <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> j + d[k][<span class="number">1</span>];</span><br><span class="line"> <span class="keyword">if</span> (board[x][y] == <span class="string">'O'</span>)</span><br><span class="line"> uf.union(x * n + y, i * n + j);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 所有不和 dummy 连通的 O,都要被替换</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">1</span>; i < m - <span class="number">1</span>; i++) </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 < n - <span class="number">1</span>; j++) </span><br><span class="line"> <span class="keyword">if</span> (!uf.connected(dummy, i * n + j))</span><br><span class="line"> board[i][j] = <span class="string">'X'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UF</span> {</span><br><span class="line"> <span class="comment">// 见上文</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这段代码很长,其实就是刚才的思路实现,只有和边界<code>O</code>相连的<code>O</code>才具有和<code>dummy</code>的连通性,他们不会被替换。</p><p>其实用 Union-Find 算法解决这个简单的问题有点杀鸡用牛刀,它可以解决更复杂,更具有技巧性的问题,<strong>主要思路是适时增加虚拟节点,想办法让元素「分门别类」,建立动态连通关系</strong>。</p><p>力扣第 990 题「等式方程的可满足性」用 Union-Find 算法就显得十分优美了,题目是这样:</p><p>给你一个数组<code>equations</code>,装着若干字符串表示的算式。每个算式<code>equations[i]</code>长度都是 4,而且只有这两种情况:<code>a==b</code>或者<code>a!=b</code>,其中<code>a,b</code>可以是任意小写字母。你写一个算法,如果<code>equations</code>中所有算式都不会互相冲突,返回 true,否则返回 false。</p><p>比如说,输入<code>["a==b","b!=c","c==a"]</code>,算法返回 false,因为这三个算式不可能同时正确。</p><p>再比如,输入<code>["c==c","b==d","x!=z"]</code>,算法返回 true,因为这三个算式并不会造成逻辑冲突。</p><p>我们前文说过,动态连通性其实就是一种等价关系,具有「自反性」「传递性」和「对称性」,其实<code>==</code> 关系也是一种等价关系,具有这些性质。所以这个问题用 Union-Find 算法就很自然。</p><p><strong>核心思想是,将<code>equations</code>中的算式根据<code>==</code>和<code>!=</code>分成两部分,先处理<code>==</code>算式,使得他们通过相等关系各自勾结成门派(连通分量);然后处理<code>!=</code>算式,检查不等关系是否破坏了相等关系的连通性</strong>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="title function_">equationsPossible</span><span class="params">(String[] equations)</span> {</span><br><span class="line"> <span class="comment">// 26 个英文字母</span></span><br><span class="line"> <span class="type">UF</span> <span class="variable">uf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UF</span>(<span class="number">26</span>);</span><br><span class="line"> <span class="comment">// 先让相等的字母形成连通分量</span></span><br><span class="line"> <span class="keyword">for</span> (String eq : equations) {</span><br><span class="line"> <span class="keyword">if</span> (eq.charAt(<span class="number">1</span>) == <span class="string">'='</span>) {</span><br><span class="line"> <span class="type">char</span> <span class="variable">x</span> <span class="operator">=</span> eq.charAt(<span class="number">0</span>);</span><br><span class="line"> <span class="type">char</span> <span class="variable">y</span> <span class="operator">=</span> eq.charAt(<span class="number">3</span>);</span><br><span class="line"> uf.union(x - <span class="string">'a'</span>, y - <span class="string">'a'</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">for</span> (String eq : equations) {</span><br><span class="line"> <span class="keyword">if</span> (eq.charAt(<span class="number">1</span>) == <span class="string">'!'</span>) {</span><br><span class="line"> <span class="type">char</span> <span class="variable">x</span> <span class="operator">=</span> eq.charAt(<span class="number">0</span>);</span><br><span class="line"> <span class="type">char</span> <span class="variable">y</span> <span class="operator">=</span> eq.charAt(<span class="number">3</span>);</span><br><span class="line"> <span class="comment">// 如果相等关系成立,就是逻辑冲突</span></span><br><span class="line"> <span class="keyword">if</span> (uf.connected(x - <span class="string">'a'</span>, y - <span class="string">'a'</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 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 class="keyword">class</span> <span class="title class_">UF</span> {</span><br><span class="line"> <span class="comment">// 见上文</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>至此,这道判断算式合法性的问题就解决了,借助 Union-Find 算法,是不是很简单呢?</p><p>最后,Union-Find 算法也会在一些其他经典图论算法中用到,比如判断「图」和「树」,以及最小生成树的计算,详情见 <a href="https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247492575&idx=1&sn=bf63eb391351a0dfed0d03e1ac5992e7&scene=21#wechat_redirect">Kruskal 最小生成树算法</a>。</p><h2 id="六、最后总结"><a href="#六、最后总结" class="headerlink" title="六、最后总结"></a>六、最后总结</h2><p>我们先来看⼀下完整代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UF</span> {</span><br><span class="line"> <span class="comment">// 连通分量个数</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> count;</span><br><span class="line"> <span class="comment">// 存储⼀棵树</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span>[] parent;</span><br><span class="line"> <span class="comment">// 记录树的“重量”</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span>[] size;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">UF</span><span class="params">(<span class="type">int</span> n)</span> {</span><br><span class="line"> <span class="built_in">this</span>.count = n;</span><br><span class="line"> parent = <span class="keyword">new</span> <span class="title class_">int</span>[n];</span><br><span class="line"> size = <span class="keyword">new</span> <span class="title class_">int</span>[n];</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 < n; i++) {</span><br><span class="line"> parent[i] = i;</span><br><span class="line"> size[i] = <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 class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">union</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">rootP</span> <span class="operator">=</span> find(p);</span><br><span class="line"> <span class="type">int</span> <span class="variable">rootQ</span> <span class="operator">=</span> find(q);</span><br><span class="line"> <span class="keyword">if</span> (rootP == rootQ)</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> <span class="comment">// ⼩树接到⼤树下⾯,较平衡</span></span><br><span class="line"> <span class="keyword">if</span> (size[rootP] > size[rootQ]) {</span><br><span class="line"> parent[rootQ] = rootP;</span><br><span class="line"> size[rootP] += size[rootQ];</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> parent[rootP] = rootQ;</span><br><span class="line"> size[rootQ] += size[rootP];</span><br><span class="line"> }</span><br><span class="line"> count--;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">connected</span><span class="params">(<span class="type">int</span> p, <span class="type">int</span> q)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">rootP</span> <span class="operator">=</span> find(p);</span><br><span class="line"> <span class="type">int</span> <span class="variable">rootQ</span> <span class="operator">=</span> find(q);</span><br><span class="line"> <span class="keyword">return</span> rootP == rootQ;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">find</span><span class="params">(<span class="type">int</span> x)</span> {</span><br><span class="line"> <span class="keyword">while</span> (parent[x] != x) { </span><br><span class="line"> <span class="comment">// 进⾏路径压缩</span></span><br><span class="line"> parent[x] = parent[parent[x]];</span><br><span class="line"> x = parent[x];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> x;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">count</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> count;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Union-Find 算法的复杂度可以这样分析:构造函数初始化数据结构需要</p><p>O(N) 的时间和空间复杂度;连通两个节点 union 、判断两个节点的连通</p><p>性 connected 、计算连通分量 count 所需的时间复杂度均为 O(1)。</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="Union-Find-算法(并查集算法)"><a href="#Union-Find-算法(并查集算法)" class="headerlink" title="Union-Find 算法(并查集算法)"></a>Union-Find 算法(并查集算法)</h1><</summary>
|
||
|
||
|
||
|
||
|
||
<category term="算法" scheme="https://zyjblogs.cn/categories/%E7%AE%97%E6%B3%95/"/>
|
||
|
||
|
||
<category term="算法" scheme="https://zyjblogs.cn/tags/%E7%AE%97%E6%B3%95/"/>
|
||
|
||
<category term="并查集" scheme="https://zyjblogs.cn/tags/%E5%B9%B6%E6%9F%A5%E9%9B%86/"/>
|
||
|
||
<category term="图" scheme="https://zyjblogs.cn/tags/%E5%9B%BE/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>「游戏」寻路算法之A Star算法原理及实现</title>
|
||
<link href="https://zyjblogs.cn/posts/0.html"/>
|
||
<id>https://zyjblogs.cn/posts/0.html</id>
|
||
<published>2024-03-18T16:00:00.000Z</published>
|
||
<updated>2024-03-19T06:48:25.562Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h2 id="「游戏」寻路算法之A-Star算法原理及实现"><a href="#「游戏」寻路算法之A-Star算法原理及实现" class="headerlink" title="「游戏」寻路算法之A Star算法原理及实现"></a>「游戏」寻路算法之A Star算法原理及实现</h2><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>自动寻路是在一些如MMORPG等类型游戏中常见的一种功能,其给了玩家良好的游戏体验,使得玩家在游戏过程中省去了大量游戏坐标点的记录以及长时间的键盘操作,不必记忆坐标,不必担心迷路,用最快捷的方法移动到指定地点。</p><p>寻路算法(自动寻路算法,下同),其实可以看作是一种路径查找算法以及图搜索算法,图搜索(Graph Search)算法是用于在图上进行一般性发现或显式地搜索的算法。这些算法在图上找到出路径,但没有期望这些路径是在计算意义上是最优的。</p><p>路径查找算法(Pathfinding)是建立在图搜索算法的基础上,它探索节点之间的路径,从一个节点开始,遍历关系,直到到达目的节点。这些算法用于识别图中的最优路由,算法可以用于诸如物流规划、最低成本呼叫或IP路由以及游戏模拟等用途。</p><p>常见的路径搜索算法和图搜索算法有:A<em>(A Star)算法、Dijkstra算法、广(深)度优先搜索、最佳优先搜索、Jump Point Search算法等,今天本文主要讲解的是A</em>(A Star)算法的原理以及实现。</p><h2 id="常见搜索算法"><a href="#常见搜索算法" class="headerlink" title="常见搜索算法"></a>常见搜索算法</h2><h3 id="Dijkstra算法"><a href="#Dijkstra算法" class="headerlink" title="Dijkstra算法"></a>Dijkstra算法</h3><p>迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止[^1]。</p><p>在游戏中(仅以2D为例),我们把某个场景的地图按照一定的规则划分成一个个的小格子,每个格子代表一个可用的坐标点,该坐标点有两种状态分别为:有效、无效。其中有效状态代表为该坐标点玩家可以正常通过,无效为该坐标点玩家无法到达,即当前坐标点可能存在阻挡物,如河流、山川、npc等,并且,以每个格子(以正方形为例)为中心可以分别向8个不同方向前进,在像每个方向前进时,所需的代价是不同的。</p><blockquote><p>举个例子(后续的所有移动代价皆以此计算)</p><p>当玩家向自身8个方向中的横轴以及纵轴进行移动时(即向上、下、左、右),移动代价为10(别问为啥为10,拍脑门决定的)。</p><p>当玩家向自身八个方向中的对角方向进行移动时(即左上、左下、右上、右下),移动代价为横(纵)轴移动的1.4倍(因为正方形的对角线是边长约1.4倍)。</p></blockquote><p>在使用该算法的时候需要选择一个周围8方向中,距离起点总移动代价最低的节点作为下一个要遍历的节点,一直到当前节点为终点或者无可用节点为止。那么此时就需要一个优先队列来保存当前节点的8方向中的可用节点、以方便的查找到移动代价最低的节点。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/MDnfl7nOm2iaBT42ibFHlrHngDKchicrKYEOjcqnsDQzmnpYMWw6ic1Wsul2IyKiacvACOTLWNzLibhs0ibdF2c8kSYsw/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><blockquote><p>左图为BFS算法右图为Dijkstra算法(图侵删)</p><p>上图对比了不考虑节点移动代价差异的广度优先搜索与考虑移动代价的Dijkstra算法的运行图</p></blockquote><p>应当注意,绝大多数的戴克斯特拉算法不能有效处理移动代价为负的图[^2]。</p><h3 id="最佳优先搜索算法(Best-First-Search)"><a href="#最佳优先搜索算法(Best-First-Search)" class="headerlink" title="最佳优先搜索算法(Best-First-Search)"></a>最佳优先搜索算法(Best-First-Search)</h3><p>最佳优先搜索算法是一种启发式搜索算法(Heuristic Algorithm),其基于广度优先搜索算法,不同点是其依赖于估价函数对将要遍历的节点进行估价,选择代价小的节点进行遍历,直到找到目标点为止。BFS算法不能保证找到的路径是一条最短路径,但是其计算过程相对于Dijkstra算法会快很多[^3]。</p><p>所谓的启发式搜索算法,就是针对游戏地图中的每一个位置节点进行评估,从而得到相对来说最优的位置,而后再从这个最优为止再次进行搜索,直到符合终止条件或者超出地图范围为止,这样可以省略大量无谓的搜索路径,提高了效率。在启发式搜索中,对位置的预估是十分重要的。因此就需要使用启发函数来进行位置预估。</p><p>这个算法和Dijkstra算法差不多,同样也使用一个优先队列,但此时以每个节点的移动代价作为优先级去比较,每次都选取移动代价最小的,因为此时距离终点越来越近,所以说这种算法称之为最佳优先(Best First)算法。</p><p>接下来看一下运行事例:</p><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/MDnfl7nOm2iaBT42ibFHlrHngDKchicrKYEwyJI0HkSOSPQ6yANGHicl0YJqiaK0xNvsH50vticUbEO8cmHpUCfQxEeQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><blockquote><p>图侵删</p><p>左侧为Dijkstra算法右侧为最佳优先搜索算法</p></blockquote><p>最佳优先搜索算法相对于Dijkstra算法的问题在哪里呢?看一下中途有阻挡的时候的情况:</p><p><img src="https://mmbiz.qpic.cn/mmbiz_gif/MDnfl7nOm2iaBT42ibFHlrHngDKchicrKYERDQ0sX2SM3XSwEYAhiaXvdUpt1vPMUTEVM5icfD8yN8bA0lMgcwszzLw/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1" alt="图片"></p><blockquote><p>图侵删</p><p>左侧为Dijkstra算法右侧为最佳优先搜索算法</p></blockquote><p>从上图中可以看出,当地图中存在障碍物的时候,最佳优先搜索算法并不能找到最短路径。</p><h3 id="A-Star-算法"><a href="#A-Star-算法" class="headerlink" title="A Star 算法"></a>A Star 算法</h3><p>A Star算法是一种在游戏开发过程中很常用的自动寻路算法。它有较好的性能和准确度。<strong>A*搜索算法</strong>(A* search algorithm)是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或网络游戏的BOT的移动计算上。该算法综合了最佳优先搜索和Dijkstra算法的优点:在进行启发式搜索提高算法效率的同时,可以保证找到一条基于启发函数的最优路径[^4]。</p><h3 id="启发公式"><a href="#启发公式" class="headerlink" title="启发公式"></a>启发公式</h3><p>A Star算法也是一种启发式搜索算法那么其与最佳优先搜索算法一样需要一个启发函数左右计算移动代价的方法,那么A Star的启发函数公式为:$$ f(n) = g(n)+h(n) $$</p><p>解释下这个公式的各项的含义:</p><p>•<strong>h(n)</strong>:从当前节点n到终点的预估的代价。•<strong>g(n)</strong>:从起始点到达当前节点n的代价。•<strong>f(n)</strong>:为当前节点n的综合的代价,在选择下一个节点的时候,其值越低,优先级就越高(因为代价小)。</p><p>关于启发函数中*h(n)<strong>公式的的选择,由于游戏地图中大部分都是被分为网格形式(即整个地图被拆分为多个正方形格子),那么此时就有3种可选的启发函数的</strong>h(n)*公式可用,分别为:</p><blockquote><p>曼哈顿距离:出租车几何或曼哈顿距离(Manhattan Distance)是由十九世纪的赫尔曼·闵可夫斯基所创词汇,是种使用在几何度量空间的几何学用语,用以标明两个点在标准坐标系上的绝对轴距总和。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/MDnfl7nOm2iaBT42ibFHlrHngDKchicrKYEmxQP9D2KfFHvxXchyfIEwPapq2Yic2l4lk8BS5OnLZuSwNy0pblkteA/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="图片"></p><p>曼哈顿距离一般用在在移动时,只允许朝上下左右四个方向移动的情况下使用。</p><p>在平面中X1点到X2点的距离为其公式为:$$ d(i,j)=|X1-X2|+|Y1-Y2| $$ 转换为程序语言如下,其中Cost为相邻两个节点移动所耗费的代价</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">func Manhattan(startX, startY, endX, endY float64) float64 {</span><br><span class="line"> dx := math.Abs(startX - endX)</span><br><span class="line"> dy := math.Abs(startY - endY)</span><br><span class="line"> return Cost * (dx + dy)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></blockquote><blockquote><p>对角线距离:如果你的地图允许对角线运动,则启发函数可以使用对角距离。它的计算方法如下:</p><p>其中HVCost是水平、垂直方向移动代价,SCost为倾斜方向移动代价</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">func Diagonal(startX, startY, endX, endY float64) float64 {</span><br><span class="line"> dx := math.Abs(startX - endX)</span><br><span class="line"> dy := math.Abs(startY - endY)</span><br><span class="line"> return HVCost*(dx+dy) + (SCost-2*HVCost)* math.Min(dx, dy)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></blockquote><blockquote><p>欧几里得距离:是一个通常采用的距离定义,指在m维空间中两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离),其公式为:$$ \sqrt[2]{ (x2−x1)^2+(y2−y1)^2} $$</p><p>该公式为点(x1,y1)与点(x2,y2)之间的欧氏距离</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">func Euclidean(startX, startY, endX, endY float64) float64 {</span><br><span class="line"> dx := math.Abs(startX - endX)</span><br><span class="line"> dy := math.Abs(startY - endY)</span><br><span class="line"> return D * math.Sqrt(dx * dx + dy * dy)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></blockquote><p>在此文章中我们选择对角距离公式来作为启发函数求*h(n)*的公式。</p><p>启发函数可能对算法造成的影响[^5]:</p><p>•在极端情况下,当启发函数h(n) 始终为0,则将由g(n) 决定节点的优先级,此时算法就退化成了Dijkstra算法。•如果h(n) 始终小于等于节点n到终点的代价,则A<em>算法保证一定能够找到最短路径。但是当h(n)的值越小,算法将遍历越多的节点,也就导致算法越慢。•如果h(n) 完全等于节点n到终点的代价,则A</em>算法将找到最佳路径,并且速度很快。可惜的是,并非所有场景下都能做到这一点。因为在没有达到终点之前,我们很难确切算出距离终点还有多远。•如果h(n)的值比节点n到终点的代价要大,则A*算法不能保证找到最短路径,不过此时会很快。•在另外一个极端情况下,如果h(n)相较于g(n)大很多,则此时只有h(n)产生效果,这也就变成了最佳优先搜索。</p><h3 id="A-Star算法执行过程"><a href="#A-Star算法执行过程" class="headerlink" title="A Star算法执行过程"></a>A Star算法执行过程</h3><p>在搜索过程中,A Star算法需要遍历周围8方向的8个节点,并将之有效的节点(即可通过的节点)计算*<em>f(n)*<em>后放入一个优先级队列中,以便下次循环可以直接取出优先级最高的节点继续遍历,此时我们把这个优先级队列称呼为**</em>*OpenList*<em><strong>,另外A Star算法中也需要一个容器用来存储已经遍历过的节点、以防止重复的遍历,那么此时我们把这个容器称之为</strong></em></em>CloseList****。</p><p>接下来本文讲按照步骤说明A Star算法每一步的执行过程:</p><p>1.对<em>*<em>OpenList*</em>*<em>和*<em><strong>CloseList</strong></em>进行初始化,作者采用小根堆来作为*</em>*<em>OpenList*<em><strong>的实现数据结构,采用HashMap作为</strong></em></em>CloseList*<em><strong>的数据结构,其中Key为节点坐标值Value为空结构体。2.首先确定Start节点以及End节点,而后将Start节点放入</strong></em>*OpenList*<em><strong>中。3.从</strong></em></em>OpenList****中取出一个节点Cur,如果Cur节点为End节点,则回溯当前节点结构中存储的父节点对象(像链表一样向前回溯)直到父节点对象为nil或者为Start节点,此时得到搜索到的路径,搜索结束返回。4.如果Cur节点非End节点则进行如下逻辑:</p><p>1.将当前X节点放入**<em>CloseList*</em>**中,如果在取出Cur节点时未做删除操作的话那么则从*<em><strong>OpenList</strong></em>中删除。2.遍历Cur节点其周围8方向的所有额邻居可用节点、可用节点的条件为:</p><p>1.是可通过的节点。2.未处于****CloseList****中。</p><p>3.计算当前的Cur节点的邻居可用节点,计算其**f(n)*<em>值,设置其父节点为Cur节点,随后将其加入至**</em>*OpenList****中。</p><p>5.循环执行3-4。直到找不到有效路径或找到End节点。</p><h3 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h3><p>本章代码采用Go1.16.2版本实现,主要展示的是搜索业务逻辑部分的代码。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line">// 优先队列</span><br><span class="line">type PriorityQueue []*Node</span><br><span class="line"></span><br><span class="line">func (receiver PriorityQueue) Len() int {</span><br><span class="line"> return len(receiver)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">func (receiver PriorityQueue) Less(i, j int) bool {</span><br><span class="line"> return receiver[i].Fn() < receiver[j].Fn()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">func (receiver PriorityQueue) Swap(i, j int) {</span><br><span class="line"> receiver[i], receiver[j] = receiver[j], receiver[i]</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">func (receiver *PriorityQueue) Push(x interface{}) {</span><br><span class="line"> *receiver = append(*receiver, x.(*Node))</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">func (receiver *PriorityQueue) Pop() interface{} {</span><br><span class="line"> index := len(*receiver)</span><br><span class="line"> v := (*receiver)[index-1]</span><br><span class="line"> *receiver = (*receiver)[:index-1]</span><br><span class="line"> return v</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 传入的是一个存放搜索路径的切片</span><br><span class="line">// *tools.Node 是节点指针</span><br><span class="line">func (receiver *FindAWay) doAStar(result *[]*tools.Node) {</span><br><span class="line"> for receiver.OpenQueue.Len() > 0 {</span><br><span class="line"> // 从 OpenList 里取出一个节点</span><br><span class="line"> node := receiver.openGet()</span><br><span class="line"> // 看看是不是终点</span><br><span class="line"> if receiver.end.Equal(node.Position()) {</span><br><span class="line"> receiver.statistics(node, result)</span><br><span class="line"> return</span><br><span class="line"> }</span><br><span class="line"> // 节点放入CloseList</span><br><span class="line"> receiver.closePut(node)</span><br><span class="line"> // 进行具体处理</span><br><span class="line"> receiver.azimuthProcessing(node)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">func (receiver *FindAWay) azimuthProcessing(node *tools.Node) {</span><br><span class="line"> // 遍历当前节点的8个方向</span><br><span class="line"> // tools.NodeFormula 是一个存有 计算8个方向坐标的函数数组</span><br><span class="line"> for azimuth, f := range tools.NodeFormula {</span><br><span class="line"> // Position 返回一个 存放了 XY字段的结构体代表节点坐标</span><br><span class="line"> nearNode := tools.GlobalMap.GetNode(f(node.Position()))</span><br><span class="line"> // 返回为nil代表节点超出边界</span><br><span class="line"> // EffectiveCoordinate() 代表当前邻居节点是否可用,即是否可以通过</span><br><span class="line"> if nearNode == nil || !nearNode.EffectiveCoordinate() {</span><br><span class="line"> continue</span><br><span class="line"> }</span><br><span class="line"> // 查看当前邻居节点是否处于 CloseList里</span><br><span class="line"> if _, ok := receiver.CloseMap[nearNode.Position()]; ok {</span><br><span class="line"> continue</span><br><span class="line"> }</span><br><span class="line"> // 查看当前邻居节点是否处于 OpenList里</span><br><span class="line"> // 这算是个优化,防止OpenList插入重复节点</span><br><span class="line"> if _, ok := receiver.OpenMap[nearNode.Position()]; !ok {</span><br><span class="line"> // 设置当前邻居节点的父节点为当前节点</span><br><span class="line"> nearNode.SetParent(node)</span><br><span class="line"> // 根据所处方位使用不同的移动代价计算Fn值</span><br><span class="line"></span><br><span class="line"> // 此时 HVCost =10 </span><br><span class="line"> // SCost = HVCost * 1.4</span><br><span class="line"> switch tools.Azimuth(azimuth) {</span><br><span class="line"> // 斜方计算</span><br><span class="line"> case tools.LeftUp, tools.LeftDown, tools.RightUp, tools.RightDown:</span><br><span class="line"> nearNode.CalcGn(tools.SCost).CalcHn(receiver.end.Position()).CalcFn()</span><br><span class="line"> // 垂直、水平方向计算</span><br><span class="line"> default:</span><br><span class="line"> nearNode.CalcGn(tools.HVCost).CalcHn(receiver.end.Position()).CalcFn()</span><br><span class="line"> }</span><br><span class="line"> // 将当前邻居节点放入OpenList中</span><br><span class="line"> receiver.openPut(nearNode)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="结果测试"><a href="#结果测试" class="headerlink" title="结果测试"></a>结果测试</h3><p>由于没有找到比较方便的实时现实库,所以就拿Excel做了一下,下面两个图为地图以及搜索后的结果图。</p><p>1为墙。</p><p>0为可通过的点。</p><p>5为得到的路径。</p><p>红色为起点</p><p>紫色为终点</p><p>绿色忘记删了,没有代表意义</p><p>下图为原地图:</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/MDnfl7nOm2iaBT42ibFHlrHngDKchicrKYEibAu0ZvesHNmWAnMHVxKcKA9nrKqYoNwwrAicYHzwYZjEXA5Rel8bG5A/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="图片"></p><p>下图为搜寻完毕的地图:<img src="https://mmbiz.qpic.cn/mmbiz_png/MDnfl7nOm2iaBT42ibFHlrHngDKchicrKYEjzoybMhqxXiaMJpzriaibUaQc0Bjia87c5pYEEzq9Hia2juJnjh6G8ibS9vg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="图片"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>A Star算法算是一个很简单的用于游戏中自动寻路的算法,但是其性能还有有些问题,主要问题在于其需要把每个节点的周围所有可用节点均插入**OpenList**中,因此相对来说性能损耗几乎都在这个*<em><strong>OpenList</strong></em>中,因此A Star算法有很多对其可以优化的算法。</p><p>日后作者将会写一篇关于针对A Star的优化算法即:JPS(jump point search)算法,该算法实际上是对A Star寻路算法的一个改进,A Star 算法在扩展节点时会把节点所有邻居都考虑进去,这样**<em>OpenList*</em>*<em>中点的数量会很多,搜索效率较慢。而JPS在A Star算法模型的基础之上,优化了搜索后继节点的操作。A Star的处理是把周边能搜索到的格子,加进OpenList,然后在OpenList中弹出最小值。JPS也是这样的操作,但相对于A Star来说,JPS操作*<em><strong>OpenList</strong></em>的次数很少,它会先用一种更高效的方法来搜索需要加进*</em>*<em>OpenList*<em><strong>的节点,从而减少</strong></em></em>OpenList****中节点的数量。</p><p>[^1]: Dijkstra算法 <a href="https://baike.baidu.com/item/%E8%BF%AA%E5%85%8B%E6%96%AF%E7%89%B9%E6%8B%89%E7%AE%97%E6%B3%95/23665989?fromtitle=Dijkstra%E7%AE%97%E6%B3%95&fromid=215612">https://baike.baidu.com/item/%E8%BF%AA%E5%85%8B%E6%96%AF%E7%89%B9%E6%8B%89%E7%AE%97%E6%B3%95/23665989?fromtitle=Dijkstra%E7%AE%97%E6%B3%95&fromid=215612</a> [^2]: Cormen, Thomas H.[1]; Leiserson, Charles E.[2]; Rivest, Ronald L.[3]; Stein, Clifford[4]. Section 24.3: Dijkstra’s algorithm. Introduction to Algorithms[5] Second. MIT Press[6] and McGraw–Hill[7]. 2001: 595–601. ISBN 0-262-03293-7[8]. [^3]: 最佳优先搜索算法(Best-First-Search) <a href="https://www.jianshu.com/p/9873372fe4b4">https://www.jianshu.com/p/9873372fe4b4</a> [^4]: A Star算法 <a href="https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95">https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95</a> [^5]: 路径规划之 A* 算法 <a href="https://paul.pub/a-star-algorithm/#id-dijkstra%E7%AE%97%E6%B3%95">https://paul.pub/a-star-algorithm/#id-dijkstra%E7%AE%97%E6%B3%95</a></p><h3 id="References"><a href="#References" class="headerlink" title="References"></a>References</h3>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h2 id="「游戏」寻路算法之A-Star算法原理及实现"><a href="#「游戏」寻路算法之A-Star算法原理及实现" class="headerlink" title="「游戏」寻路算法之A Star算法原理及实现"></a>「游戏」寻路算法之A Star算法原理及</summary>
|
||
|
||
|
||
|
||
|
||
<category term="算法" scheme="https://zyjblogs.cn/categories/%E7%AE%97%E6%B3%95/"/>
|
||
|
||
|
||
<category term="图" scheme="https://zyjblogs.cn/tags/%E5%9B%BE/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>数据结构简介</title>
|
||
<link href="https://zyjblogs.cn/posts/b8928e0e.html"/>
|
||
<id>https://zyjblogs.cn/posts/b8928e0e.html</id>
|
||
<published>2023-10-09T16:00:00.000Z</published>
|
||
<updated>2023-10-12T09:42:45.328Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="数据结构简介"><a href="#数据结构简介" class="headerlink" title="数据结构简介"></a>数据结构简介</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>数据结构是为实现对计算机数据有效使用的各种数据组织形式,服务于各类计算机操作。不同的数据结构具有各自对应的适用场景,旨在降低各种算法计算的时间与空间复杂度,达到最佳的任务执行效率。</p><p>如下图所示,常见的数据结构可分为「线性数据结构」与「非线性数据结构」,具体为:「数组」、「链表」、「栈」、「队列」、「树」、「图」、「散列表」、「堆」。</p><p><img src="/posts/b8928e0e/images/1599638810-SZDwfK-Picture1.png" alt="Picture1.png"></p><p>从零开始学习算法的同学对数据结构的使用方法可能尚不熟悉,本节将初步介绍各数据结构的基本特点,与 Python3 , Java , C++ 语言中各数据结构的初始化与构建方法。</p><blockquote><p>代码运行可使用本地 IDE 或 力扣 PlayGround 。</p></blockquote><h3 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h3><p>数组是将相同类型的元素存储于连续内存空间的数据结构,其长度不可变。</p><p>如下图所示,构建此数组需要在初始化时给定长度,并对数组每个索引元素赋值,代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化一个长度为 5 的数组 array</span></span><br><span class="line"><span class="type">int</span>[] array = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">5</span>];</span><br><span class="line"><span class="comment">// 元素赋值</span></span><br><span class="line">array[<span class="number">0</span>] = <span class="number">2</span>;</span><br><span class="line">array[<span class="number">1</span>] = <span class="number">3</span>;</span><br><span class="line">array[<span class="number">2</span>] = <span class="number">1</span>;</span><br><span class="line">array[<span class="number">3</span>] = <span class="number">0</span>;</span><br><span class="line">array[<span class="number">4</span>] = <span class="number">2</span>;</span><br></pre></td></tr></table></figure><p>或者可以使用直接赋值的初始化方式,代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span>[] array = {<span class="number">2</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">2</span>};</span><br></pre></td></tr></table></figure><p><img src="/posts/b8928e0e/images/1599587176-JAxwpf-Picture2.png" alt="Picture2.png"></p><p>「可变数组」是经常使用的数据结构,其基于数组和扩容机制实现,相比普通数组更加灵活。常用操作有:访问元素、添加元素、删除元素。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化可变数组</span></span><br><span class="line">List<Integer> array = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 向尾部添加元素</span></span><br><span class="line">array.add(<span class="number">2</span>);</span><br><span class="line">array.add(<span class="number">3</span>);</span><br><span class="line">array.add(<span class="number">1</span>);</span><br><span class="line">array.add(<span class="number">0</span>);</span><br><span class="line">array.add(<span class="number">2</span>);</span><br></pre></td></tr></table></figure><h3 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h3><p>链表以节点为单位,每个元素都是一个独立对象,在内存空间的存储是非连续的。链表的节点对象具有两个成员变量:「值 val」,「后继节点引用 next」 。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ListNode</span> {</span><br><span class="line"> <span class="type">int</span> val; <span class="comment">// 节点值</span></span><br><span class="line"> ListNode next; <span class="comment">// 后继节点引用</span></span><br><span class="line"> ListNode(<span class="type">int</span> x) { val = x; }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如下图所示,建立此链表需要实例化每个节点,并构建各节点的引用指向。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 实例化节点</span></span><br><span class="line"><span class="type">ListNode</span> <span class="variable">n1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>(<span class="number">4</span>); <span class="comment">// 节点 head</span></span><br><span class="line"><span class="type">ListNode</span> <span class="variable">n2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>(<span class="number">5</span>);</span><br><span class="line"><span class="type">ListNode</span> <span class="variable">n3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构建引用指向</span></span><br><span class="line">n1.next = n2;</span><br><span class="line">n2.next = n3;</span><br></pre></td></tr></table></figure><p><img src="/posts/b8928e0e/images/1599578767-zgLjYw-Picture3.png" alt="Picture3.png"></p><h3 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h3><p>栈是一种具有 「先入后出」 特点的抽象数据结构,可使用数组或链表实现。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Stack<Integer> stack = <span class="keyword">new</span> <span class="title class_">Stack</span><>();</span><br></pre></td></tr></table></figure><p>如下图所示,通过常用操作「入栈 push()」,「出栈 pop()」,展示了栈的先入后出特性。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">stack.push(<span class="number">1</span>); <span class="comment">// 元素 1 入栈</span></span><br><span class="line">stack.push(<span class="number">2</span>); <span class="comment">// 元素 2 入栈</span></span><br><span class="line">stack.pop(); <span class="comment">// 出栈 -> 元素 2</span></span><br><span class="line">stack.pop(); <span class="comment">// 出栈 -> 元素 1</span></span><br></pre></td></tr></table></figure><p><img src="/posts/b8928e0e/images/1599578767-ZifMEX-Picture4.png" alt="Picture4.png"></p><blockquote><p>注意:通常情况下,不推荐使用 Java 的 Vector 以及其子类 Stack ,而一般将 LinkedList 作为栈来使用。详细说明请见:Stack,ArrayDeque,LinkedList 的区别 。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">LinkedList<Integer> stack = <span class="keyword">new</span> <span class="title class_">LinkedList</span><>();</span><br><span class="line"></span><br><span class="line">stack.addLast(<span class="number">1</span>); <span class="comment">// 元素 1 入栈</span></span><br><span class="line">stack.addLast(<span class="number">2</span>); <span class="comment">// 元素 2 入栈</span></span><br><span class="line">stack.removeLast(); <span class="comment">// 出栈 -> 元素 2</span></span><br><span class="line">stack.removeLast(); <span class="comment">// 出栈 -> 元素 1</span></span><br></pre></td></tr></table></figure><h3 id="队列"><a href="#队列" class="headerlink" title="队列"></a>队列</h3><p>队列是一种具有 「先入先出」 特点的抽象数据结构,可使用链表实现。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Queue<Integer> queue = <span class="keyword">new</span> <span class="title class_">LinkedList</span><>();</span><br></pre></td></tr></table></figure><p>如下图所示,通过常用操作「入队 push()」,「出队 pop()」,展示了队列的先入先出特性。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">queue.offer(<span class="number">1</span>); <span class="comment">// 元素 1 入队</span></span><br><span class="line">queue.offer(<span class="number">2</span>); <span class="comment">// 元素 2 入队</span></span><br><span class="line">queue.poll(); <span class="comment">// 出队 -> 元素 1</span></span><br><span class="line">queue.poll(); <span class="comment">// 出队 -> 元素 2</span></span><br></pre></td></tr></table></figure><p><img src="/posts/b8928e0e/images/1599588416-Majmwh-Picture5.png" alt="Picture5.png"></p><h3 id="树"><a href="#树" class="headerlink" title="树"></a>树</h3><p>树是一种非线性数据结构,根据子节点数量可分为 「二叉树」 和 「多叉树」,最顶层的节点称为「根节点 root」。以二叉树为例,每个节点包含三个成员变量:「值 val」、「左子节点 left」、「右子节点 right」 。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TreeNode</span> {</span><br><span class="line"> <span class="type">int</span> val; <span class="comment">// 节点值</span></span><br><span class="line"> TreeNode left; <span class="comment">// 左子节点</span></span><br><span class="line"> TreeNode right; <span class="comment">// 右子节点</span></span><br><span class="line"> TreeNode(<span class="type">int</span> x) { val = x; }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如下图所示,建立此二叉树需要实例化每个节点,并构建各节点的引用指向。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化节点</span></span><br><span class="line"><span class="type">TreeNode</span> <span class="variable">n1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TreeNode</span>(<span class="number">3</span>); <span class="comment">// 根节点 root</span></span><br><span class="line"><span class="type">TreeNode</span> <span class="variable">n2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TreeNode</span>(<span class="number">4</span>);</span><br><span class="line"><span class="type">TreeNode</span> <span class="variable">n3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TreeNode</span>(<span class="number">5</span>);</span><br><span class="line"><span class="type">TreeNode</span> <span class="variable">n4</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TreeNode</span>(<span class="number">1</span>);</span><br><span class="line"><span class="type">TreeNode</span> <span class="variable">n5</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TreeNode</span>(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 构建引用指向</span></span><br><span class="line">n1.left = n2;</span><br><span class="line">n1.right = n3;</span><br><span class="line">n2.left = n4;</span><br><span class="line">n2.right = n5;</span><br></pre></td></tr></table></figure><p><img src="/posts/b8928e0e/images/1599579136-bBARpC-Picture6.png" alt="Picture6.png"></p><h3 id="图"><a href="#图" class="headerlink" title="图"></a>图</h3><p>图是一种非线性数据结构,由「节点(顶点)vertex」和「边 edge」组成,每条边连接一对顶点。根据边的方向有无,图可分为「有向图」和「无向图」。</p><p>如下图所示,此无向图的顶点和边集合分别为:</p><ul><li>顶点集合: vertices = {1, 2, 3, 4, 5}</li><li>边集合: edges = {(1, 2), (1, 3), (1, 4), (1, 5), (2, 4), (3, 5), (4, 5)}</li></ul><p><img src="/posts/b8928e0e/images/1599579136-Fxseew-Picture7.png" alt="Picture7.png"></p><h3 id="散列表"><a href="#散列表" class="headerlink" title="散列表"></a>散列表</h3><p>散列表是一种非线性数据结构,通过利用 Hash 函数将指定的「键 key」映射至对应的「值 value」,以实现高效的元素查找。</p><blockquote><p>设想一个简单场景:小力、小特、小扣的学号分别为 10001, 10002, 10003 。<br>现需求从「姓名」查找「学号」。</p></blockquote><p>则可通过建立姓名为 key ,学号为 value 的散列表实现此需求,代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化散列表</span></span><br><span class="line">Map<String, Integer> dic = <span class="keyword">new</span> <span class="title class_">HashMap</span><>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加 key -> value 键值对</span></span><br><span class="line">dic.put(<span class="string">"小力"</span>, <span class="number">10001</span>);</span><br><span class="line">dic.put(<span class="string">"小特"</span>, <span class="number">10002</span>);</span><br><span class="line">dic.put(<span class="string">"小扣"</span>, <span class="number">10003</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从姓名查找学号</span></span><br><span class="line">dic.get(<span class="string">"小力"</span>); <span class="comment">// -> 10001</span></span><br><span class="line">dic.get(<span class="string">"小特"</span>); <span class="comment">// -> 10002</span></span><br><span class="line">dic.get(<span class="string">"小扣"</span>); <span class="comment">// -> 10003</span></span><br></pre></td></tr></table></figure><p><img src="/posts/b8928e0e/images/1599811794-ruXMOV-Picture8.png" alt="Picture8.png"></p><h3 id="自行设计-Hash-函数:"><a href="#自行设计-Hash-函数:" class="headerlink" title="自行设计 Hash 函数:"></a>自行设计 Hash 函数:</h3><blockquote><p>假设需求:从「学号」查找「姓名」。</p></blockquote><p>将三人的姓名存储至以下数组中,则各姓名在数组中的索引分别为 0, 1, 2 。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String[] names = { <span class="string">"小力"</span>, <span class="string">"小特"</span>, <span class="string">"小扣"</span> };</span><br></pre></td></tr></table></figure><p>此时,我们构造一个简单的 Hash 函数( % 为取余符号 ),公式和封装函数如下所示:</p><blockquote><p>hash(key)=(key−1)%10000</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">hash</span><span class="params">(<span class="type">int</span> id)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> (id - <span class="number">1</span>) % <span class="number">10000</span>;</span><br><span class="line"> <span class="keyword">return</span> index;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>则我们构建了以学号为 key 、姓名对应的数组索引为 value 的散列表。利用此 Hash 函数,则可在 O(1)O(1) 时间复杂度下通过学号查找到对应姓名,即:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">names[hash(<span class="number">10001</span>)] <span class="comment">// 小力</span></span><br><span class="line">names[hash(<span class="number">10002</span>)] <span class="comment">// 小特</span></span><br><span class="line">names[hash(<span class="number">10003</span>)] <span class="comment">// 小扣</span></span><br></pre></td></tr></table></figure><p><img src="/posts/b8928e0e/images/1599811794-NfbpfW-Picture8-1.png" alt="Picture8-1.png"></p><p>以上设计只适用于此示例,实际的 Hash 函数需保证低碰撞率、 高鲁棒性等,以适用于各类数据和场景。</p><h3 id="堆:"><a href="#堆:" class="headerlink" title="堆:"></a>堆:</h3><p>堆是一种基于「完全二叉树」的数据结构,可使用数组实现。以堆为原理的排序算法称为「堆排序」,基于堆实现的数据结构为「优先队列」。堆分为「大顶堆」和「小顶堆」,大(小)顶堆:任意节点的值不大于(小于)其父节点的值。</p><blockquote><p>完全二叉树定义: 设二叉树深度为 kk ,若二叉树除第 kk 层外的其它各层(第 11 至 k-1k−1 层)的节点达到最大个数,且处于第 kk 层的节点都连续集中在最左边,则称此二叉树为完全二叉树。</p></blockquote><p>如下图所示,为包含 1, 4, 2, 6, 8 元素的小顶堆。将堆(完全二叉树)中的结点按层编号,即可映射到右边的数组存储形式。</p><p><img src="/posts/b8928e0e/images/1599584901-xoiGEQ-Picture9.png" alt="Picture9.png"></p><p>通过使用「优先队列」的「压入 push()」和「弹出 pop()」操作,即可完成堆排序,实现代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化小顶堆</span></span><br><span class="line">Queue<Integer> heap = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span><>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 元素入堆</span></span><br><span class="line">heap.add(<span class="number">1</span>);</span><br><span class="line">heap.add(<span class="number">4</span>);</span><br><span class="line">heap.add(<span class="number">2</span>);</span><br><span class="line">heap.add(<span class="number">6</span>);</span><br><span class="line">heap.add(<span class="number">8</span>);</span><br><span class="line"><span class="comment">// 元素出堆(从小到大)</span></span><br><span class="line">heap.poll(); <span class="comment">// -> 1</span></span><br><span class="line">heap.poll(); <span class="comment">// -> 2</span></span><br><span class="line">heap.poll(); <span class="comment">// -> 4</span></span><br><span class="line">heap.poll(); <span class="comment">// -> 6</span></span><br><span class="line">heap.poll(); <span class="comment">// -> 8</span></span><br></pre></td></tr></table></figure>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="数据结构简介"><a href="#数据结构简介" class="headerlink" title="数据结构简介"></a>数据结构简介</h1><h3 id="前言"><a href="#前言" class="headerlink" title="前言"><</summary>
|
||
|
||
|
||
|
||
|
||
<category term="数据结构" scheme="https://zyjblogs.cn/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
|
||
|
||
|
||
<category term="数据结构" scheme="https://zyjblogs.cn/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>二分查找模版</title>
|
||
<link href="https://zyjblogs.cn/posts/6077.html"/>
|
||
<id>https://zyjblogs.cn/posts/6077.html</id>
|
||
<published>2023-10-08T16:00:00.000Z</published>
|
||
<updated>2023-10-12T09:43:22.651Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="二分查找模版"><a href="#二分查找模版" class="headerlink" title="二分查找模版"></a>二分查找模版</h1><h2 id="模版"><a href="#模版" class="headerlink" title="模版"></a>模版</h2><h3 id="第⼀个,最基本的⼆分查找算法:"><a href="#第⼀个,最基本的⼆分查找算法:" class="headerlink" title="第⼀个,最基本的⼆分查找算法:"></a>第⼀个,最基本的⼆分查找算法:</h3><blockquote><p>因为我们初始化 right = nums.length - 1</p><p>所以决定了我们的「搜索区间」是 [left, right]</p><p>所以决定了 while (left <= right)</p><p>同时也决定了 left = mid+1 和 right = mid-1</p><p>因为我们只需找到⼀个 target 的索引即可</p><p>所以当 nums[mid] == target 时可以⽴即返回</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">int</span> <span class="title function_">binary_search</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">while</span> (left <= right) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">if</span> (nums[mid] < target) {</span><br><span class="line"> left = mid + <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] > target) {</span><br><span class="line"> right = mid - <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] == target) {</span><br><span class="line"> <span class="keyword">return</span> mid;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h3 id="第⼆个,寻找左侧边界的⼆分查找:"><a href="#第⼆个,寻找左侧边界的⼆分查找:" class="headerlink" title="第⼆个,寻找左侧边界的⼆分查找:"></a>第⼆个,寻找左侧边界的⼆分查找:</h3><blockquote><p>因为我们初始化 right = nums.length</p><p>所以决定了我们的「搜索区间」是 [left, right)</p><p>所以决定了 while (left < right)</p><p>同时也决定了 left = mid + 1 和 right = mid</p><p>因为我们需找到 target 的最左侧索引</p><p>所以当 nums[mid] == target 时不要⽴即返回</p><p>⽽要收紧右侧边界以锁定左侧边界</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">int</span> <span class="title function_">left_bound</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">while</span> (left <= right) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">if</span> (nums[mid] < target) {</span><br><span class="line"> left = mid + <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] > target) {</span><br><span class="line"> right = mid - <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] == target) {</span><br><span class="line"> <span class="comment">// 别返回,锁定左侧边界</span></span><br><span class="line"> right = mid - <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 最后要检查 left 越界的情况</span></span><br><span class="line"> <span class="keyword">if</span> (left >= nums.length || nums[left] != target) <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">return</span> left;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h3 id="第三个,寻找右侧边界的⼆分查找:"><a href="#第三个,寻找右侧边界的⼆分查找:" class="headerlink" title="第三个,寻找右侧边界的⼆分查找:"></a>第三个,寻找右侧边界的⼆分查找:</h3><blockquote><p>因为我们初始化 right = nums.length</p><p>所以决定了我们的「搜索区间」是 [left, right)</p><p>所以决定了 while (left < right)</p><p>同时也决定了 left = mid + 1 和 right = mid</p><p>因为我们需找到 target 的最右侧索引</p><p>所以当 nums[mid] == target 时不要⽴即返回</p><p>⽽要收紧左侧边界以锁定右侧边界</p><p>⼜因为收紧左侧边界时必须 left = mid + 1</p><p>所以最后⽆论返回 left 还是 right,必须减⼀</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">int</span> <span class="title function_">right_bound</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">while</span> (left <= right) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line"> <span class="keyword">if</span> (nums[mid] < target) {</span><br><span class="line"> left = mid + <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] > target) {</span><br><span class="line"> right = mid - <span class="number">1</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] == target) {</span><br><span class="line"> <span class="comment">// 别返回,锁定右侧边界</span></span><br><span class="line"> left = mid + <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 最后要检查 right 越界的情况</span></span><br><span class="line"> <span class="keyword">if</span> (right < <span class="number">0</span> || nums[right] != target) <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">return</span> right;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><blockquote><p>1、分析⼆分查找代码时,不要出现 else,全部展开成 else if ⽅便理解。</p><p>2、注意「搜索区间」和 while 的终⽌条件,如果存在漏掉的元素,记得在</p><p>最后检查。</p><p>3、如需定义左闭右开的「搜索区间」搜索左右边界,只要在 nums[mid] ==</p><p>target 时做修改即可,搜索右侧时需要减⼀。</p><p>4、如果将「搜索区间」全都统⼀成两端都闭,好记,只要稍改 nums[mid]</p><p>== target 条件处的代码和返回的逻辑即可,</p></blockquote>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="二分查找模版"><a href="#二分查找模版" class="headerlink" title="二分查找模版"></a>二分查找模版</h1><h2 id="模版"><a href="#模版" class="headerlink" title="模版"><</summary>
|
||
|
||
|
||
|
||
|
||
<category term="算法" scheme="https://zyjblogs.cn/categories/%E7%AE%97%E6%B3%95/"/>
|
||
|
||
|
||
<category term="算法" scheme="https://zyjblogs.cn/tags/%E7%AE%97%E6%B3%95/"/>
|
||
|
||
<category term="查找" scheme="https://zyjblogs.cn/tags/%E6%9F%A5%E6%89%BE/"/>
|
||
|
||
<category term="树" scheme="https://zyjblogs.cn/tags/%E6%A0%91/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>C++ 使用 chrono 库处理日期和时间</title>
|
||
<link href="https://zyjblogs.cn/posts/59243.html"/>
|
||
<id>https://zyjblogs.cn/posts/59243.html</id>
|
||
<published>2022-02-06T16:00:00.000Z</published>
|
||
<updated>2023-10-09T02:49:00.539Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="C-使用-chrono-库处理日期和时间"><a href="#C-使用-chrono-库处理日期和时间" class="headerlink" title="C++ 使用 chrono 库处理日期和时间"></a>C++ 使用 chrono 库处理日期和时间</h1><p>C++11 中提供了日期和时间相关的库 chrono,通过 chrono 库可以很方便地处理日期和时间,为程序的开发提供了便利。chrono 库主要包含三种类型的类:时间间隔duration、时钟clocks、时间点time point。</p><h2 id="1-时间间隔-duration"><a href="#1-时间间隔-duration" class="headerlink" title="1. 时间间隔 duration"></a>1. 时间间隔 duration</h2><h3 id="1-1-常用类成员"><a href="#1-1-常用类成员" class="headerlink" title="1.1 常用类成员"></a>1.1 常用类成员</h3><p>duration表示一段时间间隔,用来记录时间长度,可以表示几秒、几分钟、几个小时的时间间隔。duration 的原型如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义于头文件 <chrono></span></span><br><span class="line"><span class="keyword">template</span><</span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">Rep</span>,</span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">Period</span> = std::ratio<<span class="number">1</span>></span><br><span class="line">> <span class="keyword">class</span> duration;</span><br></pre></td></tr></table></figure><p>ratio 类表示每个时钟周期的秒数,其中第一个模板参数 Num 代表分子,Denom 代表分母,该分母值默认为 1,因此,ratio 代表的是一个分子除以分母的数值,比如:<code>ratio<2></code> 代表一个时钟周期是 2 秒,<code>ratio<60 ></code> 代表一分钟,<code>ratio<60*60 ></code> 代表一个小时,<code>ratio<60*60*24 ></code> 代表一天。而 <code>ratio<1,1000 ></code> 代表的是 <code>1/1000</code> 秒,也就是 1 毫秒,<code>ratio<1,1000000 ></code> 代表一微秒,<code>ratio<1,1000000000 ></code> 代表一纳秒。</p><blockquote><p>为了方便使用,在标准库中定义了一些常用的时间间隔,比如:时、分、秒、毫秒、微秒、纳秒,它们都位于 chrono 命名空间下,定义如下:</p></blockquote><table><thead><tr><th align="left">类型</th><th align="center">定义</th></tr></thead><tbody><tr><td align="left">纳秒:<code>std::chrono::nanoseconds</code></td><td align="center"><code>duration<Rep</code><em>/</em> 至少 64 位的有符号整数类型 <em>/</em>, <code>std::nano></code></td></tr><tr><td align="left">微秒:<code>std::chrono::microseconds</code></td><td align="center"><code>duration<Rep</code><em>/</em> 至少 55 位的有符号整数类型 <em>/</em>, <code>std::micro></code></td></tr><tr><td align="left">毫秒:<code>std::chrono::milliseconds</code></td><td align="center"><code>duration<Rep</code><em>/</em> 至少 45 位的有符号整数类型 <em>/</em>, <code>std::milli></code></td></tr><tr><td align="left">秒:<code>std::chrono::seconds</code></td><td align="center"><code>duration<Rep</code><em>/</em> 至少 35 位的有符号整数类型 <em>/</em>></td></tr><tr><td align="left">分钟:<code>std::chrono::minutes</code></td><td align="center"><code>duration<Rep</code><em>/</em> 至少 29 位的有符号整数类型 <em>/</em>, <code>std::ratio<60>></code></td></tr><tr><td align="left">小时:<code>std::chrono::hours</code></td><td align="center"><code>duration<Rep</code><em>/</em> 至少 23 位的有符号整数类型 <em>/</em>, <code>std::ratio<3600>></code></td></tr></tbody></table><p><strong>注意</strong>:到 hours 为止的每个预定义时长类型至少涵盖 ±292 年的范围。</p><blockquote><p>duration 类的构造函数原型如下:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 拷贝构造函数</span></span><br><span class="line"><span class="built_in">duration</span>( <span class="type">const</span> duration& ) = <span class="keyword">default</span>;</span><br><span class="line"><span class="comment">// 2. 通过指定时钟周期的类型来构造对象</span></span><br><span class="line"><span class="function"><span class="keyword">template</span>< <span class="keyword">class</span> Rep2 ></span></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="keyword">explicit</span> <span class="title">duration</span><span class="params">( <span class="type">const</span> Rep2& r )</span></span>;</span><br><span class="line"><span class="comment">// 3. 通过指定时钟周期类型,和时钟周期长度来构造对象</span></span><br><span class="line"><span class="function"><span class="keyword">template</span>< <span class="keyword">class</span> Rep2, <span class="keyword">class</span> Period2 ></span></span><br><span class="line"><span class="function"><span class="keyword">constexpr</span> <span class="title">duration</span><span class="params">( <span class="type">const</span> duration<Rep2,Period2>& d )</span></span>;</span><br></pre></td></tr></table></figure><blockquote><p>为了更加方便的进行 duration 对象之间的操作,类内部进行了操作符重载:</p></blockquote><table><thead><tr><th align="left">操作符重载</th><th align="center">描述</th></tr></thead><tbody><tr><td align="left">operator=</td><td align="center">赋值内容 (公开成员函数)</td></tr><tr><td align="left">operator+ operator-</td><td align="center">赋值内容 (公开成员函数)</td></tr><tr><td align="left">operator++ operator++(int) operator– operator–(int)</td><td align="center">递增或递减周期计数 (公开成员函数)</td></tr><tr><td align="left">operator+= operator-= operator*= operator/= operator%=</td><td align="center">实现二个时长间的复合赋值 (公开成员函数)</td></tr></tbody></table><blockquote><p>duration 类还提供了获取时间间隔的时钟周期数的方法 count (),函数原型如下:</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">constexpr rep count() const;</span><br></pre></td></tr></table></figure><h3 id="1-2-类的使用"><a href="#1-2-类的使用" class="headerlink" title="1.2 类的使用"></a>1.2 类的使用</h3><blockquote><p>通过构造函数构造事件间隔对象示例代码如下:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><chrono></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="function">chrono::hours <span class="title">h</span><span class="params">(<span class="number">1</span>)</span></span>; <span class="comment">// 一小时</span></span><br><span class="line"> chrono::milliseconds ms{ <span class="number">3</span> }; <span class="comment">// 3 毫秒</span></span><br><span class="line"> chrono::duration<<span class="type">int</span>, ratio<<span class="number">1000</span>>> <span class="built_in">ks</span>(<span class="number">3</span>); <span class="comment">// 3000 秒</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// chrono::duration<int, ratio<1000>> d3(3.5); // error</span></span><br><span class="line"> <span class="function">chrono::duration<<span class="type">double</span>> <span class="title">dd</span><span class="params">(<span class="number">6.6</span>)</span></span>; <span class="comment">// 6.6 秒</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 使用小数表示时钟周期的次数</span></span><br><span class="line"> chrono::duration<<span class="type">double</span>, std::ratio<<span class="number">1</span>, <span class="number">30</span>>> <span class="built_in">hz</span>(<span class="number">3.5</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>h(1) 时钟周期为 1 小时,共有 1 个时钟周期,所以 h 表示的时间间隔为 1 小时</li><li>ms(3) 时钟周期为 1 毫秒,共有 3 个时钟周期,所以 ms 表示的时间间隔为 3 毫秒</li><li>ks(3) 时钟周期为 1000 秒,一共有三个时钟周期,所以 ks 表示的时间间隔为 3000 秒</li><li>d3(3.5) 时钟周期为 1000 秒,时钟周期数量只能用整形来表示,但是此处指定的是浮点数,因此语法错误</li><li>dd(6.6) 时钟周期为默认的 1 秒,共有 6.6 个时钟周期,所以 dd 表示的时间间隔为 6.6 秒</li><li>hz(3.5) 时钟周期为 1/30 秒,共有 3.5 个时钟周期,所以 hz 表示的时间间隔为 1/30*3.5 秒</li></ul><p>chrono 库中根据 duration 类封装了不同长度的时钟周期(也可以自定义),基于这个时钟周期再进行周期次数的设置就可以得到总的时间间隔了(时钟周期 * 周期次数 = 总的时间间隔)。</p><p>示例代码如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><chrono></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> std::chrono::milliseconds ms{<span class="number">3</span>}; <span class="comment">// 3 毫秒</span></span><br><span class="line"> std::chrono::microseconds us = <span class="number">2</span>*ms; <span class="comment">// 6000 微秒</span></span><br><span class="line"> <span class="comment">// 时间间隔周期为 1/30 秒</span></span><br><span class="line"> std::chrono::duration<<span class="type">double</span>, std::ratio<<span class="number">1</span>, <span class="number">30</span>>> <span class="built_in">hz</span>(<span class="number">3.5</span>);</span><br><span class="line"></span><br><span class="line"> std::cout << <span class="string">"3 ms duration has "</span> << ms.<span class="built_in">count</span>() << <span class="string">" ticks\n"</span></span><br><span class="line"> << <span class="string">"6000 us duration has "</span> << us.<span class="built_in">count</span>() << <span class="string">" ticks\n"</span></span><br><span class="line"> << <span class="string">"3.5 hz duration has "</span> << hz.<span class="built_in">count</span>() << <span class="string">" ticks\n"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>输出的结果为:</p><figure class="highlight bash"><table><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">3 ms duration has 3 ticks</span><br><span class="line">6000 us duration has 6000 ticks</span><br><span class="line">3.5 hz duration has 3.5 ticks</span><br></pre></td></tr></table></figure><ul><li>ms 时间单位为毫秒,初始化操作 ms{3} 表示时间间隔为 3 毫秒,一共有 3 个时间周期,每个周期为 1 毫秒</li><li>us 时间单位为微秒,初始化操作 2*ms 表示时间间隔为 6000 微秒,一共有 6000 个时间周期,每个周期为 1 微秒</li><li>hz 时间单位为秒,初始化操作 hz(3.5) 表示时间间隔为 1/30*3.5 秒,一共有 3.5 个时间周期,每个周期为 1/30 秒</li></ul><p>由于在 duration 类内部做了操作符重载,因此时间间隔之间可以直接进行算术运算,比如我们要计算两个时间间隔的差值,就可以在代码中做如下处理:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><chrono></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="function">chrono::minutes <span class="title">t1</span><span class="params">(<span class="number">10</span>)</span></span>;</span><br><span class="line"> <span class="function">chrono::seconds <span class="title">t2</span><span class="params">(<span class="number">60</span>)</span></span>;</span><br><span class="line"> chrono::seconds t3 = t1 - t2;</span><br><span class="line"> cout << t3.<span class="built_in">count</span>() << <span class="string">" second"</span> << endl;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>程序输出的结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">540 second</span><br></pre></td></tr></table></figure><p>在上面的测试程序中,t1 代表 10 分钟,t2 代表 60 秒,t3 是 t1 减去 t2,也就是 60*10-60=540,这个 540 表示的时钟周期,每个时钟周期是 1 秒,因此两个时间间隔之间的差值为 540 秒。</p><blockquote><p>注意事项:duration 的加减运算有一定的规则,当两个 duration 时钟周期不相同的时候,会先统一成一种时钟,然后再进行算术运算,统一的规则如下:假设有 ratio<x1,y1> 和 ratio<x2,y2 > 两个时钟周期,首先需要求出 x1,x2 的最大公约数 X,然后求出 y1,y2 的最小公倍数 Y,统一之后的时钟周期 ratio 为 ratio<X,Y>。</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><chrono></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> chrono::duration<<span class="type">double</span>, ratio<<span class="number">9</span>, <span class="number">7</span>>> <span class="built_in">d1</span>(<span class="number">3</span>);</span><br><span class="line"> chrono::duration<<span class="type">double</span>, ratio<<span class="number">6</span>, <span class="number">5</span>>> <span class="built_in">d2</span>(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">// d1 和 d2 统一之后的时钟周期</span></span><br><span class="line"> chrono::duration<<span class="type">double</span>, ratio<<span class="number">3</span>, <span class="number">35</span>>> d3 = d1 - d2;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>对于分子 6,、9 最大公约数为 3,对于分母 7、5 最小公倍数为 35,因此推导出的时钟周期为 ratio<3,35></p><h3 id="2-时间点-time-point"><a href="#2-时间点-time-point" class="headerlink" title="2. 时间点 time point"></a>2. 时间点 time point</h3><p>chrono 库中提供了一个表示时间点的类 time_point,该类的定义如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义于头文件 <chrono></span></span><br><span class="line"><span class="keyword">template</span><</span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">Clock</span>,</span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">Duration</span> = <span class="keyword">typename</span> Clock::duration</span><br><span class="line">> <span class="keyword">class</span> time_point;</span><br></pre></td></tr></table></figure><p>它被实现成如同存储一个 Duration 类型的自 Clock 的纪元起始开始的时间间隔的值,通过这个类最终可以得到时间中的某一个时间点。</p><ul><li>Clock:此时间点在此时钟上计量</li><li>Duration:用于计量从纪元起时间的 std::chrono::duration 类型</li></ul><blockquote><p>time_point 类的构造函数原型如下:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 构造一个以新纪元(epoch,即:1970.1.1)作为值的对象,需要和时钟类一起使用,不能单独使用该无参构造函数</span></span><br><span class="line"><span class="built_in">time_point</span>();</span><br><span class="line"><span class="comment">// 2. 构造一个对象,表示一个时间点,其中d的持续时间从epoch开始,需要和时钟类一起使用,不能单独使用该构造函数</span></span><br><span class="line"><span class="function"><span class="keyword">explicit</span> <span class="title">time_point</span><span class="params">( <span class="type">const</span> duration& d )</span></span>;</span><br><span class="line"><span class="comment">// 3. 拷贝构造函数,构造与t相同时间点的对象,使用的时候需要指定模板参数</span></span><br><span class="line"><span class="function"><span class="keyword">template</span>< <span class="keyword">class</span> Duration2 ></span></span><br><span class="line"><span class="function"><span class="title">time_point</span><span class="params">( <span class="type">const</span> time_point<Clock,Duration2>& t )</span></span>;</span><br></pre></td></tr></table></figure><blockquote><p>在这个类中除了构造函数还提供了另外一个 time_since_epoch() 函数,用来获得 1970 年 1 月 1 日到 time_point 对象中记录的时间经过的时间间隔(duration),函数原型如下:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">duration <span class="title">time_since_epoch</span><span class="params">()</span> <span class="type">const</span></span>;</span><br></pre></td></tr></table></figure><blockquote><p>除此之外,时间点 time_point 对象和时间段对象 duration 之间还支持直接进行算术运算(即加减运算),时间点对象之间可以进行逻辑运算,具体细节可以参考下面的表格:</p></blockquote><blockquote><p>其中 tp 和 tp2 是 time_point 类型的对象, dtn 是 duration 类型的对象。</p></blockquote><table><thead><tr><th align="left">描述</th><th align="center">操作</th><th align="center">返回值</th></tr></thead><tbody><tr><td align="left">复合赋值 (成员函数) operator+=</td><td align="center">tp += dtn</td><td align="center">*this</td></tr><tr><td align="left">复合赋值 (成员函数) operator-=</td><td align="center">tp -= dtn</td><td align="center">*this</td></tr><tr><td align="left">算术运算符 (非成员函数) operator+</td><td align="center">tp + dtn</td><td align="center">a time_point value</td></tr><tr><td align="left">算术运算符 (非成员函数) operator+</td><td align="center">dtn + tp</td><td align="center">a time_point value</td></tr><tr><td align="left">算术运算符 (非成员函数) operator-</td><td align="center">tp - dtn</td><td align="center">a time_point value</td></tr><tr><td align="left">算术运算符 (非成员函数) operator-</td><td align="center">ttp - tp2</td><td align="center">aduration value</td></tr><tr><td align="left">关系操作符 (非成员函数) operator==</td><td align="center">tp == tp2</td><td align="center">a bool value</td></tr><tr><td align="left">关系操作符 (非成员函数) operator!=</td><td align="center">tp != tp2</td><td align="center">a bool value</td></tr><tr><td align="left">关系操作符 (非成员函数) operator<</td><td align="center">tp < tp2</td><td align="center">a bool value</td></tr><tr><td align="left">关系操作符 (非成员函数) operator></td><td align="center">tp > tp2</td><td align="center">a bool value</td></tr><tr><td align="left">关系操作符 (非成员函数) operator>=</td><td align="center">tp >= tp2</td><td align="center">a bool value</td></tr><tr><td align="left">关系操作符 (非成员函数) operator<=</td><td align="center">tp <= tp2</td><td align="center">a bool value</td></tr></tbody></table><p>由于该时间点类经常和下面要介绍的时钟类一起使用,所以在此先不举例,在时钟类的示例代码中会涉及到时间点类的使用,到此为止只需要搞明白时间点类的提供的这几个函数的作用就可以了。</p><h3 id="3-时钟-clocks"><a href="#3-时钟-clocks" class="headerlink" title="3. 时钟 clocks"></a>3. 时钟 clocks</h3><p>chrono 库中提供了获取当前的系统时间的时钟类,包含的时钟一共有三种:</p><ul><li>system_clock:系统的时钟,系统的时钟可以修改,甚至可以网络对时,因此使用系统时间计算时间差可能不准。</li><li>steady_clock:是固定的时钟,相当于秒表。开始计时后,时间只会增长并且不能修改,适合用于记录程序耗时</li><li>high_resolution_clock:和时钟类 steady_clock 是等价的(是它的别名)。</li></ul><p>在这些时钟类的内部有 time_point、duration、Rep、Period 等信息,基于这些信息来获取当前时间,以及实现 time_t 和 time_point 之间的相互转换。</p><table><thead><tr><th align="left">时钟类成员类型</th><th align="center">描述</th></tr></thead><tbody><tr><td align="left">rep</td><td align="center">表示时钟周期次数的有符号算术类型</td></tr><tr><td align="left">period</td><td align="center">表示时钟计次周期的 std::ratio 类型</td></tr><tr><td align="left">duration</td><td align="center">时间间隔,可以表示负时长</td></tr><tr><td align="left">time_point</td><td align="center">表示在当前时钟里边记录的时间点</td></tr></tbody></table><p>在使用chrono提供的时钟类的时候,不需要创建类对象,直接调用类的静态方法就可以得到想要的时间了。</p><h3 id="3-1-system-clock"><a href="#3-1-system-clock" class="headerlink" title="3.1 system_clock"></a>3.1 system_clock</h3><p>具体来说,时钟类 system_clock 是一个系统范围的实时时钟。system_clock 提供了对当前时间点 time_point 的访问,将得到时间点转换为 time_t 类型的时间对象,就可以基于这个时间对象获取到当前的时间信息了。</p><blockquote><p>system_clock 时钟类在底层源码中的定义如下:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">system_clock</span> { <span class="comment">// wraps GetSystemTimePreciseAsFileTime/GetSystemTimeAsFileTime</span></span><br><span class="line"> <span class="keyword">using</span> rep = <span class="type">long</span> <span class="type">long</span>;</span><br><span class="line"> <span class="keyword">using</span> period = ratio<<span class="number">1</span>, <span class="number">10'000'000</span>>; <span class="comment">// 100 nanoseconds</span></span><br><span class="line"> <span class="keyword">using</span> duration = chrono::duration<rep, period>;</span><br><span class="line"> <span class="keyword">using</span> time_point = chrono::time_point<system_clock>;</span><br><span class="line"> <span class="type">static</span> <span class="keyword">constexpr</span> <span class="type">bool</span> is_steady = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function">_NODISCARD <span class="type">static</span> time_point <span class="title">now</span><span class="params">()</span> <span class="keyword">noexcept</span></span></span><br><span class="line"><span class="function"> </span>{ <span class="comment">// get current time</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">time_point</span>(<span class="built_in">duration</span>(_Xtime_get_ticks()));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function">_NODISCARD <span class="type">static</span> <span class="type">__time64_t</span> <span class="title">to_time_t</span><span class="params">(<span class="type">const</span> time_point& _Time)</span> <span class="keyword">noexcept</span></span></span><br><span class="line"><span class="function"> </span>{ <span class="comment">// convert to __time64_t</span></span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">duration_cast</span><seconds>(_Time.<span class="built_in">time_since_epoch</span>()).<span class="built_in">count</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function">_NODISCARD <span class="type">static</span> time_point <span class="title">from_time_t</span><span class="params">(<span class="type">__time64_t</span> _Tm)</span> <span class="keyword">noexcept</span></span></span><br><span class="line"><span class="function"> </span>{ <span class="comment">// convert from __time64_t</span></span><br><span class="line"> <span class="keyword">return</span> time_point{seconds{_Tm}};</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>通过以上源码可以了解到在 system_clock 类中的一些细节信息:</p></blockquote><ul><li>rep:时钟周期次数是通过整形来记录的 long long</li><li>period:一个时钟周期是 100 纳秒 ratio<1, 10’000’000></li><li>duration:时间间隔为 rep*period 纳秒 chrono::duration<rep, period></li><li>time_point:时间点通过系统时钟做了初始化 chrono::time_p- oint<system_clock>,里面记录了新纪元时间点</li></ul><blockquote><p>另外还可以看到 system_clock 类一共提供了三个静态成员函数:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回表示当前时间的时间点。</span></span><br><span class="line"><span class="type">static</span> std::<span class="function">chrono::time_point<std::chrono::system_clock> <span class="title">now</span><span class="params">()</span> <span class="keyword">noexcept</span></span>;</span><br><span class="line"><span class="comment">// 将 time_point 时间点类型转换为 std::time_t 类型</span></span><br><span class="line"><span class="function"><span class="type">static</span> std::<span class="type">time_t</span> <span class="title">to_time_t</span><span class="params">( <span class="type">const</span> time_point& t )</span> <span class="keyword">noexcept</span></span>;</span><br><span class="line"><span class="comment">// 将 std::time_t 类型转换为 time_point 时间点类型</span></span><br><span class="line"><span class="type">static</span> std::chrono::<span class="function">system_clock::time_point <span class="title">from_time_t</span><span class="params">( std::<span class="type">time_t</span> t )</span> <span class="keyword">noexcept</span></span>;</span><br></pre></td></tr></table></figure><blockquote><p>比如,我们要获取当前的系统时间,并且需要将其以能够识别的方式打印出来,示例代码如下:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><chrono></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std::chrono;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// 新纪元1970.1.1时间</span></span><br><span class="line"> system_clock::time_point epoch;</span><br><span class="line"></span><br><span class="line"> duration<<span class="type">int</span>, ratio<<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>>> <span class="built_in">day</span>(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 新纪元1970.1.1时间 + 1天</span></span><br><span class="line"> <span class="function">system_clock::time_point <span class="title">ppt</span><span class="params">(day)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">using</span> dday = duration<<span class="type">int</span>, ratio<<span class="number">60</span> * <span class="number">60</span> * <span class="number">24</span>>>;</span><br><span class="line"> <span class="comment">// 新纪元1970.1.1时间 + 10天</span></span><br><span class="line"> <span class="function">time_point<system_clock, dday> <span class="title">t</span><span class="params">(dday(<span class="number">10</span>))</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 系统当前时间</span></span><br><span class="line"> system_clock::time_point today = system_clock::<span class="built_in">now</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 转换为time_t时间类型</span></span><br><span class="line"> <span class="type">time_t</span> tm = system_clock::<span class="built_in">to_time_t</span>(today);</span><br><span class="line"> cout << <span class="string">"今天的日期是: "</span> << <span class="built_in">ctime</span>(&tm);</span><br><span class="line"></span><br><span class="line"> <span class="type">time_t</span> tm1 = system_clock::<span class="built_in">to_time_t</span>(today+day);</span><br><span class="line"> cout << <span class="string">"明天的日期是: "</span> << <span class="built_in">ctime</span>(&tm1);</span><br><span class="line"></span><br><span class="line"> <span class="type">time_t</span> tm2 = system_clock::<span class="built_in">to_time_t</span>(epoch);</span><br><span class="line"> cout << <span class="string">"新纪元时间: "</span> << <span class="built_in">ctime</span>(&tm2);</span><br><span class="line"></span><br><span class="line"> <span class="type">time_t</span> tm3 = system_clock::<span class="built_in">to_time_t</span>(ppt);</span><br><span class="line"> cout << <span class="string">"新纪元时间+1天: "</span> << <span class="built_in">ctime</span>(&tm3);</span><br><span class="line"></span><br><span class="line"> <span class="type">time_t</span> tm4 = system_clock::<span class="built_in">to_time_t</span>(t);</span><br><span class="line"> cout << <span class="string">"新纪元时间+10天: "</span> << <span class="built_in">ctime</span>(&tm4);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>示例代码打印的结果为:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">今天的日期是: Thu Apr 8 11:09:49 2021</span><br><span class="line">明天的日期是: Fri Apr 9 11:09:49 2021</span><br><span class="line">新纪元时间: Thu Jan 1 08:00:00 1970</span><br><span class="line">新纪元时间+1天: Fri Jan 2 08:00:00 1970</span><br><span class="line">新纪元时间+10天: Sun Jan 11 08:00:00 1970</span><br></pre></td></tr></table></figure><h3 id="3-2-steady-clock"><a href="#3-2-steady-clock" class="headerlink" title="3.2 steady_clock"></a>3.2 steady_clock</h3><p>如果我们通过时钟不是为了获取当前的系统时间,而是进行程序耗时的时长,此时使用 syetem_clock 就不合适了,因为这个时间可以跟随系统的设置发生变化。在 C++11 中提供的时钟类 steady_clock 相当于秒表,只要启动就会进行时间的累加,并且不能被修改,非常适合于进行耗时的统计。</p><blockquote><p>steady_clock 时钟类在底层源码中的定义如下:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">steady_clock</span> { <span class="comment">// wraps QueryPerformanceCounter</span></span><br><span class="line"> <span class="keyword">using</span> rep = <span class="type">long</span> <span class="type">long</span>;</span><br><span class="line"> <span class="keyword">using</span> period = nano;</span><br><span class="line"> <span class="keyword">using</span> duration = nanoseconds;</span><br><span class="line"> <span class="keyword">using</span> time_point = chrono::time_point<steady_clock>;</span><br><span class="line"> <span class="type">static</span> <span class="keyword">constexpr</span> <span class="type">bool</span> is_steady = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// get current time</span></span><br><span class="line"> <span class="function">_NODISCARD <span class="type">static</span> time_point <span class="title">now</span><span class="params">()</span> <span class="keyword">noexcept</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="comment">// doesn't change after system boot</span></span><br><span class="line"> <span class="type">const</span> <span class="type">long</span> <span class="type">long</span> _Freq = _Query_perf_frequency();</span><br><span class="line"> <span class="type">const</span> <span class="type">long</span> <span class="type">long</span> _Ctr = _Query_perf_counter();</span><br><span class="line"> <span class="built_in">static_assert</span>(period::num == <span class="number">1</span>, <span class="string">"This assumes period::num == 1."</span>);</span><br><span class="line"> <span class="type">const</span> <span class="type">long</span> <span class="type">long</span> _Whole = (_Ctr / _Freq) * period::den;</span><br><span class="line"> <span class="type">const</span> <span class="type">long</span> <span class="type">long</span> _Part = (_Ctr % _Freq) * period::den / _Freq;</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">time_point</span>(<span class="built_in">duration</span>(_Whole + _Part));</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>通过以上源码可以了解到在 steady_clock 类中的一些细节信息:</p></blockquote><ul><li>rep:时钟周期次数是通过整形来记录的 long long</li><li>period:一个时钟周期是 1 纳秒 nano</li><li>duration:时间间隔为 1 纳秒 nanoseconds</li><li>time_point:时间点通过系统时钟做了初始化 chrono::time_point<steady_clock></li></ul><blockquote><p>另外,在这个类中也提供了一个静态的 now () 方法,用于得到当前的时间点,函数原型如下:</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">static std::chrono::time_point<std::chrono::steady_clock> now() noexcept;</span><br></pre></td></tr></table></figure><blockquote><p>假设要测试某一段程序的执行效率,可以计算它执行期间消耗的总时长,示例代码如下:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><chrono></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std::chrono;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// 获取开始时间点</span></span><br><span class="line"> steady_clock::time_point start = steady_clock::<span class="built_in">now</span>();</span><br><span class="line"> <span class="comment">// 执行业务流程</span></span><br><span class="line"> cout << <span class="string">"print 1000 stars ...."</span> << endl;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">1000</span>; ++i)</span><br><span class="line"> {</span><br><span class="line"> cout << <span class="string">"*"</span>;</span><br><span class="line"> }</span><br><span class="line"> cout << endl;</span><br><span class="line"> <span class="comment">// 获取结束时间点</span></span><br><span class="line"> steady_clock::time_point last = steady_clock::<span class="built_in">now</span>();</span><br><span class="line"> <span class="comment">// 计算差值</span></span><br><span class="line"> <span class="keyword">auto</span> dt = last - start;</span><br><span class="line"> cout << <span class="string">"总共耗时: "</span> << dt.<span class="built_in">count</span>() << <span class="string">"纳秒"</span> << endl;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="3-3-high-resolution-clock"><a href="#3-3-high-resolution-clock" class="headerlink" title="3.3 high_resolution_clock"></a>3.3 high_resolution_clock</h3><p>high_resolution_clock 提供的时钟精度比 system_clock 要高,它也是不可以修改的。在底层源码中,这个类其实是 steady_clock 类的别名。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> high_resolution_clock = steady_clock;</span><br></pre></td></tr></table></figure><p>因此 high_resolution_clock 的使用方式和 steady_clock 是一样的,在此就不再过多进行赘述了。</p><h2 id="4-转换函数"><a href="#4-转换函数" class="headerlink" title="4. 转换函数"></a>4. 转换函数</h2><h3 id="4-1-duration-cast"><a href="#4-1-duration-cast" class="headerlink" title="4.1 duration_cast"></a>4.1 duration_cast</h3><p>duration_cast 是 chrono 库提供的一个模板函数,这个函数不属于 duration 类。通过这个函数可以对 duration 类对象内部的时钟周期 Period,和周期次数的类型 Rep 进行修改,该函数原型如下:</p><figure class="highlight plaintext"><table><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">template <class ToDuration, class Rep, class Period></span><br><span class="line"> constexpr ToDuration duration_cast (const duration<Rep,Period>& dtn);</span><br></pre></td></tr></table></figure><p>在源周期能准确地为目标周期所整除的场合(例如小时到分钟),浮点时长和整数时长间转型能隐式进行无需使用 duration_cast ,其他情况下都需要通过函数进行转换。</p><p>我们可以修改一下上面测试程序执行时间的代码,在代码中修改 duration 对象的属性:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><chrono></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std::chrono;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> cout << <span class="string">"print 1000 stars ...."</span> << endl;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">1000</span>; ++i)</span><br><span class="line"> {</span><br><span class="line"> cout << <span class="string">"*"</span>;</span><br><span class="line"> }</span><br><span class="line"> cout << endl;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">auto</span> t1 = steady_clock::<span class="built_in">now</span>();</span><br><span class="line"> <span class="built_in">f</span>();</span><br><span class="line"> <span class="keyword">auto</span> t2 = steady_clock::<span class="built_in">now</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 整数时长:要求 duration_cast</span></span><br><span class="line"> <span class="keyword">auto</span> int_ms = <span class="built_in">duration_cast</span><chrono::milliseconds>(t2 - t1);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 小数时长:不要求 duration_cast</span></span><br><span class="line"> duration<<span class="type">double</span>, ratio<<span class="number">1</span>, <span class="number">1000</span>>> fp_ms = t2 - t1;</span><br><span class="line"></span><br><span class="line"> cout << <span class="string">"f() took "</span> << fp_ms.<span class="built_in">count</span>() << <span class="string">" ms, "</span></span><br><span class="line"> << <span class="string">"or "</span> << int_ms.<span class="built_in">count</span>() << <span class="string">" whole milliseconds\n"</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>示例代码输出的结果:</p><figure class="highlight plaintext"><table><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">print 1000 stars ....</span><br><span class="line">****************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************</span><br><span class="line">f() took 40.2547 ms, or 40 whole milliseconds</span><br></pre></td></tr></table></figure><h3 id="4-2-time-point-cast"><a href="#4-2-time-point-cast" class="headerlink" title="4.2 time_point_cast"></a>4.2 time_point_cast</h3><p>time_point_cast 也是 chrono 库提供的一个模板函数,这个函数不属于 time_point 类。函数的作用是对时间点进行转换,因为不同的时间点对象内部的时钟周期 Period,和周期次数的类型 Rep 可能也是不同的,一般情况下它们之间可以进行隐式类型转换,也可以通过该函数显示的进行转换,函数原型如下:</p><figure class="highlight plaintext"><table><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">template <class ToDuration, class Clock, class Duration></span><br><span class="line">time_point<Clock, ToDuration> time_point_cast(const time_point<Clock, Duration> &t);</span><br></pre></td></tr></table></figure><p>关于函数的使用,示例代码如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><chrono></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> Clock = chrono::high_resolution_clock;</span><br><span class="line"><span class="keyword">using</span> Ms = chrono::milliseconds;</span><br><span class="line"><span class="keyword">using</span> Sec = chrono::seconds;</span><br><span class="line"><span class="keyword">template</span><<span class="keyword">class</span> <span class="title class_">Duration</span>></span><br><span class="line"><span class="keyword">using</span> TimePoint = chrono::time_point<Clock, Duration>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">print_ms</span><span class="params">(<span class="type">const</span> TimePoint<Ms>& time_point)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> std::cout << time_point.<span class="built_in">time_since_epoch</span>().<span class="built_in">count</span>() << <span class="string">" ms\n"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="function">TimePoint<Sec> <span class="title">time_point_sec</span><span class="params">(Sec(<span class="number">6</span>))</span></span>;</span><br><span class="line"> <span class="comment">// 无精度损失, 可以进行隐式类型转换</span></span><br><span class="line"> <span class="function">TimePoint<Ms> <span class="title">time_point_ms</span><span class="params">(time_point_sec)</span></span>;</span><br><span class="line"> <span class="built_in">print_ms</span>(time_point_ms); <span class="comment">// 6000 ms</span></span><br><span class="line"></span><br><span class="line"> time_point_ms = <span class="built_in">TimePoint</span><Ms>(<span class="built_in">Ms</span>(<span class="number">6789</span>));</span><br><span class="line"> <span class="comment">// error,会损失精度,不允许进行隐式的类型转换</span></span><br><span class="line"> <span class="function">TimePoint<Sec> <span class="title">sec</span><span class="params">(time_point_ms)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 显示类型转换,会损失精度。6789 truncated to 6000</span></span><br><span class="line"> time_point_sec = std::chrono::<span class="built_in">time_point_cast</span><Sec>(time_point_ms);</span><br><span class="line"> <span class="built_in">print_ms</span>(time_point_sec); <span class="comment">// 6000 ms</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>注意事项</strong>:关于时间点的转换如果没有没有精度的损失可以直接进行隐式类型转换,如果会损失精度只能通过显示类型转换,也就是调用 time_point_cast 函数来完成该操作。</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="C-使用-chrono-库处理日期和时间"><a href="#C-使用-chrono-库处理日期和时间" class="headerlink" title="C++ 使用 chrono 库处理日期和时间"></a>C++ 使用 chrono 库处理日期和时间</</summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
<category term="chrono" scheme="https://zyjblogs.cn/tags/chrono/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>C/C++ 关于 cJson 库的使用</title>
|
||
<link href="https://zyjblogs.cn/posts/63502.html"/>
|
||
<id>https://zyjblogs.cn/posts/63502.html</id>
|
||
<published>2022-02-06T16:00:00.000Z</published>
|
||
<updated>2023-10-09T02:49:07.409Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="C-C-关于-cJson-库的使用"><a href="#C-C-关于-cJson-库的使用" class="headerlink" title="C/C++ 关于 cJson 库的使用"></a>C/C++ 关于 cJson 库的使用</h1><p>关于 Json 这种数据格式,在前面已经做了详细的介绍 Json 的格式和用途,在项目开发过程中我们需要针对不同的语言使用不同的库对 Json 格式的数据进行解析,下面给大家介绍一个基于 C 语言的 Json 库 – cJson。cJSON 是一个超轻巧,携带方便,单文件,简单的可以作为 ANSI-C 标准的 JSON 解析器。</p><p>cJSON 是一个开源项目,github 下载地址:</p><blockquote><p><a href="https://github.com/DaveGamble/cJSON">https://github.com/DaveGamble/cJSON</a></p></blockquote><p>cJSON,目前来说,主要的文件有两个,一个 <code>cJSON.c</code> 一个 <code>cJSON.h</code>。使用的时候,将头文件 include 进去即可。</p><p>如果是在 Linux 操作系统中使用,编译 到时候需要添加数学库 <code>libm.so</code>,如下所示:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc *.c cJSON.c -lm</span><br></pre></td></tr></table></figure><h2 id="1-cJSON-结构体"><a href="#1-cJSON-结构体" class="headerlink" title="1. cJSON 结构体"></a>1. cJSON 结构体</h2><p>在 <code>cJSON.h</code> 中定义了一个非常重要的结构体 cJSON,想要熟悉使用 cJSON 库函数可从 cJSON 结构体入手,cJSON 结构体如下所示:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">typedef struct cJSON {</span><br><span class="line"> struct cJSON *next,*prev;</span><br><span class="line"> struct cJSON *child;</span><br><span class="line"> int type;</span><br><span class="line"> char *valuestring; // value值是字符串类型</span><br><span class="line"> int valueint;</span><br><span class="line"> double valuedouble;</span><br><span class="line"> char *string; // 对象中的key</span><br><span class="line">} cJSON;</span><br></pre></td></tr></table></figure><p>关于这个结构体做如下几点的说明:</p><ol><li>cJOSN 结构体是一个双向链表,并且可通过 child 指针访问下一层。</li><li>结构体成员 type 变量用于描述数据元素的类型(如果是键值对表示 value 值的类型),数据元素可以是字符串可以是整形,也可以是浮点型。</li></ol><ul><li>如果是整形值的话可通过 valueint 将值取出</li><li>如果是浮点型的话可通过 valuedouble 将值取出</li><li>如果是字符串类型的话可通过 valuestring 将值取出</li></ul><ol><li>结构体成员 string 表示键值对中键值的名称。</li></ol><p>cJSON 作为 Json 格式的解析库,其主要功能就是构建和解析 Json 格式了,比如要发送数据:用途就是发送端将要发送的数据以 json 形式封装,然后发送,接收端收到此数据后,还是按 json 形式解析,就得到想要的数据了。</p><h2 id="2-cJson-API"><a href="#2-cJson-API" class="headerlink" title="2. cJson API"></a>2. cJson API</h2><p>Json 格式的数据无外乎有两种 Json对象和 Json数组,创建的 Json 数据串可能是二者中 的一种,也可能是二者的组合,不管哪一种通过调用相关的 API 函数都可以轻松的做到这一点。</p><h3 id="2-1-数据的封装"><a href="#2-1-数据的封装" class="headerlink" title="2.1 数据的封装"></a>2.1 数据的封装</h3><p>在 <code>cJSON.h</code> 头文件中可以看到一些函数声明,通过调用这些创建函数就可以将 Json 支持的数据类型封装为 cJSON 结构体类型:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">// 空值类型</span><br><span class="line">extern cJSON *cJSON_CreateNull(void);</span><br><span class="line">// 布尔类型</span><br><span class="line">extern cJSON *cJSON_CreateTrue(void);</span><br><span class="line">extern cJSON *cJSON_CreateFalse(void);</span><br><span class="line">extern cJSON *cJSON_CreateBool(int b);</span><br><span class="line">// 数值类型</span><br><span class="line">extern cJSON *cJSON_CreateNumber(double num);</span><br><span class="line">// 字符串类型</span><br><span class="line">extern cJSON *cJSON_CreateString(const char *string);</span><br><span class="line">// json数组(创建空数组)</span><br><span class="line">extern cJSON *cJSON_CreateArray(void);</span><br><span class="line">// json对象(创建空对象)</span><br><span class="line">extern cJSON *cJSON_CreateObject(void);</span><br></pre></td></tr></table></figure><p>另外,cJson 库中还给我我们提供了一些更为简便的操作函数,在创建数组的同时还可以进行初始化</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">// 创建一个Json数组, 元素为整形</span><br><span class="line">extern cJSON *cJSON_CreateIntArray(const int *numbers,int count);</span><br><span class="line">// 创建一个Json数组, 元素为浮点</span><br><span class="line">extern cJSON *cJSON_CreateFloatArray(const float *numbers,int count);</span><br><span class="line">extern cJSON *cJSON_CreateDoubleArray(const double *numbers,int count);</span><br><span class="line">// 创建一个Json数组, 元素为字符串类型</span><br><span class="line">extern cJSON *cJSON_CreateStringArray(const char **strings,int count);</span><br></pre></td></tr></table></figure><h3 id="2-2-Json-对象操作"><a href="#2-2-Json-对象操作" class="headerlink" title="2.2 Json 对象操作"></a>2.2 Json 对象操作</h3><p>当得到一个 Json 对象之后,就可以往对象中添加键值对了,可以使用 <code>cJSON_AddItemToObject()</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item);</span><br></pre></td></tr></table></figure><p>在 cJSON 库中节点的从属关系是通过树来维护的,每一层节点都是通过链表来维护的,这样就能分析出该函数参数的含义:</p><ul><li>object:要添加的键值对从属于那个节点</li><li>string:添加的键值对的键值</li><li>item:添加的键值对的 value 值(需要先将其封装为 cJSON 类型的结构体)</li></ul><p>为了让我的操作更加方便,cJson 库还给我们提供了一些宏函数,方便我们快速的往 Json 对象中添加键值对</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull())</span><br><span class="line">#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue())</span><br><span class="line">#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse())</span><br><span class="line">#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))</span><br><span class="line">#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))</span><br><span class="line">#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))</span><br></pre></td></tr></table></figure><p>我们还可以根据 Json 对象中的键值取出相应的 value 值,API 函数原型如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);</span><br></pre></td></tr></table></figure><h3 id="2-3-Json-数组操作"><a href="#2-3-Json-数组操作" class="headerlink" title="2.3 Json 数组操作"></a>2.3 Json 数组操作</h3><p>添加数据到 Json 数组中(原始数据需要先转换为 cJSON 结构体类型)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extern void cJSON_AddItemToArray(cJSON *array, cJSON *item);</span><br></pre></td></tr></table></figure><p>得到 Json 数组中元素的个数:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extern int cJSON_GetArraySize(cJSON *array);</span><br></pre></td></tr></table></figure><p>得到 Json 数组中指定位置的原素,如果返回 NULL 表示取值失败了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extern cJSON *cJSON_GetArrayItem(cJSON *array,int item);</span><br></pre></td></tr></table></figure><h3 id="2-4-序列化"><a href="#2-4-序列化" class="headerlink" title="2.4 序列化"></a>2.4 序列化</h3><p>序列化就是将 Json 格式的数据转换为字符串的过程,cJson 库中给我们提供了 3 个转换函数,具体如下:</p><p>第一个参数 item 表示 Json 数据块的根节点。</p><figure class="highlight plaintext"><table><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">extern char *cJSON_Print(cJSON *item);</span><br><span class="line">extern char *cJSON_PrintUnformatted(cJSON *item);</span><br><span class="line">extern char *cJSON_PrintBuffered(cJSON *item,int prebuffer,int fmt);</span><br></pre></td></tr></table></figure><ul><li>调用 <code>cJSON_Print()</code> 函数我们可以得到一个带格式的 Json 字符串(有换行,看起来更直观)</li><li>调用 <code>cJSON_PrintUnformatted()</code> 函数会得到一个没有格式的 Json 字符串(没有换行,所有的数据都在同一行)。</li><li>调用 <code>cJSON_PrintBuffered()</code> 函数使用缓冲策略将 Json 实体转换为字符串,参数 prebuffer 是指定缓冲区的大小,参数 <code>fmt==0</code> 表示未格式化,<code>fmt==1</code> 表示格式化。</li></ul><p>我们在编码过程中可以根据自己的实际需求调用相关的操作函数得到对应格式的 Json 字符串。</p><h3 id="2-5-Json-字符串的解析"><a href="#2-5-Json-字符串的解析" class="headerlink" title="2.5 Json 字符串的解析"></a>2.5 Json 字符串的解析</h3><p>如果我们得到了一个 Json 格式的字符串,想要读出里边的数据,就需要对这个字符串进行解析,处理方式就是将字符串转换为 cJSON 结构体,然后再基于这个结构体读里边的原始数据,转换函数的函数原型如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extern cJSON *cJSON_Parse(const char *value);</span><br></pre></td></tr></table></figure><h3 id="2-6-内存释放"><a href="#2-6-内存释放" class="headerlink" title="2.6 内存释放"></a>2.6 内存释放</h3><p>当我们将数据封装为 cJSON 结构类型的节点之后都会得到一块堆内存,当我们释放某个节点的时候可以调用 cJson 库提供的删除函数 <code>cJSON_Delete()</code>,函数原型如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extern void cJSON_Delete(cJSON *c);</span><br></pre></td></tr></table></figure><p>该函数的参数为要释放的节点的地址,在此强调一点:在进行内存地址释放的时候,当前节点以及其子节点都会被删除。</p><h2 id="3-Json-数据的封装"><a href="#3-Json-数据的封装" class="headerlink" title="3. Json 数据的封装"></a>3. Json 数据的封装</h2><h3 id="3-1-Json-对象操作举例"><a href="#3-1-Json-对象操作举例" class="headerlink" title="3.1 Json 对象操作举例"></a>3.1 Json 对象操作举例</h3><blockquote><p>创建一个对象,并向这个对象里添加字符串和整型键值:</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">#include<stdio.h></span><br><span class="line">#include<stdlib.h></span><br><span class="line">#include<string.h></span><br><span class="line">#include"cJSON.h"</span><br><span class="line"></span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> cJSON * root;</span><br><span class="line"> cJSON *arry;</span><br><span class="line"></span><br><span class="line"> root=cJSON_CreateObject(); // 创建根数据对象</span><br><span class="line"> cJSON_AddStringToObject(root,"name","luffy"); // 添加键值对</span><br><span class="line"> cJSON_AddStringToObject(root,"sex","man"); // 添加键值对</span><br><span class="line"> cJSON_AddNumberToObject(root,"age",19); // 添加键值对</span><br><span class="line"></span><br><span class="line"> char *out = cJSON_Print(root); // 将json形式转换成字符串</span><br><span class="line"> printf("%s\n",out);</span><br><span class="line"></span><br><span class="line"> // 释放内存</span><br><span class="line"> cJSON_Delete(root);</span><br><span class="line"> free(out);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "name": "luffy",</span><br><span class="line"> "sex": "man",</span><br><span class="line"> "age": 19</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>若干说明:</p><ul><li><code>cJSON_CreateObject</code> 函数可创建一个根对象,返回的是一个 cJSON 指针,在这个指针用完了以后,需要手动调用 <code>cJSON_Delete(root)</code> 进行内存回收。</li><li>函数 <code>cJSON_Print()</code> 内部封装了 malloc 函数,所以需要使用 free() 函数释放被 out 占用的内存空间。</li></ul><h3 id="3-2-Json-数组操作举例"><a href="#3-2-Json-数组操作举例" class="headerlink" title="3.2 Json 数组操作举例"></a>3.2 Json 数组操作举例</h3><blockquote><p>创建一个数组,并向数组添加一个字符串和一个数字</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">int main(int argc, char **argv)</span><br><span class="line">{</span><br><span class="line"> cJSON *root;</span><br><span class="line"> root = cJSON_CreateArray();</span><br><span class="line"> cJSON_AddItemToArray(root, cJSON_CreateString("Hello world"));</span><br><span class="line"> cJSON_AddItemToArray(root, cJSON_CreateNumber(10));</span><br><span class="line"> // char *s = cJSON_Print(root);</span><br><span class="line"> char *s = cJSON_PrintUnformatted(root);</span><br><span class="line"> if(s)</span><br><span class="line"> {</span><br><span class="line"> printf(" %s \n",s);</span><br><span class="line"> free(s);</span><br><span class="line"> }</span><br><span class="line"> cJSON_Delete(root);</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">["Hello world",10]</span><br></pre></td></tr></table></figure><h3 id="3-3-Json-对象、数组嵌套使用"><a href="#3-3-Json-对象、数组嵌套使用" class="headerlink" title="3.3 Json 对象、数组嵌套使用"></a>3.3 Json 对象、数组嵌套使用</h3><blockquote><p>对象里面包括一个数组,数组里面包括对象,对象里面再添加一个字符串和一个数字</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "person":[{</span><br><span class="line"> "name":"luffy",</span><br><span class="line"> "age":19</span><br><span class="line"> }]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>示例代码:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> cJSON *root, *body, *list;</span><br><span class="line"> <span class="comment">// josn 对象 root</span></span><br><span class="line"> root = <span class="built_in">cJSON_CreateObject</span>();</span><br><span class="line"> <span class="comment">// root 添加键值对 person:json数组A</span></span><br><span class="line"> <span class="built_in">cJSON_AddItemToObject</span>(root,<span class="string">"person"</span>, body = <span class="built_in">cJSON_CreateArray</span>());</span><br><span class="line"> <span class="comment">// json数组A 添加Json对象B</span></span><br><span class="line"> <span class="built_in">cJSON_AddItemToArray</span>(body, list = <span class="built_in">cJSON_CreateObject</span>());</span><br><span class="line"> <span class="comment">// 在json对象B中添加键值对: "name":"luffy"</span></span><br><span class="line"> <span class="built_in">cJSON_AddStringToObject</span>(list,<span class="string">"name"</span>,<span class="string">"luffy"</span>);</span><br><span class="line"> <span class="comment">// 在json对象B中添加键值对: "age":19</span></span><br><span class="line"> <span class="built_in">cJSON_AddNumberToObject</span>(list,<span class="string">"age"</span>,<span class="number">19</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// char *s = cJSON_Print(root);</span></span><br><span class="line"> <span class="type">char</span> *s = <span class="built_in">cJSON_PrintUnformatted</span>(root);</span><br><span class="line"> <span class="keyword">if</span>(s)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">" %s \n"</span>,s);</span><br><span class="line"> <span class="built_in">free</span>(s);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span>(root)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">cJSON_Delete</span>(root);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>运行结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">{"person":[{"name":"luffy","age":19}]}</span><br></pre></td></tr></table></figure><h2 id="4-解析-Json-字符串"><a href="#4-解析-Json-字符串" class="headerlink" title="4. 解析 Json 字符串"></a>4. 解析 Json 字符串</h2><h3 id="4-1-解析-Json-对象"><a href="#4-1-解析-Json-对象" class="headerlink" title="4.1 解析 Json 对象"></a>4.1 解析 Json 对象</h3><p>Json 字符串的解析流程和数据的封装流程相反,假设我们有这样一个 Json 字符串(字符串中的双引号需要通过转义字符将其转译为普通字符):</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">{\"name\":\"luffy\",\"sex\":\"man\",\"age\":19}</span><br></pre></td></tr></table></figure><p>示例代码如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"cJSON.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> cJSON *json, *name, *sex, *age;</span><br><span class="line"> <span class="type">char</span>* out=<span class="string">"{\"name\":\"luffy\",\"sex\":\"man\",\"age\":19}"</span>;</span><br><span class="line"></span><br><span class="line"> json = <span class="built_in">cJSON_Parse</span>(out); <span class="comment">//解析成json形式</span></span><br><span class="line"> name = <span class="built_in">cJSON_GetObjectItem</span>(json, <span class="string">"name"</span>); <span class="comment">//获取键值内容</span></span><br><span class="line"> sex = <span class="built_in">cJSON_GetObjectItem</span>(json, <span class="string">"sex"</span>);</span><br><span class="line"> age = <span class="built_in">cJSON_GetObjectItem</span>(json, <span class="string">"age"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"name:%s,sex:%s,age:%d\n"</span>, name->valuestring, sex->valuestring, age->valueint);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">cJSON_Delete</span>(json); <span class="comment">//释放内存</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>输出的结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">name:luffy,sex:man,age:19</span><br></pre></td></tr></table></figure><p><strong>如果是在严格的场所,应该先判定每个 item 的 type,然后再考虑去取值</strong>。</p><h3 id="4-2-解析嵌套的-Json-对象"><a href="#4-2-解析嵌套的-Json-对象" class="headerlink" title="4.2 解析嵌套的 Json 对象"></a>4.2 解析嵌套的 Json 对象</h3><p>加大一点难度,下面我们解析一个嵌套的 Json 对象,数据如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line">{\<span class="string">"list\":{\"name\":\"luffy\",\"age\":19},\"other\":{\"name\":\"ace\"}}</span></span><br><span class="line"><span class="string">int main()</span></span><br><span class="line"><span class="string">{</span></span><br><span class="line"><span class="string"> char *s = "</span>{\<span class="string">"list\":{\"name\":\"luffy\",\"age\":19},\"other\":{\"name\":\"ace\"}}"</span>;</span><br><span class="line"> cJSON *root = <span class="built_in">cJSON_Parse</span>(s);</span><br><span class="line"> <span class="keyword">if</span>(!root)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"get root faild !\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> cJSON *js_list = <span class="built_in">cJSON_GetObjectItem</span>(root, <span class="string">"list"</span>);</span><br><span class="line"> <span class="keyword">if</span>(!js_list)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"no list!\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"list type is %d\n"</span>,js_list->type);</span><br><span class="line"></span><br><span class="line"> cJSON *name = <span class="built_in">cJSON_GetObjectItem</span>(js_list, <span class="string">"name"</span>);</span><br><span class="line"> <span class="keyword">if</span>(!name)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"No name !\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"name type is %d\n"</span>,name->type);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"name is %s\n"</span>,name->valuestring);</span><br><span class="line"></span><br><span class="line"> cJSON *age = <span class="built_in">cJSON_GetObjectItem</span>(js_list, <span class="string">"age"</span>);</span><br><span class="line"> <span class="keyword">if</span>(!age)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"no age!\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"age type is %d\n"</span>, age->type);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"age is %d\n"</span>,age->valueint);</span><br><span class="line"></span><br><span class="line"> cJSON *js_other = <span class="built_in">cJSON_GetObjectItem</span>(root, <span class="string">"other"</span>);</span><br><span class="line"> <span class="keyword">if</span>(!js_other)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"no list!\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"list type is %d\n"</span>,js_other->type);</span><br><span class="line"></span><br><span class="line"> cJSON *js_name = <span class="built_in">cJSON_GetObjectItem</span>(js_other, <span class="string">"name"</span>);</span><br><span class="line"> <span class="keyword">if</span>(!js_name)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"No name !\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"name type is %d\n"</span>,js_name->type);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"name is %s\n"</span>,js_name->valuestring);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(root)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">cJSON_Delete</span>(root);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>打印结果:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">list type is 6</span><br><span class="line">name type is 4</span><br><span class="line">name is luffy</span><br><span class="line">age type is 3</span><br><span class="line">age is 19</span><br><span class="line">list type is 6</span><br><span class="line">name type is 4</span><br><span class="line">name is ace</span><br></pre></td></tr></table></figure><h3 id="4-3-解析-Json-数组"><a href="#4-3-解析-Json-数组" class="headerlink" title="4.3 解析 Json 数组"></a>4.3 解析 Json 数组</h3><p>如果我们遇到的 Json 字符串是一个 Json 数组格式,处理方式和 Json 对象差不多,比如我们要解析如下字符串:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">{\<span class="string">"names\":[\"luffy\",\"robin\"]}</span></span><br><span class="line"><span class="string">int main(int argc, char **argv)</span></span><br><span class="line"><span class="string">{</span></span><br><span class="line"><span class="string"> char *s = "</span>{\<span class="string">"names\":[\"luffy\",\"robin\"]}"</span>;</span><br><span class="line"> cJSON *root = <span class="built_in">cJSON_Parse</span>(s);</span><br><span class="line"> <span class="keyword">if</span>(!root)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"get root faild !\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> cJSON *js_list = <span class="built_in">cJSON_GetObjectItem</span>(root, <span class="string">"names"</span>);</span><br><span class="line"> <span class="keyword">if</span>(!js_list)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"no list!\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> array_size = <span class="built_in">cJSON_GetArraySize</span>(js_list);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"array size is %d\n"</span>,array_size);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>; i< array_size; i++)</span><br><span class="line"> {</span><br><span class="line"> cJSON *item = <span class="built_in">cJSON_GetArrayItem</span>(js_list, i);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"item type is %d\n"</span>,item->type);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%s\n"</span>,item->valuestring);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(root)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">cJSON_Delete</span>(root);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-4-解析嵌套的-Json-对象和数组"><a href="#4-4-解析嵌套的-Json-对象和数组" class="headerlink" title="4.4 解析嵌套的 Json 对象和数组"></a>4.4 解析嵌套的 Json 对象和数组</h3><p>对于 Json 字符串最复杂的个数莫过于 Json 对象和 Json 数组嵌套的形式,下面通过一个例子演示一下应该如何解析,字符串格式如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">{\"list\":[{\"name\":\"luffy\",\"age\":19},{\"name\":\"sabo\",\"age\":21}]}</span><br></pre></td></tr></table></figure><p>在解析的时候,我们只需要按照从属关系,一层层解析即可:</p><ul><li>根节点是一个 Json 对象,基于根节点中的 key 值取出对应的 value 值,得到一个 Json 数组</li><li>读出 Json 数组的大小,遍历里边的各个元素,每个元素都是一个 Json 对象</li><li>将 Json 对象中的键值对根据 key 值取出对应的 value 值</li><li>从取出的 Value 值中读出实际类型对应的数值 示例代码如下:</li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"cJSON.h"</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="type">char</span> *s = <span class="string">"{\"list\":[{\"name\":\"luffy\",\"age\":19},{\"name\":\"sabo\",\"age\":21}]}"</span>;</span><br><span class="line"> cJSON *root = <span class="built_in">cJSON_Parse</span>(s);</span><br><span class="line"> <span class="keyword">if</span>(!root)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"get root faild !\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> cJSON *list = <span class="built_in">cJSON_GetObjectItem</span>(root, <span class="string">"list"</span>);</span><br><span class="line"> <span class="keyword">if</span>(!list)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"no list!\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="type">int</span> array_size = <span class="built_in">cJSON_GetArraySize</span>(list);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"array size is %d\n"</span>,array_size);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>; i< array_size; i++)</span><br><span class="line"> {</span><br><span class="line"> cJSON* item = <span class="built_in">cJSON_GetArrayItem</span>(list, i);</span><br><span class="line"> cJSON* name = <span class="built_in">cJSON_GetObjectItem</span>(item, <span class="string">"name"</span>);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"name is %s\n"</span>,name->valuestring);</span><br><span class="line"> cJSON* age = <span class="built_in">cJSON_GetObjectItem</span>(item, <span class="string">"age"</span>);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"age is %d\n"</span>,age->valueint);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(root)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">cJSON_Delete</span>(root);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>文章链接:<a href="https://subingwen.cn/c/cjson%E4%BD%BF%E7%94%A8/">https://subingwen.cn/c/cjson%E4%BD%BF%E7%94%A8/</a></p></blockquote>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="C-C-关于-cJson-库的使用"><a href="#C-C-关于-cJson-库的使用" class="headerlink" title="C&#x2F;C++ 关于 cJson 库的使用"></a>C&#x2F;C++ 关于 cJson 库的使用</h1</summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
<category term="cJson" scheme="https://zyjblogs.cn/tags/cJson/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>C++后台</title>
|
||
<link href="https://zyjblogs.cn/posts/59764.html"/>
|
||
<id>https://zyjblogs.cn/posts/59764.html</id>
|
||
<published>2022-02-06T16:00:00.000Z</published>
|
||
<updated>2023-10-12T09:43:55.398Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="C-后台"><a href="#C-后台" class="headerlink" title="C++后台"></a>C++后台</h1><h2 id="学习篇:"><a href="#学习篇:" class="headerlink" title="学习篇:"></a>学习篇:</h2><h3 id="一、一个项目入门C-足以:CPlusPlusThings"><a href="#一、一个项目入门C-足以:CPlusPlusThings" class="headerlink" title="一、一个项目入门C++足以:CPlusPlusThings."></a>一、一个项目入门C++足以:CPlusPlusThings.</h3><p>CPlusPlusThings 是国人开源一个 C++ 学习项目。它系统地将 C++ 学习分为了【基础进阶】、【实战系列】、【C++2.0 新特性】、【设计模式】和【STL 源码剖析】、【并发编程】、【C++ 惯用法】、【学习课程】、【工具】、【拓展】。</p><p>作为一个全面系统的 C++ 学习项目,CPlusPlusThings 是优秀的,它合理地安排了 10 Days 的实战部分,在实战中了解语法和函数用法,唯一不足的是,在注释部分有些不尽人意,对部分新手程序员并不是很友好。</p><p><a href="https://github.com/Light-City/CPlusPlusThings">CPlusPlusThings</a> <a href="https://light-city.club/sc/">C++那些事 (light-city.club)</a></p><h3 id="二、C-实现的算法合集:C-Plus-Plus"><a href="#二、C-实现的算法合集:C-Plus-Plus" class="headerlink" title="二、C++实现的算法合集:C-Plus-Plus"></a>二、C++实现的算法合集:C-Plus-Plus</h3><p>C-Plus-Plus 是收录用 C++ 实现的各种算法的集合,并按照 MIT 许可协议进行授权。这些算法涵盖了计算机科学、数学和统计学、数据科学、机器学习、工程等各种主题。除外,你可能会发现针对同一目标的多个实现使用不同的算法策略和优化。</p><p><a href="https://github.com/TheAlgorithms/C-Plus-Plus">C-Plus-Plus</a></p><h3 id="三、进阶指南:CppTemplateTutorial"><a href="#三、进阶指南:CppTemplateTutorial" class="headerlink" title="三、进阶指南:CppTemplateTutorial"></a>三、进阶指南:CppTemplateTutorial</h3><p>CppTemplateTutorial 为中文的 C++ Template 的教学指南。与知名书籍 C++ Templates 不同,该系列教程将 C++ Templates 作为一门图灵完备的语言来讲授,以求帮助读者对 Meta-Programming 融会贯通。本项目写作初衷,就是通过 “编程语言” 的视角,介绍一个简单、清晰的 “模板语言”。我会尽可能地将模板的诸多要素连串起来,用一些简单的例子帮助读者学习这门 “语言”,让读者在编写、阅读模板代码的时候,能像<code>if(exp) { dosomething(); }</code>一样的信手拈来,让 “模板元编程” 技术成为读者牢固掌握、可举一反三的有用技能。</p><p>适合熟悉 C++ 的基本语法、使用过 STL、熟悉一些常用的算法,以及递归等程序设计方法的 C++ 学习者阅读。虽然项目章节文章写的深入浅出,不过唯一的遗憾是尚未完成所有章节内容。进度如下:</p><p><a href="https://github.com/wuye9036/CppTemplateTutorial">CppTemplateTutorial</a> <a href="https://mp.weixin.qq.com/s/ntCoAKFz6dAQNKzsg1_0VA">c++资料</a></p><p>四、网络库</p><p><a href="https://github.com/yedf/handy">网络库</a></p><h2 id="小试牛刀篇"><a href="#小试牛刀篇" class="headerlink" title="小试牛刀篇"></a>小试牛刀篇</h2><h3 id="一、写一个小型STL库:MyTinySTL"><a href="#一、写一个小型STL库:MyTinySTL" class="headerlink" title="一、写一个小型STL库:MyTinySTL"></a>一、写一个小型STL库:MyTinySTL</h3><p>当你学习完 C++ 的“书本”知识后,是不是有些手痒了呢?MyTinySTL 这个注释详细、实践夯实基础的项目便是你 C++ 学习之旅的下一站。作为新手练习用途,MyTinySTL 的作者 Alinshans 用 C++11 重新复写了一个小型 STL(容器库+算法库)。代码结构清晰规范、包含中文文档与注释,并且自带一个简单的测试框架,适合 C++ 新手来实践一番。</p><p><a href="https://github.com/Alinshans/MyTinySTL">MyTinySTL</a></p><h3 id="二、小实战—俄罗斯广块:Tinytetris"><a href="#二、小实战—俄罗斯广块:Tinytetris" class="headerlink" title="二、小实战—俄罗斯广块:Tinytetris"></a>二、小实战—俄罗斯广块:Tinytetris</h3><p>Tinytetris 是一个用 C++ 编写的终端版俄罗斯方块游戏。它提供了两个版本的源码,分为注释版和库版,注释较多易于理解和学习。</p><p><a href="https://github.com/taylorconor/tinytetris">Tinytetris</a></p><h3 id="三、学习下大佬们的代码,做个计算器:calculator"><a href="#三、学习下大佬们的代码,做个计算器:calculator" class="headerlink" title="三、学习下大佬们的代码,做个计算器:calculator"></a>三、学习下大佬们的代码,做个计算器:calculator</h3><p>微软开源的 Windows 系统预装的计算器工具。该工具提供标准、科学、程序员计算器的功能,以及各种度量单位和货币之间的转换功能。快来看看微软工程师编写的代码吧!学习大厂的编码规范、项目结构之类的,提高阅读源码的能力。英文的项目且没有讲解部分,需要通过阅读源码学习,难度较高。</p><p><img src="https://pica.zhimg.com/80/v2-0987699fbf54eb4f4c51a24f359d1cbc_720w.jpg?source=1940ef5c" alt="计算器"></p><p><a href="https://github.com/microsoft/calculator">微软自带计算器</a></p><h2 id="大型项目篇:"><a href="#大型项目篇:" class="headerlink" title="大型项目篇:"></a>大型项目篇:</h2><h3 id="一、C-C-高频量化投资交易平台:EliteQuant-Cpp"><a href="#一、C-C-高频量化投资交易平台:EliteQuant-Cpp" class="headerlink" title="一、C/C++高频量化投资交易平台:EliteQuant Cpp"></a>一、C/C++高频量化投资交易平台:EliteQuant Cpp</h3><p>基于C/C++ 11的多线程并发式高频交易平台。它遵循现代设计模式,例如事件驱动,服务器/客户端架构,依赖注入和松散耦合的强大稳定的分布式系统。它可以独立运行和直接使用。同时,它也作为其他EliteQuant项目的服务器端。</p><p><a href="https://gitee.com/EliteQuant/EliteQuant_Cpp">EliteQuant Cpp</a></p><h3 id="二、开源网盘云存储-Seafile"><a href="#二、开源网盘云存储-Seafile" class="headerlink" title="二、开源网盘云存储 Seafile"></a>二、开源网盘云存储 Seafile</h3><p>Seafile是一个开源、专业、可靠的云存储平台;解决文件集中存储、共享和跨平台访问等问题</p><p>Seafile是一款强大优秀的云同步软件,拥有跨平台文件同步、移动端文件访问、挂载盘、文件共享和权限控制、文件锁定、文件版本管理和资料库镜像、在线编辑和协同编辑、审计日志等功能。</p><p><a href="https://github.com/haiwen/seafile">Seafile</a></p><h3 id="三、基于-C-Python-的开源量化交易研究框架-Hikyuu"><a href="#三、基于-C-Python-的开源量化交易研究框架-Hikyuu" class="headerlink" title="三、基于 C++/Python 的开源量化交易研究框架 Hikyuu"></a>三、基于 C++/Python 的开源量化交易研究框架 Hikyuu</h3><p>Hikyuu Quant Framework是一款基于C++/Python的开源量化交易研究框架,用于策略分析及回测。其核心思想基于当前成熟的系统化交易方法,将整个系统化交易抽象为由市场环境判断策略、系统有效条件、信号指示器、止损/止盈策略、资金管理策略、盈利目标策略、移滑价差算法七大组件</p><p>你可以分别构建这些组件的策略资产库,在实际研究中对它们自由组合来观察系统的有效性、稳定性以及单一种类策略的效果。</p><p><a href="https://github.com/fasiondog/hikyuu"> Hikyuu</a></p><h3 id="四、开源自动驾驶平台-ApolloAuto"><a href="#四、开源自动驾驶平台-ApolloAuto" class="headerlink" title="四、开源自动驾驶平台 ApolloAuto"></a>四、开源自动驾驶平台 ApolloAuto</h3><p>Apollo (阿波罗)是一个开放的、完整的、安全的平台,将帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统,快速搭建一套属于自己的自动驾驶系统。</p><p>Apollo 是百度重点打造的 AI 开放平台之一,计划主要包含 4 个技术模块:定位/感知模块、车辆规划与运营(AI+大数据,精准控制车辆,适合不同路况)、软件运营框架(支持英特尔、英伟达等多种芯片)。</p><p><a href="https://www.oschina.net/p/apolloauto"> ApolloAuto</a></p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="C-后台"><a href="#C-后台" class="headerlink" title="C++后台"></a>C++后台</h1><h2 id="学习篇:"><a href="#学习篇:" class="headerlink" title="学习篇:"><</summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
<category term="后端" scheme="https://zyjblogs.cn/tags/%E5%90%8E%E7%AB%AF/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>C++编译期多态与运行期多态</title>
|
||
<link href="https://zyjblogs.cn/posts/35899.html"/>
|
||
<id>https://zyjblogs.cn/posts/35899.html</id>
|
||
<published>2022-02-06T16:00:00.000Z</published>
|
||
<updated>2023-10-16T06:46:32.549Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="C-编译期多态与运行期多态"><a href="#C-编译期多态与运行期多态" class="headerlink" title="C++编译期多态与运行期多态"></a>C++编译期多态与运行期多态</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a><strong>前言</strong></h2><p>今日的C++不再是个单纯的“带类的C”语言,它已经发展成为一个多种次语言所组成的语言集合,其中泛型编程与基于它的STL是C++发展中最为出彩的那部分。在面向对象C++编程中,多态是OO三大特性之一,这种多态称为运行期多态,也称为动态多态;在泛型编程中,多态基于template(模板)的具现化与函数的重载解析,这种多态在编译期进行,因此称为编译期多态或静态多态。在本文中,我们将了解:</p><ul><li>什么是运行期多态</li><li>什么是编译期多态</li><li>它们的优缺点在哪</li></ul><h2 id="运行期多态"><a href="#运行期多态" class="headerlink" title="运行期多态"></a><strong>运行期多态</strong></h2><p>运行期多态的设计思想要归结到类继承体系的设计上去。对于有相关功能的对象集合,我们总希望能够抽象出它们共有的功能集合,在基类中将这些功能声明为虚接口(虚函数),然后由子类继承基类去重写这些虚接口,以实现子类特有的具体功能。典型地我们会举下面这个例子:</p><p><img src="/posts/35899/images/2021-9-30-640.png" alt="图片"></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Animal</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span> :</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">shout</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dog</span> :<span class="keyword">public</span> Animal</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">shout</span><span class="params">()</span></span>{ cout << <span class="string">"汪汪!"</span><<endl; }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Cat</span> :<span class="keyword">public</span> Animal</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">shout</span><span class="params">()</span></span>{ cout << <span class="string">"喵喵~"</span><<endl; }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Bird</span> : <span class="keyword">public</span> Animal</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">shout</span><span class="params">()</span></span>{ cout << <span class="string">"叽喳!"</span><<endl; }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Animal * anim1 = <span class="keyword">new</span> Dog;</span><br><span class="line"> Animal * anim2 = <span class="keyword">new</span> Cat;</span><br><span class="line"> Animal * anim3 = <span class="keyword">new</span> Bird;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//藉由指针(或引用)调用的接口,在运行期确定指针(或引用)所指对象的真正类型,调用该类型对应的接口</span></span><br><span class="line"> anim1-><span class="built_in">shout</span>();</span><br><span class="line"> anim2-><span class="built_in">shout</span>();</span><br><span class="line"> anim3-><span class="built_in">shout</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//delete 对象</span></span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>运行期多态的实现依赖于虚函数机制。当某个类声明了虚函数时,编译器将为该类对象安插一个虚函数表指针,并为该类设置一张唯一的虚函数表,虚函数表中存放的是该类虚函数地址。运行期间通过虚函数表指针与虚函数表去确定该类虚函数的真正实现。</p></blockquote><blockquote><p>运行期多态的优势还在于它使处理异质对象集合称为可能:</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//我们有个动物园,里面有一堆动物</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> vector<Animal*>anims;</span><br><span class="line"></span><br><span class="line"> Animal * anim1 = <span class="keyword">new</span> Dog;</span><br><span class="line"> Animal * anim2 = <span class="keyword">new</span> Cat;</span><br><span class="line"> Animal * anim3 = <span class="keyword">new</span> Bird;</span><br><span class="line"> Animal * anim4 = <span class="keyword">new</span> Dog;</span><br><span class="line"> Animal * anim5 = <span class="keyword">new</span> Cat;</span><br><span class="line"> Animal * anim6 = <span class="keyword">new</span> Bird;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//处理异质类集合</span></span><br><span class="line"> anims.<span class="built_in">push_back</span>(anim1);</span><br><span class="line"> anims.<span class="built_in">push_back</span>(anim2);</span><br><span class="line"> anims.<span class="built_in">push_back</span>(anim3);</span><br><span class="line"> anims.<span class="built_in">push_back</span>(anim4);</span><br><span class="line"> anims.<span class="built_in">push_back</span>(anim5);</span><br><span class="line"> anims.<span class="built_in">push_back</span>(anim6);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">auto</span> & i : anims)</span><br><span class="line"> {</span><br><span class="line"> i-><span class="built_in">shout</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//delete对象</span></span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>总结:<strong>运行期多态通过虚函数发生于运行期</strong></p><h2 id="编译期多态"><a href="#编译期多态" class="headerlink" title="编译期多态"></a><strong>编译期多态</strong></h2><p>对模板参数而言,多态是通过模板具现化和函数重载解析实现的。<strong>以不同的模板参数具现化导致调用不同的函数</strong>,这就是所谓的编译期多态。<br>相比较于运行期多态,实现编译期多态的类之间并不需要成为一个继承体系,它们之间可以没有什么关系,但约束是它们都有相同的隐式接口。我们将上面的例子改写为:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Animal</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span> :</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">shout</span><span class="params">()</span> </span>{ cout << <span class="string">"发出动物的叫声"</span> << endl; };</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dog</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">shout</span><span class="params">()</span></span>{ cout << <span class="string">"汪汪!"</span><<endl; }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Cat</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">shout</span><span class="params">()</span></span>{ cout << <span class="string">"喵喵~"</span><<endl; }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Bird</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">shout</span><span class="params">()</span></span>{ cout << <span class="string">"叽喳!"</span><<endl; }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">animalShout</span><span class="params">(T & t)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> t.<span class="built_in">shout</span>();</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Animal anim;</span><br><span class="line"> Dog dog;</span><br><span class="line"> Cat cat;</span><br><span class="line"> Bird bird;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">animalShout</span>(anim);</span><br><span class="line"> <span class="built_in">animalShout</span>(dog);</span><br><span class="line"> <span class="built_in">animalShout</span>(cat);</span><br><span class="line"> <span class="built_in">animalShout</span>(bird);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">getchar</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在编译之前,函数模板中t.shout()调用的是哪个接口并不确定。在编译期间,编译器推断出模板参数,因此确定调用的shout是哪个具体类型的接口。<strong>不同的推断结果调用不同的函数</strong>,这就是编译器多态。这类似于重载函数在编译器进行推导,以确定哪一个函数被调用。</p><h2 id="运行期多态与编译期多态优缺点分析"><a href="#运行期多态与编译期多态优缺点分析" class="headerlink" title="运行期多态与编译期多态优缺点分析"></a><strong>运行期多态与编译期多态优缺点分析</strong></h2><h3 id="运行期多态优点"><a href="#运行期多态优点" class="headerlink" title="运行期多态优点"></a>运行期多态优点</h3><ul><li><p>OO设计中重要的特性,对客观世界直觉认识。</p></li><li><p>能够处理同一个继承体系下的异质类集合。</p></li></ul><h3 id="运行期多态缺点"><a href="#运行期多态缺点" class="headerlink" title="运行期多态缺点"></a><strong>运行期多态缺点</strong></h3><ul><li>运行期间进行虚函数绑定,提高了程序运行开销。</li><li>庞大的类继承层次,对接口的修改易影响类继承层次。</li><li>由于虚函数在运行期在确定,所以编译器无法对虚函数进行优化。</li></ul><p>虚表指针增大了对象体积,类也多了一张虚函数表,当然,这是理所应当值得付出的资源消耗,列为缺点有点勉强。</p><h3 id="编译期多态优点"><a href="#编译期多态优点" class="headerlink" title="编译期多态优点"></a><strong>编译期多态优点</strong></h3><ul><li><p>它带来了泛型编程的概念,使得C++拥有泛型编程与STL这样的强大武器。</p></li><li><p>在编译器完成多态,提高运行期效率。</p></li><li><p>具有很强的适配性与松耦合性,对于特殊类型可由模板偏特化、全特化来处理。</p></li></ul><h3 id="编译期多态缺点"><a href="#编译期多态缺点" class="headerlink" title="编译期多态缺点"></a><strong>编译期多态缺点</strong></h3><ul><li>程序可读性降低,代码调试带来困难。</li><li>无法实现模板的分离编译,当工程很大时,编译时间不可小觑。</li><li>无法处理异质对象集合。</li></ul><h2 id="关于显式接口与隐式接口"><a href="#关于显式接口与隐式接口" class="headerlink" title="关于显式接口与隐式接口"></a><strong>关于显式接口与隐式接口</strong></h2><p>所谓的显式接口是指类继承层次中定义的接口或是某个具体类提供的接口,总而言之,我们能够在源代码中找到这个接口.显式接口以函数签名为中心,例如</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">AnimalShot</span><span class="params">(Animal & anim)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> anim.<span class="built_in">shout</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们称shout为一个显式接口。在运行期多态中的接口皆为显式接口。</p><p>而对模板参数而言,接口是隐式的,奠基于有效表达式。例如:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">AnimalShot</span><span class="params">(T & anim)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> anim.<span class="built_in">shout</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>对于anim来说,必须支持哪一种接口,要由模板参数执行于anim身上的操作来决定,在上面这个例子中,T必须支持shout()操作,那么shout就是T的一个隐式接口。</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="C-编译期多态与运行期多态"><a href="#C-编译期多态与运行期多态" class="headerlink" title="C++编译期多态与运行期多态"></a>C++编译期多态与运行期多态</h1><h2 id="前言"><a href="#前言" c</summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>虚基类</title>
|
||
<link href="https://zyjblogs.cn/posts/11902.html"/>
|
||
<id>https://zyjblogs.cn/posts/11902.html</id>
|
||
<published>2022-02-06T16:00:00.000Z</published>
|
||
<updated>2023-10-09T02:48:53.723Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="虚基类"><a href="#虚基类" class="headerlink" title="虚基类"></a>虚基类</h1><h2 id="1、使用virtual修饰"><a href="#1、使用virtual修饰" class="headerlink" title="1、使用virtual修饰"></a>1、使用virtual修饰</h2><blockquote><p> 基类是虚的时候静止信息通过中间类传递给基类</p><p>需要显示的调用所需的基类构造函数</p></blockquote><p>1.为什么要引入虚基类?<br>在类的继承中,如果我们遇到这种情况:<br>“B和C同时继承A,而B和C都被D继承”<br>在此时,假如A中有一个函数fun()当然同时被B和C继承,而D按理说继承了B和C,同时也应该能调用fun()函数。这一调用就有问题了,到底是要调用B中的fun()函数还是调用C中的fun()函数呢?在C++中,有两种方法实现调用:<br>(注意:这两种方法效果是不同的)</p><p>使用作用域标识符来唯一表示它们比如:B::fun()<br>另一种方法是定义虚基类,使派生类中只保留一份拷贝。<br>作用域标识符表示<br>例子:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">base</span>{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">base</span>(){a=<span class="number">5</span>;cout<<<span class="string">"base="</span><<a<<endl;}</span><br><span class="line"> procted:</span><br><span class="line"> <span class="type">int</span> a;</span><br><span class="line">}**;**</span><br><span class="line"> <span class="keyword">class</span> <span class="title class_">base1</span>:<span class="keyword">public</span> base{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">base1</span>()</span><br><span class="line"> {a=a+<span class="number">10</span>;cout<<<span class="string">"base1="</span><<a<<endl;}</span><br><span class="line"> };</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">base2</span>:<span class="keyword">public</span> base{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">base2</span>(){a=a+<span class="number">20</span>;cout<<<span class="string">"base2="</span><<a<<endl;}</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">derived</span>:<span class="keyword">public</span> base1, <span class="keyword">public</span> base2{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">derived</span>(){</span><br><span class="line"> cout<<<span class="string">"base1::a="</span><<base1::a<<endl;</span><br><span class="line"> cout<<<span class="string">"base2::a="</span>base2::a<<endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{derived obj;</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></pre></td></tr></table></figure><blockquote><p>这是第一种方法的典型例子。写的时候新手要注意几个易敲错的点:</p><p>1.多继承定义的时候是一个权限名对应一个基类,class derived:public base1, public base2 不能是class derived:public base1,base2</p><p>2.注意相邻两个基类的说明是用逗号分隔,不要再忘了。</p><p>3.老生常谈的问题吧,不要忘记类定义最后的那个分号!!!!!<br>(我自己真的老是忘记)</p></blockquote><p>这段程序的调用顺序一定要学会熟练分析:</p><blockquote><p>1.开始定义base1,而base1继承了base类,所以base1的定义又要回到base的定义,所以先执行base的构造函数base(){a=5;cout<<”base=”<<a<<endl;}这时显示第一条base a=5.<br>2.随后,调用base1的构造函数,显示base1 a=15 这时base1定义完毕。<br>3.开始调用base2,而base2同样继承了base类,所以base2的定义又要再次回到base的构造函数所以这时输出的是base a=5 。<br>4.随后再调用base2的构造函数,输出base2 a=25 。<br>5.最后在derived中分作用域调用a,虽然是同样名称的变量a,但在base1的作用域中表现为a=15,在base2作用域中表现为a=25。</p></blockquote><p>所以这里最后的答案为:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">base a=5</span><br><span class="line">base1 a=15</span><br><span class="line">base a=5</span><br><span class="line">base2 a=25</span><br><span class="line">base1::a=15</span><br><span class="line">base2::a=25</span><br></pre></td></tr></table></figure><p>实际上构造函数调用可以通过树状图来写,特别是对于多级继承关系,可以写出每一级里面继承的基类,而每一层最后一个树枝是该类的构造函数,而每一个基类又可以用同样的方法展开,直到分离到最后完全没有继承关系的基类为止。</p><p>虚基类的调用:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">base</span>{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">base</span>(){</span><br><span class="line"> a=<span class="number">5</span>;cout<<<span class="string">"base="</span><<a<<endl;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">protected</span>:</span><br><span class="line"> <span class="type">int</span> a;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">base1</span>:<span class="keyword">virtual</span> <span class="keyword">public</span> base{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">base1</span>(){</span><br><span class="line"> a+=<span class="number">10</span>;cout<<<span class="string">"base1="</span><<a<<endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">base2</span>:<span class="keyword">virtual</span> <span class="keyword">public</span> base{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">base2</span>(){a+=<span class="number">20</span>;cout<<<span class="string">"base2="</span><<a<<endl;}</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">derived</span>:<span class="keyword">public</span> base1,<span class="keyword">public</span> base2{</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">derived</span>(){cout<<<span class="string">"derived a ="</span><<a<<endl; }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> derived obj;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在定义了虚基类后,就等于告诉了系统,这里的a是base1和base2所共有的,对于调用base1和base2构造函数的修改都是针对同一个a而言(也就是基类和两个派生类所共有的)。而对于第一个例子中针对作用域的,相当于在继承时把a拷贝给了base1和base2,而彼此之间的a是无关联的。<br>这个过程最后为:<br>1.设定为虚基类后,系统知道base1和base2都是由base派生出的,所以它就统一先构造base,调用base的构造函数。<br>2.再按照顺序调用base1和base2的构造函数,只不过在此时,大家在构造时操作的都是同一个a。<br>所以在虚基类中,其构造顺序的思路是反着来的:</p><p>虚基类的另一种理解:虚基类的核心在于这个“虚”字,base1和base2本身作为虚基类相当于算是基类base的两个延伸(就相当于是base的一个外挂),而对于derived类来说,最本质的基类还是base,而基类base与虚基类base1和base2组成一个基类体系,或者一个基类生态,通过对这个生态中不同虚基类的继承,就可以形成不同的接口,生成不同的派生类。</p><h3 id="虚基类的初始化:"><a href="#虚基类的初始化:" class="headerlink" title="虚基类的初始化:"></a>虚基类的初始化:</h3><blockquote><p>(1)如果在虚基类中定义有带形参的构造函数<strong>,并且没有定义缺省形参的构造函数,则整个继承结构中,所有直接或者间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用。</strong></p></blockquote><p>这句话是什么意思呢?我们改造上面的代码:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"> <span class="meta">#<span class="keyword">include</span><span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">base</span>{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">base</span>(<span class="type">int</span> s){</span><br><span class="line"> a=s;cout<<<span class="string">"base="</span><<a<<endl;}</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> <span class="type">int</span> a;</span><br><span class="line">};<span class="comment">//注意点1:base()构造函数里面有定义形参,所以此时下面的base1,base2</span></span><br><span class="line"><span class="comment">//虚基类的构造函数在定义时要列出对该基类构造函数的调用。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">base1</span>:<span class="keyword">virtual</span> <span class="keyword">public</span> base{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">base1</span>(<span class="type">int</span> s,<span class="type">int</span> h):<span class="built_in">base</span>(s){a+=h;cout<<<span class="string">"base1="</span><<a<<endl;}</span><br><span class="line">};<span class="comment">//注意点2:虚基类base1的第一个括号内是**“总表**”也就是里面既要有输入上基</span></span><br><span class="line"><span class="comment">//类的构造函数的参数,又要包括自己独有的参数</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">base2</span>:<span class="keyword">virtual</span> <span class="keyword">public</span> base{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">base2</span>(<span class="type">int</span> s):<span class="built_in">base</span>(s){a+=<span class="number">20</span>;cout<<<span class="string">"base2="</span><<a<<endl;}</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">derived</span>:<span class="keyword">public</span> base1,<span class="keyword">public</span> base2{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">derived</span>(<span class="type">int</span> s,<span class="type">int</span> h,<span class="type">int</span> d):<span class="built_in">base</span>(s),<span class="built_in">base1</span>(s,h),<span class="built_in">base2</span>(s){cout<<<span class="string">"derived a ="</span><<a+d<<endl; }<span class="comment">//注意点3:此处也一样,前面的括号里是总表,不要忘记基类的形参int s。注</span></span><br><span class="line"><span class="comment">//意,此时base基类一定是先放第一个的,之后才是虚基类,而虚基类间顺序没有要求。</span></span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="function">derived <span class="title">obj</span><span class="params">(<span class="number">5</span>,<span class="number">8</span>,<span class="number">9</span>)</span></span>;<span class="comment">//注意点4:此处的填数顺序和derived的构造函数的参数顺序一样,相当于在derived的构造函数中,冒号前的括号在接收数据,冒号后是在将接收到的数据分配到各个构造函数。</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>注意点1:基类构造函数里面有定义形参,所以此时下面的base1,base2虚基类的构造函数在定义时要列出对该基类构造函数的调用。</p><p>注意点2:虚基类base1的第一个括号内是<strong>“总表</strong>”也就是里面既要有输入上基类的构造函数的参数,又要包括自己独有的参数。</p><p>注意点3:此处也一样,前面的括号里是总表,不要忘记基类的形参int s。注意,此时基类构造函数一定是先放第一个的,之后才是虚基类,而虚基类间顺序没有要求。</p><p>注意点4:在主函数定义变量时的填数顺序和derived的构造函数的参数顺序一样,相当于在derived的构造函数中,冒号前的括号在接收数据,冒号后是在将接收到的数据分配到各个构造函数。</p><p>(2)如果一个虚基类派生出了多个派生类,那么决定虚基类成员的,是那个最远的派生类所调用的构造函数,而其他派生类调用的构造函数会被自动忽略。如果是同级的话(一样远),那就按照最后一个派生类调用的构造函数为准(比如图中以子类1.1.1.1.1的调用为准,因为最远)</p></blockquote>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="虚基类"><a href="#虚基类" class="headerlink" title="虚基类"></a>虚基类</h1><h2 id="1、使用virtual修饰"><a href="#1、使用virtual修饰" class="headerlink" ti</summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
</entry>
|
||
|
||
<entry>
|
||
<title>C++ 八股文(一)</title>
|
||
<link href="https://zyjblogs.cn/posts/15563.html"/>
|
||
<id>https://zyjblogs.cn/posts/15563.html</id>
|
||
<published>2022-02-06T16:00:00.000Z</published>
|
||
<updated>2023-10-09T02:48:35.573Z</updated>
|
||
|
||
<content type="html"><![CDATA[<h1 id="C-八股文(一)"><a href="#C-八股文(一)" class="headerlink" title="C++ 八股文(一)"></a>C++ 八股文(一)</h1><h2 id="多态"><a href="#多态" class="headerlink" title="多态"></a>多态</h2><h3 id="什么是多态,有什么用"><a href="#什么是多态,有什么用" class="headerlink" title="什么是多态,有什么用"></a>什么是多态,有什么用</h3><p>C++ 多态有两种:静态多态(早绑定)、动态多态(晚绑定)。静态多态是通过函数重载实现的;动态多态是通过虚函数实现的。</p><ul><li>定义:“一个接口,多种方法”,程序在运行时才决定要调用的函数。</li><li>实现:C++ 多态性主要是通过虚函数实现的,虚函数允许子类重写 override(注意和 overload 的区别,overload 是重载,是允许同名函数的表现,这些函数参数列表/类型不同)。</li></ul><p>注:多态与非多态的实质区别就是函数地址是静态绑定还是动态绑定。如果函数的调用在编译器编译期间就可以确定函数的调用地址,并产生代码,说明地址是静态绑定的;如果函数调用的地址是需要在运行期间才确定,属于动态绑定。</p><ul><li>目的:接口重用。封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。</li><li>用法:声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。</li></ul><p>用一句话概括:在基类的函数前加上 virtual 关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。</p><h2 id="重写、重载与隐藏的区别"><a href="#重写、重载与隐藏的区别" class="headerlink" title="重写、重载与隐藏的区别"></a>重写、重载与隐藏的区别</h2><h3 id="Overload-重载"><a href="#Overload-重载" class="headerlink" title="Overload 重载"></a>Overload 重载</h3><p>在 C++ 程序中,可以将语义、功能相似的几个函数用同一个名字表示,但参数或返回值不同(包括类型、顺序不同),即函数重载。</p><ul><li>相同的范围(在同一个类中);</li><li>函数名字相同;</li><li>参数不同;</li><li>virtual 关键字可有可无;</li></ul><h3 id="Override-覆盖或重写"><a href="#Override-覆盖或重写" class="headerlink" title="Override(覆盖或重写)"></a>Override(覆盖或重写)</h3><p>是指派生类函数覆盖基类函数,特征是:</p><ul><li>不同的范围(分别位于派生类与基类);</li><li>函数名字相同;参数相同;</li><li>基类函数必须有 virtual 关键字。</li></ul><p>注:重写基类虚函数的时候,会自动转换这个函数为 virtual 函数,不管有没有加 virtual,因此重写的时候不加 virtual 也是可以的,不过为了易读性,还是加上比较好。</p><h3 id="Overwrite-重写-隐藏,"><a href="#Overwrite-重写-隐藏," class="headerlink" title="Overwrite(重写)隐藏,"></a>Overwrite(重写)隐藏,</h3><p>是指派生类的函数屏蔽了与其同名的基类函数,规则如下:</p><ul><li>如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载混淆)。</li><li>如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。</li></ul><h3 id="虚函数和纯虚函数"><a href="#虚函数和纯虚函数" class="headerlink" title="虚函数和纯虚函数"></a>虚函数和纯虚函数</h3><ul><li>虚函数:为了实现动态绑定。使用基类的引用或指针调用虚函数的时候会发生动态绑定。</li><li>纯虚函数:抽象类</li><li>构造函数可以重载,但不能是虚函数,析构函数可以是虚函数。</li></ul><h3 id="基类为什么需要虚析构函数?"><a href="#基类为什么需要虚析构函数?" class="headerlink" title="基类为什么需要虚析构函数?"></a>基类为什么需要虚析构函数?</h3><p>防止内存泄漏。想去借助父类指针去销毁子类对象的时候,不能去销毁子类对象。假如没有虚析构函数,释放一个由基类指针指向的派生类对象时,不会触发动态绑定,则只会调用基类的析构函数,不会调用派生类的。派生类中申请的空间则得不到释放导致内存泄漏。</p><h3 id="构造-析构函数调用虚函数"><a href="#构造-析构函数调用虚函数" class="headerlink" title="构造/析构函数调用虚函数"></a>构造/析构函数调用虚函数</h3><p>派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。</p><p>同样,进入基类析构函数时,对象也是基类类型。</p><p>所以,虚函数始终仅仅调用基类的虚函数(如果是基类调用虚函数),不能达到多态的效果。</p><h3 id="虚函数表"><a href="#虚函数表" class="headerlink" title="虚函数表"></a>虚函数表</h3><ul><li>产生时间:编译期</li><li>存储位置:只读数据段 .rodata</li><li>虚指针:类的每一个对象都包含一个虚指针(指向虚表),存在对象实例的最前面四个字节</li><li>虚指针创建时间:构造函数</li></ul><p>注:虚表中的指针会指向其继承的最近的一个类的虚函数</p><h2 id="const-相关"><a href="#const-相关" class="headerlink" title="const 相关"></a>const 相关</h2><h2 id="如何初始化-const-和-static-数据成员?"><a href="#如何初始化-const-和-static-数据成员?" class="headerlink" title="如何初始化 const 和 static 数据成员?"></a>如何初始化 const 和 static 数据成员?</h2><p>通常在类外申明 static 成员,但是 static const 的整型( bool,char,int,long )可以在类中声明且初始化,static const 的其他类型必须在类外初始化(包括整型数组)。</p><h3 id="static-和-const-分别怎么用,类里面-static-和-const-可以同时修饰成员函数吗?"><a href="#static-和-const-分别怎么用,类里面-static-和-const-可以同时修饰成员函数吗?" class="headerlink" title="static 和 const 分别怎么用,类里面 static 和 const 可以同时修饰成员函数吗?"></a>static 和 const 分别怎么用,类里面 static 和 const 可以同时修饰成员函数吗?</h3><p>static 的作用:对 static 的三条作用做一句话总结。首先 static 的最主要功能是隐藏,其次因为 static 变量存放在静态存储区,所以它具备持久性和默认值 0。</p><h2 id="对变量"><a href="#对变量" class="headerlink" title="对变量"></a>对变量</h2><h3 id="局部变量"><a href="#局部变量" class="headerlink" title="局部变量"></a>局部变量</h3><p>在局部变量之前加上关键字 static,局部变量就被定义成为一个局部静态变量。</p><ul><li>内存中的位置:静态存储区</li><li>初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)</li><li>作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。</li></ul><p>注:当 static 用来修饰局部变量的时候,它就改变了局部变量的存储位置(从原来的栈中存放改为静态存储区)及其生命周期(局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问),但未改变其作用域。</p><h3 id="全局变量"><a href="#全局变量" class="headerlink" title="全局变量"></a>全局变量</h3><p>在全局变量之前加上关键字 static,全局变量就被定义成为一个全局静态变量。</p><ul><li>内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)</li><li>初始化:未经初始化的全局静态变量会被程序自动初始化为 0(自动对象的值是任意的,除非他被显示初始化)</li><li>作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。</li></ul><p>注:static 修饰全局变量,并未改变其存储位置及生命周期,而是改变了其作用域,使当前文件外的源文件无法访问该变量,好处如下:</p><ul><li>不会被其他文件所访问,修改</li><li>其他文件中可以使用相同名字的变量,不会发生冲突。对全局函数也是有隐藏作用。而普通全局变量只要定义了,任何地方都能使用,使用前需要声明所有的 .c 文件,只能定义一次普通全局变量,但是可以声明多次(外部链接)。</li></ul><p>注意:全局变量的作用域是全局范围,但是在某个文件中使用时,必须先声明。</p><h2 id="对类"><a href="#对类" class="headerlink" title="对类"></a>对类</h2><h3 id="成员变量"><a href="#成员变量" class="headerlink" title="成员变量"></a>成员变量</h3><p>用 static 修饰类的数据成员实际使其成为类的全局变量,会被类的所有对象共享,包括派生类的对象。因此,static 成员必须在类外进行初始化(初始化格式:int base::var=10;),而不能在构造函数内进行初始化,不过也可以用 const 修饰 static 数据成员在类内初始化 。因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。</p><p>特点:</p><ul><li>不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上<code>#ifndef</code> <code>#define</code> <code>#endif</code>或者<code>#pragma once</code>也不行。</li><li>静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。</li><li>静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的指针或引用。</li></ul><h3 id="成员函数"><a href="#成员函数" class="headerlink" title="成员函数"></a>成员函数</h3><ul><li>用 static 修饰成员函数,使这个类只存在这一份函数,所有对象共享该函数,不含 this 指针。</li><li>静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。<code>base::func(5,3)</code>;当 static 成员函数在类外定义时不需要加 static 修饰符。</li><li>在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。因为静态成员函数不含this指针。</li></ul><p>不可以同时用 const 和 static 修饰成员函数。</p><p>C++ 编译器在实现 const 的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数 const this*。但当一个成员为 static 的时候,该函数是没有 this 指针的。也就是说此时 const 的用法和 static 是冲突的。</p><p>我们也可以这样理解:两者的语意是矛盾的。static 的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而 const 的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。</p><p>const的作用:</p><ul><li>限定变量为不可修改。</li><li>限定成员函数不可以修改任何数据成员。</li><li>const 与指针:</li></ul><p><code>const char *p</code> 常量指针,可以换方向,不可以改内容</p><p><code>char * const p</code>,指针常量,不可以换方向,可以改内容</p><h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><h3 id="构造函数调用顺序"><a href="#构造函数调用顺序" class="headerlink" title="构造函数调用顺序"></a>构造函数调用顺序</h3><ul><li>虚基类构造函数(被继承的顺序)</li><li>非虚基类构造函数(被继承的顺序)</li><li>成员对象构造函数(声明顺序)</li><li>自己的构造函数</li></ul><h3 id="自身构造函数顺序"><a href="#自身构造函数顺序" class="headerlink" title="自身构造函数顺序"></a>自身构造函数顺序</h3><ul><li>虚表指针(防止初始化列表里面调用虚函数,否则调用的是父类的虚函数)</li><li>初始化列表(const、引用、没有定义默认构造函数的类型)</li><li>花括号里的 (初始化列表直接初始化,这个先初始化后赋值)</li></ul><h3 id="this-指针"><a href="#this-指针" class="headerlink" title="this 指针"></a>this 指针</h3><p>创建时间:成员函数调用前生成,调用后清除</p><p>如何传递给成员函数:通过函数参数的首参数来传递</p><h3 id="extern-关键字"><a href="#extern-关键字" class="headerlink" title="extern 关键字"></a>extern 关键字</h3><ul><li>置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义</li><li>extern “C” void fun(); 告诉编译器按C的规则去翻译</li></ul><h3 id="以下关键字的作用?使用场景?"><a href="#以下关键字的作用?使用场景?" class="headerlink" title="以下关键字的作用?使用场景?"></a>以下关键字的作用?使用场景?</h3><ul><li>inline:在 c/c++ 中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。</li><li>decltype:从表达式中推断出要定义变量的类型,但却不想用表达式的值去初始化变量。还有可能是函数的返回类型为某表达式的的值类型。</li><li>volatile:volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。</li></ul><h3 id="浅拷贝与深拷贝"><a href="#浅拷贝与深拷贝" class="headerlink" title="浅拷贝与深拷贝"></a>浅拷贝与深拷贝</h3><p>什么时候用到拷贝函数?</p><ul><li>一个对象以值传递的方式传入函数体(参数);</li><li>一个对象以值传递的方式从函数返回(返回值);</li><li>一个对象需要通过另外一个对象进行初始化(初始化)。</li></ul><p>如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝</p><p>默认拷贝构造函数是浅拷贝。如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。</p><p>如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如 A=B。这时,如果 B 中有一个成员变量指针已经申请了内存,那 A 中的那个成员变量也指向同一块内存。这就出现了问题:当 B 把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。</p><h3 id="C-类中成员初始化顺序"><a href="#C-类中成员初始化顺序" class="headerlink" title="C++类中成员初始化顺序"></a>C++类中成员初始化顺序</h3><p>成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。</p><p>类中 const 成员常量必须在构造函数初始化列表中初始化。类中 static 成员变量,只能在类外初始化(同一类的所有实例共享静态成员变量)。</p><h3 id="构造过程"><a href="#构造过程" class="headerlink" title="构造过程"></a>构造过程</h3><ul><li>分配内存</li><li>进行父类的构造,按照父类的声明顺序(递归过程)</li><li>构造虚表指针,对虚表指针赋值</li><li>根据初始化列表中的值初始化变量</li><li>执行构造函数{}内的</li></ul><h3 id="构造函数初始化列表"><a href="#构造函数初始化列表" class="headerlink" title="构造函数初始化列表"></a>构造函数初始化列表</h3><p>const 或引用类型的成员。因为 const 对象或引用类型只能初始化,不能对他们赋值。</p><p>与对数据成员赋值的区别:</p><ul><li>内置数据类型,复合类型(指针,引用):结果和性能上相同。</li><li>用户定义类型(类类型):结果上相同,但是性能上存在很大的差别。</li></ul><h3 id="vector-中-size-和-capacity-的区别"><a href="#vector-中-size-和-capacity-的区别" class="headerlink" title="vector 中 size() 和 capacity() 的区别"></a>vector 中 size() 和 capacity() 的区别</h3><p>size() 指容器当前拥有的元素个数(对应的<code>resize(size_type)</code>会在容器尾添加或删除一些元素,来调整容器中实际的内容,使容器达到指定的大小。);<code>capacity()</code>指容器在必须分配存储空间之前可以存储的元素总数。</p><p>size 表示的这个 vector 里容纳了多少个元素,capacity 表示 vector 能够容纳多少元素,它们的不同是在于 vector 的 size 是 2 倍增长的。如果 vector 的大小不够了,比如现在的 capacity 是 4,插入到第五个元素的时候,发现不够了,此时会给他重新分配 8 个空间,把原来的数据及新的数据复制到这个新分配的空间里。(会有迭代器失效的问题)</p><h3 id="定义一个空类编译器做了哪些操作"><a href="#定义一个空类编译器做了哪些操作" class="headerlink" title="定义一个空类编译器做了哪些操作"></a>定义一个空类编译器做了哪些操作</h3><p>如果你只是声明一个空类,不做任何事情的话,编译器会自动为你生成一个默认构造函数、一个拷贝默认构造函数、一个默认拷贝赋值操作符和一个默认析构函数。这些函数只有在第一次被调用时,才会被编译器创建。所有这些函数都是 inline 和 public 的。</p><h3 id="强制类型转换"><a href="#强制类型转换" class="headerlink" title="强制类型转换"></a>强制类型转换</h3><p><strong>static_cast</strong></p><p>用法:<code>static_cast < type-id > ( expression )</code></p><p>q1. 为什么需要 static_cast 强制转换?</p><ul><li>void指针->其他类型指针 (不安全)</li><li>改变通常的标准转换</li><li>用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。</li></ul><p><strong>dynamic_cast</strong></p><p>用法:<code>dynamic_cast < type-id > ( expression )</code></p><p>dynamic_cast 主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(同一基类的两个同级派生类)。</p><p>在类层次间进行上行转换时,<code>dynamic_cast</code>和<code>static_cast</code>的效果是一样的;在进行下行转换时,<code>dynamic_cast</code>具有类型检查的功能,比<code>static_cast</code>更安全。</p><p><strong>reinpreter_cast</strong></p><p>它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。</p><p><strong>const_cast</strong>该运算符用来修改类型的 const 或 volatile 属性。除了 const 或 volatile 修饰之外, type_id 和 expression 的类型是一样的。</p><p>常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。</p><p><strong>volatile 关键字</strong></p><ul><li>使用方法:<code>int volatile x</code>;</li><li>作用:编译器不再优化。让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值 。</li></ul><h2 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h2><h3 id="C-内存分配"><a href="#C-内存分配" class="headerlink" title="C 内存分配"></a>C 内存分配</h3><ul><li>malloc:在内存的动态分配区域中分配一个长度为 size 的连续空间,如果分配成功,则返回所分配内存空间的首地址,否则返回 NULL,申请的内存不会初始化。</li><li>calloc:分配一个 num * size 连续的空间,会自动初始化为0。</li><li>realloc:动态分配一个长度为 size 的内存空间,并把内存空间的首地址赋值给 ptr,把 ptr 内存空间调整为 size。</li></ul><h3 id="C-内存分配:"><a href="#C-内存分配:" class="headerlink" title="C++ 内存分配:"></a>C++ 内存分配:</h3><p>-栈区(stack):主要存放函数参数以及局部变量,由系统自动分配释放。</p><ul><li>堆区(heap):由用户通过 malloc/new 手动申请,手动释放。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。</li><li>全局/静态区:存放全局变量、静态变量;程序结束后由系统释放。- - 字符串常量区:字符串常量就放在这里,程序结束后由系统释放。</li><li>代码区:存放程序的二进制代码。</li></ul><h2 id="结构体字节对齐问题?结构体-类大小的计算?"><a href="#结构体字节对齐问题?结构体-类大小的计算?" class="headerlink" title="结构体字节对齐问题?结构体/类大小的计算?"></a>结构体字节对齐问题?结构体/类大小的计算?</h2><h3 id="默认字节对齐"><a href="#默认字节对齐" class="headerlink" title="默认字节对齐"></a>默认字节对齐</h3><p>各成员变量存放的起始地址相对于结构的起始地址的偏移量必须是该变量的类型所占用的字节数的倍数,结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数 n 字节对齐。</p><h3 id="pragma-pack-n"><a href="#pragma-pack-n" class="headerlink" title="pragma pack(n)"></a>pragma pack(n)</h3><ul><li>如果 n 大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式;</li><li>如果 n 小于该变量的类型所占用的字节数,那么偏移量为 n 的倍数,不用满足默认的对齐方式;</li><li>如果 n 大于所有成员变量类型所占用的字节数,那么结构体的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数(两者相比,取小);</li></ul><h3 id="虚函数的大小计算"><a href="#虚函数的大小计算" class="headerlink" title="虚函数的大小计算"></a>虚函数的大小计算</h3><p>假设经过成员对齐后的类的大小为 size 个字节。那么类的 sizeof 大小可以这么计算:size + 4*(虚函数指针的个数 n)。</p><h3 id="联合体的大小计算"><a href="#联合体的大小计算" class="headerlink" title="联合体的大小计算"></a>联合体的大小计算</h3><p>联合体所占的空间不仅取决于最宽成员,还跟所有成员有关系,即其大小必须满足两个条件:</p><ul><li>大小足够容纳最宽的成员;</li><li>大小能被其包含的所有基本数据类型的大小所整除。</li></ul><p>常见例子:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">class A {};: sizeof(A) = 1;</span><br><span class="line">class A { virtual Fun(){} };: sizeof(A) = 4(32位机器)/8(64位机器);</span><br><span class="line">class A { static int a; };: sizeof(A) = 1;</span><br><span class="line">class A { int a; };: sizeof(A) = 4;</span><br><span class="line">class A { static int a; int b; };: sizeof(A) = 4;</span><br></pre></td></tr></table></figure><h2 id="指针和引用"><a href="#指针和引用" class="headerlink" title="指针和引用"></a>指针和引用</h2><h3 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h3><ul><li>定义:指针是一个对象,引用本身不是对象,只是另一个对象的别名;</li><li>指针是“指向”另外一种类型的复合类型;</li><li>引用本身不是一个对象,所以不能定义引用的引用;</li><li>引用只能绑定到对象上,它只是一个对象的别名,因此引用必须初始化,且不能更换引用对象。</li></ul><h3 id="指针"><a href="#指针" class="headerlink" title="指针"></a>指针</h3><p>可以有 const 指针,但是没有 const 引用(const 引用可读不可改,与绑定对象是否为 const 无关)</p><p>注:引用可以指向常量,也可以指向变量。例如<code>int &a=b</code>,使引用 a 指向变量 b。而为了让引用指向常量,必须使用常量引用,如<code>const int &a=1</code>; 它代表的是引用 a 指向一个<code>const int</code>型,这个 int 型的值不能被改变,而不是引用 a 的指向不能被改变,因为引用的指向本来就是不可变的,无需加 const 声明。即指针存在常量指针<code>int const *p</code>和指针常量<code>int *const p</code>,而引用只存在常量引用<code>int const &a</code>,不存在引用常量<code>int& const a</code>。</p><ul><li>指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a 是不合法的)</li><li>指针的值可以为空,但是引用的值不能为 NULL,并且引用在定义的时候必须初始化;</li><li>指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。</li><li>“sizeof 引用”得到的是所指向的变量(对象)的大小,而” sizeof 指针”得到的是指针本身的大小;</li><li>指针和引用的自增(++)运算意义不一样;</li><li>指针使用时需要解引用(*),引用则不需要;</li></ul><h3 id="指针的注意点"><a href="#指针的注意点" class="headerlink" title="指针的注意点"></a>指针的注意点</h3><p><strong>1、指针指向常量存储区对象</strong></p><p><code>char *p="abc"</code>;此时 p 指向的是一个字符串常量,不能对 *p 的内容进行写操作,如 srtcpy(p,s) 是错误的,因为 p 的内容为 “abc” 字符串常量,该数据存储在常量存储区,但可以对指针 p 进行操作,让其指向其他的内存空间。</p><p><strong>2、资源泄漏</strong></p><figure class="highlight plaintext"><table><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">char *p=new char[3]; //分配三个字符空间,p指向该内存空间</span><br><span class="line">p="ab"; //此时p指向常量“ab”,而不再是new char分配的内存空间了,从而造成了资源泄漏</span><br><span class="line">delete []p; //释放时报错</span><br></pre></td></tr></table></figure><p><strong>3、内存越界</strong></p><figure class="highlight plaintext"><table><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">char *p=new char[3]; //分配三个字符空间,p指向该内存空间</span><br><span class="line">strcpy(p,"abcd"); //将abcd存处在分配的内存空间中,由于strlen("abcd")=4>3,越界</span><br><span class="line">delete []p; //释放时出错</span><br></pre></td></tr></table></figure><p><strong>new和malloc的区别</strong></p><ul><li>new 是运算符,malloc() 是一个库函数;</li><li>new 会调用构造函数,malloc 不会;</li><li>new 返回指定类型指针,malloc 返回 void* 指针,需要强制类型转换;</li><li>new 会自动计算需分配的空间,malloc 不行;</li><li>new 可以被重载,malloc 不能。</li></ul><h3 id="悬空指针与野指针"><a href="#悬空指针与野指针" class="headerlink" title="悬空指针与野指针"></a>悬空指针与野指针</h3><ul><li>悬空指针:当所指向的对象被释放或者收回,但是没有让指针指向NULL;</li><li>野指针:那些未初始化的指针;</li></ul><h3 id="空指针能调用类成员函数吗"><a href="#空指针能调用类成员函数吗" class="headerlink" title="空指针能调用类成员函数吗"></a>空指针能调用类成员函数吗</h3><p>可以调用成员函数。当调用<code>p->func1()</code>; 这句话时,其实就是调用 <code>A::func1(this)</code> ,而成员函数的地址在编译时就已经确定, 所以空指针也是可以调用普通成员函数,只不过此时的 this 指针指向空而已,但函数 fun1 函数体内并没有用到 this 指针,所以不会出现问题。</p><p>不可以调用虚函数。如果一个类中包含虚函数,那么它所实例化处的对象的前四个字节是一个虚表指针,这个虚表指针指向的是虚函数表。当然,虚函数的地址也是在编译时就已经确定了,这些虚函数地址存放在虚函数表里面,而虚函数表就在程序地址空间的数据段(静态区),也就是说虚表的建立是在编译阶段就完成的;当调用构造函数的时候才会初始化虚函数表指针,即把虚表指针存放在对象前四个字节(32 位下)。试想一下,假如用空指针调用虚函数,这个指针根本就找不到对应的对象的地址,因此他也不知道虚表的地址,没有虚表的地址,怎么能调用虚函数呢</p><h3 id="智能指针"><a href="#智能指针" class="headerlink" title="智能指针"></a>智能指针</h3><p><strong>unique_ptr</strong></p><p>摒弃 auto_ptr 的原因:避免潜在的内存崩溃问题。如下代码用 auto_ptr 的话不会出现问题,但 p3 是无法访问的。</p><figure class="highlight plaintext"><table><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">unique_ptr<string> p3 (new string ("auto");</span><br><span class="line">unique_ptr<string> p4;</span><br><span class="line">p4 = p3; // 编译器认为非法</span><br></pre></td></tr></table></figure><p>只允许基础指针的一个所有者。unique_ptr小巧高效;大小等同于一个指针且支持右值引用,从而可实现快速插入和对STL集合的检索。</p><p><strong>注意</strong>:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做。</p><p><strong>shared_ptr</strong></p><p>采用引用计数的智能指针,主要用于要将一个原始指针分配给多个所有者(例如,从容器返回了指针副本又想保留原始指针时)的情况。当所有的 shared_ptr 所有者超出了范围或放弃所有权,才会删除原始指针。大小为两个指针;一个用于对象,另一个用于包含引用计数的共享控制块。</p><p>最安全的分配和使用动态内存的方法是调用 make_shared 标准库函数,此函数在动态分配内存中分配一个对象并初始化它,返回对象的 shared_ptr。</p><p><strong>堆和栈</strong></p><ul><li>栈 :只要栈的剩余空间大于所申请的空间,系统将为程序提供内存,否则将报异常提示栈溢出。</li><li>堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统受到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆。</li></ul><h2 id="编译与优化"><a href="#编译与优化" class="headerlink" title="编译与优化"></a>编译与优化</h2><h3 id="静态链接与动态链接"><a href="#静态链接与动态链接" class="headerlink" title="静态链接与动态链接"></a>静态链接与动态链接</h3><p><strong>静态链接:</strong></p><ul><li>定义:在生成可执行文件的时候(链接阶段),把所有需要的函数的二进制代码都包含到可执行文件中去。</li><li>特点:链接器需要知道参与链接的目标文件需要哪些函数,同时也要知道每个目标文件都能提供什么函数,这样链接器才能知道是不是每个目标文件所需要的函数都能正确地链接。如果某个目标文件需要的函数在参与链接的目标文件中找不到的话,链接器就报错了。目标文件中有两个重要的接口来提供这些信息:一个是符号表,另外一个是重定位表。</li><li>缺点:1. 程序体积会变大;2. 静态库有更新的话,所有可执行文件都需要重新链接</li></ul><p><strong>动态链接:</strong></p><ul><li>定义:在编译的时候不直接拷贝可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码,最终达到运行时连接的目的。</li></ul><p>缺点:1. 运行时加载,影响性能</p><p><strong>静态链接过程</strong></p><ul><li>操作系统会读取可执行文件的头部,检查文件的合法性,然后从头部中的 <code>“Program Header”</code> 中读取每个 <code>“Segment”</code> 的虚拟地址、文件地址和属性,并将它们映射到进程虚拟空间的相应位置;</li><li>操作系统就会把控制权交给可执行文件的入口地址,然后程序开始执行。</li></ul><p><strong>动态链接过程</strong></p><ul><li>操作系统会读取可执行文件的头部,检查文件的合法性,然后从头部中的 <code>“Program Header”</code> 中读取每个 <code>“Segment”</code> 的虚拟地址、文件地址和属性,并将它们映射到进程虚拟空间的相应位置;</li><li>操作系统启动一个动态链接器——ld.so,它其实是个共享对象,操作系统同样通过映射的方式将它加在到进程的地址空间中,加载完动态链接器之后,将控制权交给动态链接器的入口地址;</li><li>动态链接器开始执行一系列自身的初始化操作,然后根据当前的环境参数,开始对可执行文件进行动态链接工作;</li><li>所有动态链接工作完成后,动态链接器就会将控制权交给可执行文件的入口地址,程序开始正式执行。</li></ul><p><strong>程序加载的内存分布</strong></p><p>在多任务操作系统中,每个进程都运行在一个属于自己的虚拟内存中,而虚拟内存被分为许多页,并映射到物理内存中,被加载到物理内存中的文件才能够被执行。</p><ul><li>代码段(.text):用来存放可执行文件的机器指令。存放在只读区域,以防止被修改。</li><li>只读数据段(.rodata):<strong>用来存放常量存放在只读区域,如字符串常量、全局const变量</strong>等。</li><li>可读写数据段(.data):用来存放可执行文件中已初始化的全局变量和局部静态变量。</li><li>BSS 段(.bss):未初始化的全局变量和局部静态变量以及初始化为 0 的全局变量一般放在 .bss 的段里,以节省内存空间。<code>static int a=0</code>;(初始化为 0 的全局变量(静态变量)放在 .bss)。</li><li>堆:用来容纳应用程序动态分配的内存区域。当程序使用 malloc 或 new 分配内存时,得到的内存来自堆。堆通常位于栈的下方。向上生长</li><li>栈:用于维护函数调用的上下文。栈通常分配在用户空间的最高地址处分配。向下生长</li><li>动态链接库映射区:如果程序调用了动态链接库,则会有这一部分。该区域是用于映射装载的动态链接库。</li><li>保留区:内存中受到保护而禁止访问的内存区域。</li></ul><h2 id="溢出,越界,泄漏"><a href="#溢出,越界,泄漏" class="headerlink" title="溢出,越界,泄漏"></a>溢出,越界,泄漏</h2><h3 id="溢出"><a href="#溢出" class="headerlink" title="溢出"></a>溢出</h3><p>1、栈溢出:栈的大小通常是 1M-2M,所以栈溢出包含两种情况,一是分配的的大小超过栈的最大值,二是分配的大小没有超过最大值,但是接收的 buff 比新 buff 小 ,具体情况如下。</p><figure class="highlight plaintext"><table><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">char a[10] = {0};</span><br><span class="line">strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii");</span><br></pre></td></tr></table></figure><p>注意:调试时栈溢出的异常要在函数调用结束后才会检测到,因为栈是在函数结束时才会开始进行出栈操作。</p><p>2、内存溢出:使用 malloc 和 new 分配的内存,在拷贝时接收 buff 小于新 buff 时造成的现象。</p><h3 id="越界"><a href="#越界" class="headerlink" title="越界"></a>越界</h3><p>通常指数组越界</p><h3 id="泄露"><a href="#泄露" class="headerlink" title="泄露"></a>泄露</h3><p>指堆内存泄漏,是指使用 malloc 和 new 分配的内存没有释放造成的</p>]]></content>
|
||
|
||
|
||
|
||
|
||
<summary type="html"><h1 id="C-八股文(一)"><a href="#C-八股文(一)" class="headerlink" title="C++ 八股文(一)"></a>C++ 八股文(一)</h1><h2 id="多态"><a href="#多态" class="headerlink" </summary>
|
||
|
||
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/categories/c/"/>
|
||
|
||
|
||
<category term="c++" scheme="https://zyjblogs.cn/tags/c/"/>
|
||
|
||
<category term="后端" scheme="https://zyjblogs.cn/tags/%E5%90%8E%E7%AB%AF/"/>
|
||
|
||
</entry>
|
||
|
||
</feed>
|