<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://bssn520.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://bssn520.github.io/" rel="alternate" type="text/html" /><updated>2026-10-30T06:46:49+00:00</updated><id>https://bssn520.github.io/feed.xml</id><title type="html">Luke</title><subtitle>&quot;Read widely, follow the news, snack less, sleep well.&quot;
</subtitle><author><name>Luke</name><email>Bssn520@duck.com</email></author><entry><title type="html">用 Surge 替代 LocalDevVPN，给 SideStore 做自动续签</title><link href="https://bssn520.github.io/p/22/" rel="alternate" type="text/html" title="用 Surge 替代 LocalDevVPN，给 SideStore 做自动续签" /><published>2026-10-30T00:00:00+00:00</published><updated>2026-10-30T00:00:00+00:00</updated><id>https://bssn520.github.io/p/%E7%94%A8Surge%E6%9B%BF%E4%BB%A3LocalDevVPN%E5%81%9ASideStore%E7%BB%AD%E7%AD%BE</id><content type="html" xml:base="https://bssn520.github.io/p/22/"><![CDATA[<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2026-10-30</code>；同日补充快捷指令与 Loon 说明。</p>
</blockquote>

<p>SideStore 续签，绕不开 <code class="language-plaintext highlighter-rouge">10.7.0.1</code> 这个回环地址。官方做法是再装一个 LocalDevVPN。</p>

<p>可我手机上已经开着 Surge 了。再挂一个 VPN，既多余，也容易抢路由。</p>

<p>于是想：既然 Surge 本身就能改路由、改 IP，为什么还要多开一个？</p>

<h2 id="本质">本质</h2>

<p>SideStore 刷新时，会连本机的 <code class="language-plaintext highlighter-rouge">10.7.0.1</code>。LocalDevVPN 的作用，就是把这个地址指回手机自己。</p>

<p>Surge iOS 5.23.0 起支持 <code class="language-plaintext highlighter-rouge">IP Rewrite</code> 的 <code class="language-plaintext highlighter-rouge">reflect</code>。一句话：把目标 IP 改写成当前设备自身。</p>

<p>所以 LocalDevVPN 要干的事，Surge 一条规则就够了。</p>

<h2 id="模块">模块</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!name=SideStore Loopback
#!desc=用 Surge 替代 LocalDevVPN 做 10.7.0.1 回环（需 Surge iOS 5.23.0+，开增强模式）

[General]
ipv6-vif = disabled
tun-included-routes = %INSERT% 10.7.0.1/32

[IP Rewrite]
10.7.0.1 = reflect
</code></pre></div></div>

<p>两点别漏：</p>

<ul>
  <li>开增强模式，否则 <code class="language-plaintext highlighter-rouge">IP Rewrite</code> 不生效。</li>
  <li><code class="language-plaintext highlighter-rouge">tun-included-routes</code> 用 <code class="language-plaintext highlighter-rouge">%INSERT%</code>，只把 <code class="language-plaintext highlighter-rouge">10.7.0.1/32</code> 塞进现有路由，别用 <code class="language-plaintext highlighter-rouge">%APPEND%</code> 把整段覆盖掉。</li>
</ul>

<p>模块我丢在自己的 Scripts gist 里，Surge 直接订阅。</p>

<h2 id="再配快捷指令">再配快捷指令</h2>

<p>回环通了以后，续签本身还是 SideStore 的事。把「刷新全部 App」丢进快捷指令，再挂个定时自动化，就能定期自动续签，不用再盯着七天过期。</p>

<p>我用的这套快捷指令是给 <strong>Surge + Seal</strong> 准备的，不是裸跑 SideStore：</p>

<ul>
  <li>快捷指令：<a href="https://www.icloud.com/shortcuts/561b1c4a0cbe422a9fa12640426d56b0">iCloud 链接</a></li>
  <li>Seal 是什么、怎么配，看这个视频：<a href="https://www.bilibili.com/video/BV1k5HS6aE5s/">BV1k5HS6aE5s</a></li>
</ul>

<p>以前是 LocalDevVPN + 手动点；现在是 Surge 常驻 + 快捷指令定时跑。少一个 App，少一步操作。</p>

<h2 id="loon-怎么替代未测试">Loon 怎么替代（未测试）</h2>

<p>正文里写过「Loon 大致也能」。有网友追问具体怎么做，我查了官方文档，整理如下。</p>

<p><strong>我没有 Loon，下面整段都没实测。</strong> 按文档推导，欢迎用 Loon 的朋友反馈。</p>

<p>Loon 从 Build <strong>1007</strong> 起支持 <code class="language-plaintext highlighter-rouge">include-tun</code> 和插件里的 <code class="language-plaintext highlighter-rouge">[IP Rewrite]</code>，官方示例就是 <code class="language-plaintext highlighter-rouge">10.7.0.1 = reflect</code>，思路和 Surge 同构。参见：<a href="https://loon0x00.github.io/docs/Plugin">Plugin 文档</a>、<a href="https://loon0x00.github.io/docs/General">General · include-tun</a>。</p>

<p>注意：<code class="language-plaintext highlighter-rouge">[IP Rewrite]</code> <strong>只能写在插件里</strong>，写主配置不生效。</p>

<p>可对标 Surge 建一个插件：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!name=SideStore Loopback
#!desc=用 Loon 替代 LocalDevVPN 做 10.7.0.1 回环（需 Build 1007+，未测试）

[General]
include-tun = 10.7.0.1/32
ipv6-vif = off

[IP Rewrite]
10.7.0.1 = reflect
</code></pre></div></div>

<p>启用插件后，断开再连一次 Loon 的 VPN，让路由生效；同时关掉 LocalDevVPN 或旧的 SideStore WireGuard 隧道（iOS 同时只能挂一个 VPN）。然后在 SideStore 里试 Refresh，看 Device Reachability。</p>

<p>几点别踩坑：</p>

<ul>
  <li>Build 低于 1007 没有 IP Rewrite。</li>
  <li>主配置若把 <code class="language-plaintext highlighter-rouge">10.7.0.1</code> 写进更具体的 <code class="language-plaintext highlighter-rouge">bypass-tun</code>，可能盖掉插件的 <code class="language-plaintext highlighter-rouge">include-tun</code>。</li>
  <li>别再走「导入 SideStore 的 WireGuard <code class="language-plaintext highlighter-rouge">.conf</code>」那条旧路；2023 年有人说 Loon 不支持那种回环，指的是 WG，不是现在的 <code class="language-plaintext highlighter-rouge">reflect</code>。</li>
  <li>上面那条 iCloud 快捷指令是 Surge + Seal 路径，<strong>不要当成 Loon 方案照搬</strong>。Loon 有 <code class="language-plaintext highlighter-rouge">Loon://on</code> / <code class="language-plaintext highlighter-rouge">Loon://off</code>，和 Seal 怎么拼，我没测过。</li>
</ul>

<p>若只是想换掉 LocalDevVPN、不一定非用 Loon，社区也有 ClashMi 的现成覆写可参考：<a href="https://github.com/tom-snow/Sidestore-ClashMi">Sidestore-ClashMi</a>。</p>

<p>回环和 anisette 是两件事。签名服务器、on-device anisette 仍在 SideStore 设置里管，跟代理客户端无关。</p>

<h2 id="一点判断">一点判断</h2>

<p>不是所有代理客户端都能这么干。Loon 按官方文档，能力已经对齐；Shadowrocket 大概率可以；Quantumult X、Stash、Egern 我还没确认。</p>

<p>对我来说，结论很简单：已经在用 Surge，就没必要再为 SideStore 单独养一个 VPN。</p>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="iOS" /><summary type="html"><![CDATA[本文创建于 2026-10-30；同日补充快捷指令与 Loon 说明。]]></summary></entry><entry><title type="html">Posix API</title><link href="https://bssn520.github.io/p/21/" rel="alternate" type="text/html" title="Posix API" /><published>2024-12-12T00:00:00+00:00</published><updated>2024-12-12T00:00:00+00:00</updated><id>https://bssn520.github.io/p/PosixApi%E4%BB%A5%E5%8F%8A%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E6%A0%88</id><content type="html" xml:base="https://bssn520.github.io/p/21/"><![CDATA[<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2024-12-12</code>；修改于 <code class="language-plaintext highlighter-rouge">2024-12-13</code>；</p>
</blockquote>

<h2 id="一linux-网络io-api">一、Linux 网络IO API</h2>

<p>Posix API 网络通信相关函数：</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.45hpx9xjau.webp" alt="image" style="zoom:50%;" />
</div>

<p>TCP 状态迁移图：</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.8vmywzv4nj.webp" alt="image" style="zoom:50%;" />
</div>

<h2 id="二用于建立连接的api">二、用于建立连接的API</h2>

<h3 id="socket">socket</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># man socket
</span>
<span class="cp"># desc:  socket - create an endpoint for communication
</span>
       <span class="cp">#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span>       <span class="cp">#include</span> <span class="cpf">&lt;sys/socket.h&gt;</span><span class="cp">
</span>
       <span class="kt">int</span> <span class="nf">socket</span><span class="p">(</span><span class="kt">int</span> <span class="n">domain</span><span class="p">,</span> <span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="kt">int</span> <span class="n">protocol</span><span class="p">);</span>
</code></pre></div></div>

<p>一个 socket 套接字由两部分组成，分别是 <strong>文件描述符 <code class="language-plaintext highlighter-rouge">fd</code></strong> 和 <strong>TCP控制块</strong> <code class="language-plaintext highlighter-rouge">tcp control block</code>；理所应当的，socket 函数就是负责处理这两部分的。</p>

<ul>
  <li>fd: 文件描述符，int 类型，系统递增分配，底层为 bitmap(位图)，通过置0置1来判断某个fd是否已经分配了；</li>
</ul>

<h3 id="bind">bind</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># man bind:
</span>
<span class="cp"># desc: bind - bind a name to a socket
</span>
       <span class="cp">#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span>       <span class="cp">#include</span> <span class="cpf">&lt;sys/socket.h&gt;</span><span class="cp">
</span>
       <span class="kt">int</span> <span class="nf">bind</span><span class="p">(</span><span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="n">addr</span><span class="p">,</span> <span class="n">socklen_t</span> <span class="n">addrlen</span><span class="p">);</span>

</code></pre></div></div>

<p>bind 函数用于将我们设置的 ip地址 和 port端口设置进 socket 分配的 tcb(TCP控制块)内，以备后续连接。</p>

<p>注：对于 client 来说，bind 不是必须的，如果没有主动绑定到端口的话，后期系统会自动为其分配随机端口。</p>

<h3 id="listern">listern</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># man listen
</span>
<span class="cp"># desc: listen - listen for connections on a socket
</span>
       <span class="cp">#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span>       <span class="cp">#include</span> <span class="cpf">&lt;sys/socket.h&gt;</span><span class="cp">
</span>
       <span class="kt">int</span> <span class="nf">listen</span><span class="p">(</span><span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="kt">int</span> <span class="n">backlog</span><span class="p">);</span>
</code></pre></div></div>

<p>listen 函数主要职责就是，将套接字中tcb内的状态设置为监听状态；为 tcb 分配一个全连接队列，以及一个半连接队列；</p>

<ul>
  <li>tcb -&gt; status = TCP_STATUS_LISTEN</li>
  <li>tcb -&gt; syn_queue</li>
  <li>tcb -&gt; qccept_queue</li>
</ul>

<p>listen状态后，才能够建立TCP连接，接下来就是TCP建立一个连接的流程；</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.9rjgcg99i9.webp" alt="image" style="zoom:50%;" />
</div>

<h3 id="accept">accept</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># man accept
</span>
<span class="cp"># desc: accept, accept4 - accept a connection on a socket
</span>
       <span class="cp">#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span>       <span class="cp">#include</span> <span class="cpf">&lt;sys/socket.h&gt;</span><span class="cp">
</span>
       <span class="kt">int</span> <span class="nf">accept</span><span class="p">(</span><span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="n">addr</span><span class="p">,</span> <span class="n">socklen_t</span> <span class="o">*</span><span class="n">addrlen</span><span class="p">);</span>
</code></pre></div></div>

<p>accept 函数的工作分为两部分，分配fd给对应的client，把 fd 和 tcb 做映射；</p>

<h2 id="三用于传输数据的api">三、用于传输数据的API</h2>

<h3 id="recv">recv</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># man recv
</span>
<span class="n">desc</span><span class="o">:</span> <span class="n">recv</span><span class="p">,</span> <span class="n">recvfrom</span><span class="p">,</span> <span class="n">recvmsg</span> <span class="o">-</span> <span class="n">receive</span> <span class="n">a</span> <span class="n">message</span> <span class="n">from</span> <span class="n">a</span> <span class="n">socket</span>

       <span class="cp">#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span>       <span class="cp">#include</span> <span class="cpf">&lt;sys/socket.h&gt;</span><span class="cp">
</span>
       <span class="kt">ssize_t</span> <span class="nf">recv</span><span class="p">(</span><span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="send">send</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># man send
</span>
<span class="n">desc</span><span class="o">:</span> <span class="n">send</span><span class="p">,</span> <span class="n">sendto</span><span class="p">,</span> <span class="n">sendmsg</span> <span class="o">-</span> <span class="n">send</span> <span class="n">a</span> <span class="n">message</span> <span class="n">on</span> <span class="n">a</span> <span class="n">socket</span>

       <span class="cp">#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span>       <span class="cp">#include</span> <span class="cpf">&lt;sys/socket.h&gt;</span><span class="cp">
</span>
       <span class="kt">ssize_t</span> <span class="nf">send</span><span class="p">(</span><span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="四断开连接">四、断开连接</h2>

<h3 id="close">close</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"># man close
</span>
<span class="n">desc</span><span class="o">:</span> <span class="n">close</span> <span class="o">-</span> <span class="n">close</span> <span class="n">a</span> <span class="n">file</span> <span class="n">descriptor</span>

       <span class="cp">#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
</span>
       <span class="kt">int</span> <span class="nf">close</span><span class="p">(</span><span class="kt">int</span> <span class="n">fd</span><span class="p">);</span>
</code></pre></div></div>

<p>close 函数的工作大概是：</p>

<ol>
  <li>关闭一个fd；</li>
  <li>之后向被动方发送一个 <code class="language-plaintext highlighter-rouge">fin 字段置1; 数据长度为0</code> 的tcp报文</li>
  <li>被动发 recv 该报文后发现数据段长度为0，得知这是要求关闭的一个tcp报文，会回发 <code class="language-plaintext highlighter-rouge">ack 字段置1</code> 的tcp报文</li>
</ol>

<p>TCP 四次挥手流程：</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.3nro9zah9c.webp" alt="image" style="zoom:50%;" />
</div>

<p>注意：在断开连接时没有客户端与服务端的概念，只有主动方与被动方；</p>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="网络IO" /><summary type="html"><![CDATA[本文创建于 2024-12-12；修改于 2024-12-13；]]></summary></entry><entry><title type="html">Parallels上安装配置Alpine虚拟机进行C/C++开发</title><link href="https://bssn520.github.io/p/20/" rel="alternate" type="text/html" title="Parallels上安装配置Alpine虚拟机进行C/C++开发" /><published>2024-12-10T00:00:00+00:00</published><updated>2024-12-10T00:00:00+00:00</updated><id>https://bssn520.github.io/p/Parallels%E4%B8%8A%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AEalpine%E8%99%9A%E6%8B%9F%E6%9C%BA</id><content type="html" xml:base="https://bssn520.github.io/p/20/"><![CDATA[<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2024-12-10</code>；修改于 <code class="language-plaintext highlighter-rouge">2024-12-10</code>；</p>
</blockquote>

<h3 id="一下载-alpine-官方镜像">一、下载 alpine 官方镜像</h3>

<p>官网：<a href="https://alpinelinux.org/downloads/"> Alpine official download</a></p>

<p>选择 Standard 中 的 aarch64 下载</p>

<h3 id="二安装镜像">二、安装镜像</h3>

<p>参考：<a href="https://www.bilibili.com/video/BV1H3411H7W4">vmware安装alpine linux_哔哩哔哩_bilibili</a></p>

<h3 id="三配置-ssh">三、配置 ssh</h3>

<ol>
  <li>编辑 <code class="language-plaintext highlighter-rouge">vi /etc/ssh/sshd_config</code>，根据需求自行配置端口、登录方式等选项；</li>
</ol>

<p>如果你跟我一样有用 VsCode 连接进行远程开发的需求，记得在配置 <code class="language-plaintext highlighter-rouge">sshd_config</code> 打开以下选项：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">AllowTcpForwarding yes</code></li>
  <li><code class="language-plaintext highlighter-rouge">PermitTunnel yes</code></li>
  <li><code class="language-plaintext highlighter-rouge">GatewayPorts yes</code></li>
</ul>

<ol>
  <li>保存并退出后，重启 ssh 服务：</li>
</ol>

<ul>
  <li><code class="language-plaintext highlighter-rouge">service sshd restart</code></li>
</ul>

<h3 id="四安装常用软件">四、安装常用软件</h3>

<p>安装之前，记得配置一下软件源，根据根据alpine版本和网络情况自行选择源，我这里使用官方源:</p>

<ol>
  <li>查看 alpine 版本：</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[root@alpine-parallels ~]# cat /etc/alpine-release
3.21.0
</code></pre></div></div>

<ol>
  <li>添加软件源：</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># vi /etc/apk/repositories
http://dl-cdn.alpinelinux.org/alpine/v3.21/main
http://dl-cdn.alpinelinux.org/alpine/v3.21/community
</code></pre></div></div>

<p>之后不要忘了执行 <code class="language-plaintext highlighter-rouge">apk update</code> 更新软件源列表。</p>

<ol>
  <li>安装软件以及开发所需工具</li>
</ol>

<p>根据需求安装即可，例如：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># build-base: 提供 GCC、G++ 和其他编译工具; 
# linux-headers: 提供 Linux 内核头文件。
apk add build-base linux-headers

# 一个顺手的编辑器以备不时之需
apk add neovim

# 根据需求添加即可
apk add git wget curl bash
...

</code></pre></div></div>

<h3 id="五设置默认shell">五、设置默认shell</h3>

<ol>
  <li>安装 bash</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">apk add bash</code></p>

<ol>
  <li>在 Alpine 上，默认的 shell 信息存储在 <strong>/etc/passwd</strong> 文件中。你可以手动编辑该文件，改变用户的默认 shell 为 /bin/bash。</li>
</ol>

<p>编辑 /etc/passwd 文件：</p>

<p><code class="language-plaintext highlighter-rouge">vi /etc/passwd</code></p>

<p>找到你的用户名（例如 <strong>root</strong> 或其他用户名），你会看到类似如下的条目：</p>

<p><code class="language-plaintext highlighter-rouge">root:x:0:0:root:/root:/bin/ash</code></p>

<p>更改为：</p>

<p><code class="language-plaintext highlighter-rouge">root:x:0:0:root:/root:/bin/bash</code></p>

<p>保存并退出，现在，当你重新登录时，bash 应该成为默认的 shell。</p>

<ol>
  <li>配置 .bashrc 文件</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">.bashrc</code> 文件通常位于用户的 home 目录下，如果它不存在，可以手动创建或编辑它：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># vi ~/.bashrc

# 设置命令提示符
PS1='[\u@\h \W]\$ '

# 设置别名
alias ll='ls -al'
alias cl='clear'
alias nv='nvim'
alias n='nvim'

# 添加自定义路径
export PATH="$PATH:/usr/local/bin"

</code></pre></div></div>

<h3 id="六杂记以后想起来再加吧">六、杂记，以后想起来再加吧…</h3>

<ol>
  <li>命令方式打开 Parallels 虚拟机，减少不必要的内存占用：</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 启动名为 "alpine" 的虚拟机
prlctl start "alpine"

# 查看虚拟机配置
prlctl list -a -i
</code></pre></div></div>

<ol>
  <li>…</li>
</ol>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="Linux" /><summary type="html"><![CDATA[本文创建于 2024-12-10；修改于 2024-12-10；]]></summary></entry><entry><title type="html">Reactor以及io多路复用</title><link href="https://bssn520.github.io/p/19/" rel="alternate" type="text/html" title="Reactor以及io多路复用" /><published>2024-12-08T00:00:00+00:00</published><updated>2024-12-08T00:00:00+00:00</updated><id>https://bssn520.github.io/p/reactor-epoll</id><content type="html" xml:base="https://bssn520.github.io/p/19/"><![CDATA[<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2024-12-08</code>；修改于 <code class="language-plaintext highlighter-rouge">2024-12-26</code>；</p>
</blockquote>

<h2 id="零基础知识">零、基础知识</h2>

<h3 id="文件描述符">文件描述符</h3>

<p>服务器与客户端的通信流程图如下：</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.8vmzfvuey8.webp" alt="image" style="zoom:50%;" />
</div>

<p>在服务端，文件描述符可分为两类：监听文件描述符，通信文件描述符；</p>

<ul>
  <li>监听文件描述符
    <ul>
      <li>用于监听新的客户端的连接请求；</li>
      <li>例：accept 函数会检测监听文件描述符的读缓冲区，如果有数据，则建立新的连接；否则阻塞。</li>
    </ul>
  </li>
  <li>通信文件描述符
    <ul>
      <li>与已经建立连接的客户端进行通信；</li>
    </ul>
  </li>
</ul>

<p>每个文件描述符内存结构:</p>

<p>每个文件描述符对应两块内存：读缓冲区，写缓冲区</p>

<ul>
  <li>读数据：通过对应的文件描述符，将内存中的数据读出，这块内存称之为读缓冲区；
    <ul>
      <li>例：read 或 recv 函数，检测通信文件描述符的读缓冲区，如果有数据则将其读出；否则阻塞。</li>
    </ul>
  </li>
  <li>写数据：通过对应的文件描述符，将数据写入到某块内存中，这块内存称之为写缓冲区；
    <ul>
      <li>例：write 或 send 函数，检测通信文件描述符的写缓冲区是否有空间供我们写入数据，有空间则写入；否则阻塞。</li>
    </ul>
  </li>
</ul>

<h2 id="一select">一、select</h2>

<blockquote>
  <p>select 函数通过轮询获得每个io的状态，根据状态执行不同的操作；</p>
</blockquote>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">NAME</span>
       <span class="n">select</span><span class="p">,</span> <span class="n">pselect</span><span class="p">,</span> <span class="n">FD_CLR</span><span class="p">,</span> <span class="n">FD_ISSET</span><span class="p">,</span> <span class="n">FD_SET</span><span class="p">,</span> <span class="n">FD_ZERO</span><span class="p">,</span> <span class="n">fd_set</span> <span class="o">-</span> <span class="n">synchronous</span> <span class="n">I</span><span class="o">/</span><span class="n">O</span> <span class="n">multiplexing</span>

<span class="n">SYNOPSIS</span>
       <span class="cp">#include</span> <span class="cpf">&lt;sys/select.h&gt;</span><span class="cp">
</span>
       <span class="kt">int</span> <span class="nf">select</span><span class="p">(</span><span class="kt">int</span> <span class="n">nfds</span><span class="p">,</span> <span class="n">fd_set</span> <span class="o">*</span><span class="n">_Nullable</span> <span class="kr">restrict</span> <span class="n">readfds</span><span class="p">,</span>
                  <span class="n">fd_set</span> <span class="o">*</span><span class="n">_Nullable</span> <span class="kr">restrict</span> <span class="n">writefds</span><span class="p">,</span>
                  <span class="n">fd_set</span> <span class="o">*</span><span class="n">_Nullable</span> <span class="kr">restrict</span> <span class="n">exceptfds</span><span class="p">,</span>
                  <span class="k">struct</span> <span class="n">timeval</span> <span class="o">*</span><span class="n">_Nullable</span> <span class="kr">restrict</span> <span class="n">timeout</span><span class="p">);</span>
</code></pre></div></div>

<ul>
  <li>maxfd， fd即文件描述符，是递增的 int 类型，maxfd 即最大的文件描述符，select 函数内部用来遍历；</li>
  <li>readfds，可读io集合，</li>
  <li>writefds，可写io集合，</li>
  <li>exceptfds，判断某个 <code class="language-plaintext highlighter-rouge">io(即文件描述符)</code> 是否出错；</li>
  <li>timeout，轮询时长间隔，即多久轮训一次；设置为 <code class="language-plaintext highlighter-rouge">NULL</code>时表示一直等待；</li>
</ul>

<h3 id="select-缺点">select 缺点：</h3>

<ul>
  <li>参数较多；</li>
  <li>每次把待检测 <code class="language-plaintext highlighter-rouge">io</code>集合，copy进入内核；</li>
  <li>对 <code class="language-plaintext highlighter-rouge">io</code>的数量是有限制;</li>
</ul>

<h3 id="poll">poll</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;poll.h&gt;</span><span class="cp">
</span><span class="c1">// 每个委托poll检测的fd都对应这样一个结构体</span>
<span class="k">struct</span> <span class="n">pollfd</span> <span class="p">{</span>
    <span class="kt">int</span>   <span class="n">fd</span><span class="p">;</span>         <span class="c1">// 委托内核检测的文件描述符</span>
    <span class="kt">short</span> <span class="n">events</span><span class="p">;</span>     <span class="c1">// 委托内核检测文件描述符的什么事件</span>
    <span class="kt">short</span> <span class="n">revents</span><span class="p">;</span>    <span class="c1">// 文件描述符实际发生的事件 -&gt; 传出</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="n">pollfd</span> <span class="n">fds</span><span class="p">[</span><span class="mi">100</span><span class="p">];</span>
<span class="kt">int</span> <span class="nf">poll</span><span class="p">(</span><span class="k">struct</span> <span class="n">pollfd</span> <span class="o">*</span><span class="n">fds</span><span class="p">,</span> <span class="n">nfds_t</span> <span class="n">nfds</span><span class="p">,</span> <span class="kt">int</span> <span class="n">timeout</span><span class="p">);</span>
</code></pre></div></div>

<p>poll 的底层与 select 一样，只是对参数等做了优化，不再重复记录，看代码即可理解；</p>

<h2 id="二epoll">二、epoll</h2>

<h3 id="成员函数">成员函数：</h3>

<ol>
  <li>epoll_create</li>
  <li>epoll_ctl</li>
  <li>epoll_wait</li>
</ol>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="网络IO" /><summary type="html"><![CDATA[本文创建于 2024-12-08；修改于 2024-12-26；]]></summary></entry><entry><title type="html">CMake备忘</title><link href="https://bssn520.github.io/p/18/" rel="alternate" type="text/html" title="CMake备忘" /><published>2024-12-07T00:00:00+00:00</published><updated>2024-12-07T00:00:00+00:00</updated><id>https://bssn520.github.io/p/CMake%E5%A4%87%E5%BF%98</id><content type="html" xml:base="https://bssn520.github.io/p/18/"><![CDATA[<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2024-12-07</code>；修改于 <code class="language-plaintext highlighter-rouge">2024-12-07</code>；</p>
</blockquote>

<h2 id="一cmake概述">一、CMake概述</h2>

<p>CMake是一个跨平合的安装（编译）工具，可以用简单的语句采描述所有平合的安装（编译过程）。他能够输出各种各样的makefile或者project文件，能测试编译器所支持的C++特性，类似UNIX下的automake。只是CMake 的组态档取名为CMakeLists.txt。Cmake 并不直接建构出最终的软件，而是产生标准的建构档（如Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces），然后再依一般的建构方式使用。</p>

<h2 id="二cmake使用">二、CMake使用</h2>

<h3 id="1-注释">1. 注释</h3>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 你好呀，我是单行注释，使用 # 即可</span>

<span class="cm">#[[
你好呀，我是多行注释，注意我的格式哦～
]]</span>

</code></pre></div></div>

<h3 id="2-如何在-cmakeliststxt-文件中添加的三个命令">2. 如何在 CMakeLists.txt 文件中添加的三个命令</h3>

<p><code class="language-plaintext highlighter-rouge">cmake_minimum_required</code> 指定使用的cmake工具的最低版本；</p>

<p><code class="language-plaintext highlighter-rouge">project</code> 定义工程名，也可以指定工程的版本、描述、web网页地址、支持的语言；</p>

<p><code class="language-plaintext highlighter-rouge">add_executable</code> 定义工程会生成一个可执行程序；</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cmake_minimum_required</span><span class="p">(</span>VERSION 3.15<span class="p">)</span>
<span class="nb">project</span><span class="p">(</span>test<span class="p">)</span>
<span class="nf">add_excutable</span><span class="p">(</span>app a.cpp b.cpp c.cpp<span class="p">)</span>
</code></pre></div></div>

<h4 id="21-定义变量">2.1 定义变量</h4>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 格式 set(variable value1;value2;value3;...)</span>

<span class="c1"># 源文件之间可以使用空格进行分割</span>
<span class="nb">set</span><span class="p">(</span>SRC_LIST a.cpp b.cpp c.app<span class="p">)</span>

<span class="c1"># 源文件之间也可以使用分号进行分割</span>
<span class="nb">set</span><span class="p">(</span>SRC_LIST a.cpp;b.cpp;c.cpp<span class="p">)</span>

<span class="c1"># 此时的 add_excutable 命令如下</span>
<span class="nf">add_excutable</span><span class="p">(</span>app <span class="si">${</span><span class="nv">SRC_LIST</span><span class="si">}</span><span class="p">)</span>
</code></pre></div></div>

<h4 id="22-指定编译时所使用的c标准">2.2 指定编译时所使用的C++标准</h4>

<ul>
  <li>在 CMakeLists.txt 中指定</li>
</ul>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 使用 C++11 标准进行编译</span>
<span class="nb">set</span><span class="p">(</span>CMAKE_CXX_STANDARD 11<span class="p">)</span>

<span class="c1"># 使用 C++14 标准进行编译</span>
<span class="nb">set</span><span class="p">(</span>CMAKE_CXX_STANDARD 14<span class="p">)</span>

...
</code></pre></div></div>

<ul>
  <li>执行 cmake 命令时指出</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 增加 -std=c++11</span>
cmake CMakeLists.txt <span class="nt">-DCMAKE_CXX_STANDARD</span><span class="o">=</span>11

...
</code></pre></div></div>

<h4 id="23-指定输出路径">2.3 指定输出路径</h4>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 定义变量用于存储项目绝对路径</span>
<span class="nb">set</span><span class="p">(</span>HOME /home/usernmae/project_test<span class="p">)</span>
<span class="c1"># 将拼接后的路径传给 EXECUTABLE_OUTPUT_PATH 宏，如果目录不存在会自动创建</span>
<span class="nb">set</span><span class="p">(</span>EXECUTABLE_OUTPUT_PATH <span class="si">${</span><span class="nv">HOME</span><span class="si">}</span>/bin<span class="p">)</span>
</code></pre></div></div>

<h3 id="3-搜索文件">3. 搜索文件</h3>

<p><code class="language-plaintext highlighter-rouge">PROJECT_SOURCE_DIR</code>宏对应的值就是在执行cmake命令时，后面紧跟的目录，一般是工程的根目录。</p>

<p><code class="language-plaintext highlighter-rouge">CMAKE_CURRENT_SOURCE_DIR</code> 宏表示当前访问的 CMakeLists.txt 文件所在的路径；</p>

<h4 id="31-aux_source_directory-命令">3.1 aux_source_directory 命令</h4>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># PROJECT_SOURCE_DIR 宏表示搜索路径是 cmake 后面所写的 CMakeLists.txt 的路径，并将所搜结果赋值给 SRC 变量</span>
<span class="nb">aux_source_directory</span><span class="p">(</span><span class="si">${</span><span class="nv">PROJECT_SOURCE_DIR</span><span class="si">}</span> SRC<span class="p">)</span>
</code></pre></div></div>

<h4 id="32-file-命令">3.2 file 命令</h4>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">file</span><span class="p">(</span>GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型<span class="p">)</span>
</code></pre></div></div>

<ul>
  <li>
    <p>GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表，并将其存储到变量中。</p>
  </li>
  <li>
    <p>GLOB_RECURSE：递归搜索指定目录，将搜索到的满足条件的文件名生成一个列表，并将其存储到变量中。</p>
  </li>
</ul>

<h4 id="33-包含头文件">3.3 包含头文件</h4>

<p>在CMake中设置要包含的目录也很简单，通过 <code class="language-plaintext highlighter-rouge">include_directories</code>命令即可：</p>

<p><code class="language-plaintext highlighter-rouge">include_directories(headfile_path)</code></p>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="makefile" /><summary type="html"><![CDATA[本文创建于 2024-12-07；修改于 2024-12-07；]]></summary></entry><entry><title type="html">C++11新特性-多线程</title><link href="https://bssn520.github.io/p/17/" rel="alternate" type="text/html" title="C++11新特性-多线程" /><published>2024-12-04T00:00:00+00:00</published><updated>2024-12-04T00:00:00+00:00</updated><id>https://bssn520.github.io/p/C++11%E6%96%B0%E7%89%B9%E6%80%A7-%E5%A4%9A%E7%BA%BF%E7%A8%8B</id><content type="html" xml:base="https://bssn520.github.io/p/17/"><![CDATA[<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2024-12-04</code>；修改于 <code class="language-plaintext highlighter-rouge">2024-12-04</code>；</p>
</blockquote>

<h3 id="11-线程-thread">1.1 线程 thread</h3>

<p>C++11 中提供的线程类叫做 std：：thread，基于这个类创建一个新的线程非常的简单，只需要提供线程函效或者函效对象即可，并且可以同时指定线雇函数的參数。我们首先来了解一下这个类提供的一些常用 API ：</p>

<h3 id="12-互斥量">1.2 互斥量</h3>

<p>C++11提供如下4种语义的互斥量（mutex）：</p>

<ul>
  <li><strong>std::mutex，独占的互斥量，不能递归使用</strong></li>
  <li>std::time_mutex，带超时的独占互斥量，不能递归使用</li>
  <li>std::recursive_mutex，递归互斥量，不带超时功能</li>
  <li>std::recursive_timed_mutex，带超时的递归互斥量</li>
</ul>

<h4 id="lock_guard-和-unique_lock">lock_guard 和 unique_lock</h4>

<p><strong>lock_guard</strong></p>

<ol>
  <li>std:lock_guard 在构造函数中进行加锁，析构函数中进行解锁。</li>
</ol>

<p><strong>unique_lock</strong></p>

<ol>
  <li>.unique_ lock 是通用互斥包装體，允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件交量一同使用。</li>
  <li>unique_ lockttlock_guard使用更加灵活，功能更加强大。</li>
  <li>使用unique_lock需要付出更多的时间、性能成本。</li>
</ol>

<p><strong>使用场景：需要结合notify+wait的场景使用unique_lock；如果只是单纯的互斥使用lock guard</strong></p>

<h3 id="13-条件变量">1.3 条件变量</h3>

<h4 id="条件变量的成员函数">条件变量的成员函数</h4>

<h5 id="1-wait">1. wait</h5>

<p>包含两种重载，第一种只包含unique_lock对象，另外一个Predicate 对象（等待条件），这里必须使用unique_lock，因为wait函数的工作原理</p>

<ul>
  <li>当前线程调用wait() 后将被阻塞并且函数会解锁互斥量、直到另外某个线程调用notify_one或者notify_all唤醒当前线程；一旦当前线程获得通知（notify），<strong>wait() 函数也是自动调用lock()，同理不能使用lock guard对象</strong>；</li>
  <li><strong>如果wait没有第二个参数</strong>，第一次调用默认条件不成立，直接解锁互斥量并阻塞到本行，直到某一个线程调用notify_one或notify_all为止，被唤醒后，wait重新尝试获取互斥量，如果得不到，线程会卡在这里，直到获取到互斥量，然后无条件地继续进行后面的操作。</li>
  <li><strong>如果wait包含第二个参数</strong>，如果第二个参数不满足，那么wait将解锁互斥量并堵塞到本行，直到某一个线程调用notify_one或notify_all为止，被唤醒后，wait重新尝试获取互斥量，如果得不到，线程会卡在这里，直到获取到互斥量，然后继续判断第二个参数，如果表达式为false，wait对互斥量解锁，然后休眠，如果为true，则进行后面的操作。</li>
</ul>

<h5 id="2-wait_for">2. wait_for</h5>

<p>和wait不同的是，wait_for可以执行一个时间段，在线程收到唤醒通知或者时间超时之前，该线程都会处于阻塞状态，如果收到唤醒通知或者时间超时，wait_for返回，剩下操作和wait类似。</p>

<h5 id="3-wait_until">3. wait_until</h5>

<p>函数原型：</p>

<pre><code class="language-C++">void notify_one() noexcept;
</code></pre>

<p>作用：</p>

<p>与wait_for类似，只是wait_unti可以指定一个时间点，在当前线程收到通知或者指定的时间点超时之前，该线程
都会处于阻塞状态。如果超时或者收到唤醒通知，wait_until返回，剩下操作和wait类似</p>

<h5 id="4-notify_one">4. notify_one</h5>

<p>函数原型：</p>

<pre><code class="language-C++">void notify_one() noexcept;
</code></pre>

<p>作用：</p>

<p>解锁正在等待当前条件的线程中的一个，如果没有线程在等待，则函数不执行任何操作，如果正在等待的线程多余一个，则唤醒的线程是不确定的</p>

<h5 id="5-notify_all">5. notify_all</h5>

<p>函数原型：</p>

<pre><code class="language-C++">void notify_all() noexcept;
</code></pre>

<p>作用：</p>

<p>解锁正在等待当前条件的所有线程，如果没有正在等待的线程，则函数不执行任何操作。</p>

<h3 id="14-原子变量">1.4 原子变量</h3>

<blockquote>
  <p>原子变量的作用大白话解释就是，把CPU对某一个数据的<strong>读(CPU从内存中读取数据)、修改(CPU对数据进行修改)、写(写回到内存)</strong>三步操作定义为一个整体操作，这个操作不可再被分割，必须一口气执行完读、修改、写三步操作。</p>
</blockquote>

<h4 id="atomic类">atomic类</h4>

<p>对原子变量对象的操作分为写、修改、读三种：</p>

<p>写：</p>

<p>store</p>

<p>exchange</p>

<p>赋值运算符 =</p>

<p>修改：</p>

<p>fetch_add</p>

<p>fetch_sub</p>

<p>fetch_and</p>

<p>fetch_or</p>

<p>fetch_xor</p>

<p>读：</p>

<p>load</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;atomic&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;string&gt;</span><span class="cp">
</span>
<span class="n">class</span> <span class="n">Base</span>
<span class="p">{</span>
<span class="nl">public:</span>
    <span class="n">Base</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="o">&amp;</span><span class="n">name</span><span class="p">,</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">age</span><span class="p">)</span> <span class="o">:</span>
        <span class="n">m_Name</span><span class="p">(</span><span class="n">name</span><span class="p">),</span> <span class="n">m_Age</span><span class="p">(</span><span class="n">age</span><span class="p">)</span> <span class="p">{}</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">m_Name</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">m_Age</span><span class="p">;</span>
<span class="p">};</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// 声明 atomic 对象</span>
    <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">&gt;</span> <span class="n">a</span> <span class="o">=</span> <span class="sc">'a'</span><span class="p">;</span> <span class="c1">// 模版方法声明 atomic 对象</span>
    <span class="n">std</span><span class="o">::</span><span class="n">atomic_int</span> <span class="n">b</span><span class="p">;</span> <span class="c1">// 特化类型方法声明 atomic 对象，但是没有初始化</span>
    <span class="n">std</span><span class="o">::</span><span class="n">atomic_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">b</span><span class="p">,</span> <span class="mi">9</span><span class="p">);</span> <span class="c1">// 使用 init 函数初始化 atomic 对象</span>

    <span class="c1">// 对 atomic 对象进行修改</span>
    <span class="n">a</span><span class="p">.</span><span class="n">store</span><span class="p">(</span><span class="sc">'A'</span><span class="p">);</span>
    <span class="k">auto</span> <span class="n">bb</span> <span class="o">=</span> <span class="n">b</span><span class="p">.</span><span class="n">exchange</span><span class="p">(</span><span class="mi">99</span><span class="p">);</span>
    <span class="n">b</span><span class="p">.</span><span class="n">fetch_add</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>

    <span class="c1">// 指针类型</span>
    <span class="n">Base</span> <span class="n">base</span><span class="p">(</span><span class="s">"luffy"</span><span class="p">,</span> <span class="mi">18</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="n">Base</span> <span class="o">*&gt;</span> <span class="n">atc_base</span><span class="p">(</span><span class="o">&amp;</span><span class="n">base</span><span class="p">);</span>
    <span class="k">auto</span> <span class="n">tmp</span> <span class="o">=</span> <span class="n">atc_base</span><span class="p">.</span><span class="n">load</span><span class="p">();</span>

    <span class="c1">// 打印结果</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"a value: "</span> <span class="o">&lt;&lt;</span> <span class="n">a</span><span class="p">.</span><span class="n">load</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"b value: "</span> <span class="o">&lt;&lt;</span> <span class="n">b</span><span class="p">.</span><span class="n">load</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"bb value: "</span> <span class="o">&lt;&lt;</span> <span class="n">bb</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"base value: "</span> <span class="o">&lt;&lt;</span> <span class="n">tmp</span><span class="o">-&gt;</span><span class="n">m_Name</span> <span class="o">&lt;&lt;</span> <span class="s">" "</span> <span class="o">&lt;&lt;</span> <span class="n">tmp</span><span class="o">-&gt;</span><span class="n">m_Age</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

</code></pre></div></div>

<h4 id="使用原子变量进行线程同步">使用原子变量进行线程同步</h4>

<p>对需要保护的数据使用原子变量类型即可，注意不能是复合类型或浮点数类型。</p>

<h4 id="内存顺序约束">内存顺序约束</h4>

<h3 id="15-异步操作">1.5 异步操作</h3>

<ul>
  <li>std:future类：异步指向某个任务，然后通过future特性去获取任务函数的返回结果。
    <ul>
      <li>get()</li>
      <li>wait()</li>
      <li>wait_for()</li>
      <li>wait_until()</li>
    </ul>
  </li>
  <li>std:promise：协助线程赋值的类，能够将数据和future对象绑定，为获取线程函数中的某个值提供便利。
    <ul>
      <li>set_value</li>
      <li>set_value_at_thread_exit</li>
    </ul>
  </li>
  <li>std:aysnc：异步运行某个任务函数</li>
  <li>std:packaged_task：将任务和feature绑定在一起的模板，<strong>是一种封装对任务的封装。</strong></li>
</ul>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="C++11" /><summary type="html"><![CDATA[本文创建于 2024-12-04；修改于 2024-12-04；]]></summary></entry><entry><title type="html">设计模式-P2</title><link href="https://bssn520.github.io/p/16/" rel="alternate" type="text/html" title="设计模式-P2" /><published>2024-12-01T00:00:00+00:00</published><updated>2024-12-01T00:00:00+00:00</updated><id>https://bssn520.github.io/p/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-P2</id><content type="html" xml:base="https://bssn520.github.io/p/16/"><![CDATA[<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2024-11-30</code>；修改于 <code class="language-plaintext highlighter-rouge">2024-12-1</code>；</p>
</blockquote>

<h3 id="单例模式">单例模式</h3>

<h4 id="1-定义">1. 定义</h4>

<p>保证一个类仅有一个实例，并提供一个该实例的全局访问点。</p>

<h4 id="2-解决的问题">2. 解决的问题</h4>

<ul>
  <li>稳定点：类只有一个实例，并提供全局访问点</li>
  <li>变化点：有多个类都是单例，能不能复用代码</li>
</ul>

<h4 id="3-代码结构">3. 代码结构</h4>

<p>私有的构造和析构函数</p>

<p>禁止拷贝构造、拷贝赋值、移动构造、移动赋值</p>

<p>静态类成员函数</p>

<p>静态私有成员变量</p>

<h4 id="4-符合哪些设计原则">4. 符合哪些设计原则</h4>

<p>单一职责原则：类只有一个职责，即管理自己的唯一实例</p>

<p>开放封闭原则：通过静态成员函数获取实例，不修改类的外部接口</p>

<p>依赖倒置原则：依赖于抽象而不是具体实现</p>

<h4 id="5-如何拓展">5. 如何拓展</h4>

<ul>
  <li>多线程环境下的线程安全实现</li>
  <li>通过配置文件或参数动态决定单例类的具体实现</li>
</ul>

<h3 id="工厂模式">工厂模式</h3>

<h4 id="1-定义-1">1. 定义</h4>

<p>定义一个用于创建对象的接口，让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。</p>

<h4 id="2-解决的问题-1">2. 解决的问题</h4>

<ul>
  <li>稳定点：创建同类对象的接口；同类对象有一个相同的职责</li>
  <li>变化点：创建对象的拓展</li>
</ul>

<h4 id="3-代码结构-1">3. 代码结构</h4>

<p>对象创建接口</p>

<ul>
  <li>根据功能创建具体对象</li>
  <li>调用功能结构</li>
</ul>

<p>单个功能接口</p>

<h4 id="4-符合哪些设计原则-1">4. 符合哪些设计原则</h4>

<p>最小知道原则：工厂类只负责创建对象，不关心对象的具体实现</p>

<p>面向接口编程：通过接口定义对象的创建和使用</p>

<p>依赖倒置原则：依赖于抽象而不是具体实现</p>

<h4 id="5-如何拓展-1">5. 如何拓展</h4>

<ul>
  <li>实现对象创建接口</li>
  <li>实现功能接口</li>
  <li>多态调用</li>
</ul>

<h3 id="抽象工厂">抽象工厂</h3>

<h4 id="1-定义-2">1. 定义</h4>

<p>提供一个接口，让该接口负责创建一系列“相关或者相互依赖的对象”，无需指定它们具体的类。</p>

<h4 id="2-解决的问题-2">2. 解决的问题</h4>

<ul>
  <li>稳定点：创建同类对象的接口；同类对象有多个相同的职责；</li>
  <li>变化点：创建对象的拓展</li>
</ul>

<h4 id="3-代码结构-2">3. 代码结构</h4>

<p>对象创建接口</p>

<ul>
  <li>根据功能创建具体对象</li>
  <li>提供多个功能接口</li>
</ul>

<p>多个功能接口</p>

<h4 id="4-符合哪些设计原则-2">4. 符合哪些设计原则</h4>

<p>单一职责原则：每个工厂只负责创建一组相关的对象</p>

<p>开放封闭原则：通过扩展工厂类来支持新的对象类型</p>

<p>依赖倒置原则：依赖于抽象而不是具体实现</p>

<h4 id="5-如何拓展-2">5. 如何拓展</h4>

<ul>
  <li>实现对象创建接口</li>
  <li>实现功能接口</li>
  <li>多态调用</li>
</ul>

<h3 id="责任链">责任链</h3>

<h4 id="1-定义-3">1. 定义</h4>

<p>使多个对象都有机会处理请求，从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链，并沿着这条链传递请求，直到有一个对象处理它为止。</p>

<h4 id="2-解决的问题-3">2. 解决的问题</h4>

<ul>
  <li>稳定点：处理流程，请求按照链条传递；处理流程可以被打断；</li>
  <li>变化点：处理节点的个数、处理顺序以及处理条件；</li>
</ul>

<h4 id="3-代码结构-3">3. 代码结构</h4>

<p>从单个节点出发，实现处理接口，实现一个构建链表关系的静态接口；</p>

<h4 id="4-符合哪些设计原则-3">4. 符合哪些设计原则</h4>

<p>组合优于继承</p>

<p>面向接口编程</p>

<p>接口依赖</p>

<h4 id="5-如何拓展-3">5. 如何拓展</h4>

<ul>
  <li>实现处理接口；</li>
  <li>修改静态接口；</li>
</ul>

<h3 id="装饰器">装饰器</h3>

<h4 id="1-定义-4">1. 定义</h4>

<p>动态地给一个对象增加一些额外的职责，就增加功能而言，装饰器模式比生产子类更为灵活。</p>

<h4 id="2-解决的问题-4">2. 解决的问题</h4>

<ul>
  <li>稳定点：顺序无关的增加职责</li>
  <li>变化点：职责拓展</li>
</ul>

<h4 id="3-代码结构-4">3. 代码结构</h4>

<p>定义一个抽象组件接口</p>

<p>定义一个具体组件实现抽象组件接口</p>

<p>定义一个装饰器类，继承抽象组件接口，并包含一个抽象组件的引用</p>

<p>定义具体装饰器类，继承装饰器类，并在其中添加额外的职责</p>

<h4 id="4-符合哪些设计原则-4">4. 符合哪些设计原则</h4>

<p>组合优于继承</p>

<p>面向接口编程</p>

<p>接口依赖</p>

<h4 id="5-如何拓展-4">5. 如何拓展</h4>

<ul>
  <li>
    <p>实现新的装饰器类，增加新的职责</p>
  </li>
  <li>
    <p>修改现有装饰器类，调整职责的顺序或逻辑</p>
  </li>
</ul>

<h3 id="组合模式">组合模式</h3>

<h4 id="1-定义-5">1. 定义</h4>

<p>将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。</p>

<h4 id="2-解决的问题-5">2. 解决的问题</h4>

<ul>
  <li>稳定点：层次关系稳定；对象和组合对象；</li>
  <li>变化点：对象的职责变更；组合对象里对象数量的变更；</li>
</ul>

<h4 id="3-代码结构-5">3. 代码结构</h4>

<p>定义一个抽象组件接口</p>

<p>定义一个叶子节点类，实现抽象组件接口</p>

<p>定义一个组合节点类，实现抽象组件接口，并包含一个子组件的集合</p>

<h4 id="4-符合哪些设计原则-5">4. 符合哪些设计原则</h4>

<p>单一职责原则：每个类只负责自己的职责</p>

<p>开放封闭原则：通过扩展组合节点来支持新的子组件</p>

<p>依赖倒置原则：依赖于抽象的组件接口而不是具体实现</p>

<h4 id="5-如何拓展-5">5. 如何拓展</h4>

<ul>
  <li>实现新的叶子节点类，增加新的职责</li>
  <li>修改组合节点类，调整子组件的逻辑或顺序</li>
</ul>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="设计模式" /><summary type="html"><![CDATA[本文创建于 2024-11-30；修改于 2024-12-1；]]></summary></entry><entry><title type="html">设计模式-P1</title><link href="https://bssn520.github.io/p/15/" rel="alternate" type="text/html" title="设计模式-P1" /><published>2024-11-29T00:00:00+00:00</published><updated>2024-11-29T00:00:00+00:00</updated><id>https://bssn520.github.io/p/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-P1</id><content type="html" xml:base="https://bssn520.github.io/p/15/"><![CDATA[<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2024-11-29</code>；修改于 <code class="language-plaintext highlighter-rouge">2024-11-29</code>；</p>
</blockquote>

<h2 id="一设计原则">一、设计原则</h2>

<h3 id="1-依赖倒置">1. 依赖倒置</h3>

<blockquote>
  <p>实现依赖抽象(接口)，用户也依赖抽象。</p>
</blockquote>

<p>高层模块不应该依赖低层模块，两者都应该依赖抽象；</p>

<p>抽象不应该依赖具体实现，具体实现应该依赖于抽象；</p>

<h3 id="2-开闭原则">2. 开闭原则</h3>

<blockquote>
  <p>对扩展开放，对修改关闭。</p>
</blockquote>

<p>解释：软件实体（类、模块、函数等）应该允许扩展其行为，而不需要修改其源代码。通过继承、接口等方式实现扩展。</p>

<h3 id="3面向接口编程">3.面向接口编程</h3>

<blockquote>
  <p>使用接口而不是具体类来定义对象的行为。</p>
</blockquote>

<p>解释：通过接口定义对象的行为，而不是依赖于具体实现，这样可以更容易地替换实现，提高代码的灵活性。</p>

<h3 id="4-封装变化点">4. 封装变化点</h3>

<blockquote>
  <p>将变化的部分封装起来，使其不影响其他部分。</p>
</blockquote>

<p>解释：判断需求中的变化点，并将其封装在独立的模块中，这样可以减少变化对系统其他部分的影响。</p>

<h3 id="5-单一职责原则">5. 单一职责原则</h3>

<blockquote>
  <p>职责，即变化；一个类或模块应该只有一个引起它变化的原因。</p>
</blockquote>

<p>解释：每个类或模块应该只负责一个职责，这样可以提高代码的可维护性和可读性。</p>

<h3 id="6-里氏变换">6. 里氏变换</h3>

<blockquote>
  <p>子类可以替换父类，而不影响程序的正确性。</p>
</blockquote>

<p>解释：子类应该能够替换父类，而不破坏程序的正确性。</p>

<h3 id="7-接口隔离">7. 接口隔离</h3>

<blockquote>
  <p>不应该强迫客户端依赖于它们不使用的接口。</p>
</blockquote>

<p>解释：将大接口拆分为多个小接口，客户端只需要依赖于它们需要的接口，这样可以减少不必要的依赖。</p>

<h3 id="8-组合优于继承">8. 组合优于继承</h3>

<blockquote>
  <p>优先使用组合而不是继承来实现代码复用。</p>
</blockquote>

<p>解释：通过组合对象来实现代码复用，而不是通过继承，这样可以减少类之间的耦合，提高代码的灵活性。</p>

<h3 id="9-最小知道原则">9. 最小知道原则</h3>

<blockquote>
  <p>一个对象应该对其他对象有尽可能少的了解。</p>
</blockquote>

<p>解释：减少对象之间的依赖，降低耦合度，提高代码的可维护性和可扩展性。</p>

<h2 id="二设计模式">二、设计模式</h2>

<h3 id="模版方法">模版方法</h3>

<h4 id="1-定义">1. 定义</h4>

<p>定义一个操作中的算法的骨架，而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。</p>

<h4 id="2-解决的问题">2. 解决的问题</h4>

<ul>
  <li>稳定点：算法骨架</li>
  <li>变化点：子流程需要变换</li>
</ul>

<h4 id="3-代码结构">3. 代码结构</h4>

<ul>
  <li>基类中有骨架流程接口</li>
  <li>所有子流程对子类开放并且是虚函数</li>
  <li>使用多态的方式</li>
</ul>

<h4 id="4-符合哪些设计原则">4. 符合哪些设计原则</h4>

<p>单一职责</p>

<p>开闭</p>

<p>依赖倒置</p>

<ul>
  <li>子类拓展时，需要依赖基类的虚函数实现</li>
  <li>使用者只依赖接口</li>
</ul>

<p>封装变化点</p>

<ul>
  <li>protected 关键字</li>
</ul>

<p>接口隔离</p>

<p>最小知道原则</p>

<h4 id="5-如何拓展">5. 如何拓展</h4>

<ul>
  <li>实现子类继承基类，复写子流程</li>
  <li>通过多态方式进行调用</li>
</ul>

<h3 id="观察者模式">观察者模式</h3>

<h4 id="1-定义-1">1. 定义</h4>

<p>定义对象间的一种一对多（变化）的依赖关系，以便当一个对象（Subject）的状态发生改变时，所有依赖于它的对象都得到通知并自动更新。</p>

<h4 id="2-解决的问题-1">2. 解决的问题</h4>

<ul>
  <li>稳定点：“一”对”多”的依赖关系；”一”变化，“多”也跟着变化；</li>
  <li>变化点：”多”增加；“多”减少</li>
</ul>

<h4 id="3-代码结构-1">3. 代码结构</h4>

<ul>
  <li>定义一个 <code class="language-plaintext highlighter-rouge">Subject</code> 接口，包含 <code class="language-plaintext highlighter-rouge">attach</code>、<code class="language-plaintext highlighter-rouge">detach</code> 和 <code class="language-plaintext highlighter-rouge">notify</code> 方法。</li>
  <li>定义一个 <code class="language-plaintext highlighter-rouge">Observer</code> 接口，包含 <code class="language-plaintext highlighter-rouge">update</code> 方法。</li>
  <li><code class="language-plaintext highlighter-rouge">Subject</code> 类维护一个 <code class="language-plaintext highlighter-rouge">Observer</code> 列表，并在状态变化时通知所有观察者。</li>
</ul>

<h4 id="4-符合哪些设计原则-1">4. 符合哪些设计原则</h4>

<p>面向接口编程：使用 <code class="language-plaintext highlighter-rouge">Subject</code> 和 <code class="language-plaintext highlighter-rouge">Observer</code> 接口定义行为。</p>

<p>接口隔离</p>

<ul>
  <li>类与类依赖一个接口</li>
</ul>

<p>封装变化点</p>

<h4 id="5-如何拓展-1">5. 如何拓展</h4>

<ol>
  <li>继承实现 <code class="language-plaintext highlighter-rouge">Observer</code> 接口</li>
  <li>调用变化点封装函数</li>
</ol>

<h3 id="策略模式">策略模式</h3>

<h4 id="1-定义-2">1. 定义</h4>

<p>定义一系列算法，把它们一个个封装起来，并且使它们可互相替换。该模式使得算法可独立于使用它的客户程序而变化。</p>

<h4 id="2-解决的问题-2">2. 解决的问题</h4>

<ul>
  <li>稳定点：客户程序与算法的调用关系</li>
  <li>变化点：新加算法；算法内容变动</li>
</ul>

<h4 id="3-代码结构-2">3. 代码结构</h4>

<ul>
  <li>定义一个策略接口，包含算法的抽象方法。</li>
  <li>实现不同的策略类，每个类实现策略接口。</li>
  <li>客户程序通过依赖注入（传参）的方式使用策略。</li>
</ul>

<h4 id="4-符合哪些设计原则-2">4. 符合哪些设计原则</h4>

<p>接口隔离</p>

<ul>
  <li>依赖注入（传参）</li>
  <li>解决通过一个接口解决两个类的依赖</li>
</ul>

<p>面向接口编程</p>

<p>开闭原则</p>

<h4 id="5-如何拓展-2">5. 如何拓展</h4>

<ul>
  <li>实现新的策略类，继承策略接口。</li>
  <li>通过依赖注入的方式使用新的策略。</li>
</ul>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="设计模式" /><summary type="html"><![CDATA[本文创建于 2024-11-29；修改于 2024-11-29；]]></summary></entry><entry><title type="html">散列表与布隆过滤器</title><link href="https://bssn520.github.io/p/14/" rel="alternate" type="text/html" title="散列表与布隆过滤器" /><published>2024-11-28T00:00:00+00:00</published><updated>2024-11-28T00:00:00+00:00</updated><id>https://bssn520.github.io/p/%E6%95%A3%E5%88%97%E8%A1%A8%E4%B8%8E%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8</id><content type="html" xml:base="https://bssn520.github.io/p/14/"><![CDATA[<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2024-11-28</code>；修改于 <code class="language-plaintext highlighter-rouge">2024-11-28</code>；</p>
</blockquote>

<p>主要内容：</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.7sn90jy1ld.webp" alt="image" style="zoom:50%;" />
</div>

<h2 id="一散列表">一、散列表</h2>

<blockquote>
  <p>根据 key 计算 key 在表中的位置的数据结构；是key 和其所在存储地址的映射关系；</p>
</blockquote>

<h3 id="1-构成">1. 构成</h3>

<ul>
  <li>Hash 函数</li>
  <li>数组</li>
</ul>

<h3 id="2-哈希函数">2. 哈希函数</h3>

<blockquote>
  <p>映射函数 Hash(Key) = addr；</p>
</blockquote>

<h4 id="选择哈希函数的原则">选择哈希函数的原则</h4>

<ul>
  <li>计算速度快，性能高</li>
  <li>强随机分布（体现在数据被均匀、等概率的分布在整个地址空间）</li>
  <li>常用 hash函数：murmurhash1(速度快，质量差)、<code class="language-plaintext highlighter-rouge">murmurhash2</code>(速度快，质量也还行)、murmurhash3(速度慢，质量最好)、<code class="language-plaintext highlighter-rouge">siphash</code>（redis 6.0 中使用，rust 等大多数语言选择该hash算法来实现 <code class="language-plaintext highlighter-rouge">hashmap</code>），<code class="language-plaintext highlighter-rouge">cityhash</code>等都具备强随机分布性</li>
  <li>siphash 主要解决字符串接近的强随机分布性</li>
</ul>

<h3 id="3-哈希冲突">3. 哈希冲突</h3>

<h4 id="冲突的原因">冲突的原因</h4>

<p>哈希函数可能会把两个或两个以上的不同的 key 映射到同一个地址，这种情况称之为 hash冲突(或 hash碰撞)。</p>

<h4 id="负载因子">负载因子</h4>

<ul>
  <li>负载因子：数组中元素的个数/数组的容量；用来形容散列表的存储密度；负载因子越小，冲突越小，负载因子越大，冲突越大；</li>
</ul>

<h4 id="冲突处理">冲突处理</h4>

<h5 id="负载因子在合理范围内的处理方法">负载因子在合理范围内的处理方法：</h5>

<h6 id="链表法">链表法</h6>

<p>使用链表将冲突元链接起来，但可能出现冲突元素过多，链表性能低的情况，此时，可以将链表转换为 红黑树，最小堆；时间复杂度由链表O(n) 转化为O(log2n)；通常在链表长度超过 256 个时将链表结构转换为红黑树或堆结构；</p>

<h6 id="开放寻址法">开放寻址法</h6>

<p>将所有的元素都放在数组中，不使用额外的数据结构辅助；一般使用线性探测的思路解决：</p>

<ol>
  <li>当插入新元素时，使用哈希函数在哈希表中定位元素位置；</li>
  <li>检查数组中该索引是否已经存在元素。如果该位置不存在元素，则插入；否则进行下一步：</li>
  <li>对第2步得到的数组索引加上一定步长接着检查第2步：
    <ul>
      <li>i+1, i+2, i+3, i+4…</li>
      <li>i-1^2, i+2^2, i-3^2, i+4^2…</li>
      <li>但是上面两种方法均会导致一个问题：同类hash会聚集，也就是近似key的hash值也接近，在数组中的存储位置也接近；可以使用 <code class="language-plaintext highlighter-rouge">双重哈希</code>来解决。</li>
    </ul>
  </li>
</ol>

<h5 id="负载因子不在合理范围内的处理方法">负载因子不在合理范围内的处理方法：</h5>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">size</code> 表示数组中有效元素的长度；<code class="language-plaintext highlighter-rouge">capacity</code> 表示数组的容量(最大能存储的元素个数)</p>
</blockquote>

<h6 id="扩容">扩容</h6>

<p>当 <code class="language-plaintext highlighter-rouge">size &gt; capacity</code> 时，我们应该对数组进行扩容操作；</p>

<h6 id="缩容">缩容</h6>

<p>当 <code class="language-plaintext highlighter-rouge">size &lt; 0.1 * capacity</code> 时，我们应该对数组进行扩容操作；</p>

<h6 id="rehash">rehash</h6>

<p>哈希函数的形式：index = hash(key) % capacity；当对数组进行扩容或缩容操作后，数组的 capacity 发生改变，自然的，每个数组元素的index也应当重新计算。</p>

<h3 id="4-stl-中-unordered_-散列表实现">4. STL 中 unordered_* 散列表实现</h3>

<blockquote>
  <p>STL 中，unordered_map, unordered_set, unordered_multimap, unordered_multiset底层实现都是散列表；</p>
</blockquote>

<p>STL 为了实现迭代器的操作，对发生哈希冲突时使用的链表做了优化，把各个 bucket 所使用的链表链接在了一起，形成一个全局链表(单链表)：</p>

<p>插入节点：</p>

<p>使用头插法。</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/Arc-2024-11-28-16.21.36.2rv694oejp.webp" alt="image" style="zoom:40%;" />
</div>

<h2 id="二布隆过滤器-bloomfilter">二、布隆过滤器 BloomFilter</h2>

<blockquote>
  <p>当我们只想判断某个键(key)在数据集中是否存在，而不需要拿到具体的值(value)，这时我们就可以使用 布隆过滤器 来实现；</p>

  <p>能确定一个 key 一定不存在，可控假阳率的确定 key 存在；</p>
</blockquote>

<h3 id="1-构成-1">1. 构成</h3>

<ul>
  <li>位图 BitMap</li>
  <li>多个 Hash函数</li>
</ul>

<h3 id="2-原理">2. 原理</h3>

<p>buf[8] 构成了一个 8x8 的位图，根据 hash(key) 得到 index 的值，之后再对index进行计算，得到 key 在位图中的坐标。</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.13ltbz4e11.webp" alt="image" style="zoom:50%;" />
</div>

<p>插入和查询流程：</p>

<p>插入：先使用n个hash函数对 key 进行计算，拿到 n 个 index，之后，在位图中，将对应 index 得索引位置为1；</p>

<p>查询：使用n个hash函数对 key 进行计算，拿到 n 个 index；与位图中的索引位为index的值进行比较:</p>

<ul>
  <li>不存在：只有有一个索引位的值为0，则 key 不存在；</li>
  <li>如果所有 index 对应索引位的值都为 1，key 也不一定存在；</li>
</ul>

<p>布隆过滤器 不支持删除操作：</p>

<ul>
  <li>在位图中每个索引位只有两种状态（0或者 1），一个索引位被设置为1状态，但不确定它被设置了多少次；也就是不知道被多少个 key ，多少次 hash 映射而来以及是被具体哪个 hash 函数映射而来；</li>
</ul>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.4n7r1snkus.webp" alt="image" style="zoom:40%;" />
</div>

<p>确定布隆过滤器的大小：</p>

<p>预估要在布隆过滤器中插入的 key 的个数为 n；</p>

<p>设定 假阳率 p 为需求值；</p>

<p>通过下面这个网站可以计算出对应需求的 <code class="language-plaintext highlighter-rouge">位图大小m</code>以及最低需要的 <code class="language-plaintext highlighter-rouge">Hash函数个数k</code>：</p>

<p><a href="https://hur.st/bloomfilter">布隆过滤器在线计算: https://hur.st/bloomfilter</a></p>

<h2 id="三分布式一致性hash算法">三、分布式一致性Hash算法</h2>

<blockquote>
  <p>用来解决分布式缓存扩容的问题；</p>
</blockquote>

<p>分布式缓存为了避免缓存失效，通常使用 <strong>固定算法 和 数据迁移</strong> 两种方法；</p>

<p>为了保证分布式缓存中的数据均衡，通常使用 <strong>增加虚拟节点</strong> 的方法；</p>

<h3 id="1-固定算法">1. 固定算法</h3>

<blockquote>
  <p>固定算法是指在分布式缓存系统中，使用一种固定的算法来决定数据应该存储在哪个节点上。常见的固定算法包括哈希算法、一致性哈希算法等。</p>
</blockquote>

<p>一致性 Hash 算法是一种常见的固定算法；它通过将缓存节点(k)和数据(192.168….)映射到一个逻辑圆上，来决定数据应该存储在哪个节点上。</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.3yehhuqlox.webp" alt="image" style="zoom:25%;" />
</div>

<ul>
  <li>确定数据存储的节点，对于每个数据，找到逻辑圆上顺时针方向最近的节点。例如：
    <ul>
      <li>192.168.1.100，顺时针方向最近的节点是 k1</li>
      <li>192.168.1.101，顺时针方向最近的节点是 k2</li>
      <li>192.168.1.102，顺时针方向最近的节点是 k4 (未插入 k3 节点前)</li>
    </ul>
  </li>
</ul>

<h3 id="2-hash迁移">2. hash迁移</h3>

<blockquote>
  <p>在分布式缓存系统中，当节点增减(会造成缓存实效)或数据分布不均衡时，将数据从一个节点迁移到另一个节点的过程称为数据迁移；</p>
</blockquote>

<p>如上图，192.168.1.102 原来存储在 k4中，当我们插入新节点 k3 后会发现，192.168.1.102 顺时针方向的最近节点变成了 k3，所以就导致了 192.168.1.102 所映射的数据发生了缓存实效，我们无法再根据一致性hash函数来获取到 192.168.1.102 的值；</p>

<p>我们此时就需要将 192.168.1.102 从 k4 节点迁移到 k3 节点中，从而避免缓存实效。</p>

<h3 id="3-hash-偏移">3. hash 偏移</h3>

<blockquote>
  <p>当样本数过少，造成算法不稳定，每个节点存储的数据量极其不均衡，这种现象称为 hash偏移；</p>
</blockquote>

<p>通常使用 增加虚拟节点 的方法来解决hash偏移问题，增加虚拟节点 是指在分布式缓存系统中，为每个物理节点创建多个虚拟节点，并将这些虚拟节点映射到逻辑圆上。通过增加虚拟节点，可以进一步提高数据分布的均衡性。</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.5q7gcs37sk.webp" alt="image" style="zoom:25%;" />
</div>

<p>增加虚拟节点达到的效果：</p>

<ul>
  <li>提高均衡性 ：通过增加虚拟节点，可以进一步提高数据分布的均衡性，避免单个物理节点负载过重。</li>
  <li>简化数据迁移 ：虚拟节点的增加可以简化数据迁移的过程，减少迁移的数据量。</li>
</ul>

<h2 id="四应用实例">四、应用实例</h2>

<h3 id="如何只用-2gb-内存在-20亿个整数中找到出现次数最多的数">如何只用 2GB 内存在 20亿个整数中找到出现次数最多的数？</h3>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.491bb142x4.webp" alt="image" style="zoom:25%;" />
</div>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="数据结构" /><summary type="html"><![CDATA[本文创建于 2024-11-28；修改于 2024-11-28；]]></summary></entry><entry><title type="html">B树与B+树基础</title><link href="https://bssn520.github.io/p/12/" rel="alternate" type="text/html" title="B树与B+树基础" /><published>2024-11-27T00:00:00+00:00</published><updated>2024-11-27T00:00:00+00:00</updated><id>https://bssn520.github.io/p/B%E6%A0%91%E4%B8%8EB+%E6%A0%91%E5%9F%BA%E7%A1%80</id><content type="html" xml:base="https://bssn520.github.io/p/12/"><![CDATA[<blockquote>
  <p>没有写完，以后再补吧。</p>
</blockquote>

<blockquote>
  <p>本文创建于 <code class="language-plaintext highlighter-rouge">2024-11-27</code>；修改于 <code class="language-plaintext highlighter-rouge">2024-11-27</code>；</p>
</blockquote>

<h3 id="1-b树的性质">1. B树的性质</h3>

<p>一棵 M 阶B树 T，满足以下条件：</p>

<ol>
  <li>每个结点至多拥有M棵子树</li>
  <li>根结点至少拥有两棵子树</li>
  <li>除了根结点以外，其余每个分支结点至少拥有 M/2 棵子树</li>
  <li>所有的叶结点都在同一层上</li>
  <li>有 k 棵子树的分支结点则存在 k-1 个关键字，关键字按照递增顺序进行排序</li>
  <li>关键字数量满足 ceil(M/2) - 1 &lt;= n &lt;= M-1</li>
</ol>

<h3 id="2-b树-和-b-树的区别">2. B树 和 B+ 树的区别</h3>

<p>B树：所有节点存储数据；</p>

<p>B+树：叶子节点存储数据，内节点用于索引；</p>

<p>应用场景：</p>

<p>在内存中使用 B+树 存储数据的索引，索引对应的数据存储在硬盘内；这样可以做到一次寻址访问数据，性能很高。</p>

<h3 id="3-b树添加节点">3. B树添加节点</h3>

<p>B树 会将新节点插入到叶子节点，当叶子节点数据达到阈值(图中是5)时，开始分裂，如图所示：</p>

<p><img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.4g4j4zm0ev.webp" alt="image" style="zoom:40%;" /></p>

<h3 id="4-b树-删除节点">4. B树 删除节点</h3>

<p>有如下 B树，想要删除节点A，由于B树的性质，”关键字数量满足 ceil(M/2) - 1 &lt;= n &lt;= M-1” ，所以，我们无法直接将A节点删除：</p>

<div style="text-align: center;">
  <img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.26lili8qjd.webp" alt="image" style="zoom:40%;" />
</div>

<ol>
  <li>使用 <strong>合并</strong> 方法进行删除节点：</li>
</ol>

<p><img src="https://github.com/Bssn520/picx-images-hosting/raw/master/Arc-2024-11-27-18.23.31.73tzfcslpb.webp" alt="Arc-2024-11-27-18" style="zoom:40%;" /></p>

<ol>
  <li>从父节点 <strong>借位</strong> 进行删除节点：</li>
</ol>

<blockquote>
  <p>因为此时如果把 F 推下去合并，L 的左子树就不再满足 B树性质，所以要从父节点借位以满足性质。</p>
</blockquote>

<p><img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.3nrnn9m9s7.webp" alt="image" style="zoom:40%;" /></p>

<ul>
  <li>借不到就合并：</li>
</ul>

<p><img src="https://github.com/Bssn520/picx-images-hosting/raw/master/image.2h8ceo2952.webp" alt="image" style="zoom:40%;" /></p>

<ol>
  <li>
    <p>如果要删除的节点是一个内节点，则通过 借位 将内节点移动到叶子节点再进行叶子节点的删除流程。</p>
  </li>
  <li>
    <p>删除流程总结：</p>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">借 || 合 -&gt; 删</code></li>
    </ul>
  </li>
</ol>]]></content><author><name>[&quot;Luke&quot;]</name></author><category term="数据结构" /><summary type="html"><![CDATA[没有写完，以后再补吧。]]></summary></entry></feed>