<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[NightBack]]></title><description><![CDATA[NightBack]]></description><link>https://blog.vning.vip</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 10:50:38 GMT</lastBuildDate><atom:link href="https://blog.vning.vip/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[你愿意相信机器，还是人]]></title><description><![CDATA[春节回家，跟老爸一起刷了小破球2。
之后忽然想到了这个话题：你愿意相信机器，还是人

（一）
春节的时候，陪老爸去配了一副眼镜。
因为前不久我也正好花重金配了一副，在之前做了很多的功课，诸如眼镜的前倾角、面弯、镜间距、单眼瞳高、单眼瞳距之类的知识，了解了一些配镜过程中应该注意的事项。
老家是一个小县城，虽然眼镜店遍地都是，但专业的还真没几个。在配镜的过程中，能注意到这些细节的眼镜店几乎没有。蔡司专业的三维定位的仪器，更是全县城都没有。
当我提出需要仪器辅助测量这些参数的时候，一个配镜师傅跟我说，...]]></description><link>https://blog.vning.vip/5l2g5os5osp55u45lh5py65zmo77ym6ly5piv5lq6</link><guid isPermaLink="true">https://blog.vning.vip/5l2g5os5osp55u45lh5py65zmo77ym6ly5piv5lq6</guid><category><![CDATA[闲聊]]></category><dc:creator><![CDATA[夜归人]]></dc:creator><pubDate>Mon, 30 Jan 2023 02:00:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/XJXWbfSo2f0/upload/d1e1a95b94d9c9b9d8b784d346873044.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>春节回家，跟老爸一起刷了小破球2。</p>
<p>之后忽然想到了这个话题：你愿意相信机器，还是人</p>
<hr />
<p>（一）</p>
<p>春节的时候，陪老爸去配了一副眼镜。</p>
<p>因为前不久我也正好花重金配了一副，在之前做了很多的功课，诸如眼镜的前倾角、面弯、镜间距、单眼瞳高、单眼瞳距之类的知识，了解了一些配镜过程中应该注意的事项。</p>
<p>老家是一个小县城，虽然眼镜店遍地都是，但专业的还真没几个。在配镜的过程中，能注意到这些细节的眼镜店几乎没有。蔡司专业的三维定位的仪器，更是全县城都没有。</p>
<p>当我提出需要仪器辅助测量这些参数的时候，一个配镜师傅跟我说，他们可以用最原始的方法，通过手动测量来得出这些值。</p>
<p>我的犹豫引起了他的反感，他说，如果你只相信这些参数，那么就没法配这个系列了。</p>
<p>我相信我们县城有很厉害的师傅，可以通过手动测量和计算得出这些参数。但是我不相信在茫茫人海中，我能有幸遇到这个师傅。</p>
<p>蔡司的专业设备是投入大量的人力物力研发的，而且我相信研发这些设备的工程师的能力也不低。所以这样的设备，他的上限和下限都极其逼近于其平均水平。我们可以期待他有一个稳定且正常的发挥。</p>
<p>一个专业的配镜师傅，他的上限和下限是相差极大的。智者千虑必有一失，愚者千虑必有一得。但这个师傅的稳定水平，可能会收到很多因素的影响，包括但不限于：今天天气怎么样、出门的时候是否跟老婆吵架了、甚至都不能期待他的发挥是他的平均水平。</p>
<p>但同样还是人，如果是研发蔡司的设备的眼光配镜的师傅，我可能会更相信于这个人。因为我知道，他有超出这个设备的能力。</p>
<hr />
<p>（二）</p>
<p>最近十年，去银行柜台的次数，基本上一只手数得过来。</p>
<p>十年前，存钱、取钱，大部分还是要去柜台。虽然有ATM机，但是存钱的时候的识别率并不高，去柜台的效率高于ATM机。</p>
<p>十年后，纸币在日常生活里的作用逐渐的弱化。微信、支付宝的流行，甚至让市井小偷都面临职业危机。</p>
<p>会想最近一次去银行柜台，感觉偌大的银行大厅甚至有些冷清。而且，在柜台，也没有看到存取钱的客户。</p>
<p>之前有新闻说要取消高速公路口处的收费人员，全部采用类似ETC的方式来代替。大概也是这样吧。</p>
<hr />
<p>（三）</p>
<p>这几年网购东西，越来越少的去咨询客服了。因为我知道， 对面大概率也是一个机器人，回复一些正确且无用的答案。</p>
<p>刚开始网购的时候，互联网的测评并不流行，产品页的描述提供的信息可未必详细。想知道一个参数，跟客服沟通会是一个最佳的选择。</p>
<p>那时候的客服也很热情，会有一种类似线下购物的交流感。</p>
<p>如今，评测剧透，甚至优先于产品发布。你对产品的了解，甚至多过对面的人工客服。</p>
<p>此刻的你，可能既不需要人，也不需要机器。</p>
]]></content:encoded></item><item><title><![CDATA[Cache 缓存三问]]></title><description><![CDATA[哎，我一个个人项目基本上都没有超过过两位数的QPS的人，面试你天天问我缓存的这些问题，你们礼貌吗？
缓存穿透
是啥
当请求试图访问一个在缓存和数据库都不存在的 key时，因为缓存没有，所以请求会直接转发到数据库上。然而数据库里面也没有这个 key 的记录，所以也就没有办法将这个数据写入缓存。当下一次同样的请求来了的时候，还是会转发到数据库上。
在这种情况下，缓存基本上也就没用了，就像被穿透了一样，请求每次都会走到数据库，流量大时数据库可能会扛不住。
咋整

对请求进行校验。一些明显不合理的参数直...]]></description><link>https://blog.vning.vip/cache</link><guid isPermaLink="true">https://blog.vning.vip/cache</guid><category><![CDATA[cache]]></category><dc:creator><![CDATA[夜归人]]></dc:creator><pubDate>Fri, 17 Jun 2022 08:01:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/hvSr_CVecVI/upload/v1655452787144/dUGWTOZrg.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>哎，我一个个人项目基本上都没有超过过两位数的QPS的人，面试你天天问我缓存的这些问题，<strong>你们礼貌吗？</strong></p>
<h1 id="heading-57yt5a2y56m6ycp">缓存穿透</h1>
<h2 id="heading-5piv5zwl">是啥</h2>
<p>当请求试图访问一个在缓存和数据库都不存在的 key时，因为缓存没有，所以请求会直接转发到数据库上。然而数据库里面也没有这个 key 的记录，所以也就没有办法将这个数据写入缓存。当下一次同样的请求来了的时候，还是会转发到数据库上。</p>
<p>在这种情况下，缓存基本上也就没用了，就像被穿透了一样，请求每次都会走到数据库，流量大时数据库可能会扛不住。</p>
<h2 id="heading-5zkl5pw0">咋整</h2>
<ol>
<li>对请求进行校验。一些明显不合理的参数直接前端过滤掉就好了，就不要让他进来了。</li>
<li>将空值缓存起来。查完之后，即使数据库没有这条记录，也把这个 key 和没有记录的情况缓存起来，这样下次就不会再查数据库了。</li>
<li>布隆过滤器。挺有意思的一个东西，值得单独讲讲。</li>
</ol>
<h3 id="heading-5bid6zqg6lh5ruk5zmo">布隆过滤器</h3>
<h4 id="heading-5lul57un">介绍</h4>
<p>布隆过滤器是一种概率型的数据结构，它可以告诉你某条记录一定不存在或者有可能存在。</p>
<p>正常我们考虑一个东西是否存在的判断的时候，大家下意识的反应可能会是哈希。但是哈希需要保存所有的对象，以便后续哈希冲突的时候找到特定的值。所以这也导致了哈希对大量数据的时候，内存是非常大的。</p>
<p>所以就有个小天才，尝试我们用多个哈希，来讲存在变成一个概率性的事情：</p>
<p>比如说：</p>
<ul>
<li>我们初始化一个值为0，容量为10的数组：[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
然后设定两个不同的哈希方法：</li>
<li>哈希1 =&gt; %5</li>
<li>哈希2 =&gt; %7</li>
</ul>
<p>现在，我们要存放 25 这个值，我们来看一下：
25：哈希1得到0，我们将0这个下标对应的值标记成1；哈希2得到4，我们将4这个下标对应的值修改成1；此时数组为：[1, 0, 0, 0, 1, 0, 0, 0, 0, 0]</p>
<p>当我们尝试获取25的时候，我们还是按照哈希1和哈希2，取得对应的下标，然后获取下标对应的值。如果下标的值一旦有0值，那么我们就认为这条记录是不存在的，但即便是都是1，也不一定百分百存在，为什么呢？</p>
<p>假设说我们现在要存 60 这个值，哈希1返回下标0，哈希2返回下标4，跟25是一样的结果。所以0和4的下标都是1的时候，可能是25带来的，也可以能是60带来的，这并不能确定，只能说，有概率存在。</p>
<p>但这个种并不是很精准筛查，在某些条件下，已经可以帮我们解决很大程度上的问题了。</p>
<h4 id="heading-5zoi5bim5ye95pww5liq5pww5zkm5bid6zqg6lh5ruk5zmo6zw5bqm">哈希函数个数和布隆过滤器长度</h4>
<p>布隆过滤器长度越长，误报概率越小。
哈希函数越多，一个值写的1也越多。</p>
<h4 id="heading-57up5yw45bqu55so">经典应用</h4>
<p>用布隆过滤器减少磁盘 IO 和网络请求。</p>
<h1 id="heading-57yt5a2y5ye756m">缓存击穿</h1>
<h2 id="heading-5piv5zwl">是啥</h2>
<p>某一热门的 key，在它过期的一瞬间，突然来了对它的好多请求。因为缓存已经过期了，所以这些请求就都转到了数据库，那个可怜的数据库又扛不住了。</p>
<h2 id="heading-5zkl5pw0">咋整</h2>
<ol>
<li>加互斥锁。比如 Redis 分布式锁。</li>
<li>设置热点数据不过期。</li>
</ol>
<h1 id="heading-57yt5a2y6zuq5bsp">缓存雪崩</h1>
<h2 id="heading-5piv5zwl">是啥</h2>
<p>大量热点 key 设置了相同的过期时间，在他们过期的一瞬间，出现了大量的请求，然后那个数据库应该更扛不住了。可以简单的理解，缓存击穿时一个 key，缓存雪崩是一堆 key。</p>
<h2 id="heading-5zkl5pw0">咋整</h2>
<p>基本上跟缓存击穿一样，无法时加了一个打散过期时间来处理一组的问题。</p>
<ol>
<li>打散过期时间</li>
<li>加互斥锁</li>
<li>热点数据不过期</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[C# 托管代码和垃圾回收]]></title><description><![CDATA[什么是托管代码
托管代码就是执行过程交由运行时管理的代码。运行时一般是指 CLR，公共语言运行时。

CLR 负责提取托管代码、将其编译成机器代码，然后执行它。除此之外，运行时还负责自动内存管理、安全边界、类型安全等等。

如果在 .Net 里面直接调用 C/C++ 程序，此类代码也称为“非托管代码”。在非托管代码的环境中，操作系统将程序加载进内存，然后调用内部的二进制代码，所以从内存管理到安全等诸多因素都需要程序员自己处理。
正常使用 .Net 编写的代码，会先编译成中间语言 （IL）。执行的...]]></description><link>https://blog.vning.vip/csharp-gc</link><guid isPermaLink="true">https://blog.vning.vip/csharp-gc</guid><category><![CDATA[C#]]></category><category><![CDATA[.NET]]></category><category><![CDATA[gc]]></category><dc:creator><![CDATA[夜归人]]></dc:creator><pubDate>Tue, 14 Jun 2022 02:14:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/YzSZN3qvHeo/upload/v1655172750848/EM5yAryfu.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-5lua5lmi5piv5omy566h5luj56cb">什么是托管代码</h1>
<p>托管代码就是执行过程交由运行时管理的代码。运行时一般是指 CLR，公共语言运行时。</p>
<blockquote>
<p>CLR 负责提取托管代码、将其编译成机器代码，然后执行它。除此之外，运行时还负责自动内存管理、安全边界、类型安全等等。</p>
</blockquote>
<p>如果在 .Net 里面直接调用 C/C++ 程序，此类代码也称为“非托管代码”。在非托管代码的环境中，操作系统将程序加载进内存，然后调用内部的二进制代码，所以从内存管理到安全等诸多因素都需要程序员自己处理。</p>
<p>正常使用 .Net 编写的代码，会先编译成中间语言 （IL）。执行的时候，CLR 会接管编译后的 IL，然后通过实时编译（JIT）将 IL 编译成可以在CPU上执行的机器码。这样 CLR 就能知道代码的确切作用，并管理代码。</p>
<h1 id="heading-5yig6ywn5yaf5a2y">分配内存</h1>
<p>初始化新进程时，运行时会为进程创建托管堆。</p>
<p>托管堆是一个连续的地址空间区域，托管堆内维护着一个指针，用它指向将在堆中分配的下一个对象的地址。最初的时候，该指针指向托管堆的基址。</p>
<p>当应用程序创建第一个引用类型时，将为托管堆的基址中的类型分配内存。 应用程序创建下一个对象时，垃圾回收器在紧接第一个对象后面的地址空间内为它分配内存。 只要地址空间可用，垃圾回收器就会继续以这种方式为新对象分配空间。</p>
<p>严格来说，必须在托管堆上分配所有非 null 引用类型对象和所有装箱值类型对象。</p>
<p>从托管堆中分配内存要比非托管内存分配速度快。 由于运行时通过为指针添加值来为对象分配内存，所以这几乎和从堆栈中分配内存一样快。 另外，由于连续分配的新对象在托管堆中是连续存储，所以应用程序可以快速访问这些对象。</p>
<h1 id="heading-6yek5ps5yaf5a2y">释放内存</h1>
<p>垃圾回收器的优化引擎根据所执行的分配决定执行回收的最佳时间。</p>
<p>每个应用程序都有一组根。每个根或者指向托管堆中的对象，或者为空。
应用程序的根包含线程堆栈上的静态字段、局部变量和参数以及 CPU 寄存器。</p>
<p>垃圾回收器可以访问由<a target="_blank" href="https://docs.microsoft.com/zh-cn/dotnet/standard/managed-execution-process">实时 (JIT) 编译器</a>和运行时维护的活动根的列表。垃圾回收器对照此根列表检查应用程序的根，并在此过程中创建一个图表，在其中包含所有可从这些根中访问的对象。</p>
<p>如果有一个对象，不在上面创建的图表中，那意味着这个对象将无法从应用程序的根中访问。这个时候，垃圾回收器就可以考虑释放为它们分配的内存。</p>
<p>回收的时候，垃圾回收器会检查托管堆，查找无法访问的对象所占据的地址块。当发现无法访问的对象的时候，GC 会通过内存复制的方法来压缩内存中可以访问的对象，并释放分配给不可访问对象的地址空间块。在压缩可访问的对象之后，GC 会更正原来指向这个对象的指针，使应用程序的根指向新地址中的对象。在 GC 完成之后，会将托管堆指针指向最后一个可访问的对象。</p>
<blockquote>
<p>只有在发现了大量无法访问的对象的时候，才会压缩内存。</p>
</blockquote>
<h1 id="heading-5omy566h5acg55qe57qn5yir5zkm5ocn6io9">托管堆的级别和性能</h1>
<p>为了提高 GC 的性能，托管堆被划分为3代：第0代、第1代和第2代。</p>
<blockquote>
<p>划分不同代的依据：</p>
<ol>
<li>压缩托管堆的一部分内存比压缩整个托管堆速度快</li>
<li>较新的对象生存期较短，较旧的对象生存期较长</li>
<li>较新的对象趋向于互相关联，并且大致同时由应用程序访问</li>
</ol>
</blockquote>
<p>新创建的对象储存在第0代。在第0代满了的时候，GC 会执行回收，尝试释放第0代中的地址空间。在第0代的回收执行之后，GC 会将第0代的内存对象升级到第1代。</p>
<p>如果第0代托管堆的回收没有回收足够的内存，不能使应用程序成功完成创建新对象的尝试，垃圾回收器就会先执行第1代托管堆的回收，然后再执行第2代托管堆的回收。如果这样仍不能回收足够的内存，垃圾回收器将执行第2、1 和 0 代托管堆的回收。</p>
<p> 每次回收后，垃圾回收器都会压缩第0代托管堆中的可访问对象并将它们升级至第1代托管堆。 第1代托管堆中未被回收的对象将会升级至第2代托管堆。 由于垃圾回收器只支持三个级别，因此第2代托管堆中未被回收的对象会继续保留在第2代托管堆中，直到在将来的回收中确定它们为无法访问为止。</p>
<p>如果新创建的对象是大型对象，它们将延续到大型对象堆 (LOH)，这有时称为第 3 代。 第 3 代是在第 2 代中逻辑收集的物理生成。</p>
<h1 id="heading-6z2e5omy566h6lwe5rqq55qe5yaf5a2y6yek5ps">非托管资源的内存释放</h1>
<p>非托管资源需要显式清除。</p>
<p>最常用的非托管资源类型是包装操作系统资源的对象（例如文件句柄、窗口句柄或网络连接）。GC 可以跟踪封装非托管资源的托管对象的生存期，但却无法具体了解如何清理资源。</p>
<p>创建封装非托管资源的对象时，建议在公共 <strong>Dispose</strong> 方法中提供必要的代码以清理非托管资源。 通过提供 <strong>Dispose</strong> 方法，对象的用户可以在使用完对象后显式释放其内存。 使用封装非托管资源的对象时，应该了解 <strong>Dispose</strong> 并在必要时调用它。</p>
<p>还必须提供一种释放非托管资源的方法，以防类型使用者忘记调用 <strong>Dispose</strong>。 可以使用安全句柄来包装非托管资源，也可以重写 <a target="_blank" href="https://docs.microsoft.com/zh-CN/dotnet/api/system.object.finalize#system-object-finalize">Object.Finalize()</a> 方法。</p>
<h1 id="heading-5yaf5a2y55qe5z65pys55l6kg">内存的基本知识</h1>
<ul>
<li>每个进程都有其单独的虚拟地址空间</li>
<li>同一台计算机上的所有进程共享相同的物理内存和页文件（如果有）</li>
<li>默认情况下，32位计算机上的每个进程都具有 2 GB的用户模式虚拟地址空间</li>
<li>程序开发人员，默认只能使用虚拟地址空间。GC 会为你分配和释放托管堆上的虚拟内存</li>
<li>虚拟内存的三种状态：<ul>
<li>Free：该内存块没有引用关系，可用于分配</li>
<li>Reserved：给你用，其他人不能用。但是在提交之前，无法存储数据</li>
<li>Committed：内存块已指派给物理存储</li>
</ul>
</li>
<li>如果没有足够的可供保留的虚拟地址空间或可供提交的物理空间，则可能会用尽内存</li>
<li>托管堆可以看做大对象推和小对象堆的集合</li>
<li>大对象堆包含大小不小于 85,000 个字节的对象，这些对象通常是数组。 非常大的实例对象是很少见的。（可以配置阈值大小）</li>
</ul>
<p>即使在物理内存压力（即物理内存的需求）较低的情况下也会使用页文件。 首次出现物理内存压力较高的情况时，操作系统必须在物理内存中腾出空间来存储数据，并将物理内存中的部分数据备份到页文件中。 该数据只会在需要时进行分页，所以在物理内存压力较低的情况下也可能会进行分页。</p>
<h1 id="heading-5z6d5zy5zue5ps255qe6lh56il">垃圾回收的过程</h1>
<p>垃圾回收分为以下几个阶段：</p>
<ul>
<li>标记阶段：找到并创建所有活动对象的列表。</li>
<li>重定位阶段：用于更新对将要压缩的对象的引用。</li>
<li>压缩阶段：用于回收由死对象占用的空间，并压缩幸存的对象。 压缩阶段将垃圾回收中幸存下来的对象移至段中时间较早的一端。</li>
</ul>
<p>因为第 2 代回收可以占用多个段，所以可以将已提升到第 2 代中的对象移动到时间较早的段中。 可以将第 1 代幸存者和第 2 代幸存者都移动到不同的段，因为它们已被提升到第 2 代。</p>
<p>通常，由于复制大型对象会造成性能代偿，因此不会压缩大型对象堆 (LOH)。 但是，在 .NET Core 和 .NET Framework 4.5.1 及更高版本中，可以根据需要使用 <a target="_blank" href="https://docs.microsoft.com/zh-CN/dotnet/api/system.runtime.gcsettings.largeobjectheapcompactionmode">GCSettings.LargeObjectHeapCompactionMode</a> 属性按需压缩大型对象堆。 </p>
<h2 id="heading-56gu5a6a5rs75yqo5a56lgh">确定活动对象</h2>
<p>垃圾回收器使用以下信息来确定对象是否为活动对象：</p>
<ul>
<li><strong>堆栈根</strong>。 由实时 (JIT) 编译器和堆栈查看器提供的堆栈变量。 JIT 优化可以延长或缩短报告给垃圾回收器的堆栈变量内的代码的区域。</li>
<li><strong>垃圾回收句柄</strong>。 指向托管对象且可由用户代码或公共语言运行时分配的句柄。</li>
<li><strong>静态数据</strong>。 应用程序域中可能引用其他对象的静态对象。 每个应用程序域都会跟踪其静态对象。</li>
</ul>
<h2 id="heading-5oyc6lw3">挂起</h2>
<p>在垃圾回收启动之前，除了触发垃圾回收的线程以外的所有托管线程均会挂起。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655172950483/wWiwVyrWR.png" alt="image.png" /></p>
<p>//todo
内存虚拟地址和物理地址的转换过程</p>
]]></content:encoded></item><item><title><![CDATA[合并箱子重量 —— 亚马逊 2022 面试题]]></title><description><![CDATA[#leetcode #算法 
题目

Consider n packages, where packageWeights[i] represents the weight of the i th package, You can combine i th and i+1 th package if packageWeights[i] <packageWeights[i+1] and then discard the i th package. After this operation numbe...]]></description><link>https://blog.vning.vip/merge-weight</link><guid isPermaLink="true">https://blog.vning.vip/merge-weight</guid><category><![CDATA[C#]]></category><category><![CDATA[Amazon]]></category><dc:creator><![CDATA[夜归人]]></dc:creator><pubDate>Sat, 11 Jun 2022 17:11:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/sxNt9g77PE0/upload/v1654967381637/zjGaEy9H4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>#leetcode #算法 </p>
<h1 id="heading-6aky55uu">题目</h1>
<blockquote>
<p>Consider n packages, where packageWeights[i] represents the weight of the i th package, You can combine i th and i+1 th package if packageWeights[i] &lt;packageWeights[i+1] and then discard the i th package. After this operation number of packages reduces by 1 and weight of i+1 th package increases by packageWeights[i]. You can merge as many times as you want. Find the max possible weight of the package that can be achieved after any sequence of merge operations</p>
<p>Eg packageWeights =[2,9,10,3, 7] optimal order:</p>
<ul>
<li>iteration 1 combine packages at index 2 and 3 -&gt;new packageWeights =[2,19,3,7]</li>
<li>iteration 2 combine packages at index 1 and 2 -&gt;new packageWeights =[21,3,7]</li>
<li>iteration 3 combine packages at index 2 and 3 -&gt;new packageWeights =[21,10]</li>
<li>No more packages can be combined. The weight of the heaviest package is 21 Result:21</li>
</ul>
</blockquote>
<h1 id="heading-5yig5p6q">分析</h1>
<p>其实例子中给出来的分析已经足够。在理解这个问题的时候，可以直接尝试着用程序化的语言来理解，比如：</p>
<blockquote>
<ol>
<li>packageWeights =[2,9,10,3, 7]; var max = 0;</li>
<li>var index = 4; value = 7; current = 7; max = 7;</li>
<li>index = 3; value = 3; value &lt; current; current = 7 + 3; max = 10;</li>
<li>index = 2; value = 10; value !&lt; current; current = value(10); max = 10;</li>
<li>index = 1; value = 9; value &lt; current; current = 10 + 9; max = 19;</li>
<li>index = 0; value = 2; value &lt; current; current = 19 + 2; max = 21;</li>
<li>return max;</li>
</ol>
</blockquote>
<h1 id="heading-5luj56cb">代码</h1>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> packageCount = packageWeights.Length;
<span class="hljs-keyword">var</span> max = <span class="hljs-number">0</span>;
<span class="hljs-keyword">if</span> (packageCount == <span class="hljs-number">0</span>) { <span class="hljs-keyword">return</span> max; }

<span class="hljs-keyword">var</span> current = packageWeights[^<span class="hljs-number">1</span>];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> index = packageCount - <span class="hljs-number">1</span>; index &gt;= <span class="hljs-number">0</span>; index--)
{
    <span class="hljs-keyword">var</span> <span class="hljs-keyword">value</span> = packageWeights[index];
    current = <span class="hljs-keyword">value</span> &lt; current
        ? current + <span class="hljs-keyword">value</span>
        : <span class="hljs-keyword">value</span>;
    max = Math.Max(max, current);
}

<span class="hljs-keyword">return</span> max;
</code></pre>
<p>有没有发现，在上一步中，当你用程序化的思维去理解这个问题的时候，编程就成了一种自然而然的语言转换了。
时间复杂度：O(n)
空间复杂度：O(1)</p>
<h1 id="heading-5rwl6kv5qgi5l6l">测试案例</h1>
<p>如果你是在面对面的跟面试官沟通的话，往往面试官会问你要怎么设计测试用例。
其实这个也是考察你对编码的严谨性的。</p>
<p>本题的测试案例可以这样逐步来想：</p>
<ol>
<li>只有一条重量数据：<code>[TestCase(new[] { 2 }, ExpectedResult = 2)]</code><ol>
<li>增加一个大重量的包在前面：<code>[TestCase(new[] { 10, 2 }, ExpectedResult = 10)]</code><ol>
<li>增加一个相同重量的包在前面：<code>[TestCase(new[] { 10, 10, 2 }, ExpectedResult = 10)]</code></li>
<li>增加一个相同重量的包在后面：<code>[TestCase(new[] { 10, 2, 2 }, ExpectedResult = 10)]</code></li>
</ol>
</li>
<li>增加一个大重量的包在后面：<code>[TestCase(new[] { 2, 10 }, ExpectedResult = 12)]</code></li>
</ol>
</li>
<li>合并之后有两个包：<code>[TestCase(new[] { 2, 9, 10, 3, 7 }, ExpectedResult = 21)]</code></li>
<li>合并之后只有一个包：<code>[TestCase(new[] { 2, 9, 10, 3, 8 }, ExpectedResult = 32)]</code></li>
</ol>
]]></content:encoded></item><item><title><![CDATA[C# 发布-订阅模式]]></title><description><![CDATA[#CSharp #PubSub
发布-订阅模式
有些时候，发布-订阅模式也会被称为观察者模式，但其实，如果细分的话，这两者之间还是有些细微的差别的。
观察者模式是在主题者（Subject）内部，维护一个观察者（Observer）列表。然后当主题者的状态发生变更的时候，主题者可以通知观察者发生了变化。观察者可以主动访问主题者，获得变更。也可以在通知观察者的时候，直接带上变更的消息。
在观察者模式中，主题者和观察者还是存在部分的耦合。因此，我们可以引入一个中介，来接触这种耦合。
类 A（发布者）发生...]]></description><link>https://blog.vning.vip/csharp-pubsub</link><guid isPermaLink="true">https://blog.vning.vip/csharp-pubsub</guid><category><![CDATA[C#]]></category><dc:creator><![CDATA[夜归人]]></dc:creator><pubDate>Wed, 08 Jun 2022 06:51:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/V5vqWC9gyEU/upload/v1654671005225/oqM-XAsD_.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>#CSharp #PubSub</p>
<h1 id="heading-5yr5bidleiuoumyheaooew8jw">发布-订阅模式</h1>
<p>有些时候，发布-订阅模式也会被称为观察者模式，但其实，如果细分的话，这两者之间还是有些细微的差别的。</p>
<p>观察者模式是在主题者（Subject）内部，维护一个观察者（Observer）列表。然后当主题者的状态发生变更的时候，主题者可以通知观察者发生了变化。观察者可以主动访问主题者，获得变更。也可以在通知观察者的时候，直接带上变更的消息。</p>
<p>在观察者模式中，主题者和观察者还是存在部分的耦合。因此，我们可以引入一个中介，来接触这种耦合。</p>
<p>类 A（发布者）发生了变化，将这个变化以消息或者事件的形式，发送给中介类，然后中介类将这个消息，转发给订阅这个消息的类 B（订阅者），这样发布者和订阅者并不直接产生依赖，他们都仅依赖于中介类（这个类可以是一个消息队列，也可以是你自定义的一个类型）。</p>
<h1 id="heading-5luj56cb">代码</h1>
<p>发布者基类：</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EventPublisher</span>  
{  
    <span class="hljs-keyword">private</span> CustomEventHub EventHub { <span class="hljs-keyword">get</span>; }  

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">EventPublisher</span>(<span class="hljs-params"></span>)</span>  
    {
        EventHub = CustomEventHub.GetInstance();  
    }  

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PublishEvent</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">EventType eventType, T <span class="hljs-keyword">value</span></span>)</span>  
    {
        EventHub.Publish(<span class="hljs-keyword">new</span> CustomEvent { Type = eventType, Value = <span class="hljs-keyword">value</span>});  
    }
}
</code></pre>
<p>订阅者基类：</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EventSubscriber</span>
{
    <span class="hljs-keyword">private</span> CustomEventHub EventHub { <span class="hljs-keyword">get</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">EventSubscriber</span>(<span class="hljs-params"></span>)</span>
    {
        EventHub = CustomEventHub.GetInstance();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Subscribe</span>(<span class="hljs-params">EventType eventType, Action&lt;CustomEvent&gt; action</span>)</span>
    {
        EventHub.AddSubscriber(eventType, action);
    }
}
</code></pre>
<p>中介类：（为保证只有一个，这里额外加入了单例模式）</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CustomEventHub</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> CustomEventHub Instance = <span class="hljs-keyword">new</span>();
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> Dictionary&lt;EventType, IList&lt;Action&lt;CustomEvent&gt;&gt;&gt; _eventTypeAndSubscribers = <span class="hljs-keyword">new</span>();

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> CustomEventHub <span class="hljs-title">GetInstance</span>(<span class="hljs-params"></span>)</span> =&gt; Instance;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">CustomEventHub</span>(<span class="hljs-params"></span>)</span> { }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Publish</span>(<span class="hljs-params">CustomEvent customEvent</span>)</span>
    {
        <span class="hljs-keyword">var</span> eventType = customEvent.Type;
        <span class="hljs-keyword">if</span> (!_eventTypeAndSubscribers.ContainsKey(eventType)) { <span class="hljs-keyword">return</span>; }

        <span class="hljs-keyword">var</span> subscribers = _eventTypeAndSubscribers[eventType];
        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> subscriberAction <span class="hljs-keyword">in</span> subscribers)
        {
            subscriberAction.Invoke(customEvent);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AddSubscriber</span>(<span class="hljs-params">EventType eventType, Action&lt;CustomEvent&gt; action</span>)</span>
    {
        <span class="hljs-keyword">if</span> (_eventTypeAndSubscribers.ContainsKey(eventType))
        {
            _eventTypeAndSubscribers[eventType].Add(action);
        }
        <span class="hljs-keyword">else</span>
        {
            _eventTypeAndSubscribers.Add(eventType, <span class="hljs-keyword">new</span> List&lt;Action&lt;CustomEvent&gt;&gt; { action });
        }
    }
}
</code></pre>
<p>自定义事件：</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CustomEvent</span>
{
    <span class="hljs-keyword">public</span> EventType Type { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">object</span> Value { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>事件类型：</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> EventType
{
    TimeChanged
}
</code></pre>
<h1 id="heading-5a6e6zmf5bqu55so">实际应用</h1>
<p>时间变化时，发出事件通知：</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TimeProvider</span> : <span class="hljs-title">EventPublisher</span>
{
    <span class="hljs-keyword">private</span> Timer _timer = <span class="hljs-literal">null</span>!;

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Interval { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> DateTime CurrentTime { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Init</span>(<span class="hljs-params"></span>)</span>
    {
        CurrentTime = <span class="hljs-keyword">new</span> DateTime(<span class="hljs-number">1900</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>);
        _timer = <span class="hljs-keyword">new</span> Timer(Interval)
        {
            AutoReset = <span class="hljs-literal">true</span>
        };
        _timer.Elapsed += OnTimeChanged;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnTimeChanged</span>(<span class="hljs-params"><span class="hljs-keyword">object</span>? sender, ElapsedEventArgs e</span>)</span>
    {
        Console.Write(<span class="hljs-string">$"TimeChanged From <span class="hljs-subst">{CurrentTime :HH:mm:ss}</span>"</span>);
        CurrentTime = CurrentTime.Add(TimeSpan.FromMilliseconds(Interval));
        Console.WriteLine(<span class="hljs-string">$" To <span class="hljs-subst">{CurrentTime :HH:mm:ss}</span>"</span>);
        PublishEvent(EventType.TimeChanged, CurrentTime);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Start</span>(<span class="hljs-params"></span>)</span>
    {
        _timer.Enabled = <span class="hljs-literal">true</span>;
        GC.KeepAlive(_timer);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Stop</span>(<span class="hljs-params"></span>)</span>
    {
        _timer.Enabled = <span class="hljs-literal">false</span>;
    }
}
</code></pre>
<p>收到时间变化的事件后，打印一个“Hello World”：</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SimulatorSystem</span> : <span class="hljs-title">EventSubscriber</span>
{
    <span class="hljs-keyword">private</span> CustomEventHub _eventHub;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Init</span>(<span class="hljs-params"></span>)</span>
    {
        Subscribe(EventType.TimeChanged, OnTimeChanged);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnTimeChanged</span>(<span class="hljs-params">CustomEvent customEvent</span>)</span>
    {
        <span class="hljs-keyword">if</span> (customEvent.Type != EventType.TimeChanged) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Invalid event type"</span>); }
        Console.WriteLine(<span class="hljs-string">"Hello World"</span>);
    }
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[扇区4k对齐]]></title><description><![CDATA[#Disk #分区格式 #分区对齐
Documnet：

https://www.diskgenius.cn/exp/about-4k-alignment.php
https://study.qqcf.com/web/445/73426.htm

扇区
扇区的概念来自于物理硬盘，扇区是硬盘最小的物理存储单元。
之前的硬盘比较小，扇区的大小是512B。后来硬盘容量变大了，扇区的大小变成了4K。
但是为了兼容以前的系统读写，又发明了一个逻辑扇区的概念。
所谓的逻辑扇区就是硬盘内部，将4K的物理扇区在...]]></description><link>https://blog.vning.vip/4k-align</link><guid isPermaLink="true">https://blog.vning.vip/4k-align</guid><category><![CDATA[disk]]></category><dc:creator><![CDATA[夜归人]]></dc:creator><pubDate>Fri, 03 Jun 2022 03:48:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/wYD_wfifJVs/upload/v1654228037924/322gF9Oei.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>#Disk #分区格式 #分区对齐</p>
<p>Documnet：</p>
<ol>
<li><a target="_blank" href="https://www.diskgenius.cn/exp/about-4k-alignment.php">https://www.diskgenius.cn/exp/about-4k-alignment.php</a></li>
<li><a target="_blank" href="https://study.qqcf.com/web/445/73426.htm">https://study.qqcf.com/web/445/73426.htm</a></li>
</ol>
<h1 id="heading-5omh5yy6">扇区</h1>
<p>扇区的概念来自于物理硬盘，扇区是硬盘最小的物理存储单元。</p>
<p>之前的硬盘比较小，扇区的大小是512B。后来硬盘容量变大了，扇区的大小变成了4K。</p>
<p>但是为了兼容以前的系统读写，又发明了一个逻辑扇区的概念。
所谓的逻辑扇区就是硬盘内部，将4K的物理扇区在逻辑上划分为多个扇区片段，并将其作为普通的扇区（一般为512字节大小）报告给操作系统及应用软件。实际读写时，由硬盘内的程序（固件）负责在逻辑扇区与物理扇区之间进行转换，上层程序“感觉”不到物理扇区的存在。</p>
<p>所以逻辑扇区是一个人为制造的概念，<strong>在操作系统要读写某个逻辑扇区时，硬盘底层在实际操作时都会读写逻辑扇区所在的整个物理扇区</strong>。</p>
<h1 id="heading-57ch">簇</h1>
<p>文件系统是操作系统和硬盘之间的接口。当我们需要读取一个文件的时候，会请求相应的文件系统打开一个文件。但是操作系统无法对数目众多的扇区（逻辑扇区）进行寻址，所以操作系统就会将相邻的扇区组合在一起，形成一个簇（Cluster）。</p>
<p>而且操作系统为了更好地管理磁盘空间和更高效地从硬盘读取数据，规定<strong>一个簇中只能放置一个文件的内容</strong>。因此文件所占用的空间，只能是簇的整数倍。</p>
<p>==即使文件实际大小小于一个簇的大小，它也要占一个簇的空间==。</p>
<p>所以，一般情况下文件所占空间要略大于文件的实际大小，只有在少数情况下，即文件的实际大小恰好是簇的整数倍时，文件的实际大小才会与所占空间完全一致。</p>
<p>计算文件所占空间时，可以用如下公式： </p>
<blockquote>
<p>簇数=取整(文件大小/簇大小)+1 
所占空间=簇数×磁盘簇大小</p>
</blockquote>
<h1 id="heading-5yig5yy65zkm5qc85byp5yyw">分区和格式化</h1>
<p>分区是指从磁盘上划分出来的一大片连续的扇区。</p>
<p>格式化则是对分区范围内扇区的使用进行规划。
比如文件数据的储存如何安排、文件属性储存在哪里、目录结构如何存储等等。</p>
<p>格式化程序会将分区里面的所有扇区从头至尾进行分组，划分为固定大小的“簇”，并按顺序进行编号。每个“簇”可固定包含一个或多个扇区，其扇区个数总是2的n次方。格式化以后，分区就会以“簇”为最小单位进行读写。文件的数据、属性等等信息都要保存到“簇”里面。</p>
<h1 id="heading-5yig5yy65a56b2q">分区对齐</h1>
<p><strong>划分分区时，是以逻辑扇区为单位进行划分的</strong>，分区可以从任意编号的逻辑扇区开始。</p>
<p>如果分区的起始位置没有对齐到某个物理扇区的边缘，格式化后，所有的“簇”也将无法对齐到物理扇区的边缘。</p>
<p>如下图所示，每个物理扇区由4个逻辑扇区组成。分区是从3号扇区开始的。格式化后，每个簇占用了4个扇区，这些簇都没有对齐到物理扇区的边缘，也就是说，每个簇都跨越了2个物理扇区。 </p>
<p><img src="https://www.diskgenius.cn/exp/img/4k-align-01.png" alt="为什么要分区对齐" />  </p>
<p>由于磁盘总是以物理扇区为单位进行读写，在这样的分区情况下，当要读取某个簇时，实际上总是需要多读取一个物理扇区的数据。比如要读取0号簇共4个逻辑扇区的数据，磁盘实际执行时，必须要读取0号和1号两个物理扇区共8个逻辑扇区的数据。同理，对“簇”的写入操作也是这样。显而易见，这样会造成读写性能的严重下降。</p>
<p>下面再看对齐的情况。如下图所示，分区从4号扇区开始，刚好对齐到了物理扇区1的边缘，格式化后，每个簇同样占用了4个扇区，而且这些簇都对齐到了物理扇区的边缘。</p>
<p><img src="https://www.diskgenius.cn/exp/img/4k-align-02.png" alt="为什么要分区对齐" />  </p>
<p>在这样对齐的情况下，当要读取某个簇，磁盘实际执行时并不需要额外读取任何扇区，可以充分发挥磁盘的读写性能。显然这正是我们需要的。</p>
<p>由此可见，<strong>对于物理扇区大小与逻辑扇区大小不一致的磁盘，分区4K对齐才能充分发挥磁盘的读写性能。而不对齐就会造成磁盘读写性能的下降</strong>。</p>
]]></content:encoded></item><item><title><![CDATA[C# Yield]]></title><description><![CDATA[#CSharp #yield
yield return
理解及应用
yield就是生成了一个值之后，控制回到了调用方继续执行，执行完成后，需要下一个值的时候，会重新回到yield return之后的语句处，继续执行。
结合下面的程序，理解了这点之后，跟yield相关的知识基本就都了解了。
代码：
public static void Main()
{
    var testClass = new TestClass();
    Console.WriteLine("[Main] BeginT...]]></description><link>https://blog.vning.vip/csharp-yield</link><guid isPermaLink="true">https://blog.vning.vip/csharp-yield</guid><category><![CDATA[C#]]></category><dc:creator><![CDATA[夜归人]]></dc:creator><pubDate>Thu, 02 Jun 2022 16:09:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1654225925157/GMztzV8Nq.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>#CSharp #yield</p>
<h1 id="heading-yield-return">yield return</h1>
<h2 id="heading-55cg6kej5yk5bqu55so">理解及应用</h2>
<p>yield就是生成了一个值之后，控制回到了调用方继续执行，执行完成后，需要下一个值的时候，会重新回到yield return之后的语句处，继续执行。</p>
<p>结合下面的程序，理解了这点之后，跟yield相关的知识基本就都了解了。</p>
<p>代码：</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> testClass = <span class="hljs-keyword">new</span> TestClass();
    Console.WriteLine(<span class="hljs-string">"[Main] BeginTest"</span>);
    <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> <span class="hljs-keyword">value</span> <span class="hljs-keyword">in</span> testClass)
    {
        Console.WriteLine(<span class="hljs-string">$"[Main] Got Value <span class="hljs-subst">{<span class="hljs-keyword">value</span>}</span>"</span>);  
        Console.WriteLine(<span class="hljs-string">"[Main] Do some logic here"</span>);  
        Console.WriteLine(<span class="hljs-string">"[Main] Try to get next value"</span>);  
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TestClass</span> : <span class="hljs-title">IEnumerable</span>&lt;<span class="hljs-title">int</span>&gt;  
{  
    <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator&lt;<span class="hljs-keyword">int</span>&gt; <span class="hljs-title">GetEnumerator</span>(<span class="hljs-params"></span>)</span>  
    {        
        Console.WriteLine(<span class="hljs-string">"[Class] Begin Generate"</span>);  
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> index = <span class="hljs-number">0</span>; index &lt; <span class="hljs-number">3</span>; index++)  
        {   
            Console.WriteLine(<span class="hljs-string">$"[Class] Generate <span class="hljs-subst">{index}</span>"</span>);  
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> index;  
            Console.WriteLine(<span class="hljs-string">$"[Class] Finished send <span class="hljs-subst">{index}</span>"</span>);  
        }  
        Console.WriteLine(<span class="hljs-string">"[Class] Finished Generate"</span>);  
    }  

    IEnumerator IEnumerable.GetEnumerator()  
    {   
        <span class="hljs-keyword">return</span> GetEnumerator();  
    }
}
</code></pre>
<p>程序执行日志：</p>
<pre><code class="lang-log">[Main] BeginTest
[Class] Begin Generate
[Class] Generate 0
[Main] Got Value 0
[Main] Do some logic here
[Main] Try to get next value
[Class] Finished send 0
[Class] Generate 1
[Main] Got Value 1
[Main] Do some logic here
[Main] Try to get next value
[Class] Finished send 1
[Class] Generate 2
[Main] Got Value 2
[Main] Do some logic here
[Main] Try to get next value
[Class] Finished send 2
[Class] Finished Generate
</code></pre>
<h2 id="heading-6ycs5b2s6lt5luj5zmo">递归迭代器</h2>
<p>代码：</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>  
{  
    <span class="hljs-keyword">var</span> testClass = <span class="hljs-keyword">new</span> TestClass();  
    testClass.Value = <span class="hljs-number">1</span>;  
    testClass.SubClasses = <span class="hljs-keyword">new</span> List&lt;TestClass&gt;  
    {   
        <span class="hljs-keyword">new</span>()  
        {   
            Value = <span class="hljs-number">2</span>,  
            SubClasses = <span class="hljs-keyword">new</span> List&lt;TestClass&gt;  
            {   
                <span class="hljs-keyword">new</span>() { Value = <span class="hljs-number">3</span> },  
                <span class="hljs-keyword">new</span>() { Value = <span class="hljs-number">4</span> }  
            }
        },
        <span class="hljs-keyword">new</span>() { Value = <span class="hljs-number">5</span> }  
    };  
    <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> testClass)  
    {
        Console.WriteLine(item);  
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TestClass</span> : <span class="hljs-title">IEnumerable</span>&lt;<span class="hljs-title">int</span>&gt;  
{  
    <span class="hljs-keyword">public</span> IList&lt;TestClass&gt; SubClasses { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }  
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Value { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }  
    <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator&lt;<span class="hljs-keyword">int</span>&gt; <span class="hljs-title">GetEnumerator</span>(<span class="hljs-params"></span>)</span>  
    {        
        <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> Value;  

        <span class="hljs-keyword">if</span> (SubClasses <span class="hljs-keyword">is</span> not { Count: &gt; <span class="hljs-number">0</span> }) { <span class="hljs-keyword">yield</span> <span class="hljs-keyword">break</span>; }  

        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> subClass <span class="hljs-keyword">in</span> SubClasses)  
        {            
            <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> subClass)  
            {    
                <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> item;  
            }
        }
    }  

    IEnumerator IEnumerable.GetEnumerator() =&gt; GetEnumerator();  
}
</code></pre>
<p>程序执行日志：</p>
<pre><code class="lang-log">1
2
3
4
5
</code></pre>
<h2 id="heading-5roo5osp">注意</h2>
<p>递归迭代器存在一定的风险，如果是不平衡二叉树的话，可能会很深，获取值的时候的递归迭代的成本较高。</p>
<h2 id="heading-5y6f55cg">原理</h2>
<p>C# 编译器在遇到迭代器的时候，会根据枚举数模式（<a target="_blank" href="https://codewithshadman.com/iterator-pattern-csharp/#what-is-iterator-pattern">Iterator Pattern</a>）将代码展开成CLI。</p>
<p>在生成的代码中，C# 编译器会首先创建一个嵌套的私有类来实现 <code>IEnumerator&lt;T&gt;</code> 接口，以及它的 <code>Current</code> 属性和 <code>MoveNext()</code> 方法。</p>
<p><code>Current</code> 属性返回与迭代器的返回类型对应的一个类型。</p>
<p>C# 编译器检查包含在迭代器中的代码，并在 <code>MoveNext()</code> 方法和 <code>Current</code> 属性中创建必要的代码来模拟它的行为。</p>
<h1 id="heading-yield-break">yield break</h1>
<p>取消更多的迭代</p>
<h1 id="heading-5roo5osp">注意</h1>
<ol>
<li><code>yield</code> 语句只能在方法、用户自定义操作符、或者索引器或属性的 <code>get</code> 防蚊器方法中出现。成员不可以获取任何 <code>ref</code> 或 <code>out</code> 参数</li>
<li><code>yield</code> 语句不能在匿名语句或者 <code>lambda</code> 方法中出现</li>
<li><code>yield</code> 语句不能在 <code>try</code> 语句的 <code>catch</code> 和 <code>final</code> 子句中出现。除此之外，<code>yield</code> 语句能在 <code>try</code> 块中出现的前提是，它没有 <code>catch</code> 块</li>
</ol>
]]></content:encoded></item></channel></rss>