<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title><![CDATA[奇诺日记|qinor.cn - 技术攻略分享]]></title> 
<description><![CDATA[实用技术攻略分享]]></description>
<link>https://www.qinor.cn/</link>
<language>zh-cn</language>
<generator>www.emlog.net</generator>
<item>
	<title>微信v3.3.0.84 消息防撤回及多开补丁</title>
	<link>https://www.qinor.cn/post-66.html</link>
	<description><![CDATA[<strong></strong> 
<div class="Fengdown_tit">
	<i class="ico"></i>
</div>
<div class="newfujian">
	<div class="fileico rar">
	</div>
	<div class="filecont">
		<div class="filetit">
			<a href="http://pan.qinor.cn/s/p7q06z83" target="_blank" rel="nofollow" title="点击下载">微信v3.3.0.84 多开、防撤回补丁</a><span>大小：19.89 | 来源：奇诺网盘</span> 
		</div>
		<div class="fileaq">
			已经过安全软件检测无毒，请您放心下载。
		</div>
	</div>
	<div class="down_2">
		<a href="http://pan.qinor.cn/s/p7q06z83" target="_blank" rel="nofollow" title="点击下载"></a>
	</div>
</div>]]></description>
	<pubDate>Mon, 21 Jun 2021 11:15:37 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-66.html</guid>

</item>
<item>
	<title>最近很火的小霸王游戏机模拟开源项目--JSNES</title>
	<link>https://www.qinor.cn/post-65.html</link>
	<description><![CDATA[<p>
	<p class="MsoNormal" align="left" style="margin-left:0cm;text-align:left;">
		开源地址：<span>https://github.com/bfirsh/jsnes</span>
	</p>
	<p class="MsoNormal" align="left" style="margin-left:0cm;text-align:left;">
		<br />
	</p>
	<h1>
		一、简介：
	</h1>
	<p class="MsoNormal">
		<span>Jsnes</span>是一个用<span> JavaScript </span>实现的<span> NES </span>模拟器；这是一个可以在浏览器和<span> Node.js </span>中使用的库，浏览器的用户界面可以在<span> https://github.com/bfirsh/jsnes-web </span>中找到。
	</p>
	<p class="MsoNormal" align="left" style="margin-left:0cm;text-align:left;">
		<br />
	</p>
	<p class="MsoNormal" align="left" style="margin-left:0cm;text-align:left;">
		<span class="1"><span style="font-size:24px;"><strong>二、</strong></span><span style="font-size:24px;"><strong>Installation&nbsp;</strong></span><span style="font-size:24px;"><strong>安装</strong></span></span><br />
<span class="2" style="font-size:16px;">1.&nbsp;&nbsp;</span><span class="2"><span style="font-size:16px;">对于</span><span style="font-size:16px;"> Node.js </span><span style="font-size:16px;">或</span><span style="font-size:16px;"> Webpack:</span></span>
	</p>
	<p class="MsoNormal">
<pre class="prettyprint lang-js linenums">$ npm install jsnes</pre>
	</p>
	<p class="MsoNormal">
		<span></span>
	</p>
	<p class="MsoNormal">
		或者使用<span></span>
	</p>
	<p class="MsoNormal">
		<span></span>
	</p>
	<p class="MsoNormal">
<pre class="prettyprint lang-js linenums">$ yarn add jsnes</pre>
	</p>
	<p class="MsoNormal" align="left" style="text-align:left;">
		<br />
	</p>
	<h2>
		<span>2.</span>在浏览器中，你可以使用<span> unpkg:</span>
	</h2>
	<p class="MsoNormal">
<pre class="prettyprint lang-js linenums">&lt;script type="text/javascript" src="https://unpkg.com/jsnes/dist/jsnes.min.js"&gt;&lt;/script&gt;</pre>
	</p>
	<p class="MsoNormal" align="left" style="text-align:left;">
		<br />
	</p>
	<h1>
		三、<span>Usage&nbsp;</span>用法
	</h1>
	<p class="MsoNormal" align="left" style="text-align:left;">
<pre class="prettyprint lang-js linenums">// Initialize and set up outputs

var nes = new jsnes.NES({

  onFrame: function(frameBuffer) {

    // ... write frameBuffer to screen

  },

  onAudioSample: function(left, right) {

    // ... play audio sample

  }

});



// Read ROM data from disk (using Node.js APIs, for the sake of this example)

const fs = require('fs');

var romData = fs.readFileSync('path/to/rom.nes', {encoding: 'binary'});



// Load ROM data as a string or byte array

nes.loadROM(romData);



// Run frames at 60 fps, or as fast as you can.

// You are responsible for reliable timing as best you can on your platform.

nes.frame();

nes.frame();

// ...



// Hook up whatever input device you have to the controller.

nes.buttonDown(1, jsnes.Controller.BUTTON_A);

nes.frame();

nes.buttonUp(1, jsnes.Controller.BUTTON_A);

nes.frame();

// ...</pre>
	</p>
	<p class="MsoNormal" align="left" style="text-align:left;">
		<br />
	</p>
	<h1>
		四、<span>Build&nbsp;</span>构建
	</h1>
	<p class="MsoNormal">
		如果想要构建好一个发布版，可以执行：
	</p>
	<p class="MsoNormal" align="left" style="text-align:left;">
<pre class="prettyprint lang-js linenums">$ yarn run build</pre>
	</p>
	<p class="MsoNormal">
		（这会在目录下创建<span> dist/jsnes.min.js&nbsp; </span>文件）<span></span>
	</p>
	<p class="MsoNormal" align="left" style="text-align:left;">
		<br />
	</p>
	<p class="MsoNormal" align="left" style="margin-left:0cm;text-align:left;">
		<span class="1"><strong><span style="font-size:24px;">五、</span></strong><span style="font-size:24px;"><strong>Running
tests </strong></span><strong><span style="font-size:24px;">运行测试</span></strong></span>
	</p>
	<p class="MsoNormal" align="left" style="text-align:left;">
<pre class="prettyprint lang-js linenums">$ yarn test</pre>
	</p>
	<p class="MsoNormal" align="left" style="margin-left:0cm;text-align:left;">
		<br />
	</p>
	<h1>
		六、以下是演示链接，兼容<span>PC</span>端、移动端：
	</h1>
	<p class="MsoNormal" align="left" style="margin-left:0cm;text-align:left;">
		<a href="http://fc.255615.cn/" target="_blank">http://fc.255615.cn/</a>
	</p>
</p>
<p>
	<br />
</p>]]></description>
	<pubDate>Thu, 27 May 2021 01:38:06 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-65.html</guid>

</item>
<item>
	<title>对比学习Redis与Memcache差异不同点</title>
	<link>https://www.qinor.cn/post-64.html</link>
	<description><![CDATA[<p style="background-image:none;background-repeat:repeat;background-attachment:scroll;background-position:0% 0%;">
	<span style="font-size:9pt;color:#333333;"><span style="line-height:1;">说到Redis就会联想到Memcached，反之亦然。<br />
</span><span style="line-height:1;">了解过两者的同学有那么个大致的印象：Redis与Memcached相比，比仅支持简单的key-value数据类型，同时还提供list,set,zset,hash等数据结构的存储；redis支持数据的备份，redis支持数据的持久化，看起来redis比memcached更加牛逼一些，那么事实上是不是这样的呢？<br />
</span></span> 
</p>
<p style="background-image:none;background-repeat:repeat;background-attachment:scroll;background-position:0% 0%;">
	<span style="font-size:9pt;color:#333333;"><span style="line-height:1;"><span style="line-height:1.5;"></span><br />
</span></span> 
</p>
<p style="background-image:none;background-repeat:repeat;background-attachment:scroll;background-position:0% 0%;">
	<span style="font-size:9pt;color:#333333;line-height:1;">Redis</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">的作者</span><span style="line-height:1;">Salvatore Sanfilippo</span><span style="line-height:1;">曾经对这两种基于内存的数据存储系统进行过比较：</span></span> 
</p>
<p>
	<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">1 Redis</span></span></strong><strong><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">支持服务器端的数据操作：</span></strong><span><span style="font-size:9pt;color:#333333;line-height:1;">Redis</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">相比</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">来说，拥有更多的数据结构和并支持更丰富的数据操作，通常在</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">里，你需要将数据拿到客户端来进行类似的修改再</span><span style="line-height:1;">set</span><span style="line-height:1;">回去。这大大增加了网络</span><span style="line-height:1;">IO</span><span style="line-height:1;">的次数和数据体积。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">在</span><span style="line-height:1;">Redis</span><span style="line-height:1;">中，这些复杂的操作通常和一般的</span><span style="line-height:1;">GET/SET</span><span style="line-height:1;">一样高效。所以，如果需要缓存能够支持更复杂的结构和操作，那么</span><span style="line-height:1;">Redis</span><span style="line-height:1;">会是不错的选择。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">2 </span></span></strong><strong><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">内存使用效率对比：</span></strong><span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">使用简单的</span><span style="line-height:1;">key-value</span><span style="line-height:1;">存储的话，</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">的内存利用率更高，而如果</span><span style="line-height:1;">Redis</span><span style="line-height:1;">采用</span><span style="line-height:1;">hash</span><span style="line-height:1;">结构来做</span><span style="line-height:1;">key-value</span><span style="line-height:1;">存储，由于其组合式的压缩，其内存利用率会高于</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">。</span></span></span> 
</p>
<p>
	<span style="font-size:9pt;color:#333333;"></span> 
</p>
<p>
	<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">3 </span></span></strong><strong><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">性能对比：</span></strong><span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">由于</span><span style="line-height:1;">Redis</span><span style="line-height:1;">只使用单核，而</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">可以使用多核，所以平均每一个核上</span><span style="line-height:1;">Redis</span><span style="line-height:1;">在存储小数据时比</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">性能更高。</span></span><span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">而在</span><span style="line-height:1;">100k</span><span style="line-height:1;">以上的数据中，</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">性能要高于</span><span style="line-height:1;">Redis</span><span style="line-height:1;">，虽然</span><span style="line-height:1;">Redis</span><span style="line-height:1;">最近也在存储大数据的性能上进行优化，但是比起</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">，还是稍有逊色。</span></span></span> 
</p>
<p>
	<span style="line-height:1;"><span style="line-height:1.5;"></span></span> 
</p>
<p>
	<strong><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">数据类型支持不同</span></strong> 
</p>
<p>
	<span style="color:#333333;font-size:9pt;line-height:1;">与</span><span style="color:#333333;font-size:9pt;line-height:1;">Memcached</span><span style="color:#333333;font-size:9pt;line-height:1;">仅支持简单的</span><span style="color:#333333;font-size:9pt;line-height:1;">key-value</span><span style="color:#333333;font-size:9pt;line-height:1;">结构的数据记录不同，</span><span style="color:#333333;font-size:9pt;line-height:1;">Redis</span><span style="color:#333333;font-size:9pt;line-height:1;">支持的数据类型要丰富得多。</span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">最为常用的数据类型主要由五种：</span><span style="line-height:1;">String</span><span style="line-height:1;">、</span><span style="line-height:1;">Hash</span><span style="line-height:1;">、</span><span style="line-height:1;">List</span><span style="line-height:1;">、</span><span style="line-height:1;">Set</span><span style="line-height:1;">和</span><span style="line-height:1;">Sorted Set</span><span style="line-height:1;">。</span><span style="line-height:1;">Redis</span><span style="line-height:1;">内部使用一个</span><span style="line-height:1;">redisObject</span><span style="line-height:1;">对象来表示所有的</span><span style="line-height:1;">key</span><span style="line-height:1;">和</span><span style="line-height:1;">value</span><span style="line-height:1;">。</span><span style="line-height:1;">redisObject</span><span style="line-height:1;">最主要的信息如图所示：</span></span></span> 
</p>
<p align="center">
	<span><span style="font-size:9pt;color:#333333;"></span><a id="ematt:728" href="https://www.qinor.cn/content/uploadfile/202011/f3cc1606273999.jpg" target="_blank"><img title="点击查看原图" border="0" alt="1.jpg" src="https://www.qinor.cn/content/uploadfile/202011/thum-f3cc1606273999.jpg" width="420" height="217" /></a><span></span></span> 
</p>
<p>
	<span style="font-size:9pt;color:#333333;"></span> 
</p>
<p text-align:center;"="" align="center"> <span><span style="font-size:9pt;color:#333333;"></span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span style="font-size:9pt;color:#333333;"></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">type</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">代表一个</span><span style="line-height:1;">value</span><span style="line-height:1;">对象具体是何种数据类型，</span><span style="line-height:1;">encoding</span><span style="line-height:1;">是不同数据类型在</span><span style="line-height:1;">redis</span><span style="line-height:1;">内部的存储方式，比如：</span><span style="line-height:1;">type=string</span><span style="line-height:1;">代表</span><span style="line-height:1;">value</span><span style="line-height:1;">存储的是一个普通字符串，那么对应的</span><span style="line-height:1;">encoding</span><span style="line-height:1;">可以是</span><span style="line-height:1;">raw</span><span style="line-height:1;">或者是</span><span style="line-height:1;">int</span><span style="line-height:1;">，如果是</span><span style="line-height:1;">int</span><span style="line-height:1;">则代表实际</span><span style="line-height:1;">redis</span><span style="line-height:1;">内部是按数值型类存储和表示这个字符串的，当然前提是这个字符串本身可以用数值表示，比如</span><span style="line-height:1;">:</span><span style="line-height:1;">”</span><span style="line-height:1;">123</span><span style="line-height:1;">″ “</span><span style="line-height:1;">456</span><span style="line-height:1;">”这样的字符串。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">只有打开了</span><span style="line-height:1;">Redis</span><span style="line-height:1;">的虚拟内存功能，</span><span style="line-height:1;">vm</span><span style="line-height:1;">字段字段才会真正的分配内存，该功能默认是关闭状态的。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span><span style="line-height:1;"></span><br />
</span></span></span> 
	</p>
	<p>
		<span style="font-size:9pt;color:#333333;"></span> 
	</p>
	<p>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">String</span></span></strong><span style="font-size:9pt;color:#333333;"></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">1 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">常用命令：</span><span style="line-height:1;">set/get/decr/incr/mget</span><span style="line-height:1;">等；</span></span><span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">2 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">应用场景：</span><span style="line-height:1;">String</span><span style="line-height:1;">是最常用的一种数据类型，普通的</span><span style="line-height:1;">key/value</span><span style="line-height:1;">存储都可以归为此类；</span></span><span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">3 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">实现方式：</span><span style="line-height:1;">String</span><span style="line-height:1;">在</span><span style="line-height:1;">redis</span><span style="line-height:1;">内部存储默认就是一个字符串，被</span><span style="line-height:1;">redisObject</span><span style="line-height:1;">所引用，当遇到</span><span style="line-height:1;">incr</span><span style="line-height:1;">、</span><span style="line-height:1;">decr</span><span style="line-height:1;">等操作时会转成数值型进行计算，此时</span><span style="line-height:1;">redisObject</span><span style="line-height:1;">的</span><span style="line-height:1;">encoding</span><span style="line-height:1;">字段为</span><span style="line-height:1;">int</span><span style="line-height:1;">。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span><span style="line-height:1;"></span><br />
</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">Hash</span></span></strong><span style="font-size:9pt;color:#333333;"></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">1 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">常用命令：</span><span style="line-height:1;">hget/hset/hgetall</span><span style="line-height:1;">等</span></span><span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">2 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">应用场景：我们要存储一个用户信息对象数据，其中包括用户</span><span style="line-height:1;">ID</span><span style="line-height:1;">、用户姓名、年龄和生日，通过用户</span><span style="line-height:1;">ID</span><span style="line-height:1;">我们希望获取该用户的姓名或者年龄或者生日；</span></span><span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">3 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">实现方式：</span><span style="line-height:1;">Redis</span><span style="line-height:1;">的</span><span style="line-height:1;">Hash</span><span style="line-height:1;">实际是内部存储的</span><span style="line-height:1;">Value</span><span style="line-height:1;">为一个</span><span style="line-height:1;">HashMap</span><span style="line-height:1;">，并提供了直接存取这个</span><span style="line-height:1;">Map</span><span style="line-height:1;">成员的接口。</span></span></span> 
	</p>
	<p>
		<span style="color:#333333;font-size:9pt;line-height:1;">如图所示，</span><span style="color:#333333;font-size:9pt;line-height:1;">Key</span><span style="color:#333333;font-size:9pt;line-height:1;">是用户</span><span style="color:#333333;font-size:9pt;line-height:1;">ID, value</span><span style="color:#333333;font-size:9pt;line-height:1;">是一个</span><span style="color:#333333;font-size:9pt;line-height:1;">Map</span><span style="color:#333333;font-size:9pt;line-height:1;">。这个</span><span style="color:#333333;font-size:9pt;line-height:1;">Map</span><span style="color:#333333;font-size:9pt;line-height:1;">的</span><span style="color:#333333;font-size:9pt;line-height:1;">key</span><span style="color:#333333;font-size:9pt;line-height:1;">是成员的属性名，</span><span style="color:#333333;font-size:9pt;line-height:1;">value</span><span style="color:#333333;font-size:9pt;line-height:1;">是属性值。</span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">这样对数据的修改和存取都可以直接通过其内部</span><span style="line-height:1;">Map</span><span style="line-height:1;">的</span><span style="line-height:1;">Key(Redis</span><span style="line-height:1;">里称内部</span><span style="line-height:1;">Map</span><span style="line-height:1;">的</span><span style="line-height:1;">key</span><span style="line-height:1;">为</span><span style="line-height:1;">field), </span><span style="line-height:1;">也就是通过</span><span style="line-height:1;"> key(</span><span style="line-height:1;">用户</span><span style="line-height:1;">ID) + field(</span><span style="line-height:1;">属性标签</span><span style="line-height:1;">) </span><span style="line-height:1;">就可以操作对应属性数据。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">当前</span><span style="line-height:1;">HashMap</span><span style="line-height:1;">的实现有两种方式：当</span><span style="line-height:1;">HashMap</span><span style="line-height:1;">的成员比较少时</span><span style="line-height:1;">Redis</span><span style="line-height:1;">为了节省内存会采用类似一维数组的方式来紧凑存储，而不会采用真正的</span><span style="line-height:1;">HashMap</span><span style="line-height:1;">结构，这时对应的</span><span style="line-height:1;">value</span><span style="line-height:1;">的</span><span style="line-height:1;">redisObject</span><span style="line-height:1;">的</span><span style="line-height:1;">encoding</span><span style="line-height:1;">为</span><span style="line-height:1;">zipmap</span><span style="line-height:1;">，当成员数量增大时会自动转成真正的</span><span style="line-height:1;">HashMap,</span><span style="line-height:1;">此时</span><span style="line-height:1;">encoding</span><span style="line-height:1;">为</span><span style="line-height:1;">ht</span><span style="line-height:1;">。</span></span></span> 
	</p>
	<p align="center">
		<span><a id="ematt:730" href="https://www.qinor.cn/content/uploadfile/202011/15601606274000.jpg" target="_blank"><img title="点击查看原图" border="0" alt="2.jpg" src="https://www.qinor.cn/content/uploadfile/202011/15601606274000.jpg" width="384" height="192" /></a><span style="font-size:9pt;color:#333333;"></span></span> 
	</p>
	<p>
		<span style="line-height:1;"></span> 
	</p>
<p text-align:center;"="" align="center"> <span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span style="font-size:9pt;color:#333333;"></span> 
</p>
<p>
	<strong><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">List</span></strong><span style="font-size:9pt;color:#333333;"></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">1</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">常用命令：</span><span style="line-height:1;">lpush/rpush/lpop/rpop/lrange</span><span style="line-height:1;">等；</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">2 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">应用场景：</span><span style="line-height:1;">Redis list</span><span style="line-height:1;">的应用场景非常多，也是</span><span style="line-height:1;">Redis</span><span style="line-height:1;">最重要的数据结构之一，比如</span><span style="line-height:1;">twitter</span><span style="line-height:1;">的关注列表，粉丝列表等都可以用</span><span style="line-height:1;">Redis</span><span style="line-height:1;">的</span><span style="line-height:1;">list</span><span style="line-height:1;">结构来实现；</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">3 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">实现方式：</span><span style="line-height:1;">Redis list</span><span style="line-height:1;">的实现为一个双向链表，即可以支持反向查找和遍历，更方便操作，不过带来了部分额外的内存开销，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">内部的很多实现，包括发送缓冲队列等也都是用的这个数据结构。</span></span></span> 
</p>
<p>
	<br />
</p>
<p>
	<span style="line-height:1;"></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">Set</span></span></strong><span style="font-size:9pt;color:#333333;"></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">1 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">常用命令：</span><span style="line-height:1;">sadd/spop/smembers/sunion</span><span style="line-height:1;">等；</span></span></span> 
</p>
<p>
	<span style="font-size:9pt;color:#333333;"></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">2 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">应用场景：</span><span style="line-height:1;">Redis set</span><span style="line-height:1;">对外提供的功能与</span><span style="line-height:1;">list</span><span style="line-height:1;">类似是一个列表的功能，特殊之处在于</span><span style="line-height:1;">set</span><span style="line-height:1;">是可以自动排重的，当你需要存储一个列表数据，又不希望出现重复数据时，</span><span style="line-height:1;">set</span><span style="line-height:1;">是一个很好的选择，并且</span><span style="line-height:1;">set</span><span style="line-height:1;">提供了判断某个成员是否在一个</span><span style="line-height:1;">set</span><span style="line-height:1;">集合内的重要接口，这个也是</span><span style="line-height:1;">list</span><span style="line-height:1;">所不能提供的；</span></span></span> 
</p>
<p>
	<span style="font-size:9pt;color:#333333;"></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">3 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">实现方式：</span><span style="line-height:1;">set </span><span style="line-height:1;">的内部实现是一个</span><span style="line-height:1;"> value</span><span style="line-height:1;">永远为</span><span style="line-height:1;">null</span><span style="line-height:1;">的</span><span style="line-height:1;">HashMap</span><span style="line-height:1;">，实际就是通过计算</span><span style="line-height:1;">hash</span><span style="line-height:1;">的方式来快速排重的，这也是</span><span style="line-height:1;">set</span><span style="line-height:1;">能提供判断一个成员是否在集合内的原因。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;"><br />
</span></span></span>
</p>
<p>
	<span style="line-height:1;"></span> 
</p>
<p>
	<span style="font-size:9pt;color:#333333;"></span> 
</p>
<p>
	<span style="font-size:9pt;color:#333333;"></span> 
</p>
<span> 
<p>
	<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">Sorted Set</span></span></strong><span style="font-size:9pt;color:#333333;"></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">1 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">常用命令：</span><span style="line-height:1;">zadd/zrange/zrem/zcard</span><span style="line-height:1;">等；</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">2 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">应用场景：</span><span style="line-height:1;">Redis sorted set</span><span style="line-height:1;">的使用场景与</span><span style="line-height:1;">set</span><span style="line-height:1;">类似，区别是</span><span style="line-height:1;">set</span><span style="line-height:1;">不是自动有序的，而</span><span style="line-height:1;">sorted set</span><span style="line-height:1;">可以通过用户额外提供一个优先级</span><span style="line-height:1;">(score)</span><span style="line-height:1;">的参数来为成员排序，并且是插入有序的，即自动排序。当你需要一个有序的并且不重复的集合列表，那么可以选择</span><span style="line-height:1;">sorted set</span><span style="line-height:1;">数据结构，比如</span><span style="line-height:1;">twitter </span><span style="line-height:1;">的</span><span style="line-height:1;">public timeline</span><span style="line-height:1;">可以以发表时间作为</span><span style="line-height:1;">score</span><span style="line-height:1;">来存储，这样获取时就是自动按时间排好序的。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">3 </span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">实现方式：</span><span style="line-height:1;">Redis sorted set</span><span style="line-height:1;">的内部使用</span><span style="line-height:1;">HashMap</span><span style="line-height:1;">和跳跃表</span><span style="line-height:1;">(SkipList)</span><span style="line-height:1;">来保证数据的存储和有序，</span><span style="line-height:1;">HashMap</span><span style="line-height:1;">里放的是成员到</span><span style="line-height:1;">score</span><span style="line-height:1;">的映射，而跳跃表里存放的是所有的成员，排序依据是</span><span style="line-height:1;">HashMap</span><span style="line-height:1;">里存的</span><span style="line-height:1;">score,</span><span style="line-height:1;">使用跳跃表的结构可以获得比较高的查找效率，并且在实现上比较简单。</span></span><span></span></span> 
</p>
<p>
	<strong><span><span style="font-size:9pt;font-family:宋体;color:#333333;"><span style="background-color:;"></span><span style="line-height:1.5;"></span><br />
</span></span></strong> 
</p>
<p>
	<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">内存管理机制不同</span></span></strong> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">在</span><span style="line-height:1;">Redis</span><span style="line-height:1;">中，并不是所有的数据都一直存储在内存中的。这是和</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">相比一个最大的区别。</span></span></span> 
</p>
<p>
	<span style="color:#333333;font-size:9pt;line-height:1;">当物理内存用完时，</span><span style="color:#333333;font-size:9pt;line-height:1;">Redis</span><span style="color:#333333;font-size:9pt;line-height:1;">可以将一些很久没用到的</span><span style="color:#333333;font-size:9pt;line-height:1;">value</span><span style="color:#333333;font-size:9pt;line-height:1;">交换到磁盘。</span><span style="color:#333333;font-size:9pt;line-height:1;">Redis</span><span style="color:#333333;font-size:9pt;line-height:1;">只会缓存所有的</span><span style="color:#333333;font-size:9pt;line-height:1;">key</span><span style="color:#333333;font-size:9pt;line-height:1;">的信息，如果</span><span style="color:#333333;font-size:9pt;line-height:1;">Redis</span><span style="color:#333333;font-size:9pt;line-height:1;">发现内存的使用量超过了某一个阀值，将触发</span><span style="color:#333333;font-size:9pt;line-height:1;">swap</span><span style="color:#333333;font-size:9pt;line-height:1;">的操作，</span><span style="color:#333333;font-size:9pt;line-height:1;">Redis</span><span style="color:#333333;font-size:9pt;line-height:1;">根据“</span><span style="color:#333333;font-size:9pt;line-height:1;">swappability = age*log(size_in_memory)</span><span style="color:#333333;font-size:9pt;line-height:1;">”计算出哪些</span><span style="color:#333333;font-size:9pt;line-height:1;">key</span><span style="color:#333333;font-size:9pt;line-height:1;">对应的</span><span style="color:#333333;font-size:9pt;line-height:1;">value</span><span style="color:#333333;font-size:9pt;line-height:1;">需要</span><span style="color:#333333;font-size:9pt;line-height:1;">swap</span><span style="color:#333333;font-size:9pt;line-height:1;">到磁盘。</span> 
</p>
<p>
	<span style="color:#333333;font-size:9pt;line-height:1;">然后再将这些</span><span style="color:#333333;font-size:9pt;line-height:1;">key</span><span style="color:#333333;font-size:9pt;line-height:1;">对应的</span><span style="color:#333333;font-size:9pt;line-height:1;">value</span><span style="color:#333333;font-size:9pt;line-height:1;">持久化到磁盘中，同时在内存中清除。这种特性使得</span><span style="color:#333333;font-size:9pt;line-height:1;">Redis</span><span style="color:#333333;font-size:9pt;line-height:1;">可以保持超过其机器本身内存大小的数据。</span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">当然，机器本身的内存必须要能够保持所有的</span><span style="line-height:1;">key</span><span style="line-height:1;">，毕竟这些数据是不会进行</span><span style="line-height:1;">swap</span><span style="line-height:1;">操作的。同时由于</span><span style="line-height:1;">Redis</span><span style="line-height:1;">将内存中的数据</span><span style="line-height:1;">swap</span><span style="line-height:1;">到磁盘中的时候，提供服务的主线程和进行</span><span style="line-height:1;">swap</span><span style="line-height:1;">操作的子线程会共享这部分内存，所以如果更新需要</span><span style="line-height:1;">swap</span><span style="line-height:1;">的数据，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">将阻塞这个操作，直到子线程完成</span><span style="line-height:1;">swap</span><span style="line-height:1;">操作后才可以进行修改。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">当从</span><span style="line-height:1;">Redis</span><span style="line-height:1;">中读取数据的时候，如果读取的</span><span style="line-height:1;">key</span><span style="line-height:1;">对应的</span><span style="line-height:1;">value</span><span style="line-height:1;">不在内存中，那么</span><span style="line-height:1;">Redis</span><span style="line-height:1;">就需要从</span><span style="line-height:1;">swap</span><span style="line-height:1;">文件中加载相应数据，然后再返回给请求方。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">这里就存在一个</span><span style="line-height:1;">I/O</span><span style="line-height:1;">线程池的问题。</span></span><span><span style="line-height:1;">在默认的情况下，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">会出现阻塞，即完成所有的</span><span style="line-height:1;">swap</span><span style="line-height:1;">文件加载后才会相应。这种策略在客户端的数量较小，进行批量操作的时候比较合适。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">但是如果将</span><span style="line-height:1;">Redis</span><span style="line-height:1;">应用在一个大型的网站应用程序中，这显然是无法满足大并发的情况的。所以</span><span style="line-height:1;">Redis</span><span style="line-height:1;">运行我们设置</span><span style="line-height:1;">I/O</span><span style="line-height:1;">线程池的大小，对需要从</span><span style="line-height:1;">swap</span><span style="line-height:1;">文件中加载相应数据的读取请求进行并发操作，减少阻塞的时间。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">对于像</span><span style="line-height:1;">Redis</span><span style="line-height:1;">和</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">这种基于内存的数据库系统来说，内存管理的效率高低是影响系</span></span></span><span style="line-height:1;"><span style="font-size:9pt;color:#333333;line-height:1;">统性能的关键因素。</span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">传统</span><span style="line-height:1;">C</span><span style="line-height:1;">语言中的</span><span style="line-height:1;">malloc/free</span><span style="line-height:1;">函数是最常用的分配和释放内存的方法，但是这种方法存在着很大的缺陷：首先，对于开发人员来说不匹配的</span><span style="line-height:1;">malloc</span><span style="line-height:1;">和</span><span style="line-height:1;">free</span><span style="line-height:1;">容易造成内存泄露；其次频繁调用会造成大量内存碎片无法回收重新利用，降低内存利用率；最后作为系统调用，其系统开销远远大于一般函数调用。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">所以，为了提高内存的管理效率，高效的内存管理方案都不会直接使用</span><span style="line-height:1;">malloc/free</span><span style="line-height:1;">调用。</span><span style="line-height:1;">Redis</span><span style="line-height:1;">和</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">均使用了自身设计的内存管理机制，但是实现方法存在很大的差异，下面将会对两者的内存管理机制分别进行介绍。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">Memcached</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">默认使用</span><span style="line-height:1;">Slab Allocation</span><span style="line-height:1;">机制管理内存，其主要思想是按照预先规定的大小，将分配的内存分割成特定长度的块以存储相应长度的</span><span style="line-height:1;">key-value</span><span style="line-height:1;">数据记录，以完全解决内存碎片问题。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">Slab Allocation</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">机制只为存储外部数据而设计，也就是说所有的</span><span style="line-height:1;">key-value</span><span style="line-height:1;">数据都存储在</span><span style="line-height:1;">Slab Allocation</span><span style="line-height:1;">系统里，而</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">的其它内存请求则通过普通的</span><span style="line-height:1;">malloc/free</span><span style="line-height:1;">来申请，因为这些请求的数量和频率决定了它们不会对整个系统的性能造成影响</span><span style="line-height:1;">Slab Allocation</span><span style="line-height:1;">的原理相当简单。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">如图所示，它首先从操作系统申请一大块内存，并将其分割成各种尺寸的块</span><span style="line-height:1;">Chunk</span><span style="line-height:1;">，并把尺寸相同的块分成组</span><span style="line-height:1;">Slab Class</span><span style="line-height:1;">。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">其中，</span><span style="line-height:1;">Chunk</span><span style="line-height:1;">就是用来存储</span><span style="line-height:1;">key-value</span><span style="line-height:1;">数据的最小单位。每个</span><span style="line-height:1;">Slab Class</span><span style="line-height:1;">的大小，可以在</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">启动的时候通过制定</span><span style="line-height:1;">Growth Factor</span><span style="line-height:1;">来控制。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"></span> </span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">假定图中</span><span style="line-height:1;">Growth Factor</span><span style="line-height:1;">的取值为</span><span style="line-height:1;">1.25</span><span style="line-height:1;">，如果第一组</span><span style="line-height:1;">Chunk</span><span style="line-height:1;">的大小为</span><span style="line-height:1;">88</span><span style="line-height:1;">个字节，第二组</span><span style="line-height:1;">Chunk</span><span style="line-height:1;">的大小就为</span><span style="line-height:1;">112</span><span style="line-height:1;">个字节，依此类推。</span></span></span> 
</p>
<p align="center">
	<span><span style="font-size:9pt;color:#333333;"></span><a id="ematt:731" href="https://www.qinor.cn/content/uploadfile/202011/799b1606274001.jpg" target="_blank"><img title="点击查看原图" border="0" alt="3.jpg" src="https://www.qinor.cn/content/uploadfile/202011/thum-799b1606274001.jpg" width="420" height="338" /></a><span></span></span> 
</p>
<p>
	<span><span></span></span><span style="line-height:1;">&nbsp;</span> 
</p>
<p text-align:center;margin-left:1px;"="" align="center"> <span style="font-size:9pt;color:#212529;"></span> 
	</p>
	<p>
		<span style="font-size:9pt;color:#333333;"><span style="line-height:1;">当</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">接收到客户端发送过来的数据时首先会根据收到数据的大小选择一个最合适的</span><span style="line-height:1;">Slab Class</span><span style="line-height:1;">，然后通过查询</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">保存着的该</span><span style="line-height:1;">Slab Class</span><span style="line-height:1;">内空闲</span><span style="line-height:1;">Chunk</span><span style="line-height:1;">的列表就可以找到一个可用于存储数据的</span><span style="line-height:1;">Chunk</span><span style="line-height:1;">。当一条数据库过期或者丢弃时，该记录所占用的</span><span style="line-height:1;">Chunk</span><span style="line-height:1;">就可以回收，重新添加到空闲列表中。</span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">从以上过程我们可以看出</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">的内存管理制效率高，而且不会造成内存碎片，但是它最大的缺点就是会导致空间浪费。因为每个</span><span style="line-height:1;">Chunk</span><span style="line-height:1;">都分配了特定长度的内存空间，所以变长数据无法充分利用这些空间。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">如图 所示，将</span><span style="line-height:1;">100</span><span style="line-height:1;">个字节的数据缓存到</span><span style="line-height:1;">128</span><span style="line-height:1;">个字节的</span><span style="line-height:1;">Chunk</span><span style="line-height:1;">中，剩余的</span><span style="line-height:1;">28</span><span style="line-height:1;">个字节就浪费掉了。</span></span></span> 
	</p>
	<p align="center">
		<span><span style="font-size:9pt;color:#333333;"></span><a id="ematt:733" href="https://www.qinor.cn/content/uploadfile/202011/09dd1606274001.png" target="_blank"><img title="点击查看原图" border="0" alt="4.png" src="https://www.qinor.cn/content/uploadfile/202011/09dd1606274001.png" width="347" height="142" /></a></span> 
	</p>
	<p align="center">
		<span><span></span></span><span style="line-height:1;">&nbsp;</span> 
	</p>
<p text-align:center;margin-left:1px;"="" align="center"> <span style="font-size:9pt;color:#212529;"></span><span style="font-size:9pt;color:#212529;"></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;line-height:1;">Redis</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">的内存管理主要通过源码中</span><span style="line-height:1;">zmalloc.h</span><span style="line-height:1;">和</span><span style="line-height:1;">zmalloc.c</span><span style="line-height:1;">两个文件来实现的。</span><span style="line-height:1;">Redis</span><span style="line-height:1;">为了方便内存的管理，在分配一块内存之后，会将这块内存的大小存入内存块的头部。</span></span></span> 
</p>
<p>
	<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">如图所示，</span><span style="line-height:1;">real_ptr</span><span style="line-height:1;">是</span><span style="line-height:1;">redis</span><span style="line-height:1;">调用</span><span style="line-height:1;">malloc</span><span style="line-height:1;">后返回的指针。</span><span style="line-height:1;">redis</span><span style="line-height:1;">将内存块的大小</span><span style="line-height:1;">size</span><span style="line-height:1;">存入头部，</span><span style="line-height:1;">size</span><span style="line-height:1;">所占据的内存大小是已知的，为</span><span style="line-height:1;">size_t</span><span style="line-height:1;">类型的长度，然后返回</span><span style="line-height:1;">ret_ptr</span><span style="line-height:1;">。当需要释放内存的时候，</span><span style="line-height:1;">ret_ptr</span><span style="line-height:1;">被传给内存管理程序。通过</span><span style="line-height:1;">ret_ptr</span><span style="line-height:1;">，程序可以很容易的算出</span><span style="line-height:1;">real_ptr</span><span style="line-height:1;">的值，然后将</span><span style="line-height:1;">real_ptr</span><span style="line-height:1;">传给</span><span style="line-height:1;">free</span><span style="line-height:1;">释放内存。</span></span></span> 
</p>
<p align="center">
	<span><span style="font-size:9pt;color:#333333;"></span><a id="ematt:734" href="https://www.qinor.cn/content/uploadfile/202011/82661606274001.png" target="_blank"><img title="点击查看原图" border="0" alt="5.png" src="https://www.qinor.cn/content/uploadfile/202011/82661606274001.png" width="349" height="95" /></a><span></span></span> 
</p>
<p>
	<span><span></span></span><span style="line-height:1;">&nbsp;</span> 
</p>
<p text-align:center;margin-left:1px;"="" align="center"> <span style="font-size:9pt;color:#212529;"></span><span style="font-size:9pt;color:#212529;"></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">Redis</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">通过定义一个数组来记录所有的内存分配情况，这个数组的长度为</span><span style="line-height:1;">ZMALLOC_MAX_ALLOC_STAT</span><span style="line-height:1;">。数组的每一个元素代表当前程序所分配的内存块的个数，且内存块的大小为该元素的下标。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">在源码中，这个数组为</span><span style="line-height:1;">zmalloc_allocations</span><span style="line-height:1;">。</span><span style="line-height:1;">zmalloc_allocations[16]</span><span style="line-height:1;">代表已经分配的长度为</span><span style="line-height:1;">16bytes</span><span style="line-height:1;">的内存块的个数。</span><span style="line-height:1;">zmalloc.c</span><span style="line-height:1;">中有一个静态变量</span><span style="line-height:1;">used_memory</span><span style="line-height:1;">用来记录当前分配的内存总大小。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">所以，总的来看，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">采用的是包装的</span><span style="line-height:1;">mallc/free</span><span style="line-height:1;">，相较于</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">的内存管理方法来说，要简单很多。</span></span><span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;"><span style="line-height:1.5;"></span><br />
</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<h2>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">数据持久化支持</span></span></strong><span style="font-size:9pt;font-weight:normal;color:#333333;"></span> 
	</h2>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">Redis</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">虽然是基于内存的存储系统，但是它本身是支持内存数据的持久化的，而且提供两种主要的持久化策略：</span><span style="line-height:1;">RDB</span><span style="line-height:1;">快照和</span><span style="line-height:1;">AOF</span><span style="line-height:1;">日志。</span></span><span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">而</span><span style="line-height:1;">memcached</span><span style="line-height:1;">是不支持数据持久化操作的。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;"><span style="line-height:1.5;"></span><br />
</span></span></span>
	</p>
	<p>
		<span style="line-height:1.5;"></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">RDB</span></span></strong><strong><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">快照</span></strong><span style="font-size:9pt;color:#333333;"></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">Redis</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">支持将当前数据的快照存成一个数据文件的持久化机制，即</span><span style="line-height:1;">RDB</span><span style="line-height:1;">快照。但是一个持续写入的数据库如何生成快照呢？</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">Redis</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">借助了</span><span style="line-height:1;">fork</span><span style="line-height:1;">命令的</span><span style="line-height:1;">copy on write</span><span style="line-height:1;">机制。在生成快照时，将当前进程</span><span style="line-height:1;">fork</span><span style="line-height:1;">出一个子进程，然后在子进程中循环所有的数据，将数据写成为</span><span style="line-height:1;">RDB</span><span style="line-height:1;">文件。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">我们可以通过</span><span style="line-height:1;">Redis</span><span style="line-height:1;">的</span><span style="line-height:1;">save</span><span style="line-height:1;">指令来配置</span><span style="line-height:1;">RDB</span><span style="line-height:1;">快照生成的时机，比如配置</span><span style="line-height:1;">10</span><span style="line-height:1;">分钟就生成快照，也可以配置有</span><span style="line-height:1;">1000</span><span style="line-height:1;">次写入就生成快照，也可以多个规则一起实施。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">这些规则的定义就在</span><span style="line-height:1;">Redis</span><span style="line-height:1;">的配置文件中，你也可以通过</span><span style="line-height:1;">Redis</span><span style="line-height:1;">的</span><span style="line-height:1;">CONFIG SET</span><span style="line-height:1;">命令在</span><span style="line-height:1;">Redis</span><span style="line-height:1;">运行时设置规则，不需要重启</span><span style="line-height:1;">Redis</span><span style="line-height:1;">。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">Redis</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">的</span><span style="line-height:1;">RDB</span><span style="line-height:1;">文件不会坏掉，因为其写操作是在一个新进程中进行的，当生成一个新的</span><span style="line-height:1;">RDB</span><span style="line-height:1;">文件时，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">生成的子进程会先将数据写到一个临时文件中，然后通过原子性</span><span style="line-height:1;">rename</span><span style="line-height:1;">系统调用将临时文件重命名为</span><span style="line-height:1;">RDB</span><span style="line-height:1;">文件，这样在任何时候出现故障，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">的</span><span style="line-height:1;">RDB</span><span style="line-height:1;">文件都总是可用的。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">同时，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">的</span><span style="line-height:1;">RDB</span><span style="line-height:1;">文件也是</span><span style="line-height:1;">Redis</span><span style="line-height:1;">主从同步内部实现中的一环。</span><span style="line-height:1;">RDB</span><span style="line-height:1;">有他的不足，就是一旦数据库出现问题，那么我们的</span><span style="line-height:1;">RDB</span><span style="line-height:1;">文件中保存的数据并不是全新的，从上次</span><span style="line-height:1;">RDB</span><span style="line-height:1;">文件生成到</span><span style="line-height:1;">Redis</span><span style="line-height:1;">停机这段时间的数据全部丢掉了。在某些业务下，这是可以忍受的。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;"><span style="line-height:1.5;"></span><br />
</span></span></span>
	</p>
	<p>
		<span style="line-height:1.5;"></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">AOF</span></span></strong><strong><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">日志</span></strong><span style="font-size:9pt;color:#333333;"></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">AOF</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">日志的全称是</span><span style="line-height:1;">append only file</span><span style="line-height:1;">，它是一个追加写入的日志文件。与一般数据库的</span><span style="line-height:1;">binlog</span><span style="line-height:1;">不同的是，</span><span style="line-height:1;">AOF</span><span style="line-height:1;">文件是可识别的纯文本，它的内容就是一个个的</span><span style="line-height:1;">Redis</span><span style="line-height:1;">标准命令。只有那些会导致数据发生修改的命令才会追加到</span><span style="line-height:1;">AOF</span><span style="line-height:1;">文件。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">每一条修改数据的命令都生成一条日志，</span><span style="line-height:1;">AOF</span><span style="line-height:1;">文件会越来越大，所以</span><span style="line-height:1;">Redis</span><span style="line-height:1;">又提供了一个功能，叫做</span><span style="line-height:1;">AOF rewrite</span><span style="line-height:1;">。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">其功能就是重新生成一份</span><span style="line-height:1;">AOF</span><span style="line-height:1;">文件，新的</span><span style="line-height:1;">AOF</span><span style="line-height:1;">文件中一条记录的操作只会有一次，而不像一份老文件那样，可能记录了对同一个值的多次操作。其生成过程和</span><span style="line-height:1;">RDB</span><span style="line-height:1;">类似，也是</span><span style="line-height:1;">fork</span><span style="line-height:1;">一个进程，直接遍历数据，写入新的</span><span style="line-height:1;">AOF</span><span style="line-height:1;">临时文件。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">在写入新文件的过程中，所有的写操作日志还是会写到原来老的</span><span style="line-height:1;">AOF</span><span style="line-height:1;">文件中，同时还会记录在内存缓冲区中。当重完操作完成后，会将所有缓冲区中的日志一次性写入到临时文件中。然后调用原子性的</span><span style="line-height:1;">rename</span><span style="line-height:1;">命令用新的</span><span style="line-height:1;">AOF</span><span style="line-height:1;">文件取代老的</span><span style="line-height:1;">AOF</span><span style="line-height:1;">文件。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">AOF</span><span style="font-size:9pt;color:#333333;line-height:1;">是一个写文件操作，其目的是将操作日志写到磁盘上，所以它也同样会遇到我们上面说的写操作的流程。</span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;"><br />
</span></span>
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">在</span><span style="line-height:1;">Redis</span><span style="line-height:1;">中对</span><span style="line-height:1;">AOF</span><span style="line-height:1;">调用</span><span style="line-height:1;">write</span><span style="line-height:1;">写入后，通过</span><span style="line-height:1;">appendfsync</span><span style="line-height:1;">选项来控制调用</span><span style="line-height:1;">fsync</span><span style="line-height:1;">将其写到磁盘上的时间，下面</span><span style="line-height:1;">appendfsync</span><span style="line-height:1;">的三个设置项，安全强度逐渐变强。</span></span><span></span></span> 
	</p>
	<p>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">1 appendfsync no</span></span></strong><span><span class="apple-converted-space" style="line-height:1;"><span style="font-size:9pt;color:#333333;line-height:1;">&nbsp;</span></span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">当设置</span><span style="line-height:1;">appendfsync</span><span style="line-height:1;">为</span><span style="line-height:1;">no</span><span style="line-height:1;">的时候，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">不会主动调用</span><span style="line-height:1;">fsync</span><span style="line-height:1;">去将</span><span style="line-height:1;">AOF</span><span style="line-height:1;">日志内容同步到磁盘，所以这一切就完全依赖于操作系统的调试了。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">对大多数</span><span style="line-height:1;">Linux</span><span style="line-height:1;">操作系统，是每</span><span style="line-height:1;">30</span><span style="line-height:1;">秒进行一次</span><span style="line-height:1;">fsync</span><span style="line-height:1;">，将缓冲区中的数据写到磁盘上。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">2 appendfsync everysec</span></span></strong><span><span class="apple-converted-space" style="line-height:1;"><span style="font-size:9pt;color:#333333;line-height:1;">&nbsp;</span></span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">当设置</span><span style="line-height:1;">appendfsync</span><span style="line-height:1;">为</span><span style="line-height:1;">everysec</span><span style="line-height:1;">的时候，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">会默认每隔一秒进行一次</span><span style="line-height:1;">fsync</span><span style="line-height:1;">调用，将缓冲区中的数据写到磁盘。但是当这一次的</span><span style="line-height:1;">fsync</span><span style="line-height:1;">调用时长超过</span><span style="line-height:1;">1</span><span style="line-height:1;">秒时。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;line-height:1;">Redis</span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">会采取延迟</span><span style="line-height:1;">fsync</span><span style="line-height:1;">的策略，再等一秒钟。也就是在两秒后再进行</span><span style="line-height:1;">fsync</span><span style="line-height:1;">，这一次的</span><span style="line-height:1;">fsync</span><span style="line-height:1;">就不管会执行多长时间都会进行。这时候由于在</span><span style="line-height:1;">fsync</span><span style="line-height:1;">时文件描述符会被阻塞，所以当前的写操作就会阻塞。所以结论就是，在绝大多数情况下，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">会每隔一秒进行一次</span><span style="line-height:1;">fsync</span><span style="line-height:1;">。</span></span><span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">在最坏的情况下，两秒钟会进行一次</span><span style="line-height:1;">fsync</span><span style="line-height:1;">操作。这一操作在大多数数据库系统中被称为</span><span style="line-height:1;">group commit</span><span style="line-height:1;">，就是组合多次写操作的数据，一次性将日志写到磁盘。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">3 appednfsync always</span></span></strong><span><span class="apple-converted-space" style="line-height:1;"><span style="font-size:9pt;color:#333333;line-height:1;">&nbsp;</span></span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">当设置</span><span style="line-height:1;">appendfsync</span><span style="line-height:1;">为</span><span style="line-height:1;">always</span><span style="line-height:1;">时，每一次写操作都会调用一次</span><span style="line-height:1;">fsync</span><span style="line-height:1;">，这时数据是最安全的，当然，由于每次都会执行</span><span style="line-height:1;">fsync</span><span style="line-height:1;">，所以其性能也会受到影响。</span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"><span style="line-height:1;">对于一般性的业务需求，建议使用</span><span style="line-height:1;">RDB</span><span style="line-height:1;">的方式进行持久化，原因是</span><span style="line-height:1;">RDB</span><span style="line-height:1;">的开销并相比</span><span style="line-height:1;">AOF</span><span style="line-height:1;">日志要低很多，对于那些无法忍数据丢失的应用，建议使用</span><span style="line-height:1;">AOF</span><span style="line-height:1;">日志。</span></span><span></span></span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<p>
		<span><span style="font-size:9pt;color:#333333;"></span> </span> 
	</p>
	<h2>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;"><span style="line-height:1.5;"></span><br />
</span></span></strong> 
	</h2>
	<h2>
		<strong><span style="line-height:1;"><span style="font-size:9pt;font-family:宋体;color:#333333;line-height:1;">集群管理的不同</span></span></strong><span style="font-size:9pt;font-weight:normal;color:#333333;"></span> 
	</h2>
<p margin-left:1px;"=""> <span><span style="font-size:9pt;color:#212529;line-height:1;">Memcached</span><span style="font-size:9pt;color:#212529;"><span style="line-height:1;">是全内存的数据缓冲系统，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">虽然支持数据的持久化，但是全内存毕竟才是其高性能的本质。</span></span></span> 
</p>
<p margin-left:1px;"=""><span style="color:#212529;font-size:9pt;line-height:1;">作为基于内存的存储系统来说，机器物理内存的大小就是系统能够容纳的最大数据量。</span> 
	</p>
<p margin-left:1px;"=""><span style="color:#212529;font-size:9pt;line-height:1;">如果需要处理的数据量超过了单台机器的物理内存大小，就需要构建分布式集群来扩展存储能力。</span> 
</p>
<p margin-left:1px;"=""><span style="font-size:9pt;color:#212529;line-height:1;">Memcached</span><span style="font-size:9pt;color:#212529;"><span style="line-height:1;">本身并不支持分布式，因此只能在客户端通过像一致性哈希这样的分布式算法来实现</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">的分布式存储。</span></span> 
	</p>
<p margin-left:1px;"=""><span style="color:#212529;font-size:9pt;line-height:1;">下图给出了</span><span style="color:#212529;font-size:9pt;line-height:1;">Memcached</span><span style="color:#212529;font-size:9pt;line-height:1;">的分布式存储实现架构。当客户端向</span><span style="color:#212529;font-size:9pt;line-height:1;">Memcached</span><span style="color:#212529;font-size:9pt;line-height:1;">集群发送数据之前，首先会通过内置的分布式算法计算出该条数据的目标节点，然后数据会直接发送到该节点上存储。</span> 
</p>
<p margin-left:1px;"=""><span style="color:#212529;font-size:9pt;line-height:1;">但客户端查询数据时，同样要计算出查询数据所在的节点，然后直接向该节点发送查询请求以获取数据。</span> 
	</p>
<p margin-left:1px;"="" align="center"> <span><a id="ematt:735" href="https://www.qinor.cn/content/uploadfile/202011/18e21606274002.jpg" target="_blank"><img title="点击查看原图" border="0" alt="6.jpg" src="https://www.qinor.cn/content/uploadfile/202011/18e21606274002.jpg" width="392" height="276" /></a><span style="font-size:9pt;color:#212529;"></span></span> 
</p>
<p margin-left:1px;"=""> <span><span style="font-size:9pt;color:#212529;"></span><span></span></span><span style="line-height:1;">&nbsp;</span> 
	</p>
<p text-align:center;margin-left:1px;"="" align="center"> <span style="font-size:9pt;color:#212529;"></span><span style="font-size:9pt;color:#212529;"></span> 
</p>
<p margin-left:1px;"=""> <span><span style="font-size:9pt;color:#212529;"><span style="line-height:1;">相较于</span><span style="line-height:1;">Memcached</span><span style="line-height:1;">只能采用客户端实现分布式存储，</span><span style="line-height:1;">Redis</span><span style="line-height:1;">更偏向于在服务器端构建分布式存储。最新版本的</span><span style="line-height:1;">Redis</span><span style="line-height:1;">已经支持了分布式存储功能。</span></span></span> 
	</p>
<p margin-left:1px;"=""><span style="font-size:9pt;color:#212529;line-height:1;">Redis Cluster</span><span style="font-size:9pt;color:#212529;"><span style="line-height:1;">是一个实现了分布式且允许单点故障的</span><span style="line-height:1;">Redis</span><span style="line-height:1;">高级版本，它没有中心节点，具有线性可伸缩的功能。</span></span> 
</p>
<p margin-left:1px;"=""><span style="color:#212529;font-size:9pt;line-height:1;">下图给出</span><span style="color:#212529;font-size:9pt;line-height:1;">Redis Cluster</span><span style="color:#212529;font-size:9pt;line-height:1;">的分布式存储架构，其中节点与节点之间通过二进制协议进行通信，节点与客户端之间通过</span><span style="color:#212529;font-size:9pt;line-height:1;">ascii</span><span style="color:#212529;font-size:9pt;line-height:1;">协议进行通信。在数据的放置策略上，</span><span style="color:#212529;font-size:9pt;line-height:1;">Redis Cluster</span><span style="color:#212529;font-size:9pt;line-height:1;">将整个</span><span style="color:#212529;font-size:9pt;line-height:1;">key</span><span style="color:#212529;font-size:9pt;line-height:1;">的数值域分成</span><span style="color:#212529;font-size:9pt;line-height:1;">4096</span><span style="color:#212529;font-size:9pt;line-height:1;">个哈希槽，每个节点上可以存储一个或多个哈希槽，也就是说当前</span><span style="color:#212529;font-size:9pt;line-height:1;">Redis Cluster</span><span style="color:#212529;font-size:9pt;line-height:1;">支持的最大节点数就是</span><span style="color:#212529;font-size:9pt;line-height:1;">4096</span><span style="color:#212529;font-size:9pt;line-height:1;">。</span><span style="color:#212529;font-size:9pt;line-height:1;">Redis Cluster</span><span style="color:#212529;font-size:9pt;line-height:1;">使用的分布式算法也很简单：</span><span style="color:#212529;font-size:9pt;line-height:1;">crc16( key ) % HASH_SLOTS_NUMBER</span><span style="color:#212529;font-size:9pt;line-height:1;">。</span> 
	</p>
<span style="font-size:9pt;color:#212529;"></span> 
	<p align="center">
		<a id="ematt:736" href="https://www.qinor.cn/content/uploadfile/202011/fe5d1606274002.jpg" target="_blank"><img title="点击查看原图" border="0" alt="7.jpg" src="https://www.qinor.cn/content/uploadfile/202011/fe5d1606274002.jpg" width="300" height="347" /></a><a id="ematt:737" href="https://www.qinor.cn/content/uploadfile/202011/8cda1606274002.jpg" target="_blank"></a> 
	</p>
<span></span> 
	<p>
		<span></span><span style="line-height:1;">&nbsp;</span> 
	</p>
<p text-align:center;margin-left:1px;"="" align="center"> <span style="font-size:9pt;color:#212529;"></span> 
</p>
<p text-align:center;"="" align="center"> <span style="font-size:9pt;color:#212529;"></span> 
	</p>
<p margin-left:1px;"=""> <span><span style="font-size:9pt;color:#212529;"><span style="line-height:1;">为了保证单点故障下的数据可用性，</span><span style="line-height:1;">Redis Cluster</span><span style="line-height:1;">引入了</span><span style="line-height:1;">Master</span><span style="line-height:1;">节点和</span><span style="line-height:1;">Slave</span><span style="line-height:1;">节点。在</span><span style="line-height:1;">Redis Cluster</span><span style="line-height:1;">中，每个</span><span style="line-height:1;">Master</span><span style="line-height:1;">节点都会有对应的两个用于冗余的</span><span style="line-height:1;">Slave</span><span style="line-height:1;">节点。</span></span><span></span></span> 
</p>
<p margin-left:1px;"=""> <span><span style="font-size:9pt;color:#212529;"><span style="line-height:1;">这样在整个集群中，任意两个节点的宕机都不会导致数据的不可用。当</span><span style="line-height:1;">Master</span><span style="line-height:1;">节点退出后，集群会自动选择一个</span><span style="line-height:1;">Slave</span><span style="line-height:1;">节点成为新的</span><span style="line-height:1;">Master</span><span style="line-height:1;">节点。</span></span></span> 
	</p>
<p margin-left:1px;"=""> <span><span style="font-size:9pt;color:#212529;"></span></span><span style="line-height:1;">&nbsp;</span> 
</p>
<p margin-left:1px;"="" align="center"> <span><a id="ematt:737" href="https://www.qinor.cn/content/uploadfile/202011/8cda1606274002.jpg" target="_blank"><img title="点击查看原图" border="0" alt="8.jpg" src="https://www.qinor.cn/content/uploadfile/202011/thum-8cda1606274002.jpg" width="420" height="168" /></a></span> 
	</p>
<p margin-left:1px;"="" align="center"> <span><span style="font-size:9pt;color:#212529;"></span><span></span></span><span style="line-height:1;">&nbsp;</span> 
</p>
<p class="MsoNormal" style="text-align:center;margin-left:1px;" align="center">
	<span style="font-size:9pt;font-family:宋体;"></span> 
</p>
</span>]]></description>
	<pubDate>Wed, 25 Nov 2020 03:12:54 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-64.html</guid>

</item>
<item>
	<title>字节跳动面试官：请你实现一个大文件上传和断点续传</title>
	<link>https://www.qinor.cn/post-63.html</link>
	<description><![CDATA[<h1 class="heading" data-id="heading-0" style="text-align:left;">
	<span style="font-size:24px;">前言</span> 
</h1>
<p>
	<span style="line-height:1.5;">这段时间面试官都挺忙的，频频出现在博客文章标题，虽然我不是特别想蹭热度，但是实在想不到好的标题了-。-，蹭蹭就蹭蹭 :)</span> 
</p>
<p>
	<span style="line-height:1.5;">事实上我在面试的时候确实被问到了这个问题，而且是一道在线 coding 的编程题，当时虽然思路正确，可惜最终也并不算完全答对</span> 
</p>
<p>
	<span style="line-height:1.5;">结束后花了一段时间整理了下思路，那么究竟该如何实现一个大文件上传，以及在上传中如何实现断点续传的功能呢？</span> 
</p>
<p>
	<span style="line-height:1.5;">本文将从零搭建前端和服务端，实现一个大文件上传和断点续传的 demo</span> 
</p>
<p>
	<span style="line-height:1.5;">前端：</span><code><span style="color:#E56600;line-height:1.5;">vue</span></code> <code><span style="color:#E56600;line-height:1.5;">element-ui</span></code> 
</p>
<p>
	<span style="line-height:1.5;">服务端：</span><code><span style="color:#E56600;line-height:1.5;">nodejs</span></code> 
</p>
<p>
	<code><span style="line-height:1.5;">文章有误解的地方，欢迎指出，将在第一时间改正，有更好的实现方式希望留下你的评论</span></code> 
</p>
<h1 class="heading" data-id="heading-1">
	<span style="font-size:24px;">大文件上传</span> 
</h1>
<h2 class="heading" data-id="heading-2">
	<span style="font-size:18px;">整体思路</span> 
</h2>
<h3 class="heading" data-id="heading-3">
	前端
</h3>
<p>
	前端大文件上传网上的大部分文章已经给出了解决方案，核心是<code><span style="color:#E56600;">利用 Blob.prototype.slice</span></code> 方法，和数组的 slice 方法相似，调用的 slice 方法可以返回<code><span style="color:#E56600;">原文件的某个切片</span></code> 
</p>
<p>
	这样我们就可以根据预先设置好的切片最大数量将文件切分为一个个切片，然后借助 http 的可并发性，同时上传多个切片，这样从原本传一个大文件，变成了<code><span style="color:#E56600;">同时</span></code>传多个小的文件切片，可以大大减少上传时间
</p>
<p>
	另外由于是并发，传输到服务端的顺序可能会发生变化，所以我们还需要给每个切片记录顺序
</p>
<h3 class="heading" data-id="heading-4">
	服务端
</h3>
<p>
	服务端需要负责接受这些切片，并在接收到所有切片后<code><span style="color:#E56600;">合并</span></code>切片
</p>
<p>
	这里又引伸出两个问题
</p>
<ol>
	<li>
		何时合并切片，即切片什么时候传输完成
	</li>
	<li>
		如何合并切片
	</li>
</ol>
<p>
	第一个问题需要前端进行配合，前端在每个切片中都携带切片最大数量的信息，当服务端接受到这个数量的切片时自动合并，也可以额外发一个请求主动通知服务端进行切片的合并
</p>
<p>
	第二个问题，具体如何合并切片呢？这里可以使用 nodejs 的 api <code><span style="color:#E56600;">fs.appendFileSync</span></code>，它可以同步地将数据追加到指定文件，也就是说，当服务端接受到所有切片后，先创建一个最终的文件，然后将所有切片逐步合并到这个文件中
</p>
<p>
	<code><span style="color:#E56600;">talk is cheap,show me the code</span></code>，接着我们用代码实现上面的思路
</p>
<h2 class="heading" data-id="heading-5">
	<span style="font-size:18px;">前端部分</span> 
</h2>
<p>
	前端使用 Vue 作为开发框架，对界面没有太大要求，原生也可以，考虑到美观使用 element-ui 作为 UI 框架
</p>
<h3 class="heading" data-id="heading-6">
	上传控件
</h3>
<p>
	首先创建选择文件的控件，监听 change 事件以及上传按钮。“vue”:
</p>
<pre><code class="hljs vue copyable" lang="vue">&lt;template&gt;
&nbsp; &nbsp;&lt;div&gt;
&nbsp; &nbsp; &lt;input type="file" @change="handleFileChange" /&gt;
&nbsp; &nbsp; &lt;el-button @click="handleUpload"&gt;上传&lt;/el-button&gt;
&nbsp; &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
&nbsp; data: () =&gt; ({
&nbsp; &nbsp; container: {
&nbsp; &nbsp; &nbsp; file: null
&nbsp; &nbsp; }
&nbsp; }),
&nbsp; methods: {
&nbsp; &nbsp; &nbsp;handleFileChange(e) {
&nbsp; &nbsp; &nbsp; const [file] = e.target.files;
&nbsp; &nbsp; &nbsp; if (!file) return;
&nbsp; &nbsp; &nbsp; Object.assign(this.$data, this.$options.data());
&nbsp; &nbsp; &nbsp; this.container.file = file;
&nbsp; &nbsp; },
&nbsp; &nbsp; async handleUpload() {}
&nbsp; }
};
&lt;/script&gt;</code></pre>
<h3 class="heading" data-id="heading-7" style="text-align:right;">
	<span style="text-align:center;font-size:12px;font-weight:normal;"></span> 
</h3>
<h3 class="heading" data-id="heading-7" style="text-align:center;">
</h3>
<h3 class="heading" data-id="heading-7" style="white-space:normal;">
	<span style="text-align:center;white-space:pre;"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-4a471579402702.png" title="点击查看原图" alt="1.png" border="0" width="420" height="73" /></span> 
</h3>
<br />
<h3 class="heading" data-id="heading-7">
	请求逻辑
</h3>
<p>
	考虑到通用性，这里没有用第三方的请求库，而是用原生 XMLHttpRequest 做一层简单的封装来发请求
</p>
<pre>request({
&nbsp; &nbsp; &nbsp; url,
&nbsp; &nbsp; &nbsp; method = "post",
&nbsp; &nbsp; &nbsp; data,
&nbsp; &nbsp; &nbsp; headers = {},
&nbsp; &nbsp; &nbsp; requestList
&nbsp; &nbsp; }) {
&nbsp; &nbsp; &nbsp; return new Promise(resolve =&gt; {
&nbsp; &nbsp; &nbsp; &nbsp; const xhr = new XMLHttpRequest();
&nbsp; &nbsp; &nbsp; &nbsp; xhr.open(method, url);
&nbsp; &nbsp; &nbsp; &nbsp; Object.keys(headers).forEach(key =&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; xhr.setRequestHeader(key, headers[key])
&nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; &nbsp; &nbsp; xhr.send(data);
&nbsp; &nbsp; &nbsp; &nbsp; xhr.onload = e =&gt; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data: e.target.response
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; };
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; }
</pre>
<h3 class="heading" data-id="heading-8">
	上传切片
</h3>
<p>
	接着实现比较重要的上传功能，上传需要做两件事
</p>
<ul>
	<li>
		对文件进行切片
	</li>
	<li>
		将切片传输给服务端
	</li>
</ul>
<pre>"diff":
&lt;template&gt;
&nbsp; &lt;div&gt;
&nbsp; &nbsp; &lt;input type="file" @change="handleFileChange" /&gt;
&nbsp; &nbsp; &lt;el-button @click="handleUpload"&gt;上传&lt;/el-button&gt;
&nbsp; &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
+ const LENGTH = 10; // 切片数量

export default {
&nbsp; data: () =&gt; ({
&nbsp; &nbsp; container: {
&nbsp; &nbsp; &nbsp; file: null,
+&nbsp; &nbsp; &nbsp;data: []
&nbsp; &nbsp; }
&nbsp; }),
&nbsp; methods: {
&nbsp; &nbsp; request() {},
&nbsp; &nbsp; handleFileChange() {},
+&nbsp; &nbsp; // 生成文件切片
+&nbsp; &nbsp; createFileChunk(file, length = LENGTH) {
+&nbsp; &nbsp; &nbsp; const fileChunkList = [];
+&nbsp; &nbsp; &nbsp; const chunkSize = Math.ceil(file.size / length);
+&nbsp; &nbsp; &nbsp; let cur = 0;
+&nbsp; &nbsp; &nbsp; while (cur &lt; file.size) {
+&nbsp; &nbsp; &nbsp; &nbsp; fileChunkList.push({ file: file.slice(cur, cur + chunkSize) });
+&nbsp; &nbsp; &nbsp; &nbsp; cur += chunkSize;
+&nbsp; &nbsp; &nbsp; }
+&nbsp; &nbsp; &nbsp; return fileChunkList;
+&nbsp; &nbsp; },
+&nbsp; &nbsp;// 上传切片
+&nbsp; &nbsp; async uploadChunks() {
+&nbsp; &nbsp; &nbsp; const requestList = this.data
+&nbsp; &nbsp; &nbsp; &nbsp; .map(({ chunk，hash }) =&gt; {
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const formData = new FormData();
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("chunk", chunk);
+<span style="white-space:pre;"> </span>&nbsp; &nbsp;formData.append("hash", hash);
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("filename", this.container.file.name);
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return { formData };
+&nbsp; &nbsp; &nbsp; &nbsp; })
+&nbsp; &nbsp; &nbsp; &nbsp; .map(async ({ formData }) =&gt;
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.request({
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; url: "http://localhost:3000",
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data: formData
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })
+&nbsp; &nbsp; &nbsp; &nbsp; );
+&nbsp; &nbsp; &nbsp; await Promise.all(requestList); // 并发切片
+&nbsp; &nbsp; },
+&nbsp; &nbsp; async handleUpload() {
+&nbsp; &nbsp; &nbsp; if (!this.container.file) return;
+&nbsp; &nbsp; &nbsp; const fileChunkList = this.createFileChunk(this.container.file);
+&nbsp; &nbsp; &nbsp; this.data = fileChunkList.map(({ file }，index) =&gt; ({
+&nbsp; &nbsp; &nbsp; &nbsp; chunk: file,
+&nbsp; &nbsp; &nbsp; &nbsp; hash: this.container.file.name + "-" + index // 文件名 + 数组下标
+&nbsp; &nbsp; &nbsp; }));
+&nbsp; &nbsp; &nbsp; await this.uploadChunks();
+&nbsp; &nbsp; }
&nbsp; }
};
&lt;/script&gt;
</pre>
<p>
	当点击上传按钮时，调用 <code><span style="color:#E56600;">createFileChunk</span></code> 将文件切片，切片数量通过一个常量 Length 控制，这里设置为 10，即将文件分成 10 个切片上传
</p>
<p>
	createFileChunk 内使用 while 循环和 slice 方法将切片放入 <code><span style="color:#E56600;">fileChunkList</span></code> 数组中返回
</p>
<p>
	在生成文件切片时，需要给每个切片一个标识作为 hash，这里暂时使用<code><span style="color:#E56600;">文件名 + 下标</span></code>，这样后端可以知道当前切片是第几个切片，用于之后的合并切片
</p>
<p>
	随后调用 <code><span style="color:#E56600;">uploadChunks</span></code> 上传所有的文件切片，将文件切片，切片 hash，以及文件名放入 FormData 中，再调用上一步的 <code><span style="color:#E56600;">request</span></code> 函数返回一个 proimise，最后调用 Promise.all 并发上传所有的切片
</p>
<h3 class="heading" data-id="heading-9">
	发送合并请求
</h3>
<p>
	这里使用整体思路中提到的第二种合并切片的方式，即前端主动通知服务端进行合并，所以前端还需要额外发请求，服务端接受到这个请求时主动合并切片"diff":
</p>
<pre>&lt;template&gt;
&nbsp; &lt;div&gt;
&nbsp; &nbsp; &lt;input type="file" @change="handleFileChange" /&gt;
&nbsp; &nbsp; &lt;el-button @click="handleUpload"&gt;上传&lt;/el-button&gt;
&nbsp; &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
&nbsp; data: () =&gt; ({
&nbsp; &nbsp; container: {
&nbsp; &nbsp; &nbsp; file: null
&nbsp; &nbsp; },
&nbsp; &nbsp; data: []
&nbsp; }),
&nbsp; methods: {
&nbsp; &nbsp; request() {},
&nbsp; &nbsp; handleFileChange() {},
&nbsp; &nbsp; createFileChunk() {},
&nbsp; &nbsp; // 上传切片，同时过滤已上传的切片
&nbsp; &nbsp; async uploadChunks() {
&nbsp; &nbsp; &nbsp; const requestList = this.data
&nbsp; &nbsp; &nbsp; &nbsp; .map(({ chunk，hash }) =&gt; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const formData = new FormData();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("chunk", chunk);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("hash", hash);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("filename", this.container.file.name);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return { formData };
&nbsp; &nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; &nbsp; .map(async ({ formData }) =&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.request({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; url: "http://localhost:3000",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data: formData
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; &nbsp; await Promise.all(requestList);
+&nbsp; &nbsp; &nbsp; // 合并切片
+&nbsp; &nbsp; &nbsp;await this.mergeRequest();
&nbsp; &nbsp; },
+&nbsp; &nbsp; async mergeRequest() {
+&nbsp; &nbsp; &nbsp; await this.request({
+&nbsp; &nbsp; &nbsp; &nbsp; url: "http://localhost:3000/merge",
+&nbsp; &nbsp; &nbsp; &nbsp; headers: {
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "content-type": "application/json"
+&nbsp; &nbsp; &nbsp; &nbsp; },
+&nbsp; &nbsp; &nbsp; &nbsp; data: JSON.stringify({
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; filename: this.container.file.name
+&nbsp; &nbsp; &nbsp; &nbsp; })
+&nbsp; &nbsp; &nbsp; });
+&nbsp; &nbsp; },&nbsp; &nbsp;&nbsp;
&nbsp; &nbsp; async handleUpload() {}
&nbsp; }
};
&lt;/script&gt;

</pre>
<h2 class="heading" data-id="heading-10">
	服务端部分
</h2>
<p>
	简单使用 http 模块搭建服务端
</p>
<pre>const http = require("http");
const server = http.createServer();

server.on("request", async (req, res) =&gt; {
&nbsp; res.setHeader("Access-Control-Allow-Origin", "*");
&nbsp; res.setHeader("Access-Control-Allow-Headers", "*");
&nbsp; if (req.method === "OPTIONS") {
&nbsp; &nbsp; res.status = 200;
&nbsp; &nbsp; res.end();
&nbsp; &nbsp; return;
&nbsp; }
});

server.listen(3000, () =&gt; console.log("正在监听 3000 端口"));

 </pre>
<h3 class="heading" data-id="heading-11">
	接受切片
</h3>
<p>
	使用 <code><span style="color:#E56600;">multiparty</span></code> 包处理前端传来的 FormData
</p>
<p>
	在 multiparty.parse 的回调中，files 参数保存了 FormData 中文件，fields 参数保存了 FormData 中非文件的字段
</p>
<pre><code class="hljs diff copyable" lang="diff">const http = require("http");
const path = require("path");
const fse = require("fs-extra");
const multiparty = require("multiparty");

const server = http.createServer();
+ const UPLOAD_DIR = path.resolve(__dirname, "..", "target"); // 大文件存储目录

server.on("request", async (req, res) =&gt; {
&nbsp; res.setHeader("Access-Control-Allow-Origin", "*");
&nbsp; res.setHeader("Access-Control-Allow-Headers", "*");
&nbsp; if (req.method === "OPTIONS") {
&nbsp; &nbsp; res.status = 200;
&nbsp; &nbsp; res.end();
&nbsp; &nbsp; return;
&nbsp; }

+&nbsp; const multipart = new multiparty.Form();

+&nbsp; multipart.parse(req, async (err, fields, files) =&gt; {
+&nbsp; &nbsp; if (err) {
+&nbsp; &nbsp; &nbsp; return;
+&nbsp; &nbsp; }
+&nbsp; &nbsp; const [chunk] = files.chunk;
+&nbsp; &nbsp; const [hash] = fields.hash;
+&nbsp; &nbsp; const [filename] = fields.filename;
+&nbsp; &nbsp; const chunkDir = `${UPLOAD_DIR}/${filename}`;

+&nbsp; &nbsp;// 切片目录不存在，创建切片目录
+&nbsp; &nbsp; if (!fse.existsSync(chunkDir)) {
+&nbsp; &nbsp; &nbsp; await fse.mkdirs(chunkDir);
+&nbsp; &nbsp; }

+&nbsp; &nbsp; &nbsp; // fs-extra 专用方法，类似 fs.rename 并且跨平台
+&nbsp; &nbsp; &nbsp; // fs-extra 的 rename 方法 windows 平台会有权限问题
+&nbsp; &nbsp; &nbsp; // https://github.com/meteor/meteor/issues/7852#issuecomment-255767835
+&nbsp; &nbsp; &nbsp; await fse.move(chunk.path, `${chunkDir}/${hash}`);
+&nbsp; &nbsp; res.end("received file chunk");
+&nbsp; });
});

server.listen(3000, () =&gt; console.log("正在监听 3000 端口")); <span class="copy-code-btn"></span></code></pre>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/fb5c1579402987.png" id="ematt:698"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-fb5c1579402987.png" title="点击查看原图" alt="2.png" border="0" width="420" height="89" /></a><br />
<figcaption></figcaption></figure>
<p>
	查看 multiparty 处理后的 chunk 对象，path 是存储临时文件的路径，size 是临时文件大小，在 multiparty 文档中提到可以使用 fs.rename(由于我用的是 fs-extra，它的 rename 方法 windows 平台权限问题，所以换成了 fse.move) 移动临时文件，即移动文件切片
</p>
<p>
	在接受文件切片时，需要先创建存储切片的文件夹，由于前端在发送每个切片时额外携带了唯一值 hash，所以以 hash 作为文件名，将切片从临时路径移动切片文件夹中，最后的结果如下
</p>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/10fb1579403018.png" id="ematt:700"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-10fb1579403018.png" title="点击查看原图" alt="3.png" border="0" width="420" height="222" /></a><br />
<figcaption></figcaption></figure>
<h3 class="heading" data-id="heading-12">
	合并切片
</h3>
<p>
	在接收到前端发送的合并请求后，服务端将文件夹下的所有切片进行合并。
</p>
<pre>“diff”：
const http = require("http");
const path = require("path");
const fse = require("fs-extra");

const server = http.createServer();
const UPLOAD_DIR = path.resolve(__dirname, "..", "target"); // 大文件存储目录

+ const resolvePost = req =&gt;
+&nbsp; &nbsp;new Promise(resolve =&gt; {
+&nbsp; &nbsp; &nbsp;let chunk = "";
+&nbsp; &nbsp; &nbsp;req.on("data", data =&gt; {
+&nbsp; &nbsp; &nbsp; &nbsp;chunk += data;
+&nbsp; &nbsp; &nbsp;});
+&nbsp; &nbsp; &nbsp;req.on("end", () =&gt; {
+&nbsp; &nbsp; &nbsp; &nbsp;resolve(JSON.parse(chunk));
+&nbsp; &nbsp; &nbsp;});
+&nbsp; &nbsp;});

+ // 合并切片
+ const mergeFileChunk = async (filePath, filename) =&gt; {
+&nbsp; &nbsp;const chunkDir = `${UPLOAD_DIR}/${filename}`;
+&nbsp; &nbsp;const chunkPaths = await fse.readdir(chunkDir);
+&nbsp; &nbsp;await fse.writeFile(filePath, "");
+&nbsp; &nbsp;chunkPaths.forEach(chunkPath =&gt; {
+&nbsp; &nbsp; &nbsp;fse.appendFileSync(filePath, fse.readFileSync(`${chunkDir}/${chunkPath}`));
+&nbsp; &nbsp; &nbsp;fse.unlinkSync(`${chunkDir}/${chunkPath}`);
+&nbsp; &nbsp;});
+&nbsp; &nbsp;fse.rmdirSync(chunkDir); // 合并后删除保存切片的目录
+ };

server.on("request", async (req, res) =&gt; {
&nbsp; res.setHeader("Access-Control-Allow-Origin", "*");
&nbsp; res.setHeader("Access-Control-Allow-Headers", "*");
&nbsp; if (req.method === "OPTIONS") {
&nbsp; &nbsp; res.status = 200;
&nbsp; &nbsp; res.end();
&nbsp; &nbsp; return;
&nbsp; }

+&nbsp; &nbsp;if (req.url === "/merge") {
+&nbsp; &nbsp; &nbsp;const data = await resolvePost(req);
+&nbsp; &nbsp; &nbsp;const { filename } = data;
+&nbsp; &nbsp; &nbsp;const filePath = `${UPLOAD_DIR}/${filename}`;
+&nbsp; &nbsp; &nbsp;await mergeFileChunk(filePath, filename);
+&nbsp; &nbsp; &nbsp;res.end(
+&nbsp; &nbsp; &nbsp; &nbsp;JSON.stringify({
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;code: 0,
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;message: "file merged success"
+&nbsp; &nbsp; &nbsp; &nbsp;})
+&nbsp; &nbsp; &nbsp;);
+&nbsp; &nbsp;}

});

server.listen(3000, () =&gt; console.log("正在监听 3000 端口")); </pre>
<p>
	由于前端在发送合并请求时会携带文件名，服务端根据文件名可以找到上一步创建的切片文件夹
</p>
<p>
	接着使用 fs.writeFileSync 先创建一个空文件，这个空文件的文件名就是<strong>切片文件夹名 + 后缀名</strong>组合而成，随后通过 fs.appendFileSync 从切片文件夹中不断将切片合并到空文件中，每次合并完成后删除这个切片，等所有切片都合并完毕后最后删除切片文件夹
</p>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/09dd1579403078.png" id="ematt:702"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-09dd1579403078.png" title="点击查看原图" alt="4.png" border="0" width="420" height="48" /></a><br />
<figcaption></figcaption></figure>
<p>
	至此一个简单的大文件上传就完成了，接下来我们再此基础上扩展一些额外的功能
</p>
<h2 class="heading" data-id="heading-13">
	<span style="font-size:18px;">显示上传进度条</span> 
</h2>
<p>
	上传进度分两种，一个是每个切片的上传进度，另一个是整个文件的上传进度，而整个文件的上传进度是基于每个切片上传进度计算而来，所以我们先实现切片的上传进度
</p>
<h3 class="heading" data-id="heading-14">
	切片进度条
</h3>
<p>
	XMLHttpRequest 原生支持上传进度的监听，只需要监听 upload.onprogress 即可，我们在原来的 request 基础上传入 onProgress 参数，给 XMLHttpRequest 注册监听事件。
</p>
<pre>“diff”：
&nbsp;// xhr
&nbsp; &nbsp; request({
&nbsp; &nbsp; &nbsp; url,
&nbsp; &nbsp; &nbsp; method = "post",
&nbsp; &nbsp; &nbsp; data,
&nbsp; &nbsp; &nbsp; headers = {},
+&nbsp; &nbsp; &nbsp; onProgress = e =&gt; e,
&nbsp; &nbsp; &nbsp; requestList
&nbsp; &nbsp; }) {
&nbsp; &nbsp; &nbsp; return new Promise(resolve =&gt; {
&nbsp; &nbsp; &nbsp; &nbsp; const xhr = new XMLHttpRequest();
+&nbsp; &nbsp; &nbsp; &nbsp;xhr.upload.onprogress = onProgress;
&nbsp; &nbsp; &nbsp; &nbsp; xhr.open(method, url);
&nbsp; &nbsp; &nbsp; &nbsp; Object.keys(headers).forEach(key =&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; xhr.setRequestHeader(key, headers[key])
&nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; &nbsp; &nbsp; xhr.send(data);
&nbsp; &nbsp; &nbsp; &nbsp; xhr.onload = e =&gt; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data: e.target.response
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; };
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; } </pre>
<p>
	由于每个切片都需要触发独立的监听事件，所以还需要一个工厂函数，根据传入的切片返回不同的监听函数
</p>
<p>
	在原先的前端上传逻辑中新增监听函数部分。
</p>
<pre>“diff”：
&nbsp; &nbsp; // 上传切片，同时过滤已上传的切片
&nbsp; &nbsp; async uploadChunks(uploadedList = []) {
&nbsp; &nbsp; &nbsp; const requestList = this.data
+&nbsp; &nbsp; &nbsp; &nbsp;.map(({ chunk,hash,index }) =&gt; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const formData = new FormData();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("chunk", chunk);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("hash", hash);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("filename", this.container.file.name);
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return { formData,index };
&nbsp; &nbsp; &nbsp; &nbsp; })
+&nbsp; &nbsp; &nbsp; &nbsp;.map(async ({ formData,index }) =&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.request({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; url: "http://localhost:3000",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data: formData，
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;onProgress: this.createProgressHandler(this.data[index]),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; &nbsp; await Promise.all(requestList);
&nbsp; &nbsp; &nbsp; &nbsp;// 合并切片
&nbsp; &nbsp; &nbsp; await this.mergeRequest();
&nbsp; &nbsp; },
&nbsp; &nbsp; async handleUpload() {
&nbsp; &nbsp; &nbsp; if (!this.container.file) return;
&nbsp; &nbsp; &nbsp; const fileChunkList = this.createFileChunk(this.container.file);
&nbsp; &nbsp; &nbsp; this.data = fileChunkList.map(({ file }，index) =&gt; ({
&nbsp; &nbsp; &nbsp; &nbsp; chunk: file,
+&nbsp; &nbsp; &nbsp; &nbsp;index,
&nbsp; &nbsp; &nbsp; &nbsp; hash: this.container.file.name + "-" + index
+&nbsp; &nbsp; &nbsp; &nbsp;percentage:0
&nbsp; &nbsp; &nbsp; }));
&nbsp; &nbsp; &nbsp; await this.uploadChunks();
&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;
+&nbsp; &nbsp;createProgressHandler(item) {
+&nbsp; &nbsp; &nbsp; return e =&gt; {
+&nbsp; &nbsp; &nbsp; &nbsp; item.percentage = parseInt(String((e.loaded / e.total) * 100));
+&nbsp; &nbsp; &nbsp; };
+&nbsp; &nbsp; } </pre>
<p>
	每个切片在上传时都会通过监听函数更新 data 数组对应元素的 percentage 属性，之后把将 data 数组放到视图中展示即可
</p>
<h3 class="heading" data-id="heading-15">
	文件进度条
</h3>
<p>
	将每个切片已上传的部分累加，除以整个文件的大小，就能得出当前文件的上传进度，所以这里使用 Vue 计算属性
</p>
<pre>&nbsp; computed: {
&nbsp; &nbsp; &nbsp; &nbsp;uploadPercentage() {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!this.container.file || !this.data.length) return 0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const loaded = this.data
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map(item =&gt; item.size * item.percentage)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce((acc, cur) =&gt; acc + cur);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return parseInt((loaded / this.container.file.size).toFixed(2));
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp;} </pre>
<p>
	最终视图如下
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/82661579403176.png" id="ematt:704"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-82661579403176.png" title="点击查看原图" alt="5.png" border="0" width="420" height="222" /></a> 
</p>
<figure><figcaption></figcaption></figure>
<h1 class="heading" data-id="heading-16">
	断点续传
</h1>
<p>
	断点续传的原理在于前端/服务端需要<code><span style="color:#E56600;">记住</span></code>已上传的切片，这样下次上传就可以跳过之前已上传的部分，有两种方案实现记忆的功能
</p>
<ul>
	<li>
		前端使用 localStorage 记录已上传的切片 hash
	</li>
	<li>
		服务端保存已上传的切片 hash，前端每次上传前向服务端获取已上传的切片
	</li>
</ul>
<p>
	第一种是前端的解决方案，第二种是服务端，而前端方案有一个缺陷，如果换了个浏览器就失去了记忆的效果，所以这里选取后者
</p>
<h2 class="heading" data-id="heading-17">
	生成 hash
</h2>
<p>
	无论是前端还是服务端，都必须要生成文件和切片的 hash，<code><span style="color:#E56600;">之前我们使用文件名 + 切片下标作为切片 hash</span></code>，这样做文件名一旦修改就失去了效果，而事实上只要文件内容不变，hash 就不应该变化，所以正确的做法是<code><span style="color:#E56600;">根据文件内容生成 hash</span></code>，所以我们修改一下 hash 的生成规则
</p>
<p>
	这里用到另一个库 <code><span style="color:#E56600;">spark-md5</span></code>，它可以根据文件内容计算出文件的 hash 值，另外考虑到如果上传一个超大文件，读取文件内容计算 hash 是非常耗费时间的，并且会<code><span style="color:#E56600;">引起 UI 的阻塞</span></code>，导致页面假死状态，所以我们使用 web-worker 在 worker 线程计算 hash，这样用户仍可以在主界面正常的交互
</p>
<p>
	由于实例化 web-worker 时，参数是一个 js 文件路径且不能跨域，所以我们单独创建一个 hash.js 文件放在 public 目录下，另外在 worker 中也是不允许访问 dom 的，但它提供了<code><span style="color:#E56600;">importScripts</span></code> 函数用于导入外部脚本，通过它导入 spark-md5
</p>
<pre>// /public/hash.js
self.importScripts("/spark-md5.min.js"); // 导入脚本

// 生成文件 hash
self.onmessage = e =&gt; {
&nbsp; const { fileChunkList } = e.data;
&nbsp; const spark = new self.SparkMD5.ArrayBuffer();
&nbsp; let percentage = 0;
&nbsp; let count = 0;
&nbsp; const loadNext = index =&gt; {
&nbsp; &nbsp; const reader = new FileReader();
&nbsp; &nbsp; reader.readAsArrayBuffer(fileChunkList[index].file);
&nbsp; &nbsp; reader.onload = e =&gt; {
&nbsp; &nbsp; &nbsp; count++;
&nbsp; &nbsp; &nbsp; spark.append(e.target.result);
&nbsp; &nbsp; &nbsp; if (count === fileChunkList.length) {
&nbsp; &nbsp; &nbsp; &nbsp; self.postMessage({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; percentage: 100,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hash: spark.end()
&nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; self.close();
&nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; percentage += 100 / fileChunkList.length;
&nbsp; &nbsp; &nbsp; &nbsp; self.postMessage({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; percentage
&nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; // 递归计算下一个切片
&nbsp; &nbsp; &nbsp; &nbsp; loadNext(count);
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; };
&nbsp; };
&nbsp; loadNext(0);
};

 </pre>
<p>
	在 worker 线程中，接受文件切片 fileChunkList，利用 FileReader 读取每个切片的 ArrayBuffer 并不断传入 spark-md5 中，每计算完一个切片通过 postMessage 向主线程发送一个进度事件，全部完成后将最终的 hash 发送给主线程
</p>
<p>
	<code><span style="color:#E56600;">spark-md5 需要根据所有切片才能算出一个 hash 值，不能直接将整个文件放入计算，否则即使不同文件也会有相同的 hash，具体可以看官方文档</span></code> 
</p>
<p>
	<a target="_blank" href="https://www.npmjs.com/package/spark-md5" rel="nofollow noopener noreferrer">spark-md5</a> 
</p>
<p>
	接着编写主线程与 worker 线程通讯的逻辑。
</p>
<pre>“diff”：
+<span style="white-space:pre;"> </span>&nbsp; &nbsp;// 生成文件 hash（web-worker）
+&nbsp; &nbsp; calculateHash(fileChunkList) {
+&nbsp; &nbsp; &nbsp; return new Promise(resolve =&gt; {
+&nbsp; &nbsp; &nbsp; &nbsp;// 添加 worker 属性
+&nbsp; &nbsp; &nbsp; &nbsp; this.container.worker = new Worker("/hash.js");
+&nbsp; &nbsp; &nbsp; &nbsp; this.container.worker.postMessage({ fileChunkList });
+&nbsp; &nbsp; &nbsp; &nbsp; this.container.worker.onmessage = e =&gt; {
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const { percentage, hash } = e.data;
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.hashPercentage = percentage;
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (hash) {
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve(hash);
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
+&nbsp; &nbsp; &nbsp; &nbsp; };
+&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; },
&nbsp; &nbsp; async handleUpload() {
&nbsp; &nbsp; &nbsp; if (!this.container.file) return;
&nbsp; &nbsp; &nbsp; const fileChunkList = this.createFileChunk(this.container.file);
+&nbsp; &nbsp; &nbsp;this.container.hash = await this.calculateHash(fileChunkList);
&nbsp; &nbsp; &nbsp; this.data = fileChunkList.map(({ file }，index) =&gt; ({
+&nbsp; &nbsp; &nbsp; &nbsp;fileHash: this.container.hash,
&nbsp; &nbsp; &nbsp; &nbsp; chunk: file,
&nbsp; &nbsp; &nbsp; &nbsp; hash: this.container.file.name + "-" + index, // 文件名 + 数组下标
&nbsp; &nbsp; &nbsp; &nbsp; percentage:0
&nbsp; &nbsp; &nbsp; }));
&nbsp; &nbsp; &nbsp; await this.uploadChunks();
&nbsp; &nbsp; }&nbsp;&nbsp;
</pre>
<p>
	主线程使用 <code><span style="color:#E56600;">postMessage</span></code> 给 worker 线程传入所有切片 fileChunkList，并监听 worker 线程发出的 postMessage 事件拿到文件 hash
</p>
<p>
	加上显示计算 hash 的进度条，看起来像这样
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/f19c1579403378.png" id="ematt:706"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-f19c1579403378.png" title="点击查看原图" alt="6.png" border="0" width="420" height="51" /></a> 
</p>
<figure><figcaption></figcaption></figure>
<p>
	至此前端需要将之前用文件名作为 hash 的地方改写为 workder 返回的这个 hash
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/9eb91579403387.png" id="ematt:708"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-9eb91579403387.png" title="点击查看原图" alt="7.png" border="0" width="420" height="232" /></a> 
</p>
<figure><figcaption></figcaption></figure>
<p>
	服务端则使用 hash 作为切片文件夹名，hash + 下标作为切片名，hash + 扩展名作为文件名，没有新增的逻辑
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/602e1579403401.png" id="ematt:710"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-602e1579403401.png" title="点击查看原图" alt="8.png" border="0" width="420" height="174" /></a> 
</p>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/7afb1579403409.png" id="ematt:712"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-7afb1579403409.png" title="点击查看原图" alt="9.png" border="0" width="420" height="65" /></a><br />
</figure><figure><figcaption></figcaption></figure>
<h2 class="heading" data-id="heading-18">
	文件秒传
</h2>
<p>
	在实现断点续传前先简单介绍一下文件秒传
</p>
<p>
	所谓的文件秒传，即在服务端已经存在了上传的资源，所以当用户<code><span style="color:#E56600;">再次上传</span></code>时会直接提示上传成功
</p>
<p>
	文件秒传需要依赖上一步生成的 hash，即在<code><span style="color:#E56600;">上传前</span></code>，先计算出文件 hash，并把 hash 发送给服务端进行验证，由于 hash 的唯一性，所以一旦服务端能找到 hash 相同的文件，则直接返回上传成功的信息即可。
</p>
<pre>“diff”：
+&nbsp; &nbsp; async verifyUpload(filename, fileHash) {
+&nbsp; &nbsp; &nbsp; &nbsp;const { data } = await this.request({
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;url: "http://localhost:3000/verify",
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;headers: {
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;"content-type": "application/json"
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;},
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;data: JSON.stringify({
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;filename,
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;fileHash
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;})
+&nbsp; &nbsp; &nbsp; &nbsp;});
+&nbsp; &nbsp; &nbsp; &nbsp;return JSON.parse(data);
+&nbsp; &nbsp; &nbsp;},
&nbsp; &nbsp;async handleUpload() {
&nbsp; &nbsp; &nbsp; if (!this.container.file) return;
&nbsp; &nbsp; &nbsp; const fileChunkList = this.createFileChunk(this.container.file);
&nbsp; &nbsp; &nbsp; this.container.hash = await this.calculateHash(fileChunkList);
+&nbsp; &nbsp; &nbsp;const { shouldUpload } = await this.verifyUpload(
+&nbsp; &nbsp; &nbsp; &nbsp;this.container.file.name,
+&nbsp; &nbsp; &nbsp; &nbsp;this.container.hash
+&nbsp; &nbsp; &nbsp;);
+&nbsp; &nbsp; &nbsp;if (!shouldUpload) {
+&nbsp; &nbsp; &nbsp; &nbsp;this.$message.success("秒传：上传成功");
+&nbsp; &nbsp; &nbsp; &nbsp;return;
+&nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp;this.data = fileChunkList.map(({ file }, index) =&gt; ({
&nbsp; &nbsp; &nbsp; &nbsp; fileHash: this.container.hash,
&nbsp; &nbsp; &nbsp; &nbsp; index,
&nbsp; &nbsp; &nbsp; &nbsp; hash: this.container.hash + "-" + index,
&nbsp; &nbsp; &nbsp; &nbsp; chunk: file,
&nbsp; &nbsp; &nbsp; &nbsp; percentage: 0
&nbsp; &nbsp; &nbsp; }));
&nbsp; &nbsp; &nbsp; await this.uploadChunks();
&nbsp; &nbsp; }&nbsp;&nbsp;

</pre>
<p>
	秒传其实就是给用户看的障眼法，实质上根本没有上传
</p>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/586e1579403469.png" id="ematt:714"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-586e1579403469.png" title="点击查看原图" alt="10.png" border="0" width="420" height="98" /></a><br />
<figcaption></figcaption></figure>
<p>
	:)
</p>
<p>
	服务端的逻辑非常简单，新增一个验证接口，验证文件是否存在即可。
</p>
<pre>“diff”：
+ const extractExt = filename =&gt;
+&nbsp; filename.slice(filename.lastIndexOf("."), filename.length); // 提取后缀名
const UPLOAD_DIR = path.resolve(__dirname, "..", "target"); // 大文件存储目录

const resolvePost = req =&gt;
&nbsp; new Promise(resolve =&gt; {
&nbsp; &nbsp; let chunk = "";
&nbsp; &nbsp; req.on("data", data =&gt; {
&nbsp; &nbsp; &nbsp; chunk += data;
&nbsp; &nbsp; });
&nbsp; &nbsp; req.on("end", () =&gt; {
&nbsp; &nbsp; &nbsp; resolve(JSON.parse(chunk));
&nbsp; &nbsp; });
&nbsp; });

server.on("request", async (req, res) =&gt; {
&nbsp; if (req.url === "/verify") {
+&nbsp; &nbsp; const data = await resolvePost(req);
+&nbsp; &nbsp; const { fileHash, filename } = data;
+&nbsp; &nbsp; const ext = extractExt(filename);
+&nbsp; &nbsp; const filePath = `${UPLOAD_DIR}/${fileHash}${ext}`;
+&nbsp; &nbsp; if (fse.existsSync(filePath)) {
+&nbsp; &nbsp; &nbsp; res.end(
+&nbsp; &nbsp; &nbsp; &nbsp; JSON.stringify({
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shouldUpload: false
+&nbsp; &nbsp; &nbsp; &nbsp; })
+&nbsp; &nbsp; &nbsp; );
+&nbsp; &nbsp; } else {
+&nbsp; &nbsp; &nbsp; res.end(
+&nbsp; &nbsp; &nbsp; &nbsp; JSON.stringify({
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shouldUpload: true
+&nbsp; &nbsp; &nbsp; &nbsp; })
+&nbsp; &nbsp; &nbsp; );
+&nbsp; &nbsp; }
&nbsp; }
});
server.listen(3000, () =&gt; console.log("正在监听 3000 端口"));
</pre>
<h2 class="heading" data-id="heading-19">
	暂停上传
</h2>
<p>
	讲完了生成 hash 和文件秒传，回到断点续传
</p>
<p>
	断点续传顾名思义即断点 + 续传，所以我们第一步先实现“断点”，也就是暂停上传
</p>
<p>
	原理是使用 XMLHttpRequest 的 <code><span style="color:#E56600;">abort</span></code> 方法，可以取消一个 xhr 请求的发送，为此我们需要将上传每个切片的 xhr 对象保存起来，我们再改造一下 request 方法。
</p>
<pre>“diff”：
&nbsp; &nbsp;request({
&nbsp; &nbsp; &nbsp; url,
&nbsp; &nbsp; &nbsp; method = "post",
&nbsp; &nbsp; &nbsp; data,
&nbsp; &nbsp; &nbsp; headers = {},
&nbsp; &nbsp; &nbsp; onProgress = e =&gt; e,
+&nbsp; &nbsp; &nbsp;requestList
&nbsp; &nbsp; }) {
&nbsp; &nbsp; &nbsp; return new Promise(resolve =&gt; {
&nbsp; &nbsp; &nbsp; &nbsp; const xhr = new XMLHttpRequest();
&nbsp; &nbsp; &nbsp; &nbsp; xhr.upload.onprogress = onProgress;
&nbsp; &nbsp; &nbsp; &nbsp; xhr.open(method, url);
&nbsp; &nbsp; &nbsp; &nbsp; Object.keys(headers).forEach(key =&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; xhr.setRequestHeader(key, headers[key])
&nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; &nbsp; &nbsp; xhr.send(data);
&nbsp; &nbsp; &nbsp; &nbsp; xhr.onload = e =&gt; {
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 将请求成功的 xhr 从列表中删除
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (requestList) {
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const xhrIndex = requestList.findIndex(item =&gt; item === xhr);
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; requestList.splice(xhrIndex, 1);
+&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data: e.target.response
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; };
+&nbsp; &nbsp; &nbsp; &nbsp; // 暴露当前 xhr 给外部
+&nbsp; &nbsp; &nbsp; &nbsp; requestList?.push(xhr);
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; },

</pre>
<p>
	这样在上传切片时传入 requestList 数组作为参数，request 方法就会将所有的 xhr 保存在数组中了
</p>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/59b21579403542.png" id="ematt:716"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-59b21579403542.png" title="点击查看原图" alt="11.png" border="0" width="420" height="430" /></a><br />
<figcaption></figcaption></figure>
<p>
	每当一个切片上传成功时，将对应的 xhr 从 requestList 中删除，所以 requestList 中只保存<code><span style="color:#E56600;">正在上传切片的 xhr</span></code> 
</p>
<p>
	之后新建一个暂停按钮，当点击按钮时，调用保存在 requestList 中 xhr 的 abort 方法，即取消并清空所有正在上传的切片
</p>
<pre>&nbsp;handlePause() {
&nbsp; &nbsp; this.requestList.forEach(xhr =&gt; xhr?.abort());
&nbsp; &nbsp; this.requestList = [];
} </pre>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/9eb61579403614.png" id="ematt:718"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-9eb61579403614.png" title="点击查看原图" alt="12.png" border="0" width="420" height="50" /></a><br />
<figcaption></figcaption></figure>
<p>
	点击暂停按钮可以看到 xhr 都被取消了
</p>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/c00b1579403644.png" id="ematt:720"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-c00b1579403644.png" title="点击查看原图" alt="13.png" border="0" width="420" height="109" /></a><br />
<figcaption></figcaption></figure>
<h2 class="heading" data-id="heading-20">
	恢复上传
</h2>
<p>
	之前在介绍断点续传的时提到使用第二种服务端存储的方式实现续传
</p>
<p>
	由于当文件切片上传后，服务端会建立一个文件夹存储所有上传的切片，所以每次前端上传前可以调用一个接口，服务端将已上传的切片的切片名返回，前端再跳过这些已经上传切片，这样就实现了“续传”的效果
</p>
<p>
	而这个接口可以和之前秒传的验证接口合并，前端每次上传前发送一个验证的请求，返回两种结果
</p>
<ul>
	<li>
		服务端已存在该文件，不需要再次上传
	</li>
	<li>
		服务端不存在该文件或者已上传部分文件切片，通知前端进行上传，并把<strong>已上传</strong>的文件切片返回给前端
	</li>
</ul>
<p>
	所以我们改造一下之前文件秒传的服务端验证接口。
</p>
<p>
	<br />
</p>
<pre class="prettyprint linenums">const extractExt = filename =&gt;
  filename.slice(filename.lastIndexOf("."), filename.length); // 提取后缀名
const UPLOAD_DIR = path.resolve(__dirname, "..", "target"); // 大文件存储目录

const resolvePost = req =&gt;
  new Promise(resolve =&gt; {
    let chunk = "";
    req.on("data", data =&gt; {
      chunk += data;
    });
    req.on("end", () =&gt; {
      resolve(JSON.parse(chunk));
    });
  });
  
+  // 返回已经上传切片名列表
+ const createUploadedList = async fileHash =&gt;
+   fse.existsSync(`${UPLOAD_DIR}/${fileHash}`)
+    ? await fse.readdir(`${UPLOAD_DIR}/${fileHash}`)
+    : [];

server.on("request", async (req, res) =&gt; {
  if (req.url === "/verify") {
    const data = await resolvePost(req);
    const { fileHash, filename } = data;
    const ext = extractExt(filename);
    const filePath = `${UPLOAD_DIR}/${fileHash}${ext}`;
    if (fse.existsSync(filePath)) {
      res.end(
        JSON.stringify({
          shouldUpload: false
        })
      );
    } else {
      res.end(
        JSON.stringify({
          shouldUpload: true，
+         uploadedList: await createUploadedList(fileHash)
        })
      );
    }
  }
});
server.listen(3000, () =&gt; console.log("正在监听 3000 端口"));</pre>
<p>
	<br />
</p>
<p>
	接着回到前端，前端有两个地方需要调用验证的接口
</p>
<ul>
	<li>
		点击上传时，检查是否需要上传和已上传的切片
	</li>
	<li>
		点击暂停后的恢复上传，返回已上传的切片
	</li>
</ul>
<p>
	新增恢复按钮并改造原来上传切片的逻辑。
</p>
<p>
	<br />
</p>
<pre class="prettyprint linenums">“diff”：
&lt;template&gt;
&nbsp; &lt;div id="app"&gt;
&nbsp; &nbsp; &nbsp; &lt;input
&nbsp; &nbsp; &nbsp; &nbsp; type="file"
&nbsp; &nbsp; &nbsp; &nbsp; @change="handleFileChange"
&nbsp; &nbsp; &nbsp; /&gt;
&nbsp; &nbsp; &nbsp; &nbsp;&lt;el-button @click="handleUpload"&gt;上传&lt;/el-button&gt;
&nbsp; &nbsp; &nbsp; &nbsp;&lt;el-button @click="handlePause" v-if="isPaused"&gt;暂停&lt;/el-button&gt;
+&nbsp; &nbsp; &nbsp; &lt;el-button @click="handleResume" v-else&gt;恢复&lt;/el-button&gt;
&nbsp; &nbsp; &nbsp; //...
&nbsp; &nbsp; &lt;/div&gt;
&lt;/template&gt;

+&nbsp; &nbsp;async handleResume() {
+&nbsp; &nbsp; &nbsp; const { uploadedList } = await this.verifyUpload(
+&nbsp; &nbsp; &nbsp; &nbsp; this.container.file.name,
+&nbsp; &nbsp; &nbsp; &nbsp; this.container.hash
+&nbsp; &nbsp; &nbsp; );
+&nbsp; &nbsp; &nbsp; await this.uploadChunks(uploadedList);
&nbsp; &nbsp; },
&nbsp; &nbsp; async handleUpload() {
&nbsp; &nbsp; &nbsp; if (!this.container.file) return;
&nbsp; &nbsp; &nbsp; const fileChunkList = this.createFileChunk(this.container.file);
&nbsp; &nbsp; &nbsp; this.container.hash = await this.calculateHash(fileChunkList);

+&nbsp; &nbsp; &nbsp;const { shouldUpload, uploadedList } = await this.verifyUpload(
&nbsp; &nbsp; &nbsp; &nbsp; this.container.file.name,
&nbsp; &nbsp; &nbsp; &nbsp; this.container.hash
&nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; &nbsp; if (!shouldUpload) {
&nbsp; &nbsp; &nbsp; &nbsp; this.$message.success("秒传：上传成功");
&nbsp; &nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; this.data = fileChunkList.map(({ file }, index) =&gt; ({
&nbsp; &nbsp; &nbsp; &nbsp; fileHash: this.container.hash,
&nbsp; &nbsp; &nbsp; &nbsp; index,
&nbsp; &nbsp; &nbsp; &nbsp; hash: this.container.hash + "-" + index,
&nbsp; &nbsp; &nbsp; &nbsp; chunk: file，
&nbsp; &nbsp; &nbsp; &nbsp; percentage: 0
&nbsp; &nbsp; &nbsp; }));

+&nbsp; &nbsp; &nbsp; await this.uploadChunks(uploadedList);
&nbsp; &nbsp; },
&nbsp; &nbsp;// 上传切片，同时过滤已上传的切片
+&nbsp; &nbsp;async uploadChunks(uploadedList = []) {
&nbsp; &nbsp; &nbsp; const requestList = this.data
+&nbsp; &nbsp; &nbsp; &nbsp; .filter(({ hash }) =&gt; !uploadedList.includes(hash))
&nbsp; &nbsp; &nbsp; &nbsp; .map(({ chunk, hash, index }) =&gt; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const formData = new FormData();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("chunk", chunk);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("hash", hash);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("filename", this.container.file.name);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; formData.append("fileHash", this.container.hash);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return { formData, index };
&nbsp; &nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; &nbsp; .map(async ({ formData, index }) =&gt;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.request({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; url: "http://localhost:3000",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data: formData,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onProgress: this.createProgressHandler(this.data[index]),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; requestList: this.requestList
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; &nbsp; await Promise.all(requestList);
&nbsp; &nbsp; &nbsp; // 之前上传的切片数量 + 本次上传的切片数量 = 所有切片数量时
&nbsp; &nbsp; &nbsp; // 合并切片
+&nbsp; &nbsp; &nbsp; if (uploadedList.length + requestList.length === this.data.length) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;await this.mergeRequest();
+&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

</pre>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/7b6f1579403846.png" id="ematt:722"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-7b6f1579403846.png" title="点击查看原图" alt="14.png" border="0" width="420" height="50" /></a><br />
<figcaption></figcaption></figure>
<p>
	这里给原来上传切片的函数新增 uploadedList 参数，即上图中服务端返回的切片名列表，通过 filter 过滤掉已上传的切片，并且由于新增了已上传的部分，所以之前合并接口的触发条件做了一些改动
</p>
<p>
	到这里断点续传的功能基本完成了
</p>
<h2 class="heading" data-id="heading-21">
	进度条改进
</h2>
<p>
	虽然实现了断点续传，但还需要修改一下进度条的显示规则，否则在暂停上传/接收到已上传切片时的进度条会出现偏差
</p>
<h3 class="heading" data-id="heading-22">
	切片进度条
</h3>
<p>
	由于在点击上传/恢复上传时，会调用验证接口返回已上传的切片，所以需要将已上传切片的进度变成 100%。
</p>
<pre>“diff”：
&nbsp; &nbsp;async handleUpload() {
&nbsp; &nbsp; &nbsp; if (!this.container.file) return;
&nbsp; &nbsp; &nbsp; const fileChunkList = this.createFileChunk(this.container.file);
&nbsp; &nbsp; &nbsp; this.container.hash = await this.calculateHash(fileChunkList);
&nbsp; &nbsp; &nbsp; const { shouldUpload, uploadedList } = await this.verifyUpload(
&nbsp; &nbsp; &nbsp; &nbsp; this.container.file.name,
&nbsp; &nbsp; &nbsp; &nbsp; this.container.hash
&nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; &nbsp; if (!shouldUpload) {
&nbsp; &nbsp; &nbsp; &nbsp; this.$message.success("秒传：上传成功");
&nbsp; &nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; this.data = fileChunkList.map(({ file }, index) =&gt; ({
&nbsp; &nbsp; &nbsp; &nbsp; fileHash: this.container.hash,
&nbsp; &nbsp; &nbsp; &nbsp; index,
&nbsp; &nbsp; &nbsp; &nbsp; hash: this.container.hash + "-" + index,
&nbsp; &nbsp; &nbsp; &nbsp; chunk: file,
+&nbsp; &nbsp; &nbsp; &nbsp;percentage: uploadedList.includes(index) ? 100 : 0
&nbsp; &nbsp; &nbsp; }));
&nbsp; &nbsp; &nbsp; await this.uploadChunks(uploadedList);
&nbsp; &nbsp; }, </pre>
<p>
	uploadedList 会返回已上传的切片，在遍历所有切片时判断当前切片是否在已上传列表里即可
</p>
<h3 class="heading" data-id="heading-23">
	文件进度条
</h3>
<p>
	之前说到文件进度条是一个计算属性，根据所有切片的上传进度计算而来，这就遇到了一个问题
</p>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/d6421579403911.png" id="ematt:724"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-d6421579403911.png" title="点击查看原图" alt="15.png" border="0" width="420" height="181" /></a><br />
<figcaption></figcaption></figure>
<p>
	点击暂停会取消并清空切片的 xhr 请求，此时如果已经上传了一部分，就会发现文件进度条有<code>倒退</code>的现象
</p>
<figure style="text-align:center;"><a target="_blank" href="https://www.qinor.cn/content/uploadfile/202001/1e411579403923.png" id="ematt:726"><img src="https://www.qinor.cn/content/uploadfile/202001/thum-1e411579403923.png" title="点击查看原图" alt="16.png" border="0" width="420" height="178" /></a><br />
<figcaption></figcaption></figure>
<p>
	当点击恢复时，由于重新创建了 xhr 导致切片进度清零，所以总进度条就会倒退
</p>
<p>
	解决方案是创建一个“假”的进度条，这个假进度条基于文件进度条，但只会停止和增加，然后给用户展示这个假的进度条
</p>
<p>
	这里我们使用 Vue 的监听属性。
</p>
<pre>“diff”：
&nbsp; data: () =&gt; ({
+&nbsp; &nbsp; fakeUploadPercentage: 0
&nbsp; }),
&nbsp; computed: {
&nbsp; &nbsp; uploadPercentage() {
&nbsp; &nbsp; &nbsp; if (!this.container.file || !this.data.length) return 0;
&nbsp; &nbsp; &nbsp; const loaded = this.data
&nbsp; &nbsp; &nbsp; &nbsp; .map(item =&gt; item.size * item.percentage)
&nbsp; &nbsp; &nbsp; &nbsp; .reduce((acc, cur) =&gt; acc + cur);
&nbsp; &nbsp; &nbsp; return parseInt((loaded / this.container.file.size).toFixed(2));
&nbsp; &nbsp; }
&nbsp; },&nbsp;&nbsp;
&nbsp; watch: {
+&nbsp; &nbsp; uploadPercentage(now) {
+&nbsp; &nbsp; &nbsp; if (now &gt; this.fakeUploadPercentage) {
+&nbsp; &nbsp; &nbsp; &nbsp; this.fakeUploadPercentage = now;
+&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; },

</pre>
<p>
	当 uploadPercentage 即真的文件进度条增加时，fakeUploadPercentage 也增加，一旦文件进度条后退，假的进度条只需停止即可
</p>
<p>
	至此一个大文件上传 + 断点续传的解决方案就完成了
</p>
<h1 class="heading" data-id="heading-24">
	总结
</h1>
<p>
	大文件上传
</p>
<ul>
	<li>
		前端上传大文件时使用 Blob.prototype.slice 将文件切片，并发上传多个切片，最后发送一个合并的请求通知服务端合并切片
	</li>
	<li>
		服务端接收切片并存储，收到合并请求后使用 fs.appendFileSync 对多个切片进行合并
	</li>
	<li>
		原生 XMLHttpRequest 的 upload.onprogress 对切片上传进度的监听
	</li>
	<li>
		使用 Vue 计算属性根据每个切片的进度算出整个文件的上传进度
	</li>
</ul>
<p>
	断点续传
</p>
<ul>
	<li>
		使用 spark-md5 根据文件内容算出文件 hash
	</li>
	<li>
		通过 hash 可以判断服务端是否已经上传该文件，从而直接提示用户上传成功（秒传）
	</li>
	<li>
		通过 XMLHttpRequest 的 abort 方法暂停切片的上传
	</li>
	<li>
		上传前服务端返回已经上传的切片名，前端跳过这些切片的上传
	</li>
</ul>
<h1 class="heading" data-id="heading-25">
	反馈的问题
</h1>
<p>
	部分功能由于不方便测试，这里列出评论区收集到的一些问题
</p>
<ul>
	<li>
		应使用文件大小切片，而并非文件数量
	</li>
	<li>
		没有做切片上传失败的处理
	</li>
	<li>
		使用 web socket 由服务端发送进度信息
	</li>
	<li>
		打开页面没有自动获取上传切片，而需要主动再次上传一次后才显示
	</li>
</ul>
<p>
	<br />
</p>
作者：yeyan1996<br />
链接：https://juejin.im/post/5dff8a26e51d4558105420ed<br />
来源：掘金<br />
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
<p>
	<br />
</p>]]></description>
	<pubDate>Sun, 19 Jan 2020 02:42:18 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-63.html</guid>

</item>
<item>
	<title>人生不就是这样吗？</title>
	<link>https://www.qinor.cn/post-61.html</link>
	<description><![CDATA[<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	人的一生不就是这样的吗
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	18岁，你读了大学。
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	20岁，你大二结束，开始悔恨
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	自己前两年幼稚的行为
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	于是开始努力
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	22岁你大学毕业了，却发现
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	找不到一份令自己满意的工作
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	26岁，你看着身边的人都结了婚
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	婚礼的份子钱逐年递增
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	春节回家，父母从带你串亲戚
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	变成了带你去见相亲对象
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	见了十几个姑娘
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你每次都觉得和那个她比
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	差了一点
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	28岁那年，你遇到了一个和你遭遇差不多的姑娘
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你们有一搭没一搭的聊着
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	她说：你还不错
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你喝了一口可乐说：你也是
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你还不确定喜不喜欢她
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	双方家长就已经摆好了订婚宴
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	结婚的前一周，你和朋友出去喝酒
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你说，不想结婚
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	朋友说，你啊，就是想太多。谁不是这么过来的？
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	29岁，你们终于结了婚
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	婚礼办的不大不小，朋友来的不多不少
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	攒了几年想要去实现理想的钱
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	搭在了这一场百人的私人庙会上
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	&nbsp;
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	婚礼进行到中间
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	司仪带着标准的商业化微笑
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	对着台下的亲朋喊道
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	要不要让他们亲一个！
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	台下那些人跟着一起起哄
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	不知道为什么
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你简简单单的亲了一口
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	俩人恢复到了一开始的站位
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你小声说了一句：我爱你
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	那个昨天还看不惯你倒腾模型的新娘
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	愣了一下说：我也爱你
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你不确定她是不是对你说的
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	就像你不确定是不是对她说的一样
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	婚礼结束后，并没有你想象的浪漫
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你听着外屋的新娘一笔一笔的算着份子钱
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	想着不过才两年，怎么就变成这样了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	想着想着，洞房夜就睡着了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	30岁，她怀孕了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	辞掉了工作，在家养胎
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你在公司逐渐有了点地位
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	手里管着十来个人
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	独立负责一个项目
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	结婚前陪嫁的那辆20万左右的车
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	也变成了你一个人的独享
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	但你依然不敢放松
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	每次加班
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	电话那头都是抱怨与委屈
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	但你不能争辩什么
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	谁让她怀了你的孩子
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	在这一刻
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	不论是她的父母还是你的父母
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	都无条件的站在这一边
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	31岁，孩子落地了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	前前后后连孕检带住院费花了10万块钱
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	不过无所谓
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你看着你的孩子，怎么看怎么喜欢
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	高兴的仿佛这是你的新生
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	32岁，这是人生最不愿意重复的一年
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	平均睡眠3小时
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	孩子每一个小时都要闹腾一次
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	第二天拖着睡不醒的眼睛去上班
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	老板说你上班不干活
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	回家媳妇说你不干活
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你想了半天不明白，那谁干活呢？
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	那辆开了3年的车
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	成为了你真正的家
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你不在抱怨路上拥堵的交通
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你甚至开始希望
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	再多堵一会
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	回到家，你关了发动机
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	在车上点了一根烟
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	这是你每天最幸福的十分钟
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	车前是功名利禄，车尾是柴米油盐
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	35岁 你因为身体越来越差
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	加班越来越少
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	晋升的速度也越来越缓慢
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	那天下班，媳妇告诉你
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	孩子要上幼儿园了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	双语的一个月3000
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你皱了皱眉头，那边就已经不耐烦了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	“四单元的老王家孩子，一个月6000”
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	“你已经这样了，你想让孩子也输？”
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你没说话，回屋给媳妇转了6000块钱
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	这笔钱，你原本打算给自己过个生日，买个新电脑
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	38岁，孩子上了一年级
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	老师说一年级最关键，打好基础很重要
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你笑着说，是是是，老师您多照顾
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	新生接待的老师看着你不明事理的脸
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	给你指了一条明路
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	“课外辅导班，一个月2200”
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	40岁的时候，孩子上了三年级
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	老师说，三年级，最关键，承上启下很重要
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你笑着说：是是是，正打算再报个补习班
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	44岁，孩子上了初中
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	有一天回到家，她对你说
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	爸爸，我想学钢琴
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你没什么犹豫的
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你以为这些年，你已经习惯了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	但那句“爸爸现在买不起”你始终说不出口
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	好在孩子比较懂事
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	她说：爸爸没事，要不我先学陶笛也可以
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你看着这么懂事的孩子，却开心不起来
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	46岁，孩子上了一个不好不差的高中
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	有一天你在开会，接到了老师的电话
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	电话里说你的孩子在学校打架了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	叫你去一趟
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你唯唯诺诺的
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	和那个比你还小5岁的领导请了个假
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	到学校又被老师训了一通
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	无非台词就是那一句
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你们做家长的就知道工作，能不能陪陪孩子
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你看着这个老师，有点可笑
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	好像当时说：
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	家长在外辛苦点
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	多赚点钱让孩子多补补课的和他不是一个人
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	50岁，孩子上了大学
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	很争气，是一个一本
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	他学的专业你有点看不懂
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你只知道工作不一定好找
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	而且学费还死贵
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你和他深夜想聊聊
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	准备了半斤白酒，一碟花生米
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你说着那些曾经你最讨厌的话
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	还是要为以后工作着想
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	挑个热门的专业
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	活着比热爱重要
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你们从交流变成了争吵
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你发现，你老了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	老到可能都打不过这个18岁的孩子
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你说不过他，只能说一句：我是你爸爸！
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	孩子看着你，知道再怎么争辩都没用
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	这场确立你最后威严的酒局不欢而散
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你听的不真切
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	在孩子回自己屋的路上好像叨叨了一句
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	“我不想活的像你一样”
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	怎么就哭了呢？50岁的人了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	一定是酒太辣了，对不对
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	一定是酒太辣了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	55岁，孩子工作了，似乎有一点理解你了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	但你却反了过来，你说不要妥协
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	56岁，孩子也结婚了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你问他喜欢那个姑娘么
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	他愣了愣说：喜欢吧
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	60岁，辛苦了一辈子，想出去走走
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	身边的那个人过了30年
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你依旧分不清到底喜不喜欢
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你们开始规划旅游路线
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	这么多年了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你们还是存在分歧，还是在争吵
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	某个瞬间，你觉得
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	这样可能也挺好
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	一切都准备好了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	儿子说：爸妈，我工作太忙了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	可以帮我照顾一下孩子么
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你们退了机票，又回到了30年前
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	70岁，孩子的孩子也长大了，不用天天操心了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你下定决心说：一定要去玩一趟
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	可是手边的拐杖
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	只能支持你走到楼下的花园
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	75岁，你在医院的病床上
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	身边聚满了人，你迷迷糊糊的看见医生摇了摇头
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	周围那些人神情肃穆
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你明白了，你要死掉了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你没有感到一丝害怕
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你突然问自己，我到底是什么时候死掉的呢？
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你想起来30岁的那场婚礼
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	原来，那时候，你就死掉了吧
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	依照惯例
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	死前的3秒，你的大脑要走马灯
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	倒叙你这75个年头的一生
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	画面一张一张的过
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	1秒
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	2秒
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	两秒过去了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你面无表情的看着这两秒内的回忆
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	第3秒
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	突然你笑了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	原来已经回到了15岁的那一年
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你看见一个男孩
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	他叼着一袋牛奶，背着书包
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	从另一个女孩家的阳台下跑过
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	那个男孩朝窗户里看了看
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	那是15岁的你暗恋的那个女孩子
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你想不起来她长什么样子了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	最后一秒你努力的回忆着
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	然后终于笑了出来
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	3秒过去了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	身边的人突然间开始嚎啕大哭
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你可能听不清了
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	你最后听到的嘈杂的声音
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	是一群十五六的少年 起着哄说的
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	答应他
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	答应他
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	答应他
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	<br />
</div>
<div style="line-height:21px;outline:none;font-family:&quot;font-size:14px;font-variant-numeric:normal;font-variant-east-asian:normal;white-space:normal;widows:1;">
	不爱你，不度生。
</div>]]></description>
	<pubDate>Wed, 02 Oct 2019 11:33:22 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-61.html</guid>

</item>
<item>
	<title>Dijkstra最短路径算法</title>
	<link>https://www.qinor.cn/post-60.html</link>
	<description><![CDATA[<p class="MsoNormal" align="center" style="text-align:center;">
	<b><span style="font-size:16px;">题目:</span><span style="font-size:16px;">Dijkstra最短路径算法</span><span style="font-size:16px;"></span></b><b></b> 
</p>
<p class="MsoNormal" align="center" style="text-align:center;">
	<span style="background-color:;">设计人：张钦颖</span>
</p>
<p class="MsoNormal" align="center" style="text-align:center;">
	<span style="background-color:;">学号：1414080901218</span>
</p>
<p class="MsoNormal" align="center" style="text-align:center;">
	<br />
</p>
<p class="MsoNormal" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">一、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
实验环境：</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">1、硬件环境：个人机，CPU主频：4.01Ghz&nbsp; 内存：16G</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">2、软件环境：操作系统：win10</span>
</p>
<p class="MsoNormal" style="margin-left:98.95pt;text-indent:-8.95pt;">
	<span style="background-color:;">编程语言：C++</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;"></span>
</p>
<p class="MsoNormal" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">二、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
实验任务解决方案：</span>
</p>
<p class="MsoListParagraph" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">1、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Dijkstra最短路径算法的流程图。</span>
</p>
<p class="MsoListParagraph" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">（1）&nbsp;&nbsp;
创建一组</span><span>sptSet</span><span style="background-color:;">（最短路径数组），它存的是已经在</span><span>SPT</span><span style="background-color:;">里的节点，也就是这些是已经计算过的，确定的最小值，一开始这里是空的。</span><span></span> 
</p>
<p class="MsoListParagraph" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">（2）&nbsp;&nbsp;
给所有的给定图中的节点一个指定的距离值。将所有的距离值初始化为无限，将源节点初始为</span><span>0</span><span style="background-color:;">。</span><span></span> 
</p>
<p class="MsoListParagraph" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">（3）&nbsp;&nbsp;
当另一个（保存未放入</span><span>SPT</span><span style="background-color:;">的节点的那个）不为空时：</span><span></span> 
</p>
<p class="MsoListParagraph" style="margin-left:54.0pt;text-indent:-18.0pt;">
	<span style="background-color:;">①&nbsp;
选择一个不在</span><span>SPTset</span><span style="background-color:;">中一个离源点距离最小的节点</span><span></span> 
</p>
<p class="MsoListParagraph" style="margin-left:54.0pt;text-indent:-18.0pt;">
	<span style="background-color:;">②&nbsp;
把它加入</span><span>SPTset</span> 
</p>
<p class="MsoListParagraph" style="margin-left:54.0pt;text-indent:-18.0pt;">
	<span style="background-color:;">③&nbsp;
更新所有于这个节点临近的节点的距离值。为了更距离值，遍历这个点所有的相邻点。对于每一个相邻的节点，如果这临近点</span><span>u</span><span style="background-color:;">的距离值（从源节点开始的和）比</span><span>v</span><span style="background-color:;">（新加入的）小，就把</span><span>v</span><span style="background-color:;">的距离值更新</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">例如：</span><span></span> 
</p>
<p class="MsoNormal" style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201909/af0b1569499496.jpg" id="ematt:678"><img src="https://www.qinor.cn/content/uploadfile/201909/thum-af0b1569499496.jpg" title="点击查看原图" alt="gaitubao_1.jpg" border="0" width="420" height="420" /></a>
</p>
<p class="MsoNormal" style="text-align:center;">
	<br />
</p>
<p style="text-align:left;">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;一开始SPTset是空的，并且它初始化的距离值是{0，INF，INF，INF，INF，INF，INF，INF}，其中INF表示无限。现在选择最小距离值的顶点。我们选了节点0，将它加入到sptSet。所以sptSet变为{0}。加入0至sptSet后，更新其相邻顶点的距离值。0相邻的节点为1和6，他们的距离值便被更新成3和4。图中标示出在SPT的顶点显示为绿色。
</p>
<p style="text-align:left;">
	<br />
</p>
<br />
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201909/eaee1569499496.jpg" id="ematt:680"><img src="https://www.qinor.cn/content/uploadfile/201909/thum-eaee1569499496.jpg" title="点击查看原图" alt="gaitubao_2.jpg" border="0" width="420" height="420" /></a>
</p>
<p>
	<br />
</p>
<p style="text-align:left;">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;然后我们选择一个不包含在SPT里一个路径值为最小的一个节点，节点1被选了而且被加入到了sptSET，所以sptSET现在变为了{0,1}更新1临近节点的距离值，临近节点为2跟8，节点2跟8的距离值便变成了3+6=9、3+16=19。
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201909/bf951569499496.jpg" id="ematt:682" style="text-align:center;"><img src="https://www.qinor.cn/content/uploadfile/201909/thum-bf951569499496.jpg" title="点击查看原图" alt="gaitubao_3.jpg" border="0" width="420" height="420" /></a>
</p>
<p style="text-align:left;">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;像上面一样选一个，我们选了节点6，加入到sptSet，所以sptSet变成了{0,1,6}，更新节点6的临近节点的距离值，临近节点是5和7，更新后5：4+20=24，7：4+10=14.
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201909/bf951569499496.jpg" id="ematt:682"><img src="https://www.qinor.cn/content/uploadfile/201909/thum-bf951569499496.jpg" title="点击查看原图" alt="gaitubao_3.jpg" border="0" width="420" height="420" /></a>
</p>
<p style="text-align:left;">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;然后我们再选一个2加入，sptSet又新来了一个数字2，2的临近节点3和8便更新为11和26.
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201909/06f41569499496.jpg" id="ematt:684"><img src="https://www.qinor.cn/content/uploadfile/201909/thum-06f41569499496.jpg" title="点击查看原图" alt="gaitubao_4.jpg" border="0" width="420" height="420" /></a>
</p>
<p class="MsoNormal" align="left" style="text-align:left;text-indent:21.0pt;">
	<span style="background-color:;">一直重复这些事儿直到sptset包含所有的节点：</span>
</p>
<p class="MsoNormal" align="left" style="text-align:left;text-indent:21.0pt;">
	<br />
</p>
<p class="MsoNormal" align="left" style="text-align:center;text-indent:21pt;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201909/9ef61569499496.jpg" id="ematt:686"><img src="https://www.qinor.cn/content/uploadfile/201909/thum-9ef61569499496.jpg" title="点击查看原图" alt="gaitubao_5.jpg" border="0" width="420" height="420" /></a> 
</p>
<p class="MsoNormal" align="left" style="text-align:left;">
	<br />
</p>
<p class="MsoNormal" align="left" style="text-align:center;text-indent:21pt;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201909/bbfd1569499496.jpg" id="ematt:688"><img src="https://www.qinor.cn/content/uploadfile/201909/thum-bbfd1569499496.jpg" title="点击查看原图" alt="gaitubao_6.jpg" border="0" width="420" height="420" /></a> 
</p>
<p class="MsoNormal" align="left" style="text-align:left;text-indent:21.0pt;">
	<span style="background-color:;">最后，我们终于得到了一棵完整的最短路径树</span>
</p>
<p class="MsoNormal" align="left" style="text-align:center;">
	<span style="background-color:;">&nbsp;</span>
</p>
<p class="MsoNormal" align="left" style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201909/fa641569499497.jpg" id="ematt:692"><img src="https://www.qinor.cn/content/uploadfile/201909/thum-fa641569499497.jpg" title="点击查看原图" alt="gaitubao_8.jpg" border="0" width="420" height="420" /></a>
</p>
<p class="MsoNormal" align="left" style="text-align:center;">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">2、Dijkstra最短路径算法实现的关键代码。</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">#include &lt;stdio.h&gt;</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">#include &lt;limits.h&gt;</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">// Number of vertices in the graph</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">#define V 9</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">// A utility function to find the vertex with minimum distance value,
from</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">// the set of vertices not yet included in shortest path tree</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">int minDistance(int dist[], bool sptSet[])</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">{</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Initialize min value</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int min = INT_MAX,
min_index;</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int v = 0; v &lt; V;
v++)</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (sptSet[v] == false
&amp;&amp; dist[v] &lt; min)</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; min = dist[v],
min_index = v;</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return min_index;</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">}</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">// A utility function to print the constructed distance array</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">void printSolution(int dist[], int n)</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">{</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("顶点\t\t距离源点的最短路径\n");</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; V;
i++)</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("%d \t\t%d\n",
i, dist[i]);</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">}</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">// Funtion that implements Dijkstra's single source shortest path
algorithm</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">// for a graph represented using adjacency matrix representation</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">void dijkstra(int graph[V][V], int src)</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">{</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int dist[V];&nbsp;&nbsp;&nbsp;&nbsp; // The output array.&nbsp; dist[i] will hold the shortest</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // distance from src to i</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool sptSet[V]; // sptSet[i]
will true if vertex i is included in shortest</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // path tree or shortest
distance from src to i is finalized</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Initialize all distances
as INFINITE and stpSet[] as false</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; V;
i++)</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dist[i] = INT_MAX,
sptSet[i] = false;</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Distance of source vertex
from itself is always 0</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dist[src] = 0;</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Find shortest path for
all vertices</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int count = 0; count
&lt; V - 1; count++)</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Pick the minimum
distance vertex from the set of vertices not</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // yet processed. u is
always equal to src in first iteration.</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int u =
minDistance(dist, sptSet);</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Mark the picked
vertex as processed</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sptSet[u] = true;</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Update dist value of
the adjacent vertices of the picked vertex.</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int v = 0; v &lt;
V; v++)</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Update dist[v]
only if distance to v is not in sptSet, there</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // is an edge
from u to v, and total weight of path from src to</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // v through u is
smaller than dist[v]</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!sptSet[v]
&amp;&amp; graph[u][v] &amp;&amp; dist[u] + graph[u][v] &lt; dist[v])</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dist[v] = dist[u]
+ graph[u][v];</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // print the constructed
distance array</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printSolution(dist, V);</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">}</span>
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	<span style="background-color:;">// driver program to test above function</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">int main()</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">{</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Let us create the example
graph discussed above */</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int graph[V][V] = {</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { 0, 3, 0, 0, 0, 0, 4,
0, 0 },</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { 3, 0, 6, 0, 0, 0, 0,
0, 16 },</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { 0, 6, 0, 2, 0, 0, 0,
0, 17 },</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { 0, 0, 2, 0, 4, 0, 0,
0, 9 },</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { 0, 0, 0, 4, 0, 11, 0,
7, 0 },</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { 0, 0, 0, 0, 11, 0,
20, 0, 0 },</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { 4, 0, 0, 0, 0, 20, 0,
10, 0 },</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { 0, 0, 0, 0, 7, 0, 10,
0, 1 },</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { 0, 16, 17, 9, 0, 0,
0, 1, 0 }</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dijkstra(graph, 0);</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;</span>
</p>
<p class="MsoNormal">
	<span style="background-color:;">}</span>
</p>
<p class="MsoNormal" style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201909/cb0a1569499497.png" id="ematt:694"><img src="https://www.qinor.cn/content/uploadfile/201909/thum-cb0a1569499497.png" title="点击查看原图" alt="gaitubao_9.png" border="0" width="420" height="272" /></a> 
</p>
<p class="MsoNormal" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">三、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Dijkstra最短路径算法的计算复杂度分析（最好、最差、平均情况复杂度）：</span>
</p>
<p class="MsoListParagraph" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">（1） 最好的情况：每个顶点第一次检测到就是路径值就是最短路径。复杂度最低。</span><span></span> 
</p>
<p class="MsoNormal" style="margin-left:21.0pt;">
	<span style="background-color:;">复杂度为：</span><span>2n</span><span style="background-color:;">²</span><span>+6n</span> 
</p>
<p class="MsoListParagraph" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">（2） 最坏情况：顶点被检测了</span><span>1</span><span style="background-color:;">次以上，并且每次检测都更新路径值为最小，即每次找的都不是最短路径，直到最后一次检测。复杂度最高</span><span></span> 
</p>
<p class="MsoNormal" style="text-indent:21.0pt;">
	<span style="background-color:;">复杂度为：</span><span>3n</span><span style="background-color:;">²</span><span>+6n</span> 
</p>
<p class="MsoListParagraph" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">（3） 平均情况复杂度：时间复杂度为</span><span>O(n</span><span style="background-color:;">²</span><span>)</span> 
</p>
<p class="MsoNormal" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<span style="background-color:;">四、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
总结综合实验心得体会：</span>
</p>
<p class="MsoNormal" style="margin-left:21.0pt;text-indent:28.0pt;">
	<span style="background-color:;">本次的Dijkstra最短路径算法在数据结构的课程上也接触过，所以感觉这个不成问题，选用的例子是自己随手画出来的，然后按照Dijkstra最短路径算法把流程步骤分别画了出来。然后其他部分参考了一下wiki以及geek论坛等国外大神的论文。还有很多东西老师在课堂上也给我们又讲了一遍，所以从算法思想上就不成多大问题了，主要是代码，而且必须得了解了算法才能mark得出代码来，否则根本无从下手。</span>
</p>]]></description>
	<pubDate>Thu, 26 Sep 2019 12:03:59 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-60.html</guid>

</item>
<item>
	<title>基于矩阵变换算法的图同构识别</title>
	<link>https://www.qinor.cn/post-58.html</link>
	<description><![CDATA[<p class="MsoNormal" align="center" style="text-align:center;">
	<b>题目：（例如）基于矩阵变换算法的图同构识别</b><b></b> 
</p>
<p class="MsoNormal" align="center" style="text-align:center;">
	设计人：张钦颖
</p>
<p class="MsoNormal" align="center" style="text-align:center;">
	学号：1414080901218
</p>
<p class="MsoNormal" align="center" style="text-align:center;">
	&nbsp;
</p>
<p class="MsoNormal" style="margin-left:36.0pt;text-indent:-36.0pt;">
	一、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
实验环境：
</p>
<p class="MsoNormal">
	1、硬件环境：个人机，CPU主频：4.01Ghz&nbsp;&nbsp;&nbsp;&nbsp; 内存：16G
</p>
<p class="MsoNormal">
	2、软件环境：操作系统：win10
</p>
<p class="MsoNormal" style="text-indent:0px;">
	<span style="text-indent:-8.95pt;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;编程语言：C++</span> 
</p>
<p class="MsoNormal">
	&nbsp;
</p>
<p class="MsoNormal" style="margin-left:36.0pt;text-indent:-36.0pt;">
	二、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
实验任务解决方案：
</p>
<p class="MsoNormal" style="margin-left:36.0pt;text-indent:-36.0pt;">
	1、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
矩阵变换算法的流程图。
</p>
<p class="MsoNormal" style="margin-left:21.0pt;">
	实验原理：
</p>
<p class="MsoNormal" style="margin-left:21.0pt;">
	设两个无向图G=（V，E），G'=（V'，E'），当且仅当两图的邻接矩阵、行间同或矩阵、行间异或矩阵具有相同的行行置换时GG '同构。
</p>
<p class="MsoNormal" style="margin-left:21.0pt;">
	矩阵算法步骤：
</p>
<p class="MsoNormal" style="margin-left:57.0pt;text-indent:-36.0pt;">
	（1）
根据定义求出同型矩阵。
</p>
<p class="MsoNormal" style="margin-left:57.0pt;text-indent:-36.0pt;">
	（2）
计算出行间同或矩阵，行间异或矩阵。
</p>
<p class="MsoNormal" style="margin-left:57.0pt;text-indent:-36.0pt;">
	（3）
以图G=（v，E）的行间异或矩阵为参照，对行间异或矩阵的每一行，从异或矩阵搜索所有行，找到一个匹配。
</p>
<p class="MsoNormal" style="margin-left:75.0pt;text-indent:-36.0pt;">
	①、&nbsp;&nbsp; 若不匹配，则两图不同构；
</p>
<p class="MsoNormal" style="margin-left:75.0pt;text-indent:-36.0pt;">
	②、&nbsp;&nbsp; 若匹配，则继续往下判断执行。
</p>
<p class="MsoNormal" style="margin-left:57.0pt;text-indent:-36.0pt;">
	（4）
判断邻接矩阵、行间同或矩阵中是否存在同样的匹配。
</p>
<p class="MsoNormal" style="margin-left:57.0pt;text-indent:-36.0pt;">
	&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;①、&nbsp; 若匹配存在，调整邻接矩阵、行间异或矩阵、行间同或矩阵，对应的行和列。
</p>
<p class="MsoNormal" style="margin-left:57.0pt;text-indent:-36.0pt;">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ②、&nbsp; 若不匹配，则两图不同构。
</p>
<p class="MsoNormal" style="text-align:center;margin-left:57pt;text-indent:-36pt;">
	&nbsp;&nbsp;<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201908/4a471565791832.png" id="ematt:670"><img src="https://www.qinor.cn/content/uploadfile/201908/thum-4a471565791832.png" title="点击查看原图" alt="1.png" border="0" width="420" height="361" /></a> 
</p>
<p class="MsoNormal" style="text-align:center;margin-left:57pt;text-indent:-36pt;">
	<br />
</p>
<p class="MsoNormal" style="text-align:left;margin-left:57pt;text-indent:-36pt;">
	<br />
</p>
<p class="MsoNormal">
	2、矩阵变换算法实现的关键代码。<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;
</p>
<p class="MsoNormal">
	//冒泡排序 <o:p></o:p>
</p>
<p class="MsoNormal">
	void wensen_mp(int mp[],int n) <o:p></o:p>
</p>
<p class="MsoNormal">
	{ <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int t; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;for(int i=0;i&lt;n-1;i++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;{ <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; for(int j=0;j&lt;n-1-i;j++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; if(mp[j]&gt;mp[j+1]) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; t=mp[j]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; mp[j]=mp[j+1]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; mp[j+1]=t; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;} <o:p></o:p>
</p>
<p class="MsoNormal">
	} <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	/////////////////////////核心代码 <o:p></o:p>
</p>
<p class="MsoNormal">
	//异或矩阵行转换 <o:p></o:p>
</p>
<p class="MsoNormal">
	void wensen_hx(int **p1,int **p13,int **p14,int **p2,int **p23,int
**p24,int n) <o:p></o:p>
</p>
<p class="MsoNormal">
	{ <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int *p77=new int[n];//用于替换的临时一维数组存放p13 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int *p88=new int[n];//用于替换的临时一维数组存放p23 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int *p33=new int[n];//用于替换的临时一维数组存放p1 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int *p44=new int[n];//用于替换的临时一维数组存放p14 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int *p55=new int[n];//用于替换的临时一维数组存放p2 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int *p66=new int[n];//用于替换的临时一维数组存放p24 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int *p99=new int[n];//用于行行替换的临时数组 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int t; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int tt;//进行跳转判断 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;int ttt=0;//进行跳转判断 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;
</p>
<p class="MsoNormal">
	&nbsp;//行行替换 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;for( int i=0;i&lt;n;i++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;{ <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; //首先进行行赋值给另外一个数组p13 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; for(int i77=0;i77&lt;n;i77++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; p77[i77]=p13[i][i77]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; //首先进行行赋值给另外一个数组p1 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; for(int i33=0;i33&lt;n;i33++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; p33[i33]=p1[i][i33]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; //首先进行行赋值给另外一个数组 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; for(int i44=0;i44&lt;n;i44++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; p44[i44]=p14[i][i44]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; //p77,p33,p44冒泡排序 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; wensen_mp(p77,n); <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; wensen_mp(p33,n); <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; wensen_mp(p44,n); <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; //开始进行比较,p12的每一行与p23的每一行进行比较 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; for(int y=i;y&lt;n;y++)<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; tt=0; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; //首先进行行赋值给另外一个数组 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; for(int i88=0;i88&lt;n;i88++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; p88[i88]=p23[y][i88]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; //首先进行行赋值给另外一个数组 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; for(int i55=0;i55&lt;n;i55++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; p55[i55]=p2[y][i55]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; //首先进行行赋值给另外一个数组 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; for(int i66=0;i66&lt;n;i66++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; p66[i66]=p24[y][i66]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; //p88,p55,p66冒泡排序 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; wensen_mp(p88,n); <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; wensen_mp(p55,n); <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; wensen_mp(p66,n); <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; //开始比较 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; for(int a=0;a&lt;n;a++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; &nbsp;&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; if(p77[a]==p88[a]) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; tt=a; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; if(a==n-1)//也就是各个都相等,找到匹配 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //开始进行邻接矩阵对应位置比较 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int b=0;b&lt;n;b++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(p33[b]==p55[b])<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if(b&lt;n-1) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"不同构\n"; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //开始进行同或矩阵 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int c=0;c&lt;n;c++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(p44[c]==p66[c]) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if(c&lt;n-1) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"不同构\n"; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ttt++;//表示成功匹配一行 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //进行行行转换p2 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int u1=0;u1&lt;n;u1++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t=p2[i][u1]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p2[i][u1]=p2[y][u1]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p2[y][u1]=t; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int
u11=0;u11&lt;n;u11++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t=p2[u11][i]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p2[u11][i]=p2[u11][y]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p2[u11][y]=t; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //进行行行转换p23 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int u2=0;u2&lt;n;u2++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t=p23[i][u2]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p23[i][u2]=p23[y][u2]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p23[y][u2]=t; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int
u22=0;u22&lt;n;u22++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t=p23[u22][i]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p23[u22][i]=p23[u22][y]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p23[u22][y]=t; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //进行行行转换p24 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int u3=0;u3&lt;n;u3++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t=p24[i][u3]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p24[i][u3]=p24[y][u3]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p24[y][u3]=t; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int
u33=0;u33&lt;n;u33++) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t=p24[u33][i];<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp; p24[u33][i]=p24[u33][y]; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p24[u33][y]=t; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; break; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; else <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; else if(y==n-1)//一直循环到最后都未找到匹配 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"不同构\n"; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; return; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; &nbsp;&nbsp;} <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; else <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp; break; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; //上面的匹配没有问题则进行行替换 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; if(tt==n-1) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; if(ttt==n) <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; {<o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"同构\n"; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return; <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; else <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; { <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break; //成功跳出循环判断下一行 <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp; } <o:p></o:p>
</p>
<p class="MsoNormal">
	&nbsp;} <o:p></o:p>
</p>
<p class="MsoNormal">
	}
</p>
<p class="MsoNormal" style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201908/fb5c1565791902.png" id="ematt:672"><img src="https://www.qinor.cn/content/uploadfile/201908/thum-fb5c1565791902.png" title="点击查看原图" alt="2.png" border="0" width="420" height="285" /></a>
</p>
<p class="MsoNormal" style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201908/10fb1565791902.png" id="ematt:674"><img src="https://www.qinor.cn/content/uploadfile/201908/thum-10fb1565791902.png" title="点击查看原图" alt="3.png" border="0" width="420" height="285" /></a> 
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal">
	三、<span style="text-indent:-36pt;">矩阵变换算法的计算复杂度分析（最好、最差、平均情况复杂度）：</span> 
</p>
<p class="MsoNormal">
	<span style="text-indent:-36pt;">&nbsp; &nbsp; &nbsp; &nbsp;（1） 最好情况：n</span><sup style="text-indent:-36pt;">2</sup><span style="text-indent:-36pt;">+5*3n+32=O(n</span><sup style="text-indent:-36pt;">2</sup><span style="text-indent:-36pt;">)</span> 
</p>
<p class="MsoNormal">
	<span style="text-indent:-36pt;">&nbsp; &nbsp; &nbsp; （2） 最差情况：5n</span><sup style="text-indent:-36pt;">3</sup><span style="text-indent:-36pt;">+15*6n</span><sup style="text-indent:-36pt;">2</sup><span style="text-indent:-36pt;">+88*5n+18=O(n</span><sup style="text-indent:-36pt;">3</sup><span style="text-indent:-36pt;">)</span> 
</p>
<p class="MsoNormal">
	<span style="text-indent:-36pt;">&nbsp; &nbsp; &nbsp; （3） 平均复杂度：O(n</span><sup style="text-indent:-36pt;">2</sup><span style="text-indent:-36pt;">)</span> 
</p>
<p class="MsoNormal">
	<br />
</p>
<p class="MsoNormal" style="margin-left:36.0pt;text-indent:-36.0pt;">
	<br />
</p>
<p class="MsoNormal" style="margin-left:36.0pt;text-indent:-36.0pt;">
	四、总结综合实验心得体会：
</p>
<p class="MsoNormal" style="text-indent:-48px;margin-left:36pt;">
	<span style="text-indent:28pt;">&nbsp; &nbsp; &nbsp; &nbsp;同构图我们课上讲过许多，利用暴力法识别同构图我们也上过课，但是基于矩阵变换算法的图同构识别我就不会。由于矩阵变换算法为什么能识别同构图一点都不了解，所以我查阅了很多资料，当且仅当两图的邻接矩阵、行间同或矩阵、行间异或矩阵具有相同的行行置换时两个图是同构图，所以利用该原理，查阅了资料参考着写了代码。最后把该实验做了出来。</span> 
</p>
<p style="text-indent:28.0pt;">
	下面补充各种矩阵的定义：
</p>
<p>
	<br />
</p>
<p style="text-align:center;text-indent:28pt;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201908/09dd1565792100.png" id="ematt:676"><img src="https://www.qinor.cn/content/uploadfile/201908/thum-09dd1565792100.png" title="点击查看原图" alt="4.png" border="0" width="420" height="252" /></a> 
</p>
<p>
	<br />
</p>
<p class="MsoNormal" style="text-align:left;margin-left:57pt;text-indent:-36pt;">
	<span style="text-indent:-36pt;"></span><span style="text-indent:-36pt;"></span> 
</p>
<p>
	<br />
</p>
<p class="MsoNormal" style="margin-left:57.0pt;text-indent:-36.0pt;">
	<br />
</p>
<p>
	<br />
</p>]]></description>
	<pubDate>Wed, 14 Aug 2019 14:01:51 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-58.html</guid>

</item>
<item>
	<title>QQ9.1.3(25332)消息防撤回补丁</title>
	<link>https://www.qinor.cn/post-57.html</link>
	<description><![CDATA[<div class="newfujian">
	<div class="fileico rar">
	</div>
	<div class="filecont">
		<div class="filetit">
			<a href="http://pan.qinor.cn/s/jb4szf19" target="_blank" rel="nofollow" title="点击下载">QQ9.1.3(25332)消息防撤回DLL文件</a><span>大小：1.37M | 来源：奇诺云盘</span> 
		</div>
		<div class="fileaq">
			已经过安全软件检测无毒，请您放心下载。
		</div>
	</div>
	<div class="down_2">
		<a href="http://pan.qinor.cn/s/jb4szf19" target="_blank" rel="nofollow" title="点击下载"></a> 
	</div>
</div>]]></description>
	<pubDate>Sun, 16 Jun 2019 11:36:52 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-57.html</guid>

</item>
<item>
	<title>如何利用Wireshark抓取ftp帐密</title>
	<link>https://www.qinor.cn/post-56.html</link>
	<description><![CDATA[<p align="center" style="text-align:left;margin-left:0cm;">
	<b>1.1</b><b>实验简介</b>
</p>
<p style="text-indent:21.0pt;">
	本实验主要简单介绍了<span>Wireshark</span>的使用，通过本实验可以掌握如何通过<span>Wireshark</span>抓取网络传输中的明文密码。<span></span> 
</p>
<p style="margin-left:0cm;">
	<b>1.2</b><b>实验环境</b><span></span> 
</p>
<p style="text-indent:24.0pt;">
	服务器配置：<span>Windows&nbsp;xp&nbsp;+&nbsp;FileZilla&nbsp;Server&nbsp;ftp</span>服务器
</p>
<p style="text-indent:24.0pt;">
	密码信息：<span>&nbsp;ftp</span>用户名<span>/</span>密码<span>&nbsp;&nbsp;&nbsp;root/roottoor&nbsp; &nbsp; &nbsp;&nbsp;</span> 
</p>
<p style="margin-left:0cm;">
	<b>1.3</b><b>实验步骤</b><span></span> 
</p>
<p style="text-indent:24.0pt;">
	1.打开<span>Wireshark</span>，点击“<span>&nbsp;</span>列出可用的抓包接口“按钮，在弹出的对话框中，选择你正在使用的网卡，并点击<span>Start</span>开始抓包<span></span> 
</p>
<p>
	<span></span> 
</p>
<p style="text-align:center;">
	&nbsp;<span><a target="_blank" href="https://www.qinor.cn/content/uploadfile/201906/43891560244670.png" id="ematt:656"><img src="https://www.qinor.cn/content/uploadfile/201906/43891560244670.png" title="点击查看原图" alt="12311.png" border="0" width="554" height="269" /></a></span> 
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201906/6b261560244670.png" id="ematt:658"><img src="https://www.qinor.cn/content/uploadfile/201906/thum-6b261560244670.png" title="点击查看原图" alt="12312.png" border="0" width="420" height="137" /></a> 
</p>
<p>
	<span></span> 
</p>
<p style="text-indent:24.0pt;">
	2.打开<span>cmd</span>对话框，访问服务器<span>x.x.x.x</span>上的<span>ftp</span>服务，并输入正确的用户名和密码后，和服务器断开连接。<span></span> 
</p>
<p style="text-align:center;">
	&nbsp;<span><a target="_blank" href="https://www.qinor.cn/content/uploadfile/201906/1f021560244671.png" id="ematt:660"><img src="https://www.qinor.cn/content/uploadfile/201906/thum-1f021560244671.png" title="点击查看原图" alt="12313.png" border="0" width="420" height="275" /></a></span> 
</p>
<p>
	<span></span> 
</p>
<p style="text-indent:24.0pt;">
	3.单击“停止”按钮停止抓包。<span></span> 
</p>
<p style="text-align:center;">
	&nbsp;<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201906/fea81560244671.png" id="ematt:662"><img src="https://www.qinor.cn/content/uploadfile/201906/thum-fea81560244671.png" title="点击查看原图" alt="12314.png" border="0" width="420" height="369" /></a><span></span> 
</p>
<p>
	<span></span> 
</p>
<p style="text-indent:24.0pt;">
	4. 由于我们知道<span>ftp</span>客户端首先与服务器的<span>21</span>端口建立连接并完成认证后再打开数据通道，所以我们只需要关注<span>TCP&nbsp;21</span>端口的数据包即可。在过滤器中输入 “<span>tcp.port==21</span>”，即可以过滤出与<span>tcp&nbsp;21</span>端口有关的所有数据包。如图所示。<span>(</span>按<span>Enter</span>键或点击<span>Apply(</span>应用<span>)</span>按钮使过滤规则生
效<span>)</span><span></span> 
</p>
<p style="text-align:center;">
	&nbsp;<span><a target="_blank" href="https://www.qinor.cn/content/uploadfile/201906/47271560244671.png" id="ematt:664"><img src="https://www.qinor.cn/content/uploadfile/201906/thum-47271560244671.png" title="点击查看原图" alt="12315.png" border="0" width="420" height="310" /></a></span> 
</p>
<p>
	<span></span> 
</p>
<p style="text-indent:24.0pt;">
	5.对任意一个<span>FTP</span>数据包右击选择“跟踪<span>TCP</span>码流”（<span>Fllow&nbsp;TCP&nbsp;Stream</span>），即可以在弹出的窗口中看到整个<span>FTP</span>控制通道的数据交互内容，从中可以看到我们登陆<span>FTP</span>服务器的明文用户名与密码。<span></span> 
</p>
<p style="text-align:center;">
	&nbsp;<span><a target="_blank" href="https://www.qinor.cn/content/uploadfile/201906/0dbd1560244671.png" id="ematt:666"><img src="https://www.qinor.cn/content/uploadfile/201906/thum-0dbd1560244671.png" title="点击查看原图" alt="12316.png" border="0" width="420" height="301" /></a></span> 
</p>
<p style="text-align:center;">
	&nbsp;<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201906/67f31560244671.png" id="ematt:668"><img src="https://www.qinor.cn/content/uploadfile/201906/thum-67f31560244671.png" title="点击查看原图" alt="12317.png" border="0" width="420" height="320" /></a><span></span> 
</p>]]></description>
	<pubDate>Tue, 11 Jun 2019 09:01:13 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-56.html</guid>

</item>
<item>
	<title>精选好看的&quot;网站正在维护升级中&quot;页面</title>
	<link>https://www.qinor.cn/post-55.html</link>
	<description><![CDATA[<p style="text-align:left;">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; 下面分享两个博主觉得好看的"网站正在维护升级中"的维护页面：
</p>
<p style="text-align:left;">
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; 如果想要修改联系方式的可以自己在index.html中手动编辑成自己的联系方式哦！
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201903/a0421553057342.jpg" id="ematt:652"><img src="https://www.qinor.cn/content/uploadfile/201903/thum-a0421553057342.jpg" title="点击查看原图" alt="QQ截图20190320125121.jpg" border="0" width="420" height="217" /></a> 
</p>
<p style="text-align:center;">
	<a target="_blank" href="https://www.qinor.cn/content/uploadfile/201903/ee4a1553057353.jpg" id="ematt:654"><img src="https://www.qinor.cn/content/uploadfile/201903/thum-ee4a1553057353.jpg" title="点击查看原图" alt="QQ截图20190320125232.jpg" border="0" width="420" height="206" /></a> 
</p>
<p style="text-align:left;">
</p>
<p style="text-align:center;">
	<br />
</p>
<p>
分享码密码为：[cv]7zgr54p0[/cv]
</p>
<div class="Fengdown_tit">
	<i class="ico"></i>下载地址&nbsp;
</div>
<span onclick="window.open('http://pan.qinor.cn/s/DqU7');" class="Fengdown"><i class="ico"></i><i class="line"></i>本地下载</span>&nbsp;]]></description>
	<pubDate>Wed, 20 Mar 2019 04:45:23 +0000</pubDate>
	<author>Chin</author>
	<guid>https://www.qinor.cn/post-55.html</guid>

</item></channel>
</rss>