6903 lines
1.4 MiB
6903 lines
1.4 MiB
<?xml version="1.0" encoding="utf-8"?>
|
||
<search>
|
||
<entry>
|
||
<title>第一次使用hexo</title>
|
||
<url>/posts/45609.html</url>
|
||
<content><![CDATA[<h1 id="第一次使用仅为测试"><a href="#第一次使用仅为测试" class="headerlink" title="第一次使用仅为测试"></a>第一次使用仅为测试</h1>]]></content>
|
||
</entry>
|
||
<entry>
|
||
<title>数据结构简介</title>
|
||
<url>/posts/b8928e0e.html</url>
|
||
<content><![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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>数据结构</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>数据结构</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>C++书单</title>
|
||
<url>/posts/58849.html</url>
|
||
<content><![CDATA[<h1 id="C-书单"><a href="#C-书单" class="headerlink" title="C++书单"></a>C++书单</h1><ul>
|
||
<li>《C++ Primer Plus》<ul>
|
||
<li>看来几章,但是对于对我来讲,太过基础了,没有什么编程知识的人适合看这个</li>
|
||
<li>不太可能继续看</li>
|
||
</ul>
|
||
</li>
|
||
<li>《深入实践C++模板编程》<ul>
|
||
<li>模板元编程的入门书籍,推荐</li>
|
||
<li>正在看,我想我会看它第二遍</li>
|
||
</ul>
|
||
</li>
|
||
<li>《深入理解C++11》<ul>
|
||
<li>推荐:⭐⭐⭐⭐</li>
|
||
<li>当前进度:正在看</li>
|
||
</ul>
|
||
</li>
|
||
<li>《C++语言的设计和演化》<ul>
|
||
<li>推荐:</li>
|
||
<li>评价:</li>
|
||
<li>当前进度:</li>
|
||
</ul>
|
||
</li>
|
||
<li>《C++程序设计语言》<ul>
|
||
<li>推荐:⭐⭐⭐⭐⭐</li>
|
||
<li>评价:<ul>
|
||
<li>C++之父写的,看一遍是绝对不够的!!!!!!</li>
|
||
<li>这绝对不是什么入门书籍!!!有一定基础的人再来看效果可能更好</li>
|
||
</ul>
|
||
</li>
|
||
<li>当前进度:第16、17章看完了。 暂停</li>
|
||
</ul>
|
||
</li>
|
||
<li>《深入探索C++对象模型》<ul>
|
||
<li>推荐:⭐⭐⭐⭐⭐</li>
|
||
<li>评价:这书真的写的很好,虽然中文翻译不怎么样,如果不是内容太好,我真的撑不下去,会看第二遍</li>
|
||
<li>当前进度:看完了</li>
|
||
</ul>
|
||
</li>
|
||
<li>《Effective C++ 》<ul>
|
||
<li>推荐:❤️❤️❤️❤️❤️</li>
|
||
<li>评价:<ul>
|
||
<li>信息密度很高,推荐。</li>
|
||
<li>这是我看过的写的最好的技术书籍之一</li>
|
||
<li>第6章写的实在太好了,已买书收藏。</li>
|
||
<li>会看第二遍</li>
|
||
</ul>
|
||
</li>
|
||
<li>当前进度:看完了,名不虚传, 我觉得是写C++必读的书籍,就算你不打算全部看完,至少也要看完第六章</li>
|
||
</ul>
|
||
</li>
|
||
<li>《More Effective C++ 》<ul>
|
||
<li>推荐:⭐⭐⭐</li>
|
||
<li>评价:<ul>
|
||
<li>感觉我的时间被浪费了</li>
|
||
<li>这跟《Effective C++ 》真的是一个作者吗,质量相差太大了,我很失望。</li>
|
||
<li>这是我近期读过的技术书中最难看的一本。准确评分应该是三星半。</li>
|
||
</ul>
|
||
</li>
|
||
<li>当前进度:看完了大部分【近期不打算再看了,冷藏】</li>
|
||
</ul>
|
||
</li>
|
||
<li>《C++ Boost程序库完全开发指南》<ul>
|
||
<li>推荐:⭐⭐⭐⭐⭐</li>
|
||
<li>评价:<ul>
|
||
<li>豆瓣评分居然这么多差评,至于吗,(豆瓣)对国产作者也苛刻了吧,为此加一星。</li>
|
||
<li>当然,学习boost最好的资料是官方文档,但是谁让我有这本📕呢,我一定会看完它的!</li>
|
||
</ul>
|
||
</li>
|
||
<li>当前进度:正在看(暂停)</li>
|
||
</ul>
|
||
</li>
|
||
<li>《Effective STL》<ul>
|
||
<li>当前进度:看到第3节,暂停</li>
|
||
</ul>
|
||
</li>
|
||
<li>《Exceptional C++ 》</li>
|
||
<li>《STL源码剖析》</li>
|
||
</ul>
|
||
<hr>
|
||
<ul>
|
||
<li>2021/5.20: C++语法特性暂停,我觉得我已经学的差不多了,需要一点实践,实践完了之后再回头看。接下来打算看网络编程的书籍</li>
|
||
</ul>
|
||
<hr>
|
||
<ul>
|
||
<li>《HTTP权威指南》<ul>
|
||
<li>推荐:💚💚💚</li>
|
||
<li>评价:对HTTP协议感兴趣的可以看看,还是挺有用的</li>
|
||
<li>当前进度:走马观花的看完了,还是挺有用的</li>
|
||
</ul>
|
||
</li>
|
||
<li>《Linux-UNIX系统编程手册》<ul>
|
||
<li>推荐:❤️❤️❤️❤️💚</li>
|
||
<li>评价:孤篇横绝,竟为大家</li>
|
||
<li>当前进度:看到第58章,暂停</li>
|
||
</ul>
|
||
</li>
|
||
<li>《TCP-IP高效编程:改善网络程序的44个技巧》<ul>
|
||
<li>推荐:💚💚💚💚</li>
|
||
<li>评价:谁翻译的?译者语文不好吗?还不如机器翻译呢!!!内容ok,翻译烂</li>
|
||
<li>当前进度:正在看</li>
|
||
</ul>
|
||
</li>
|
||
<li>《TCP/IP详解:卷二》<ul>
|
||
<li>4.4BSD-Lite源码的下载地址: <code>http://ftp.icm.edu.pl/packages/4.4BSD-Lite/ https://pub.allbsd.org/bsd-sources/</code></li>
|
||
</ul>
|
||
</li>
|
||
<li>《用TCP_IP进行网际互联:设计、实现与内核 (卷2)》<ul>
|
||
<li>推荐:</li>
|
||
<li>评价:<ul>
|
||
<li>信息密度很高,读来有种原来如此的感觉,将买书收藏,常看常新</li>
|
||
<li>被安利了操作系统课程,以及对Xinu的源码感兴趣了</li>
|
||
<li>感觉网友说的“比TCP/IP详解”更好是有道理的</li>
|
||
<li>这书的源码网上没有找到。</li>
|
||
</ul>
|
||
</li>
|
||
<li>当前进度:正在看</li>
|
||
</ul>
|
||
</li>
|
||
<li>《用TCP_IP进行网际互联:客户-服务器编程与应用(Linux版) (卷3)》<ul>
|
||
<li>推荐:❤️❤️❤️💚💚</li>
|
||
<li>评价:<ul>
|
||
<li>这书我想买下来收藏,适合有一定基础的人看</li>
|
||
<li>第1到17章质量很高,后面的就比较拉胯了</li>
|
||
</ul>
|
||
</li>
|
||
<li>当前进度:看到第26章,暂停</li>
|
||
</ul>
|
||
</li>
|
||
<li>《TCP/IP详解卷1:协议》<ul>
|
||
<li>推荐:</li>
|
||
<li>评价:</li>
|
||
<li>当前进度:<ul>
|
||
<li>正在看(2021/06/07)</li>
|
||
<li>暂停,因为我觉得它不说人话(2021/06/08)</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li>《TCP/IP网络编程》<ul>
|
||
<li>推荐:</li>
|
||
<li>评价:</li>
|
||
</ul>
|
||
</li>
|
||
<li>《Unix/Linux编程实践教程》<ul>
|
||
<li>推荐:</li>
|
||
<li>评价:</li>
|
||
<li>当前进度:</li>
|
||
</ul>
|
||
</li>
|
||
<li>《一本书读懂TCP_IP》<ul>
|
||
<li>推荐:🌒</li>
|
||
<li>评价: 不推荐,为什么这样的书也能出版</li>
|
||
<li>当前进度: 已看完</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<hr>
|
||
<ul>
|
||
<li>Linux高性能服务器编程</li>
|
||
<li>Linux多线程服务器端编程(选看)</li>
|
||
<li>UNIX 环境高级编程</li>
|
||
<li>UNIX网络编程卷1(必看,五星推荐)</li>
|
||
</ul>
|
||
<p>零.手册类:</p>
|
||
<ul>
|
||
<li>《C++程序设计语言(The C++ Programming Language)》</li>
|
||
<li>《C++标准程序库(C++ Standard Library Tutorial and Reference) 》</li>
|
||
<li>《The C++ IO Streams and Locales》</li>
|
||
<li>《The C++ Standard (INCITS/ISO/IEC 14882-2011)》</li>
|
||
<li>《Overview of the New C++ (C++11/14) 》</li>
|
||
<li>《The Standard C Library》</li>
|
||
</ul>
|
||
<p>一.初级入门系列:</p>
|
||
<ul>
|
||
<li>《C++ Primer》</li>
|
||
<li>《Accelerated C++》</li>
|
||
<li>《C++编程思想(Thinking in C++) 》</li>
|
||
<li>《C++程序设计原理与实践(Programming: Principles and Practice Using C++ )》</li>
|
||
<li>《C++初学者指南》</li>
|
||
<li>《Visual.C++.2008入门经典》</li>
|
||
<li>《面向对象程序设计—C++语言描述》</li>
|
||
<li>《数据结构(C++语言版)第三版_邓俊辉》</li>
|
||
</ul>
|
||
<p>二.实用系列:</p>
|
||
<ul>
|
||
<li>《深入浅出设计模式》</li>
|
||
<li>《设计模式:可复用面向对象软件的基础》</li>
|
||
<li>《HeadFirst设计模式》</li>
|
||
<li>《大话设计模式》</li>
|
||
</ul>
|
||
<p>三.中级进阶系列:</p>
|
||
<ul>
|
||
<li>《C++ templates》</li>
|
||
<li>《More Exceptional C++》</li>
|
||
<li>《Exceptional C++ Style》</li>
|
||
<li>《C++编程规范(C++ Coding Standards) 》</li>
|
||
<li>《C++ 模板完全指南(C++ Templates: The Complete Guide)》</li>
|
||
<li>《Beyond the C++ Standard Library(Boost)》</li>
|
||
<li>《C和C++安全编码》</li>
|
||
</ul>
|
||
<p>四.高级成神系列:</p>
|
||
<ul>
|
||
<li>《C++设计新思维-泛型编程与设计模式之应用(Modern C++ Design ) 》</li>
|
||
<li>《C++模板元编程(C++ Template Metaprogramming)》</li>
|
||
<li>《C++ Concurrency In Action》</li>
|
||
<li>《Advanced c++ Programming Styles and Idioms 》</li>
|
||
<li>《Real-Time C++ Efficient Object-Oriented and Template Microcontroller Programming》</li>
|
||
<li>《Advanced_Metaprogramming_in_Classic_C 》</li>
|
||
</ul>
|
||
]]></content>
|
||
<categories>
|
||
<category>书</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c</tag>
|
||
<tag>c++</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>HashMap简介</title>
|
||
<url>/posts/dbb6295a.html</url>
|
||
<content><![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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>数据结构</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>hashmap</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>HashMap的最大容量是多少</title>
|
||
<url>/posts/87ddd1f4.html</url>
|
||
<content><![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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>数据结构</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>hashmap</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>N皇后</title>
|
||
<url>/posts/bd497d25.html</url>
|
||
<content><![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="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="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="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>
|
||
<categories>
|
||
<category>算法</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>算法</tag>
|
||
<tag>回溯</tag>
|
||
<tag>递归</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>微服务框架</title>
|
||
<url>/posts/39831.html</url>
|
||
<content><![CDATA[<h1 id="微服务框架"><a href="#微服务框架" class="headerlink" title="微服务框架"></a>微服务框架</h1><p>[TOC]</p>
|
||
<h2 id="一、SpringCloud介绍"><a href="#一、SpringCloud介绍" class="headerlink" title="一、SpringCloud介绍"></a>一、SpringCloud介绍</h2><h3 id="1-1-微服务架构"><a href="#1-1-微服务架构" class="headerlink" title="1.1 微服务架构"></a>1.1 微服务架构</h3><blockquote>
|
||
<p><a href="https://martinfowler.com/articles/microservices.html">https://martinfowler.com/articles/microservices.html</a></p>
|
||
<p>微服务架构提出者:马丁福勒</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>简而言之,微服务体系结构[样式 <a href="https://martinfowler.com/articles/microservices.html#footnote-etymology">1]</a>是一种将单个应用程序开发为一组小型服务的方法,每个应用程序在自己的进程中运行,并与轻量级机制(通常是 HTTP 资源 API)通信。这些服务围绕业务功能构建,可通过全自动部署机制独立部署。这些服务的集中管理最少,可能以不同的编程语言编写,并使用不同的数据存储技术。</p>
|
||
</blockquote>
|
||
<h3 id="1-2-SpringCloud介绍"><a href="#1-2-SpringCloud介绍" class="headerlink" title="1.2 SpringCloud介绍"></a>1.2 SpringCloud介绍</h3><blockquote>
|
||
<p>SpringCloud是微服务架构落地的一套技术栈</p>
|
||
<p>SpringCloud中的大多数技术都是基于Netflix公司的技术进行第二次开发。</p>
|
||
<p>1、SpringCloud的中文社区网站:<a href="http://springcloud.cn/">http://springcloud.cn/</a></p>
|
||
<p>2、SpringCloud的中文网 :<a href="http://springcloud.cc/">http://springcloud.cc/</a></p>
|
||
<p>八个技术点:</p>
|
||
<p>1、Eureka - 服务的注册与发现</p>
|
||
<p>2、Robbn - 服务之间的负载均衡</p>
|
||
<p>3、Feign - 服务之间的通讯</p>
|
||
<p>4、Hystrix - 服务的线程隔离及其熔断器</p>
|
||
<p>5、Zuul - 服务网关</p>
|
||
<p>6、Stream - 实现MQ的使用</p>
|
||
<p>7、Config - 动态配置</p>
|
||
<p>8、 Sleuth - 服务追踪</p>
|
||
</blockquote>
|
||
<h2 id="二、服务的注册与发现-Eureka"><a href="#二、服务的注册与发现-Eureka" class="headerlink" title="二、服务的注册与发现-Eureka"></a>二、服务的注册与发现-Eureka</h2><h3 id="2-1-引言"><a href="#2-1-引言" class="headerlink" title="2.1 引言"></a>2.1 引言</h3><blockquote>
|
||
<p>Eureka就是帮助我们维护所有服务的信息,以便服务之间的相互调用</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/1605019839175.png" alt="1605019839175"></p>
|
||
<h3 id="2-2-Eureka的快速入门"><a href="#2-2-Eureka的快速入门" class="headerlink" title="2.2 Eureka的快速入门"></a>2.2 Eureka的快速入门</h3><h4 id="2-2-1-创建EurekaServer"><a href="#2-2-1-创建EurekaServer" class="headerlink" title="2.2.1 创建EurekaServer"></a>2.2.1 创建EurekaServer</h4><blockquote>
|
||
<p>1、创建一个父工程,并且在父工程中指定SpringCloud版本,并且将packaing修改为pom</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">packaging</span>></span>pom<span class="tag"></<span class="name">packaging</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">properties</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">java.version</span>></span>1.8<span class="tag"></<span class="name">java.version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">spring.cloud-version</span>></span>Hoxton.SR8<span class="tag"></<span class="name">spring.cloud-version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">properties</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependencyManagement</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-dependencies<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>${spring.cloud-version}<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">type</span>></span>pom<span class="tag"></<span class="name">type</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>import<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependencyManagement</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、创建eureka的server,创建springboot工程,并且导入依赖,再启动类中添<code>@EnableEurekaServer</code>注解和编写yml文件</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2.1、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-server<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependencies</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2.2、启动类添加注解</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EnableEurekaServer</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EurekaApplication</span> {</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"> SpringApplication.run(EurekaApplication.class,args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2.3 编写yml配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">8761</span></span><br><span class="line"></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">instance:</span></span><br><span class="line"> <span class="attr">hostname:</span> <span class="string">localhost</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="comment">#当前的eureka是单机版的</span></span><br><span class="line"> <span class="attr">registerWithEureka:</span> <span class="literal">false</span></span><br><span class="line"> <span class="attr">fetchRegistry:</span> <span class="literal">false</span></span><br><span class="line"> <span class="attr">serviceUrl:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://${eureka.instance.hostname}:${server.port}/eureka/</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="2-2-2-创建EurekaClient"><a href="#2-2-2-创建EurekaClient" class="headerlink" title="2.2.2 创建EurekaClient"></a>2.2.2 创建EurekaClient</h4><blockquote>
|
||
<p>1、创建Maven工程,修改为SpringBoot</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、在启动类上添加注解<code>@EnableEurekaClient</code></p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EnableEurekaClient</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerApplication</span> {</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"> SpringApplication.run(CustomerApplication.class,args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、编写配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#指定Eureka服务地址</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">service-url:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://localhost:8761/eureka</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#指定服务名称</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">application:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">CUSTOMER</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<p><img src="/posts/39831/images/image-20201111095518705.png" alt="image-20201111095518705"></p>
|
||
<h4 id="2-2-3-测试Eureka"><a href="#2-2-3-测试Eureka" class="headerlink" title="2.2.3 测试Eureka"></a>2.2.3 测试Eureka</h4><blockquote>
|
||
<p>1、创建了一个Search搜索模块,并且注册到Eureka</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、使用EurekaClient的对象获取服务信息</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> EurekaClient eurekaClient;</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、创建RestTemplate</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RestTemplateConfig</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="keyword">public</span> RestTemplate <span class="title function_">restTemplate</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RestTemplate</span>();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、使用RestTemplate调用</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@GetMapping("/customer")</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">customer</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">//1. 通过eurekaClient 获取到SEARCH服务的信息</span></span><br><span class="line"> <span class="type">InstanceInfo</span> <span class="variable">info</span> <span class="operator">=</span> eurekaClient.getNextServerFromEureka(<span class="string">"SEARCH"</span>, <span class="literal">false</span>);</span><br><span class="line"> <span class="comment">//2. 获取到访问的地址</span></span><br><span class="line"> <span class="type">String</span> <span class="variable">url</span> <span class="operator">=</span> info.getHomePageUrl();</span><br><span class="line"> System.out.println(url);</span><br><span class="line"> <span class="comment">//3. 通过restTemplate访问</span></span><br><span class="line"> <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> restTemplate.getForObject(url + <span class="string">"/search"</span>, String.class);</span><br><span class="line"> <span class="comment">//4. 返回</span></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="2-3-Eureka的安全性"><a href="#2-3-Eureka的安全性" class="headerlink" title="2.3 Eureka的安全性"></a>2.3 Eureka的安全性</h3><blockquote>
|
||
<p>实现Eureka认证</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>1、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-security<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、编写配置类</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EnableWebSecurity</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">WebSecurityConfig</span> <span class="keyword">extends</span> <span class="title class_">WebSecurityConfigurerAdapter</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> <span class="comment">//忽略掉/eureka/**路径</span></span><br><span class="line"> http.csrf().ignoringAntMatchers(<span class="string">"/eureka/**"</span>);</span><br><span class="line"> <span class="built_in">super</span>.configure(http);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、编写配置文件,配置用户密码</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#指定用户名密码</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">security:</span></span><br><span class="line"> <span class="attr">user:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">root</span></span><br><span class="line"> <span class="attr">password:</span> <span class="string">root</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、其他服务想注册到Eureka上需要添加用户名密码</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#指定Eureka服务地址</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">service-url:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://用户名:密码@localhost:8761/eureka</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="2-4-Eureka的高可用性"><a href="#2-4-Eureka的高可用性" class="headerlink" title="2.4 Eureka的高可用性"></a>2.4 Eureka的高可用性</h3><blockquote>
|
||
<p>如果程序正在运行,突然Eureka宕机了</p>
|
||
<p>1、如果调用方访问过一次被调用方,Eureka的宕机就不会影响到功能</p>
|
||
<p>2、如果调用方没有访问过被调用方,Eureka的宕机就会造成当前功能的不可用到功能</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>搭建Eureka高可用</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201111110217411.png" alt="image-20201111110217411"></p>
|
||
<blockquote>
|
||
<p>1、准备多态Eureka</p>
|
||
<p>采用了复制的方式,删除iml和target文件,并且修改pom.xml中的项目名称,再给负公差添加module</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、让服务注册到多台Eureka上</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">8761</span></span><br><span class="line"></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">serviceUrl:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://root:root@localhost:8762/eureka/</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">8762</span></span><br><span class="line"></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">serviceUrl:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://root:root@localhost:8761/eureka/</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、让多台Eureka之间相互通讯</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="comment">#当前的eureka是单机版的 false单机版 true集群</span></span><br><span class="line"> <span class="attr">registerWithEureka:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">fetchRegistry:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">serviceUrl:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://root:root@localhost:8761/eureka/</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="2-5-Eureka的细节"><a href="#2-5-Eureka的细节" class="headerlink" title="2.5 Eureka的细节"></a>2.5 Eureka的细节</h3><blockquote>
|
||
<p>1、EurekaClient启动时,讲自己的信息注册到EurekaServer上,EurekaServer就会储存EurekaClient的注册信息。</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、当EurekaClient调用服务时,本地没有注册信息的缓存时,去EurekaServer中获取注册信息</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>3、EurekaClient会通过心跳的方式去和EurekaServer进行连接。(默认30s EurekaClient就会发送一次心跳请求,如果超过了90s还没有发送心跳信息的话,EurekaSevrer就认为你宕机了,将当前的EurekaClient从注册表中移除)</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">instance:</span></span><br><span class="line"> <span class="attr">lease-renewal-interval-in-seconds:</span> <span class="number">30</span> <span class="comment">#心跳间隔</span></span><br><span class="line"> <span class="attr">lease-expiration-duration-in-seconds:</span> <span class="number">90</span> <span class="comment">#多久没法送,就认为你宕机了</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、EurekaClient会每个30s去EurekaServer中去更新本地的注册表</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="comment">#每隔多久去更新一下本地的注册表缓存信息</span></span><br><span class="line"> <span class="attr">registry-fetch-interval-seconds:</span> <span class="number">30</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、Eureka的自我保护机制,统计15分钟内,如果一个服务的心跳发送比例低于85%,EurekaServer就会开启自我保护机制</p>
|
||
<p> 1、不会从EurekaServer中去移除长时间没有收到心跳的服务</p>
|
||
<p> 2、EurekaServer还是可以正常提供服务的</p>
|
||
<p> 3、网络稳定时,EurekaServer才会开始将自己的信息被其他节点同步过去</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201111132523595.png" alt="image-20201111132523595"></p>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="comment"># Eureka保护机制配置</span></span><br><span class="line"> <span class="attr">server:</span></span><br><span class="line"> <span class="comment">#true 开启 false关闭</span></span><br><span class="line"> <span class="attr">enable-self-preservation:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>6、CAP定理,C-一致性 A-可用性 P-分区容错性,这三个特新在分布是环境下,只能满足2个,而且分区容错性在分布式环境下,时必须要满足的。只能在AC之间进行权衡。</p>
|
||
<p>1、如果选择CP,保证了一致性,可能会造成你系统在一定时间内是不可以的,如果你同步数据的时间比较长,造成的损失就越大。</p>
|
||
<p>2、如果选择AP的效果,高可用的集群,Eureka集群是无中心,Eureka即便宕机几个也不会影响系统的使用,不需要重新去枚举一个master,也会导致一定时间内数据是不一致。</p>
|
||
</blockquote>
|
||
<h2 id="三、服务间的负载均衡-Robbin"><a href="#三、服务间的负载均衡-Robbin" class="headerlink" title="三、服务间的负载均衡-Robbin"></a>三、服务间的负载均衡-Robbin</h2><h3 id="3-1-引言"><a href="#3-1-引言" class="headerlink" title="3.1 引言"></a>3.1 引言</h3><blockquote>
|
||
<p>Robbin是帮助我们实现服务和服务负载均衡</p>
|
||
<p>客户端负载均衡:customer客户端模块,将2个Search模块信息全部拉取到本地的缓存,在customer中自己做一个负载均衡的策略,选中某一个服务。</p>
|
||
<p>服务端负载均衡:在注册中心中,直接根据你指定的负载均衡策略,帮你选中一个指定的服务器信息,并返回。</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201111134022333.png" alt="image-20201111134022333"></p>
|
||
<h3 id="3-2-Robbin的快速入门"><a href="#3-2-Robbin的快速入门" class="headerlink" title="3.2 Robbin的快速入门"></a>3.2 Robbin的快速入门</h3><blockquote>
|
||
<p>1、启动两个Search模块</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、在customer导入robbin依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-ribbon<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、配置整合RestTemplate和Robbin</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RestTemplateConfig</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="meta">@LoadBalanced</span></span><br><span class="line"> <span class="keyword">public</span> RestTemplate <span class="title function_">restTemplate</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RestTemplate</span>();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、在customer中去访问Search</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@GetMapping("/customer")</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">customer</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> restTemplate.getForObject(<span class="string">"http://SEARCH/search"</span>, String.class);</span><br><span class="line"> <span class="comment">//4. 返回</span></span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="3-3-Robbin配置负载均衡策略"><a href="#3-3-Robbin配置负载均衡策略" class="headerlink" title="3.3 Robbin配置负载均衡策略"></a>3.3 Robbin配置负载均衡策略</h3><blockquote>
|
||
<p>1、负载均衡策略</p>
|
||
<ol>
|
||
<li>RandomRule:随机策略</li>
|
||
<li>RoundRobbinRule:轮询策略</li>
|
||
<li>WeightedResponseTimeRule:默认会采用轮询的策略,后续会根据服务的响应时间,自动给你分配权重</li>
|
||
<li>BestAvailableRule:根据被调用方并发数最小的去分配</li>
|
||
</ol>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、采用注解的形式</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> IRule <span class="title function_">robbinRule</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RandomRule</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、配置文件去指定负载均衡的策略(推荐)</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#指定具体服务的负载均衡策略</span></span><br><span class="line"><span class="attr">SEARCH:</span> <span class="comment">#编写服务名称</span></span><br><span class="line"> <span class="attr">ribbon:</span></span><br><span class="line"> <span class="attr">NFLoadBalancerRuleClassName:</span> <span class="string">com.netflix.loadbalancer.WeightedResponseTimeRule</span> <span class="comment">#具体负载均衡使用的类</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<h2 id="四、服务间的调用-Feign"><a href="#四、服务间的调用-Feign" class="headerlink" title="四、服务间的调用-Feign"></a>四、服务间的调用-Feign</h2><h3 id="4-1-引言"><a href="#4-1-引言" class="headerlink" title="4.1 引言"></a>4.1 引言</h3><blockquote>
|
||
<p>Feign可以帮助我们实现面向接口编程,就直接调用其他服务,简化开发。</p>
|
||
</blockquote>
|
||
<h3 id="4-2-Feign的快速入门"><a href="#4-2-Feign的快速入门" class="headerlink" title="4.2 Feign的快速入门"></a>4.2 Feign的快速入门</h3><blockquote>
|
||
<p>1、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-openfeign<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、添加一个注解<code>@EnableFeignClients</code></p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EnableEurekaClient</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableFeignClients</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerApplication</span> {</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"> SpringApplication.run(CustomerApplication.class,args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、创建一个接口,并且和search模块做映射</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//指定服务名称</span></span><br><span class="line"><span class="meta">@FeignClient("SEARCH")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">SearchClient</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">//value -> 目标服务的请求路径,method -> 映射请求方式</span></span><br><span class="line"> <span class="meta">@RequestMapping(value = "/search",method = RequestMethod.GET)</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">search</span><span class="params">()</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、测试使用</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> SearchClient searchClient;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping("/customer")</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">customer</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">String</span> <span class="variable">result</span> <span class="operator">=</span> searchClient.search();</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="4-3-Feign的传递参数方式"><a href="#4-3-Feign的传递参数方式" class="headerlink" title="4.3 Feign的传递参数方式"></a>4.3 Feign的传递参数方式</h3><blockquote>
|
||
<p>1、注意事项</p>
|
||
<ol>
|
||
<li>如果你传递的参数,比较复杂时,默认会采用POST的请求方式。</li>
|
||
<li>传递单个参数时,推荐使用@PathVariable,如果传递的单个参数比较多,这里也可以采用@RequestParam,不要省略value属性</li>
|
||
<li>传递对象信息时,统一采用json的方式,添加@RequestBody</li>
|
||
<li>Client接口必须采用@RequestMapping</li>
|
||
</ol>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、在Search模块下准备三个接口</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@GetMapping("/search/{id}")</span></span><br><span class="line"><span class="keyword">public</span> Customer <span class="title function_">findById</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Customer</span>(<span class="number">1</span>, <span class="string">"zhangsan"</span>, <span class="number">23</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping("/getCustomer")</span></span><br><span class="line"><span class="keyword">public</span> Customer <span class="title function_">getcustomer</span><span class="params">(<span class="meta">@RequestParam</span> Integer id, <span class="meta">@RequestParam</span> String name)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Customer</span>(id, name, <span class="number">23</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@PostMapping("save")</span></span><br><span class="line"><span class="keyword">public</span> Customer <span class="title function_">save</span><span class="params">(<span class="meta">@RequestBody</span> Customer customer)</span> {</span><br><span class="line"> <span class="keyword">return</span> customer;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、封装Customer模块的Controller</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@GetMapping("/customer/{id}")</span></span><br><span class="line"><span class="keyword">public</span> Customer <span class="title function_">findById</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> {</span><br><span class="line"> <span class="keyword">return</span> searchClient.findById(id);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping("/getCustomer")</span></span><br><span class="line"><span class="keyword">public</span> Customer <span class="title function_">getcustomer</span><span class="params">(<span class="meta">@RequestParam</span> Integer id, <span class="meta">@RequestParam</span> String name)</span> {</span><br><span class="line"> <span class="keyword">return</span> searchClient.getcustomer(id,name);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping("save")</span></span><br><span class="line"><span class="keyword">public</span> Customer <span class="title function_">save</span><span class="params">(Customer customer)</span> {</span><br><span class="line"> <span class="keyword">return</span> searchClient.save(customer);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、再封装Client接口</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(value = "/search/{id}",method = RequestMethod.GET)</span></span><br><span class="line">Customer <span class="title function_">findById</span><span class="params">(<span class="meta">@PathVariable(value = "id")</span> Integer id)</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@RequestMapping(value = "/getCustomer",method = RequestMethod.GET)</span></span><br><span class="line">Customer <span class="title function_">getcustomer</span><span class="params">(<span class="meta">@RequestParam(value = "id")</span> Integer id, <span class="meta">@RequestParam(value = "name")</span> String name)</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@RequestMapping(value = "save",method = RequestMethod.GET)</span> <span class="comment">//会自动转化成POST请求 405</span></span><br><span class="line">Customer <span class="title function_">save</span><span class="params">(<span class="meta">@RequestBody</span> Customer customer)</span>;</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201111155409030.png" alt="image-20201111155409030"></p>
|
||
<h3 id="4-4-Feign的Fallback"><a href="#4-4-Feign的Fallback" class="headerlink" title="4.4 Feign的Fallback"></a>4.4 Feign的Fallback</h3><blockquote>
|
||
<p>Fallback可以帮助我们在使用Feign去调用另一个服务时,如果出现了问题,走服务降级,返回一个错误的数据,避免功能因为一个服务出现问题,全部失效</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>1、创建一个POJO类,实现Client接口</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SearchClientFallBack</span> <span class="keyword">implements</span> <span class="title class_">SearchClient</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_">search</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"出现问题了"</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> Customer <span class="title function_">findById</span><span class="params">(Integer id)</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Customer <span class="title function_">getcustomer</span><span class="params">(Integer id, String name)</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Customer <span class="title function_">save</span><span class="params">(Customer customer)</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></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、修改Client接口中的注解,添加一个属性</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@FeignClient(value = "SEARCH",fallback = SearchClientFallBack.class)</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、添加一个配置文件。</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#feign和hystrix主件整合</span></span><br><span class="line"><span class="attr">feign:</span></span><br><span class="line"> <span class="attr">hystrix:</span></span><br><span class="line"> <span class="attr">enabled:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>调用方无法知道具体的错误信息是什么,通过FallBackFactory的方式去实现这个功能</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>1、FallBackFactory基于fallback</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、创建一个POJO类,实现FallBackFactory<Client></p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SearchClientFallBackFactory</span> <span class="keyword">implements</span> <span class="title class_">FallbackFactory</span><SearchClient> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> SearchClientFallBack searchClientFallBack;</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> SearchClient <span class="title function_">create</span><span class="params">(Throwable throwable)</span> {</span><br><span class="line"> throwable.printStackTrace();</span><br><span class="line"> <span class="keyword">return</span> searchClientFallBack;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、修改Client接口中的属性</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@FeignClient(value = "SEARCH",</span></span><br><span class="line"><span class="meta"> fallbackFactory = SearchClientFallBackFactory.class</span></span><br><span class="line"><span class="meta">)</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<h2 id="五、服务的隔离及熔断器-Hystrix"><a href="#五、服务的隔离及熔断器-Hystrix" class="headerlink" title="五、服务的隔离及熔断器-Hystrix"></a>五、服务的隔离及熔断器-Hystrix</h2><h3 id="5-1-引言"><a href="#5-1-引言" class="headerlink" title="5.1 引言"></a>5.1 引言</h3><p><img src="/posts/39831/images/image-20201111162630381.png" alt="image-20201111162630381"></p>
|
||
<h3 id="5-2-降级机制实现"><a href="#5-2-降级机制实现" class="headerlink" title="5.2 降级机制实现"></a>5.2 降级机制实现</h3><blockquote>
|
||
<p>1、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-hystrix<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、添加一个注解<code>@EnableCircuitBreaker</code></p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EnableEurekaClient</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableCircuitBreaker</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerApplication</span> {</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"> SpringApplication.run(CustomerApplication.class,args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、针对某一个接口去编写他的降级方法</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@GetMapping("/customer/{id}")</span></span><br><span class="line"><span class="meta">@HystrixCommand(fallbackMethod ="findByIdFallBack" )</span></span><br><span class="line"><span class="keyword">public</span> Customer <span class="title function_">findById</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>/<span class="number">0</span>;</span><br><span class="line"> <span class="keyword">return</span> searchClient.findById(id);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//findById的降级方法,方法描述要与接口一致</span></span><br><span class="line"><span class="keyword">public</span> Customer <span class="title function_">findByIdFallBack</span><span class="params">(Integer id)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Customer</span>(-<span class="number">1</span>,<span class="string">""</span>,<span class="number">0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、在接口上添加注解</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@HystrixCommand(fallbackMethod ="findByIdFallBack" )</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201111164239591.png" alt="image-20201111164239591"></p>
|
||
<h3 id="5-3-线程隔离"><a href="#5-3-线程隔离" class="headerlink" title="5.3 线程隔离"></a>5.3 线程隔离</h3><blockquote>
|
||
<p>如果使用Tomcat的线程池去接收用户的请求,使用当前线程去执行其他服务的功能,如果某一个服务出现了故障,导致tomcat的线程大量的堆积,导致tomcat无法处理其他业务功能。</p>
|
||
<p>1、Hystrix线程池(默认),接收用户请求采用tomcat的线程池,执行业务代码,调用其他服务时,采用Hystrix的线程池。</p>
|
||
<p>2、信号量,使用的还是Tomcat的线程池,帮助我们取关了Tomcat的线程池</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>1、Hystrix的线程池的配置(具体的配置属性需要去查看HystrixCommandProperties类) <a href="https://github.com/Netflix/Hystrix/wiki/Configuration">wiki</a></p>
|
||
<ol>
|
||
<li>线程隔离策略:name = <code>hystrix.command.default.execution.isolation.strategy</code>,value=<code>THREAD</code>,<code>SEMAPHORE</code></li>
|
||
<li>指定超时时间(针对线程池):name= <code>hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds</code> ,value =<code>1000</code></li>
|
||
<li>是否开启超时时间配置:name=<code>hystrix.command.default.execution.timeout.enabled</code>,value=<code>true</code></li>
|
||
<li>超时之后是否中断线程:name=<code>hystrix.command.default.execution.isolation.thread.interruptOnTimeout</code>,value=<code>true</code></li>
|
||
<li>取消任务之后是否中断线程:name=<code>hystrix.command.default.execution.isolation.thread.interruptOnCancel</code>,value=false</li>
|
||
</ol>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、信号量配置信息</p>
|
||
<ol>
|
||
<li>线程隔离策略:name = <code>hystrix.command.default.execution.isolation.strategy</code>,value=<code>THREAD</code>,<code>SEMAPHORE</code></li>
|
||
<li>指定信号量的最大并发请求数:name=<code>hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests</code>,value=<code>10</code></li>
|
||
</ol>
|
||
</blockquote>
|
||
<h3 id="5-4-断路器"><a href="#5-4-断路器" class="headerlink" title="5.4 断路器"></a>5.4 断路器</h3><h4 id="5-4-1-断路器介绍"><a href="#5-4-1-断路器介绍" class="headerlink" title="5.4.1 断路器介绍"></a>5.4.1 断路器介绍</h4><blockquote>
|
||
<p>如果调用指定服务时,如果说这个服务的失败率达到你输入的阈值麻将断路器从closed状态,转变为open状态,指定服务时无法被访问的,如果你访问就直接走fallback方法,在一定时间内,open状态会再次转变为half open状态,允许一个请求发送到我指定服务,如果成功,则转变为closed,如果失败,服务再次转变为open状态,会再次循环到hald open,直到专路器回到一个closed状态。</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201111181650929.png" alt="image-20201111181650929"></p>
|
||
<h4 id="5-4-2-配置断路器的监控界面"><a href="#5-4-2-配置断路器的监控界面" class="headerlink" title="5.4.2 配置断路器的监控界面"></a>5.4.2 配置断路器的监控界面</h4><blockquote>
|
||
<p>1、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-hystrix-dashboard<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、在启动类中添加注解<code>@EnableHystrixDashboard</code></p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EnableEurekaClient</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableFeignClients</span></span><br><span class="line"><span class="meta">@EnableCircuitBreaker</span></span><br><span class="line"><span class="meta">@EnableHystrixDashboard</span></span><br><span class="line"><span class="meta">@ServletComponentScan("cn.zyjblogs.servlet")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerApplication</span> {</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"> SpringApplication.run(CustomerApplication.class,args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、配置一个Servlet路径,指定上Hystrix的Servlet</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@WebServlet("/hystrix.stream")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HystrixServlet</span> <span class="keyword">extends</span> <span class="title class_">HystrixMetricsStreamServlet</span> {</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、在启动类上添加扫描Servlet的注解<code>@ServletComponentScan("cn.zyjblogs.servlet")</code></p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>5、配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">hystrix:</span></span><br><span class="line"> <span class="attr">dashboard:</span></span><br><span class="line"> <span class="attr">proxy-stream-allow-list:</span> <span class="string">localhost</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>6、测试</p>
|
||
</blockquote>
|
||
<p>直接访问: <a href="http://host:port/hystrix.stream">http://host:port/hystrix.stream</a></p>
|
||
<p><img src="/posts/39831/images/1605103421256.png" alt="1605103421256"></p>
|
||
<blockquote>
|
||
<p>在当前位置输入映射好的servlet路径</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/1605103436874.png" alt="1605103436874"></p>
|
||
<h4 id="5-4-3-配置断路器的属性"><a href="#5-4-3-配置断路器的属性" class="headerlink" title="5.4.3 配置断路器的属性"></a>5.4.3 配置断路器的属性</h4><blockquote>
|
||
<p>断路器的属性(10秒之内)</p>
|
||
<ol>
|
||
<li>断路器的开关:name=<code>hystrix.command.default.circuitBreaker.enabled</code>,value=<code>true</code></li>
|
||
<li>失败阈值的总请求数:name=<code>hystrix.command.default.circuitBreaker.requestVolumeThreshold</code>,value=<code>20</code></li>
|
||
<li>请求总数失败率达到%多少时打开断路器:name=<code>hystrix.command.default.circuitBreaker.errorThresholdPercentage</code>,value=<code>50</code></li>
|
||
<li>断路器open状态后,多少秒是拒绝请求的:name=<code>hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds</code>value=<code>5000</code></li>
|
||
<li>强制让服务拒绝请求:name=<code>hystrix.command.default.circuitBreaker.forceOpen</code>,value=<code>false</code></li>
|
||
<li>强制让服务接收请求:name=<code>hystrix.command.default.circuitBreaker.forceClosed</code>,value=<code>false</code></li>
|
||
</ol>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>具体配置方式</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@GetMapping("/customer/{id}")</span></span><br><span class="line"><span class="meta">@HystrixCommand(fallbackMethod ="findByIdFallBack",commandProperties = {</span></span><br><span class="line"><span class="meta"> @HystrixProperty(name = "circuitBreaker.enabled",value="true"),</span></span><br><span class="line"><span class="meta"> @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value="10"),</span></span><br><span class="line"><span class="meta"> @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value="70"),</span></span><br><span class="line"><span class="meta"> @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value="5000")</span></span><br><span class="line"><span class="meta">})</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="5-5-请求缓存"><a href="#5-5-请求缓存" class="headerlink" title="5.5 请求缓存"></a>5.5 请求缓存</h3><h4 id="5-5-1-请求缓存的介绍"><a href="#5-5-1-请求缓存的介绍" class="headerlink" title="5.5.1 请求缓存的介绍"></a>5.5.1 请求缓存的介绍</h4><blockquote>
|
||
<p>1、请求缓存的声明周期是一次请求</p>
|
||
<p>2、请求缓存是缓存当前线程中的一个方法,将方法参数作为key,方法的返回结果作为value</p>
|
||
<p>3、在一次请求中,目标方法被调用过一次以后就都会被缓存</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/1605104987931.png" alt="1605104987931"></p>
|
||
<h4 id="5-5-2-请求缓存的实现"><a href="#5-5-2-请求缓存的实现" class="headerlink" title="5.5.2 请求缓存的实现"></a>5.5.2 请求缓存的实现</h4><blockquote>
|
||
<p>1、创建一个Service,在Service中调用Search服务</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerService</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> SearchClient searchClient;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CacheResult</span></span><br><span class="line"> <span class="meta">@HystrixCommand(commandKey = "findById")</span></span><br><span class="line"> <span class="keyword">public</span> Customer <span class="title function_">findById</span><span class="params">(<span class="meta">@CacheKey</span> Integer id)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="keyword">return</span> searchClient.findById(id);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CacheRemove(commandKey = "findById")</span></span><br><span class="line"> <span class="meta">@HystrixCommand</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">clearFindById</span><span class="params">(<span class="meta">@CacheKey</span> Integer id)</span> {</span><br><span class="line"> System.out.println(<span class="string">"findById缓存被清空"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、使用请求缓存的注解<code>@CacheResult</code> <code>CacheRemove</code></p>
|
||
<ol>
|
||
<li>@CacheResult:帮助我们缓存当前方法的返回结果(必须配合@HystrixCommand使用)</li>
|
||
<li>@CacheRemove:帮助我们清除某一个缓存信息(基于commandKey)</li>
|
||
<li>@CacheKey:指定那个方法参数作为缓存标识</li>
|
||
</ol>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>3、修改Search模块的结果返回值</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Customer</span>(id, name, (<span class="type">int</span>) (Math.random() * <span class="number">100000</span>));</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、编写Filter,去构建HystrixRequestContext</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@WebFilter("/*")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HystrixRequestContextFilter</span> <span class="keyword">implements</span> <span class="title class_">Filter</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_">doFilter</span><span class="params">(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)</span> <span class="keyword">throws</span> IOException, ServletException {</span><br><span class="line"> HystrixRequestContext.initializeContext();</span><br><span class="line"> filterChain.doFilter(servletRequest,servletResponse);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、修改Controller</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> CustomerService customerService;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping("/customer/{id}")</span></span><br><span class="line"> <span class="meta">@HystrixCommand(</span></span><br><span class="line"><span class="meta"> fallbackMethod = "findByIdFallBack",</span></span><br><span class="line"><span class="meta"> commandProperties = {</span></span><br><span class="line"><span class="meta"> @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),</span></span><br><span class="line"><span class="meta"> @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),</span></span><br><span class="line"><span class="meta"> @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "70"),</span></span><br><span class="line"><span class="meta"> @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000"),</span></span><br><span class="line"><span class="meta"> })</span></span><br><span class="line"> <span class="keyword">public</span> Customer <span class="title function_">findById</span><span class="params">(<span class="meta">@PathVariable</span> Integer id)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> System.out.println(Thread.currentThread().getName());</span><br><span class="line"> <span class="keyword">if</span> (id == <span class="number">1</span>) {</span><br><span class="line"> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span> / <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//一下为添加内容</span></span><br><span class="line"> System.out.println(customerService.findById(id));</span><br><span class="line"> System.out.println(customerService.findById(id));</span><br><span class="line"> customerService.clearFindById(id);</span><br><span class="line"> System.out.println(customerService.findById(id));</span><br><span class="line"> System.out.println(customerService.findById(id));</span><br><span class="line"> customerService.clearFindById(id);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> searchClient.findById(id);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>6、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/1605106759891.png" alt="1605106759891"></p>
|
||
<h2 id="六、服务的网关-Zuul"><a href="#六、服务的网关-Zuul" class="headerlink" title="六、服务的网关-Zuul"></a>六、服务的网关-Zuul</h2><h3 id="6-1-引言"><a href="#6-1-引言" class="headerlink" title="6.1 引言"></a>6.1 引言</h3><blockquote>
|
||
<p>1、客户端维护大量的ip和port信息,直接访问指定服务</p>
|
||
<p>2、认证和授权操作,需要在每一个模块中添加认证和授权操作</p>
|
||
<p>3、项目迭代,服务拆分,服务要合并,需要客户端镜像大量的变化</p>
|
||
<p>4、统一的把安全性校验都放在Zuul中</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112110708902.png" alt="网关"></p>
|
||
<h3 id="6-2-Zuul的快速入门"><a href="#6-2-Zuul的快速入门" class="headerlink" title="6.2 Zuul的快速入门"></a>6.2 Zuul的快速入门</h3><blockquote>
|
||
<p>1、创建Maven项目,修改SpringBoot</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-web<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-zuul<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、添加注解<code>@EnableZuulProxy</code> <code>@EnableEurekaClient</code></p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaClient</span></span><br><span class="line"><span class="meta">@EnableZuulProxy</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ZuulApplication</span> {</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"> SpringApplication.run(ZuulApplication.class,args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、编写配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line"><span class="comment">#指定Eureka服务地址</span></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">service-url:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#指定服务名称</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">application:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">ZUUL</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112113609523.png" alt="image-20201112113609523"></p>
|
||
<h3 id="6-3-Zuul常用配置信息"><a href="#6-3-Zuul常用配置信息" class="headerlink" title="6.3 Zuul常用配置信息"></a>6.3 Zuul常用配置信息</h3><h4 id="6-3-1-Zuul的监控界面"><a href="#6-3-1-Zuul的监控界面" class="headerlink" title="6.3.1 Zuul的监控界面"></a>6.3.1 Zuul的监控界面</h4><blockquote>
|
||
<p>1、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-actuator<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、编写配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#查看zuul的监考界面(开发时,配置为*,上线,不要配置)</span></span><br><span class="line"><span class="attr">management:</span></span><br><span class="line"> <span class="attr">endpoints:</span></span><br><span class="line"> <span class="attr">web:</span></span><br><span class="line"> <span class="attr">exposure:</span></span><br><span class="line"> <span class="attr">include:</span> <span class="string">"*"</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、直接访问:<code>http://localhost/actuator/routes</code></p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112132217601.png" alt="image-20201112132217601"></p>
|
||
<h4 id="6-3-2-忽略服务设置"><a href="#6-3-2-忽略服务设置" class="headerlink" title="6.3.2 忽略服务设置"></a>6.3.2 忽略服务设置</h4><figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># zuul的配置</span></span><br><span class="line"><span class="attr">zuul:</span></span><br><span class="line"> <span class="comment">#基于服务名忽略服务,无法查看,如果要忽略全部的服务,"*",默认配置的全部路径都会被忽略掉(自定义服务配置,通过这种方式是无法忽略的)</span></span><br><span class="line"> <span class="attr">ignored-services:</span> <span class="string">eureka</span></span><br><span class="line"> <span class="comment">#监考界面依然可以查看,在访问的时候,404无法访问</span></span><br><span class="line"> <span class="attr">ignored-patterns:</span> <span class="string">/**/search/**</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="6-3-3-自定义服务配置"><a href="#6-3-3-自定义服务配置" class="headerlink" title="6.3.3 自定义服务配置"></a>6.3.3 自定义服务配置</h4><figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># zuul的配置</span></span><br><span class="line"><span class="attr">zuul:</span></span><br><span class="line"> <span class="comment">#基于服务名忽略服务,无法查看,如果要忽略全部的服务,"*",默认配置的全部路径都会被忽略掉(自定义服务配置,通过这种方式是无法忽略的)</span></span><br><span class="line"> <span class="attr">ignored-services:</span> <span class="string">"*"</span></span><br><span class="line"> <span class="comment">#监考界面依然可以查看,在访问的时候,404无法访问</span></span><br><span class="line"> <span class="attr">ignored-patterns:</span> <span class="string">/**/search/**</span></span><br><span class="line"> <span class="comment"># 指定自定义服务(方式一,key(服务名):value(路径))</span></span><br><span class="line"><span class="comment"># routes:</span></span><br><span class="line"><span class="comment"># search: /ss/**</span></span><br><span class="line"><span class="comment"># customer: /cc/**</span></span><br><span class="line"> <span class="comment"># 指定自定义服务(方式二)</span></span><br><span class="line"> <span class="attr">routes:</span></span><br><span class="line"> <span class="attr">kehu:</span> <span class="comment">#自定义名称</span></span><br><span class="line"> <span class="attr">path:</span> <span class="string">/cc/**</span> <span class="comment"># 映射路径</span></span><br><span class="line"> <span class="attr">serviceId:</span> <span class="string">customer</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="6-3-4-灰度发布"><a href="#6-3-4-灰度发布" class="headerlink" title="6.3.4 灰度发布"></a>6.3.4 灰度发布</h4><blockquote>
|
||
<p>1、添加一个配置类</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ZuulConfig</span> {</span><br><span class="line"> <span class="meta">@Bean</span></span><br><span class="line"> <span class="keyword">public</span> PatternServiceRouteMapper <span class="title function_">serviceRouteMapper</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PatternServiceRouteMapper</span>(</span><br><span class="line"> <span class="string">"(?<name>^.+)-(?<version>v.+$)"</span>,</span><br><span class="line"> <span class="string">"${version}/${name}"</span>);</span><br><span class="line"> <span class="comment">//服务名-v版本</span></span><br><span class="line"> <span class="comment">// /v版本/路径</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、准备一个服务,提供2个版本</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">v1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#指定服务名称</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">application:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">CUSTOMER-${version}</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、修改Zuul的配置</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">zuul:</span></span><br><span class="line"> <span class="comment">#基于服务名忽略服务,无法查看,如果需要用到-v的方式,一定要忽略掉</span></span><br><span class="line"><span class="comment"># ignored-services: "*"</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、修改CustomerController</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Value("${version}")</span></span><br><span class="line"> <span class="keyword">private</span> String version;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping("/version")</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">version</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> version;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112135410803.png" alt="image-20201112135410803"></p>
|
||
<p><img src="/posts/39831/images/image-20201112135429409.png" alt="image-20201112135429409"></p>
|
||
<h3 id="6-4-Zuul的过滤器执行流程"><a href="#6-4-Zuul的过滤器执行流程" class="headerlink" title="6.4 Zuul的过滤器执行流程"></a>6.4 Zuul的过滤器执行流程</h3><blockquote>
|
||
<p>客户端请求发送到Zuul服务商,首先通过PreFilter,如果正常放行,会把请求再次转发给RoutingFilter,请求转发到一个指定的服务,在指定的服务响应一个结果之后,再次走一个PostFilter的过滤器链,最终将响应信息返回给客户端。</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112140312697.png" alt="image-20201112140312697"></p>
|
||
<h3 id="6-5-Zuul过滤器入门"><a href="#6-5-Zuul过滤器入门" class="headerlink" title="6.5 Zuul过滤器入门"></a>6.5 Zuul过滤器入门</h3><blockquote>
|
||
<p>1、创建POJO类,继承ZuulFilter</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ZuulFilterTest</span> <span class="keyword">extends</span> <span class="title class_">ZuulFilter</span> {</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、指定当前过滤器的类型</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">filterType</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> FilterConstants.PRE_TYPE;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、指定过滤器的执行顺序</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><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_">filterOrder</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> FilterConstants.PRE_DECORATION_FILTER_ORDER -<span class="number">1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、配置是否启用</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><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_">shouldFilter</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">//开启当前过滤器</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、指定过滤器中的具体业务代码</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Object <span class="title function_">run</span><span class="params">()</span> <span class="keyword">throws</span> ZuulException {</span><br><span class="line"> System.out.println(<span class="string">"prefix过滤器已经执行~~~"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>6、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112141523371.png" alt="image-20201112141523371"></p>
|
||
<h3 id="6-6-PreFilter实现token校验"><a href="#6-6-PreFilter实现token校验" class="headerlink" title="6.6 PreFilter实现token校验"></a>6.6 PreFilter实现token校验</h3><blockquote>
|
||
<p>1、准备访问路径,请求参数专递token</p>
|
||
</blockquote>
|
||
<p><code>http://localhost/v1/customer/version?token=123</code></p>
|
||
<blockquote>
|
||
<p>2、创建AuthenticationFilter</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AuthenticationFilter</span> <span class="keyword">extends</span> <span class="title class_">ZuulFilter</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_">filterType</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> FilterConstants.PRE_TYPE;</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_">filterOrder</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> FilterConstants.PRE_DECORATION_FILTER_ORDER - <span class="number">2</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">boolean</span> <span class="title function_">shouldFilter</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">run</span><span class="params">()</span> <span class="keyword">throws</span> ZuulException {</span><br><span class="line"> <span class="comment">//...</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></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、在run方法中编写具体的业务逻辑代码</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Object <span class="title function_">run</span><span class="params">()</span> <span class="keyword">throws</span> ZuulException {</span><br><span class="line"> System.out.println(<span class="string">"AuthenticationFilter执行了"</span>);</span><br><span class="line"> <span class="comment">//1. 获取Request对象</span></span><br><span class="line"> <span class="type">RequestContext</span> <span class="variable">requestContext</span> <span class="operator">=</span> RequestContext.getCurrentContext();</span><br><span class="line"> <span class="type">HttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> requestContext.getRequest();</span><br><span class="line"> <span class="comment">//2. 获取token参数</span></span><br><span class="line"> <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> request.getParameter(<span class="string">"token"</span>);</span><br><span class="line"> <span class="comment">//3.对比token</span></span><br><span class="line"> <span class="keyword">if</span> (token == <span class="literal">null</span> || !<span class="string">"123"</span>.equalsIgnoreCase(token)) {</span><br><span class="line"> System.out.println(<span class="string">"token校验失败"</span>);</span><br><span class="line"> <span class="comment">//4. token校验失败,直接响应数据</span></span><br><span class="line"> requestContext.setSendZuulResponse(<span class="literal">false</span>);</span><br><span class="line"> requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112145449105.png" alt="image-20201112145449105"></p>
|
||
<h3 id="6-7-Zuul的降级"><a href="#6-7-Zuul的降级" class="headerlink" title="6.7 Zuul的降级"></a>6.7 Zuul的降级</h3><blockquote>
|
||
<p>1、创建POJO类,实现接口FallbackProvider</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ZuulFallBack</span> <span class="keyword">implements</span> <span class="title class_">FallbackProvider</span> {</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、重写两个方法</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">getRoute</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">//代表指定全部出现问题的服务,都走这个降级方法</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"*"</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> ClientHttpResponse <span class="title function_">fallbackResponse</span><span class="params">(String route, Throwable cause)</span> {</span><br><span class="line"> System.out.println(<span class="string">"降级的服务"</span>+route);</span><br><span class="line"> cause.printStackTrace();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ClientHttpResponse</span>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> HttpHeaders <span class="title function_">getHeaders</span><span class="params">()</span> {</span><br><span class="line"> <span class="comment">//指定响应头信息</span></span><br><span class="line"> <span class="type">HttpHeaders</span> <span class="variable">headers</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HttpHeaders</span>();</span><br><span class="line"> headers.setContentType(MediaType.APPLICATION_JSON);</span><br><span class="line"> <span class="keyword">return</span> headers;</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> InputStream <span class="title function_">getBody</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="comment">//给用户响应的信息</span></span><br><span class="line"> <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="string">"当前服务"</span> + route + <span class="string">"出现问题!!!"</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(msg.getBytes());</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> HttpStatus <span class="title function_">getStatusCode</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="comment">//指定具体的HttpStatus</span></span><br><span class="line"> <span class="keyword">return</span> HttpStatus.INTERNAL_SERVER_ERROR;</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_">getRawStatusCode</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="comment">//返回状态码</span></span><br><span class="line"> <span class="keyword">return</span> HttpStatus.INTERNAL_SERVER_ERROR.value();</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> String <span class="title function_">getStatusText</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="comment">//指定错误信息</span></span><br><span class="line"> <span class="keyword">return</span> HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();</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_">close</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><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112151632111.png" alt="image-20201112151632111"></p>
|
||
<h3 id="6-8-Zuul动态路由"><a href="#6-8-Zuul动态路由" class="headerlink" title="6.8 Zuul动态路由"></a>6.8 Zuul动态路由</h3><blockquote>
|
||
<p>1、创建一个过滤器</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DynamicRoutingFilter</span> <span class="keyword">extends</span> <span class="title class_">ZuulFilter</span>{</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、在run方法中编辑业务逻辑</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">filterType</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> FilterConstants.PRE_TYPE;</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_">filterOrder</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> FilterConstants.PRE_DECORATION_FILTER_ORDER +<span class="number">3</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">boolean</span> <span class="title function_">shouldFilter</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="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Object <span class="title function_">run</span><span class="params">()</span> <span class="keyword">throws</span> ZuulException {</span><br><span class="line"> System.out.println(<span class="string">"DynamicRoutingFilter执行了"</span>);</span><br><span class="line"> <span class="comment">//1、获取Request对象</span></span><br><span class="line"> <span class="type">RequestContext</span> <span class="variable">context</span> <span class="operator">=</span> RequestContext.getCurrentContext();</span><br><span class="line"> <span class="type">HttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> context.getRequest();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//2、获取参数,redisKey</span></span><br><span class="line"> <span class="type">String</span> <span class="variable">redisKey</span> <span class="operator">=</span> request.getParameter(<span class="string">"redisKey"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//3、直接判断</span></span><br><span class="line"> <span class="keyword">if</span> (redisKey != <span class="literal">null</span> && redisKey.equalsIgnoreCase(<span class="string">"customer"</span>)) {</span><br><span class="line"> System.out.println(<span class="string">"DynamicRoutingFilter执行了路由到了/customer"</span>);</span><br><span class="line"> <span class="comment">//http://localhost:8080/customer</span></span><br><span class="line"> context.put(FilterConstants.SERVICE_ID_KEY,<span class="string">"customer-v1"</span>);</span><br><span class="line"> context.put(FilterConstants.REQUEST_URI_KEY,<span class="string">"/customer"</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (redisKey != <span class="literal">null</span> && redisKey.equalsIgnoreCase(<span class="string">"search"</span>)) {</span><br><span class="line"> System.out.println(<span class="string">"DynamicRoutingFilter执行了路由到了/search/1"</span>);</span><br><span class="line"> <span class="comment">//http://localhost:8081/search</span></span><br><span class="line"> context.put(FilterConstants.SERVICE_ID_KEY,<span class="string">"search"</span>);</span><br><span class="line"> context.put(FilterConstants.REQUEST_URI_KEY,<span class="string">"/search/1"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112155707148.png" alt="image-20201112155707148"></p>
|
||
<p><img src="/posts/39831/images/image-20201112155652269.png" alt="image-20201112155652269"></p>
|
||
<h2 id="七、多语言支持-Sidecar"><a href="#七、多语言支持-Sidecar" class="headerlink" title="七、多语言支持-Sidecar"></a>七、多语言支持-Sidecar</h2><h3 id="7-1-引言"><a href="#7-1-引言" class="headerlink" title="7.1 引言"></a>7.1 引言</h3><blockquote>
|
||
<p>在SpringCloud的项目中,需要接入一些非java程序,第三方接口,无法接入eureka,hystrix,feign等组件。启动一个代理的微服务,代理微服务去和非java的程序或第三方接口交流,通过代理的非服务去计入SpringCloud的相关组件。</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112160258956.png" alt="image-20201112160258956"></p>
|
||
<h3 id="7-2-Sidecar实现"><a href="#7-2-Sidecar实现" class="headerlink" title="7.2 Sidecar实现"></a>7.2 Sidecar实现</h3><blockquote>
|
||
<p>1、创建一个第三方的服务</p>
|
||
</blockquote>
|
||
<p><code>创建一个SpringBoot工程,并且添加一个Controller</code></p>
|
||
<blockquote>
|
||
<p>2、创建Maven工程,修改为SpringBoot</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、导入以来</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-netflix-sidecar<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、添加注解<code>@EnableSidecar</code></p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>5、编写配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">81</span></span><br><span class="line"></span><br><span class="line"><span class="attr">eureka:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">service-url:</span></span><br><span class="line"> <span class="attr">defaultZone:</span> <span class="string">http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#指定服务名称</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">application:</span></span><br><span class="line"> <span class="attr">name:</span> <span class="string">OTHER-SERVICE</span> <span class="comment">#other-service</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 指定代理的第三方服务</span></span><br><span class="line"><span class="attr">sidecar:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">7001</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>6、通过customer通过Feign的方式调用第三方服务</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112165325023.png" alt="image-20201112165325023"></p>
|
||
<h2 id="八、服务间消息传递-Stream"><a href="#八、服务间消息传递-Stream" class="headerlink" title="八、服务间消息传递-Stream"></a>八、服务间消息传递-Stream</h2><h3 id="8-1-引言"><a href="#8-1-引言" class="headerlink" title="8.1 引言"></a>8.1 引言</h3><blockquote>
|
||
<p>用于构建消息驱动微服务的框架(在下面方便起见也叫它Stream框架),该框架在Spring Boot的基础上整合了Spring Integration来连接消息代理中间件(RabbitMQ,Kafka等)。它支持多个消息中间件的自定义配置,同时吸收了这些消息中间件的部分概念,例如持久化订阅、消费者分组,和分区等概念。使用Stream框架,我们不必关系如何连接各个消息代理中间件,也不必关系消息的发送与接收,只需要进行简单的配置就可以实现这些功能了,可以让我们更敏捷的进行开发主体业务逻辑了。</p>
|
||
<p><strong>Spring Cloud Stream框架的组成部分:</strong></p>
|
||
<ol>
|
||
<li>Stream框架自己的应用模型;</li>
|
||
<li>绑定器抽象层,可以与消息代理中间件进行绑定,通过绑定器的API,可实现插件式的绑定器。</li>
|
||
<li>持久化订阅的支持。</li>
|
||
<li>消费者组的支持。</li>
|
||
<li>Topic分区的支持。</li>
|
||
</ol>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/20190325232054468.png" alt="image-20201112165641574"></p>
|
||
<h3 id="8-2-Stream快速入门"><a href="#8-2-Stream快速入门" class="headerlink" title="8.2 Stream快速入门"></a>8.2 Stream快速入门</h3><blockquote>
|
||
<p>1、启动RabbitMQ</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、消费者-导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-stream-rabbit<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、消费者-配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">rabbitmq:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">5672</span></span><br><span class="line"> <span class="attr">username:</span> <span class="string">test</span></span><br><span class="line"> <span class="attr">password:</span> <span class="string">test</span></span><br><span class="line"> <span class="attr">virtual-host:</span> <span class="string">/test</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、消费者-监听的队列</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">StreamClient</span> {</span><br><span class="line"> <span class="meta">@Input("myMessage")</span></span><br><span class="line"> SubscribableChannel <span class="title function_">input</span><span class="params">()</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@EnableBinding(StreamClient.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StreamReceiver</span> {</span><br><span class="line"> <span class="meta">@StreamListener("myMessage")</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">msg</span><span class="params">(Object msg)</span> {</span><br><span class="line"> System.out.println(<span class="string">"接收到消息"</span> + msg);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、启动类添加注解<code>@EnableBinding(StreamClient.class)</code></p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>6、生产者-导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-stream-rabbit<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>7、生产者-配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">rabbitmq:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">5672</span></span><br><span class="line"> <span class="attr">username:</span> <span class="string">test</span></span><br><span class="line"> <span class="attr">password:</span> <span class="string">test</span></span><br><span class="line"> <span class="attr">virtual-host:</span> <span class="string">/test</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>8、生产者-发布消息</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">StreamClient</span> {</span><br><span class="line"> <span class="meta">@Output("myMessage")</span></span><br><span class="line"> MessageChannel <span class="title function_">output</span><span class="params">()</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MessageController</span> {</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> StreamClient streamClient;</span><br><span class="line"> <span class="meta">@GetMapping("/send")</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">send</span><span class="params">()</span> {</span><br><span class="line"> streamClient.output().send(MessageBuilder.withPayload(<span class="string">"Hello Stream"</span>).build());</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"消息发送成功!!"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>9、启动类添加注解<code>@EnableBinding(StreamClient.class)</code></p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>10、测试访问<code>http://localhost:8080/send</code></p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113093057704.png" alt="image-20201113093057704"></p>
|
||
<p><img src="/posts/39831/images/image-20201112173342142.png" alt="image-20201112173342142"></p>
|
||
<h3 id="8-3-Stream重复消费问题"><a href="#8-3-Stream重复消费问题" class="headerlink" title="8.3 Stream重复消费问题"></a>8.3 Stream重复消费问题</h3><blockquote>
|
||
<p>只需要添加一个配置,指定消费者组</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">cloud:</span></span><br><span class="line"> <span class="attr">stream:</span></span><br><span class="line"> <span class="attr">binders:</span></span><br><span class="line"> <span class="attr">myMessage:</span> <span class="comment">#队列名称</span></span><br><span class="line"> <span class="attr">group:</span> <span class="string">customer</span> <span class="comment">#消费者组</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="8-4-Stream的消费者手动ack"><a href="#8-4-Stream的消费者手动ack" class="headerlink" title="8.4 Stream的消费者手动ack"></a>8.4 Stream的消费者手动ack</h3><blockquote>
|
||
<p>1、编写配置</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#指定服务名称</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">cloud:</span></span><br><span class="line"> <span class="attr">stream:</span></span><br><span class="line"> <span class="comment">#实现手动ack</span></span><br><span class="line"> <span class="attr">rabbit:</span></span><br><span class="line"> <span class="attr">bindings:</span></span><br><span class="line"> <span class="attr">myMessage:</span></span><br><span class="line"> <span class="attr">consumer:</span></span><br><span class="line"> <span class="attr">acknowledgeMode:</span> <span class="string">MANUAL</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、修改消费端方法</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@EnableBinding(StreamClient.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StreamReceiver</span> {</span><br><span class="line"> <span class="meta">@StreamListener("myMessage")</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">msg</span><span class="params">(Object msg,</span></span><br><span class="line"><span class="params"> <span class="meta">@Header(name = AmqpHeaders.CHANNEL)</span> Channel channel,</span></span><br><span class="line"><span class="params"> <span class="meta">@Header(name = AmqpHeaders.DELIVERY_TAG)</span> Long deliveryTag)</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> System.out.println(<span class="string">"接收到消息"</span> + msg);</span><br><span class="line"> channel.basicAck(deliveryTag,<span class="literal">false</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h2 id="九、服务的动态配置-Config"><a href="#九、服务的动态配置-Config" class="headerlink" title="九、服务的动态配置-Config"></a>九、服务的动态配置-Config</h2><h3 id="9-1-引言"><a href="#9-1-引言" class="headerlink" title="9.1 引言"></a>9.1 引言</h3><blockquote>
|
||
<p>1、配置文件分散在不同项目中的,不方便去维护。</p>
|
||
<p>2、配置文件的安全问题。</p>
|
||
<p>3、修改配置文件,无法立即生效。</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201112180552221.png" alt="image-20201112180552221"></p>
|
||
<h3 id="9-2-搭建Config-Server"><a href="#9-2-搭建Config-Server" class="headerlink" title="9.2 搭建Config-Server"></a>9.2 搭建Config-Server</h3><blockquote>
|
||
<p>1、创建Maven工程,修改SpringBoot</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-netflix-eureka-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-config-server<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、添加注解<code>@EnableConfigServer</code></p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableConfigServer</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConfigApplication</span> {</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"> SpringApplication.run(ConfigApplication.class,args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、编写配置文件(git)</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">cloud:</span></span><br><span class="line"> <span class="attr">config:</span></span><br><span class="line"> <span class="attr">server:</span></span><br><span class="line"> <span class="attr">git:</span></span><br><span class="line"> <span class="attr">basedir:</span> <span class="string">E:\config</span> <span class="comment"># 本地仓库地址</span></span><br><span class="line"> <span class="attr">username:</span> <span class="string">xxxxxx@xxxx.com</span> <span class="comment">#远程仓库的用户名</span></span><br><span class="line"> <span class="attr">password:</span> <span class="string">xxxxxxxx</span> <span class="comment">#远程仓库的密码</span></span><br><span class="line"> <span class="attr">uri:</span> <span class="string">https://gitee.com/zyjblog/config-resp.git</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、测试(例子:<code>http://localhost:82/master/customer-xxx.yml</code> (master可以省略))</p>
|
||
<p>访问方式如下:</p>
|
||
<p><code>/{application}/{profile}[/{label}]</code><br><code>/{application}-{profile}.yml</code><br><code>/{label}/{application}-{profile}.yml</code></p>
|
||
<p><code>/{application}-{profile}.properties</code><br><code>/{label}/{application}-{profile}.properties</code></p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113094055276.png" alt="image-20201113094055276"></p>
|
||
<h3 id="9-3、修改Customer连接Config"><a href="#9-3、修改Customer连接Config" class="headerlink" title="9.3、修改Customer连接Config"></a>9.3、修改Customer连接Config</h3><blockquote>
|
||
<p>1、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="string"><dependency></span></span><br><span class="line"> <span class="string"><groupId>org.springframework.cloud</groupId></span></span><br><span class="line"> <span class="string"><artifactId>spring-cloud-config-client</artifactId></span></span><br><span class="line"><span class="string"></dependency></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、修改配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">#指定Eureka服务地址</span><br><span class="line">eureka:</span><br><span class="line"> client:</span><br><span class="line"> service-url:</span><br><span class="line"> defaultZone: http:<span class="comment">//root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka</span></span><br><span class="line"></span><br><span class="line">version: v1</span><br><span class="line">#指定服务名称</span><br><span class="line">spring:</span><br><span class="line"> application:</span><br><span class="line"> name: CUSTOMER-${version}</span><br><span class="line"> cloud:</span><br><span class="line"> config:</span><br><span class="line"> discovery:</span><br><span class="line"> enabled: <span class="literal">true</span></span><br><span class="line"> service-id: CONFIG</span><br><span class="line"> profile: dev</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、修改配置文件名称<code>application.yml</code>改为<code>bootstrap.yml</code></p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>4、测试测试发布消息到RabbMQ</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113102316294.png" alt="image-20201113102316294"></p>
|
||
<h3 id="9-4-实现动态配置"><a href="#9-4-实现动态配置" class="headerlink" title="9.4 实现动态配置"></a>9.4 实现动态配置</h3><h4 id="9-4-1-实现原理"><a href="#9-4-1-实现原理" class="headerlink" title="9.4.1 实现原理"></a>9.4.1 实现原理</h4><p><img src="/posts/39831/images/image-20201113103152115.png" alt="image-20201113103152115"></p>
|
||
<h4 id="9-4-2-服务连接RabbitMQ"><a href="#9-4-2-服务连接RabbitMQ" class="headerlink" title="9.4.2 服务连接RabbitMQ"></a>9.4.2 服务连接RabbitMQ</h4><blockquote>
|
||
<p>1、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-bus-amqp<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、编写配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">rabbitmq:</span></span><br><span class="line"> <span class="attr">virtual-host:</span> <span class="string">/test</span></span><br><span class="line"> <span class="attr">host:</span> <span class="string">localhost</span></span><br><span class="line"> <span class="attr">username:</span> <span class="string">test</span></span><br><span class="line"> <span class="attr">password:</span> <span class="string">test</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">5672</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113104134210.png" alt="image-20201113104134210"></p>
|
||
<h4 id="9-4-3-实现手动刷新"><a href="#9-4-3-实现手动刷新" class="headerlink" title="9.4.3 实现手动刷新"></a>9.4.3 实现手动刷新</h4><blockquote>
|
||
<p>1、导入依赖(两个服务config和customer均添加)</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-actuator<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、编写配置文件(两个服务config和customer均添加)</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">management:</span></span><br><span class="line"> <span class="attr">endpoints:</span></span><br><span class="line"> <span class="attr">web:</span></span><br><span class="line"> <span class="attr">exposure:</span></span><br><span class="line"> <span class="attr">include:</span> <span class="string">"*"</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、为customer添加一个controller,添加注解<code>@RefreshScope</code></p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RefreshScope</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerController</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Value("${env}")</span></span><br><span class="line"> <span class="keyword">private</span> String env;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@GetMapping("/env")</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">env</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> env;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、测试</p>
|
||
<ol>
|
||
<li>CONFIG在gitee修改之后,自动拉取最新的配置信息。</li>
|
||
<li>其他模块需要更新的话,手动发送一个POST请求:<a href="http://localhost:10000/actuator/bus-refresh">http://localhost:10000/actuator/bus-refresh</a> ,不重启项目,即可获取最新的配置信息</li>
|
||
</ol>
|
||
</blockquote>
|
||
<h4 id="9-4-4-内网穿透"><a href="#9-4-4-内网穿透" class="headerlink" title="9.4.4 内网穿透"></a>9.4.4 内网穿透</h4><blockquote>
|
||
<p>1、内网穿透官网:<a href="http://www.ngrok.cc/">http://www.ngrok.cc/</a></p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、注册登录</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>3、购买免费隧道,并配置</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113113814310.png" alt="image-20201113113814310"></p>
|
||
<blockquote>
|
||
<p>4、下载客户端,并复制隧道id,点击运行客户端,复制到客户端中</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113114050201.png" alt="image-20201113114050201"></p>
|
||
<blockquote>
|
||
<p>5、测试访问是否成功</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113114131931.png" alt="image-20201113114131931"></p>
|
||
<h4 id="9-4-5-实现自动刷新配置"><a href="#9-4-5-实现自动刷新配置" class="headerlink" title="9.4.5 实现自动刷新配置"></a>9.4.5 实现自动刷新配置</h4><blockquote>
|
||
<p>1、配置Gitee中的WebHooks</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113120733998.png" alt="image-20201113120733998"></p>
|
||
<blockquote>
|
||
<p>2、给Config添加一个过滤器UrlFilter</p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@WebFilter("/*")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UrlFilter</span> <span class="keyword">implements</span> <span class="title class_">Filter</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_">doFilter</span><span class="params">(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)</span> <span class="keyword">throws</span> IOException, ServletException {</span><br><span class="line"> HttpServletRequest httpServletRequest= (HttpServletRequest) servletRequest;</span><br><span class="line"> String url=httpServletRequest.getRequestURI();</span><br><span class="line"> System.out.println(url);</span><br><span class="line"> <span class="keyword">if</span>(!url.endsWith(<span class="string">"/actuator/bus-refresh"</span>)){</span><br><span class="line"> filterChain.doFilter(servletRequest,servletResponse);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> String body=(httpServletRequest).toString();</span><br><span class="line"> System.out.println(<span class="string">"original body: "</span>+ body);</span><br><span class="line"> RequestWrapper requestWrapper=<span class="keyword">new</span> <span class="title class_">RequestWrapper</span>(httpServletRequest);</span><br><span class="line"> filterChain.doFilter(requestWrapper,servletResponse);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">RequestWrapper</span> <span class="keyword">extends</span> <span class="title class_">HttpServletRequestWrapper</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">RequestWrapper</span><span class="params">(HttpServletRequest request)</span> {</span><br><span class="line"> <span class="built_in">super</span>(request);</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> ServletInputStream <span class="title function_">getInputStream</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">0</span>];</span><br><span class="line"> <span class="type">ByteArrayInputStream</span> <span class="variable">byteArrayInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(bytes);</span><br><span class="line"> <span class="type">ServletInputStream</span> <span class="variable">servletInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServletInputStream</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_">read</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="keyword">return</span> byteArrayInputStream.read();</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_">isFinished</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> byteArrayInputStream.read() == -<span class="number">1</span> ? <span class="literal">true</span> : <span class="literal">false</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">boolean</span> <span class="title function_">isReady</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="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setReadListener</span><span class="params">(ReadListener listener)</span> {</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">return</span> servletInputStream;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、添加注解<code>@ServletComponentScan("cn.zyjblogs.filter")</code></p>
|
||
</blockquote>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableConfigServer</span></span><br><span class="line"><span class="meta">@ServletComponentScan("cn.zyjblogs.filter")</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ConfigApplication</span> {</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"> SpringApplication.run(ConfigApplication.class,args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113121035781.png" alt="image-20201113121035781"></p>
|
||
<h2 id="十、服务的追踪-Sleuth"><a href="#十、服务的追踪-Sleuth" class="headerlink" title="十、服务的追踪-Sleuth"></a>十、服务的追踪-Sleuth</h2><h3 id="10-1-引言"><a href="#10-1-引言" class="headerlink" title="10.1 引言"></a>10.1 引言</h3><blockquote>
|
||
<p>在整个微服务架构中,微服务很多,一个请求可能需要调用很多很多的服务,最终才能完成一个功能,如果说,整个功能出现了问题,在这么多的服务中,如何区定位到问题的所在点,出现问题的原因是什么。</p>
|
||
<p>1、Sleuth可以获取得到整个服务链路的信息</p>
|
||
<p>2、Zipkin通过图形化界面去看到信息。</p>
|
||
<p>3、Sleuth将日志信息存储到数据库中</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113131325996.png" alt="image-20201113131325996"></p>
|
||
<h3 id="10-2-Sleuth使用"><a href="#10-2-Sleuth使用" class="headerlink" title="10.2 Sleuth使用"></a>10.2 Sleuth使用</h3><blockquote>
|
||
<p>1、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-sleuth<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、编写配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">logging:</span></span><br><span class="line"> <span class="attr">level:</span></span><br><span class="line"> <span class="attr">org.springframework.web.servlet.DispatcherServlet:</span> <span class="string">DEBUG</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、测试</p>
|
||
<p>SEARCH:服务名称</p>
|
||
<p>012 总链路id</p>
|
||
<p>b0e:当前服务的链路id</p>
|
||
<p>false:不会将当前的日志信息,输出到其他系统中</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113133658258.png" alt="image-20201113133658258"></p>
|
||
<p><img src="/posts/39831/images/image-20201113133713872.png" alt="image-20201113133713872"></p>
|
||
<p><img src="/posts/39831/images/image-20201113133745985.png" alt="image-20201113133745985"></p>
|
||
<h3 id="10-3-Zipkin的使用"><a href="#10-3-Zipkin的使用" class="headerlink" title="10.3 Zipkin的使用"></a>10.3 Zipkin的使用</h3><blockquote>
|
||
<p>1、搭建Zipkin的web工程 <a href="https://zipkin.io/pages/quickstart">https://zipkin.io/pages/quickstart</a></p>
|
||
<ol>
|
||
<li>docker安装Zipkin</li>
|
||
</ol>
|
||
</blockquote>
|
||
<p>1、使用docker pull拉取</p>
|
||
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker pull openzipkin/zipkin</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>2、使用docker-compose</p>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">"3.1"</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"> <span class="attr">zipkin:</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">daocloud.io/daocloud/zipkin:latest</span></span><br><span class="line"> <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line"> <span class="attr">container_name:</span> <span class="string">zipkin</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="number">9411</span><span class="string">:9411</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker-compose up -d <span class="comment">#启动</span></span><br><span class="line">docker-compose down <span class="comment">#关闭</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、导入依赖</p>
|
||
</blockquote>
|
||
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-zipkin<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、编写配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">sleuth:</span></span><br><span class="line"> <span class="attr">sampler:</span></span><br><span class="line"> <span class="attr">probability:</span> <span class="number">1</span> <span class="comment">#百分之多少的sleuth信息需要输出到zipkin</span></span><br><span class="line"> <span class="attr">zipkin:</span></span><br><span class="line"> <span class="attr">base-url:</span> <span class="string">http://127.0.0.1:9411/</span> <span class="comment">#指定zipkin的地址</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>4、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113153443738.png" alt="image-20201113153443738"></p>
|
||
<h3 id="10-4-整合RabbitMQ"><a href="#10-4-整合RabbitMQ" class="headerlink" title="10.4 整合RabbitMQ"></a>10.4 整合RabbitMQ</h3><blockquote>
|
||
<p>1、导入RabbitMQ依赖(zipkin中已经依赖了RabbitMQ了)</p>
|
||
</blockquote>
|
||
<blockquote>
|
||
<p>2、修改配置文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">zipkin:</span></span><br><span class="line"> <span class="attr">sender:</span></span><br><span class="line"> <span class="attr">type:</span> <span class="string">rabbit</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、修改Zipkin信息</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">"3.1"</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"> <span class="attr">zipkin:</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">daocloud.io/daocloud/zipkin:latest</span></span><br><span class="line"> <span class="comment">#image: docker.io/openzipkin/zipkin:latest</span></span><br><span class="line"> <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line"> <span class="attr">container_name:</span> <span class="string">zipkin</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="number">9411</span><span class="string">:9411</span></span><br><span class="line"> <span class="attr">environment:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">RABBIT_ADDRESSES=10.27.10.123:5672</span> <span class="comment">#本地ipv4地址:端口</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">RABBIT_USER=test</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">RABBIT_PASSWORD=test</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">RABBIT_VIRTUAL_HOST=/test</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113160658535.png" alt="image-20201113160658535"></p>
|
||
<p><img src="/posts/39831/images/image-20201113160724897.png" alt="image-20201113160724897"></p>
|
||
<h3 id="10-5-Zipkin存储数据到ES"><a href="#10-5-Zipkin存储数据到ES" class="headerlink" title="10.5 Zipkin存储数据到ES"></a>10.5 Zipkin存储数据到ES</h3><blockquote>
|
||
<p>1、重新修改zipkin的文件yml文件</p>
|
||
</blockquote>
|
||
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">"3.1"</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"> <span class="attr">zipkin:</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">daocloud.io/daocloud/zipkin:latest</span></span><br><span class="line"> <span class="comment">#image: docker.io/openzipkin/zipkin:latest</span></span><br><span class="line"> <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line"> <span class="attr">container_name:</span> <span class="string">zipkin</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="number">9411</span><span class="string">:9411</span></span><br><span class="line"> <span class="attr">environment:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">RABBIT_ADDRESSES=10.27.10.123:5672</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">RABBIT_USER=test</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">RABBIT_PASSWORD=test</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">RABBIT_VIRTUAL_HOST=/test</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">STORAGE_TYPE=elasticsearch</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">ES_HOSTS=http://10.27.10.123:9200</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>2、安装Es</p>
|
||
</blockquote>
|
||
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"> <span class="comment">#拉取镜像</span></span><br><span class="line"> docker pull elasticsearch</span><br><span class="line"> <span class="comment">#启动参数</span></span><br><span class="line"> docker run --name es1_6.6.0 \</span><br><span class="line">-p 9200:9200 \</span><br><span class="line">-p 9300:9300 \</span><br><span class="line">-e ES_JAVA_OPTS=<span class="string">"-Xms256m -Xmx256m"</span> \</span><br><span class="line">-v /d/elasticsearch/config/es1.yml:/usr/share/elasticsearch/config/elasticsearch.yml \</span><br><span class="line">-v /d/elasticsearch/data/es1:/usr/share/elasticsearch/data \</span><br><span class="line">-v /d/elasticsearch/logs/es1:/usr/share/elasticsearch/logs \</span><br><span class="line">-d 13aa43015aa1</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>3、安装kibana</p>
|
||
</blockquote>
|
||
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment">#拉取kibana</span></span><br><span class="line">docker pull kibana</span><br><span class="line"><span class="comment">#启动参数</span></span><br><span class="line">docker run --name kibana6.6.0 -e ELASTICSEARCH_URL=http://10.27.10.123:9200 -p 5601:5601 -d dfc685453eaa</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>创建索引</p>
|
||
<p><img src="/posts/39831/images/image-20201113164636525.png" alt="image-20201113164636525"></p>
|
||
<blockquote>
|
||
<p>4、重启zipkin后数据未丢失</p>
|
||
</blockquote>
|
||
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker-compose restart</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>5、测试</p>
|
||
</blockquote>
|
||
<p><img src="/posts/39831/images/image-20201113164930192.png" alt="image-20201113164930192"></p>
|
||
<h2 id="十一、完整SpringCloud架构图"><a href="#十一、完整SpringCloud架构图" class="headerlink" title="十一、完整SpringCloud架构图"></a>十一、完整SpringCloud架构图</h2><p><img src="/posts/39831/images/image-20201113165759501.png" alt="image-20201113165759501"></p>
|
||
]]></content>
|
||
<categories>
|
||
<category>java</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>springcloud</tag>
|
||
<tag>java</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>二分查找模版</title>
|
||
<url>/posts/6077.html</url>
|
||
<content><![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="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="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="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>
|
||
<categories>
|
||
<category>算法</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>算法</tag>
|
||
<tag>查找</tag>
|
||
<tag>树</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>状态转移方程</title>
|
||
<url>/posts/92fa7813.html</url>
|
||
<content><![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="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="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="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>
|
||
<categories>
|
||
<category>算法</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>dp</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>Union-Find 算法</title>
|
||
<url>/posts/e88a2bb0.html</url>
|
||
<content><![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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>算法</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>算法</tag>
|
||
<tag>并查集</tag>
|
||
<tag>图</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>「游戏」寻路算法之A Star算法原理及实现</title>
|
||
<url>/posts/0.html</url>
|
||
<content><![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="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="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="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="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>
|
||
<categories>
|
||
<category>算法</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>图</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>C++ 使用 chrono 库处理日期和时间</title>
|
||
<url>/posts/59243.html</url>
|
||
<content><![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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
<tag>chrono</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>C/C++ 关于 cJson 库的使用</title>
|
||
<url>/posts/63502.html</url>
|
||
<content><![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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
<tag>cJson</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>C++后台</title>
|
||
<url>/posts/59764.html</url>
|
||
<content><![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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
<tag>后端</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>一起探索C++类内存分布</title>
|
||
<url>/posts/97623f3c.html</url>
|
||
<content><![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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
<tag>memory</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>C++编译期多态与运行期多态</title>
|
||
<url>/posts/35899.html</url>
|
||
<content><![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="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="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="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="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="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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>C++内存管理</title>
|
||
<url>/posts/b57ba5ed.html</url>
|
||
<content><![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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
<tag>memory</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>虚基类</title>
|
||
<url>/posts/11902.html</url>
|
||
<content><![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="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="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="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="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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>C语言中三块难啃的硬骨头</title>
|
||
<url>/posts/0.html</url>
|
||
<content><![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="code"><pre><span class="line">int p;</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>这是一个普通的整型变量</p>
|
||
<figure class="highlight plaintext"><table><tr><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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
<tag>指针</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>BIO,NIO,AIO 总结</title>
|
||
<url>/posts/43515.html</url>
|
||
<content><![CDATA[<h1 id="BIO-NIO-AIO-总结"><a href="#BIO-NIO-AIO-总结" class="headerlink" title="BIO,NIO,AIO 总结"></a>BIO,NIO,AIO 总结</h1><p> Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。</p>
|
||
<p>在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。</p>
|
||
<p>关于同步和异步的概念解读困扰着很多程序员,大部分的解读都会带有自己的一点偏见。参考了 <a href="https://stackoverflow.com/questions/748175/asynchronous-vs-synchronous-execution-what-does-it-really-mean">Stackoverflow</a>相关问题后对原有答案进行了进一步完善:</p>
|
||
<blockquote>
|
||
<p>When you execute something synchronously, you wait for it to finish before moving on to another task. When you execute something asynchronously, you can move on to another task before it finishes.</p>
|
||
<p>当你同步执行某项任务时,你需要等待其完成才能继续执行其他任务。当你异步执行某些操作时,你可以在完成另一个任务之前继续进行。</p>
|
||
</blockquote>
|
||
<ul>
|
||
<li><strong>同步</strong> :两个同步任务相互依赖,并且一个任务必须以依赖于另一任务的某种方式执行。 比如在<code>A->B</code>事件模型中,你需要先完成 A 才能执行B。 再换句话说,同步调用中被调用者未处理完请求之前,调用不返回,调用者会一直等待结果的返回。</li>
|
||
<li><strong>异步</strong>: 两个异步的任务完全独立的,一方的执行不需要等待另外一方的执行。再换句话说,异步调用种一调用就返回结果不需要等待结果返回,当结果返回的时候通过回调函数或者其他方式拿着结果再做相关事情,</li>
|
||
</ul>
|
||
<p><strong>阻塞和非阻塞</strong></p>
|
||
<ul>
|
||
<li><strong>阻塞:</strong> 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。</li>
|
||
<li><strong>非阻塞:</strong> 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。</li>
|
||
</ul>
|
||
<p><strong>如何区分 “同步/异步 ”和 “阻塞/非阻塞” 呢?</strong></p>
|
||
<p>同步/异步是从行为角度描述事物的,而阻塞和非阻塞描述的当前事物的状态(等待调用结果时的状态)。</p>
|
||
<h2 id="1-BIO-Blocking-I-O"><a href="#1-BIO-Blocking-I-O" class="headerlink" title="1. BIO (Blocking I/O)"></a>1. BIO (Blocking I/O)</h2><p>同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。</p>
|
||
<h3 id="1-1-传统-BIO"><a href="#1-1-传统-BIO" class="headerlink" title="1.1 传统 BIO"></a>1.1 传统 BIO</h3><p>BIO通信(一请求一应答)模型图如下(图源网络,原出处不明):</p>
|
||
<p><img src="/posts/43515/images/2.png" alt="传统BIO通信模型图"></p>
|
||
<p>采用 <strong>BIO 通信模型</strong> 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在<code>while(true)</code> 循环中服务端会调用 <code>accept()</code> 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。</p>
|
||
<p>如果要让 <strong>BIO 通信模型</strong> 能够同时处理多个客户端请求,就必须使用多线程(主要原因是<code>socket.accept()</code>、<code>socket.read()</code>、<code>socket.write()</code> 涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 <strong>一请求一应答通信模型</strong> 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过 <strong>线程池机制</strong> 改善,线程池还可以让线程的创建和回收成本相对较低。使用<code>FixedThreadPool</code> 可以有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M),下面一节”伪异步 BIO”中会详细介绍到。</p>
|
||
<p><strong>我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题?</strong></p>
|
||
<p>在 Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。尤其在 Linux 这样的操作系统中,线程本质上就是一个进程,创建和销毁线程都是重量级的系统函数。如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。</p>
|
||
<h3 id="1-2-伪异步-IO"><a href="#1-2-伪异步-IO" class="headerlink" title="1.2 伪异步 IO"></a>1.2 伪异步 IO</h3><p>为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化一一一后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N.通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。</p>
|
||
<p>伪异步IO模型图(图源网络,原出处不明):</p>
|
||
<p><img src="/posts/43515/images/3.png" alt="伪异步IO模型图"></p>
|
||
<p>采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。</p>
|
||
<p>伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。</p>
|
||
<h3 id="1-3-代码示例"><a href="#1-3-代码示例" class="headerlink" title="1.3 代码示例"></a>1.3 代码示例</h3><p>下面代码中演示了BIO通信(一请求一应答)模型。我们会在客户端创建多个线程依次连接服务端并向其发送”当前时间+:hello world”,服务端会为每个客户端线程创建一个线程来处理。代码示例出自闪电侠的博客,原地址如下:</p>
|
||
<p><a href="https://www.jianshu.com/p/a4e03835921a">https://www.jianshu.com/p/a4e03835921a</a></p>
|
||
<p><strong>客户端</strong></p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> 闪电侠</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2018年10月14日</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>:客户端</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IOClient</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">// TODO 创建多个线程,模拟多个客户端连接服务端</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Socket</span>(<span class="string">"127.0.0.1"</span>, <span class="number">3333</span>);</span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> socket.getOutputStream().write((<span class="keyword">new</span> <span class="title class_">Date</span>() + <span class="string">": hello world"</span>).getBytes());</span><br><span class="line"> Thread.sleep(<span class="number">2000</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> }</span><br><span class="line"> }).start();</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><strong>服务端</strong></p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> 闪电侠</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2018年10月14日</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 服务端</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IOServer</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> IOException {</span><br><span class="line"> <span class="comment">// TODO 服务端处理客户端连接请求</span></span><br><span class="line"> <span class="type">ServerSocket</span> <span class="variable">serverSocket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerSocket</span>(<span class="number">3333</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 阻塞方法获取新的连接</span></span><br><span class="line"> <span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> serverSocket.accept();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 每一个新的连接都创建一个线程,负责读取数据</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">int</span> len;</span><br><span class="line"> <span class="type">byte</span>[] data = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span>];</span><br><span class="line"> <span class="type">InputStream</span> <span class="variable">inputStream</span> <span class="operator">=</span> socket.getInputStream();</span><br><span class="line"> <span class="comment">// 按字节流方式读取数据</span></span><br><span class="line"> <span class="keyword">while</span> ((len = inputStream.read(data)) != -<span class="number">1</span>) {</span><br><span class="line"> System.out.println(<span class="keyword">new</span> <span class="title class_">String</span>(data, <span class="number">0</span>, len));</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> }).start();</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>
|
||
|
||
<h3 id="1-4-总结"><a href="#1-4-总结" class="headerlink" title="1.4 总结"></a>1.4 总结</h3><p>在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。</p>
|
||
<h2 id="2-NIO-New-I-O"><a href="#2-NIO-New-I-O" class="headerlink" title="2. NIO (New I/O)"></a>2. NIO (New I/O)</h2><h3 id="2-1-NIO-简介"><a href="#2-1-NIO-简介" class="headerlink" title="2.1 NIO 简介"></a>2.1 NIO 简介</h3><p> NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。</p>
|
||
<p>NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 <code>Socket</code> 和 <code>ServerSocket</code> 相对应的 <code>SocketChannel</code> 和 <code>ServerSocketChannel</code> 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。</p>
|
||
<h3 id="2-2-NIO的特性-NIO与IO区别"><a href="#2-2-NIO的特性-NIO与IO区别" class="headerlink" title="2.2 NIO的特性/NIO与IO区别"></a>2.2 NIO的特性/NIO与IO区别</h3><p>如果是在面试中回答这个问题,我觉得首先肯定要从 NIO 流是非阻塞 IO 而 IO 流是阻塞 IO 说起。然后,可以从 NIO 的3个核心组件/特性为 NIO 带来的一些改进来分析。如果,你把这些都回答上了我觉得你对于 NIO 就有了更为深入一点的认识,面试官问到你这个问题,你也能很轻松的回答上来了。</p>
|
||
<h4 id="1-Non-blocking-IO(非阻塞IO)"><a href="#1-Non-blocking-IO(非阻塞IO)" class="headerlink" title="1)Non-blocking IO(非阻塞IO)"></a>1)Non-blocking IO(非阻塞IO)</h4><p><strong>IO流是阻塞的,NIO流是不阻塞的。</strong></p>
|
||
<p>Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。</p>
|
||
<p>Java IO的各种流是阻塞的。这意味着,当一个线程调用 <code>read()</code> 或 <code>write()</code> 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了</p>
|
||
<h4 id="2-Buffer-缓冲区"><a href="#2-Buffer-缓冲区" class="headerlink" title="2)Buffer(缓冲区)"></a>2)Buffer(缓冲区)</h4><p><strong>IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。</strong></p>
|
||
<p>Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。</p>
|
||
<p>在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。</p>
|
||
<p>最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。</p>
|
||
<h4 id="3-Channel-通道"><a href="#3-Channel-通道" class="headerlink" title="3)Channel (通道)"></a>3)Channel (通道)</h4><p>NIO 通过Channel(通道) 进行读写。</p>
|
||
<p>通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。</p>
|
||
<h4 id="4-Selector-选择器"><a href="#4-Selector-选择器" class="headerlink" title="4)Selector (选择器)"></a>4)Selector (选择器)</h4><p>NIO有选择器,而IO没有。</p>
|
||
<p>选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。</p>
|
||
<p><img src="/posts/43515/images/Slector.png" alt="一个单线程中Selector维护3个Channel的示意图"></p>
|
||
<h3 id="2-3-NIO-读数据和写数据方式"><a href="#2-3-NIO-读数据和写数据方式" class="headerlink" title="2.3 NIO 读数据和写数据方式"></a>2.3 NIO 读数据和写数据方式</h3><p>通常来说NIO中的所有IO都是从 Channel(通道) 开始的。</p>
|
||
<ul>
|
||
<li>从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。</li>
|
||
<li>从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。</li>
|
||
</ul>
|
||
<p>数据读取和写入操作图示:</p>
|
||
<p><img src="/posts/43515/images/NIO%E8%AF%BB%E5%86%99%E6%95%B0%E6%8D%AE%E7%9A%84%E6%96%B9%E5%BC%8F.png" alt="NIO读写数据的方式"></p>
|
||
<h3 id="2-4-NIO核心组件简单介绍"><a href="#2-4-NIO核心组件简单介绍" class="headerlink" title="2.4 NIO核心组件简单介绍"></a>2.4 NIO核心组件简单介绍</h3><p>NIO 包含下面几个核心的组件:</p>
|
||
<ul>
|
||
<li>Channel(通道)</li>
|
||
<li>Buffer(缓冲区)</li>
|
||
<li>Selector(选择器)</li>
|
||
</ul>
|
||
<p>整个NIO体系包含的类远远不止这三个,只能说这三个是NIO体系的“核心API”。我们上面已经对这三个概念进行了基本的阐述,这里就不多做解释了。</p>
|
||
<h3 id="2-5-代码示例"><a href="#2-5-代码示例" class="headerlink" title="2.5 代码示例"></a>2.5 代码示例</h3><p>代码示例出自闪电侠的博客,原地址如下:</p>
|
||
<p><a href="https://www.jianshu.com/p/a4e03835921a">https://www.jianshu.com/p/a4e03835921a</a></p>
|
||
<p>客户端 IOClient.java 的代码不变,我们对服务端使用 NIO 进行改造。以下代码较多而且逻辑比较复杂,大家看看就好。</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> 闪电侠</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2019年2月21日</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: NIO 改造后的服务端</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NIOServer</span> {</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> IOException {</span><br><span class="line"> <span class="comment">// 1. serverSelector负责轮询是否有新的连接,服务端监测到新的连接之后,不再创建一个新的线程,</span></span><br><span class="line"> <span class="comment">// 而是直接将新连接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等</span></span><br><span class="line"> <span class="type">Selector</span> <span class="variable">serverSelector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line"> <span class="comment">// 2. clientSelector负责轮询连接是否有数据可读</span></span><br><span class="line"> <span class="type">Selector</span> <span class="variable">clientSelector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 对应IO编程中服务端启动</span></span><br><span class="line"> <span class="type">ServerSocketChannel</span> <span class="variable">listenerChannel</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line"> listenerChannel.socket().bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">3333</span>));</span><br><span class="line"> listenerChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line"> listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> <span class="comment">// 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms</span></span><br><span class="line"> <span class="keyword">if</span> (serverSelector.select(<span class="number">1</span>) > <span class="number">0</span>) {</span><br><span class="line"> Set<SelectionKey> set = serverSelector.selectedKeys();</span><br><span class="line"> Iterator<SelectionKey> keyIterator = set.iterator();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (keyIterator.hasNext()) {</span><br><span class="line"> <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> keyIterator.next();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (key.isAcceptable()) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector</span></span><br><span class="line"> <span class="type">SocketChannel</span> <span class="variable">clientChannel</span> <span class="operator">=</span> ((ServerSocketChannel) key.channel()).accept();</span><br><span class="line"> clientChannel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line"> clientChannel.register(clientSelector, SelectionKey.OP_READ);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> keyIterator.remove();</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="keyword">catch</span> (IOException ignored) {</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> <span class="comment">// (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms</span></span><br><span class="line"> <span class="keyword">if</span> (clientSelector.select(<span class="number">1</span>) > <span class="number">0</span>) {</span><br><span class="line"> Set<SelectionKey> set = clientSelector.selectedKeys();</span><br><span class="line"> Iterator<SelectionKey> keyIterator = set.iterator();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (keyIterator.hasNext()) {</span><br><span class="line"> <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> keyIterator.next();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (key.isReadable()) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="type">SocketChannel</span> <span class="variable">clientChannel</span> <span class="operator">=</span> (SocketChannel) key.channel();</span><br><span class="line"> <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"> <span class="comment">// (3) 面向 Buffer</span></span><br><span class="line"> clientChannel.read(byteBuffer);</span><br><span class="line"> byteBuffer.flip();</span><br><span class="line"> System.out.println(</span><br><span class="line"> Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> keyIterator.remove();</span><br><span class="line"> key.interestOps(SelectionKey.OP_READ);</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="keyword">catch</span> (IOException ignored) {</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?从上面的代码中大家都可以看出来,是真的难用!除了编程复杂、编程模型难之外,它还有以下让人诟病的问题:</p>
|
||
<ul>
|
||
<li>JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%</li>
|
||
<li>项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高,上面这一坨代码我都不能保证没有 bug</li>
|
||
</ul>
|
||
<p>Netty 的出现很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题。</p>
|
||
<h3 id="3-AIO-Asynchronous-I-O"><a href="#3-AIO-Asynchronous-I-O" class="headerlink" title="3. AIO (Asynchronous I/O)"></a>3. AIO (Asynchronous I/O)</h3><p>AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。</p>
|
||
<p>AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。(除了 AIO 其他的 IO 类型都是同步的,这一点可以从底层IO线程模型解释,推荐一篇文章:<a href="https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41#wechat_redirect">《漫话:如何给女朋友解释什么是Linux的五种IO模型?》</a> )</p>
|
||
<p>查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。</p>
|
||
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
|
||
<li>《Netty 权威指南》第二版</li>
|
||
<li><a href="https://zhuanlan.zhihu.com/p/23488863">https://zhuanlan.zhihu.com/p/23488863</a> (美团技术团队)</li>
|
||
</ul>
|
||
]]></content>
|
||
<categories>
|
||
<category>网络</category>
|
||
<category>java</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>java</tag>
|
||
<tag>网络</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>Java线程生命周期与状态切换</title>
|
||
<url>/posts/43515.html</url>
|
||
<content><![CDATA[<h1 id="Java线程生命周期与状态切换"><a href="#Java线程生命周期与状态切换" class="headerlink" title="Java线程生命周期与状态切换"></a>Java线程生命周期与状态切换</h1><p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-logo.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-logo.png" alt="img"></a></p>
|
||
<h2 id="前提"><a href="#前提" class="headerlink" title="前提#"></a>前提<a href="https://www.cnblogs.com/throwable/p/13439079.html#%E5%89%8D%E6%8F%90">#</a></h2><p>最近有点懒散,没什么比较有深度的产出。刚好想重新研读一下<code>JUC</code>线程池的源码实现,在此之前先深入了解一下<code>Java</code>中的线程实现,包括线程的生命周期、状态切换以及线程的上下文切换等等。编写本文的时候,使用的<code>JDK</code>版本是11。</p>
|
||
<h2 id="Java线程的实现"><a href="#Java线程的实现" class="headerlink" title="Java线程的实现#"></a>Java线程的实现<a href="https://www.cnblogs.com/throwable/p/13439079.html#java%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%AE%9E%E7%8E%B0">#</a></h2><p>在<strong>JDK1.2之后</strong>,Java线程模型已经确定了基于操作系统原生线程模型实现。因此,目前或者今后的JDK版本中,操作系统支持怎么样的线程模型,在很大程度上决定了Java虚拟机的线程如何映射,这一点在不同的平台上没有办法达成一致,虚拟机规范中也未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对于Java程序来说,这些差异是透明的。</p>
|
||
<p>对应<code>Oracle Sun JDK</code>或者说<code>Oracle Sun JVM</code>而言,它的Windows版本和Linux版本都是使用<strong>一对一的线程模型</strong>实现的(如下图所示)。</p>
|
||
<p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-1.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-1.png" alt="j-t-l-s-1.png"></a></p>
|
||
<p>也就是一条<code>Java</code>线程就映射到一条轻量级进程(<strong>Light Weight Process</strong>)中,而一条轻量级线程又映射到一条内核线程(<strong>Kernel-Level Thread</strong>)。我们平时所说的线程,往往就是指轻量级进程(或者通俗来说我们平时新建的<code>java.lang.Thread</code>就是轻量级进程实例的一个”句柄”,因为一个<code>java.lang.Thread</code>实例会对应<code>JVM</code>里面的一个<code>JavaThread</code>实例,而<code>JVM</code>里面的<code>JavaThread</code>就应该理解为轻量级进程)。前面推算这个线程映射关系,可以知道,我们在应用程序中创建或者操作的<code>java.lang.Thread</code>实例最终会映射到系统的内核线程,如果我们恶意或者实验性无限创建<code>java.lang.Thread</code>实例,最终会影响系统的正常运行甚至导致系统崩溃(可以在<code>Windows</code>开发环境中做实验,确保内存足够的情况下使用死循环创建和运行<code>java.lang.Thread</code>实例)。</p>
|
||
<p>线程调度方式包括两种,协同式线程调度和抢占式线程调度。</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">协同式线程调度</td>
|
||
<td align="center">线程的执行时间由线程本身控制,执行完毕后主动通知操作系统切换到另一个线程上</td>
|
||
<td align="center">某个线程如果不让出CPU执行时间可能会导致整个系统崩溃</td>
|
||
<td align="center">实现简单,没有线程同步的问题</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center">抢占式线程调度</td>
|
||
<td align="center">每个线程由操作系统来分配执行时间,线程的切换不由线程自身决定</td>
|
||
<td align="center">实现相对复杂,操作系统需要控制线程同步和切换</td>
|
||
<td align="center">不会出现一个线程阻塞导致系统崩溃的问题</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<p><code>Java</code>线程最终会映射为系统内核原生线程,所以<code>Java</code>线程调度最终取决于系操作系统,而目前主流的操作系统内核线程调度基本都是使用抢占式线程调度。也就是可以死记硬背一下:<strong>Java线程是使用抢占式线程调度方式进行线程调度的</strong>。</p>
|
||
<p>很多操作系统都提供线程优先级的概念,但是由于平台特性的问题,Java中的线程优先级和不同平台中系统线程优先级并不匹配,所以Java线程优先级可以仅仅理解为“<strong>建议优先级</strong>”,通俗来说就是<code>java.lang.Thread#setPriority(int newPriority)</code>并不一定生效,<strong>有可能Java线程的优先级会被系统自行改变</strong>。</p>
|
||
<h2 id="Java线程的状态切换"><a href="#Java线程的状态切换" class="headerlink" title="Java线程的状态切换#"></a>Java线程的状态切换<a href="https://www.cnblogs.com/throwable/p/13439079.html#java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81%E5%88%87%E6%8D%A2">#</a></h2><p><code>Java</code>线程的状态可以从<code>java.lang.Thread</code>的内部枚举类<code>java.lang.Thread$State</code>得知:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">State</span> {</span><br><span class="line"> </span><br><span class="line"> NEW,</span><br><span class="line"></span><br><span class="line"> RUNNABLE,</span><br><span class="line"></span><br><span class="line"> BLOCKED,</span><br><span class="line"></span><br><span class="line"> WAITING,</span><br><span class="line"></span><br><span class="line"> TIMED_WAITING,</span><br><span class="line"></span><br><span class="line"> TERMINATED;</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/202008/j-t-l-s-3.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-3.png" alt="j-t-l-s-3"></a></p>
|
||
<p><strong>线程状态之间关系切换</strong>图如下:</p>
|
||
<p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-2.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-2.png" alt="j-t-l-s-2"></a></p>
|
||
<p>下面通过API注释和一些简单的代码例子分析一下Java线程的状态含义和状态切换。</p>
|
||
<h3 id="NEW状态"><a href="#NEW状态" class="headerlink" title="NEW状态#"></a>NEW状态<a href="https://www.cnblogs.com/throwable/p/13439079.html#new%E7%8A%B6%E6%80%81">#</a></h3><p><strong>API注释</strong>:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Thread state for a thread which has not yet started.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">NEW,</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>线程实例尚未启动时候的线程状态。</p>
|
||
</blockquote>
|
||
<p>一个刚创建而尚未启动(尚未调用<code>Thread#start()</code>方法)的Java线程实例的就是处于<code>NEW</code>状态。</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadState</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="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>();</span><br><span class="line"> System.out.println(thread.getState());</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">NEW</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="RUNNABLE状态"><a href="#RUNNABLE状态" class="headerlink" title="RUNNABLE状态#"></a>RUNNABLE状态<a href="https://www.cnblogs.com/throwable/p/13439079.html#runnable%E7%8A%B6%E6%80%81">#</a></h3><p><strong>API注释</strong>:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Thread state for a runnable thread. A thread in the runnable</span></span><br><span class="line"><span class="comment"> * state is executing in the Java virtual machine but it may</span></span><br><span class="line"><span class="comment"> * be waiting for other resources from the operating system</span></span><br><span class="line"><span class="comment"> * such as processor.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">RUNNABLE,</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>可运行状态下线程的线程状态。可运行状态下的线程在Java虚拟机中执行,但它可能执行等待操作系统的其他资源,例如处理器。</p>
|
||
</blockquote>
|
||
<p>当Java线程实例调用了<code>Thread#start()</code>之后,就会进入<code>RUNNABLE</code>状态。<code>RUNNABLE</code>状态可以认为包含两个子状态:<code>READY</code>和<code>RUNNING</code>。</p>
|
||
<ul>
|
||
<li><code>READY</code>:该状态的线程可以被线程调度器进行调度使之更变为<code>RUNNING</code>状态。</li>
|
||
<li><code>RUNNING</code>:该状态表示线程正在运行,线程对象的<code>run()</code>方法中的代码所对应的的指令正在被CPU执行。</li>
|
||
</ul>
|
||
<p>当Java线程实例<code>Thread#yield()</code>方法被调用时或者由于线程调度器的调度,线程实例的状态有可能由<code>RUNNING</code>转变为<code>READY</code>,但是从线程状态<code>Thread#getState()</code>获取到的状态依然是<code>RUNNABLE</code>。例如:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadState1</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="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()-> {</span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>){</span><br><span class="line"> Thread.<span class="keyword">yield</span>();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> thread.start();</span><br><span class="line"> Thread.sleep(<span class="number">2000</span>);</span><br><span class="line"> System.out.println(thread.getState());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 输出结果</span></span><br><span class="line">RUNNABLE</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="WAITING状态"><a href="#WAITING状态" class="headerlink" title="WAITING状态#"></a>WAITING状态<a href="https://www.cnblogs.com/throwable/p/13439079.html#waiting%E7%8A%B6%E6%80%81">#</a></h3><p><strong>API注释</strong>:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Thread state for a waiting thread.</span></span><br><span class="line"><span class="comment"> * A thread is in the waiting state due to calling one of the</span></span><br><span class="line"><span class="comment"> * following methods:</span></span><br><span class="line"><span class="comment"> * <ul></span></span><br><span class="line"><span class="comment"> * <li>{<span class="doctag">@link</span> Object#wait() Object.wait} with no timeout</li></span></span><br><span class="line"><span class="comment"> * <li>{<span class="doctag">@link</span> #join() Thread.join} with no timeout</li></span></span><br><span class="line"><span class="comment"> * <li>{<span class="doctag">@link</span> LockSupport#park() LockSupport.park}</li></span></span><br><span class="line"><span class="comment"> * </ul></span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <p>A thread in the waiting state is waiting for another thread to</span></span><br><span class="line"><span class="comment"> * perform a particular action.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * For example, a thread that has called {<span class="doctag">@code</span> Object.wait()}</span></span><br><span class="line"><span class="comment"> * on an object is waiting for another thread to call</span></span><br><span class="line"><span class="comment"> * {<span class="doctag">@code</span> Object.notify()} or {<span class="doctag">@code</span> Object.notifyAll()} on</span></span><br><span class="line"><span class="comment"> * that object. A thread that has called {<span class="doctag">@code</span> Thread.join()}</span></span><br><span class="line"><span class="comment"> * is waiting for a specified thread to terminate.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> WAITING,</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>等待中线程的状态。一个线程进入等待状态是由于调用了下面方法之一:<br>不带超时的Object#wait()<br>不带超时的Thread#join()<br>LockSupport.park()<br>一个处于等待状态的线程总是在等待另一个线程进行一些特殊的处理。<br>例如:一个线程调用了Object#wait(),那么它在等待另一个线程调用对象上的Object#notify()或者Object#notifyAll();一个线程调用了Thread#join(),那么它在等待另一个线程终结。</p>
|
||
</blockquote>
|
||
<p><code>WAITING</code>是<strong>无限期的等待状态</strong>,这种状态下的线程不会被分配CPU执行时间。当一个线程执行了某些方法之后就会进入无限期等待状态,直到被显式唤醒,被唤醒后,线程状态由<code>WAITING</code>更变为<code>RUNNABLE</code>然后继续执行。</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th align="center"><code>RUNNABLE</code>转换为<code>WAITING</code>的方法(无限期等待)</th>
|
||
<th align="center"><code>WAITING</code>转换为<code>RUNNABLE</code>的方法(唤醒)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td align="center"><code>Object#wait()</code></td>
|
||
<td align="center">`Object#notify()</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center"><code>Thread#join()</code></td>
|
||
<td align="center">-</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center"><code>LockSupport.part()</code></td>
|
||
<td align="center"><code>LockSupport.unpart(thread)</code></td>
|
||
</tr>
|
||
</tbody></table>
|
||
<p>其中<code>Thread#join()</code>方法相对比较特殊,它会阻塞线程实例直到线程实例执行完毕,可以观察它的源码如下:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">join</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> join(<span class="number">0</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">join</span><span class="params">(<span class="type">long</span> millis)</span><span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="type">long</span> <span class="variable">base</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"> <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (millis < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">"timeout value is negative"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (millis == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">while</span> (isAlive()) {</span><br><span class="line"> wait(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">while</span> (isAlive()) {</span><br><span class="line"> <span class="type">long</span> <span class="variable">delay</span> <span class="operator">=</span> millis - now;</span><br><span class="line"> <span class="keyword">if</span> (delay <= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> wait(delay);</span><br><span class="line"> now = System.currentTimeMillis() - base;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>可见<code>Thread#join()</code>是在线程实例存活的时候总是调用<code>Object#wait()</code>方法,也就是必须在线程执行完毕<code>isAlive()</code>为false(意味着线程生命周期已经终结)的时候才会解除阻塞。</p>
|
||
<p>基于<code>WAITING</code>状态举个例子:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadState3</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="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()-> {</span><br><span class="line"> LockSupport.park();</span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>){</span><br><span class="line"> Thread.<span class="keyword">yield</span>();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> thread.start();</span><br><span class="line"> Thread.sleep(<span class="number">50</span>);</span><br><span class="line"> System.out.println(thread.getState());</span><br><span class="line"> LockSupport.unpark(thread);</span><br><span class="line"> Thread.sleep(<span class="number">50</span>);</span><br><span class="line"> System.out.println(thread.getState());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 输出结果</span></span><br><span class="line">WAITING</span><br><span class="line">RUNNABLE</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="TIMED-WAITING状态"><a href="#TIMED-WAITING状态" class="headerlink" title="TIMED WAITING状态#"></a>TIMED WAITING状态<a href="https://www.cnblogs.com/throwable/p/13439079.html#timed-waiting%E7%8A%B6%E6%80%81">#</a></h3><p><strong>API注释</strong>:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* Thread state for a waiting thread with a specified waiting time.</span></span><br><span class="line"><span class="comment">* A thread is in the timed waiting state due to calling one of</span></span><br><span class="line"><span class="comment">* the following methods with a specified positive waiting time:</span></span><br><span class="line"><span class="comment">* <ul></span></span><br><span class="line"><span class="comment">* <li>{<span class="doctag">@link</span> #sleep Thread.sleep}</li></span></span><br><span class="line"><span class="comment">* <li>{<span class="doctag">@link</span> Object#wait(long) Object.wait} with timeout</li></span></span><br><span class="line"><span class="comment">* <li>{<span class="doctag">@link</span> #join(long) Thread.join} with timeout</li></span></span><br><span class="line"><span class="comment">* <li>{<span class="doctag">@link</span> LockSupport#parkNanos LockSupport.parkNanos}</li></span></span><br><span class="line"><span class="comment">* <li>{<span class="doctag">@link</span> LockSupport#parkUntil LockSupport.parkUntil}</li></span></span><br><span class="line"><span class="comment">* </ul></span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">TIMED_WAITING,</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>定义了具体等待时间的等待中线程的状态。一个线程进入该状态是由于指定了具体的超时期限调用了下面方法之一:<br>Thread.sleep()<br>带超时的Object#wait()<br>带超时的Thread#join()<br>LockSupport.parkNanos()<br>LockSupport.parkUntil()</p>
|
||
</blockquote>
|
||
<p><code>TIMED WAITING</code>就是<strong>有限期等待状态</strong>,它和<code>WAITING</code>有点相似,这种状态下的线程不会被分配CPU执行时间,不过这种状态下的线程不需要被显式唤醒,只需要等待超时限期到达就会被<code>VM</code>唤醒,有点类似于现实生活中的闹钟。</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th align="center"><code>RUNNABLE</code>转换为<code>TIMED WAITING</code>的方法(有限期等待)</th>
|
||
<th align="center"><code>TIMED WAITING</code>转换为<code>RUNNABLE</code>的方法(超时解除等待)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td align="center"><code>Object#wait(timeout)</code></td>
|
||
<td align="center">-</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center"><code>Thread#sleep(timeout)</code></td>
|
||
<td align="center">-</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center"><code>Thread#join(timeout)</code></td>
|
||
<td align="center">-</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center"><code>LockSupport.parkNanos(timeout)</code></td>
|
||
<td align="center">-</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center"><code>LockSupport.parkUntil(timeout)</code></td>
|
||
<td align="center">-</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<p>举个例子:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadState4</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="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()-> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> <span class="comment">//ignore</span></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> thread.start();</span><br><span class="line"> Thread.sleep(<span class="number">50</span>);</span><br><span class="line"> System.out.println(thread.getState());</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> System.out.println(thread.getState());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 输出结果</span></span><br><span class="line">TIMED_WAITING</span><br><span class="line">TERMINATED</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="BLOCKED状态"><a href="#BLOCKED状态" class="headerlink" title="BLOCKED状态#"></a>BLOCKED状态<a href="https://www.cnblogs.com/throwable/p/13439079.html#blocked%E7%8A%B6%E6%80%81">#</a></h3><p><strong>API注释</strong>:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* Thread state for a thread blocked waiting for a monitor lock.</span></span><br><span class="line"><span class="comment">* A thread in the blocked state is waiting for a monitor lock</span></span><br><span class="line"><span class="comment">* to enter a synchronized block/method or</span></span><br><span class="line"><span class="comment">* reenter a synchronized block/method after calling</span></span><br><span class="line"><span class="comment">* {<span class="doctag">@link</span> Object#wait() Object.wait}.</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">BLOCKED,</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>此状态表示一个线程正在阻塞等待获取一个监视器锁。如果线程处于阻塞状态,说明线程等待进入同步代码块或者同步方法的监视器锁或者在调用了Object#wait()之后重入同步代码块或者同步方法。</p>
|
||
</blockquote>
|
||
<p><code>BLOCKED</code>状态也就是阻塞状态,该状态下的线程不会被分配CPU执行时间。线程的状态为<code>BLOCKED</code>的时候有两种可能的情况:</p>
|
||
<blockquote>
|
||
<p>A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method</p>
|
||
</blockquote>
|
||
<ol>
|
||
<li>线程正在等待一个监视器锁,只有获取监视器锁之后才能进入<code>synchronized</code>代码块或者<code>synchronized</code>方法,在此等待获取锁的过程线程都处于阻塞状态。</li>
|
||
</ol>
|
||
<blockquote>
|
||
<p>reenter a synchronized block/method after calling Object#wait()</p>
|
||
</blockquote>
|
||
<ol>
|
||
<li>线程X步入<code>synchronized</code>代码块或者<code>synchronized</code>方法后(此时已经释放监视器锁)调用<code>Object#wait()</code>方法之后进行阻塞,当接收其他线程T调用该锁对象<code>Object#notify()/notifyAll()</code>,但是线程T尚未退出它所在的<code>synchronized</code>代码块或者<code>synchronized</code>方法,那么线程X依然处于阻塞状态(注意API注释中的<strong>reenter</strong>,理解它场景2就豁然开朗)。</li>
|
||
</ol>
|
||
<p>更加详细的描述可以参考笔者之前写过的一篇文章:<a href="http://www.throwable.club/2019/04/30/java-object-wait-notify">深入理解Object提供的阻塞和唤醒API</a></p>
|
||
<p>针对上面的场景1举个简单的例子:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadState6</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">MONITOR</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</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="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()-> {</span><br><span class="line"> <span class="keyword">synchronized</span> (MONITOR){</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Thread.sleep(Integer.MAX_VALUE);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> <span class="comment">//ignore</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()-> {</span><br><span class="line"> <span class="keyword">synchronized</span> (MONITOR){</span><br><span class="line"> System.out.println(<span class="string">"thread2 got monitor lock..."</span>);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> thread1.start();</span><br><span class="line"> Thread.sleep(<span class="number">50</span>);</span><br><span class="line"> thread2.start();</span><br><span class="line"> Thread.sleep(<span class="number">50</span>);</span><br><span class="line"> System.out.println(thread2.getState());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 输出结果</span></span><br><span class="line">BLOCKED</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>针对上面的场景2举个简单的例子:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadState7</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">MONITOR</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">DateTimeFormatter</span> <span class="variable">F</span> <span class="operator">=</span> DateTimeFormatter.ofPattern(<span class="string">"yyyy-MM-dd HH:mm:ss"</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"> System.out.println(String.format(<span class="string">"[%s]-begin..."</span>, F.format(LocalDateTime.now())));</span><br><span class="line"> <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">synchronized</span> (MONITOR) {</span><br><span class="line"> System.out.println(String.format(<span class="string">"[%s]-thread1 got monitor lock..."</span>, F.format(LocalDateTime.now())));</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> MONITOR.wait();</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> <span class="comment">//ignore</span></span><br><span class="line"> }</span><br><span class="line"> System.out.println(String.format(<span class="string">"[%s]-thread1 exit waiting..."</span>, F.format(LocalDateTime.now())));</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"> <span class="keyword">synchronized</span> (MONITOR) {</span><br><span class="line"> System.out.println(String.format(<span class="string">"[%s]-thread2 got monitor lock..."</span>, F.format(LocalDateTime.now())));</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> MONITOR.notify();</span><br><span class="line"> Thread.sleep(<span class="number">2000</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> <span class="comment">//ignore</span></span><br><span class="line"> }</span><br><span class="line"> System.out.println(String.format(<span class="string">"[%s]-thread2 releases monitor lock..."</span>, F.format(LocalDateTime.now())));</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> thread1.start();</span><br><span class="line"> thread2.start();</span><br><span class="line"> <span class="comment">// 这里故意让主线程sleep 1500毫秒从而让thread2调用了Object#notify()并且尚未退出同步代码块,确保thread1调用了Object#wait()</span></span><br><span class="line"> Thread.sleep(<span class="number">1500</span>); </span><br><span class="line"> System.out.println(thread1.getState());</span><br><span class="line"> System.out.println(String.format(<span class="string">"[%s]-end..."</span>, F.format(LocalDateTime.now())));</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">2019</span>-<span class="number">06</span>-<span class="number">20</span> <span class="number">00</span>:<span class="number">30</span>:<span class="number">22</span>]-begin...</span><br><span class="line">[<span class="number">2019</span>-<span class="number">06</span>-<span class="number">20</span> <span class="number">00</span>:<span class="number">30</span>:<span class="number">22</span>]-thread1 got monitor lock...</span><br><span class="line">[<span class="number">2019</span>-<span class="number">06</span>-<span class="number">20</span> <span class="number">00</span>:<span class="number">30</span>:<span class="number">23</span>]-thread2 got monitor lock...</span><br><span class="line">BLOCKED</span><br><span class="line">[<span class="number">2019</span>-<span class="number">06</span>-<span class="number">20</span> <span class="number">00</span>:<span class="number">30</span>:<span class="number">23</span>]-end...</span><br><span class="line">[<span class="number">2019</span>-<span class="number">06</span>-<span class="number">20</span> <span class="number">00</span>:<span class="number">30</span>:<span class="number">25</span>]-thread2 releases monitor lock...</span><br><span class="line">[<span class="number">2019</span>-<span class="number">06</span>-<span class="number">20</span> <span class="number">00</span>:<span class="number">30</span>:<span class="number">25</span>]-thread1 exit waiting...</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>场景2中:</p>
|
||
<ul>
|
||
<li>线程2调用<code>Object#notify()</code>后睡眠2000毫秒再退出同步代码块,释放监视器锁。</li>
|
||
<li>线程1只睡眠了1000毫秒就调用了<code>Object#wait()</code>,此时它已经释放了监视器锁,所以线程2成功进入同步块,线程1处于API注释中所述的<code>reenter a synchronized block/method</code>的状态。</li>
|
||
<li>主线程睡眠1500毫秒刚好可以命中线程1处于<code>reenter</code>状态并且打印其线程状态,刚好就是<code>BLOCKED</code>状态。</li>
|
||
</ul>
|
||
<p>这三点看起来有点绕,多看几次多思考一下应该就能理解。</p>
|
||
<h3 id="TERMINATED状态"><a href="#TERMINATED状态" class="headerlink" title="TERMINATED状态#"></a>TERMINATED状态<a href="https://www.cnblogs.com/throwable/p/13439079.html#terminated%E7%8A%B6%E6%80%81">#</a></h3><p><strong>API注释</strong>:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Thread state for a terminated thread.</span></span><br><span class="line"><span class="comment"> * The thread has completed execution.</span></span><br><span class="line"><span class="comment"> */</span> </span><br><span class="line">TERMINATED;</span><br></pre></td></tr></table></figure>
|
||
|
||
<blockquote>
|
||
<p>终结的线程对应的线程状态,此时线程已经执行完毕。</p>
|
||
</blockquote>
|
||
<p><code>TERMINATED</code>状态表示线程已经终结。一个线程实例只能被启动一次,准确来说,只会调用一次<code>Thread#run()</code>方法,<code>Thread#run()</code>方法执行结束之后,线程状态就会更变为<code>TERMINATED</code>,意味着线程的生命周期已经结束。</p>
|
||
<p>举个简单的例子:</p>
|
||
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadState8</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="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -> {</span><br><span class="line"></span><br><span class="line"> });</span><br><span class="line"> thread.start();</span><br><span class="line"> Thread.sleep(<span class="number">50</span>);</span><br><span class="line"> System.out.println(thread.getState());</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 输出结果</span></span><br><span class="line">TERMINATED</span><br></pre></td></tr></table></figure>
|
||
|
||
<h2 id="上下文切换"><a href="#上下文切换" class="headerlink" title="上下文切换#"></a>上下文切换<a href="https://www.cnblogs.com/throwable/p/13439079.html#%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2">#</a></h2><p>多线程环境中,当一个线程的状态由<code>RUNNABLE</code>转换为非<code>RUNNABLE</code>(<code>BLOCKED</code>、<code>WAITING</code>或者<code>TIMED_WAITING</code>)时,相应线程的上下文信息(也就是常说的<code>Context</code>,包括<code>CPU</code>的寄存器和程序计数器在某一时间点的内容等等)需要被保存,以便线程稍后恢复为<code>RUNNABLE</code>状态时能够在之前的执行进度的基础上继续执行。而一个线程的状态由非<code>RUNNABLE</code>状态进入<code>RUNNABLE</code>状态时可能涉及恢复之前保存的线程上下文信息并且在此基础上继续执行。这里的对<strong>线程的上下文信息进行保存和恢复的过程</strong>就称为上下文切换(<code>Context Switch</code>)。</p>
|
||
<p>线程的上下文切换会带来额外的性能开销,这包括保存和恢复线程上下文信息的开销、对线程进行调度的<code>CPU</code>时间开销以及<code>CPU</code>缓存内容失效的开销(线程所执行的代码从<code>CPU</code>缓存中访问其所需要的变量值要比从主内存(<code>RAM</code>)中访问响应的变量值要快得多,但是**线程上下文切换会导致相关线程所访问的CPU缓存内容失效,一般是CPU的<code>L1 Cache</code>和<code>L2 Cache</code>**,使得相关线程稍后被重新调度到运行时其不得不再次访问主内存中的变量以重新创建<code>CPU</code>缓存内容)。</p>
|
||
<p>在<code>Linux</code>系统中,可以通过<code>vmstat</code>命令来查看全局的上下文切换的次数,例如:</p>
|
||
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">vmstat 1</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<p>对于<code>Java</code>程序的运行,在<code>Linux</code>系统中也可以通过<code>perf</code>命令进行监视,例如:</p>
|
||
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">perf <span class="built_in">stat</span> -e cpu-clock,task-clock,cs,cache-reference,cache-misses java YourJavaClass</span></span><br></pre></td></tr></table></figure>
|
||
|
||
<p>参考资料中提到<code>Windows</code>系统下可以通过自带的工具<code>perfmon</code>(其实也就是任务管理器)来监视线程的上下文切换,实际上笔者并没有从任务管理器发现有任何办法查看上下文切换,通过搜索之后发现了一个工具:<a href="https://docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer">Process Explorer</a>。运行<code>Process Explorer</code>同时运行一个<code>Java</code>程序并且查看其状态:</p>
|
||
<p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-4.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-4.png" alt="j-t-l-s-4.png"></a></p>
|
||
<p>因为打了断点,可以看到运行中的程序的上下文切换一共7000多次,当前一秒的上下文切换增量为26(因为笔者设置了<code>Process Explorer</code>每秒刷新一次数据)。</p>
|
||
<h2 id="监控线程状态"><a href="#监控线程状态" class="headerlink" title="监控线程状态#"></a>监控线程状态<a href="https://www.cnblogs.com/throwable/p/13439079.html#%E7%9B%91%E6%8E%A7%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81">#</a></h2><p>如果项目在生产环境中运行,不可能频繁调用<code>Thread#getState()</code>方法去监测线程的状态变化。JDK本身提供了一些监控线程状态的工具,还有一些开源的轻量级工具如阿里的<a href="https://alibaba.github.io/arthas/">Arthas</a>,这里简单介绍一下。</p>
|
||
<h3 id="使用jvisualvm"><a href="#使用jvisualvm" class="headerlink" title="使用jvisualvm#"></a>使用jvisualvm<a href="https://www.cnblogs.com/throwable/p/13439079.html#%E4%BD%BF%E7%94%A8jvisualvm">#</a></h3><p><code>jvisualvm</code>是JDK自带的堆、线程等待JVM指标监控工具,适合使用于开发和测试环境。它位于<code>JAVA_HOME/bin</code>目录之下。</p>
|
||
<p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-5.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-5.png" alt="j-t-l-s-5.png"></a></p>
|
||
<p>其中<code>线程Dump</code>的按钮类似于下面要提到的<code>jstack</code>命令,用于导出所有线程的栈信息。</p>
|
||
<h3 id="使用jstack"><a href="#使用jstack" class="headerlink" title="使用jstack#"></a>使用jstack<a href="https://www.cnblogs.com/throwable/p/13439079.html#%E4%BD%BF%E7%94%A8jstack">#</a></h3><p><code>jstack</code>是JDK自带的命令行工具,功能是用于获取指定PID的Java进程的线程栈信息。例如本地运行的一个<code>IDEA</code>实例的<code>PID</code>是11376,那么只需要输入:</p>
|
||
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">jstack 11376</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>然后控制台输出如下:</p>
|
||
<p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-6.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-6.png" alt="j-t-l-s-6.png"></a></p>
|
||
<p>另外,如果想要定位具体Java进程的<code>PID</code>,可以使用<code>jps</code>命令。</p>
|
||
<h3 id="使用JMC"><a href="#使用JMC" class="headerlink" title="使用JMC#"></a>使用JMC<a href="https://www.cnblogs.com/throwable/p/13439079.html#%E4%BD%BF%E7%94%A8jmc">#</a></h3><p><code>JMC</code>也就是<code>Java Mission Control</code>,它也是JDK自带的工具,提供的功能要比<code>jvisualvm</code>强大,包括MBean的处理、线程栈已经状态查看、飞行记录器等等。</p>
|
||
<p><a href="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-7.png"><img src="https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202008/j-t-l-s-7.png" alt="j-t-l-s-7.png"></a></p>
|
||
<h2 id="小结"><a href="#小结" class="headerlink" title="小结#"></a>小结<a href="https://www.cnblogs.com/throwable/p/13439079.html#%E5%B0%8F%E7%BB%93">#</a></h2><p>理解Java线程状态的切换和一些监控手段,更有利于日常开发多线程程序,对于生产环境出现问题,通过监控线程的栈信息能够快速定位到问题的根本原因(通常来说,目前比较主流的<code>MVC</code>应用(准确来说应该是<code>Servlet</code>容器如<code>Tomcat</code>)都是通过一个线程处理一个单独的请求,当请求出现阻塞的时候,导出对应处理请求的线程基本可以定位到阻塞的精准位置,如果使用消息队列例如<code>RabbitMQ</code>,消费者线程出现阻塞也可以利用相似的思路解决)。</p>
|
||
]]></content>
|
||
<categories>
|
||
<category>多线程</category>
|
||
<category>java</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>java</tag>
|
||
<tag>多线程</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>synchronized 实现原理</title>
|
||
<url>/posts/f11fd659.html</url>
|
||
<content><![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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>多线程</category>
|
||
<category>java</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>java</tag>
|
||
<tag>多线程</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>详细分析JDK中Stream的实现原理</title>
|
||
<url>/posts/f5786ffa.html</url>
|
||
<content><![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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>java</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>java</tag>
|
||
<tag>stream</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>C++ 八股文(一)</title>
|
||
<url>/posts/15563.html</url>
|
||
<content><![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="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="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="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="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="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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
<tag>后端</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>C++编码优化之减少冗余拷贝或赋值</title>
|
||
<url>/posts/97da918c.html</url>
|
||
<content><![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="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="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="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="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="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="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="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="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>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
</tags>
|
||
</entry>
|
||
<entry>
|
||
<title>五万字长文总结 C/C++ 知识点</title>
|
||
<url>/posts/3189.html</url>
|
||
<content><![CDATA[<h1 id="五万字长文总结-C-C-知识点"><a href="#五万字长文总结-C-C-知识点" class="headerlink" title="五万字长文总结 C/C++ 知识点"></a>五万字长文总结 C/C++ 知识点</h1><h1 id="C-C-知识总结"><a href="#C-C-知识总结" class="headerlink" title="C/C++ 知识总结"></a>C/C++ 知识总结</h1><h2 id="C-C"><a href="#C-C" class="headerlink" title="C/C++"></a>C/C++</h2><h3 id="const"><a href="#const" class="headerlink" title="const"></a>const</h3><h4 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h4><ol>
|
||
<li>修饰变量,说明该变量不可以被改变;</li>
|
||
<li>修饰指针,分为指向常量的指针和指针常量;</li>
|
||
<li>常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;</li>
|
||
<li>修饰成员函数,说明该成员函数内不能修改成员变量。</li>
|
||
</ol>
|
||
<h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 类</span><br><span class="line">class A</span><br><span class="line">{</span><br><span class="line">private:</span><br><span class="line"> const int a; // 常对象成员,只能在初始化列表赋值</span><br><span class="line"></span><br><span class="line">public:</span><br><span class="line"> // 构造函数</span><br><span class="line"> A() { };</span><br><span class="line"> A(int x) : a(x) { }; // 初始化列表</span><br><span class="line"></span><br><span class="line"> // const可用于对重载函数的区分</span><br><span class="line"> int getValue(); // 普通成员函数</span><br><span class="line"> int getValue() const; // 常成员函数,不得修改类中的任何数据成员的值</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">void function()</span><br><span class="line">{</span><br><span class="line"> // 对象</span><br><span class="line"> A b; // 普通对象,可以调用全部成员函数</span><br><span class="line"> const A a; // 常对象,只能调用常成员函数、更新常成员变量</span><br><span class="line"> const A *p = &a; // 常指针</span><br><span class="line"> const A &q = a; // 常引用</span><br><span class="line"></span><br><span class="line"> // 指针</span><br><span class="line"> char greeting[] = "Hello";</span><br><span class="line"> char* p1 = greeting; // 指针变量,指向字符数组变量</span><br><span class="line"> const char* p2 = greeting; // 指针变量,指向字符数组常量</span><br><span class="line"> char* const p3 = greeting; // 常指针,指向字符数组变量</span><br><span class="line"> const char* const p4 = greeting; // 常指针,指向字符数组常量</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 函数</span><br><span class="line">void function1(const int Var); // 传递过来的参数在函数内不可变</span><br><span class="line">void function2(const char* Var); // 参数指针所指内容为常量</span><br><span class="line">void function3(char* const Var); // 参数指针为常指针</span><br><span class="line">void function4(const int& Var); // 引用参数在函数内为常量</span><br><span class="line"></span><br><span class="line">// 函数返回值</span><br><span class="line">const int function5(); // 返回一个常数</span><br><span class="line">const int* function6(); // 返回一个指向常量的指针变量,使用:const int *p = function6();</span><br><span class="line">int* const function7(); // 返回一个指向变量的常指针,使用:int* const p = function7();</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="static"><a href="#static" class="headerlink" title="static"></a>static</h3><h4 id="作用-1"><a href="#作用-1" class="headerlink" title="作用"></a>作用</h4><ol>
|
||
<li>修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。</li>
|
||
<li>修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重名,可以将函数定位为 static。</li>
|
||
<li>修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。</li>
|
||
<li>修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。</li>
|
||
</ol>
|
||
<h3 id="this-指针"><a href="#this-指针" class="headerlink" title="this 指针"></a>this 指针</h3><ol>
|
||
<li><p><code>this</code> 指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向正在被该成员函数操作的那个对象。</p>
|
||
</li>
|
||
<li><p>当对一个对象调用成员函数时,编译程序先将对象的地址赋给 <code>this</code> 指针,然后调用成员函数,每次成员函数存取数据成员时,由隐含使用 <code>this</code> 指针。</p>
|
||
</li>
|
||
<li><p>当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向这个成员函数所在的对象的指针。</p>
|
||
</li>
|
||
<li><p><code>this</code> 指针被隐含地声明为: <code>ClassName *const this</code>,这意味着不能给 <code>this</code> 指针赋值;在 <code>ClassName</code> 类的 <code>const</code> 成员函数中,<code>this</code> 指针的类型为:<code>const ClassName* const</code>,这说明不能对 <code>this</code> 指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作);</p>
|
||
</li>
|
||
<li><p><code>this</code> 并不是一个常规变量,而是个右值,所以不能取得 <code>this</code> 的地址(不能 <code>&this</code>)。</p>
|
||
</li>
|
||
<li><p>在以下场景中,经常需要显式引用 <code>this</code> 指针:</p>
|
||
</li>
|
||
<li><ol>
|
||
<li>为实现对象的链式引用;</li>
|
||
<li>为避免对同一对象进行赋值操作;</li>
|
||
<li>在实现一些数据结构时,如 <code>list</code>。</li>
|
||
</ol>
|
||
</li>
|
||
</ol>
|
||
<h3 id="inline-内联函数"><a href="#inline-内联函数" class="headerlink" title="inline 内联函数"></a>inline 内联函数</h3><h4 id="特征"><a href="#特征" class="headerlink" title="特征"></a>特征</h4><ul>
|
||
<li>相当于把内联函数里面的内容写在调用内联函数处;</li>
|
||
<li>相当于不用执行进入函数的步骤,直接执行函数体;</li>
|
||
<li>相当于宏,却比宏多了类型检查,真正具有函数特性;</li>
|
||
<li>不能包含循环、递归、switch 等复杂操作;</li>
|
||
<li>在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。</li>
|
||
</ul>
|
||
<h4 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 声明1(加 inline,建议使用)</span><br><span class="line">inline int functionName(int first, int secend,...);</span><br><span class="line"></span><br><span class="line">// 声明2(不加 inline)</span><br><span class="line">int functionName(int first, int secend,...);</span><br><span class="line"></span><br><span class="line">// 定义</span><br><span class="line">inline int functionName(int first, int secend,...) {/****/};</span><br><span class="line"></span><br><span class="line">// 类内定义,隐式内联</span><br><span class="line">class A {</span><br><span class="line"> int doA() { return 0; } // 隐式内联</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 类外定义,需要显式内联</span><br><span class="line">class A {</span><br><span class="line"> int doA();</span><br><span class="line">}</span><br><span class="line">inline int A::doA() { return 0; } // 需要显式内联</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="编译器对-inline-函数的处理步骤"><a href="#编译器对-inline-函数的处理步骤" class="headerlink" title="编译器对 inline 函数的处理步骤"></a>编译器对 inline 函数的处理步骤</h4><ol>
|
||
<li>将 inline 函数体复制到 inline 函数调用点处;</li>
|
||
<li>为所用 inline 函数中的局部变量分配内存空间;</li>
|
||
<li>将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;</li>
|
||
<li>如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)。</li>
|
||
</ol>
|
||
<h4 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h4><p>优点</p>
|
||
<ol>
|
||
<li>内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。</li>
|
||
<li>内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。</li>
|
||
<li>在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。</li>
|
||
<li>内联函数在运行时可调试,而宏定义不可以。</li>
|
||
</ol>
|
||
<p>缺点</p>
|
||
<ol>
|
||
<li>代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。</li>
|
||
<li>inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。</li>
|
||
<li>是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。</li>
|
||
</ol>
|
||
<h4 id="虚函数(virtual)可以是内联函数(inline)吗?"><a href="#虚函数(virtual)可以是内联函数(inline)吗?" class="headerlink" title="虚函数(virtual)可以是内联函数(inline)吗?"></a>虚函数(virtual)可以是内联函数(inline)吗?</h4><blockquote>
|
||
<p>Are “inline virtual” member functions ever actually “inlined”?</p>
|
||
<p>答案:<a href="http://www.cs.technion.ac.il/users/yechiel/c++-faq/inline-virtuals.html">http://www.cs.technion.ac.il/users/yechiel/c++-faq/inline-virtuals.html</a></p>
|
||
</blockquote>
|
||
<ul>
|
||
<li>虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。</li>
|
||
<li>内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。</li>
|
||
<li><code>inline virtual</code> 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 <code>Base::who()</code>),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。</li>
|
||
</ul>
|
||
<h4 id="虚函数内联使用"><a href="#虚函数内联使用" class="headerlink" title="虚函数内联使用"></a>虚函数内联使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#include <iostream></span><br><span class="line">using namespace std;</span><br><span class="line">class Base</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> inline virtual void who()</span><br><span class="line"> {</span><br><span class="line"> cout << "I am Base\n";</span><br><span class="line"> }</span><br><span class="line"> virtual ~Base() {}</span><br><span class="line">};</span><br><span class="line">class Derived : public Base</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> inline void who() // 不写inline时隐式内联</span><br><span class="line"> {</span><br><span class="line"> cout << "I am Derived\n";</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。</span><br><span class="line"> Base b;</span><br><span class="line"> b.who();</span><br><span class="line"></span><br><span class="line"> // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。</span><br><span class="line"> Base *ptr = new Derived();</span><br><span class="line"> ptr->who();</span><br><span class="line"></span><br><span class="line"> // 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。</span><br><span class="line"> delete ptr;</span><br><span class="line"> ptr = nullptr;</span><br><span class="line"></span><br><span class="line"> system("pause");</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
|
||
|
||
|
||
|
||
<h3 id="assert"><a href="#assert" class="headerlink" title="assert()"></a>assert()</h3><p>断言,是宏,而非函数。assert 宏的原型定义在 <code><assert.h></code>(C)、<code><cassert></code>(C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义 <code>NDEBUG</code> 来关闭 assert,但是需要在源代码的开头,<code>include <assert.h></code> 之前。</p>
|
||
<h4 id="使用-2"><a href="#使用-2" class="headerlink" title="使用"></a>使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#define NDEBUG // 加上这行,则 assert 不可用</span><br><span class="line">#include <assert.h></span><br><span class="line"></span><br><span class="line">assert( p != NULL ); // assert 不可用</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="sizeof"><a href="#sizeof" class="headerlink" title="sizeof()"></a>sizeof()</h3><ul>
|
||
<li>sizeof 对数组,得到整个数组所占空间大小。</li>
|
||
<li>sizeof 对指针,得到指针本身所占空间大小。</li>
|
||
</ul>
|
||
<h3 id="pragma-pack-n"><a href="#pragma-pack-n" class="headerlink" title="#pragma pack(n)"></a>#pragma pack(n)</h3><p>设定结构体、联合以及类成员变量以 n 字节方式对齐</p>
|
||
<h4 id="使用-3"><a href="#使用-3" class="headerlink" title="使用"></a>使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#pragma pack(push) // 保存对齐状态</span><br><span class="line">#pragma pack(4) // 设定为 4 字节对齐</span><br><span class="line"></span><br><span class="line">struct test</span><br><span class="line">{</span><br><span class="line"> char m1;</span><br><span class="line"> double m4;</span><br><span class="line"> int m3;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">#pragma pack(pop) // 恢复对齐状态</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="位域"><a href="#位域" class="headerlink" title="位域"></a>位域</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Bit mode: 2; // mode 占 2 位</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>类可以将其(非静态)数据成员定义为位域(bit-field),在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。</p>
|
||
<ul>
|
||
<li>位域在内存中的布局是与机器有关的</li>
|
||
<li>位域的类型必须是整型或枚举类型,带符号类型中的位域的行为将因具体实现而定</li>
|
||
<li>取地址运算符(&)不能作用于位域,任何指针都无法指向类的位域</li>
|
||
</ul>
|
||
<h3 id="volatile"><a href="#volatile" class="headerlink" title="volatile"></a>volatile</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">volatile int i = 10;</span><br></pre></td></tr></table></figure>
|
||
|
||
<ul>
|
||
<li>volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以使用 volatile 告诉编译器不应对这样的对象进行优化。</li>
|
||
<li>volatile 关键字声明的变量,每次访问时都必须从内存中取出值(没有被 volatile 修饰的变量,可能由于编译器的优化,从 CPU 寄存器中取值)</li>
|
||
<li>const 可以是 volatile (如只读的状态寄存器)</li>
|
||
<li>指针可以是 volatile</li>
|
||
</ul>
|
||
<h3 id="extern-“C”"><a href="#extern-“C”" class="headerlink" title="extern “C”"></a>extern “C”</h3><ul>
|
||
<li>被 extern 限定的函数或变量是 extern 类型的</li>
|
||
<li>被 <code>extern "C"</code> 修饰的变量和函数是按照 C 语言方式编译和连接的</li>
|
||
</ul>
|
||
<p><code>extern "C"</code> 的作用是让 C++ 编译器将 <code>extern "C"</code> 声明的代码当作 C 语言代码处理,可以避免 C++ 因符号修饰导致代码不能和C语言库中的符号进行链接的问题。</p>
|
||
<h4 id="“C”-使用"><a href="#“C”-使用" class="headerlink" title="“C” 使用"></a>“C” 使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#ifdef __cplusplus</span><br><span class="line">extern "C" {</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">void *memset(void *, int, size_t);</span><br><span class="line"></span><br><span class="line">#ifdef __cplusplus</span><br><span class="line">}</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="struct-和-typedef-struct"><a href="#struct-和-typedef-struct" class="headerlink" title="struct 和 typedef struct"></a>struct 和 typedef struct</h3><h4 id="C-中"><a href="#C-中" class="headerlink" title="C 中"></a>C 中</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// c</span><br><span class="line">typedef struct Student {</span><br><span class="line"> int age;</span><br><span class="line">} S;</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>等价于</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// c</span><br><span class="line">struct Student {</span><br><span class="line"> int age;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">typedef struct Student S;</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>此时 <code>S</code> 等价于 <code>struct Student</code>,但两个标识符名称空间不相同。</p>
|
||
<p>另外还可以定义与 <code>struct Student</code> 不冲突的 <code>void Student() {}</code>。</p>
|
||
<h4 id="C-中-1"><a href="#C-中-1" class="headerlink" title="C++ 中"></a>C++ 中</h4><p>由于编译器定位符号的规则(搜索规则)改变,导致不同于C语言。</p>
|
||
<p>一、如果在类标识符空间定义了 <code>struct Student {...};</code>,使用 <code>Student me;</code> 时,编译器将搜索全局标识符表,<code>Student</code> 未找到,则在类标识符内搜索。</p>
|
||
<p>即表现为可以使用 <code>Student</code> 也可以使用 <code>struct Student</code>,如下:</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// cpp</span><br><span class="line">struct Student {</span><br><span class="line"> int age;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">void f( Student me ); // 正确,"struct" 关键字可省略</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>二、若定义了与 <code>Student</code> 同名函数之后,则 <code>Student</code> 只代表函数,不代表结构体,如下:</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">typedef struct Student {</span><br><span class="line"> int age;</span><br><span class="line">} S;</span><br><span class="line"></span><br><span class="line">void Student() {} // 正确,定义后 "Student" 只代表此函数</span><br><span class="line"></span><br><span class="line">//void S() {} // 错误,符号 "S" 已经被定义为一个 "struct Student" 的别名</span><br><span class="line"></span><br><span class="line">int main() {</span><br><span class="line"> Student();</span><br><span class="line"> struct Student me; // 或者 "S me";</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="C-中-struct-和-class"><a href="#C-中-struct-和-class" class="headerlink" title="C++ 中 struct 和 class"></a>C++ 中 struct 和 class</h3><p>总的来说,struct 更适合看成是一个数据结构的实现体,class 更适合看成是一个对象的实现体。</p>
|
||
<h4 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h4><ul>
|
||
<li><p>最本质的一个区别就是默认的访问控制</p>
|
||
</li>
|
||
<li><ol>
|
||
<li>默认的继承访问权限。struct 是 public 的,class 是 private 的。</li>
|
||
<li>struct 作为数据结构的实现体,它默认的数据访问控制是 public 的,而 class 作为对象的实现体,它默认的成员变量访问控制是 private 的。</li>
|
||
</ol>
|
||
</li>
|
||
</ul>
|
||
<h3 id="union-联合"><a href="#union-联合" class="headerlink" title="union 联合"></a>union 联合</h3><p>联合(union)是一种节省空间的特殊的类,一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。当某个成员被赋值后其他成员变为未定义状态。联合有如下特点:</p>
|
||
<ul>
|
||
<li><p>默认访问控制符为 public</p>
|
||
</li>
|
||
<li><p>可以含有构造函数、析构函数</p>
|
||
</li>
|
||
<li><p>不能含有引用类型的成员</p>
|
||
</li>
|
||
<li><p>不能继承自其他类,不能作为基类</p>
|
||
</li>
|
||
<li><p>不能含有虚函数</p>
|
||
</li>
|
||
<li><p>匿名 union 在定义所在作用域可直接访问 union 成员</p>
|
||
</li>
|
||
<li><p>匿名 union 不能包含 protected 成员或 private 成员</p>
|
||
</li>
|
||
<li><p>全局匿名联合必须是静态(static)的</p>
|
||
<h4 id="使用-4"><a href="#使用-4" class="headerlink" title="使用"></a>使用</h4></li>
|
||
</ul>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#include<iostream></span><br><span class="line"></span><br><span class="line">union UnionTest {</span><br><span class="line"> UnionTest() : i(10) {};</span><br><span class="line"> int i;</span><br><span class="line"> double d;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">static union {</span><br><span class="line"> int i;</span><br><span class="line"> double d;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">int main() {</span><br><span class="line"> UnionTest u;</span><br><span class="line"></span><br><span class="line"> union {</span><br><span class="line"> int i;</span><br><span class="line"> double d;</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> std::cout << u.i << std::endl; // 输出 UnionTest 联合的 10</span><br><span class="line"></span><br><span class="line"> ::i = 20;</span><br><span class="line"> std::cout << ::i << std::endl; // 输出全局静态匿名联合的 20</span><br><span class="line"></span><br><span class="line"> i = 30;</span><br><span class="line"> std::cout << i << std::endl; // 输出局部匿名联合的 30</span><br><span class="line"></span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
|
||
|
||
<h3 id="C-实现-C-类"><a href="#C-实现-C-类" class="headerlink" title="C 实现 C++ 类"></a>C 实现 C++ 类</h3><blockquote>
|
||
<p>C 语言实现封装、继承和多态:</p>
|
||
<p><a href="http://dongxicheng.org/cpp/ooc/">http://dongxicheng.org/cpp/ooc/</a></p>
|
||
</blockquote>
|
||
<h3 id="explicit(显式)构造函数"><a href="#explicit(显式)构造函数" class="headerlink" title="explicit(显式)构造函数"></a>explicit(显式)构造函数</h3><p>explicit 修饰的构造函数可用来防止隐式转换</p>
|
||
<h4 id="explicit-使用"><a href="#explicit-使用" class="headerlink" title="explicit 使用"></a>explicit 使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">class Test1</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> Test1(int n) // 普通构造函数</span><br><span class="line"> {</span><br><span class="line"> num=n;</span><br><span class="line"> }</span><br><span class="line">private:</span><br><span class="line"> int num;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class Test2</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> explicit Test2(int n) // explicit(显式)构造函数</span><br><span class="line"> {</span><br><span class="line"> num=n;</span><br><span class="line"> }</span><br><span class="line">private:</span><br><span class="line"> int num;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> Test1 t1=12; // 隐式调用其构造函数,成功</span><br><span class="line"> Test2 t2=12; // 编译错误,不能隐式调用其构造函数</span><br><span class="line"> Test2 t2(12); // 显式调用成功</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="friend-友元类和友元函数"><a href="#friend-友元类和友元函数" class="headerlink" title="friend 友元类和友元函数"></a>friend 友元类和友元函数</h3><ul>
|
||
<li>能访问私有成员</li>
|
||
<li>破坏封装性</li>
|
||
<li>友元关系不可传递</li>
|
||
<li>友元关系的单向性</li>
|
||
<li>友元声明的形式及数量不受限制</li>
|
||
</ul>
|
||
<h3 id="using"><a href="#using" class="headerlink" title="using"></a>using</h3><h4 id="using-声明"><a href="#using-声明" class="headerlink" title="using 声明"></a>using 声明</h4><p>一条 <code>using 声明</code> 语句一次只引入命名空间的一个成员。它使得我们可以清楚知道程序中所引用的到底是哪个名字。如:</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">using namespace_name::name;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="构造函数的-using-声明【C-11】"><a href="#构造函数的-using-声明【C-11】" class="headerlink" title="构造函数的 using 声明【C++11】"></a>构造函数的 using 声明【C++11】</h4><p>在 C++11 中,派生类能够重用其直接基类定义的构造函数。</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">class Derived : Base {</span><br><span class="line">public:</span><br><span class="line"> using Base::Base;</span><br><span class="line"> /* ... */</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>如上 using 声明,对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数。生成如下类型构造函数:</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">derived(parms) : base(args) { }</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="using-指示"><a href="#using-指示" class="headerlink" title="using 指示"></a>using 指示</h4><p><code>using 指示</code> 使得某个特定命名空间中所有名字都可见,这样我们就无需再为它们添加任何前缀限定符了。如:</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">using namespace_name name;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="尽量少使用-using-指示-污染命名空间"><a href="#尽量少使用-using-指示-污染命名空间" class="headerlink" title="尽量少使用 using 指示 污染命名空间"></a>尽量少使用 <code>using 指示</code> 污染命名空间</h4><blockquote>
|
||
<p>一般说来,使用 using 命令比使用 using 编译命令更安全,这是由于它<strong>只导入了制定的名称</strong>。如果该名称与局部名称发生冲突,编译器将<strong>发出指示</strong>。using编译命令导入所有的名称,包括可能并不需要的名称。如果与局部名称发生冲突,则<strong>局部名称将覆盖名称空间版本</strong>,而编译器<strong>并不会发出警告</strong>。另外,名称空间的开放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。</p>
|
||
</blockquote>
|
||
<h4 id="using-使用"><a href="#using-使用" class="headerlink" title="using 使用"></a>using 使用</h4><p>尽量少使用 <code>using 指示</code></p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">using namespace std;</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>应该多使用 <code>using 声明</code></p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">int x;</span><br><span class="line">std::cin >> x ;</span><br><span class="line">std::cout << x << std::endl;</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>或者</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">using std::cin;</span><br><span class="line">using std::cout;</span><br><span class="line">using std::endl;</span><br><span class="line">int x;</span><br><span class="line">cin >> x;</span><br><span class="line">cout << x << endl;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="范围解析运算符"><a href="#范围解析运算符" class="headerlink" title=":: 范围解析运算符"></a>:: 范围解析运算符</h3><h4 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h4><ol>
|
||
<li>全局作用域符(<code>::name</code>):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间</li>
|
||
<li>类作用域符(<code>class::name</code>):用于表示指定类型的作用域范围是具体某个类的</li>
|
||
<li>命名空间作用域符(<code>namespace::name</code>):用于表示指定类型的作用域范围是具体某个命名空间的</li>
|
||
</ol>
|
||
<h4 id="使用-5"><a href="#使用-5" class="headerlink" title=":: 使用"></a>:: 使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">int count = 0; // 全局(::)的 count</span><br><span class="line"></span><br><span class="line">class A {</span><br><span class="line">public:</span><br><span class="line"> static int count; // 类 A 的 count(A::count)</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">int main() {</span><br><span class="line"> ::count = 1; // 设置全局的 count 的值为 1</span><br><span class="line"></span><br><span class="line"> A::count = 2; // 设置类 A 的 count 为 2</span><br><span class="line"></span><br><span class="line"> int count = 0; // 局部的 count</span><br><span class="line"> count = 3; // 设置局部的 count 的值为 3</span><br><span class="line"></span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="enum-枚举类型"><a href="#enum-枚举类型" class="headerlink" title="enum 枚举类型"></a>enum 枚举类型</h3><h4 id="限定作用域的枚举类型"><a href="#限定作用域的枚举类型" class="headerlink" title="限定作用域的枚举类型"></a>限定作用域的枚举类型</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">enum class open_modes { input, output, append };</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="不限定作用域的枚举类型"><a href="#不限定作用域的枚举类型" class="headerlink" title="不限定作用域的枚举类型"></a>不限定作用域的枚举类型</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">enum color { red, yellow, green };</span><br><span class="line">enum { floatPrec = 6, doublePrec = 10 };</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="decltype"><a href="#decltype" class="headerlink" title="decltype"></a>decltype</h3><p>decltype 关键字用于检查实体的声明类型或表达式的类型及值分类。语法:</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">decltype ( expression )</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="使用-6"><a href="#使用-6" class="headerlink" title="使用"></a>使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 尾置返回允许我们在参数列表之后声明返回类型</span><br><span class="line">template <typename It></span><br><span class="line">auto fcn(It beg, It end) -> decltype(*beg)</span><br><span class="line">{</span><br><span class="line"> // 处理序列</span><br><span class="line"> return *beg; // 返回序列中一个元素的引用</span><br><span class="line">}</span><br><span class="line">// 为了使用模板参数成员,必须用 typename</span><br><span class="line">template <typename It></span><br><span class="line">auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type</span><br><span class="line">{</span><br><span class="line"> // 处理序列</span><br><span class="line"> return *beg; // 返回序列中一个元素的拷贝</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h3><h4 id="左值引用"><a href="#左值引用" class="headerlink" title="左值引用"></a>左值引用</h4><p>常规引用,一般表示对象的身份。</p>
|
||
<h4 id="右值引用"><a href="#右值引用" class="headerlink" title="右值引用"></a>右值引用</h4><p>右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。</p>
|
||
<p>右值引用可实现转移语义(Move Sementics)和精确传递(Perfect Forwarding),它的主要目的有两个方面:</p>
|
||
<ul>
|
||
<li>消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。</li>
|
||
<li>能够更简洁明确地定义泛型函数。</li>
|
||
</ul>
|
||
<h4 id="引用折叠"><a href="#引用折叠" class="headerlink" title="引用折叠"></a>引用折叠</h4><ul>
|
||
<li>X& &、X& &&、X&& & 可折叠成 X&</li>
|
||
<li>X&& && 可折叠成 X&&</li>
|
||
</ul>
|
||
<h3 id="宏"><a href="#宏" class="headerlink" title="宏"></a>宏</h3><ul>
|
||
<li>宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对 “参数” 进行的是一对一的替换。</li>
|
||
</ul>
|
||
<h3 id="成员初始化列表"><a href="#成员初始化列表" class="headerlink" title="成员初始化列表"></a>成员初始化列表</h3><p>好处</p>
|
||
<ul>
|
||
<li>更高效:少了一次调用默认构造函数的过程。</li>
|
||
<li>有些场合必须要用初始化列表:</li>
|
||
</ul>
|
||
<ol>
|
||
<li>常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面</li>
|
||
<li>引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面</li>
|
||
<li>没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。</li>
|
||
</ol>
|
||
<h3 id="initializer-list-列表初始化【C-11】"><a href="#initializer-list-列表初始化【C-11】" class="headerlink" title="initializer_list 列表初始化【C++11】"></a>initializer_list 列表初始化【C++11】</h3><p>用花括号初始化器列表列表初始化一个对象,其中对应构造函数接受一个 <code>std::initializer_list</code> 参数.</p>
|
||
<h4 id="initializer-list-使用"><a href="#initializer-list-使用" class="headerlink" title="initializer_list 使用"></a>initializer_list 使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#include <iostream></span><br><span class="line">#include <vector></span><br><span class="line">#include <initializer_list></span><br><span class="line"></span><br><span class="line">template <class T></span><br><span class="line">struct S {</span><br><span class="line"> std::vector<T> v;</span><br><span class="line"> S(std::initializer_list<T> l) : v(l) {</span><br><span class="line"> std::cout << "constructed with a " << l.size() << "-element list\n";</span><br><span class="line"> }</span><br><span class="line"> void append(std::initializer_list<T> l) {</span><br><span class="line"> v.insert(v.end(), l.begin(), l.end());</span><br><span class="line"> }</span><br><span class="line"> std::pair<const T*, std::size_t> c_arr() const {</span><br><span class="line"> return {&v[0], v.size()}; // 在 return 语句中复制列表初始化</span><br><span class="line"> // 这不使用 std::initializer_list</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">template <typename T></span><br><span class="line">void templated_fn(T) {}</span><br><span class="line"></span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> S<int> s = {1, 2, 3, 4, 5}; // 复制初始化</span><br><span class="line"> s.append({6, 7, 8}); // 函数调用中的列表初始化</span><br><span class="line"></span><br><span class="line"> std::cout << "The vector size is now " << s.c_arr().second << " ints:\n";</span><br><span class="line"></span><br><span class="line"> for (auto n : s.v)</span><br><span class="line"> std::cout << n << ' ';</span><br><span class="line"> std::cout << '\n';</span><br><span class="line"></span><br><span class="line"> std::cout << "Range-for over brace-init-list: \n";</span><br><span class="line"></span><br><span class="line"> for (int x : {-1, -2, -3}) // auto 的规则令此带范围 for 工作</span><br><span class="line"> std::cout << x << ' ';</span><br><span class="line"> std::cout << '\n';</span><br><span class="line"></span><br><span class="line"> auto al = {10, 11, 12}; // auto 的特殊规则</span><br><span class="line"></span><br><span class="line"> std::cout << "The list bound to auto has size() = " << al.size() << '\n';</span><br><span class="line"></span><br><span class="line">// templated_fn({1, 2, 3}); // 编译错误!“ {1, 2, 3} ”不是表达式,</span><br><span class="line"> // 它无类型,故 T 无法推导</span><br><span class="line"> templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK</span><br><span class="line"> templated_fn<std::vector<int>>({1, 2, 3}); // 也 OK</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
|
||
|
||
<h3 id="面向对象"><a href="#面向对象" class="headerlink" title="面向对象"></a>面向对象</h3><p>面向对象程序设计(Object-oriented programming,OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。</p>
|
||
<p><img src="/posts/3189/images/640.webp" alt="图片">面向对象特征</p>
|
||
<p>面向对象三大特征 —— 封装、继承、多态</p>
|
||
<h3 id="封装"><a href="#封装" class="headerlink" title="封装"></a>封装</h3><ul>
|
||
<li><p>把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。</p>
|
||
</li>
|
||
<li><p>关键字:public, protected, friendly, private。不写默认为 friendly。</p>
|
||
</li>
|
||
</ul>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>关键字</th>
|
||
<th>当前类</th>
|
||
<th>包内</th>
|
||
<th>子孙类</th>
|
||
<th>包外</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>public</td>
|
||
<td>√</td>
|
||
<td>√</td>
|
||
<td>√</td>
|
||
<td>√</td>
|
||
</tr>
|
||
<tr>
|
||
<td>protected</td>
|
||
<td>√</td>
|
||
<td>√</td>
|
||
<td>√</td>
|
||
<td>×</td>
|
||
</tr>
|
||
<tr>
|
||
<td>friendly</td>
|
||
<td>√</td>
|
||
<td>√</td>
|
||
<td>×</td>
|
||
<td>×</td>
|
||
</tr>
|
||
<tr>
|
||
<td>private</td>
|
||
<td>√</td>
|
||
<td>×</td>
|
||
<td>×</td>
|
||
<td>×</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h3 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h3><ul>
|
||
<li>基类(父类)——> 派生类(子类)</li>
|
||
</ul>
|
||
<h3 id="多态"><a href="#多态" class="headerlink" title="多态"></a>多态</h3><ul>
|
||
<li>多态,即多种状态,在面向对象语言中,接口的多种不同的实现方式即为多态。</li>
|
||
<li>C++ 多态有两种:静态多态(早绑定)、动态多态(晚绑定)。静态多态是通过函数重载实现的;动态多态是通过虚函数实现的。</li>
|
||
<li>多态是以封装和继承为基础的。</li>
|
||
</ul>
|
||
<h4 id="静态多态(早绑定)"><a href="#静态多态(早绑定)" class="headerlink" title="静态多态(早绑定)"></a>静态多态(早绑定)</h4><p>函数重载</p>
|
||
<figure class="highlight plaintext"><table><tr><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"> void do(int a);</span><br><span class="line"> void do(int a, int b);</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="动态多态(晚绑定)"><a href="#动态多态(晚绑定)" class="headerlink" title="动态多态(晚绑定)"></a>动态多态(晚绑定)</h4><ul>
|
||
<li>虚函数:用 virtual 修饰成员函数,使其成为虚函数</li>
|
||
</ul>
|
||
<p><strong>注意:</strong></p>
|
||
<ul>
|
||
<li>普通函数(非类成员函数)不能是虚函数</li>
|
||
<li>静态函数(static)不能是虚函数</li>
|
||
<li>构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针)</li>
|
||
<li>内联函数不能是表现多态性时的虚函数,解释见:虚函数(virtual)可以是内联函数(inline)吗?:<a href="http://t.cn/E4WVXSP">http://t.cn/E4WVXSP</a></li>
|
||
</ul>
|
||
<h4 id="动态多态使用"><a href="#动态多态使用" class="headerlink" title="动态多态使用"></a>动态多态使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">class Shape // 形状类</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual double calcArea()</span><br><span class="line"> {</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> virtual ~Shape();</span><br><span class="line">};</span><br><span class="line">class Circle : public Shape // 圆形类</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual double calcArea();</span><br><span class="line"> ...</span><br><span class="line">};</span><br><span class="line">class Rect : public Shape // 矩形类</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual double calcArea();</span><br><span class="line"> ...</span><br><span class="line">};</span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> Shape * shape1 = new Circle(4.0);</span><br><span class="line"> Shape * shape2 = new Rect(5.0, 6.0);</span><br><span class="line"> shape1->calcArea(); // 调用圆形类里面的方法</span><br><span class="line"> shape2->calcArea(); // 调用矩形类里面的方法</span><br><span class="line"> delete shape1;</span><br><span class="line"> shape1 = nullptr;</span><br><span class="line"> delete shape2;</span><br><span class="line"> shape2 = nullptr;</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="虚析构函数"><a href="#虚析构函数" class="headerlink" title="虚析构函数"></a>虚析构函数</h3><p>虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。</p>
|
||
<h4 id="虚析构函数使用"><a href="#虚析构函数使用" class="headerlink" title="虚析构函数使用"></a>虚析构函数使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">class Shape</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> Shape(); // 构造函数不能是虚函数</span><br><span class="line"> virtual double calcArea();</span><br><span class="line"> virtual ~Shape(); // 虚析构函数</span><br><span class="line">};</span><br><span class="line">class Circle : public Shape // 圆形类</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual double calcArea();</span><br><span class="line"> ...</span><br><span class="line">};</span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> Shape * shape1 = new Circle(4.0);</span><br><span class="line"> shape1->calcArea();</span><br><span class="line"> delete shape1; // 因为Shape有虚析构函数,所以delete释放内存时,先调用子类析构函数,再调用基类析构函数,防止内存泄漏。</span><br><span class="line"> shape1 = NULL;</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="纯虚函数"><a href="#纯虚函数" class="headerlink" title="纯虚函数"></a>纯虚函数</h3><p>纯虚函数是一种特殊的虚函数,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">virtual int A() = 0;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="虚函数、纯虚函数"><a href="#虚函数、纯虚函数" class="headerlink" title="虚函数、纯虚函数"></a>虚函数、纯虚函数</h3><blockquote>
|
||
<p>CSDN . C++ 中的虚函数、纯虚函数区别和联系:<a href="http://t.cn/E4WVQBI">http://t.cn/E4WVQBI</a></p>
|
||
</blockquote>
|
||
<ul>
|
||
<li>类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样的话,这样编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。</li>
|
||
<li>虚函数在子类里面也可以不重载的;但纯虚函数必须在子类去实现。</li>
|
||
<li>虚函数的类用于 “实作继承”,继承接口的同时也继承了父类的实现。当然大家也可以完成自己的实现。纯虚函数关注的是接口的统一性,实现由子类完成。</li>
|
||
<li>带纯虚函数的类叫抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。抽象类和大家口头常说的虚基类还是有区别的,在 C# 中用 abstract 定义抽象类,而在 C++ 中有抽象类的概念,但是没有这个关键字。抽象类被继承后,子类可以继续是抽象类,也可以是普通类,而虚基类,是含有纯虚函数的类,它如果被继承,那么子类就必须实现虚基类里面的所有纯虚函数,其子类不能是抽象类。</li>
|
||
</ul>
|
||
<h3 id="虚函数指针、虚函数表"><a href="#虚函数指针、虚函数表" class="headerlink" title="虚函数指针、虚函数表"></a>虚函数指针、虚函数表</h3><ul>
|
||
<li>虚函数指针:在含有虚函数类的对象中,指向虚函数表,在运行时确定。</li>
|
||
<li>虚函数表:在程序只读数据段(<code>.rodata section</code>,见:目标文件存储结构:<a href="http://t.cn/E4WVBeF%EF%BC%89%EF%BC%8C%E5%AD%98%E6%94%BE%E8%99%9A%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%EF%BC%8C%E5%A6%82%E6%9E%9C%E6%B4%BE%E7%94%9F%E7%B1%BB%E5%AE%9E%E7%8E%B0%E4%BA%86%E5%9F%BA%E7%B1%BB%E7%9A%84%E6%9F%90%E4%B8%AA%E8%99%9A%E5%87%BD%E6%95%B0%EF%BC%8C%E5%88%99%E5%9C%A8%E8%99%9A%E8%A1%A8%E4%B8%AD%E8%A6%86%E7%9B%96%E5%8E%9F%E6%9C%AC%E5%9F%BA%E7%B1%BB%E7%9A%84%E9%82%A3%E4%B8%AA%E8%99%9A%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%EF%BC%8C%E5%9C%A8%E7%BC%96%E8%AF%91%E6%97%B6%E6%A0%B9%E6%8D%AE%E7%B1%BB%E7%9A%84%E5%A3%B0%E6%98%8E%E5%88%9B%E5%BB%BA%E3%80%82">http://t.cn/E4WVBeF),存放虚函数指针,如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。</a></li>
|
||
</ul>
|
||
<h3 id="虚继承"><a href="#虚继承" class="headerlink" title="虚继承"></a>虚继承</h3><p>虚继承用于解决多继承条件下的菱形继承问题(浪费存储空间、存在二义性)。</p>
|
||
<p>底层实现原理与编译器相关,一般通过<strong>虚基类指针</strong>和<strong>虚基类表</strong>实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。</p>
|
||
<p>实际上,vbptr 指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。</p>
|
||
<h3 id="虚继承、虚函数"><a href="#虚继承、虚函数" class="headerlink" title="虚继承、虚函数"></a>虚继承、虚函数</h3><ul>
|
||
<li><p>相同之处:都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)</p>
|
||
</li>
|
||
<li><p>不同之处:</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>虚函数不占用存储空间</li>
|
||
<li>虚函数表存储的是虚函数地址</li>
|
||
<li>虚基类依旧存在继承类中,只占用存储空间</li>
|
||
<li>虚基类表存储的是虚基类相对直接继承类的偏移</li>
|
||
<li>虚继承</li>
|
||
<li>虚函数</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<h3 id="模板类、成员模板、虚函数"><a href="#模板类、成员模板、虚函数" class="headerlink" title="模板类、成员模板、虚函数"></a>模板类、成员模板、虚函数</h3><ul>
|
||
<li>模板类中可以使用虚函数</li>
|
||
<li>一个类(无论是普通类还是类模板)的成员模板(本身是模板的成员函数)不能是虚函数</li>
|
||
</ul>
|
||
<h3 id="抽象类、接口类、聚合类"><a href="#抽象类、接口类、聚合类" class="headerlink" title="抽象类、接口类、聚合类"></a>抽象类、接口类、聚合类</h3><ul>
|
||
<li><p>抽象类:含有纯虚函数的类</p>
|
||
</li>
|
||
<li><p>接口类:仅含有纯虚函数的抽象类</p>
|
||
</li>
|
||
<li><p>聚合类:用户可以直接访问其成员,并且具有特殊的初始化语法形式。满足如下特点:</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>所有成员都是 public</li>
|
||
<li>没有有定于任何构造函数</li>
|
||
<li>没有类内初始化</li>
|
||
<li>没有基类,也没有 virtual 函数</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<h3 id="内存分配和管理"><a href="#内存分配和管理" class="headerlink" title="内存分配和管理"></a>内存分配和管理</h3><h4 id="malloc、calloc、realloc、alloca"><a href="#malloc、calloc、realloc、alloca" class="headerlink" title="malloc、calloc、realloc、alloca"></a>malloc、calloc、realloc、alloca</h4><ol>
|
||
<li>malloc:申请指定字节数的内存。申请到的内存中的初始值不确定。</li>
|
||
<li>calloc:为指定长度的对象,分配能容纳其指定个数的内存。申请到的内存的每一位(bit)都初始化为 0。</li>
|
||
<li>realloc:更改以前分配的内存长度(增加或减少)。当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,而新增区域内的初始值则不确定。</li>
|
||
<li>alloca:在栈上申请内存。程序在出栈的时候,会自动释放内存。但是需要注意的是,alloca 不具可移植性, 而且在没有传统堆栈的机器上很难实现。alloca 不宜使用在必须广泛移植的程序中。C99 中支持变长数组 (VLA),可以用来替代 alloca。</li>
|
||
</ol>
|
||
<h4 id="malloc、free"><a href="#malloc、free" class="headerlink" title="malloc、free"></a>malloc、free</h4><p>用于分配、释放内存</p>
|
||
<h4 id="malloc、free-使用"><a href="#malloc、free-使用" class="headerlink" title="malloc、free 使用"></a>malloc、free 使用</h4><p>申请内存,确认是否申请成功</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">char *str = (char*) malloc(100);</span><br><span class="line">assert(str != nullptr);</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>释放内存后指针置空</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">free(p);</span><br><span class="line">p = nullptr;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="new、delete"><a href="#new、delete" class="headerlink" title="new、delete"></a>new、delete</h4><ol>
|
||
<li>new / new[]:完成两件事,先底层调用 malloc 分了配内存,然后调用构造函数(创建对象)。</li>
|
||
<li>delete/delete[]:也完成两件事,先调用析构函数(清理资源),然后底层调用 free 释放空间。</li>
|
||
<li>new 在申请内存时会自动计算所需字节数,而 malloc 则需我们自己输入申请内存空间的字节数。</li>
|
||
</ol>
|
||
<h4 id="new、delete-使用"><a href="#new、delete-使用" class="headerlink" title="new、delete 使用"></a>new、delete 使用</h4><p>申请内存,确认是否申请成功</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> T* t = new T(); // 先内存分配 ,再构造函数</span><br><span class="line"> delete t; // 先析构函数,再内存释放</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="定位-new"><a href="#定位-new" class="headerlink" title="定位 new"></a>定位 new</h4><p>定位 new(placement new)允许我们向 new 传递额外的参数。</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">new (palce_address) type</span><br><span class="line">new (palce_address) type (initializers)</span><br><span class="line">new (palce_address) type [size]</span><br><span class="line">new (palce_address) type [size] { braced initializer list }</span><br></pre></td></tr></table></figure>
|
||
|
||
<ul>
|
||
<li><code>palce_address</code> 是个指针</li>
|
||
<li><code>initializers</code> 提供一个(可能为空的)以逗号分隔的初始值列表</li>
|
||
</ul>
|
||
<h3 id="delete-this-合法吗?"><a href="#delete-this-合法吗?" class="headerlink" title="delete this 合法吗?"></a>delete this 合法吗?</h3><blockquote>
|
||
<p>Is it legal (and moral) for a member function to say delete this?<br>答案:<a href="http://t.cn/E4Wfcfl">http://t.cn/E4Wfcfl</a></p>
|
||
</blockquote>
|
||
<p>合法,但:</p>
|
||
<ol>
|
||
<li>必须保证 this 对象是通过 <code>new</code>(不是 <code>new[]</code>、不是 placement new、不是栈上、不是全局、不是其他对象成员)分配的</li>
|
||
<li>必须保证调用 <code>delete this</code> 的成员函数是最后一个调用 this 的成员函数</li>
|
||
<li>必须保证成员函数的 <code>delete this</code> 后面没有调用 this 了</li>
|
||
<li>必须保证 <code>delete this</code> 后没有人使用了</li>
|
||
</ol>
|
||
<h3 id="如何定义一个只能在堆上(栈上)生成对象的类?"><a href="#如何定义一个只能在堆上(栈上)生成对象的类?" class="headerlink" title="如何定义一个只能在堆上(栈上)生成对象的类?"></a>如何定义一个只能在堆上(栈上)生成对象的类?</h3><blockquote>
|
||
<p>如何定义一个只能在堆上(栈上)生成对象的类?</p>
|
||
<p>答案:<a href="http://t.cn/E4WfDhP">http://t.cn/E4WfDhP</a></p>
|
||
</blockquote>
|
||
<h4 id="只能在堆上"><a href="#只能在堆上" class="headerlink" title="只能在堆上"></a>只能在堆上</h4><p>方法:将析构函数设置为私有</p>
|
||
<p>原因:C++ 是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。</p>
|
||
<h4 id="只能在栈上"><a href="#只能在栈上" class="headerlink" title="只能在栈上"></a>只能在栈上</h4><p>方法:将 new 和 delete 重载为私有</p>
|
||
<p>原因:在堆上生成对象,使用 new 关键词操作,其过程分为两阶段:第一阶段,使用 new 在堆上寻找可用内存,分配给对象;第二阶段,调用构造函数生成对象。将 new 操作设置为私有,那么第一阶段就无法完成,就不能够在堆上生成对象。</p>
|
||
<h3 id="智能指针"><a href="#智能指针" class="headerlink" title="智能指针"></a>智能指针</h3><h4 id="C-标准库(STL)中"><a href="#C-标准库(STL)中" class="headerlink" title="C++ 标准库(STL)中"></a>C++ 标准库(STL)中</h4><p>头文件:<code>#include <memory></code></p>
|
||
<h4 id="C-98"><a href="#C-98" class="headerlink" title="C++ 98"></a>C++ 98</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">std::auto_ptr<std::string> ps (new std::string(str));</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="C-11"><a href="#C-11" class="headerlink" title="C++ 11"></a>C++ 11</h4><ol>
|
||
<li>shared_ptr</li>
|
||
<li>unique_ptr</li>
|
||
<li>weak_ptr</li>
|
||
<li>auto_ptr(被 C++11 弃用)</li>
|
||
</ol>
|
||
<ul>
|
||
<li>Class shared_ptr 实现共享式拥有(shared ownership)概念。多个智能指针指向相同对象,该对象和其相关资源会在 “最后一个 reference 被销毁” 时被释放。为了在结构较复杂的情景中执行上述工作,标准库提供 weak_ptr、bad_weak_ptr 和 enable_shared_from_this 等辅助类。</li>
|
||
<li>Class unique_ptr 实现独占式拥有(exclusive ownership)或严格拥有(strict ownership)概念,保证同一时间内只有一个智能指针可以指向该对象。你可以移交拥有权。它对于避免内存泄漏(resource leak)——如 new 后忘记 delete ——特别有用。</li>
|
||
</ul>
|
||
<h5 id="shared-ptr"><a href="#shared-ptr" class="headerlink" title="shared_ptr"></a>shared_ptr</h5><p>多个智能指针可以共享同一个对象,对象的最末一个拥有着有责任销毁对象,并清理与该对象相关的所有资源。</p>
|
||
<ul>
|
||
<li>支持定制型删除器(custom deleter),可防范 Cross-DLL 问题(对象在动态链接库(DLL)中被 new 创建,却在另一个 DLL 内被 delete 销毁)、自动解除互斥锁</li>
|
||
</ul>
|
||
<h5 id="weak-ptr"><a href="#weak-ptr" class="headerlink" title="weak_ptr"></a>weak_ptr</h5><p>weak_ptr 允许你共享但不拥有某对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何 weak_ptr 都会自动成空(empty)。因此,在 default 和 copy 构造函数之外,weak_ptr 只提供 “接受一个 shared_ptr” 的构造函数。</p>
|
||
<ul>
|
||
<li>可打破环状引用(cycles of references,两个其实已经没有被使用的对象彼此互指,使之看似还在 “被使用” 的状态)的问题</li>
|
||
</ul>
|
||
<h5 id="unique-ptr"><a href="#unique-ptr" class="headerlink" title="unique_ptr"></a>unique_ptr</h5><p>unique_ptr 是 C++11 才开始提供的类型,是一种在异常时可以帮助避免资源泄漏的智能指针。采用独占式拥有,意味着可以确保一个对象和其相应的资源同一时间只被一个 pointer 拥有。一旦拥有着被销毁或编程 empty,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源亦会被释放。</p>
|
||
<ul>
|
||
<li>unique_ptr 用于取代 auto_ptr</li>
|
||
</ul>
|
||
<h5 id="auto-ptr"><a href="#auto-ptr" class="headerlink" title="auto_ptr"></a>auto_ptr</h5><p>被 c++11 弃用,原因是缺乏语言特性如 “针对构造和赋值” 的 <code>std::move</code> 语义,以及其他瑕疵。</p>
|
||
<h5 id="auto-ptr-与-unique-ptr-比较"><a href="#auto-ptr-与-unique-ptr-比较" class="headerlink" title="auto_ptr 与 unique_ptr 比较"></a>auto_ptr 与 unique_ptr 比较</h5><ul>
|
||
<li>auto_ptr 可以赋值拷贝,复制拷贝后所有权转移;unqiue_ptr 无拷贝赋值语义,但实现了<code>move</code> 语义;</li>
|
||
<li>auto_ptr 对象不能管理数组(析构调用 <code>delete</code>),unique_ptr 可以管理数组(析构调用 <code>delete[]</code> );</li>
|
||
</ul>
|
||
<h3 id="强制类型转换运算符"><a href="#强制类型转换运算符" class="headerlink" title="强制类型转换运算符"></a>强制类型转换运算符</h3><blockquote>
|
||
<p>MSDN . 强制转换运算符:<a href="http://t.cn/E4WIt5W">http://t.cn/E4WIt5W</a></p>
|
||
</blockquote>
|
||
<h4 id="static-cast"><a href="#static-cast" class="headerlink" title="static_cast"></a>static_cast</h4><ul>
|
||
<li>用于非多态类型的转换</li>
|
||
<li>不执行运行时类型检查(转换安全性不如 dynamic_cast)</li>
|
||
<li>通常用于转换数值数据类型(如 float -> int)</li>
|
||
<li>可以在整个类层次结构中移动指针,子类转化为父类安全(向上转换),父类转化为子类不安全(因为子类可能有不在父类的字段或方法)</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>向上转换是一种隐式转换。</p>
|
||
</blockquote>
|
||
<h4 id="dynamic-cast"><a href="#dynamic-cast" class="headerlink" title="dynamic_cast"></a>dynamic_cast</h4><ul>
|
||
<li>用于多态类型的转换</li>
|
||
<li>执行行运行时类型检查</li>
|
||
<li>只适用于指针或引用</li>
|
||
<li>对不明确的指针的转换将失败(返回 nullptr),但不引发异常</li>
|
||
<li>可以在整个类层次结构中移动指针,包括向上转换、向下转换</li>
|
||
</ul>
|
||
<h4 id="const-cast"><a href="#const-cast" class="headerlink" title="const_cast"></a>const_cast</h4><ul>
|
||
<li>用于删除 const、volatile 和 __unaligned 特性(如将 const int 类型转换为 int 类型 )</li>
|
||
</ul>
|
||
<h4 id="reinterpret-cast"><a href="#reinterpret-cast" class="headerlink" title="reinterpret_cast"></a>reinterpret_cast</h4><ul>
|
||
<li>用于位的简单重新解释</li>
|
||
<li>滥用 reinterpret_cast 运算符可能很容易带来风险。除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一。</li>
|
||
<li>允许将任何指针转换为任何其他指针类型(如 <code>char*</code> 到 <code>int*</code> 或 <code>One_class*</code> 到 <code>Unrelated_class*</code> 之类的转换,但其本身并不安全)</li>
|
||
<li>也允许将任何整数类型转换为任何指针类型以及反向转换。</li>
|
||
<li>reinterpret_cast 运算符不能丢掉 const、volatile 或 __unaligned 特性。</li>
|
||
<li>reinterpret_cast 的一个实际用途是在哈希函数中,即,通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。</li>
|
||
</ul>
|
||
<h4 id="bad-cast"><a href="#bad-cast" class="headerlink" title="bad_cast"></a>bad_cast</h4><ul>
|
||
<li>由于强制转换为引用类型失败,dynamic_cast 运算符引发 bad_cast 异常。</li>
|
||
</ul>
|
||
<h4 id="bad-cast-使用"><a href="#bad-cast-使用" class="headerlink" title="bad_cast 使用"></a>bad_cast 使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">try {</span><br><span class="line"> Circle& ref_circle = dynamic_cast<Circle&>(ref_shape);</span><br><span class="line">}</span><br><span class="line">catch (bad_cast b) {</span><br><span class="line"> cout << "Caught: " << b.what();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="运行时类型信息-RTTI"><a href="#运行时类型信息-RTTI" class="headerlink" title="运行时类型信息 (RTTI)"></a>运行时类型信息 (RTTI)</h3><h4 id="dynamic-cast-1"><a href="#dynamic-cast-1" class="headerlink" title="dynamic_cast"></a>dynamic_cast</h4><ul>
|
||
<li>用于多态类型的转换</li>
|
||
</ul>
|
||
<h4 id="typeid"><a href="#typeid" class="headerlink" title="typeid"></a>typeid</h4><ul>
|
||
<li>typeid 运算符允许在运行时确定对象的类型</li>
|
||
<li>type_id 返回一个 type_info 对象的引用</li>
|
||
<li>如果想通过基类的指针获得派生类的数据类型,基类必须带有虚函数</li>
|
||
<li>只能获取对象的实际类型</li>
|
||
</ul>
|
||
<h4 id="type-info"><a href="#type-info" class="headerlink" title="type_info"></a>type_info</h4><ul>
|
||
<li>type_info 类描述编译器在程序中生成的类型信息。此类的对象可以有效存储指向类型的名称的指针。type_info 类还可存储适合比较两个类型是否相等或比较其排列顺序的编码值。类型的编码规则和排列顺序是未指定的,并且可能因程序而异。</li>
|
||
<li>头文件:<code>typeinfo</code></li>
|
||
</ul>
|
||
<h4 id="typeid、type-info-使用"><a href="#typeid、type-info-使用" class="headerlink" title="typeid、type_info 使用"></a>typeid、type_info 使用</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">class Flyable // 能飞的</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> virtual void takeoff() = 0; // 起飞</span><br><span class="line"> virtual void land() = 0; // 降落</span><br><span class="line">};</span><br><span class="line">class Bird : public Flyable // 鸟</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> void foraging() {...} // 觅食</span><br><span class="line"> virtual void takeoff() {...}</span><br><span class="line"> virtual void land() {...}</span><br><span class="line">};</span><br><span class="line">class Plane : public Flyable // 飞机</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> void carry() {...} // 运输</span><br><span class="line"> virtual void take off() {...}</span><br><span class="line"> virtual void land() {...}</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class type_info</span><br><span class="line">{</span><br><span class="line">public:</span><br><span class="line"> const char* name() const;</span><br><span class="line"> bool operator == (const type_info & rhs) const;</span><br><span class="line"> bool operator != (const type_info & rhs) const;</span><br><span class="line"> int before(const type_info & rhs) const;</span><br><span class="line"> virtual ~type_info();</span><br><span class="line">private:</span><br><span class="line"> ...</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">class doSomething(Flyable *obj) // 做些事情</span><br><span class="line">{</span><br><span class="line"> obj->takeoff();</span><br><span class="line"></span><br><span class="line"> cout << typeid(*obj).name() << endl; // 输出传入对象类型("class Bird" or "class Plane")</span><br><span class="line"></span><br><span class="line"> if(typeid(*obj) == typeid(Bird)) // 判断对象类型</span><br><span class="line"> {</span><br><span class="line"> Bird *bird = dynamic_cast<Bird *>(obj); // 对象转化</span><br><span class="line"> bird->foraging();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> obj->land();</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
|
||
|
||
|
||
|
||
<h3 id="Effective-C"><a href="#Effective-C" class="headerlink" title="Effective C++"></a>Effective C++</h3><ol>
|
||
<li>视 C++ 为一个语言联邦(C、Object-Oriented C++、Template C++、STL)</li>
|
||
<li>宁可以编译器替换预处理器(尽量以 <code>const</code>、<code>enum</code>、<code>inline</code> 替换 <code>#define</code>)</li>
|
||
<li>尽可能使用 const</li>
|
||
<li>确定对象被使用前已先被初始化(构造时赋值(copy 构造函数)比 default 构造后赋值(copy assignment)效率高)</li>
|
||
<li>了解 C++ 默默编写并调用哪些函数(编译器暗自为 class 创建 default 构造函数、copy 构造函数、copy assignment 操作符、析构函数)</li>
|
||
<li>若不想使用编译器自动生成的函数,就应该明确拒绝(将不想使用的成员函数声明为 private,并且不予实现)</li>
|
||
<li>为多态基类声明 virtual 析构函数(如果 class 带有任何 virtual 函数,它就应该拥有一个 virtual 析构函数)</li>
|
||
<li>别让异常逃离析构函数(析构函数应该吞下不传播异常,或者结束程序,而不是吐出异常;如果要处理异常应该在非析构的普通函数处理)</li>
|
||
<li>绝不在构造和析构过程中调用 virtual 函数(因为这类调用从不下降至 derived class)</li>
|
||
<li>令 <code>operator=</code> 返回一个 <code>reference to *this</code> (用于连锁赋值)</li>
|
||
<li>在 <code>operator=</code> 中处理 “自我赋值”</li>
|
||
<li>赋值对象时应确保复制 “对象内的所有成员变量” 及 “所有 base class 成分”(调用基类复制构造函数)</li>
|
||
<li>以对象管理资源(资源在构造函数获得,在析构函数释放,建议使用智能指针,资源取得时机便是初始化时机(Resource Acquisition Is Initialization,RAII))</li>
|
||
<li>在资源管理类中小心 copying 行为(普遍的 RAII class copying 行为是:抑制 copying、引用计数、深度拷贝、转移底部资源拥有权(类似 auto_ptr))</li>
|
||
<li>在资源管理类中提供对原始资源(raw resources)的访问(对原始资源的访问可能经过显式转换或隐式转换,一般而言显示转换比较安全,隐式转换对客户比较方便)</li>
|
||
<li>成对使用 new 和 delete 时要采取相同形式(<code>new</code> 中使用 <code>[]</code> 则 <code>delete []</code>,<code>new</code> 中不使用 <code>[]</code> 则 <code>delete</code>)</li>
|
||
<li>以独立语句将 newed 对象存储于(置入)智能指针(如果不这样做,可能会因为编译器优化,导致难以察觉的资源泄漏)</li>
|
||
<li>让接口容易被正确使用,不易被误用(促进正常使用的办法:接口的一致性、内置类型的行为兼容;阻止误用的办法:建立新类型,限制类型上的操作,约束对象值、消除客户的资源管理责任)</li>
|
||
<li>设计 class 犹如设计 type,需要考虑对象创建、销毁、初始化、赋值、值传递、合法值、继承关系、转换、一般化等等。</li>
|
||
<li>宁以 pass-by-reference-to-const 替换 pass-by-value (前者通常更高效、避免切割问题(slicing problem),但不适用于内置类型、STL迭代器、函数对象)</li>
|
||
<li>必须返回对象时,别妄想返回其 reference(绝不返回 pointer 或 reference 指向一个 local stack 对象,或返回 reference 指向一个 heap-allocated 对象,或返回 pointer 或 reference 指向一个 local static 对象而有可能同时需要多个这样的对象。)</li>
|
||
<li>将成员变量声明为 private(为了封装、一致性、对其读写精确控制等)</li>
|
||
<li>宁以 non-member、non-friend 替换 member 函数(可增加封装性、包裹弹性(packaging flexibility)、机能扩充性)</li>
|
||
<li>若所有参数(包括被this指针所指的那个隐喻参数)皆须要类型转换,请为此采用 non-member 函数</li>
|
||
<li>考虑写一个不抛异常的 swap 函数</li>
|
||
<li>尽可能延后变量定义式的出现时间(可增加程序清晰度并改善程序效率)</li>
|
||
<li>尽量少做转型动作(旧式:<code>(T)expression</code>、<code>T(expression)</code>;新式:<code>const_cast(expression)</code>、<code>dynamic_cast(expression)</code>、<code>reinterpret_cast(expression)</code>、<code>static_cast(expression)</code>、;尽量避免转型、注重效率避免 dynamic_casts、尽量设计成无需转型、可把转型封装成函数、宁可用新式转型)</li>
|
||
<li>避免使用 handles(包括 引用、指针、迭代器)指向对象内部(以增加封装性、使 const 成员函数的行为更像 const、降低 “虚吊号码牌”(dangling handles,如悬空指针等)的可能性)</li>
|
||
<li>为 “异常安全” 而努力是值得的(异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构败坏,分为三种可能的保证:基本型、强列型、不抛异常型)</li>
|
||
<li>透彻了解 inlining 的里里外外(inlining 在大多数 C++ 程序中是编译期的行为;inline 函数是否真正 inline,取决于编译器;大部分编译器拒绝太过复杂(如带有循环或递归)的函数 inlining,而所有对 virtual 函数的调用(除非是最平淡无奇的)也都会使 inlining 落空;inline 造成的代码膨胀可能带来效率损失;inline 函数无法随着程序库的升级而升级)</li>
|
||
<li>将文件间的编译依存关系降至最低(如果使用 object references 或 object pointers 可以完成任务,就不要使用 objects;如果能过够,尽量以 class 声明式替换 class 定义式;为声明式和定义式提供不同的头文件)</li>
|
||
<li>确定你的 public 继承塑模出 is-a(是一种)关系(适用于 base classes 身上的每一件事情一定适用于 derived classes 身上,因为每一个 derived class 对象也都是一个 base class 对象)</li>
|
||
<li>避免遮掩继承而来的名字(可使用 using 声明式或转交函数(forwarding functions)来让被遮掩的名字再见天日)</li>
|
||
<li>区分接口继承和实现继承(在 public 继承之下,derived classes 总是继承 base class 的接口;pure virtual 函数只具体指定接口继承;非纯 impure virtual 函数具体指定接口继承及缺省实现继承;non-virtual 函数具体指定接口继承以及强制性实现继承)</li>
|
||
<li>考虑 virtual 函数以外的其他选择(如 Template Method 设计模式的 non-virtual interface(NVI)手法,将 virtual 函数替换为 “函数指针成员变量”,以 <code>tr1::function</code> 成员变量替换 virtual 函数,将继承体系内的 virtual 函数替换为另一个继承体系内的 virtual 函数)</li>
|
||
<li>绝不重新定义继承而来的 non-virtual 函数</li>
|
||
<li>绝不重新定义继承而来的缺省参数值,因为缺省参数值是静态绑定(statically bound),而 virtual 函数却是动态绑定(dynamically bound)</li>
|
||
<li>通过复合塑模 has-a(有一个)或 “根据某物实现出”(在应用域(application domain),复合意味 has-a(有一个);在实现域(implementation domain),复合意味着 is-implemented-in-terms-of(根据某物实现出))</li>
|
||
<li>明智而审慎地使用 private 继承(private 继承意味着 is-implemented-in-terms-of(根据某物实现出),尽可能使用复合,当 derived class 需要访问 protected base class 的成员,或需要重新定义继承而来的时候 virtual 函数,或需要 empty base 最优化时,才使用 private 继承)</li>
|
||
<li>明智而审慎地使用多重继承(多继承比单一继承复杂,可能导致新的歧义性,以及对 virtual 继承的需要,但确有正当用途,如 “public 继承某个 interface class” 和 “private 继承某个协助实现的 class”;virtual 继承可解决多继承下菱形继承的二义性问题,但会增加大小、速度、初始化及赋值的复杂度等等成本)</li>
|
||
<li>了解隐式接口和编译期多态(class 和 templates 都支持接口(interfaces)和多态(polymorphism);class 的接口是以签名为中心的显式的(explicit),多态则是通过 virtual 函数发生于运行期;template 的接口是奠基于有效表达式的隐式的(implicit),多态则是通过 template 具现化和函数重载解析(function overloading resolution)发生于编译期)</li>
|
||
<li>了解 typename 的双重意义(声明 template 类型参数是,前缀关键字 class 和 typename 的意义完全相同;请使用关键字 typename 标识嵌套从属类型名称,但不得在基类列(base class lists)或成员初值列(member initialization list)内以它作为 basee class 修饰符)</li>
|
||
<li>学习处理模板化基类内的名称(可在 derived class templates 内通过 <code>this-></code> 指涉 base class templates 内的成员名称,或藉由一个明白写出的 “base class 资格修饰符” 完成)</li>
|
||
<li>将与参数无关的代码抽离 templates(因类型模板参数(non-type template parameters)而造成代码膨胀往往可以通过函数参数或 class 成员变量替换 template 参数来消除;因类型参数(type parameters)而造成的代码膨胀往往可以通过让带有完全相同二进制表述(binary representations)的实现类型(instantiation types)共享实现码)</li>
|
||
<li>运用成员函数模板接受所有兼容类型(请使用成员函数模板(member function templates)生成 “可接受所有兼容类型” 的函数;声明 member templates 用于 “泛化 copy 构造” 或 “泛化 assignment 操作” 时还需要声明正常的 copy 构造函数和 copy assignment 操作符)</li>
|
||
<li>需要类型转换时请为模板定义非成员函数(当我们编写一个 class template,而它所提供之 “与此 template 相关的” 函数支持 “所有参数之隐式类型转换” 时,请将那些函数定义为 “class template 内部的 friend 函数”)</li>
|
||
<li>请使用 traits classes 表现类型信息(traits classes 通过 templates 和 “templates 特化” 使得 “类型相关信息” 在编译期可用,通过重载技术(overloading)实现在编译期对类型执行 if…else 测试)</li>
|
||
<li>认识 template 元编程(模板元编程(TMP,template metaprogramming)可将工作由运行期移往编译期,因此得以实现早期错误侦测和更高的执行效率;TMP 可被用来生成 “给予政策选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码)</li>
|
||
<li>了解 new-handler 的行为(set_new_handler 允许客户指定一个在内存分配无法获得满足时被调用的函数;nothrow new 是一个颇具局限的工具,因为它只适用于内存分配(operator new),后继的构造函数调用还是可能抛出异常)</li>
|
||
</ol>
|
||
<h3 id="Google-C-Style-Guide"><a href="#Google-C-Style-Guide" class="headerlink" title="Google C++ Style Guide"></a>Google C++ Style Guide</h3><blockquote>
|
||
<p>英文:Google C++ Style Guide :<a href="http://t.cn/RqhluJP">http://t.cn/RqhluJP</a></p>
|
||
<p>中文:C++ 风格指南:<a href="http://t.cn/ELDTnur">http://t.cn/ELDTnur</a></p>
|
||
<h4 id="Google-C-Style-Guide-图"><a href="#Google-C-Style-Guide-图" class="headerlink" title="Google C++ Style Guide 图"></a>Google C++ Style Guide 图</h4></blockquote>
|
||
<p><img src="/posts/3189/images/650.webp" alt="图片"></p>
|
||
<blockquote>
|
||
<p>图片来源于:CSDN . 一张图总结Google C++编程规范(Google C++ Style Guide)</p>
|
||
</blockquote>
|
||
<h2 id="STL"><a href="#STL" class="headerlink" title="STL"></a>STL</h2><h3 id="STL-索引"><a href="#STL-索引" class="headerlink" title="STL 索引"></a>STL 索引</h3><p>STL 方法含义索引:<a href="http://t.cn/E4WMXXs">http://t.cn/E4WMXXs</a></p>
|
||
<h3 id="STL-容器"><a href="#STL-容器" class="headerlink" title="STL 容器"></a>STL 容器</h3><p>容器的详细说明:<a href="http://t.cn/E4WMXXs">http://t.cn/E4WMXXs</a></p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>容器</th>
|
||
<th>底层数据结构</th>
|
||
<th>时间复杂度</th>
|
||
<th>有无序</th>
|
||
<th>可不可重复</th>
|
||
<th>其他</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>array</td>
|
||
<td>数组</td>
|
||
<td>随机读改 O(1)</td>
|
||
<td>无序</td>
|
||
<td>可重复</td>
|
||
<td>支持快速随机访问</td>
|
||
</tr>
|
||
<tr>
|
||
<td>vector</td>
|
||
<td>数组</td>
|
||
<td>随机读改、尾部插入、尾部删除 O(1) 头部插入、头部删除 O(n)</td>
|
||
<td>无序</td>
|
||
<td>可重复</td>
|
||
<td>支持快速随机访问</td>
|
||
</tr>
|
||
<tr>
|
||
<td>list</td>
|
||
<td>双向链表</td>
|
||
<td>插入、删除 O(1) 随机读改 O(n)</td>
|
||
<td>无序</td>
|
||
<td>可重复</td>
|
||
<td>支持快速增删</td>
|
||
</tr>
|
||
<tr>
|
||
<td>deque</td>
|
||
<td>双端队列</td>
|
||
<td>头尾插入、头尾删除 O(1)</td>
|
||
<td>无序</td>
|
||
<td>可重复</td>
|
||
<td>一个中央控制器 + 多个缓冲区,支持首尾快速增删,支持随机访问</td>
|
||
</tr>
|
||
<tr>
|
||
<td>stack</td>
|
||
<td>deque / list</td>
|
||
<td>顶部插入、顶部删除 O(1)</td>
|
||
<td>无序</td>
|
||
<td>可重复</td>
|
||
<td>deque 或 list 封闭头端开口,不用 vector 的原因应该是容量大小有限制,扩容耗时</td>
|
||
</tr>
|
||
<tr>
|
||
<td>queue</td>
|
||
<td>deque / list</td>
|
||
<td>尾部插入、头部删除 O(1)</td>
|
||
<td>无序</td>
|
||
<td>可重复</td>
|
||
<td>deque 或 list 封闭头端开口,不用 vector 的原因应该是容量大小有限制,扩容耗时</td>
|
||
</tr>
|
||
<tr>
|
||
<td>priority_queue</td>
|
||
<td>vector + max-heap</td>
|
||
<td>插入、删除 O(log2n)</td>
|
||
<td>有序</td>
|
||
<td>可重复</td>
|
||
<td>vector容器+heap处理规则</td>
|
||
</tr>
|
||
<tr>
|
||
<td>set</td>
|
||
<td>红黑树</td>
|
||
<td>插入、删除、查找 O(log2n)</td>
|
||
<td>有序</td>
|
||
<td>不可重复</td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>multiset</td>
|
||
<td>红黑树</td>
|
||
<td>插入、删除、查找 O(log2n)</td>
|
||
<td>有序</td>
|
||
<td>可重复</td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>map</td>
|
||
<td>红黑树</td>
|
||
<td>插入、删除、查找 O(log2n)</td>
|
||
<td>有序</td>
|
||
<td>不可重复</td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>multimap</td>
|
||
<td>红黑树</td>
|
||
<td>插入、删除、查找 O(log2n)</td>
|
||
<td>有序</td>
|
||
<td>可重复</td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>hash_set</td>
|
||
<td>哈希表</td>
|
||
<td>插入、删除、查找 O(1) 最差 O(n)</td>
|
||
<td>无序</td>
|
||
<td>不可重复</td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>hash_multiset</td>
|
||
<td>哈希表</td>
|
||
<td>插入、删除、查找 O(1) 最差 O(n)</td>
|
||
<td>无序</td>
|
||
<td>可重复</td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>hash_map</td>
|
||
<td>哈希表</td>
|
||
<td>插入、删除、查找 O(1) 最差 O(n)</td>
|
||
<td>无序</td>
|
||
<td>不可重复</td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>hash_multimap</td>
|
||
<td>哈希表</td>
|
||
<td>插入、删除、查找 O(1) 最差 O(n)</td>
|
||
<td>无序</td>
|
||
<td>可重复</td>
|
||
<td></td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h3 id="STL-算法"><a href="#STL-算法" class="headerlink" title="STL 算法"></a>STL 算法</h3><p><a href="http://t.cn/aEv0DV">http://t.cn/aEv0DV</a></p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>算法</th>
|
||
<th>底层算法</th>
|
||
<th>时间复杂度</th>
|
||
<th>可不可重复</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>find</td>
|
||
<td>顺序查找</td>
|
||
<td>O(n)</td>
|
||
<td>可重复</td>
|
||
</tr>
|
||
<tr>
|
||
<td>sort</td>
|
||
<td>内省排序</td>
|
||
<td>O(n*log2n)</td>
|
||
<td>可重复</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><h3 id="顺序结构"><a href="#顺序结构" class="headerlink" title="顺序结构"></a>顺序结构</h3><h4 id="顺序栈(Sequence-Stack)"><a href="#顺序栈(Sequence-Stack)" class="headerlink" title="顺序栈(Sequence Stack)"></a>顺序栈(Sequence Stack)</h4><p>SqStack.cpp:<a href="http://t.cn/E4WxO0b">http://t.cn/E4WxO0b</a></p>
|
||
<h4 id="顺序栈数据结构和图片"><a href="#顺序栈数据结构和图片" class="headerlink" title="顺序栈数据结构和图片"></a>顺序栈数据结构和图片</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">typedef struct {</span><br><span class="line"> ElemType *elem;</span><br><span class="line"> int top;</span><br><span class="line"> int size;</span><br><span class="line"> int increment;</span><br><span class="line">} SqSrack;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="队列(Sequence-Queue)"><a href="#队列(Sequence-Queue)" class="headerlink" title="队列(Sequence Queue)"></a>队列(Sequence Queue)</h4><h4 id="队列数据结构"><a href="#队列数据结构" class="headerlink" title="队列数据结构"></a>队列数据结构</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">typedef struct {</span><br><span class="line"> ElemType * elem;</span><br><span class="line"> int front;</span><br><span class="line"> int rear;</span><br><span class="line"> int maxSize;</span><br><span class="line">}SqQueue;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h5 id="非循环队列"><a href="#非循环队列" class="headerlink" title="非循环队列"></a>非循环队列</h5><h4 id="非循环队列图片"><a href="#非循环队列图片" class="headerlink" title="非循环队列图片"></a>非循环队列图片</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">SqQueue.rear++</span><br></pre></td></tr></table></figure>
|
||
|
||
<h5 id="循环队列"><a href="#循环队列" class="headerlink" title="循环队列"></a>循环队列</h5><h4 id="循环队列图片"><a href="#循环队列图片" class="headerlink" title="循环队列图片"></a>循环队列图片</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">SqQueue.rear = (SqQueue.rear + 1) % SqQueue.maxSize</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="顺序表(Sequence-List)"><a href="#顺序表(Sequence-List)" class="headerlink" title="顺序表(Sequence List)"></a>顺序表(Sequence List)</h4><p>SqList.cpp</p>
|
||
<p>顺序表数据结构和图片</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">typedef struct {</span><br><span class="line"> ElemType *elem;</span><br><span class="line"> int length;</span><br><span class="line"> int size;</span><br><span class="line"> int increment;</span><br><span class="line">} SqList;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="链式结构"><a href="#链式结构" class="headerlink" title="链式结构"></a>链式结构</h3><p>LinkList.cpp</p>
|
||
<p>LinkList_with_head.cpp</p>
|
||
<h4 id="链式数据结构"><a href="#链式数据结构" class="headerlink" title="链式数据结构"></a>链式数据结构</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">typedef struct LNode {</span><br><span class="line"> ElemType data;</span><br><span class="line"> struct LNode *next;</span><br><span class="line">} LNode, *LinkList;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="链队列(Link-Queue)"><a href="#链队列(Link-Queue)" class="headerlink" title="链队列(Link Queue)"></a>链队列(Link Queue)</h4><h4 id="链队列图片"><a href="#链队列图片" class="headerlink" title="链队列图片"></a>链队列图片</h4><h4 id="线性表的链式表示"><a href="#线性表的链式表示" class="headerlink" title="线性表的链式表示"></a>线性表的链式表示</h4><h5 id="单链表(Link-List)"><a href="#单链表(Link-List)" class="headerlink" title="单链表(Link List)"></a>单链表(Link List)</h5><h4 id="单链表图片"><a href="#单链表图片" class="headerlink" title="单链表图片"></a>单链表图片</h4><h5 id="双向链表(Du-Link-List)"><a href="#双向链表(Du-Link-List)" class="headerlink" title="双向链表(Du-Link-List)"></a>双向链表(Du-Link-List)</h5><h4 id="双向链表图片"><a href="#双向链表图片" class="headerlink" title="双向链表图片"></a>双向链表图片</h4><h5 id="循环链表(Cir-Link-List)"><a href="#循环链表(Cir-Link-List)" class="headerlink" title="循环链表(Cir-Link-List)"></a>循环链表(Cir-Link-List)</h5><h4 id="循环链表图片"><a href="#循环链表图片" class="headerlink" title="循环链表图片"></a>循环链表图片</h4><h3 id="哈希表"><a href="#哈希表" class="headerlink" title="哈希表"></a>哈希表</h3><p>HashTable.cpp</p>
|
||
<h4 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h4><p>哈希函数:<code>H(key): K -> D , key ∈ K</code></p>
|
||
<h4 id="构造方法"><a href="#构造方法" class="headerlink" title="构造方法"></a>构造方法</h4><ul>
|
||
<li>直接定址法</li>
|
||
<li>除留余数法</li>
|
||
<li>数字分析法</li>
|
||
<li>折叠法</li>
|
||
<li>平方取中法</li>
|
||
</ul>
|
||
<h4 id="冲突处理方法"><a href="#冲突处理方法" class="headerlink" title="冲突处理方法"></a>冲突处理方法</h4><ul>
|
||
<li><p>链地址法:key 相同的用单链表链接</p>
|
||
</li>
|
||
<li><p>开放定址法</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>线性探测法:key 相同 -> 放到 key 的下一个位置,<code>Hi = (H(key) + i) % m</code></li>
|
||
<li>二次探测法:key 相同 -> 放到 <code>Di = 1^2, -1^2, …, ±(k)^2,(k<=m/2)</code></li>
|
||
<li>随机探测法:<code>H = (H(key) + 伪随机数) % m</code></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<h4 id="线性探测的哈希表数据结构"><a href="#线性探测的哈希表数据结构" class="headerlink" title="线性探测的哈希表数据结构"></a>线性探测的哈希表数据结构</h4><h4 id="线性探测的哈希表数据结构和图片"><a href="#线性探测的哈希表数据结构和图片" class="headerlink" title="线性探测的哈希表数据结构和图片"></a>线性探测的哈希表数据结构和图片</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">typedef char KeyType;</span><br><span class="line"></span><br><span class="line">typedef struct {</span><br><span class="line"> KeyType key;</span><br><span class="line">}RcdType;</span><br><span class="line"></span><br><span class="line">typedef struct {</span><br><span class="line"> RcdType *rcd;</span><br><span class="line"> int size;</span><br><span class="line"> int count;</span><br><span class="line"> bool *tag;</span><br><span class="line">}HashTable;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="递归"><a href="#递归" class="headerlink" title="递归"></a>递归</h3><h4 id="概念-1"><a href="#概念-1" class="headerlink" title="概念"></a>概念</h4><p>函数直接或间接地调用自身</p>
|
||
<h4 id="递归与分治"><a href="#递归与分治" class="headerlink" title="递归与分治"></a>递归与分治</h4><ul>
|
||
<li><p>分治法</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>问题的分解</li>
|
||
<li>问题规模的分解</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>折半查找(递归)</p>
|
||
</li>
|
||
<li><p>归并查找(递归)</p>
|
||
</li>
|
||
<li><p>快速排序(递归)</p>
|
||
</li>
|
||
</ul>
|
||
<h4 id="递归与迭代"><a href="#递归与迭代" class="headerlink" title="递归与迭代"></a>递归与迭代</h4><ul>
|
||
<li>迭代:反复利用变量旧值推出新值</li>
|
||
<li>折半查找(迭代)</li>
|
||
<li>归并查找(迭代)</li>
|
||
</ul>
|
||
<h4 id="广义表"><a href="#广义表" class="headerlink" title="广义表"></a>广义表</h4><h5 id="头尾链表存储表示"><a href="#头尾链表存储表示" class="headerlink" title="头尾链表存储表示"></a>头尾链表存储表示</h5><h4 id="广义表的头尾链表存储表示和图片"><a href="#广义表的头尾链表存储表示和图片" class="headerlink" title="广义表的头尾链表存储表示和图片"></a>广义表的头尾链表存储表示和图片</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 广义表的头尾链表存储表示</span><br><span class="line">typedef enum {ATOM, LIST} ElemTag;</span><br><span class="line">// ATOM==0:原子,LIST==1:子表</span><br><span class="line">typedef struct GLNode {</span><br><span class="line"> ElemTag tag;</span><br><span class="line"> // 公共部分,用于区分原子结点和表结点</span><br><span class="line"> union {</span><br><span class="line"> // 原子结点和表结点的联合部分</span><br><span class="line"> AtomType atom;</span><br><span class="line"> // atom 是原子结点的值域,AtomType 由用户定义</span><br><span class="line"> struct {</span><br><span class="line"> struct GLNode *hp, *tp;</span><br><span class="line"> } ptr;</span><br><span class="line"> // ptr 是表结点的指针域,prt.hp 和 ptr.tp 分别指向表头和表尾</span><br><span class="line"> } a;</span><br><span class="line">} *GList, GLNode;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h5 id="扩展线性链表存储表示"><a href="#扩展线性链表存储表示" class="headerlink" title="扩展线性链表存储表示"></a>扩展线性链表存储表示</h5><h4 id="扩展线性链表存储表示和图片"><a href="#扩展线性链表存储表示和图片" class="headerlink" title="扩展线性链表存储表示和图片"></a>扩展线性链表存储表示和图片</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 广义表的扩展线性链表存储表示</span><br><span class="line">typedef enum {ATOM, LIST} ElemTag;</span><br><span class="line">// ATOM==0:原子,LIST==1:子表</span><br><span class="line">typedef struct GLNode1 {</span><br><span class="line"> ElemTag tag;</span><br><span class="line"> // 公共部分,用于区分原子结点和表结点</span><br><span class="line"> union {</span><br><span class="line"> // 原子结点和表结点的联合部分</span><br><span class="line"> AtomType atom; // 原子结点的值域</span><br><span class="line"> struct GLNode1 *hp; // 表结点的表头指针</span><br><span class="line"> } a;</span><br><span class="line"> struct GLNode1 *tp;</span><br><span class="line"> // 相当于线性链表的 next,指向下一个元素结点</span><br><span class="line">} *GList1, GLNode1;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="二叉树"><a href="#二叉树" class="headerlink" title="二叉树"></a>二叉树</h3><p>BinaryTree.cpp</p>
|
||
<h4 id="性质"><a href="#性质" class="headerlink" title="性质"></a>性质</h4><ol>
|
||
<li><p>非空二叉树第 i 层最多 2(i-1) 个结点 (i >= 1)</p>
|
||
</li>
|
||
<li><p>深度为 k 的二叉树最多 2k - 1 个结点 (k >= 1)</p>
|
||
</li>
|
||
<li><p>度为 0 的结点数为 n0,度为 2 的结点数为 n2,则 n0 = n2 + 1</p>
|
||
</li>
|
||
<li><p>有 n 个结点的完全二叉树深度 k = ⌊ log2(n) ⌋ + 1</p>
|
||
</li>
|
||
<li><p>对于含 n 个结点的完全二叉树中编号为 i (1 <= i <= n) 的结点</p>
|
||
</li>
|
||
<li><ol>
|
||
<li>若 i = 1,为根,否则双亲为 ⌊ i / 2 ⌋</li>
|
||
<li>若 2i > n,则 i 结点没有左孩子,否则孩子编号为 2i</li>
|
||
<li>若 2i + 1 > n,则 i 结点没有右孩子,否则孩子编号为 2i + 1</li>
|
||
</ol>
|
||
</li>
|
||
</ol>
|
||
<h4 id="存储结构"><a href="#存储结构" class="headerlink" title="存储结构"></a>存储结构</h4><h4 id="二叉树数据结构"><a href="#二叉树数据结构" class="headerlink" title="二叉树数据结构"></a>二叉树数据结构</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">typedef struct BiTNode</span><br><span class="line">{</span><br><span class="line"> TElemType data;</span><br><span class="line"> struct BiTNode *lchild, *rchild;</span><br><span class="line">}BiTNode, *BiTree;</span><br></pre></td></tr></table></figure>
|
||
|
||
<h5 id="顺序存储"><a href="#顺序存储" class="headerlink" title="顺序存储"></a>顺序存储</h5><h4 id="二叉树顺序存储图片"><a href="#二叉树顺序存储图片" class="headerlink" title="二叉树顺序存储图片"></a>二叉树顺序存储图片</h4><h5 id="链式存储"><a href="#链式存储" class="headerlink" title="链式存储"></a>链式存储</h5><h4 id="二叉树链式存储图片"><a href="#二叉树链式存储图片" class="headerlink" title="二叉树链式存储图片"></a>二叉树链式存储图片</h4><h4 id="遍历方式"><a href="#遍历方式" class="headerlink" title="遍历方式"></a>遍历方式</h4><ul>
|
||
<li>先序遍历</li>
|
||
<li>中序遍历</li>
|
||
<li>后续遍历</li>
|
||
<li>层次遍历</li>
|
||
</ul>
|
||
<h4 id="分类-1"><a href="#分类-1" class="headerlink" title="分类"></a>分类</h4><ul>
|
||
<li><p>满二叉树</p>
|
||
</li>
|
||
<li><p>完全二叉树(堆)</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>大顶堆:根 >= 左 && 根 >= 右</li>
|
||
<li>小顶堆:根 <= 左 && 根 <= 右</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>二叉查找树(二叉排序树):左 < 根 < 右</p>
|
||
</li>
|
||
<li><p>平衡二叉树(AVL树):| 左子树树高 - 右子树树高 | <= 1</p>
|
||
</li>
|
||
<li><p>最小失衡树:平衡二叉树插入新结点导致失衡的子树:调整:</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>LL型:根的左孩子右旋</li>
|
||
<li>RR型:根的右孩子左旋</li>
|
||
<li>LR型:根的左孩子左旋,再右旋</li>
|
||
<li>RL型:右孩子的左子树,先右旋,再左旋</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<h3 id="其他树及森林"><a href="#其他树及森林" class="headerlink" title="其他树及森林"></a>其他树及森林</h3><h4 id="树的存储结构"><a href="#树的存储结构" class="headerlink" title="树的存储结构"></a>树的存储结构</h4><ul>
|
||
<li>双亲表示法</li>
|
||
<li>双亲孩子表示法</li>
|
||
<li>孩子兄弟表示法</li>
|
||
</ul>
|
||
<h4 id="并查集"><a href="#并查集" class="headerlink" title="并查集"></a>并查集</h4><p>一种不相交的子集所构成的集合 S = {S1, S2, …, Sn}</p>
|
||
<h4 id="平衡二叉树(AVL树)"><a href="#平衡二叉树(AVL树)" class="headerlink" title="平衡二叉树(AVL树)"></a>平衡二叉树(AVL树)</h4><h5 id="性质-1"><a href="#性质-1" class="headerlink" title="性质"></a>性质</h5><ul>
|
||
<li>| 左子树树高 - 右子树树高 | <= 1</li>
|
||
<li>平衡二叉树必定是二叉搜索树,反之则不一定</li>
|
||
<li>最小二叉平衡树的节点的公式:<code>F(n)=F(n-1)+F(n-2)+1</code> (1 是根节点,F(n-1) 是左子树的节点数量,F(n-2) 是右子树的节点数量)</li>
|
||
</ul>
|
||
<h4 id="平衡二叉树图片"><a href="#平衡二叉树图片" class="headerlink" title="平衡二叉树图片"></a>平衡二叉树图片</h4><h5 id="最小失衡树"><a href="#最小失衡树" class="headerlink" title="最小失衡树"></a>最小失衡树</h5><p>平衡二叉树插入新结点导致失衡的子树</p>
|
||
<p>调整:</p>
|
||
<ul>
|
||
<li>LL 型:根的左孩子右旋</li>
|
||
<li>RR 型:根的右孩子左旋</li>
|
||
<li>LR 型:根的左孩子左旋,再右旋</li>
|
||
<li>RL 型:右孩子的左子树,先右旋,再左旋</li>
|
||
</ul>
|
||
<h4 id="红黑树"><a href="#红黑树" class="headerlink" title="红黑树"></a>红黑树</h4><h5 id="红黑树的特征是什么?"><a href="#红黑树的特征是什么?" class="headerlink" title="红黑树的特征是什么?"></a>红黑树的特征是什么?</h5><ol>
|
||
<li>节点是红色或黑色。</li>
|
||
<li>根是黑色。</li>
|
||
<li>所有叶子都是黑色(叶子是 NIL 节点)。</li>
|
||
<li>每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)(新增节点的父节点必须相同)</li>
|
||
<li>从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。(新增节点必须为红)</li>
|
||
</ol>
|
||
<h5 id="调整"><a href="#调整" class="headerlink" title="调整"></a>调整</h5><ol>
|
||
<li>变色</li>
|
||
<li>左旋</li>
|
||
<li>右旋</li>
|
||
</ol>
|
||
<h5 id="应用"><a href="#应用" class="headerlink" title="应用"></a>应用</h5><ul>
|
||
<li>关联数组:如 STL 中的 map、set</li>
|
||
</ul>
|
||
<h5 id="红黑树、B-树、B-树的区别?"><a href="#红黑树、B-树、B-树的区别?" class="headerlink" title="红黑树、B 树、B+ 树的区别?"></a>红黑树、B 树、B+ 树的区别?</h5><ul>
|
||
<li>红黑树的深度比较大,而 B 树和 B+ 树的深度则相对要小一些</li>
|
||
<li>B+ 树则将数据都保存在叶子节点,同时通过链表的形式将他们连接在一起。</li>
|
||
</ul>
|
||
<h4 id="B-树(B-tree)、B-树(B-tree)"><a href="#B-树(B-tree)、B-树(B-tree)" class="headerlink" title="B 树(B-tree)、B+ 树(B+-tree)"></a>B 树(B-tree)、B+ 树(B+-tree)</h4><h4 id="B-树、B-树图片"><a href="#B-树、B-树图片" class="headerlink" title="B 树、B+ 树图片"></a>B 树、B+ 树图片</h4><p><img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="图片">B 树(B-tree)、B+ 树(B+-tree)</p>
|
||
<h5 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h5><ul>
|
||
<li>一般化的二叉查找树(binary search tree)</li>
|
||
<li>“矮胖”,内部(非叶子)节点可以拥有可变数量的子节点(数量范围预先定义好)</li>
|
||
</ul>
|
||
<h5 id="应用-1"><a href="#应用-1" class="headerlink" title="应用"></a>应用</h5><ul>
|
||
<li>大部分文件系统、数据库系统都采用B树、B+树作为索引结构</li>
|
||
</ul>
|
||
<h5 id="区别-1"><a href="#区别-1" class="headerlink" title="区别"></a>区别</h5><ul>
|
||
<li>B+树中只有叶子节点会带有指向记录的指针(ROWID),而B树则所有节点都带有,在内部节点出现的索引项不会再出现在叶子节点中。</li>
|
||
<li>B+树中所有叶子节点都是通过指针连接在一起,而B树不会。</li>
|
||
</ul>
|
||
<h5 id="B树的优点"><a href="#B树的优点" class="headerlink" title="B树的优点"></a>B树的优点</h5><p>对于在内部节点的数据,可直接得到,不必根据叶子节点来定位。</p>
|
||
<h5 id="B-树的优点"><a href="#B-树的优点" class="headerlink" title="B+树的优点"></a>B+树的优点</h5><ul>
|
||
<li>非叶子节点不会带上 ROWID,这样,一个块中可以容纳更多的索引项,一是可以降低树的高度。二是一个内部节点可以定位更多的叶子节点。</li>
|
||
<li>叶子节点之间通过指针来连接,范围扫描将十分简单,而对于B树来说,则需要在叶子节点和内部节点不停的往返移动。</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>B 树、B+ 树区别来自:differences-between-b-trees-and-b-trees、B树和B+树的区别:</p>
|
||
<p><a href="http://t.cn/RrBAaZa">http://t.cn/RrBAaZa</a></p>
|
||
<p><a href="http://t.cn/E4WJhmZ">http://t.cn/E4WJhmZ</a></p>
|
||
</blockquote>
|
||
<h4 id="八叉树"><a href="#八叉树" class="headerlink" title="八叉树"></a>八叉树</h4><h4 id="八叉树图片"><a href="#八叉树图片" class="headerlink" title="八叉树图片"></a>八叉树图片</h4><p><img src="/posts/3189/images/660.webp" alt="图片"></p>
|
||
<p>八叉树(octree),或称八元树,是一种用于描述三维空间(划分空间)的树状数据结构。八叉树的每个节点表示一个正方体的体积元素,每个节点有八个子节点,这八个子节点所表示的体积元素加在一起就等于父节点的体积。一般中心点作为节点的分叉中心。</p>
|
||
<h5 id="用途"><a href="#用途" class="headerlink" title="用途"></a>用途</h5><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><a href="http://t.cn/E4WJUGz">http://t.cn/E4WJUGz</a></p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>排序算法</th>
|
||
<th>平均时间复杂度</th>
|
||
<th>最差时间复杂度</th>
|
||
<th>空间复杂度</th>
|
||
<th>数据对象稳定性</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>冒泡排序</td>
|
||
<td>O(n2)</td>
|
||
<td>O(n2)</td>
|
||
<td>O(1)</td>
|
||
<td>稳定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>选择排序</td>
|
||
<td>O(n2)</td>
|
||
<td>O(n2)</td>
|
||
<td>O(1)</td>
|
||
<td>数组不稳定、链表稳定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>插入排序</td>
|
||
<td>O(n2)</td>
|
||
<td>O(n2)</td>
|
||
<td>O(1)</td>
|
||
<td>稳定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>快速排序</td>
|
||
<td>O(n*log2n)</td>
|
||
<td>O(n2)</td>
|
||
<td>O(log2n)</td>
|
||
<td>不稳定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>堆排序</td>
|
||
<td>O(n*log2n)</td>
|
||
<td>O(n*log2n)</td>
|
||
<td>O(1)</td>
|
||
<td>不稳定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>归并排序</td>
|
||
<td>O(n*log2n)</td>
|
||
<td>O(n*log2n)</td>
|
||
<td>O(n)</td>
|
||
<td>稳定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>希尔排序</td>
|
||
<td>O(n*log2n)</td>
|
||
<td>O(n2)</td>
|
||
<td>O(1)</td>
|
||
<td>不稳定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>计数排序</td>
|
||
<td>O(n+m)</td>
|
||
<td>O(n+m)</td>
|
||
<td>O(n+m)</td>
|
||
<td>稳定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>桶排序</td>
|
||
<td>O(n)</td>
|
||
<td>O(n)</td>
|
||
<td>O(m)</td>
|
||
<td>稳定</td>
|
||
</tr>
|
||
<tr>
|
||
<td>基数排序</td>
|
||
<td>O(k*n)</td>
|
||
<td>O(n2)</td>
|
||
<td></td>
|
||
<td>稳定</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<blockquote>
|
||
<p>均按从小到大排列</p>
|
||
<ul>
|
||
<li>k:代表数值中的 “数位” 个数</li>
|
||
<li>n:代表数据规模</li>
|
||
<li>m:代表数据的最大值减最小值</li>
|
||
<li>来自:wikipedia . 排序算法</li>
|
||
</ul>
|
||
</blockquote>
|
||
<h3 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h3><table>
|
||
<thead>
|
||
<tr>
|
||
<th>查找算法</th>
|
||
<th>平均时间复杂度</th>
|
||
<th>空间复杂度</th>
|
||
<th>查找条件</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>顺序查找</td>
|
||
<td>O(n)</td>
|
||
<td>O(1)</td>
|
||
<td>无序或有序</td>
|
||
</tr>
|
||
<tr>
|
||
<td>二分查找(折半查找)</td>
|
||
<td>O(log2n)</td>
|
||
<td>O(1)</td>
|
||
<td>有序</td>
|
||
</tr>
|
||
<tr>
|
||
<td>插值查找</td>
|
||
<td>O(log2(log2n))</td>
|
||
<td>O(1)</td>
|
||
<td>有序</td>
|
||
</tr>
|
||
<tr>
|
||
<td>斐波那契查找</td>
|
||
<td>O(log2n)</td>
|
||
<td>O(1)</td>
|
||
<td>有序</td>
|
||
</tr>
|
||
<tr>
|
||
<td>哈希查找</td>
|
||
<td>O(1)</td>
|
||
<td>O(n)</td>
|
||
<td>无序或有序</td>
|
||
</tr>
|
||
<tr>
|
||
<td>二叉查找树(二叉搜索树查找)</td>
|
||
<td>O(log2n)</td>
|
||
<td></td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>红黑树</td>
|
||
<td>O(log2n)</td>
|
||
<td></td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>2-3树</td>
|
||
<td>O(log2n - log3n)</td>
|
||
<td></td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>B树/B+树</td>
|
||
<td>O(log2n)</td>
|
||
<td></td>
|
||
<td></td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h3 id="图搜索算法"><a href="#图搜索算法" class="headerlink" title="图搜索算法"></a>图搜索算法</h3><table>
|
||
<thead>
|
||
<tr>
|
||
<th>图搜索算法</th>
|
||
<th>数据结构</th>
|
||
<th>遍历时间复杂度</th>
|
||
<th>空间复杂度</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>BFS广度优先搜索</td>
|
||
<td>邻接矩阵 邻接链表</td>
|
||
<td>O(|v|2) O(|v|+|E|)</td>
|
||
<td>O(|v|2) O(|v|+|E|)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>DFS深度优先搜索</td>
|
||
<td>邻接矩阵 邻接链表</td>
|
||
<td>O(|v|2) O(|v|+|E|)</td>
|
||
<td>O(|v|2) O(|v|+|E|)</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h3 id="其他算法"><a href="#其他算法" class="headerlink" title="其他算法"></a>其他算法</h3><table>
|
||
<thead>
|
||
<tr>
|
||
<th>算法</th>
|
||
<th>思想</th>
|
||
<th>应用</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>分治法</td>
|
||
<td>把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并</td>
|
||
<td>循环赛日程安排问题、排序算法(快速排序、归并排序)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>动态规划</td>
|
||
<td>通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法,适用于有重叠子问题和最优子结构性质的问题</td>
|
||
<td>背包问题、斐波那契数列</td>
|
||
</tr>
|
||
<tr>
|
||
<td>贪心法</td>
|
||
<td>一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法</td>
|
||
<td>旅行推销员问题(最短路径问题)、最小生成树、哈夫曼编码</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h2 id="Problems"><a href="#Problems" class="headerlink" title="Problems"></a>Problems</h2><h3 id="Single-Problem"><a href="#Single-Problem" class="headerlink" title="Single Problem"></a>Single Problem</h3><ul>
|
||
<li>Chessboard Coverage Problem(棋盘覆盖问题)</li>
|
||
<li>Knapsack Problem(背包问题)</li>
|
||
<li>Neumann Neighbor Problem(冯诺依曼邻居问题)</li>
|
||
<li>Round Robin Problem(循环赛日程安排问题)</li>
|
||
<li>Tubing Problem(输油管道问题)</li>
|
||
</ul>
|
||
<h3 id="Leetcode-Problems"><a href="#Leetcode-Problems" class="headerlink" title="Leetcode Problems"></a>Leetcode Problems</h3><ul>
|
||
<li>Github . haoel/leetcode</li>
|
||
<li>Github . pezy/LeetCode</li>
|
||
</ul>
|
||
<h3 id="剑指-Offer"><a href="#剑指-Offer" class="headerlink" title="剑指 Offer"></a>剑指 Offer</h3><ul>
|
||
<li>Github . zhedahht/CodingInterviewChinese2</li>
|
||
<li>Github . gatieme/CodingInterviews</li>
|
||
</ul>
|
||
<h3 id="Cracking-the-Coding-Interview-程序员面试金典"><a href="#Cracking-the-Coding-Interview-程序员面试金典" class="headerlink" title="Cracking the Coding Interview 程序员面试金典"></a>Cracking the Coding Interview 程序员面试金典</h3><ul>
|
||
<li>Github . careercup/ctci</li>
|
||
<li>牛客网 . 程序员面试金典</li>
|
||
</ul>
|
||
<h3 id="牛客网"><a href="#牛客网" class="headerlink" title="牛客网"></a>牛客网</h3><ul>
|
||
<li>牛客网 . 在线编程专题</li>
|
||
</ul>
|
||
<h2 id="操作系统"><a href="#操作系统" class="headerlink" title="操作系统"></a>操作系统</h2><h3 id="进程与线程"><a href="#进程与线程" class="headerlink" title="进程与线程"></a>进程与线程</h3><p>对于有线程系统:</p>
|
||
<ul>
|
||
<li>进程是资源分配的独立单位</li>
|
||
<li>线程是资源调度的独立单位</li>
|
||
</ul>
|
||
<p>对于无线程系统:</p>
|
||
<ul>
|
||
<li>进程是资源调度、分配的独立单位</li>
|
||
</ul>
|
||
<h4 id="进程之间的通信方式以及优缺点"><a href="#进程之间的通信方式以及优缺点" class="headerlink" title="进程之间的通信方式以及优缺点"></a>进程之间的通信方式以及优缺点</h4><ul>
|
||
<li><p>管道(PIPE)</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>优点:简单方便</li>
|
||
<li>缺点:</li>
|
||
<li>优点:可以实现任意关系的进程间的通信</li>
|
||
<li>缺点:</li>
|
||
<li>有名管道:一种半双工的通信方式,它允许无亲缘关系进程间的通信</li>
|
||
<li>无名管道:一种半双工的通信方式,只能在具有亲缘关系的进程间使用(父子进程)</li>
|
||
</ul>
|
||
</li>
|
||
<li><ol>
|
||
<li>局限于单向通信</li>
|
||
<li>只能创建在它的进程以及其有亲缘关系的进程之间</li>
|
||
<li>缓冲区有限</li>
|
||
</ol>
|
||
</li>
|
||
<li><ol>
|
||
<li>长期存于系统中,使用不当容易出错</li>
|
||
<li>缓冲区有限</li>
|
||
</ol>
|
||
</li>
|
||
<li><p>信号量(Semaphore):一个计数器,可以用来控制多个线程对共享资源的访问</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>优点:可以同步进程</li>
|
||
<li>缺点:信号量有限</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>信号(Signal):一种比较复杂的通信方式,用于通知接收进程某个事件已经发生</p>
|
||
</li>
|
||
<li><p>消息队列(Message Queue):是消息的链表,存放在内核中并由消息队列标识符标识</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>优点:可以实现任意进程间的通信,并通过系统调用函数来实现消息发送和接收之间的同步,无需考虑同步问题,方便</li>
|
||
<li>缺点:信息的复制需要额外消耗 CPU 的时间,不适宜于信息量大或操作频繁的场合</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>共享内存(Shared Memory):映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>优点:无须复制,快捷,信息量大</li>
|
||
<li>缺点:</li>
|
||
</ul>
|
||
</li>
|
||
<li><ol>
|
||
<li>通信是通过将共享空间缓冲区直接附加到进程的虚拟地址空间中来实现的,因此进程间的读写操作的同步问题</li>
|
||
<li>利用内存缓冲区直接交换信息,内存的实体存在于计算机中,只能同一个计算机系统中的诸多进程共享,不方便网络通信</li>
|
||
</ol>
|
||
</li>
|
||
<li><p>套接字(Socket):可用于不同及其间的进程通信</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>优点:</li>
|
||
<li>缺点:需对传输的数据进行解析,转化成应用级的数据。</li>
|
||
</ul>
|
||
</li>
|
||
<li><ol>
|
||
<li>传输数据为字节级,传输数据可自定义,数据量小效率高</li>
|
||
<li>传输数据时间短,性能高</li>
|
||
<li>适合于客户端和服务器端之间信息实时交互</li>
|
||
<li>可以加密,数据安全性强</li>
|
||
</ol>
|
||
</li>
|
||
</ul>
|
||
<h4 id="线程之间的通信方式"><a href="#线程之间的通信方式" class="headerlink" title="线程之间的通信方式"></a>线程之间的通信方式</h4><ul>
|
||
<li><p>锁机制:包括互斥锁/量(mutex)、读写锁(reader-writer lock)、自旋锁(spin lock)、条件变量(condition)</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>互斥锁/量(mutex):提供了以排他方式防止数据结构被并发修改的方法。</li>
|
||
<li>读写锁(reader-writer lock):允许多个线程同时读共享数据,而对写操作是互斥的。</li>
|
||
<li>自旋锁(spin lock)与互斥锁类似,都是为了保护共享资源。互斥锁是当资源被占用,申请者进入睡眠状态;而自旋锁则循环检测保持着是否已经释放锁。</li>
|
||
<li>条件变量(condition):可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>信号量机制(Semaphore)</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>无名线程信号量</li>
|
||
<li>命名线程信号量</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>信号机制(Signal):类似进程间的信号处理</p>
|
||
</li>
|
||
<li><p>屏障(barrier):屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从该点继续执行。</p>
|
||
</li>
|
||
</ul>
|
||
<p>线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制</p>
|
||
<blockquote>
|
||
<p>进程之间的通信方式以及优缺点来源于:进程线程面试题总结</p>
|
||
</blockquote>
|
||
<h4 id="进程之间私有和共享的资源"><a href="#进程之间私有和共享的资源" class="headerlink" title="进程之间私有和共享的资源"></a>进程之间私有和共享的资源</h4><ul>
|
||
<li>私有:地址空间、堆、全局变量、栈、寄存器</li>
|
||
<li>共享:代码段,公共数据,进程目录,进程 ID</li>
|
||
</ul>
|
||
<h4 id="线程之间私有和共享的资源"><a href="#线程之间私有和共享的资源" class="headerlink" title="线程之间私有和共享的资源"></a>线程之间私有和共享的资源</h4><ul>
|
||
<li>私有:线程栈,寄存器,程序寄存器</li>
|
||
<li>共享:堆,地址空间,全局变量,静态变量</li>
|
||
</ul>
|
||
<h4 id="多进程与多线程间的对比、优劣与选择"><a href="#多进程与多线程间的对比、优劣与选择" class="headerlink" title="多进程与多线程间的对比、优劣与选择"></a>多进程与多线程间的对比、优劣与选择</h4><h5 id="对比"><a href="#对比" class="headerlink" title="对比"></a>对比</h5><table>
|
||
<thead>
|
||
<tr>
|
||
<th>对比维度</th>
|
||
<th>多进程</th>
|
||
<th>多线程</th>
|
||
<th>总结</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>数据共享、同步</td>
|
||
<td>数据共享复杂,需要用 IPC;数据是分开的,同步简单</td>
|
||
<td>因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂</td>
|
||
<td>各有优势</td>
|
||
</tr>
|
||
<tr>
|
||
<td>内存、CPU</td>
|
||
<td>占用内存多,切换复杂,CPU 利用率低</td>
|
||
<td>占用内存少,切换简单,CPU 利用率高</td>
|
||
<td>线程占优</td>
|
||
</tr>
|
||
<tr>
|
||
<td>创建销毁、切换</td>
|
||
<td>创建销毁、切换复杂,速度慢</td>
|
||
<td>创建销毁、切换简单,速度很快</td>
|
||
<td>线程占优</td>
|
||
</tr>
|
||
<tr>
|
||
<td>编程、调试</td>
|
||
<td>编程简单,调试简单</td>
|
||
<td>编程复杂,调试复杂</td>
|
||
<td>进程占优</td>
|
||
</tr>
|
||
<tr>
|
||
<td>可靠性</td>
|
||
<td>进程间不会互相影响</td>
|
||
<td>一个线程挂掉将导致整个进程挂掉</td>
|
||
<td>进程占优</td>
|
||
</tr>
|
||
<tr>
|
||
<td>分布式</td>
|
||
<td>适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单</td>
|
||
<td>适应于多核分布式</td>
|
||
<td>进程占优</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h5 id="优劣"><a href="#优劣" class="headerlink" title="优劣"></a>优劣</h5><table>
|
||
<thead>
|
||
<tr>
|
||
<th>优劣</th>
|
||
<th>多进程</th>
|
||
<th>多线程</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>优点</td>
|
||
<td>编程、调试简单,可靠性较高</td>
|
||
<td>创建、销毁、切换速度快,内存、资源占用小</td>
|
||
</tr>
|
||
<tr>
|
||
<td>缺点</td>
|
||
<td>创建、销毁、切换速度慢,内存、资源占用大</td>
|
||
<td>编程、调试复杂,可靠性较差</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h5 id="选择"><a href="#选择" class="headerlink" title="选择"></a>选择</h5><ul>
|
||
<li>需要频繁创建销毁的优先用线程</li>
|
||
<li>需要进行大量计算的优先使用线程</li>
|
||
<li>强相关的处理用线程,弱相关的处理用进程</li>
|
||
<li>可能要扩展到多机分布的用进程,多核分布的用线程</li>
|
||
<li>都满足需求的情况下,用你最熟悉、最拿手的方式</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>多进程与多线程间的对比、优劣与选择来自:多线程还是多进程的选择及区别</p>
|
||
</blockquote>
|
||
<h3 id="Linux-内核的同步方式"><a href="#Linux-内核的同步方式" class="headerlink" title="Linux 内核的同步方式"></a>Linux 内核的同步方式</h3><h4 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h4><p>在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问。尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问。</p>
|
||
<h4 id="同步方式"><a href="#同步方式" class="headerlink" title="同步方式"></a>同步方式</h4><ul>
|
||
<li>原子操作</li>
|
||
<li>信号量(semaphore)</li>
|
||
<li>读写信号量(rw_semaphore)</li>
|
||
<li>自旋锁(spinlock)</li>
|
||
<li>大内核锁(BKL,Big Kernel Lock)</li>
|
||
<li>读写锁(rwlock)</li>
|
||
<li>大读者锁(brlock-Big Reader Lock)</li>
|
||
<li>读-拷贝修改(RCU,Read-Copy Update)</li>
|
||
<li>顺序锁(seqlock)</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>来自:Linux 内核的同步机制,第 1 部分、Linux 内核的同步机制,第 2 部分</p>
|
||
</blockquote>
|
||
<h3 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h3><h4 id="原因-1"><a href="#原因-1" class="headerlink" title="原因"></a>原因</h4><ul>
|
||
<li>系统资源不足</li>
|
||
<li>资源分配不当</li>
|
||
<li>进程运行推进顺序不合适</li>
|
||
</ul>
|
||
<h4 id="产生条件"><a href="#产生条件" class="headerlink" title="产生条件"></a>产生条件</h4><ul>
|
||
<li>互斥</li>
|
||
<li>请求和保持</li>
|
||
<li>不剥夺</li>
|
||
<li>环路</li>
|
||
</ul>
|
||
<h4 id="预防"><a href="#预防" class="headerlink" title="预防"></a>预防</h4><ul>
|
||
<li>打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。</li>
|
||
<li>打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。</li>
|
||
<li>打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。</li>
|
||
<li>打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。</li>
|
||
<li>有序资源分配法</li>
|
||
<li>银行家算法</li>
|
||
</ul>
|
||
<h3 id="文件系统"><a href="#文件系统" class="headerlink" title="文件系统"></a>文件系统</h3><ul>
|
||
<li>Windows:FCB 表 + FAT + 位图</li>
|
||
<li>Unix:inode + 混合索引 + 成组链接</li>
|
||
</ul>
|
||
<h3 id="主机字节序与网络字节序"><a href="#主机字节序与网络字节序" class="headerlink" title="主机字节序与网络字节序"></a>主机字节序与网络字节序</h3><h4 id="主机字节序(CPU-字节序)"><a href="#主机字节序(CPU-字节序)" class="headerlink" title="主机字节序(CPU 字节序)"></a>主机字节序(CPU 字节序)</h4><h5 id="概念-2"><a href="#概念-2" class="headerlink" title="概念"></a>概念</h5><p>主机字节序又叫 CPU 字节序,其不是由操作系统决定的,而是由 CPU 指令集架构决定的。主机字节序分为两种:</p>
|
||
<ul>
|
||
<li>大端字节序(Big Endian):高序字节存储在低位地址,低序字节存储在高位地址</li>
|
||
<li>小端字节序(Little Endian):高序字节存储在高位地址,低序字节存储在低位地址</li>
|
||
</ul>
|
||
<h5 id="存储方式"><a href="#存储方式" class="headerlink" title="存储方式"></a>存储方式</h5><p>32 位整数 <code>0x12345678</code> 是从起始位置为 <code>0x00</code> 的地址开始存放,则:</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>内存地址</th>
|
||
<th>0x00</th>
|
||
<th>0x01</th>
|
||
<th>0x02</th>
|
||
<th>0x03</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>大端</td>
|
||
<td>12</td>
|
||
<td>34</td>
|
||
<td>56</td>
|
||
<td>78</td>
|
||
</tr>
|
||
<tr>
|
||
<td>小端</td>
|
||
<td>78</td>
|
||
<td>56</td>
|
||
<td>34</td>
|
||
<td>12</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h4 id="大端小端图片"><a href="#大端小端图片" class="headerlink" title="大端小端图片"></a>大端小端图片</h4><p>大端序</p>
|
||
<p>小端序</p>
|
||
<h5 id="判断大端小端"><a href="#判断大端小端" class="headerlink" title="判断大端小端"></a>判断大端小端</h5><h4 id="判断大端小端-1"><a href="#判断大端小端-1" class="headerlink" title="判断大端小端"></a>判断大端小端</h4><p>可以这样判断自己 CPU 字节序是大端还是小端:</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#include <iostream></span><br><span class="line">using namespace std;</span><br><span class="line"></span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> int i = 0x12345678;</span><br><span class="line"></span><br><span class="line"> if (*((char*)&i) == 0x12)</span><br><span class="line"> cout << "大端" << endl;</span><br><span class="line"> else</span><br><span class="line"> cout << "小端" << endl;</span><br><span class="line"></span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h5 id="各架构处理器的字节序"><a href="#各架构处理器的字节序" class="headerlink" title="各架构处理器的字节序"></a>各架构处理器的字节序</h5><ul>
|
||
<li>x86(Intel、AMD)、MOS Technology 6502、Z80、VAX、PDP-11 等处理器为小端序;</li>
|
||
<li>Motorola 6800、Motorola 68000、PowerPC 970、System/370、SPARC(除 V9 外)等处理器为大端序;</li>
|
||
<li>ARM(默认小端序)、PowerPC(除 PowerPC 970 外)、DEC Alpha、SPARC V9、MIPS、PA-RISC 及 IA64 的字节序是可配置的。</li>
|
||
</ul>
|
||
<h4 id="网络字节序"><a href="#网络字节序" class="headerlink" title="网络字节序"></a>网络字节序</h4><p>网络字节顺序是 TCP/IP 中规定好的一种数据表示格式,它与具体的 CPU 类型、操作系统等无关,从而可以保重数据在不同主机之间传输时能够被正确解释。</p>
|
||
<p>网络字节顺序采用:大端(Big Endian)排列方式。</p>
|
||
<h3 id="页面置换算法"><a href="#页面置换算法" class="headerlink" title="页面置换算法"></a>页面置换算法</h3><p>在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。</p>
|
||
<h4 id="分类-2"><a href="#分类-2" class="headerlink" title="分类"></a>分类</h4><ul>
|
||
<li>全局置换:在整个内存空间置换</li>
|
||
<li>局部置换:在本进程中进行置换</li>
|
||
</ul>
|
||
<h4 id="算法-1"><a href="#算法-1" class="headerlink" title="算法"></a>算法</h4><p>全局:</p>
|
||
<ul>
|
||
<li>工作集算法</li>
|
||
<li>缺页率置换算法</li>
|
||
</ul>
|
||
<p>局部:</p>
|
||
<ul>
|
||
<li>最佳置换算法(OPT)</li>
|
||
<li>先进先出置换算法(FIFO)</li>
|
||
<li>最近最久未使用(LRU)算法</li>
|
||
<li>时钟(Clock)置换算法</li>
|
||
</ul>
|
||
<h2 id="计算机网络"><a href="#计算机网络" class="headerlink" title="计算机网络"></a>计算机网络</h2><p>计算机经网络体系结构:</p>
|
||
<p>计算机经网络体系结构</p>
|
||
<h3 id="各层作用及协议"><a href="#各层作用及协议" class="headerlink" title="各层作用及协议"></a>各层作用及协议</h3><table>
|
||
<thead>
|
||
<tr>
|
||
<th>分层</th>
|
||
<th>作用</th>
|
||
<th>协议</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>物理层</td>
|
||
<td>通过媒介传输比特,确定机械及电气规范(比特 Bit)</td>
|
||
<td>RJ45、CLOCK、IEEE802.3(中继器,集线器)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>数据链路层</td>
|
||
<td>将比特组装成帧和点到点的传递(帧 Frame)</td>
|
||
<td>PPP、FR、HDLC、VLAN、MAC(网桥,交换机)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>网络层</td>
|
||
<td>负责数据包从源到宿的传递和网际互连(包 Packet)</td>
|
||
<td>IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP(路由器)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>运输层</td>
|
||
<td>提供端到端的可靠报文传递和错误恢复( 段Segment)</td>
|
||
<td>TCP、UDP、SPX</td>
|
||
</tr>
|
||
<tr>
|
||
<td>会话层</td>
|
||
<td>建立、管理和终止会话(会话协议数据单元 SPDU)</td>
|
||
<td>NFS、SQL、NETBIOS、RPC</td>
|
||
</tr>
|
||
<tr>
|
||
<td>表示层</td>
|
||
<td>对数据进行翻译、加密和压缩(表示协议数据单元 PPDU)</td>
|
||
<td>JPEG、MPEG、ASII</td>
|
||
</tr>
|
||
<tr>
|
||
<td>应用层</td>
|
||
<td>允许访问OSI环境的手段(应用协议数据单元 APDU)</td>
|
||
<td>FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h3 id="物理层"><a href="#物理层" class="headerlink" title="物理层"></a>物理层</h3><ul>
|
||
<li>传输数据的单位 ———— 比特</li>
|
||
<li>数据传输系统:源系统(源点、发送器) –> 传输系统 –> 目的系统(接收器、终点)</li>
|
||
</ul>
|
||
<p>通道:</p>
|
||
<ul>
|
||
<li>单向通道(单工通道):只有一个方向通信,没有反方向交互,如广播</li>
|
||
<li>双向交替通行(半双工通信):通信双方都可发消息,但不能同时发送或接收</li>
|
||
<li>双向同时通信(全双工通信):通信双方可以同时发送和接收信息</li>
|
||
</ul>
|
||
<p>通道复用技术:</p>
|
||
<ul>
|
||
<li>频分复用(FDM,Frequency Division Multiplexing):不同用户在不同频带,所用用户在同样时间占用不同带宽资源</li>
|
||
<li>时分复用(TDM,Time Division Multiplexing):不同用户在同一时间段的不同时间片,所有用户在不同时间占用同样的频带宽度</li>
|
||
<li>波分复用(WDM,Wavelength Division Multiplexing):光的频分复用</li>
|
||
<li>码分复用(CDM,Code Division Multiplexing):不同用户使用不同的码,可以在同样时间使用同样频带通信</li>
|
||
</ul>
|
||
<h3 id="数据链路层"><a href="#数据链路层" class="headerlink" title="数据链路层"></a>数据链路层</h3><p>主要信道:</p>
|
||
<ul>
|
||
<li>点对点信道</li>
|
||
<li>广播信道</li>
|
||
</ul>
|
||
<h4 id="点对点信道"><a href="#点对点信道" class="headerlink" title="点对点信道"></a>点对点信道</h4><ul>
|
||
<li>数据单元 ———— 帧</li>
|
||
</ul>
|
||
<p>三个基本问题:</p>
|
||
<ul>
|
||
<li>封装成帧:把网络层的 IP 数据报封装成帧,<code>SOH - 数据部分 - EOT</code></li>
|
||
<li>透明传输:不管数据部分什么字符,都能传输出去;可以通过字节填充方法解决(冲突字符前加转义字符)</li>
|
||
<li>差错检测:降低误码率(BER,Bit Error Rate),广泛使用循环冗余检测(CRC,Cyclic Redundancy Check)</li>
|
||
</ul>
|
||
<p>点对点协议(Point-to-Point Protocol):</p>
|
||
<ul>
|
||
<li>点对点协议(Point-to-Point Protocol):用户计算机和 ISP 通信时所使用的协议</li>
|
||
</ul>
|
||
<h4 id="广播信道"><a href="#广播信道" class="headerlink" title="广播信道"></a>广播信道</h4><p>广播通信:</p>
|
||
<ul>
|
||
<li>硬件地址(物理地址、MAC 地址)</li>
|
||
<li>单播(unicast)帧(一对一):收到的帧的 MAC 地址与本站的硬件地址相同</li>
|
||
<li>广播(broadcast)帧(一对全体):发送给本局域网上所有站点的帧</li>
|
||
<li>多播(multicast)帧(一对多):发送给本局域网上一部分站点的帧</li>
|
||
</ul>
|
||
<h3 id="网络层"><a href="#网络层" class="headerlink" title="网络层"></a>网络层</h3><ul>
|
||
<li>IP(Internet Protocol,网际协议)是为计算机网络相互连接进行通信而设计的协议。</li>
|
||
<li>ARP(Address Resolution Protocol,地址解析协议)</li>
|
||
<li>ICMP(Internet Control Message Protocol,网际控制报文协议)</li>
|
||
<li>IGMP(Internet Group Management Protocol,网际组管理协议)</li>
|
||
</ul>
|
||
<h4 id="IP-网际协议"><a href="#IP-网际协议" class="headerlink" title="IP 网际协议"></a>IP 网际协议</h4><p>IP 地址分类:</p>
|
||
<ul>
|
||
<li><code>IP 地址 ::= {<网络号>,<主机号>}</code></li>
|
||
</ul>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>IP 地址类别</th>
|
||
<th>网络号</th>
|
||
<th>网络范围</th>
|
||
<th>主机号</th>
|
||
<th>IP 地址范围</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>A 类</td>
|
||
<td>8bit,第一位固定为 0</td>
|
||
<td>0 —— 127</td>
|
||
<td>24bit</td>
|
||
<td>1.0.0.0 —— 127.255.255.255</td>
|
||
</tr>
|
||
<tr>
|
||
<td>B 类</td>
|
||
<td>16bit,前两位固定为 10</td>
|
||
<td>128.0 —— 191.255</td>
|
||
<td>16bit</td>
|
||
<td>128.0.0.0 —— 191.255.255.255</td>
|
||
</tr>
|
||
<tr>
|
||
<td>C 类</td>
|
||
<td>24bit,前三位固定为 110</td>
|
||
<td>192.0.0 —— 223.255.255</td>
|
||
<td>8bit</td>
|
||
<td>192.0.0.0 —— 223.255.255.255</td>
|
||
</tr>
|
||
<tr>
|
||
<td>D 类</td>
|
||
<td>前四位固定为 1110,后面为多播地址</td>
|
||
<td></td>
|
||
<td></td>
|
||
<td></td>
|
||
</tr>
|
||
<tr>
|
||
<td>E 类</td>
|
||
<td>前五位固定为 11110,后面保留为今后所用</td>
|
||
<td></td>
|
||
<td></td>
|
||
<td></td>
|
||
</tr>
|
||
</tbody></table>
|
||
<p>应用:</p>
|
||
<ul>
|
||
<li><p>PING(Packet InterNet Groper,分组网间探测)测试两个主机之间的连通性</p>
|
||
</li>
|
||
<li><ul>
|
||
<li></li>
|
||
<li><ul>
|
||
<li>TTL(Time To Live,生存时间)该字段指定 IP 包被路由器丢弃之前允许通过的最大网段数量</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<h4 id="内部网关协议"><a href="#内部网关协议" class="headerlink" title="内部网关协议"></a>内部网关协议</h4><ul>
|
||
<li>RIP(Routing Information Protocol,路由信息协议)</li>
|
||
<li>OSPF(Open Sortest Path First,开放最短路径优先)</li>
|
||
</ul>
|
||
<h4 id="外部网关协议"><a href="#外部网关协议" class="headerlink" title="外部网关协议"></a>外部网关协议</h4><ul>
|
||
<li>BGP(Border Gateway Protocol,边界网关协议)</li>
|
||
</ul>
|
||
<h4 id="IP多播"><a href="#IP多播" class="headerlink" title="IP多播"></a>IP多播</h4><ul>
|
||
<li>IGMP(Internet Group Management Protocol,网际组管理协议)</li>
|
||
<li>多播路由选择协议</li>
|
||
</ul>
|
||
<h4 id="VPN-和-NAT"><a href="#VPN-和-NAT" class="headerlink" title="VPN 和 NAT"></a>VPN 和 NAT</h4><ul>
|
||
<li>VPN(Virtual Private Network,虚拟专用网)</li>
|
||
<li>NAT(Network Address Translation,网络地址转换)</li>
|
||
</ul>
|
||
<h4 id="路由表包含什么?"><a href="#路由表包含什么?" class="headerlink" title="路由表包含什么?"></a>路由表包含什么?</h4><ol>
|
||
<li>网络 ID(Network ID, Network number):就是目标地址的网络 ID。</li>
|
||
<li>子网掩码(subnet mask):用来判断 IP 所属网络</li>
|
||
<li>下一跳地址/接口(Next hop / interface):就是数据在发送到目标地址的旅途中下一站的地址。其中 interface 指向 next hop(即为下一个 route)。一个自治系统(AS, Autonomous system)中的 route 应该包含区域内所有的子网络,而默认网关(Network id: <code>0.0.0.0</code>, Netmask: <code>0.0.0.0</code>)指向自治系统的出口。</li>
|
||
</ol>
|
||
<p>根据应用和执行的不同,路由表可能含有如下附加信息:</p>
|
||
<ol>
|
||
<li>花费(Cost):就是数据发送过程中通过路径所需要的花费。</li>
|
||
<li>路由的服务质量</li>
|
||
<li>路由中需要过滤的出/入连接列表</li>
|
||
</ol>
|
||
<h3 id="运输层"><a href="#运输层" class="headerlink" title="运输层"></a>运输层</h3><p>协议:</p>
|
||
<ul>
|
||
<li>TCP(Transmission Control Protocol,传输控制协议)</li>
|
||
<li>UDP(User Datagram Protocol,用户数据报协议)</li>
|
||
</ul>
|
||
<p>端口:</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>应用程序</th>
|
||
<th>FTP</th>
|
||
<th>TELNET</th>
|
||
<th>SMTP</th>
|
||
<th>DNS</th>
|
||
<th>TFTP</th>
|
||
<th>HTTP</th>
|
||
<th>HTTPS</th>
|
||
<th>SNMP</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>端口号</td>
|
||
<td>21</td>
|
||
<td>23</td>
|
||
<td>25</td>
|
||
<td>53</td>
|
||
<td>69</td>
|
||
<td>80</td>
|
||
<td>443</td>
|
||
<td>161</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<p>受限于公众号文章字数限制,后续部分请看【<strong>今天的第二篇推文</strong>】,</p>
|
||
<h4 id="TCP"><a href="#TCP" class="headerlink" title="TCP"></a>TCP</h4><ul>
|
||
<li>TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,其传输的单位是报文段。</li>
|
||
</ul>
|
||
<p>特征:</p>
|
||
<ul>
|
||
<li>面向连接</li>
|
||
<li>只能点对点(一对一)通信</li>
|
||
<li>可靠交互</li>
|
||
<li>全双工通信</li>
|
||
<li>面向字节流</li>
|
||
</ul>
|
||
<p>TCP 如何保证可靠传输:</p>
|
||
<ul>
|
||
<li>确认和超时重传</li>
|
||
<li>数据合理分片和排序</li>
|
||
<li>流量控制</li>
|
||
<li>拥塞控制</li>
|
||
<li>数据校验</li>
|
||
</ul>
|
||
<p>TCP 首部</p>
|
||
<p>TCP:状态控制码(Code,Control Flag),占 6 比特,含义如下:</p>
|
||
<ul>
|
||
<li>URG:紧急比特(urgent),当 <code>URG=1</code> 时,表明紧急指针字段有效,代表该封包为紧急封包。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据), 且上图中的 Urgent Pointer 字段也会被启用。</li>
|
||
<li>ACK:确认比特(Acknowledge)。只有当 <code>ACK=1</code> 时确认号字段才有效,代表这个封包为确认封包。当 <code>ACK=0</code> 时,确认号无效。</li>
|
||
<li>PSH:(Push function)若为 1 时,代表要求对方立即传送缓冲区内的其他对应封包,而无需等缓冲满了才送。</li>
|
||
<li>RST:复位比特(Reset),当 <code>RST=1</code> 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。</li>
|
||
<li>SYN:同步比特(Synchronous),SYN 置为 1,就表示这是一个连接请求或连接接受报文,通常带有 SYN 标志的封包表示『主动』要连接到对方的意思。</li>
|
||
<li>FIN:终止比特(Final),用来释放一个连接。当 <code>FIN=1</code> 时,表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。</li>
|
||
</ul>
|
||
<h4 id="UDP"><a href="#UDP" class="headerlink" title="UDP"></a>UDP</h4><ul>
|
||
<li>UDP(User Datagram Protocol,用户数据报协议)是 OSI(Open System Interconnection 开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,其传输的单位是用户数据报。</li>
|
||
</ul>
|
||
<p>特征:</p>
|
||
<ul>
|
||
<li>无连接</li>
|
||
<li>尽最大努力交付</li>
|
||
<li>面向报文</li>
|
||
<li>没有拥塞控制</li>
|
||
<li>支持一对一、一对多、多对一、多对多的交互通信</li>
|
||
<li>首部开销小</li>
|
||
</ul>
|
||
<h4 id="TCP-与-UDP-的区别"><a href="#TCP-与-UDP-的区别" class="headerlink" title="TCP 与 UDP 的区别"></a>TCP 与 UDP 的区别</h4><ol>
|
||
<li>TCP 面向连接,UDP 是无连接的;</li>
|
||
<li>TCP 提供可靠的服务,也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付</li>
|
||
<li>TCP 的逻辑通信信道是全双工的可靠信道;UDP 则是不可靠信道</li>
|
||
<li>每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和多对多的交互通信</li>
|
||
<li>TCP 面向字节流(可能出现黏包问题),实际上是 TCP 把数据看成一连串无结构的字节流;UDP 是面向报文的(不会出现黏包问题)</li>
|
||
<li>UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 IP 电话,实时视频会议等)</li>
|
||
<li>TCP 首部开销20字节;UDP 的首部开销小,只有 8 个字节</li>
|
||
</ol>
|
||
<h4 id="TCP-黏包问题"><a href="#TCP-黏包问题" class="headerlink" title="TCP 黏包问题"></a>TCP 黏包问题</h4><h5 id="原因-2"><a href="#原因-2" class="headerlink" title="原因"></a>原因</h5><p>TCP 是一个基于字节流的传输服务(UDP 基于报文的),“流” 意味着 TCP 所传输的数据是没有边界的。所以可能会出现两个数据包黏在一起的情况。</p>
|
||
<h5 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h5><ul>
|
||
<li>发送定长包。如果每个消息的大小都是一样的,那么在接收对等方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息。</li>
|
||
<li>包头加上包体长度。包头是定长的 4 个字节,说明了包体的长度。接收对等方先接收包头长度,依据包头长度来接收包体。</li>
|
||
<li>在数据包之间设置边界,如添加特殊符号 <code>\r\n</code> 标记。FTP 协议正是这么做的。但问题在于如果数据正文中也含有 <code>\r\n</code>,则会误判为消息的边界。</li>
|
||
<li>使用更加复杂的应用层协议。</li>
|
||
</ul>
|
||
<h4 id="TCP-流量控制"><a href="#TCP-流量控制" class="headerlink" title="TCP 流量控制"></a>TCP 流量控制</h4><h5 id="概念-3"><a href="#概念-3" class="headerlink" title="概念"></a>概念</h5><p>流量控制(flow control)就是让发送方的发送速率不要太快,要让接收方来得及接收。</p>
|
||
<h5 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h5><h4 id="利用可变窗口进行流量控制"><a href="#利用可变窗口进行流量控制" class="headerlink" title="利用可变窗口进行流量控制"></a>利用可变窗口进行流量控制</h4><h4 id="TCP-拥塞控制"><a href="#TCP-拥塞控制" class="headerlink" title="TCP 拥塞控制"></a>TCP 拥塞控制</h4><h5 id="概念-4"><a href="#概念-4" class="headerlink" title="概念"></a>概念</h5><p>拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。</p>
|
||
<h5 id="方法-1"><a href="#方法-1" class="headerlink" title="方法"></a>方法</h5><ul>
|
||
<li>慢开始( slow-start )</li>
|
||
<li>拥塞避免( congestion avoidance )</li>
|
||
<li>快重传( fast retransmit )</li>
|
||
<li>快恢复( fast recovery</li>
|
||
</ul>
|
||
<h4 id="TCP-传输连接管理"><a href="#TCP-传输连接管理" class="headerlink" title="TCP 传输连接管理"></a>TCP 传输连接管理</h4><blockquote>
|
||
<p>因为 TCP 三次握手建立连接、四次挥手释放连接很重要,所以附上《计算机网络(第 7 版)-谢希仁》书中对此章的详细描述:<a href="https://github.com/huihut/interview/blob/master/images/TCP-transport-connection-management.png">https://github.com/huihut/interview/blob/master/images/TCP-transport-connection-management.png</a></p>
|
||
</blockquote>
|
||
<h5 id="TCP-三次握手建立连接"><a href="#TCP-三次握手建立连接" class="headerlink" title="TCP 三次握手建立连接"></a>TCP 三次握手建立连接</h5><p>UDP 报文</p>
|
||
<p>【TCP 建立连接全过程解释】</p>
|
||
<ol>
|
||
<li>客户端发送 SYN 给服务器,说明客户端请求建立连接;</li>
|
||
<li>服务端收到客户端发的 SYN,并回复 SYN+ACK 给客户端(同意建立连接);</li>
|
||
<li>客户端收到服务端的 SYN+ACK 后,回复 ACK 给服务端(表示客户端收到了服务端发的同意报文);</li>
|
||
<li>服务端收到客户端的 ACK,连接已建立,可以数据传输。</li>
|
||
</ol>
|
||
<h5 id="TCP-为什么要进行三次握手?"><a href="#TCP-为什么要进行三次握手?" class="headerlink" title="TCP 为什么要进行三次握手?"></a>TCP 为什么要进行三次握手?</h5><p>【答案一】因为信道不可靠,而 TCP 想在不可靠信道上建立可靠地传输,那么三次通信是理论上的最小值。(而 UDP 则不需建立可靠传输,因此 UDP 不需要三次握手。)</p>
|
||
<blockquote>
|
||
<p>Google Groups . TCP 建立连接为什么是三次握手?{技术}{网络通信}</p>
|
||
</blockquote>
|
||
<p>【答案二】因为双方都需要确认对方收到了自己发送的序列号,确认过程最少要进行三次通信。</p>
|
||
<blockquote>
|
||
<p>知乎 . TCP 为什么是三次握手,而不是两次或四次?</p>
|
||
</blockquote>
|
||
<p>【答案三】为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。</p>
|
||
<blockquote>
|
||
<p>《计算机网络(第 7 版)-谢希仁》</p>
|
||
</blockquote>
|
||
<p>【TCP 释放连接全过程解释】</p>
|
||
<ol>
|
||
<li>客户端发送 FIN 给服务器,说明客户端不必发送数据给服务器了(请求释放从客户端到服务器的连接);</li>
|
||
<li>服务器接收到客户端发的 FIN,并回复 ACK 给客户端(同意释放从客户端到服务器的连接);</li>
|
||
<li>客户端收到服务端回复的 ACK,此时从客户端到服务器的连接已释放(但服务端到客户端的连接还未释放,并且客户端还可以接收数据);</li>
|
||
<li>服务端继续发送之前没发完的数据给客户端;</li>
|
||
<li>服务端发送 FIN+ACK 给客户端,说明服务端发送完了数据(请求释放从服务端到客户端的连接,就算没收到客户端的回复,过段时间也会自动释放);</li>
|
||
<li>客户端收到服务端的 FIN+ACK,并回复 ACK 给客户端(同意释放从服务端到客户端的连接);</li>
|
||
<li>服务端收到客户端的 ACK 后,释放从服务端到客户端的连接。</li>
|
||
</ol>
|
||
<h5 id="TCP-为什么要进行四次挥手?"><a href="#TCP-为什么要进行四次挥手?" class="headerlink" title="TCP 为什么要进行四次挥手?"></a>TCP 为什么要进行四次挥手?</h5><p>【问题一】TCP 为什么要进行四次挥手?/ 为什么 TCP 建立连接需要三次,而释放连接则需要四次?</p>
|
||
<p>【答案一】因为 TCP 是全双工模式,客户端请求关闭连接后,客户端向服务端的连接关闭(一二次挥手),服务端继续传输之前没传完的数据给客户端(数据传输),服务端向客户端的连接关闭(三四次挥手)。所以 TCP 释放连接时服务器的 ACK 和 FIN 是分开发送的(中间隔着数据传输),而 TCP 建立连接时服务器的 ACK 和 SYN 是一起发送的(第二次握手),所以 TCP 建立连接需要三次,而释放连接则需要四次。</p>
|
||
<p>【问题二】为什么 TCP 连接时可以 ACK 和 SYN 一起发送,而释放时则 ACK 和 FIN 分开发送呢?(ACK 和 FIN 分开是指第二次和第三次挥手)</p>
|
||
<p>【答案二】因为客户端请求释放时,服务器可能还有数据需要传输给客户端,因此服务端要先响应客户端 FIN 请求(服务端发送 ACK),然后数据传输,传输完成后,服务端再提出 FIN 请求(服务端发送 FIN);而连接时则没有中间的数据传输,因此连接时可以 ACK 和 SYN 一起发送。</p>
|
||
<p>【问题三】为什么客户端释放最后需要 TIME-WAIT 等待 2MSL 呢?</p>
|
||
<p>【答案三】</p>
|
||
<ol>
|
||
<li><p>为了保证客户端发送的最后一个 ACK 报文能够到达服务端。若未成功到达,则服务端超时重传 FIN+ACK 报文段,客户端再重传 ACK,并重新计时。</p>
|
||
</li>
|
||
<li><p>防止已失效的连接请求报文段出现在本连接中。TIME-WAIT 持续 2MSL 可使本连接持续的时间内所产生的所有报文段都从网络中消失,这样可使下次连接中不会出现旧的连接报文段。</p>
|
||
</li>
|
||
</ol>
|
||
<h3 id="应用层"><a href="#应用层" class="headerlink" title="应用层"></a>应用层</h3><h4 id="DNS"><a href="#DNS" class="headerlink" title="DNS"></a>DNS</h4><ul>
|
||
<li>DNS(Domain Name System,域名系统)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS 使用 TCP 和 UDP 端口 53。当前,对于每一级域名长度的限制是 63 个字符,域名总长度则不能超过 253 个字符。</li>
|
||
</ul>
|
||
<p>域名:</p>
|
||
<ul>
|
||
<li><code>域名 ::= {<三级域名>.<二级域名>.<顶级域名>}</code>,如:<code>blog.huihut.com</code></li>
|
||
</ul>
|
||
<h4 id="FTP"><a href="#FTP" class="headerlink" title="FTP"></a>FTP</h4><ul>
|
||
<li>FTP(File Transfer Protocol,文件传输协议)是用于在网络上进行文件传输的一套标准协议,使用客户/服务器模式,使用 TCP 数据报,提供交互式访问,双向传输。</li>
|
||
<li>TFTP(Trivial File Transfer Protocol,简单文件传输协议)一个小且易实现的文件传输协议,也使用客户-服务器方式,使用UDP数据报,只支持文件传输而不支持交互,没有列目录,不能对用户进行身份鉴定</li>
|
||
</ul>
|
||
<h4 id="TELNET"><a href="#TELNET" class="headerlink" title="TELNET"></a>TELNET</h4><ul>
|
||
<li>TELNET 协议是 TCP/IP 协议族中的一员,是 Internet 远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。</li>
|
||
<li>HTTP(HyperText Transfer Protocol,超文本传输协议)是用于从 WWW(World Wide Web,万维网)服务器传输超文本到本地浏览器的传送协议。</li>
|
||
<li>SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。</li>
|
||
<li>Socket 建立网络通信连接至少要一对端口号(Socket)。Socket 本质是编程接口(API),对 TCP/IP 的封装,TCP/IP 也要提供可供程序员做网络开发所用的接口,这就是 Socket 编程接口。</li>
|
||
</ul>
|
||
<h4 id="WWW"><a href="#WWW" class="headerlink" title="WWW"></a>WWW</h4><ul>
|
||
<li>WWW(World Wide Web,环球信息网,万维网)是一个由许多互相链接的超文本组成的系统,通过互联网访问</li>
|
||
</ul>
|
||
<h5 id="URL"><a href="#URL" class="headerlink" title="URL"></a>URL</h5><ul>
|
||
<li>URL(Uniform Resource Locator,统一资源定位符)是因特网上标准的资源的地址(Address)</li>
|
||
</ul>
|
||
<p>标准格式:</p>
|
||
<ul>
|
||
<li><code>协议类型:[//服务器地址[:端口号]][/资源层级UNIX文件路径]文件名[?查询][#片段ID]</code></li>
|
||
</ul>
|
||
<p>完整格式:</p>
|
||
<ul>
|
||
<li><code>协议类型:[//[访问资源需要的凭证信息@]服务器地址[:端口号]][/资源层级UNIX文件路径]文件名[?查询][#片段ID]</code></li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>其中【访问凭证信息@;:端口号;?查询;#片段ID】都属于选填项<br>如:<code>https://github.com/huihut/interview#cc</code></p>
|
||
</blockquote>
|
||
<h5 id="HTTP"><a href="#HTTP" class="headerlink" title="HTTP"></a>HTTP</h5><p>HTTP(HyperText Transfer Protocol,超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是万维网的数据通信的基础。</p>
|
||
<p>请求方法</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th align="left">方法</th>
|
||
<th align="left">意义</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td align="left">OPTIONS</td>
|
||
<td align="left">请求一些选项信息,允许客户端查看服务器的性能</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="left">GET</td>
|
||
<td align="left">请求指定的页面信息,并返回实体主体</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="left">HEAD</td>
|
||
<td align="left">类似于 get 请求,只不过返回的响应中没有具体的内容,用于获取报头</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="left">POST</td>
|
||
<td align="left">向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="left">PUT</td>
|
||
<td align="left">从客户端向服务器传送的数据取代指定的文档的内容</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="left">DELETE</td>
|
||
<td align="left">请求服务器删除指定的页面</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="left">TRACE</td>
|
||
<td align="left">回显服务器收到的请求,主要用于测试或诊断</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<p>状态码(Status-Code)</p>
|
||
<ul>
|
||
<li><p>1xx:表示通知信息,如请求收到了或正在进行处理</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>100 Continue:继续,客户端应继续其请求</li>
|
||
<li>101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到 HTTP 的新版本协议</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>2xx:表示成功,如接收或知道了</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>200 OK: 请求成功</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>3xx:表示重定向,如要完成请求还必须采取进一步的行动</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>301 Moved Permanently: 永久移动。请求的资源已被永久的移动到新 URL,返回信息会包括新的 URL,浏览器会自动定向到新 URL。今后任何新的请求都应使用新的 URL 代替</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>4xx:表示客户的差错,如请求中有错误的语法或不能完成</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>400 Bad Request: 客户端请求的语法错误,服务器无法理解</li>
|
||
<li>401 Unauthorized: 请求要求用户的身份认证</li>
|
||
<li>403 Forbidden: 服务器理解请求客户端的请求,但是拒绝执行此请求(权限不够)</li>
|
||
<li>404 Not Found: 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置 “您所请求的资源无法找到” 的个性页面</li>
|
||
<li>408 Request Timeout: 服务器等待客户端发送的请求时间过长,超时</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>5xx:表示服务器的差错,如服务器失效无法完成请求</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>500 Internal Server Error: 服务器内部错误,无法完成请求</li>
|
||
<li>503 Service Unavailable: 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的 Retry-After 头信息中</li>
|
||
<li>504 Gateway Timeout: 充当网关或代理的服务器,未及时从远端服务器获取请求</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>更多状态码:菜鸟教程 . HTTP状态码</p>
|
||
</blockquote>
|
||
<h5 id="其他协议"><a href="#其他协议" class="headerlink" title="其他协议"></a>其他协议</h5><ul>
|
||
<li><p>SMTP(Simple Main Transfer Protocol,简单邮件传输协议)是在 Internet 传输 Email 的标准,是一个相对简单的基于文本的协议。在其之上指定了一条消息的一个或多个接收者(在大多数情况下被确认是存在的),然后消息文本会被传输。可以很简单地通过 Telnet 程序来测试一个 SMTP 服务器。SMTP 使用 TCP 端口 25。</p>
|
||
</li>
|
||
<li><p>DHCP(Dynamic Host Configuration Protocol,动态主机设置协议)是一个局域网的网络协议,使用 UDP 协议工作,主要有两个用途:</p>
|
||
</li>
|
||
<li><ul>
|
||
<li>用于内部网络或网络服务供应商自动分配 IP 地址给用户</li>
|
||
<li>用于内部网络管理员作为对所有电脑作中央管理的手段</li>
|
||
</ul>
|
||
</li>
|
||
<li><p>SNMP(Simple Network Management Protocol,简单网络管理协议)构成了互联网工程工作小组(IETF,Internet Engineering Task Force)定义的 Internet 协议族的一部分。该协议能够支持网络管理系统,用以监测连接到网络上的设备是否有任何引起管理上关注的情况。</p>
|
||
</li>
|
||
</ul>
|
||
<h2 id="网络编程"><a href="#网络编程" class="headerlink" title="网络编程"></a>网络编程</h2><h3 id="Socket"><a href="#Socket" class="headerlink" title="Socket"></a>Socket</h3><h4 id="Socket-中的-read-、write-函数"><a href="#Socket-中的-read-、write-函数" class="headerlink" title="Socket 中的 read()、write() 函数"></a>Socket 中的 read()、write() 函数</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">ssize_t read(int fd, void *buf, size_t count);</span><br><span class="line">ssize_t write(int fd, const void *buf, size_t count);</span><br></pre></td></tr></table></figure>
|
||
|
||
<h5 id="read"><a href="#read" class="headerlink" title="read()"></a>read()</h5><ul>
|
||
<li>read 函数是负责从 fd 中读取内容。</li>
|
||
<li>当读成功时,read 返回实际所读的字节数。</li>
|
||
<li>如果返回的值是 0 表示已经读到文件的结束了,小于 0 表示出现了错误。</li>
|
||
<li>如果错误为 EINTR 说明读是由中断引起的;如果是 ECONNREST 表示网络连接出了问题。</li>
|
||
</ul>
|
||
<h5 id="write"><a href="#write" class="headerlink" title="write()"></a>write()</h5><ul>
|
||
<li>write 函数将 buf 中的 nbytes 字节内容写入文件描述符 fd。</li>
|
||
<li>成功时返回写的字节数。失败时返回 -1,并设置 errno 变量。</li>
|
||
<li>在网络程序中,当我们向套接字文件描述符写时有俩种可能。</li>
|
||
<li>(1)write 的返回值大于 0,表示写了部分或者是全部的数据。</li>
|
||
<li>(2)返回的值小于 0,此时出现了错误。</li>
|
||
<li>如果错误为 EINTR 表示在写的时候出现了中断错误;如果为 EPIPE 表示网络连接出现了问题(对方已经关闭了连接)。</li>
|
||
</ul>
|
||
<h4 id="Socket-中-TCP-的三次握手建立连接"><a href="#Socket-中-TCP-的三次握手建立连接" class="headerlink" title="Socket 中 TCP 的三次握手建立连接"></a>Socket 中 TCP 的三次握手建立连接</h4><p>我们知道 TCP 建立连接要进行 “三次握手”,即交换三个分组。大致流程如下:</p>
|
||
<ol>
|
||
<li>客户端向服务器发送一个 SYN J</li>
|
||
<li>服务器向客户端响应一个 SYN K,并对 SYN J 进行确认 ACK J+1</li>
|
||
<li>客户端再想服务器发一个确认 ACK K+1</li>
|
||
</ol>
|
||
<p>只有就完了三次握手,但是这个三次握手发生在 Socket 的那几个函数中呢?请看下图:</p>
|
||
<p><img src="/posts/3189/images/670.webp" alt="图片">socket 中发送的 TCP 三次握手</p>
|
||
<p>从图中可以看出:</p>
|
||
<ol>
|
||
<li>当客户端调用 connect 时,触发了连接请求,向服务器发送了 SYN J 包,这时 connect 进入阻塞状态;</li>
|
||
<li>服务器监听到连接请求,即收到 SYN J 包,调用 accept 函数接收请求向客户端发送 SYN K ,ACK J+1,这时 accept 进入阻塞状态;</li>
|
||
<li>客户端收到服务器的 SYN K ,ACK J+1 之后,这时 connect 返回,并对 SYN K 进行确认;</li>
|
||
<li>服务器收到 ACK K+1 时,accept 返回,至此三次握手完毕,连接建立。</li>
|
||
</ol>
|
||
<h4 id="Socket-中-TCP-的四次握手释放连接"><a href="#Socket-中-TCP-的四次握手释放连接" class="headerlink" title="Socket 中 TCP 的四次握手释放连接"></a>Socket 中 TCP 的四次握手释放连接</h4><p>上面介绍了 socket 中 TCP 的三次握手建立过程,及其涉及的 socket 函数。现在我们介绍 socket 中的四次握手释放连接的过程,请看下图:</p>
|
||
<p><img src="/posts/3189/images/680.webp" alt="图片">socket 中发送的 TCP 四次握手</p>
|
||
<p>图示过程如下:</p>
|
||
<ol>
|
||
<li>某个应用进程首先调用 close 主动关闭连接,这时 TCP 发送一个 FIN M;</li>
|
||
<li>另一端接收到 FIN M 之后,执行被动关闭,对这个 FIN 进行确认。它的接收也作为文件结束符传递给应用进程,因为 FIN 的接收意味着应用进程在相应的连接上再也接收不到额外数据;</li>
|
||
<li>一段时间之后,接收到文件结束符的应用进程调用 close 关闭它的 socket。这导致它的 TCP 也发送一个 FIN N;</li>
|
||
<li>接收到这个 FIN 的源发送端 TCP 对它进行确认。</li>
|
||
</ol>
|
||
<p>这样每个方向上都有一个 FIN 和 ACK。</p>
|
||
<h2 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h2><ul>
|
||
<li>数据库事务四大特性:原子性、一致性、分离性、持久性</li>
|
||
<li>数据库索引:顺序索引、B+ 树索引、hash 索引<br>MySQL 索引背后的数据结构及算法原理</li>
|
||
<li>SQL 约束 (Constraints)</li>
|
||
</ul>
|
||
<h3 id="范式"><a href="#范式" class="headerlink" title="范式"></a>范式</h3><ul>
|
||
<li>第一范式(1NF):属性(字段)是最小单位不可再分</li>
|
||
<li>第二范式(2NF):满足 1NF,每个非主属性完全依赖于主键(消除 1NF 非主属性对码的部分函数依赖)</li>
|
||
<li>第三范式(3NF):满足 2NF,任何非主属性不依赖于其他非主属性(消除 2NF 主属性对码的传递函数依赖)</li>
|
||
<li>鲍依斯-科得范式(BCNF):满足 3NF,任何非主属性不能对主键子集依赖(消除 3NF 主属性对码的部分和传递函数依赖)</li>
|
||
<li>第四范式(4NF):满足 3NF,属性之间不能有非平凡且非函数依赖的多值依赖(消除 3NF 非平凡且非函数依赖的多值依赖)</li>
|
||
</ul>
|
||
<h2 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h2><blockquote>
|
||
<p>各大设计模式例子参考:CSDN专栏 . C++ 设计模式 系列博文</p>
|
||
</blockquote>
|
||
<p>设计模式工程目录</p>
|
||
<h3 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h3><p>单例模式例子</p>
|
||
<h3 id="抽象工厂模式"><a href="#抽象工厂模式" class="headerlink" title="抽象工厂模式"></a>抽象工厂模式</h3><p>抽象工厂模式例子</p>
|
||
<h3 id="适配器模式"><a href="#适配器模式" class="headerlink" title="适配器模式"></a>适配器模式</h3><p>适配器模式例子</p>
|
||
<h3 id="桥接模式"><a href="#桥接模式" class="headerlink" title="桥接模式"></a>桥接模式</h3><p>桥接模式例子</p>
|
||
<h3 id="观察者模式"><a href="#观察者模式" class="headerlink" title="观察者模式"></a>观察者模式</h3><p>观察者模式例子</p>
|
||
<h3 id="设计模式的六大原则"><a href="#设计模式的六大原则" class="headerlink" title="设计模式的六大原则"></a>设计模式的六大原则</h3><ul>
|
||
<li>单一职责原则(SRP,Single Responsibility Principle)</li>
|
||
<li>里氏替换原则(LSP,Liskov Substitution Principle)</li>
|
||
<li>依赖倒置原则(DIP,Dependence Inversion Principle)</li>
|
||
<li>接口隔离原则(ISP,Interface Segregation Principle)</li>
|
||
<li>迪米特法则(LoD,Law of Demeter)</li>
|
||
<li>开放封闭原则(OCP,Open Close Principle)</li>
|
||
</ul>
|
||
<h2 id="链接装载库"><a href="#链接装载库" class="headerlink" title="链接装载库"></a>链接装载库</h2><h3 id="内存、栈、堆"><a href="#内存、栈、堆" class="headerlink" title="内存、栈、堆"></a>内存、栈、堆</h3><p>一般应用程序内存空间有如下区域:</p>
|
||
<ul>
|
||
<li>栈:由操作系统自动分配释放,存放函数的参数值、局部变量等的值,用于维护函数调用的上下文</li>
|
||
<li>堆:一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收,用来容纳应用程序动态分配的内存区域</li>
|
||
<li>可执行文件映像:存储着可执行文件在内存中的映像,由装载器装载是将可执行文件的内存读取或映射到这里</li>
|
||
<li>保留区:保留区并不是一个单一的内存区域,而是对内存中受到保护而禁止访问的内存区域的总称,如通常 C 语言讲无效指针赋值为 0(NULL),因此 0 地址正常情况下不可能有效的访问数据</li>
|
||
</ul>
|
||
<h4 id="栈"><a href="#栈" class="headerlink" title="栈"></a>栈</h4><p>栈保存了一个函数调用所需要的维护信息,常被称为堆栈帧(Stack Frame)或活动记录(Activate Record),一般包含以下几方面:</p>
|
||
<ul>
|
||
<li>函数的返回地址和参数</li>
|
||
<li>临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量</li>
|
||
<li>保存上下文:包括函数调用前后需要保持不变的寄存器</li>
|
||
</ul>
|
||
<h4 id="堆"><a href="#堆" class="headerlink" title="堆"></a>堆</h4><p>堆分配算法:</p>
|
||
<ul>
|
||
<li>空闲链表(Free List)</li>
|
||
<li>位图(Bitmap)</li>
|
||
<li>对象池</li>
|
||
</ul>
|
||
<h4 id="“段错误(segment-fault)”-或-“非法操作,该内存地址不能-read-write”"><a href="#“段错误(segment-fault)”-或-“非法操作,该内存地址不能-read-write”" class="headerlink" title="“段错误(segment fault)” 或 “非法操作,该内存地址不能 read/write”"></a>“段错误(segment fault)” 或 “非法操作,该内存地址不能 read/write”</h4><p>典型的非法指针解引用造成的错误。当指针指向一个不允许读写的内存地址,而程序却试图利用指针来读或写该地址时,会出现这个错误。</p>
|
||
<p>普遍原因:</p>
|
||
<ul>
|
||
<li>将指针初始化为 NULL,之后没有给它一个合理的值就开始使用指针</li>
|
||
<li>没用初始化栈中的指针,指针的值一般会是随机数,之后就直接开始使用指针</li>
|
||
</ul>
|
||
<h3 id="编译链接"><a href="#编译链接" class="headerlink" title="编译链接"></a>编译链接</h3><h4 id="各平台文件格式"><a href="#各平台文件格式" class="headerlink" title="各平台文件格式"></a>各平台文件格式</h4><table>
|
||
<thead>
|
||
<tr>
|
||
<th>平台</th>
|
||
<th>可执行文件</th>
|
||
<th>目标文件</th>
|
||
<th>动态库/共享对象</th>
|
||
<th>静态库</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>Windows</td>
|
||
<td>exe</td>
|
||
<td>obj</td>
|
||
<td>dll</td>
|
||
<td>lib</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Unix/Linux</td>
|
||
<td>ELF、out</td>
|
||
<td>o</td>
|
||
<td>so</td>
|
||
<td>a</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Mac</td>
|
||
<td>Mach-O</td>
|
||
<td>o</td>
|
||
<td>dylib、tbd、framework</td>
|
||
<td>a、framework</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h4 id="编译链接过程"><a href="#编译链接过程" class="headerlink" title="编译链接过程"></a>编译链接过程</h4><ol>
|
||
<li>预编译(预编译器处理如 <code>#include</code>、<code>#define</code> 等预编译指令,生成 <code>.i</code> 或 <code>.ii</code> 文件)</li>
|
||
<li>编译(编译器进行词法分析、语法分析、语义分析、中间代码生成、目标代码生成、优化,生成 <code>.s</code> 文件)</li>
|
||
<li>汇编(汇编器把汇编码翻译成机器码,生成 <code>.o</code> 文件)</li>
|
||
<li>链接(连接器进行地址和空间分配、符号决议、重定位,生成 <code>.out</code> 文件)</li>
|
||
</ol>
|
||
<blockquote>
|
||
<p>现在版本 GCC 把预编译和编译合成一步,预编译编译程序 cc1、汇编器 as、连接器 ld</p>
|
||
<p>MSVC 编译环境,编译器 cl、连接器 link、可执行文件查看器 dumpbin</p>
|
||
</blockquote>
|
||
<h4 id="目标文件"><a href="#目标文件" class="headerlink" title="目标文件"></a>目标文件</h4><p>编译器编译源代码后生成的文件叫做目标文件。目标文件从结构上讲,它是已经编译后的可执行文件格式,只是还没有经过链接的过程,其中可能有些符号或有些地址还没有被调整。</p>
|
||
<blockquote>
|
||
<p>可执行文件(Windows 的 <code>.exe</code> 和 Linux 的 <code>ELF</code>)、动态链接库(Windows 的 <code>.dll</code> 和 Linux 的 <code>.so</code>)、静态链接库(Windows 的 <code>.lib</code> 和 Linux 的 <code>.a</code>)都是按照可执行文件格式存储(Windows 按照 PE-COFF,Linux 按照 ELF)</p>
|
||
</blockquote>
|
||
<h5 id="目标文件格式"><a href="#目标文件格式" class="headerlink" title="目标文件格式"></a>目标文件格式</h5><ul>
|
||
<li>Windows 的 PE(Portable Executable),或称为 PE-COFF,<code>.obj</code> 格式</li>
|
||
<li>Linux 的 ELF(Executable Linkable Format),<code>.o</code> 格式</li>
|
||
<li>Intel/Microsoft 的 OMF(Object Module Format)</li>
|
||
<li>Unix 的 <code>a.out</code> 格式</li>
|
||
<li>MS-DOS 的 <code>.COM</code> 格式</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>PE 和 ELF 都是 COFF(Common File Format)的变种</p>
|
||
</blockquote>
|
||
<h5 id="目标文件存储结构"><a href="#目标文件存储结构" class="headerlink" title="目标文件存储结构"></a>目标文件存储结构</h5><table>
|
||
<thead>
|
||
<tr>
|
||
<th>段</th>
|
||
<th>功能</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>File Header</td>
|
||
<td>文件头,描述整个文件的文件属性(包括文件是否可执行、是静态链接或动态连接及入口地址、目标硬件、目标操作系统等)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>.text section</td>
|
||
<td>代码段,执行语句编译成的机器代码</td>
|
||
</tr>
|
||
<tr>
|
||
<td>.data section</td>
|
||
<td>数据段,已初始化的全局变量和局部静态变量</td>
|
||
</tr>
|
||
<tr>
|
||
<td>.bss section</td>
|
||
<td>BSS 段(Block Started by Symbol),未初始化的全局变量和局部静态变量(因为默认值为 0,所以只是在此预留位置,不占空间)</td>
|
||
</tr>
|
||
<tr>
|
||
<td>.rodata section</td>
|
||
<td>只读数据段,存放只读数据,一般是程序里面的只读变量(如 const 修饰的变量)和字符串常量</td>
|
||
</tr>
|
||
<tr>
|
||
<td>.comment section</td>
|
||
<td>注释信息段,存放编译器版本信息</td>
|
||
</tr>
|
||
<tr>
|
||
<td>.note.GNU-stack section</td>
|
||
<td>堆栈提示段</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<blockquote>
|
||
<p>其他段略</p>
|
||
</blockquote>
|
||
<h4 id="链接的接口————符号"><a href="#链接的接口————符号" class="headerlink" title="链接的接口————符号"></a>链接的接口————符号</h4><p>在链接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。我们将函数和变量统称为符号(Symbol),函数名或变量名就是符号名(Symbol Name)。</p>
|
||
<p>如下符号表(Symbol Table):</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Symbol(符号名)</th>
|
||
<th>Symbol Value (地址)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>main</td>
|
||
<td>0x100</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Add</td>
|
||
<td>0x123</td>
|
||
</tr>
|
||
<tr>
|
||
<td>…</td>
|
||
<td>…</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h3 id="Linux-的共享库(Shared-Library)"><a href="#Linux-的共享库(Shared-Library)" class="headerlink" title="Linux 的共享库(Shared Library)"></a>Linux 的共享库(Shared Library)</h3><p>Linux 下的共享库就是普通的 ELF 共享对象。</p>
|
||
<p>共享库版本更新应该保证二进制接口 ABI(Application Binary Interface)的兼容</p>
|
||
<h4 id="命名"><a href="#命名" class="headerlink" title="命名"></a>命名</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">libname.so.x.y.z</span><br></pre></td></tr></table></figure>
|
||
|
||
<ul>
|
||
<li>x:主版本号,不同主版本号的库之间不兼容,需要重新编译</li>
|
||
<li>y:次版本号,高版本号向后兼容低版本号</li>
|
||
<li>z:发布版本号,不对接口进行更改,完全兼容</li>
|
||
</ul>
|
||
<h4 id="路径"><a href="#路径" class="headerlink" title="路径"></a>路径</h4><p>大部分包括 Linux 在内的开源系统遵循 FHS(File Hierarchy Standard)的标准,这标准规定了系统文件如何存放,包括各个目录结构、组织和作用。</p>
|
||
<ul>
|
||
<li><code>/lib</code>:存放系统最关键和最基础的共享库,如动态链接器、C 语言运行库、数学库等</li>
|
||
<li><code>/usr/lib</code>:存放非系统运行时所需要的关键性的库,主要是开发库</li>
|
||
<li><code>/usr/local/lib</code>:存放跟操作系统本身并不十分相关的库,主要是一些第三方应用程序的库</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>动态链接器会在 <code>/lib</code>、<code>/usr/lib</code> 和由 <code>/etc/ld.so.conf</code> 配置文件指定的,目录中查找共享库</p>
|
||
</blockquote>
|
||
<h4 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h4><ul>
|
||
<li><code>LD_LIBRARY_PATH</code>:临时改变某个应用程序的共享库查找路径,而不会影响其他应用程序</li>
|
||
<li><code>LD_PRELOAD</code>:指定预先装载的一些共享库甚至是目标文件</li>
|
||
<li><code>LD_DEBUG</code>:打开动态链接器的调试功能</li>
|
||
</ul>
|
||
<h4 id="so-共享库的编写"><a href="#so-共享库的编写" class="headerlink" title="so 共享库的编写"></a>so 共享库的编写</h4><h4 id="使用-CLion-编写共享库"><a href="#使用-CLion-编写共享库" class="headerlink" title="使用 CLion 编写共享库"></a>使用 CLion 编写共享库</h4><p>创建一个名为 MySharedLib 的共享库</p>
|
||
<p>CMakeLists.txt</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">cmake_minimum_required(VERSION 3.10)</span><br><span class="line">project(MySharedLib)</span><br><span class="line"></span><br><span class="line">set(CMAKE_CXX_STANDARD 11)</span><br><span class="line"></span><br><span class="line">add_library(MySharedLib SHARED library.cpp library.h)</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>library.h</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#ifndef MYSHAREDLIB_LIBRARY_H</span><br><span class="line">#define MYSHAREDLIB_LIBRARY_H</span><br><span class="line"></span><br><span class="line">// 打印 Hello World!</span><br><span class="line">void hello();</span><br><span class="line"></span><br><span class="line">// 使用可变模版参数求和</span><br><span class="line">template <typename T></span><br><span class="line">T sum(T t)</span><br><span class="line">{</span><br><span class="line"> return t;</span><br><span class="line">}</span><br><span class="line">template <typename T, typename ...Types></span><br><span class="line">T sum(T first, Types ... rest)</span><br><span class="line">{</span><br><span class="line"> return first + sum<T>(rest...);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">#endif</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>library.cpp</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#include <iostream></span><br><span class="line">#include "library.h"</span><br><span class="line"></span><br><span class="line">void hello() {</span><br><span class="line"> std::cout << "Hello, World!" << std::endl;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="so-共享库的使用(被可执行项目调用)"><a href="#so-共享库的使用(被可执行项目调用)" class="headerlink" title="so 共享库的使用(被可执行项目调用)"></a>so 共享库的使用(被可执行项目调用)</h4><h4 id="使用-CLion-调用共享库"><a href="#使用-CLion-调用共享库" class="headerlink" title="使用 CLion 调用共享库"></a>使用 CLion 调用共享库</h4><p>创建一个名为 TestSharedLib 的可执行项目</p>
|
||
<p>CMakeLists.txt</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">cmake_minimum_required(VERSION 3.10)</span><br><span class="line">project(TestSharedLib)</span><br><span class="line"></span><br><span class="line"># C++11 编译</span><br><span class="line">set(CMAKE_CXX_STANDARD 11)</span><br><span class="line"></span><br><span class="line"># 头文件路径</span><br><span class="line">set(INC_DIR /home/xx/code/clion/MySharedLib)</span><br><span class="line"># 库文件路径</span><br><span class="line">set(LIB_DIR /home/xx/code/clion/MySharedLib/cmake-build-debug)</span><br><span class="line"></span><br><span class="line">include_directories(${INC_DIR})</span><br><span class="line">link_directories(${LIB_DIR})</span><br><span class="line">link_libraries(MySharedLib)</span><br><span class="line"></span><br><span class="line">add_executable(TestSharedLib main.cpp)</span><br><span class="line"></span><br><span class="line"># 链接 MySharedLib 库</span><br><span class="line">target_link_libraries(TestSharedLib MySharedLib)</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>main.cpp</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#include <iostream></span><br><span class="line">#include "library.h"</span><br><span class="line">using std::cout;</span><br><span class="line">using std::endl;</span><br><span class="line"></span><br><span class="line">int main() {</span><br><span class="line"></span><br><span class="line"> hello();</span><br><span class="line"> cout << "1 + 2 = " << sum(1,2) << endl;</span><br><span class="line"> cout << "1 + 2 + 3 = " << sum(1,2,3) << endl;</span><br><span class="line"></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="code"><pre><span class="line">Hello, World!</span><br><span class="line">1 + 2 = 3</span><br><span class="line">1 + 2 + 3 = 6</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="Windows-应用程序入口函数"><a href="#Windows-应用程序入口函数" class="headerlink" title="Windows 应用程序入口函数"></a>Windows 应用程序入口函数</h3><ul>
|
||
<li>GUI(Graphical User Interface)应用,链接器选项:<code>/SUBSYSTEM:WINDOWS</code></li>
|
||
<li>CUI(Console User Interface)应用,链接器选项:<code>/SUBSYSTEM:CONSOLE</code></li>
|
||
</ul>
|
||
<h4 id="tWinMain-与-tmain-函数声明"><a href="#tWinMain-与-tmain-函数声明" class="headerlink" title="_tWinMain 与 _tmain 函数声明"></a>_tWinMain 与 _tmain 函数声明</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Int WINAPI _tWinMain(</span><br><span class="line"> HINSTANCE hInstanceExe,</span><br><span class="line"> HINSTANCE,</span><br><span class="line"> PTSTR pszCmdLine,</span><br><span class="line"> int nCmdShow);</span><br><span class="line"></span><br><span class="line">int _tmain(</span><br><span class="line"> int argc,</span><br><span class="line"> TCHAR *argv[],</span><br><span class="line"> TCHAR *envp[]);</span><br></pre></td></tr></table></figure>
|
||
|
||
|
||
|
||
|
||
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>应用程序类型</th>
|
||
<th>入口点函数</th>
|
||
<th>嵌入可执行文件的启动函数</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody><tr>
|
||
<td>处理ANSI字符(串)的GUI应用程序</td>
|
||
<td>_tWinMain(WinMain)</td>
|
||
<td>WinMainCRTSartup</td>
|
||
</tr>
|
||
<tr>
|
||
<td>处理Unicode字符(串)的GUI应用程序</td>
|
||
<td>_tWinMain(wWinMain)</td>
|
||
<td>wWinMainCRTSartup</td>
|
||
</tr>
|
||
<tr>
|
||
<td>处理ANSI字符(串)的CUI应用程序</td>
|
||
<td>_tmain(Main)</td>
|
||
<td>mainCRTSartup</td>
|
||
</tr>
|
||
<tr>
|
||
<td>处理Unicode字符(串)的CUI应用程序</td>
|
||
<td>_tmain(wMain)</td>
|
||
<td>wmainCRTSartup</td>
|
||
</tr>
|
||
<tr>
|
||
<td>动态链接库(Dynamic-Link Library)</td>
|
||
<td>DllMain</td>
|
||
<td>_DllMainCRTStartup</td>
|
||
</tr>
|
||
</tbody></table>
|
||
<h3 id="Windows-的动态链接库(Dynamic-Link-Library)"><a href="#Windows-的动态链接库(Dynamic-Link-Library)" class="headerlink" title="Windows 的动态链接库(Dynamic-Link Library)"></a>Windows 的动态链接库(Dynamic-Link Library)</h3><blockquote>
|
||
<p>知识点来自《Windows核心编程(第五版)》</p>
|
||
</blockquote>
|
||
<h4 id="用处"><a href="#用处" class="headerlink" title="用处"></a>用处</h4><ul>
|
||
<li>扩展了应用程序的特性</li>
|
||
<li>简化了项目管理</li>
|
||
<li>有助于节省内存</li>
|
||
<li>促进了资源的共享</li>
|
||
<li>促进了本地化</li>
|
||
<li>有助于解决平台间的差异</li>
|
||
<li>可以用于特殊目的</li>
|
||
</ul>
|
||
<h4 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h4><ul>
|
||
<li>创建 DLL,事实上是在创建可供一个可执行模块调用的函数</li>
|
||
<li>当一个模块提供一个内存分配函数(malloc、new)的时候,它必须同时提供另一个内存释放函数(free、delete)</li>
|
||
<li>在使用 C 和 C++ 混编的时候,要使用 extern “C” 修饰符</li>
|
||
<li>一个 DLL 可以导出函数、变量(避免导出)、C++ 类(导出导入需要同编译器,否则避免导出)</li>
|
||
<li>DLL 模块:cpp 文件中的 __declspec(dllexport) 写在 include 头文件之前</li>
|
||
<li>调用 DLL 的可执行模块:cpp 文件的 __declspec(dllimport) 之前不应该定义 MYLIBAPI</li>
|
||
</ul>
|
||
<h4 id="加载-Windows-程序的搜索顺序"><a href="#加载-Windows-程序的搜索顺序" class="headerlink" title="加载 Windows 程序的搜索顺序"></a>加载 Windows 程序的搜索顺序</h4><ol>
|
||
<li>包含可执行文件的目录</li>
|
||
<li>Windows 的系统目录,可以通过 GetSystemDirectory 得到</li>
|
||
<li>16 位的系统目录,即 Windows 目录中的 System 子目录</li>
|
||
<li>Windows 目录,可以通过 GetWindowsDirectory 得到</li>
|
||
<li>进程的当前目录</li>
|
||
<li>PATH 环境变量中所列出的目录</li>
|
||
</ol>
|
||
<h4 id="DLL-入口函数"><a href="#DLL-入口函数" class="headerlink" title="DLL 入口函数"></a>DLL 入口函数</h4><h4 id="DllMain-函数"><a href="#DllMain-函数" class="headerlink" title="DllMain 函数"></a>DllMain 函数</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)</span><br><span class="line">{</span><br><span class="line"> switch(fdwReason)</span><br><span class="line"> {</span><br><span class="line"> case DLL_PROCESS_ATTACH:</span><br><span class="line"> // 第一次将一个DLL映射到进程地址空间时调用</span><br><span class="line"> // The DLL is being mapped into the process' address space.</span><br><span class="line"> break;</span><br><span class="line"> case DLL_THREAD_ATTACH:</span><br><span class="line"> // 当进程创建一个线程的时候,用于告诉DLL执行与线程相关的初始化(非主线程执行)</span><br><span class="line"> // A thread is bing created.</span><br><span class="line"> break;</span><br><span class="line"> case DLL_THREAD_DETACH:</span><br><span class="line"> // 系统调用 ExitThread 线程退出前,即将终止的线程通过告诉DLL执行与线程相关的清理</span><br><span class="line"> // A thread is exiting cleanly.</span><br><span class="line"> break;</span><br><span class="line"> case DLL_PROCESS_DETACH:</span><br><span class="line"> // 将一个DLL从进程的地址空间时调用</span><br><span class="line"> // The DLL is being unmapped from the process' address space.</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> return (TRUE); // Used only for DLL_PROCESS_ATTACH</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="载入卸载库"><a href="#载入卸载库" class="headerlink" title="载入卸载库"></a>载入卸载库</h4><h4 id="FreeLibraryAndExitThread-函数声明"><a href="#FreeLibraryAndExitThread-函数声明" class="headerlink" title="FreeLibraryAndExitThread 函数声明"></a>FreeLibraryAndExitThread 函数声明</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// 载入库</span><br><span class="line">HMODULE WINAPI LoadLibrary(</span><br><span class="line"> _In_ LPCTSTR lpFileName</span><br><span class="line">);</span><br><span class="line">HMODULE LoadLibraryExA(</span><br><span class="line"> LPCSTR lpLibFileName,</span><br><span class="line"> HANDLE hFile,</span><br><span class="line"> DWORD dwFlags</span><br><span class="line">);</span><br><span class="line">// 若要在通用 Windows 平台(UWP)应用中加载 Win32 DLL,需要调用 LoadPackagedLibrary,而不是 LoadLibrary 或 LoadLibraryEx</span><br><span class="line">HMODULE LoadPackagedLibrary(</span><br><span class="line"> LPCWSTR lpwLibFileName,</span><br><span class="line"> DWORD Reserved</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">// 卸载库</span><br><span class="line">BOOL WINAPI FreeLibrary(</span><br><span class="line"> _In_ HMODULE hModule</span><br><span class="line">);</span><br><span class="line">// 卸载库和退出线程</span><br><span class="line">VOID WINAPI FreeLibraryAndExitThread(</span><br><span class="line"> _In_ HMODULE hModule,</span><br><span class="line"> _In_ DWORD dwExitCode</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="显示地链接到导出符号"><a href="#显示地链接到导出符号" class="headerlink" title="显示地链接到导出符号"></a>显示地链接到导出符号</h4><h4 id="GetProcAddress-函数声明"><a href="#GetProcAddress-函数声明" class="headerlink" title="GetProcAddress 函数声明"></a>GetProcAddress 函数声明</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">FARPROC GetProcAddress(</span><br><span class="line"> HMODULE hInstDll,</span><br><span class="line"> PCSTR pszSymbolName // 只能接受 ANSI 字符串,不能是 Unicode</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="DumpBin-exe-查看-DLL-信息"><a href="#DumpBin-exe-查看-DLL-信息" class="headerlink" title="DumpBin.exe 查看 DLL 信息"></a>DumpBin.exe 查看 DLL 信息</h4><p>在 <code>VS 的开发人员命令提示符</code> 使用 <code>DumpBin.exe</code> 可查看 DLL 库的导出段(导出的变量、函数、类名的符号)、相对虚拟地址(RVA,relative virtual address)。如:</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">DUMPBIN -exports D:\mydll.dll</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>DLL 头文件</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// MyLib.h</span><br><span class="line"></span><br><span class="line">#ifdef MYLIBAPI</span><br><span class="line"></span><br><span class="line">// MYLIBAPI 应该在全部 DLL 源文件的 include "Mylib.h" 之前被定义</span><br><span class="line">// 全部函数/变量正在被导出</span><br><span class="line"></span><br><span class="line">#else</span><br><span class="line"></span><br><span class="line">// 这个头文件被一个exe源代码模块包含,意味着全部函数/变量被导入</span><br><span class="line">#define MYLIBAPI extern "C" __declspec(dllimport)</span><br><span class="line"></span><br><span class="line">#endif</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">MYLIBAPI int g_nResult;</span><br><span class="line"></span><br><span class="line">// 定义导出函数原型</span><br><span class="line">MYLIBAPI int Add(int nLeft, int nRight);</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>DLL 源文件</p>
|
||
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// MyLibFile1.cpp</span><br><span class="line"></span><br><span class="line">// 包含标准Windows和C运行时头文件</span><br><span class="line">#include <windows.h></span><br><span class="line"></span><br><span class="line">// DLL源码文件导出的函数和变量</span><br><span class="line">#define MYLIBAPI extern "C" __declspec(dllexport)</span><br><span class="line"></span><br><span class="line">// 包含导出的数据结构、符号、函数、变量</span><br><span class="line">#include "MyLib.h"</span><br><span class="line"></span><br><span class="line">// 将此DLL源代码文件的代码放在此处</span><br><span class="line">int g_nResult;</span><br><span class="line"></span><br><span class="line">int Add(int nLeft, int nRight)</span><br><span class="line">{</span><br><span class="line"> g_nResult = nLeft + nRight;</span><br><span class="line"> return g_nResult;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h4 id="DLL-库的使用(运行时动态链接-DLL)"><a href="#DLL-库的使用(运行时动态链接-DLL)" class="headerlink" title="DLL 库的使用(运行时动态链接 DLL)"></a>DLL 库的使用(运行时动态链接 DLL)</h4><h4 id="DLL-库的使用(运行时动态链接-DLL)-1"><a href="#DLL-库的使用(运行时动态链接-DLL)-1" class="headerlink" title="DLL 库的使用(运行时动态链接 DLL)"></a>DLL 库的使用(运行时动态链接 DLL)</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">// A simple program that uses LoadLibrary and</span><br><span class="line">// GetProcAddress to access myPuts from Myputs.dll.</span><br><span class="line"></span><br><span class="line">#include <windows.h></span><br><span class="line">#include <stdio.h></span><br><span class="line"></span><br><span class="line">typedef int (__cdecl *MYPROC)(LPWSTR);</span><br><span class="line"></span><br><span class="line">int main( void )</span><br><span class="line">{</span><br><span class="line"> HINSTANCE hinstLib;</span><br><span class="line"> MYPROC ProcAdd;</span><br><span class="line"> BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;</span><br><span class="line"></span><br><span class="line"> // Get a handle to the DLL module.</span><br><span class="line"></span><br><span class="line"> hinstLib = LoadLibrary(TEXT("MyPuts.dll"));</span><br><span class="line"></span><br><span class="line"> // If the handle is valid, try to get the function address.</span><br><span class="line"></span><br><span class="line"> if (hinstLib != NULL)</span><br><span class="line"> {</span><br><span class="line"> ProcAdd = (MYPROC) GetProcAddress(hinstLib, "myPuts");</span><br><span class="line"></span><br><span class="line"> // If the function address is valid, call the function.</span><br><span class="line"></span><br><span class="line"> if (NULL != ProcAdd)</span><br><span class="line"> {</span><br><span class="line"> fRunTimeLinkSuccess = TRUE;</span><br><span class="line"> (ProcAdd) (L"Message sent to the DLL function\n");</span><br><span class="line"> }</span><br><span class="line"> // Free the DLL module.</span><br><span class="line"></span><br><span class="line"> fFreeResult = FreeLibrary(hinstLib);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // If unable to call the DLL function, use an alternative.</span><br><span class="line"> if (! fRunTimeLinkSuccess)</span><br><span class="line"> printf("Message printed from executable\n");</span><br><span class="line"></span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
|
||
|
||
<h3 id="运行库(Runtime-Library)"><a href="#运行库(Runtime-Library)" class="headerlink" title="运行库(Runtime Library)"></a>运行库(Runtime Library)</h3><h4 id="典型程序运行步骤"><a href="#典型程序运行步骤" class="headerlink" title="典型程序运行步骤"></a>典型程序运行步骤</h4><ol>
|
||
<li>操作系统创建进程,把控制权交给程序的入口(往往是运行库中的某个入口函数)</li>
|
||
<li>入口函数对运行库和程序运行环境进行初始化(包括堆、I/O、线程、全局变量构造等等)。</li>
|
||
<li>入口函数初始化后,调用 main 函数,正式开始执行程序主体部分。</li>
|
||
<li>main 函数执行完毕后,返回到入口函数进行清理工作(包括全局变量析构、堆销毁、关闭I/O等),然后进行系统调用结束进程。</li>
|
||
</ol>
|
||
<blockquote>
|
||
<p>一个程序的 I/O 指代程序与外界的交互,包括文件、管程、网络、命令行、信号等。更广义地讲,I/O 指代操作系统理解为 “文件” 的事物。</p>
|
||
</blockquote>
|
||
<h4 id="glibc-入口"><a href="#glibc-入口" class="headerlink" title="glibc 入口"></a>glibc 入口</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">_start -> __libc_start_main -> exit -> _exit</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>其中 <code>main(argc, argv, __environ)</code> 函数在 <code>__libc_start_main</code> 里执行。</p>
|
||
<h4 id="MSVC-CRT-入口"><a href="#MSVC-CRT-入口" class="headerlink" title="MSVC CRT 入口"></a>MSVC CRT 入口</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">int mainCRTStartup(void)</span><br></pre></td></tr></table></figure>
|
||
|
||
<p>执行如下操作:</p>
|
||
<ol>
|
||
<li>初始化和 OS 版本有关的全局变量。</li>
|
||
<li>初始化堆。</li>
|
||
<li>初始化 I/O。</li>
|
||
<li>获取命令行参数和环境变量。</li>
|
||
<li>初始化 C 库的一些数据。</li>
|
||
<li>调用 main 并记录返回值。</li>
|
||
<li>检查错误并将 main 的返回值返回。</li>
|
||
</ol>
|
||
<h4 id="C-语言运行库(CRT)"><a href="#C-语言运行库(CRT)" class="headerlink" title="C 语言运行库(CRT)"></a>C 语言运行库(CRT)</h4><p>大致包含如下功能:</p>
|
||
<ul>
|
||
<li>启动与退出:包括入口函数及入口函数所依赖的其他函数等。</li>
|
||
<li>标准函数:有 C 语言标准规定的C语言标准库所拥有的函数实现。</li>
|
||
<li>I/O:I/O 功能的封装和实现。</li>
|
||
<li>堆:堆的封装和实现。</li>
|
||
<li>语言实现:语言中一些特殊功能的实现。</li>
|
||
<li>调试:实现调试功能的代码。</li>
|
||
</ul>
|
||
<h4 id="C语言标准库(ANSI-C)"><a href="#C语言标准库(ANSI-C)" class="headerlink" title="C语言标准库(ANSI C)"></a>C语言标准库(ANSI C)</h4><p>包含:</p>
|
||
<ul>
|
||
<li>标准输入输出(stdio.h)</li>
|
||
<li>文件操作(stdio.h)</li>
|
||
<li>字符操作(ctype.h)</li>
|
||
<li>字符串操作(string.h)</li>
|
||
<li>数学函数(math.h)</li>
|
||
<li>资源管理(stdlib.h)</li>
|
||
<li>格式转换(stdlib.h)</li>
|
||
<li>时间/日期(time.h)</li>
|
||
<li>断言(assert.h)</li>
|
||
<li>各种类型上的常数(limits.h & float.h)</li>
|
||
<li>变长参数(stdarg.h)</li>
|
||
<li>非局部跳转(setjmp.h)</li>
|
||
</ul>
|
||
<h2 id="海量数据处理"><a href="#海量数据处理" class="headerlink" title="海量数据处理"></a>海量数据处理</h2><ul>
|
||
<li>海量数据处理面试题集锦</li>
|
||
<li>十道海量数据处理面试题与十个方法大总结</li>
|
||
</ul>
|
||
<h2 id="音视频"><a href="#音视频" class="headerlink" title="音视频"></a>音视频</h2><ul>
|
||
<li>最全实时音视频开发要用到的开源工程汇总</li>
|
||
<li>18个实时音视频开发中会用到开源项目</li>
|
||
</ul>
|
||
<h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><ul>
|
||
<li>Bjarne Stroustrup 的常见问题</li>
|
||
<li>Bjarne Stroustrup 的 C++ 风格和技巧常见问题</li>
|
||
</ul>
|
||
<h2 id="书籍"><a href="#书籍" class="headerlink" title="书籍"></a>书籍</h2><h3 id="语言"><a href="#语言" class="headerlink" title="语言"></a>语言</h3><ul>
|
||
<li>《C++ Primer》</li>
|
||
<li>《Effective C++》</li>
|
||
<li>《More Effective C++》</li>
|
||
<li>《深度探索 C++ 对象模型》</li>
|
||
<li>《深入理解 C++11》</li>
|
||
<li>《STL 源码剖析》</li>
|
||
</ul>
|
||
<h3 id="算法-2"><a href="#算法-2" class="headerlink" title="算法"></a>算法</h3><ul>
|
||
<li>《剑指 Offer》</li>
|
||
<li>《编程珠玑》</li>
|
||
<li>《程序员面试宝典》</li>
|
||
</ul>
|
||
<h3 id="系统"><a href="#系统" class="headerlink" title="系统"></a>系统</h3><ul>
|
||
<li>《深入理解计算机系统》</li>
|
||
<li>《Windows 核心编程》</li>
|
||
<li>《Unix 环境高级编程》</li>
|
||
</ul>
|
||
<h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><ul>
|
||
<li>《Unix 网络编程》</li>
|
||
<li>《TCP/IP 详解》</li>
|
||
</ul>
|
||
<h3 id="其他-1"><a href="#其他-1" class="headerlink" title="其他"></a>其他</h3><ul>
|
||
<li>《程序员的自我修养》</li>
|
||
</ul>
|
||
]]></content>
|
||
<categories>
|
||
<category>c++</category>
|
||
</categories>
|
||
<tags>
|
||
<tag>c++</tag>
|
||
</tags>
|
||
</entry>
|
||
</search>
|