<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>面试 on 小盒子的技术分享</title><link>https://xiaobox.github.io/tags/%E9%9D%A2%E8%AF%95/</link><description>Recent content in 面试 on 小盒子的技术分享</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 06 Mar 2026 03:44:58 +0000</lastBuildDate><atom:link href="https://xiaobox.github.io/tags/%E9%9D%A2%E8%AF%95/index.xml" rel="self" type="application/rss+xml"/><item><title>昨天面试官问我：一个 Prompt 进入大模型后，内部到底发生了什么？</title><link>https://xiaobox.github.io/p/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/</link><pubDate>Fri, 06 Mar 2026 03:44:58 +0000</pubDate><guid>https://xiaobox.github.io/p/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/cover.jpg" alt="Featured image of post 昨天面试官问我：一个 Prompt 进入大模型后，内部到底发生了什么？" /&gt;&lt;p&gt;昨天面试时，面试官抛给我一道很典型的问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“描述一下一个请求 prompt 经过 LLM 直到返回结果，这中间的推理过程，越详细越好。”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这类题看起来开放，实际上很考验基本功。&lt;/p&gt;
&lt;p&gt;因为它不是在问你会不会背几个名词，而是在看你是否真的理解：&lt;/p&gt;
&lt;p&gt;●一个请求在系统里是怎么流动的&lt;/p&gt;
&lt;p&gt;●进入模型之后到底算了什么&lt;/p&gt;
&lt;p&gt;●为什么大模型是一个 token 一个 token 地往外生成&lt;/p&gt;
&lt;p&gt;●为什么会有 prefill、decode、KV cache、sampling 这些概念&lt;/p&gt;
&lt;p&gt;●为什么工程侧还要引入 batching、FlashAttention、continuous batching 之类的优化&lt;/p&gt;
&lt;p&gt;如果回答得太浅，就会变成泛泛而谈；如果一上来就扎进公式，又很容易失去结构。&lt;/p&gt;
&lt;p&gt;我后来复盘了一下，觉得这道题最好的答法，不是“想到哪说到哪”，而是按一条完整链路去讲：&lt;strong&gt;服务层怎么处理请求，LLM 内部怎么做前向计算，生成阶段又是如何一步步产出结果的&lt;/strong&gt;。 这也是 GPT-3 所代表的自回归语言模型在推理时的基本工作方式：它不会在一次请求里更新参数，而是在固定权重下做前向传播，并逐 token 预测后续内容&lt;/p&gt;
&lt;h2 id="一个高分回答最好先把整体框架立住"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e9%ab%98%e5%88%86%e5%9b%9e%e7%ad%94%e6%9c%80%e5%a5%bd%e5%85%88%e6%8a%8a%e6%95%b4%e4%bd%93%e6%a1%86%e6%9e%b6%e7%ab%8b%e4%bd%8f" class="header-anchor"&gt;&lt;/a&gt;一个高分回答，最好先把整体框架立住
&lt;/h2&gt;&lt;p&gt;如果让我在面试里先用一句话概括，我会这样回答：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一个 prompt 从输入到输出，大体会经历 6 个阶段：请求封装、tokenization、推理调度、prefill、decode、结果反解码返回。其核心本质是：模型先并行“读懂”整段输入，建立上下文状态和 KV cache，然后再进入自回归生成循环，每次只预测下一个 token。&lt;/strong&gt; 这种“自回归 + 不做本次梯度更新”的推理方式，正是 GPT 类语言模型的基本范式；而 Transformer 则提供了它内部 attention 和前馈网络的计算骨架。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/001-6e34d687.png"&gt;&lt;/p&gt;
&lt;p&gt;这句话为什么重要？&lt;/p&gt;
&lt;p&gt;因为它先把&lt;strong&gt;系统层和模型层&lt;/strong&gt;分开了，也先把&lt;strong&gt;prefill和decode&lt;/strong&gt;分开了。很多人答这道题失分，不是因为不会，而是因为把所有层次混在一起，听起来就没有脉络。&lt;/p&gt;
&lt;h2 id="第一阶段用户输入的-prompt并不是模型真正看到的内容"&gt;&lt;a href="#%e7%ac%ac%e4%b8%80%e9%98%b6%e6%ae%b5%e7%94%a8%e6%88%b7%e8%be%93%e5%85%a5%e7%9a%84-prompt%e5%b9%b6%e4%b8%8d%e6%98%af%e6%a8%a1%e5%9e%8b%e7%9c%9f%e6%ad%a3%e7%9c%8b%e5%88%b0%e7%9a%84%e5%86%85%e5%ae%b9" class="header-anchor"&gt;&lt;/a&gt;第一阶段：用户输入的 Prompt，并不是模型真正看到的内容
&lt;/h2&gt;&lt;p&gt;我们在聊天框里看到的是自然语言，但模型真正接收到的，通常不是这段原始文本本身。&lt;/p&gt;
&lt;p&gt;在送入模型之前，服务层一般会先把 system、user、assistant 等多轮消息按固定模板组织起来，再补上一些特殊标记。随后，文本会经过 tokenizer，被切成 token 序列。像 OpenAI 开源的 tiktoken 就明确说明，它是一个用于模型的 BPE tokenizer。也就是说，对模型来说，文本首先会被变成一串离散的 token IDs，而不是“句子”本身。&lt;/p&gt;
&lt;p&gt;这一层很多人容易忽略，但它很关键。&lt;/p&gt;
&lt;p&gt;因为后面所有推理，都是建立在 token 序列上的。你输入的是一句中文、一段英文、还是一段代码，对模型来说，第一步都得先转换成 token IDs。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/002-48de7d31.png"&gt;&lt;/p&gt;
&lt;h2 id="第二阶段请求不会立刻进模型而是先进入推理服务和调度层"&gt;&lt;a href="#%e7%ac%ac%e4%ba%8c%e9%98%b6%e6%ae%b5%e8%af%b7%e6%b1%82%e4%b8%8d%e4%bc%9a%e7%ab%8b%e5%88%bb%e8%bf%9b%e6%a8%a1%e5%9e%8b%e8%80%8c%e6%98%af%e5%85%88%e8%bf%9b%e5%85%a5%e6%8e%a8%e7%90%86%e6%9c%8d%e5%8a%a1%e5%92%8c%e8%b0%83%e5%ba%a6%e5%b1%82" class="header-anchor"&gt;&lt;/a&gt;第二阶段：请求不会立刻进模型，而是先进入推理服务和调度层
&lt;/h2&gt;&lt;p&gt;在真实工程系统里，一个请求到达后，通常不会马上冲进 GPU 执行。&lt;/p&gt;
&lt;p&gt;它往往还要经过一层推理服务框架，比如 TGI、vLLM 这一类系统。它们会负责请求排队、动态 batching、缓存管理、流式返回等工作。Hugging Face 的 TGI 文档明确把 &lt;strong&gt;continuous batching、token streaming、Flash Attention、Paged Attention&lt;/strong&gt; 等列为核心特性；而 Transformers 的 continuous batching 文档也说明，这种动态调度的目的是提高 GPU 利用率、降低延迟，并允许请求在每一步动态加入和退出批次。&lt;/p&gt;
&lt;p&gt;所以，从系统视角看，链路通常是这样的：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;用户输入 → prompt 模板展开 → tokenization → 请求调度 / batching → 送入模型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这一步的意义在于：&lt;/p&gt;
&lt;p&gt;模型推理不是单个请求的“裸跑”，而是和其他请求一起，由推理引擎统一组织和优化的。&lt;/p&gt;
&lt;p&gt;我们上一阶段说的 tokenization ，&lt;strong&gt;严格来说， 不属于 Transformer 前向推理本身，模型只接收 input_ids。但在现代推理服务里，tokenizer 往往和 serving 引擎绑定在一起，所以工程上看起来像是推理引擎在处理原始字符串。像 vLLM 就同时支持 text prompt 和 pre-tokenized prompt，两种模式都能跑。&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;用户通常把原始字符串发给后端；后端中的推理服务通常持有 tokenizer，先把字符串编码成 token IDs，再交给模型执行 prefill/decode。只有在某些架构下，tokenization 才会提前在客户端或独立预处理层完成。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="第三阶段进入模型后token-会先变成向量表示"&gt;&lt;a href="#%e7%ac%ac%e4%b8%89%e9%98%b6%e6%ae%b5%e8%bf%9b%e5%85%a5%e6%a8%a1%e5%9e%8b%e5%90%8etoken-%e4%bc%9a%e5%85%88%e5%8f%98%e6%88%90%e5%90%91%e9%87%8f%e8%a1%a8%e7%a4%ba" class="header-anchor"&gt;&lt;/a&gt;第三阶段：进入模型后，token 会先变成向量表示
&lt;/h2&gt;&lt;p&gt;真正进入 LLM 后，第一步不是“开始回答”，而是把 token IDs 映射成高维向量。&lt;/p&gt;
&lt;p&gt;这一步叫 &lt;strong&gt;embedding lookup&lt;/strong&gt;。每个 token 都会查一张巨大的 embedding 表，得到自己的向量表示。到这时，模型才真正进入连续空间的数值计算。Transformer 的基础论文《Attention Is All You Need》所定义的，就是这样一种基于 attention 的序列建模方式。&lt;/p&gt;
&lt;p&gt;不过只有 token 向量还不够，因为模型还得知道“谁在前、谁在后”。&lt;/p&gt;
&lt;p&gt;早期 Transformer 使用位置编码，后来很多大模型会用 &lt;strong&gt;RoPE&lt;/strong&gt;（Rotary Position Embedding）。RoPE 的核心价值，是把位置信息融入 attention 计算中，让模型在处理 token 时同时保留相对位置信息。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/003-100bf39a.png"&gt;&lt;/p&gt;
&lt;h2 id="第四阶段真正的推理核心发生在一层层-transformer-block-里"&gt;&lt;a href="#%e7%ac%ac%e5%9b%9b%e9%98%b6%e6%ae%b5%e7%9c%9f%e6%ad%a3%e7%9a%84%e6%8e%a8%e7%90%86%e6%a0%b8%e5%bf%83%e5%8f%91%e7%94%9f%e5%9c%a8%e4%b8%80%e5%b1%82%e5%b1%82-transformer-block-%e9%87%8c" class="header-anchor"&gt;&lt;/a&gt;第四阶段：真正的“推理核心”发生在一层层 Transformer Block 里
&lt;/h2&gt;&lt;p&gt;这是这道题最核心的部分。&lt;/p&gt;
&lt;p&gt;如果面试官说“越详细越好”，你就必须把 Transformer Block 讲清楚。&lt;/p&gt;
&lt;p&gt;一个典型的 decoder-only LLM，每一层大体都会做两件事：&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;第一，Self-Attention&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;第二，FFN / MLP（前馈网络&lt;/strong&gt;）&lt;/p&gt;
&lt;p&gt;中间再配合残差连接和归一化。Transformer 论文给出的主体结构就是这样。&lt;/p&gt;
&lt;p&gt;你可以把它想成：&lt;/p&gt;
&lt;p&gt;●attention 负责“读群聊”&lt;/p&gt;
&lt;p&gt;●FFN 负责“自己想一想、整理一下”&lt;/p&gt;
&lt;h3 id="self-attention-在干什么"&gt;&lt;a href="#self-attention-%e5%9c%a8%e5%b9%b2%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;Self-Attention 在干什么？
&lt;/h3&gt;&lt;p&gt;可以把它理解成：&lt;strong&gt;当前位置的 token，要去看上下文里哪些 token 最相关。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;模型会把当前隐藏状态投影成 Query、Key、Value 三组向量，然后通过 Query 和所有 Key 的相似度算出注意力权重，再对 Value 做加权求和。Transformer 论文把它定义为 &lt;strong&gt;Scaled Dot-Product Attention&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;对于生成式语言模型，还有一个必须强调的点：causal mask。&lt;/p&gt;
&lt;p&gt;也就是当前位置只能看见自己和前面的 token，不能偷看未来。这一点决定了模型天然是自回归生成的：它永远只能基于已有上下文，去预测下一个 token。GPT-3 论文里所讨论的 few-shot/in-context learning，本质上也是建立在这种&lt;strong&gt;自回归&lt;/strong&gt;预测机制之上的。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/004-a52cdfd4.png"&gt;&lt;/p&gt;
&lt;p&gt;关于 Q、K、V，可以简单这样理解：&lt;/p&gt;
&lt;p&gt;Q = 我现在想找什么&lt;/p&gt;
&lt;p&gt;K = 每个词身上贴的“索引标签”&lt;/p&gt;
&lt;p&gt;V = 每个词真正携带、可被取走的信息。&lt;/p&gt;
&lt;p&gt;最通俗的比喻是“图书馆检索”：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;你现在脑子里有一个问题，这就是 Q（Query）；书架上每本书卡片上的主题标签，是 K（Key）；书里真正的内容，是 V（Value）。系统先拿你的问题 Q 去和所有标签 K 比一比，看看“像不像、相关不相关”；相关度高的那些书，它们的内容 V 就会被更多地取出来，最后合成当前这一步该看的信息。Transformer 论文对 attention 的定义，本质上就是“一个 query 对一组 key-value 对做匹配，输出是 values 的加权和”。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="ffn-又在干什么"&gt;&lt;a href="#ffn-%e5%8f%88%e5%9c%a8%e5%b9%b2%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;FFN 又在干什么？
&lt;/h3&gt;&lt;p&gt;如果说 attention 负责“从上下文搬运信息”，那么 FFN 更像是“对当前位置做进一步加工”。&lt;/p&gt;
&lt;p&gt;它不会跨位置交互，而是对每个 token 的表示单独做非线性变换，把特征进一步提纯和增强。Transformer 论文把它称为 position-wise feed-forward network。&lt;/p&gt;
&lt;p&gt;所以一个 Transformer Block 可以粗略理解成：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;先决定我该关注上下文里的谁，再把取回来的信息做一轮更深的特征变换&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/005-ea012f28.png"&gt;&lt;/p&gt;
&lt;p&gt;注意在整个流程中，&lt;strong&gt;prefill 和 decode 阶段，都要做 self-attention 和 FFN。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但要分清楚：“都要做”不等于“做法完全一样”。&lt;/p&gt;
&lt;p&gt;●Prefill 把整段 prompt 一次性送进去。 这时每一层都会对这批 token 做 masked self-attention，然后再过 FFN。因为整段 prompt 一开始就都已知，所以这一步可以在单个请求内部并行处理很多 token。Hugging Face 对 prefill 的描述也是：prefill 会处理整段输入，并建立 KV cache。&lt;/p&gt;
&lt;p&gt;●Decode 开始一个 token 一个 token 往后生成。 这时每生成一个新 token，它仍然要在每一层里经过：一次 self-attention，一次 FFN&lt;/p&gt;
&lt;p&gt;decode 不是把旧 token 全部再跑一遍 attention 和 FFN。有了 KV cache 后，旧 token 的 K/V 会被缓存起来；新 token 到来时，只需要为这个新 token 计算当前层需要的表示，再和历史 K/V 做注意力计算，然后继续过 FFN。Hugging Face 官方缓存文档明确说了：后续生成时，只传入尚未处理的新 token，并把 key/value 写入和读取自 cache。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;FFN 就是 Transformer 每层里、紧跟在 self-attention 后面的前馈网络，本质上是对每个 token 单独做的 MLP 加工。在标准 LLM 里，prefill 和 decode 两个阶段都要经过 self-attention 和 FFN；区别只是 prefill 处理整段已知 token，decode 只处理当前新 token，并复用历史 KV cache&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="第五阶段prefill先把整段-prompt-读完"&gt;&lt;a href="#%e7%ac%ac%e4%ba%94%e9%98%b6%e6%ae%b5prefill%e5%85%88%e6%8a%8a%e6%95%b4%e6%ae%b5-prompt-%e8%af%bb%e5%ae%8c" class="header-anchor"&gt;&lt;/a&gt;第五阶段：Prefill——先把整段 Prompt “读完”
&lt;/h2&gt;&lt;p&gt;很多人会误以为模型一进来就开始逐字生成。&lt;/p&gt;
&lt;p&gt;其实不是。生成前通常会先有一个很重要的阶段：&lt;strong&gt;Prefill&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Prefill 的意思是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;先把整段 prompt 一次性跑完整个前向过程。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在这个阶段，模型会为输入中的所有 token 计算各层隐藏状态，并且生成后面 decode 要用到的 KV cache。Hugging Face 的缓存文档明确指出，KV cache 会把注意力层中之前 token 产生的 key-value 对存下来，后续生成时直接复用，从而避免重复计算。&lt;/p&gt;
&lt;p&gt;Prefill 的一个重要特点是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它通常可以高度并行。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为整段输入已经完整给定了，GPU 能把很多矩阵操作一起做完。所以 prefill 更像“先整体读题”，吞吐通常更高。vLLM 文档也明确把 prefill 归类为更偏 compute-bound 的阶段&lt;/p&gt;
&lt;p&gt;你可以把 prefill 想象成一个正在考试的人，prefill 就是他正在读题，把题目先读到脑子里，填充好上下文，然后再开始做答（输出 token）&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/006-a96fea1e.png"&gt;&lt;/p&gt;
&lt;h2 id="第六阶段kv-cache为什么不会每次都重算全文"&gt;&lt;a href="#%e7%ac%ac%e5%85%ad%e9%98%b6%e6%ae%b5kv-cache%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e4%bc%9a%e6%af%8f%e6%ac%a1%e9%83%bd%e9%87%8d%e7%ae%97%e5%85%a8%e6%96%87" class="header-anchor"&gt;&lt;/a&gt;第六阶段：KV Cache——为什么不会每次都重算全文
&lt;/h2&gt;&lt;p&gt;这部分是面试里非常加分的点。&lt;/p&gt;
&lt;p&gt;因为它体现你不只懂“算法”，还懂“推理为什么能跑得起来”。&lt;/p&gt;
&lt;p&gt;如果没有 KV cache，那么每生成一个新 token，模型都要把整个历史上下文从头再算一遍，成本会非常高。&lt;/p&gt;
&lt;p&gt;而有了 KV cache 后，历史 token 在每层 attention 中算出的 K 和 V 都会被缓存起来。下一个时间步只需要为新 token 计算新的 Query、Key、Value，再用新的 Query 去和历史缓存里的 Key 做匹配即可。Hugging Face 的官方文档把这一点解释得很清楚：KV cache 的目标就是消除重复计算，加速自回归生成。&lt;/p&gt;
&lt;p&gt;一句话说明就是：&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;没有 KV cache，像每次都重读整篇文章&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;有 KV cache，则像前文已经做好笔记，现在只补最后一句。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="为什么-kv-cache-只缓存-k-和-v而不缓存-q"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88-kv-cache-%e5%8f%aa%e7%bc%93%e5%ad%98-k-%e5%92%8c-v%e8%80%8c%e4%b8%8d%e7%bc%93%e5%ad%98-q" class="header-anchor"&gt;&lt;/a&gt;为什么 KV cache 只缓存 K 和 V，而不缓存 Q？
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;一个东西值不值得缓存，不看它“重不重要”，而看它“后面还会不会再次被用到”。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;KV cache 只缓存 K 和 V，不缓存 Q，不是因为 Q 不重要，而是因为 Q “只在当前这一步有用一次”；而 K、V 会在后面每一步继续被反复用到。 这正是 Hugging Face 官方对缓存机制的解释：过去 token 的 K 和 V 可以缓存并复用，而在推理时，只需要“最后一个 token 的 query”来计算当前步的表示。&lt;/p&gt;
&lt;h2 id="第七阶段decode开始逐-token-生成答案"&gt;&lt;a href="#%e7%ac%ac%e4%b8%83%e9%98%b6%e6%ae%b5decode%e5%bc%80%e5%a7%8b%e9%80%90-token-%e7%94%9f%e6%88%90%e7%ad%94%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;第七阶段：Decode——开始逐 token 生成答案
&lt;/h2&gt;&lt;p&gt;当 prefill 完成后，模型已经“读懂”了整段输入。&lt;/p&gt;
&lt;p&gt;接下来，系统会取最后一个位置的隐藏状态，通过输出层映射成整个词表上的 logits，也就是“下一个 token 的打分”。随后再通过 softmax 和解码策略，决定下一个 token 输出什么。Transformer 的输出逻辑与 Hugging Face 的生成文档都说明了这一点。&lt;/p&gt;
&lt;p&gt;这里又有一个容易被问到的点：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;下一个 token 是怎么选出来的？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;并不是只有“选概率最大”这一种方式。常见解码策略包括 greedy、sampling、top-k、top-p 等。不同策略会影响文本的稳定性、多样性和创造性。Hugging Face 的生成策略文档对此有系统说明。&lt;/p&gt;
&lt;p&gt;然后，流程进入一个循环：&lt;/p&gt;
&lt;p&gt;●把刚生成的 token 接到上下文后面&lt;/p&gt;
&lt;p&gt;●复用 KV cache&lt;/p&gt;
&lt;p&gt;●只为这个新 token 跑一遍前向计算&lt;/p&gt;
&lt;p&gt;●再得到新的 logits&lt;/p&gt;
&lt;p&gt;●再生成下一个 token&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/007-469317f3.png"&gt;&lt;/p&gt;
&lt;p&gt;这就是为什么你看到的大模型回答，总是一个 token 一个 token 流式地吐出来，而不是整段瞬间出现。&lt;/p&gt;
&lt;h2 id="为什么第一个字慢后面快"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e7%ac%ac%e4%b8%80%e4%b8%aa%e5%ad%97%e6%85%a2%e5%90%8e%e9%9d%a2%e5%bf%ab" class="header-anchor"&gt;&lt;/a&gt;为什么“第一个字慢，后面快”？
&lt;/h2&gt;&lt;p&gt;这也是一个非常像面试 follow-up 的问题。&lt;/p&gt;
&lt;p&gt;很多候选人知道 prefill 和 decode，但解释不清为什么两者速度特征不同。&lt;/p&gt;
&lt;p&gt;vLLM 的优化文档明确提到，&lt;strong&gt;prefill 更偏 compute-bound，decode 更偏 memory-bound。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;原因在于：prefill 可以把整段输入并行做大矩阵乘法，吃满 GPU 算力；而 decode 虽然每步只算一个 token，但它强依赖历史 KV cache，频繁访问显存，并且步骤之间有严格的顺序依赖。&lt;/p&gt;
&lt;p&gt;这也是为什么工程上会有很多针对推理性能的优化，比如：&lt;/p&gt;
&lt;p&gt;●FlashAttention：通过 IO-aware 的 attention 计算方式，减少显存读写&lt;/p&gt;
&lt;p&gt;●continuous batching：动态调整批次，减少 GPU 空转&lt;/p&gt;
&lt;p&gt;●chunked prefill / Paged Attention：改进长上下文和缓存管理效率&lt;/p&gt;
&lt;p&gt;要注意，这些技术优化的是执行效率，不是模型的“语义本质”。模型本质上做的事情仍然是：基于已有上下文，反复预测下一个 token&lt;/p&gt;
&lt;p&gt;我现在觉得，这道题最稳妥的回答方式，就是最后收束成一句话：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一个 LLM 请求的推理过程，本质上是：先把 prompt 模板化并 token 化，经由推理服务调度进入 GPU；模型通过 embedding 和多层 Transformer block 并行完成 prefill，建立上下文表示和 KV cache；随后进入 decode 循环，基于历史缓存逐 token 执行注意力、前馈网络和采样，直到生成结束，再把 token 序列反解码成文本返回。 这条链路同时体现了 Transformer 的计算机制、自回归生成范式，以及现代推理系统在 batching、缓存和 attention kernel 上的工程优化&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="看起来都是推理引擎的活儿啊"&gt;&lt;a href="#%e7%9c%8b%e8%b5%b7%e6%9d%a5%e9%83%bd%e6%98%af%e6%8e%a8%e7%90%86%e5%bc%95%e6%93%8e%e7%9a%84%e6%b4%bb%e5%84%bf%e5%95%8a" class="header-anchor"&gt;&lt;/a&gt;看起来都是推理引擎的活儿啊？
&lt;/h2&gt;&lt;p&gt;从整个流程上看，几乎都是推理引擎在负责，所以可以这么理解，但要再往前走半步：&lt;/p&gt;
&lt;p&gt;●从“流程编排”角度看，LLM 本体确实很被动；&lt;/p&gt;
&lt;p&gt;●从“核心计算与语义生成”角度看，LLM 才是全链路里最不可替代的部分。&lt;/p&gt;
&lt;p&gt;如果把整个链路拆开，职责大致是这样的：&lt;/p&gt;
&lt;p&gt;1.&lt;strong&gt;推理引擎 / serving 系统负责&lt;/strong&gt;：接 HTTP 请求、做 tokenization / 输入处理、调度 batching、管理 KV cache、协调 GPU worker、流式返回结果、做一部分采样与系统优化。vLLM 的官方文档甚至把这几层写得很直白：最少会有 1 个 API server 负责 HTTP、tokenization 和输入处理，1 个 engine core 负责 scheduler 和 KV cache 管理，再加上 N 个 GPU worker 负责执行模型前向计算。&lt;/p&gt;
&lt;p&gt;2.&lt;strong&gt;LLM 模型本体负责&lt;/strong&gt;：对 input_ids 做 embedding，经过多层 Transformer block 的 self-attention 和 feed-forward network，输出 logits，也就是“下一个 token 的分数分布”。Transformer 论文给出的核心结构就是 attention + FFN；Transformers 文档也明确说 causal language modeling 本质上是在左侧上下文条件下做 next-token prediction，而模型输出里的 logits 是对词表中每个 token 的预测分数。&lt;/p&gt;
&lt;p&gt;所以，**推理引擎决定“怎么高效地跑”，模型决定“到底生成什么”。**前者偏“编排与优化”，后者偏“语义计算与内容生成”&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-06-zuo-tian-mian-shi-guan-wen-wo-yi-ge-prompt-jin-ru-da-mo-xing/008-d8a4beaa.png"&gt;&lt;/p&gt;</description></item><item><title>面试必问：ACID 你真的懂了吗？</title><link>https://xiaobox.github.io/p/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/</link><pubDate>Wed, 19 Feb 2025 07:45:55 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/cover.jpg" alt="Featured image of post 面试必问：ACID 你真的懂了吗？" /&gt;&lt;h2 id="acid-是什么"&gt;&lt;a href="#acid-%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;ACID 是什么？
&lt;/h2&gt;&lt;p&gt;事务处理中的 ACID 是确保数据库操作可靠性和完整性的四个核心特性&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/th&gt;
 &lt;th&gt;&lt;strong&gt;说明&lt;/strong&gt;&lt;/th&gt;
 &lt;th&gt;&lt;strong&gt;示例&lt;/strong&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;原子性（Atomicity）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;事务是不可分割的最小操作单元，事务中的所有操作要么全部成功完成，要么全部失败回滚。&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;用户在线购买书籍时的支付流程：&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;①支付扣款 ②库存扣减 ③快递下单三个步骤必须全部成功——任一步骤失败时（如库存不足），系统自动取消已付金额，退回到购物未进行状态。&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;一致性（Consistency）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;事务必须保证数据库从一个一致性状态转变到另一个一致性状态。一致性是指数据必须符合预定义的规则和约束，例如完整性约束、业务规则等。&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;银行转账场景：&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;账户A向账户B转200元后，两人账户总额保持不变（若A+B原为1000元，操作完成后仍为1000）。即便系统中途崩溃，恢复后也不会出现A扣200元但B未入账的金额&amp;quot;凭空消失&amp;quot;。&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;隔离性（Isolation）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;多个事务并发执行时，每个事务都应该感觉不到其他事务的存在，就像在隔离的环境中执行一样。事务之间互相隔离，不会互相影响。&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;航班订座系统：&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;当乘客A和B同时选择最后一个座位，先完成支付者的订单立即锁定座位，另一用户将实时看到&amp;quot;无余票&amp;quot;提示——避免出现系统误判导致超售。&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;持久性（Durability）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;em&gt;一旦事务提交成功，对数据库的修改就应该是永久性的，即使系统发生崩溃或重启等意外情况，数据也不会丢失。&lt;/em&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;线上预约挂号确认：&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;用户成功提交预约后，即便医院服务器遭遇断电，重启后系统依然保留该条预约记录并发送确认短信，不会因突发意外丢失数据。&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="mysql-是如何保证-acid-的"&gt;&lt;a href="#mysql-%e6%98%af%e5%a6%82%e4%bd%95%e4%bf%9d%e8%af%81-acid-%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;MySQL 是如何保证 ACID 的？
&lt;/h2&gt;&lt;p&gt;MySQL 实现 ACID 特性主要依赖 日志系统（undo log 和 redo log）、锁机制 和 MVCC 多版本并发控制。下面是具体实现原理的详细分析：&lt;/p&gt;
&lt;h3 id="一atomicity原子性"&gt;&lt;a href="#%e4%b8%80atomicity%e5%8e%9f%e5%ad%90%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;一、Atomicity（原子性）
&lt;/h3&gt;&lt;p&gt;事务是不可分割的最小执行单位。原子性确保事务中的所有操作要么全部成功完成，要么全部失败回滚。不允许中间状态。MySQL 通过 &lt;strong&gt;Undo Log + 事务回滚&lt;/strong&gt; 实现原子性：&lt;/p&gt;
&lt;p&gt;当事务开始时，InnoDB 会记录事务修改前的数据（旧版本）到 &lt;code&gt;Undo Log&lt;/code&gt; 中，用于事务回滚时恢复原始状态。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;Undo Log&lt;/code&gt; 记录结构包含：原始数据值、事务ID（trx_id）、回滚指针（roll_pointer）。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/001-8d841891.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Undo Log&lt;/code&gt; 记录的是逻辑操作，例如 &amp;ldquo;删除第 10 行&amp;rdquo;，&amp;ldquo;将字段 &amp;rsquo;name&amp;rsquo; 从 &amp;lsquo;old&amp;rsquo; 更新为 &amp;rsquo;new&amp;rsquo;&amp;rdquo; 等。&lt;/p&gt;
&lt;p&gt;举个简单例子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 事务未提交时，其他事务通过Undo Log读取原始数据（MVCC）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 此时Undo Log会记录balance的旧值（如200）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ROLLBACK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 使用Undo Log恢复数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果事务执行过程中发生错误或者用户显式执行 ROLLBACK，InnoDB 可以根据 &lt;code&gt;Undo Log&lt;/code&gt; 中的记录将数据恢复到事务开始之前的状态，从而实现事务的回滚&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="二isolation隔离性"&gt;&lt;a href="#%e4%ba%8cisolation%e9%9a%94%e7%a6%bb%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;二、Isolation（隔离性）
&lt;/h3&gt;&lt;p&gt;隔离性 (Isolation) 是 ACID 特性中的关键一环，它确保在多个事务并发执行时，每个事务都仿佛独立运行，互不干扰。 换句话说，一个事务的中间状态和操作不应该被其他并发事务感知到，从而避免数据混乱和不一致。 为了实现这种隔离效果，MySQL 的 InnoDB 存储引擎主要依赖于两大核心机制：&lt;strong&gt;锁机制 (Locking)&lt;/strong&gt; 和 &lt;strong&gt;多版本并发控制 (MVCC)&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="1-锁机制"&gt;&lt;a href="#1-%e9%94%81%e6%9c%ba%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;1. 锁机制
&lt;/h4&gt;&lt;p&gt;首先，&lt;strong&gt;锁机制是最基础的隔离手段&lt;/strong&gt;。InnoDB 实现了多种锁类型，以适应不同的并发场景和隔离需求。 其中，&lt;code&gt;行级锁&lt;/code&gt; 是 InnoDB 并发控制的核心，它允许事务仅锁定需要修改的数据行，最大程度地提高了并发度。 行级锁又细分为 &lt;code&gt;共享锁 (S 锁)&lt;/code&gt; 和 &lt;code&gt;排他锁 (X 锁)&lt;/code&gt;，前者允许多个事务同时读取同一行数据，而后者则保证在更新或删除数据时，只有一个事务可以独占该行。&lt;/p&gt;
&lt;p&gt;除了行级锁，MySQL 还提供 &lt;code&gt;表级锁&lt;/code&gt;，它会锁定整个表，虽然并发度较低，但在某些特定场景（如执行 LOCK TABLES 语句）下仍然适用。&lt;/p&gt;
&lt;p&gt;为了更高效地管理锁，InnoDB 引入了 &lt;code&gt;意向锁 (Intention Locks)&lt;/code&gt;，它在表级别上预先声明事务对行级锁的意图，从而优化锁的检查和兼容性。&lt;/p&gt;
&lt;p&gt;此外，在 REPEATABLE READ 和 SERIALIZABLE 这两个较高的隔离级别下，为了解决幻读问题，InnoDB 还使用了 &lt;code&gt;间隙锁 (Gap Locking)&lt;/code&gt;，它不仅锁定已存在的记录，还锁定索引记录之间的间隙，防止其他事务在该间隙中插入新记录，从而彻底避免幻读。&lt;/p&gt;
&lt;p&gt;我们通过一个具体的例子来说明 InnoDB 的 间隙锁（Gap Locking） 如何解决幻读问题：&lt;/p&gt;
&lt;p&gt;假设有一张表 &lt;code&gt;students&lt;/code&gt;，存储学生信息，主键为 &lt;code&gt;id&lt;/code&gt;，当前数据如下：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;id&lt;/th&gt;
 &lt;th&gt;name&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;Alice&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;Bob&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;Charlie&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;现在有两个事务 &lt;strong&gt;事务A&lt;/strong&gt; 和 &lt;strong&gt;事务A&lt;/strong&gt;，操作顺序如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;事务A&lt;/strong&gt; 执行范围查询并加锁：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;BEGIN;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;SELECT * FROM students WHERE id BETWEEN 1 AND 5 FOR UPDATE;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;-- 查询结果：id=1, 3, 5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;InnoDB 会为 &lt;code&gt;id&lt;/code&gt; 索引加上 &lt;strong&gt;Next-Key Lock&lt;/strong&gt;（行锁 + 间隙锁），锁定的范围包括：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(-∞, 1]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(1, 3]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(3, 5]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(5, +∞)&lt;/code&gt;
（注：假设表中无其他数据）&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;事务B&lt;/strong&gt; 尝试插入新数据：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;BEGIN;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;INSERT INTO students (id, name) VALUES (2, &amp;#39;David&amp;#39;); -- 尝试插入到间隙 (1,3)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;-- 或者
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;INSERT INTO students (id, name) VALUES (4, &amp;#39;Eve&amp;#39;); -- 尝试插入到间隙 (3,5)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;由于 &lt;strong&gt;事务A&lt;/strong&gt; 的间隙锁锁定了 &lt;code&gt;(1,3)&lt;/code&gt; 和 &lt;code&gt;(3,5)&lt;/code&gt; 的间隙，&lt;strong&gt;事务B&lt;/strong&gt; 的插入操作会被阻塞，直到 &lt;strong&gt;事务A&lt;/strong&gt; 提交或回滚。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;事务A&lt;/strong&gt; 提交：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;COMMIT;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;事务B&lt;/strong&gt; 的插入操作才会继续执行。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;结果对比&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;没有间隙锁&lt;/strong&gt;：
若事务A未加间隙锁（例如使用 &lt;code&gt;READ COMMITTED&lt;/code&gt; 隔离级别），事务B可以插入 &lt;code&gt;id=2&lt;/code&gt; 或 &lt;code&gt;id=4&lt;/code&gt;。当事务A再次执行 &lt;code&gt;SELECT&lt;/code&gt; 时，会看到新插入的行（id=2 或 4），导致&lt;strong&gt;幻读&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;有间隙锁&lt;/strong&gt;：
事务B的插入操作被阻塞，直到事务A释放锁。事务A在事务执行期间始终看到相同的数据（id=1,3,5），&lt;strong&gt;避免幻读&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="关键点"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;关键点&lt;/strong&gt;
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;间隙锁的锁定范围&lt;/strong&gt;：
InnoDB 的间隙锁不仅锁定已存在的行，还会锁定索引记录之间的“间隙”（例如 &lt;code&gt;(1,3)&lt;/code&gt; 和 &lt;code&gt;(3,5)&lt;/code&gt;），阻止其他事务在间隙中插入新数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Next-Key Lock 的作用&lt;/strong&gt;：
Next-Key Lock = 行锁（锁定已存在记录） + 间隙锁（锁定间隙）。例如，对 &lt;code&gt;id=3&lt;/code&gt; 的行锁会锁定范围 &lt;code&gt;(1,3]&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;隔离级别的影响&lt;/strong&gt;：
间隙锁仅在 &lt;code&gt;REPEATABLE READ&lt;/code&gt; 隔离级别下生效。在 &lt;code&gt;READ COMMITTED&lt;/code&gt; 级别下，InnoDB 会禁用间隙锁，幻读仍可能发生。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在实际场景中，比如在电商系统中，若一个事务正在统计某商品（例如库存范围在 100~200）的订单数量，间隙锁可以防止其他事务插入新的订单记录（例如库存为 150 的商品），确保统计结果的一致性。&lt;/p&gt;
&lt;h4 id="2-mvcc"&gt;&lt;a href="#2-mvcc" class="header-anchor"&gt;&lt;/a&gt;2. MVCC
&lt;/h4&gt;&lt;p&gt;为了进一步提升并发性能，尤其是在读多写少的场景下，InnoDB 引入了 &lt;code&gt;多版本并发控制 (MVCC)&lt;/code&gt;。 &lt;strong&gt;MVCC 的核心思想是允许事务在读取数据时，访问数据在某个时间点的快照版本，而不是直接读取最新的数据。&lt;/strong&gt; 这样，读操作就不需要等待写操作完成，从而实现读写并发执行，显著提高了系统吞吐量。 MVCC 的实现依赖于 &lt;code&gt;Undo Log&lt;/code&gt; 和 &lt;code&gt;Read View (快照读)&lt;/code&gt;。 &lt;code&gt;Undo Log&lt;/code&gt; 用于记录数据的历史版本，而 &lt;code&gt;Read View&lt;/code&gt; 则定义了事务在读取数据时应该看到哪个版本的数据。MVCC 主要应用于 READ COMMITTED 和 REPEATABLE READ 这两个隔离级别，在这两个级别下，MVCC 可以有效减少锁的竞争，提升并发性能。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/002-9516a24c.png"&gt;&lt;/p&gt;
&lt;p&gt;总结来说：MVCC 就是基于隐藏字段、undo_log 链和 ReadView 来实现的&lt;/p&gt;
&lt;h4 id="3-隔离级别与策略对比"&gt;&lt;a href="#3-%e9%9a%94%e7%a6%bb%e7%ba%a7%e5%88%ab%e4%b8%8e%e7%ad%96%e7%95%a5%e5%af%b9%e6%af%94" class="header-anchor"&gt;&lt;/a&gt;3. 隔离级别与策略对比
&lt;/h4&gt;&lt;p&gt;最后，为了满足不同应用场景对隔离程度和性能的不同需求，MySQL 提供了 四种事务隔离级别。 从最低的 READ UNCOMMITTED (读未提交) 到最高的 SERIALIZABLE (串行化)，隔离级别依次增强，但并发性能也随之降低。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;READ UNCOMMITTED 允许脏读，隔离性最弱，但性能最高；&lt;/li&gt;
&lt;li&gt;READ COMMITTED 避免了脏读，但可能出现不可重复读；&lt;/li&gt;
&lt;li&gt;REPEATABLE READ (&lt;strong&gt;InnoDB 默认级别&lt;/strong&gt;) 在 READ COMMITTED 的基础上解决了不可重复读，但仍可能存在幻读（在某些情况下，InnoDB 通过 Next-Key Locking 尝试解决幻读）；&lt;/li&gt;
&lt;li&gt;SERIALIZABLE 通过强制事务串行执行，彻底避免了所有并发问题，但并发性能也最低。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;隔离级别&lt;/th&gt;
 &lt;th&gt;脏读&lt;/th&gt;
 &lt;th&gt;不可重复读&lt;/th&gt;
 &lt;th&gt;幻读&lt;/th&gt;
 &lt;th&gt;实现方式&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;READ UNCOMMITTED&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;无锁&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;READ COMMITTED&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;✔️&lt;/td&gt;
 &lt;td&gt;每个SELECT生成新Read View&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;REPEATABLE READ*&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✖️△&lt;/td&gt;
 &lt;td&gt;首SELECT生成Read View + 间隙锁&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SERIALIZABLE&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;✖️&lt;/td&gt;
 &lt;td&gt;所有SELECT隐式转成&lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;△：MySQL通过Next-Key Lock（行锁+间隙锁组合）在REPEATABLE READ级别实际消除幻读。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id="三durability持久性"&gt;&lt;a href="#%e4%b8%89durability%e6%8c%81%e4%b9%85%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;三、Durability（持久性）
&lt;/h3&gt;&lt;p&gt;持久性 (Durability) 是 ACID 特性中保障数据安全性的最后一道防线。 它确保一旦事务成功提交，对数据库所做的所有更改都必须被永久地保存下来，即使系统随后发生崩溃、断电或任何其他类型的故障，已提交的数据也绝不会丢失。 为了实现这种强大的数据保障，MySQL 的 InnoDB 存储引擎采用了一系列精密的机制，其中最核心的是 &lt;code&gt;Redo Log&lt;/code&gt; (重做日志)，并辅以 &lt;code&gt;Write-Ahead Logging (WAL)&lt;/code&gt; 策略、 &lt;code&gt;Doublewrite Buffer&lt;/code&gt; (双写缓冲区) 和灵活的 刷盘 (Flush to Disk) 机制，同时，&lt;code&gt;Binlog&lt;/code&gt; (二进制日志) 也从更广泛的层面为数据持久性提供了支持。&lt;/p&gt;
&lt;p&gt;首先，Redo Log 是 InnoDB 实现持久性的基石。 当一个事务执行过程中，InnoDB 并不会立即将数据页的修改直接写入磁盘上的数据文件，而是先将这些修改操作，例如插入、更新或删除的具体内容，以一种紧凑、高效的形式，顺序地记录到 Redo Log Buffer 中。 这里的 Redo Log 记录的是物理层面的修改，例如“将数据页 X 的偏移量 Y 处的 Z 个字节修改为新的值”。 为了保证效率，Redo Log Buffer 存在于内存中，但为了确保持久性，InnoDB 会定期或者在事务提交时，将 Redo Log Buffer 中的内容刷新到 Redo Log 文件 这一磁盘上的持久化存储。&lt;/p&gt;
&lt;p&gt;为了进一步确保数据在极端情况下的安全性，InnoDB 遵循 &lt;code&gt;Write-Ahead Logging&lt;/code&gt; (WAL) 预写式日志 策略。 这意味着，在任何数据页的实际修改被写入磁盘数据文件之前，必须先将相应的 Redo Log 记录落盘到 Redo Log 文件中。 &lt;strong&gt;这种 “先写日志，后写数据” 的机制至关重要，它保证了即使在数据页尚未完全刷入磁盘时系统发生崩溃，已经提交的事务的所有修改操作也已经安全地记录在 Redo Log 中，从而为后续的数据恢复提供了保障。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/003-3900f38d.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Doublewrite Buffer&lt;/code&gt; (双写缓冲区) 是 InnoDB 为了应对数据页“部分写失效 (Partial Write)” 问题而引入的增强机制。 在数据页从内存刷新到磁盘数据文件的过程中，可能会因为断电等意外情况，导致数据页只写入了一部分，造成数据损坏。 为了避免这种情况，InnoDB 在数据页最终写入数据文件之前，会先将其完整地写入 Doublewrite Buffer 区域。 Doublewrite Buffer 是磁盘上一个连续的存储区域，InnoDB 会先顺序写入，保证写入的原子性。 之后，再将数据页从 Doublewrite Buffer 拷贝到真正的数据文件位置。 这样，即使在数据页写入过程中发生崩溃，InnoDB 在重启恢复时，可以通过 Doublewrite Buffer 检查数据页的完整性。 如果发现数据页写入不完整或已损坏，可以从 Doublewrite Buffer 中找到该数据页的完整副本进行恢复，从而有效地避免了数据页部分写入导致的数据丢失。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Flush to Disk&lt;/code&gt; 机制 则提供了对 Redo Log 和数据页刷盘行为的精细控制。 MySQL 提供了多个参数，例如 innodb_flush_log_at_trx_commit 参数控制 Redo Log 何时刷盘，可以设置为每次事务提交都刷盘 (最安全，但性能较低)，或者定期刷盘 (性能较高，但可能在崩溃时丢失少量已提交事务)。 innodb_flush_method 参数则控制数据页刷盘的具体方式，例如是否绕过操作系统缓存直接写入磁盘，以满足不同的性能和可靠性需求。 通过调整这些刷盘策略，用户可以在数据安全性和性能之间进行权衡，根据实际业务场景选择合适的配置。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-02-19-mian-shi-bi-wen-acid-ni-zhen-de-dong-le-ma/004-7797e2c0.png"&gt;&lt;/p&gt;
&lt;p&gt;最后，虽然 Binlog (二进制日志) 的主要用途是用于数据库的主从复制和时间点恢复，但它也间接地为数据持久性做出了贡献。 Binlog 记录了数据库中所有的数据变更操作 (逻辑操作，例如 SQL 语句)，这些日志可以用于数据库的备份和恢复，特别是当需要进行全量或增量备份，或者需要恢复到某个特定的时间点时，Binlog 就显得至关重要。 虽然 Binlog 的关注点和 Redo Log 略有不同 (Redo Log 侧重于崩溃恢复，Binlog 侧重于时间点恢复和复制)，但它们都为确保数据的长期安全性和可恢复性提供了重要的支持。&lt;/p&gt;
&lt;p&gt;总结来说：&lt;strong&gt;MySQL InnoDB 通过 Redo Log + WAL 策略 保障事务提交的修改能够被可靠地记录下来， Doublewrite Buffer 增强了数据页写入的可靠性，Flush to Disk 机制 提供了灵活的刷盘控制，而 Binlog 则从更广泛的层面支持数据备份和时间点恢复&lt;/strong&gt;。 这些机制相互配合，共同构建了持久性保障体系。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="四consistency一致性"&gt;&lt;a href="#%e5%9b%9bconsistency%e4%b8%80%e8%87%b4%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;四、Consistency（一致性）
&lt;/h3&gt;&lt;p&gt;事务必须保证数据库从一个一致性状态转变到另一个一致性状态。一致性是指数据库的完整性约束没有被破坏。例如，主键唯一性、外键约束、CHECK 约束等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;约束 (Constraints): MySQL 支持各种约束，如主键 (PRIMARY KEY)、外键 (FOREIGN KEY)、唯一键 (UNIQUE)、非空 (NOT NULL)、检查约束 (CHECK) 等。这些约束在数据写入时被强制执行，确保数据满足预定义的规则。&lt;/li&gt;
&lt;li&gt;触发器 (Triggers): 触发器是与表关联的存储程序，在特定事件 (如 INSERT、UPDATE、DELETE) 发生时自动执行。触发器可以用于实现更复杂的业务规则和一致性检查。&lt;/li&gt;
&lt;li&gt;应用程序逻辑: 虽然 MySQL 提供了约束和触发器，但最终的数据一致性也需要应用程序逻辑来保证。例如，业务逻辑需要确保事务操作符合业务规则，才能维持数据库的一致性状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其实对于一致性来说，&lt;strong&gt;它是其他三者（原子性、隔离性、持久性）的综合结果，辅以数据库约束和应用校验来共同保障最终一致性。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;总结&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;MySQL通过以下核心机制实现ACID：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;ACID特性&lt;/th&gt;
 &lt;th&gt;核心机制&lt;/th&gt;
 &lt;th&gt;关键组件&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;原子性&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Undo Log + 事务状态管理&lt;/td&gt;
 &lt;td&gt;Undo Log、事务控制块&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;一致性&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;约束 + ACID协同&lt;/td&gt;
 &lt;td&gt;主键、外键、触发器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;隔离性&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;MVCC + 锁 + Next-Key Locks&lt;/td&gt;
 &lt;td&gt;Read View、行锁、间隙锁&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;持久性&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Redo Log + Doublewrite Buffer&lt;/td&gt;
 &lt;td&gt;Redo Log、双写缓冲区&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;我们经常说的&lt;strong&gt;最终一致性&lt;/strong&gt;是其他三个特性协同作用的结果，而非独立机制。当你理解了这些底层原理将会有助于优化事务设计（如合理选择隔离级别）和故障排查（如分析锁冲突）。&lt;/p&gt;</description></item><item><title>面试时的那些潜台词</title><link>https://xiaobox.github.io/p/2024-11-21-mian-shi-shi-de-na-xie-qian-tai-ci/</link><pubDate>Thu, 21 Nov 2024 08:27:34 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-11-21-mian-shi-shi-de-na-xie-qian-tai-ci/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-21-mian-shi-shi-de-na-xie-qian-tai-ci/cover.jpg" alt="Featured image of post 面试时的那些潜台词" /&gt;&lt;p&gt;其实从你与 HR 的第一次沟通开始就已经处在面试周期了，而不是约了正式面试才开始。这期间对方问的每一个问题，说的每一句话都有他的目的，这个我想大家都知道。&lt;/p&gt;
&lt;p&gt;本文我特想说说面试中那些我知道的和我不太确定的面试 “潜台词”&lt;/p&gt;
&lt;h2 id="hr-篇"&gt;&lt;a href="#hr-%e7%af%87" class="header-anchor"&gt;&lt;/a&gt;HR 篇
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;第一种是双方都明白的，只是说法不一样，含蓄的表达或意会的，目的是给双方留面子，都是社会人没必要说的太直白。&lt;/li&gt;
&lt;li&gt;第二种是直接问的，目的是高效过滤简历&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两种常见于跟 HR 的初次沟通中。&lt;/p&gt;
&lt;h3 id="直接问的"&gt;&lt;a href="#%e7%9b%b4%e6%8e%a5%e9%97%ae%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;直接问的
&lt;/h3&gt;&lt;p&gt;比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学历 （“是全日制本科吗？” 、“是 985 211 院校吗？”）&lt;/li&gt;
&lt;li&gt;在职状态 （“目前离职了吗？”）&lt;/li&gt;
&lt;li&gt;离职原因 （“离职的原因是什么？”）&lt;/li&gt;
&lt;li&gt;婚育状态 （“您成家了吗？”）&lt;/li&gt;
&lt;li&gt;期望薪资 (“您的期望薪资是多少？”)&lt;/li&gt;
&lt;li&gt;目前薪资 (“您目前的薪资是多少？”)&lt;/li&gt;
&lt;li&gt;公司位置 （“我们公司在 xxx ，这个位置您对接受吗？”）&lt;/li&gt;
&lt;li&gt;工作性质 （“我们公司是外派到 xxx 的项目，您接受外派吗？”）&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你看得出来，这些直接问的都是一些基本情况，能问你就证明你的简历基本上入人家眼了，不然都不会浪费时间问。&lt;strong&gt;这里我要强调的是 HR 问你问题，你也要问他们，什么社保、公积金基数、总包多少，有没有年终奖，该问问，别不好意思，出来打工不是图钱，难道为爱发电吗？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;尤其对于刚入职场的小白来说，别不好意思，不然坑的就是你，因为有的公司会故意隐瞒一些坑，到时候坑的是自己。另外，初步沟通以后，立即去 “脉脉”、 “天眼查” 、“企查查” 这些平台查询公司相关信息，这很重要，&lt;strong&gt;从真实的员工那儿知道公司的情况（有没有裁员、有没有欠薪、有没有加班、有没有别的坑），从工商信息那儿查看公司的规模、投融资情况、有没有劳动争议官司、什么时候成立的、给多少人交着社保、业务范围什么的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这几个平台你看一圈儿以后心里就对将要面试的公司有个基本的判断了，如果你判断出一些问题，或者你比较犹豫，作为过来人我告诉你，不必浪费时间，直接 Pass 是最优解。&lt;/p&gt;
&lt;h3 id="不直接问的"&gt;&lt;a href="#%e4%b8%8d%e7%9b%b4%e6%8e%a5%e9%97%ae%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;不直接问的
&lt;/h3&gt;&lt;p&gt;到这里我们就要扣题了，因为潜台词要出现了。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;**您住在哪里呀？**潜台词：根据你住的远近，判断你对公司的意向是否强烈。如果你住的比较远很可能会有第二个问题：“&lt;strong&gt;您是在那里买房了吗？&lt;/strong&gt;”，潜台词是：首先判断你是租房还是买房，如果买房通勤太远你的意向就可能会有问题，如果租房还好。其次判断你的经济实力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当你约好了面试，去现场时，HR 接你的时候可能会问 ：&lt;strong&gt;“您是怎么过来的呀？过来远吗？”&lt;/strong&gt; 潜台词是：首先跟第一点类似，根据你到公司的距离判断你的意向，其次判断你的经济实力，因为你可能是开车来的，也可能是坐公交地铁来的。可能你会问面个试为什么要判断经济实力？当然是为了拿捏你了，总之软肋越少越不好被拿捏。出来混，面子是自己给的，该怎么说，不用我教你吧。哈哈&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你做过技术管理工作可能会被问：&lt;strong&gt;“您在最近 2 年的工作中，技术和管理的时间占比是多少？”&lt;/strong&gt; 潜台词：他们想找个带过人，但一直在一线写代码的。不过这个占比如何回答好，我还真拿不准，因为管理的团队越大越会多花精力和时间，不可能我只用一成的时间管理个 50 人的团队，然后再用 9 成的时间写代码，这太极端了，要么会管理的太差，要么就是管理的太好。说白了，他们是想要个纯写代码的牛马，你判断是否愿意干就知道怎么回答了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;hellip;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="不回答或晚回答"&gt;&lt;a href="#%e4%b8%8d%e5%9b%9e%e7%ad%94%e6%88%96%e6%99%9a%e5%9b%9e%e7%ad%94" class="header-anchor"&gt;&lt;/a&gt;不回答或晚回答
&lt;/h3&gt;&lt;p&gt;这种一般出现在你面试结束后长时间没有消息，面的好的话，一般情况下会比较快的通知你下面的流程。这种情况无论 HR 怎么说，大概率是以下两种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;面的不好，但那边懒得理你了&lt;/li&gt;
&lt;li&gt;面的还行，但那边在犹豫，至于犹豫的点是什么你不用关心（那可多了），是不是有备胎什么的你也不用多想，总之是犹豫。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，不回答或晚回答的话，因为有可能还有机会，如果确实挺在乎结果，强迫症想有个闭环的，是可以直接问 HR 的，多问没关系的，成了将来是同事，多打打交道不好吗？不成了以后谁认识谁呀，别有心理压力。&lt;/p&gt;
&lt;p&gt;还有，在不回答或晚回答的情况下，HR 有可能会用话术拖着你，别太当真，别太上心，别给他脸，该干啥干啥，心态渣一点儿，没坏处。&lt;/p&gt;
&lt;h3 id="女性问题"&gt;&lt;a href="#%e5%a5%b3%e6%80%a7%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;女性问题
&lt;/h3&gt;&lt;p&gt;我是男的，但我知道这个职场对于女性不那么友好，女性可能会面临更多的 “问题”，而有些问题我都没法用潜台词来形容，那是赤果果的歧视。这里我就不展开了，我想说的是职场女性很不容易，而很多 HR 也是女性，&lt;strong&gt;希望更多的 “人事” 干点儿 人事儿！&lt;/strong&gt; ，不然多荒诞啊。&lt;/p&gt;
&lt;h3 id="古典派"&gt;&lt;a href="#%e5%8f%a4%e5%85%b8%e6%b4%be" class="header-anchor"&gt;&lt;/a&gt;古典派
&lt;/h3&gt;&lt;p&gt;在面试前就问你很多过去经历、每段工作的离职原因、职业规划等的 HR 都比较古典，问的太多啦，他们无非还是想判断你的 &lt;strong&gt;稳定性&lt;/strong&gt; 看看是不是一只合格的牛马而已。但这种现在也很少了，效率越高的公司越没有这种古典派的，都是激进派的，甚至直接让用人部门自己约人面试，他们最后只负责谈钱。&lt;/p&gt;
&lt;h3 id="谈钱"&gt;&lt;a href="#%e8%b0%88%e9%92%b1" class="header-anchor"&gt;&lt;/a&gt;谈钱
&lt;/h3&gt;&lt;p&gt;到了谈钱这步，说明前面的面试你都顺利通过了，这是最后一关，但谈崩的可能性也是很大的。&lt;/p&gt;
&lt;p&gt;这里要注意的是：无论拿你的过去说你什么，都是一个目的：压价。很多人明白，但我想提醒大家一点的是：&lt;strong&gt;不要认真对待别人带有目的性的否定。&lt;/strong&gt; 你是怎么样的你要客观地评价自己，不能只通过一个 HR、一次面试、一个公司就决定了。再说了，人是会在发展中变化的，也许 2 年以后的你他们根本就高攀不起。&lt;/p&gt;
&lt;p&gt;遇到那种谈钱压价不痛快的是挺糟心的，但遇到那种痛快的，也要留心。痛快也是有原因的，可能的情况有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上一个人刚走，得赶紧找人填坑或背锅&lt;/li&gt;
&lt;li&gt;你要少了，怕你反悔，赶紧定&lt;/li&gt;
&lt;li&gt;说的数是假的，骗你的，比如压根没有年终奖，然后承诺你有三个月&lt;/li&gt;
&lt;li&gt;你太合适了，赶紧定你，怕你不选他家（这种看对眼的情况也是有的，但不多）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="技术篇"&gt;&lt;a href="#%e6%8a%80%e6%9c%af%e7%af%87" class="header-anchor"&gt;&lt;/a&gt;技术篇
&lt;/h2&gt;&lt;p&gt;有关技术面试的问题就很多了，虽然比较专业但仍然有许多潜台词，挑着说几个。&lt;/p&gt;
&lt;h3 id="算法"&gt;&lt;a href="#%e7%ae%97%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;算法
&lt;/h3&gt;&lt;p&gt;一般不会上来直接考查算法，现在有许多公司把这个当做面试流程的一个必要环节了。一般情况下不会故意难为候选人，但如果出现比较罕见的题目或者 hard ，那么潜台词是：“&lt;strong&gt;劝退&lt;/strong&gt;” 他可能已经不想要你了，&lt;strong&gt;但他需要一个非常合理的理由，出一个很难的题，你答不上来，看起来就很合理。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="经历"&gt;&lt;a href="#%e7%bb%8f%e5%8e%86" class="header-anchor"&gt;&lt;/a&gt;经历
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;以往工作经历中解决的最困难的问题是什么 ？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个问题其实是一个信号，你只要记住，听到这个问题开始吹牛逼就行了。&lt;/p&gt;
&lt;p&gt;为什么 ？就不能实话实说吗？&lt;/p&gt;
&lt;p&gt;因为，他们期待听到的，就是吹牛逼的内容。&lt;/p&gt;
&lt;p&gt;一般来说，只要问题解决了，你就不觉得它有多困难了，谦虚的程序员们也不觉得是解决了多难的问题，所谓 “难者不会，会者不难”。而且，这个行业有那么多的从业者，有多少人真正接触过非常困难和有技术含量的问题呢？就算是参与过也不是一两个人的功劳，那是团队的功劳，再说了，如果问题只有你能解决，别人解决不了，或接触不到，那么公司一定不愿意看到这种情况出现，他们想的是每个岗位的人都不能有不可替代性，这样才好拿捏你。&lt;/p&gt;
&lt;p&gt;当然了，吹牛逼也要有技巧，&lt;strong&gt;结合你的工作经历把故事说圆基本就及格了。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="八股"&gt;&lt;a href="#%e5%85%ab%e8%82%a1" class="header-anchor"&gt;&lt;/a&gt;八股
&lt;/h3&gt;&lt;p&gt;就我观察，好的面试官问八股的趋势是越来越少了。一般是为了暖场或者为一些其他问题做准备而铺垫，所以如果你听到那种没有后续的比较纯的八股，那么很有可能这个面试官比较水，进而可以判断出这个公司可能也比较水。&lt;/p&gt;
&lt;p&gt;然而还有更水的，在八股上跟你特别较真的，这种你就要小心了，因为回答对或者错都很正常，每个人都有知识盲区，正常的面试官听到答案心里有数就行了，一般不会跟你较真。这种在八股上特别较真的，很有可能不会面试，也问不出什么好问题，公司派这么个人当面试官，你猜这个公司怎么样？&lt;/p&gt;
&lt;p&gt;补充一下，我说的较真不是说，你回答错了，他纠正你，那叫点拨，你应该感谢人家。我说的较真是他可能只知道问题的一种解决方案，但却一直否定你给的其他方案。或者他根本就是说错了，但一直坚持自己是对的。再或者，他是对的，但却以别人不知道的八股为荣，进而表示出瞧不起你的态度。注意，我们这里讨论的是八股， 面试官和候选人之间的交流，达自己的目的就可以了，这破玩意儿有什么可较真的呢？同样，你如果碰到这样的面试官，心里要清楚你的目的是什么，没必要跟这种人起什么争执。面的不好，也不要放在心上，换个公司再来一次就好了。&lt;/p&gt;
&lt;h3 id="聊聊项目"&gt;&lt;a href="#%e8%81%8a%e8%81%8a%e9%a1%b9%e7%9b%ae" class="header-anchor"&gt;&lt;/a&gt;聊聊项目
&lt;/h3&gt;&lt;p&gt;一般会根据你简历中写的项目经验让你描述一下做的具体工作。这里面其实有个潜台词，就是：&lt;strong&gt;你不能说的太宏观，也不能讲的太微观&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;什么意思呢？&lt;/p&gt;
&lt;p&gt;如果你做过架构师或技术总监这种全局类的工作，很有可能会从全局角度把项目描述得比较抽象和简单，这不是说你说的不对，而是说你要照顾一下人家想听什么，他们想听什么呢？说白了就是：你到底是不是真的做过，跟他们的工作内容是否贴合，当然能力越强越好，最好解决了他们遇到且没解决过的问题。那怎么体现呢？两个字：“细节” 。在你宏观伟大的项目描述中适当加入一些细节和一些一下子就能把对方拉进具体场景的关键词，这样显得既高大上又细节满满。这样的回答就比较稳了。不会让人觉得只是夸夸其谈。&lt;/p&gt;
&lt;p&gt;如果你平常做的比较多的是具体的执行工作，比如按照需求研发功能什么的，那么你要讲项目时要注意适当的拔高一些，在细节工作基础之上稍微上上价值，上升到架构、设计、解决方案的角度 ，具体怎么说要结合你的具体情况。这样就会觉得你做的比较有价值，而且还显得比较有深度、有思考、有潜力。&lt;/p&gt;
&lt;h2 id="其他"&gt;&lt;a href="#%e5%85%b6%e4%bb%96" class="header-anchor"&gt;&lt;/a&gt;其他
&lt;/h2&gt;&lt;p&gt;其实有关面试还有很多好玩儿的事情，和背后可能蕴含的潜台词，等我找个机会再跟大家唠唠 ，今天先这样，哈哈。如果关于这个话题你也有想分享的可以在评论区跟大家分享分享，可能你的一句话会帮助到很多朋友。&lt;/p&gt;</description></item><item><title>程序员必备：最直观的数据结构图文手册</title><link>https://xiaobox.github.io/p/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/</link><pubDate>Tue, 19 Nov 2024 03:47:23 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/cover.jpg" alt="Featured image of post 程序员必备：最直观的数据结构图文手册" /&gt;&lt;p&gt;这篇文章为了方便以可视化的方式回顾那些最常用的数据结构，你可以用它做面试准备时的复习。希望这些可视化例子能够帮助大家了解这些数据结构。&lt;/p&gt;
&lt;h2 id="大-o-时间复杂度"&gt;&lt;a href="#%e5%a4%a7-o-%e6%97%b6%e9%97%b4%e5%a4%8d%e6%9d%82%e5%ba%a6" class="header-anchor"&gt;&lt;/a&gt;大 O &amp;ndash;时间复杂度
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/001-16aa0e79.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么大 O 复杂度很重要 ？&lt;/strong&gt;：对于小数据集，算法复杂度可能不会扮演非常重要的角色，但随着我们的数据量增大——算法的性能影响对响应时间有极大的影响。因此，关注复杂度在具有合理规模的任何应用领域中对于程序质量都起着至关重要的作用。&lt;/p&gt;
&lt;p&gt;举个例子：&lt;/p&gt;
&lt;p&gt;假设我们的数据集有 100 万（1,000,000）个元素&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;O(1)&lt;/code&gt;算法将进行 1 次操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;O(log(n))&lt;/code&gt;算法将进行 20 次操作&lt;/li&gt;
&lt;li&gt;&lt;code&gt;O(n)&lt;/code&gt;算法将进行 1000000 次操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;O(n * log(n))&lt;/code&gt;算法将进行 2000 万次操作。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;O(n 2 )&lt;/code&gt;算法将进行 1 万亿次（1,000,000,000,000）操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，你应该能看出算法复杂度的重要性。&lt;/p&gt;
&lt;h2 id="rum-权衡"&gt;&lt;a href="#rum-%e6%9d%83%e8%a1%a1" class="header-anchor"&gt;&lt;/a&gt;RUM 权衡
&lt;/h2&gt;&lt;p&gt;另一个在选择数据结构时需要注意的重要方面是 RUM 权衡&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读取效率（R）：从数据结构中检索或访问数据有多快。&lt;/li&gt;
&lt;li&gt;更新效率（U）：在数据结构中插入、删除或修改数据有多快。&lt;/li&gt;
&lt;li&gt;内存效率（M）：数据结构使用的内存或空间大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/002-3c8c7427.png"&gt;&lt;/p&gt;
&lt;h2 id="那些最常用且重要的数据结构"&gt;&lt;a href="#%e9%82%a3%e4%ba%9b%e6%9c%80%e5%b8%b8%e7%94%a8%e4%b8%94%e9%87%8d%e8%a6%81%e7%9a%84%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;那些最常用且重要的数据结构
&lt;/h2&gt;&lt;h3 id="数组--链表"&gt;&lt;a href="#%e6%95%b0%e7%bb%84--%e9%93%be%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;数组 &amp;amp; 链表
&lt;/h3&gt;&lt;p&gt;数组在内存中连续存储，以快速查找而闻名，但更新/写入时间较慢；而链表非连续存储，以快速更新/写入而闻名，但查找较慢&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/003-95dddf47.png"&gt;&lt;/p&gt;
&lt;h3 id="队列"&gt;&lt;a href="#%e9%98%9f%e5%88%97" class="header-anchor"&gt;&lt;/a&gt;队列
&lt;/h3&gt;&lt;p&gt;线性数据结构，遵循先进先出（FIFO）原则：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/004-8f0f7eb3.png"&gt;&lt;/p&gt;
&lt;h3 id="堆栈"&gt;&lt;a href="#%e5%a0%86%e6%a0%88" class="header-anchor"&gt;&lt;/a&gt;堆栈
&lt;/h3&gt;&lt;p&gt;遵循后进先出（LIFO）原则，元素从顶部添加和移除：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/005-026b9a2b.png"&gt;&lt;/p&gt;
&lt;h3 id="哈希表"&gt;&lt;a href="#%e5%93%88%e5%b8%8c%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;哈希表
&lt;/h3&gt;&lt;p&gt;提供几乎即时的元素访问，通过使用哈希函数创建键值对来实现。插入、删除和查找的时间复杂度为 O(1)，代价是内存利用率&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/006-e236353a.png"&gt;&lt;/p&gt;
&lt;h2 id="树形数据结构"&gt;&lt;a href="#%e6%a0%91%e5%bd%a2%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;树形数据结构
&lt;/h2&gt;&lt;p&gt;树形数据结构常用于数据密集型应用。&lt;/p&gt;
&lt;p&gt;在列出所有数据结构之前，我们首先需要回顾一个在树结构中起着关键作用的算法——二分查找算法。&lt;/p&gt;
&lt;h3 id="二分查找"&gt;&lt;a href="#%e4%ba%8c%e5%88%86%e6%9f%a5%e6%89%be" class="header-anchor"&gt;&lt;/a&gt;二分查找
&lt;/h3&gt;&lt;p&gt;这是一个对排序元素的搜索，通过不断将搜索空间分成一半来完成。就像在字典中找到中间页面，检查我们的搜索词是在字典的左半部分还是右半部分，然后不断重复这个过程，直到找到元素。使用高效的 O(log(n)) 查找&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/007-7943b840.png"&gt;&lt;/p&gt;
&lt;h3 id="二叉搜索树"&gt;&lt;a href="#%e4%ba%8c%e5%8f%89%e6%90%9c%e7%b4%a2%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;二叉搜索树
&lt;/h3&gt;&lt;p&gt;一棵二叉树，其中每个节点最多有两个子节点，且左子节点的值小于父节点，右子节点的值大于父节点。如果二叉搜索树平衡（位于左边的节点数量不超过右边的节点数量很多），则可以进行高效的 O(log(n)) 查找。这是因为我们在遍历树时（从父节点到子节点）将搜索空间减半&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/008-c33cbbdc.png"&gt;&lt;/p&gt;
&lt;h3 id="红黑树"&gt;&lt;a href="#%e7%ba%a2%e9%bb%91%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;红黑树
&lt;/h3&gt;&lt;p&gt;二叉搜索树通过将节点分配颜色（红色或黑色）并遵循一组确保其保持平衡的规则来维护其平衡：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/009-b78ecec1.png"&gt;&lt;/p&gt;
&lt;h3 id="avl-树"&gt;&lt;a href="#avl-%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;AVL 树
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;AVL 树的全称是 Adelson-Velsky and Landis Tree，以其发明者 G. M. Adelson-Velsky 和 E. M. Landis 的名字命名。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;自平衡二叉搜索树，通过确保其平衡因子（所有左子树和右子树之间的高度差）至多为 1 来实现平衡。在插入和删除操作期间通过旋转自动重新平衡&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/010-2c19daa3.png"&gt;&lt;/p&gt;
&lt;h3 id="堆"&gt;&lt;a href="#%e5%a0%86" class="header-anchor"&gt;&lt;/a&gt;堆
&lt;/h3&gt;&lt;p&gt;树中每个父节点要么大于等于（最大堆）其子节点，要么小于等于（最小堆）其子节点。允许高效检索最小或最大值，常用于实现优先队列。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/011-95c0e4d2.png"&gt;&lt;/p&gt;
&lt;h3 id="跳表"&gt;&lt;a href="#%e8%b7%b3%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;跳表
&lt;/h3&gt;&lt;p&gt;跳表（Skip List）是链表的一种扩展结构，通过引入多级链表来加速查找、插入和删除操作。它的工作原理是允许通过“跳跃”多个链表元素来快速定位到目标节点，这通常是通过从父级链表向下遍历到合适的子级链表来实现的。这种结构类似于二叉搜索树，但具有一定的随机性，通常在效率和简单性上都有不错的表现。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/012-7eaa63ee.png"&gt;&lt;/p&gt;
&lt;h3 id="b-树"&gt;&lt;a href="#b-%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;B+ 树
&lt;/h3&gt;&lt;p&gt;常用于数据库存储。B+树是一种平衡树，其中所有数据都存储在叶节点中，这些叶节点按顺序链接在一起，以便快速顺序访问：&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/013-68f325e6.png"&gt;&lt;/p&gt;
&lt;h3 id="lsmlog-structured-merge树"&gt;&lt;a href="#lsmlog-structured-merge%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;LSM（Log-Structured Merge）树
&lt;/h3&gt;&lt;p&gt;数据应用中常用的写优化树，为了理解 LSM 树，我们需要熟悉另外两种数据结构：Memtables（内存表）和 SSTables（排序字符串表）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memtable&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最初，数据被写入一个称为 Memtable 的内存结构中。这个 Memtable 在内存中保存数据，直到达到一定大小，通常通过使用平衡搜索树（如红黑树）、跳表或哈希表来实现，以提供高效的读取访问。当 Memtable 满时，其内容会被写入磁盘作为新的 SSTable。这个过程称为刷新。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/014-9ba50bba.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SSTable&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SSTables 根据键的顺序存储数据。每个 SSTable 由一系列键值对组成，其中键是有序的。一旦创建 SSTable，它就不会被修改。相反，新的数据更新会写入新的 SSTable。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/015-244ce596.png"&gt;&lt;/p&gt;
&lt;p&gt;SSTables 通常使用 Bloom 过滤器、稀疏索引等辅助数据结构来快速确定键是否存在于 SSTable 中或定位值。&lt;/p&gt;
&lt;p&gt;随着时间的推移，由于频繁更新，可能会创建多个 SSTables。为了优化性能和回收空间，SSTables 会定期合并和压缩。这涉及到将多个 SSTables 中的数据合并成更少的新的 SSTables，同时丢弃过时的条目。&lt;/p&gt;
&lt;p&gt;下图是 LSM treee 的一个完整结构：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/016-708d5b75.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：LSM-tree 不是一种数据结构，是数据组织的一种方式&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="二叉索引树斐波那契树"&gt;&lt;a href="#%e4%ba%8c%e5%8f%89%e7%b4%a2%e5%bc%95%e6%a0%91%e6%96%90%e6%b3%a2%e9%82%a3%e5%a5%91%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;二叉索引树/斐波那契树
&lt;/h3&gt;&lt;p&gt;一种紧凑且高效的数据结构，用于处理动态累积频率表或前缀和。换句话说，它非常适合用于区间查询。&lt;/p&gt;
&lt;p&gt;树结构存储在一个数组中，其中数组中每个 2 的幂次方索引位置保存其之前所有元素的累积和。举个例子，第 4 个元素（值为 22）存储的是前 4 个元素的和。为了获取树中每个区间的子数组和，我们使用位移操作，使得每次更新和读取的时间复杂度减少到对数级别 O(log(n))，从而提高区间查询的效率。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/017-39f41184.png"&gt;&lt;/p&gt;
&lt;h2 id="图数据结构"&gt;&lt;a href="#%e5%9b%be%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;图数据结构
&lt;/h2&gt;&lt;h3 id="邻接表与邻接矩阵图表示"&gt;&lt;a href="#%e9%82%bb%e6%8e%a5%e8%a1%a8%e4%b8%8e%e9%82%bb%e6%8e%a5%e7%9f%a9%e9%98%b5%e5%9b%be%e8%a1%a8%e7%a4%ba" class="header-anchor"&gt;&lt;/a&gt;邻接表与邻接矩阵图表示
&lt;/h3&gt;&lt;p&gt;一个邻接表将图表示为一系列列表的集合——每个节点都有一个与之相连的节点集合。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/018-65d2c4e4.png"&gt;&lt;/p&gt;
&lt;p&gt;邻接矩阵将图表示为一个二维矩阵。如果我们的图有 N 个节点，我们将有一个 N×N 的矩阵，其中每个单元格(i, j)表示顶点 i 和顶点 j 之间是否存在边。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/019-bf45bf53.png"&gt;&lt;/p&gt;
&lt;p&gt;示例图:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/020-21a98545.png"&gt;&lt;/p&gt;
&lt;h2 id="字符串搜索数据结构"&gt;&lt;a href="#%e5%ad%97%e7%ac%a6%e4%b8%b2%e6%90%9c%e7%b4%a2%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;字符串搜索数据结构
&lt;/h2&gt;&lt;h3 id="trie字典树"&gt;&lt;a href="#trie%e5%ad%97%e5%85%b8%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;Trie（字典树）
&lt;/h3&gt;&lt;p&gt;trie 是一种树形数据结构，用于高效地搜索字符串，其中每个节点代表一个字符，具有包括快速基于前缀的查询和插入等优势。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/021-8f5978f6.png"&gt;&lt;/p&gt;
&lt;h3 id="radix-tree"&gt;&lt;a href="#radix-tree" class="header-anchor"&gt;&lt;/a&gt;Radix Tree
&lt;/h3&gt;&lt;p&gt;它也可以被视为一个紧凑的 Trie。尽管 Trie 很棒，但它们可能会占用大量内存。Redix 树通过合并具有公共前缀的节点来解决这个问题&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/022-aa66d395.png"&gt;&lt;/p&gt;
&lt;h3 id="splay-tree"&gt;&lt;a href="#splay-tree" class="header-anchor"&gt;&lt;/a&gt;Splay Tree
&lt;/h3&gt;&lt;p&gt;伸展树是一种在数据访问频率不均时具有优秀性能的二叉搜索树。树在查找、插入和删除操作后自动调整。在树中访问一个项目后，树会重新排列，使访问的项目移动到顶部（根）。这使得对该项目的未来访问更快。伸展树在缓存中特别有用。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/023-253c3179.png"&gt;&lt;/p&gt;
&lt;h3 id="quadtree"&gt;&lt;a href="#quadtree" class="header-anchor"&gt;&lt;/a&gt;Quadtree
&lt;/h3&gt;&lt;p&gt;四叉树是一种空间数据结构，它递归地将二维空间划分为四个象限，使其在管理和查询如点或区域等空间数据时非常高效。如果一个节点包含太多点，它将被细分为四个子节点。四叉树通常用于处理碰撞检测。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/024-bb082c42.png"&gt;&lt;/p&gt;
&lt;h3 id="kd-tree"&gt;&lt;a href="#kd-tree" class="header-anchor"&gt;&lt;/a&gt;KD Tree
&lt;/h3&gt;&lt;p&gt;一棵二叉树，其中每个节点代表 k 维空间中的一个点。该树通过递归地在其中一个维度上分割空间来构建。在树的每一层，数据根据一个维度进行分割，后续的每一层交替使用维度。这使得它在范围查询和最近邻搜索中非常高效。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/025-08a899b6.png"&gt;&lt;/p&gt;
&lt;h3 id="r-tree"&gt;&lt;a href="#r-tree" class="header-anchor"&gt;&lt;/a&gt;R-Tree
&lt;/h3&gt;&lt;p&gt;R 树是一种用于高效索引多维空间数据的树形数据结构。它们将数据组织成最小边界矩形（MBR），这些矩形按层次分组，每个节点的 MBR 包含其子节点的 MBR。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/026-f0eb8de6.png"&gt;&lt;/p&gt;
&lt;h2 id="其他数据结构及图"&gt;&lt;a href="#%e5%85%b6%e4%bb%96%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84%e5%8f%8a%e5%9b%be" class="header-anchor"&gt;&lt;/a&gt;其他数据结构及图
&lt;/h2&gt;&lt;h3 id="布隆过滤器"&gt;&lt;a href="#%e5%b8%83%e9%9a%86%e8%bf%87%e6%bb%a4%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;布隆过滤器
&lt;/h3&gt;&lt;p&gt;布隆过滤器是一种空间高效的概率数据结构，用于测试一个元素是否是集合的成员，通常用于减少对不存在的键的昂贵磁盘（或网络）查找。它可以产生假阳性（报告元素在集合中而实际上不在），但永远不会产生假阴性（如果元素实际上在集合中，它永远不会错误地报告元素不在集合中）。&lt;/p&gt;
&lt;p&gt;它使用位数组来存储数据。为了将一个键映射到适当的位，它使用多个独立的哈希函数，每个函数将键映射到位数组中的不同位位置。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/027-513b7c78.png"&gt;&lt;/p&gt;
&lt;h3 id="二叉堆"&gt;&lt;a href="#%e4%ba%8c%e5%8f%89%e5%a0%86" class="header-anchor"&gt;&lt;/a&gt;二叉堆
&lt;/h3&gt;&lt;p&gt;二叉堆是一种高效管理元素集合的数据结构，支持快速插入、最小元素提取和堆合并。当需要处理频繁执行这些操作的动态元素集合时，它特别有用&lt;/p&gt;
&lt;p&gt;二叉堆由一系列二叉树组成，这些树是相互链接的特殊树&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二项式树（0 至 3 阶）：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/028-02e2da9e.png"&gt;&lt;/p&gt;
&lt;p&gt;每个堆中的二叉树都遵循最小堆属性：节点的键值大于或等于其父节点的键值。此外，每个顺序只能有一个或零个二叉树，包括零阶。&lt;/p&gt;
&lt;p&gt;以下示例&lt;strong&gt;二叉堆包含 13 个节点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/029-b64ced71.png"&gt;&lt;/p&gt;
&lt;p&gt;二叉堆在实现优先队列等场景中很有用，在这些场景中，需要频繁地合并堆或对一组元素执行其他操作。&lt;/p&gt;
&lt;h3 id="hash-array-mapped-trie-hamt"&gt;&lt;a href="#hash-array-mapped-trie-hamt" class="header-anchor"&gt;&lt;/a&gt;Hash Array Mapped Trie (HAMT)
&lt;/h3&gt;&lt;p&gt;HAMT 是一种结合了哈希表和 Trie 的优点，用于高效存储和检索键值对的数据结构。它在计算机科学中常用于实现关联数组或字典。在 HAMT 中，键被哈希以确定其在数组中的存储位置，该数组称为哈希数组。哈希数组中的每个条目可以存储多个键值对，从而实现高效的内存利用。如果多个键哈希到相同的数组索引，则使用类似 Trie 的结构来解决冲突。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/030-0371cb4b.png"&gt;&lt;/p&gt;
&lt;h3 id="merkle-tree"&gt;&lt;a href="#merkle-tree" class="header-anchor"&gt;&lt;/a&gt;Merkle Tree
&lt;/h3&gt;&lt;p&gt;帮助高效、安全地验证大量数据。它通过将数据组织成树状结构，其中每个叶子节点包含数据块的哈希值，每个非叶子节点是其两个子节点的哈希值，一直向上到顶部的 Merkle 根。这种结构在区块链和其他系统中被广泛使用，以确保数据完整性。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/031-8ee0b831.png"&gt;&lt;/p&gt;
&lt;h2 id="最后8-个数据库中常用的数据结构"&gt;&lt;a href="#%e6%9c%80%e5%90%8e8-%e4%b8%aa%e6%95%b0%e6%8d%ae%e5%ba%93%e4%b8%ad%e5%b8%b8%e7%94%a8%e7%9a%84%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;最后：8 个数据库中常用的数据结构
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-11-19-cheng-xu-yuan-bi-bei-zui-zhi-guan-de-shu-ju-jie-gou-tu-wen-s/032-2674b76e.png"&gt;&lt;/p&gt;</description></item><item><title>自己工资 14K，找到月薪 28K工作后，开心地提交辞呈，租了新房子！入职前一天HR说：原来岗位的人不走了，你offer被取消了</title><link>https://xiaobox.github.io/p/2024-09-19-zi-ji-gong-zi-14k-zhao-dao-yue-xin-28k-gong-zuo-hou-kai-xin/</link><pubDate>Thu, 19 Sep 2024 13:14:56 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-09-19-zi-ji-gong-zi-14k-zhao-dao-yue-xin-28k-gong-zuo-hou-kai-xin/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-09-19-zi-ji-gong-zi-14k-zhao-dao-yue-xin-28k-gong-zuo-hou-kai-xin-/cover.jpg" alt="Featured image of post 自己工资 14K，找到月薪 28K工作后，开心地提交辞呈，租了新房子！入职前一天HR说：原来岗位的人不走了，你offer被取消了" /&gt;&lt;p&gt;昨天，我刷到一条让人心酸又无奈的职场动态。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;小李是一名刚入职场两年的新人，目前在一家小公司做程序员，月薪14000元。虽然工资不高，但胜在工作轻松，离家也近。前段时间，小李在某招聘网站上看到一家知名企业招聘高级开发工程师，工资开到了28000元。他兴奋地投了简历，很快就接到了面试通知。经过两轮面试，小李顺利拿到了offer。他激动地跟现在的公司提交了辞呈，还跟朋友们庆祝了一番。为了配得上新工作的身份，他还租了一套离新公司更近的房子。然而，就在准备入职的前一天晚上，小李突然接到了HR的电话。HR告诉他：&amp;ldquo;很抱歉，原来那个岗位的人临时决定不走了，所以你的offer取消了。小李瞬间懵了，新房已租，旧工作已辞，这下可怎么办？&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;昨天，我刷到一条让人心酸又无奈的职场动态。一位刚入职场两年的小李，原本在一家小公司做程序员，月薪14000元。虽然工资不算高，但工作环境还不错，离家也近。&lt;/p&gt;
&lt;p&gt;前段时间，他在某招聘网站上看到一家知名互联网企业招聘高级开发工程师，工资直接翻倍到28000元。&lt;/p&gt;
&lt;p&gt;小李兴冲冲地投了简历，顺利通过了两轮技术面试和一轮HR面试，拿到了心仪的offer。他激动地向现公司提交了辞呈，还跟朋友们庆祝了一番。为了配得上新工作的身份，他甚至租了一套离新公司更近的高档公寓。&lt;/p&gt;
&lt;p&gt;然而，就在准备入职的前一天晚上，噩耗传来—— HR打电话告诉他，由于公司最新一轮融资出现问题，暂时冻结所有新入职岗位，他的offer取消了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;看到这里，你是不是也替小李捏了一把汗？新房已租，旧工作已辞，眼看就要成为无业程序员，这下该怎么办？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这则职场惨案不禁让人深思：为什么看似美好的机会会突然变成噩梦？我们又该如何避免自己成为下一个&amp;quot;小李&amp;rdquo;？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;说实话，我看完这个故事后，心里五味杂陈。作为一个在IT圈摸爬滚打多年的老码农，我深知这种情况并不罕见。让我们一起来剖析一下，为什么会发生这种令人沮丧的情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;互联网公司风险高，经营状况瞬息万变。有些看似光鲜的大厂，实际上可能正处于动荡期。融资问题、业务调整等都可能导致招聘计划的突然变更。可悲的是，最终受伤的往往是像小李这样的求职者。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;技术人员往往过于关注技术而忽视了职场规则。小李在拿到offer后就立即辞职、租房，这种行为虽然可以理解，但确实有些冒失。在职场中，任何事情都有变数，过早地把鸡蛋放在一个篮子里，风险系数自然就高了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;法律意识淡薄，没有书面保障。很多程序员拿到offer后就觉得万事大吉，殊不知口头offer其实没有任何法律效力。如果公司反悔，求职者往往就只能自认倒霉。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;行业诚信缺失，某些企业不负责任。有些公司为了抢夺人才，会开出很诱人的条件。但当情况有变时，他们却毫不犹豫地把求职者抛在一边，根本不考虑对方的感受和损失。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;求职者自身定位不清，盲目追求大厂高薪。小李原本工作虽然工资不是顶尖，但也有其他优势。为了追求大厂光环和高薪而放弃稳定工作，某种程度上也反映了他对自身价值和职业规划的判断可能存在偏差。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;看到这里，你可能会问：那遇到这种情况该怎么办？作为一个职场老兵，我有以下几点建议：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;冷静应对，及时止损。天无绝人之路，当务之急是调整心态，不要被一时的挫折打倒。立即开始寻找新的工作机会，同时考虑是否可以挽回原来的工作。技术人才的市场需求通常较大，保持信心很重要。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;学会维权，寻求补偿。虽然口头offer没有法律效力，但如果有邮件等书面证据，可以尝试与公司协商，要求一定的经济补偿。即便最后拿不到补偿，也能给不负责任的公司一些压力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;建立职业缓冲带。以后在跳槽时，可以考虑采用&amp;quot;双轨制&amp;quot;：先在新公司试用一段时间，确认稳定后再辞去原工作。这样既能保证收入的连续性，又能降低风险。对于程序员来说，可以考虑先以外包或者兼职的形式进入新公司。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;提高法律意识，要求书面offer。在接受offer时，一定要索要正式的录用函。这不仅是对自己负责，也是对公司诚意的一种考验。特别是对于互联网公司，由于其变数较大，更应该要求有书面保障。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;理性评估，不要被大厂光环和高薪蒙蔽双眼。在考虑新工作时，不要只看工资和公司名气，还要综合考虑技术栈匹配度、团队氛围、公司发展前景等因素。有时候，看似吸引人的大厂高薪工作可能暗藏陷阱。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;职场如代码，每一步都要谨慎。希望小李的经历能给大家敲响警钟。记住，真正的技术高手，不仅要有扎实的编程功底，更要有职场智慧。下次当你收到一份看似诱人的offer时，先别急着庆祝，多问问自己：这份工作靠谱吗？我准备好了吗？&lt;/p&gt;
&lt;p&gt;愿每个程序员都能在这个充满挑战的IT世界里，代码写得好，职场走得稳，找到属于自己的一片天地。&lt;/p&gt;</description></item><item><title>面试不会问的Hystrix实现资源隔离</title><link>https://xiaobox.github.io/p/2022-04-25-mian-shi-bu-hui-wen-de-hystrix-shi-xian-zi-yuan-ge-li/</link><pubDate>Mon, 25 Apr 2022 03:37:31 +0000</pubDate><guid>https://xiaobox.github.io/p/2022-04-25-mian-shi-bu-hui-wen-de-hystrix-shi-xian-zi-yuan-ge-li/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-04-25-mian-shi-bu-hui-wen-de-hystrix-shi-xian-zi-yuan-ge-li/cover.jpg" alt="Featured image of post 面试不会问的Hystrix实现资源隔离" /&gt;&lt;h2 id="资源是什么又为什么要隔离"&gt;&lt;a href="#%e8%b5%84%e6%ba%90%e6%98%af%e4%bb%80%e4%b9%88%e5%8f%88%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e9%9a%94%e7%a6%bb" class="header-anchor"&gt;&lt;/a&gt;资源是什么？又为什么要隔离？
&lt;/h2&gt;&lt;p&gt;我们设想一个这样的场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在某分布式微服务系统中的某个服务 A ，它依赖外部的 B 、C、D 三个服务，通过 RPC 远程调用它们。&lt;/li&gt;
&lt;li&gt;假设服务 A 是一个 springboot 启动的 java 进程，内部 wrap 的 web server 是 tomcat。&lt;/li&gt;
&lt;li&gt;假设 tomcat 采用的线程模型是 NIO 模式，它默认的最大连接数是 &lt;code&gt;10000&lt;/code&gt; ，也就是最多同时接收处理&lt;code&gt;10000&lt;/code&gt;个用户请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在正常情况下，只要同时请求数不超过&lt;code&gt;10000&lt;/code&gt; 且服务 A 及内外部服务都正常运行就没有问题。（理论上虽然是这样，但实际情况可能不太严谨）&lt;/p&gt;
&lt;p&gt;考虑这样一种情况：&lt;/p&gt;
&lt;p&gt;假设依赖的 B 服务由于各种原因不正常了，比如出现了超时，而且 B 服务是一个业务核心依赖（基本所有请求都要过它）。那么这时候用户从 A 服务入口进来的正常请求线程将不能正常 &lt;code&gt;终止（terminated）&lt;/code&gt;，而会 &lt;code&gt;阻塞（Blocked）&lt;/code&gt; 或者 &lt;code&gt;等待 (waiting)&lt;/code&gt; 在 B 服务这里。&lt;/p&gt;
&lt;p&gt;这时 tomcat 的可用线程数将下降，也就会导致用户对 A 服务的正常请求受到影响，如果 B 服务的情况不能得到改善，那么 A 服务将有可能面临&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程资源不足，A 服务的非核心请求也受到影响 （不走 B 服务的）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;雪崩&lt;/code&gt; 的风险，有可能会因为线程资源不足而 hang 死，产生连锁反应，导致 A 也不可用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可见，我们并不想产生这样的影响，我们希望无论 B 服务是不是核心依赖，它出了问题，都尽量不要或最小范围影响我本服务。&lt;/p&gt;
&lt;p&gt;所以，总结来看，资源具体来说就是&lt;code&gt;线程&lt;/code&gt; ，而隔离的目的，是为了在依赖服务出问题的情况下，影响范围最小化。&lt;/p&gt;
&lt;h2 id="hystrix"&gt;&lt;a href="#hystrix" class="header-anchor"&gt;&lt;/a&gt;Hystrix
&lt;/h2&gt;&lt;h3 id="hystrix-模型"&gt;&lt;a href="#hystrix-%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;Hystrix 模型
&lt;/h3&gt;&lt;p&gt;Hystrix 将远程服务的请求托管在一个线程池中。即默认情况下，所有 Hystrix 命令 (@HystrixCommand) 共享同一个线程池来处理这些请求。该线程池中持有 10 个线程来处理各种远程服务请求，可以是 REST 服务调用、数据库访问等。如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-04-25-mian-shi-bu-hui-wen-de-hystrix-shi-xian-zi-yuan-ge-li/001-3a14cda6.jpg"&gt;&lt;/p&gt;
&lt;h3 id="如何隔离"&gt;&lt;a href="#%e5%a6%82%e4%bd%95%e9%9a%94%e7%a6%bb" class="header-anchor"&gt;&lt;/a&gt;如何隔离
&lt;/h3&gt;&lt;p&gt;有两种策略分别是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程池隔离&lt;/li&gt;
&lt;li&gt;信号量隔离&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两种隔离方式都是限制对共享资源的并发访问量，线程在就绪状态、运行状态、阻塞状态、终止状态间转变时需要由操作系统调度，占用很大的性能消耗；而信号量是在访问共享资源时，进行 tryAcquire，tryAcquire 成功才允许访问共享资源。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;HystrixCommandProperties.Setter().withExecutionisolationStrategy(ExecutionlsolationStrategy.THREAD)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;HystrixCommandProperties.Setter().withExecutionisolationStrategy(ExecutionisolationStrategy.SEMAPHORE)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="线程池隔离"&gt;&lt;a href="#%e7%ba%bf%e7%a8%8b%e6%b1%a0%e9%9a%94%e7%a6%bb" class="header-anchor"&gt;&lt;/a&gt;线程池隔离
&lt;/h3&gt;&lt;p&gt;@HystrixCommand 的默认配置适用于只有少量远程调用的应用。幸运的是，Hystrix 提供了简单易用的方法实现舱壁来隔离不同的远程资源调用。下图说明了 Hystrix 将不同的远程调用隔离在不同的“舱室”（线程池）中：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-04-25-mian-shi-bu-hui-wen-de-hystrix-shi-xian-zi-yuan-ge-li/002-f2889115.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Hystrix 可以为每一个依赖建立一个线程池，使之和其他依赖的使用资源隔离，同时限制他们的并发访问和阻塞扩张。每个依赖可以根据权重分配资源（这里主要是线程），每一部分的依赖出现了问题，也不会影响其他依赖的使用资源。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2022-04-25-mian-shi-bu-hui-wen-de-hystrix-shi-xian-zi-yuan-ge-li/003-9e729873.jpg"&gt;&lt;/p&gt;
&lt;p&gt;如果简单的使用异步线程来实现依赖调用会有如下问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程的创建和销毁&lt;/li&gt;
&lt;li&gt;线程上下文空间的切换，用户态和内核态的切换带来的性能损耗。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用线程池的方式可以解决第一种问题，但是第二个问题计算开销是不能避免的。&lt;/p&gt;
&lt;p&gt;Netflix 在使用过程中详细评估了使用异步线程和同步线程带来的性能差异，结果表明在 99%的情况下，异步线程带来的几毫秒延迟的完全可以接受的。&lt;/p&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个依赖可以给予一个线程池，这个依赖的异常不会影响其他的依赖。&lt;/li&gt;
&lt;li&gt;使用线程可以完全隔离第三方代码，请求线程可以快速放回。&lt;/li&gt;
&lt;li&gt;当一个失败的依赖再次变成可用时，线程池将清理，并立即恢复可用，而不是一个长时间的恢复。&lt;/li&gt;
&lt;li&gt;可以完全模拟异步调用，方便异步编程。&lt;/li&gt;
&lt;li&gt;使用线程池，可以有效的进行实时监控、统计和封装。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用线程池的缺点主要是增加了计算的开销。每一个依赖调用都会涉及到队列，调度，上下文切换，而这些操作都有可能在不同的线程中执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;线程池的创建和管理&lt;/p&gt;
&lt;p&gt;虽然 Hystrix 可以为每个依赖建立一个线程池，但是如果依赖成千上万，建立那么多线程池肯定是不可能的。所以默认情况下，Hystrix 会为每一个 Command Group 建立一个线程池。Hystrix 的线程池在 HystrixConcurrencyStrategy 初始化，线程池是由 ThreadPoolExecutor 实现的。每个线程池默认初始化 10 个线程。Hystrix 有个静态类 Factory，创建的线程池会被存储在 Factory 中的 ConcurrentHashMap 中。ConcurrentHashMap 的 Key 则是上文说到的 CommandGroupKey 或者指定的 ThreadPoolKey。每次命令执行的时候，都会根据 ThreadPoolKey 去找到对应的线程池。线程池拥有一个继承于 rxjava 中 Scheduler 的 HystrixContextScheduler，用于在执行命令的时候，把命令在这个线程池上调度执行。&lt;/p&gt;
&lt;h3 id="信号量隔离"&gt;&lt;a href="#%e4%bf%a1%e5%8f%b7%e9%87%8f%e9%9a%94%e7%a6%bb" class="header-anchor"&gt;&lt;/a&gt;信号量隔离
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;每次调用线程，当前请求通过计数信号量进行限制，当信号大于了最大请求数（maxConcurrentRequests）时，进行限制，调用 fallback 接口快速返回。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;信号量的资源隔离只是起到一个开关的作用，例如，服务 X 的信号量大小为 10，那么同时只允许 10 个 tomcat 的线程（此处是 tomcat 的线程，而不是服务 X 的独立线程池里面的线程）来访问服务 X，其他的请求就会被拒绝，从而达到限流保护的作用。&lt;/p&gt;
&lt;p&gt;信号量的调用是同步的，也就是说，每次调用都得阻塞调用方的线程，直到结果返回。这样就导致了无法对访问做超时（只能依靠调用协议超时，无法主动释放）&lt;/p&gt;
&lt;p&gt;什么时候适合用信号量隔离而不是用线程池？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;隔离的细粒度太高，数百个实例需要隔离，此时用线程池做隔离开销过大&lt;/li&gt;
&lt;li&gt;通常这种都是非网络调用的情况下&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="选用"&gt;&lt;a href="#%e9%80%89%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;选用
&lt;/h3&gt;&lt;p&gt;两种策略对比&lt;/p&gt;
&lt;p&gt;|&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;线程池隔离&lt;/th&gt;
 &lt;th&gt;信号量隔离&lt;/th&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;线程&lt;/td&gt;
 &lt;td&gt;与调用线程非相同线程&lt;/td&gt;
 &lt;td&gt;与调用线程相同&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;开销&lt;/td&gt;
 &lt;td&gt;排队、调度、上下文开销等&lt;/td&gt;
 &lt;td&gt;无线程切换，开销低&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;异步&lt;/td&gt;
 &lt;td&gt;支持&lt;/td&gt;
 &lt;td&gt;不支持&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;并发支持&lt;/td&gt;
 &lt;td&gt;支持（最大线程池大小）&lt;/td&gt;
 &lt;td&gt;支持（最大信号量上限）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;对于那些本来延迟就比较小的请求（例如访问本地缓存成功率很高的请求）来说，线程池带来的开销是非常高的，这时，你可以考虑采用其他方法，例如非阻塞信号量（不支持超时），来实现依赖服务的隔离，使用信号量的开销很小。但绝大多数情况下，Netflix 更偏向于使用线程池来隔离依赖服务，因为其带来的额外开销可以接受，并且能支持包括超时在内的所有功能。&lt;/li&gt;
&lt;li&gt;当请求的服务网络开销比较大的时候，或者是请求比较耗时的时候，我们最好是使用线程隔离策略，这样的话，可以保证大量的容器 (tomcat) 线程可用，不会由于服务原因，一直处于阻塞或等待状态，快速失败返回。而当我们请求缓存这些服务的时候，我们可以使用信号量隔离策略，因为这类服务的返回通常会非常的快，不会占用容器线程太长时间，而且也减少了线程切换的一些开销，提高了缓存服务的效率。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;@HystrixCommand(
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; commandProperties = { //利用 commandProperties 更改线程池的一些默认配置
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; //选择“线程池”模式、&amp;#34;信号量&amp;#34;模式
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; @HystrixProperty(name=&amp;#34;execution.isolation.strategy&amp;#34;,value = &amp;#34;THREAD&amp;#34;/&amp;#34;SEMAPHORE&amp;#34;), 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; //超时
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; @HystrixProperty(name=&amp;#34;execution.isolation.thread.timeoutInMilliseconds&amp;#34;,value = &amp;#34;3000&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; //信号量大小为 10，那么同时只允许 10 个 tomcat 的线程（此处是 tomcat 的线程，而不是服务的独立线程池里面的线程）来访问服务，其他的请求就会被拒绝，从而达到限流保护的作用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; @HystrixProperty(name=&amp;#34;execution.isolation.semaphore.maxConcurrentRequests&amp;#34;,value = &amp;#34;10&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; }，
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;public List&amp;lt;License&amp;gt; getLicensesByOrg(String organizationId){
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; //....
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意，如果选用线程池，要合理的设置线程池的大小，和超时时间。&lt;/p&gt;
&lt;p&gt;spring cloud 的 yaml 配置可以参考：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;feign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hystrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 启用熔断降级策略&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;ribbon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 请求建立连接超时时间，单位：毫秒，默认：2000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ConnectTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 读取数据超时时间长，单位：毫秒，默认：5000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 这里设置为 10 秒，表示请求发出后，超过 10 秒没有读取到数据时请求超时&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ReadTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 对所有操作都进⾏重试，默认：false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;OkToRetryOnAllOperations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 对同一个实例的最大重试次数，默认：0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MaxAutoRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 切换实例重试的最大次数，默认：1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MaxAutoRetriesNextServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;hystrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 配置 HystrixCommand 命令执行是否开启超时，默认：true。&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;isolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 隔离策略，分为 THREAD 和 SEMAPHORE，默认为 THREAD。&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;THREAD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;semaphore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 信号量大小，当隔离策略为信号量时，最大并发请求达到该设置值，后续的请求将被拒绝，默认：10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;maxConcurrentRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 表示设置是否在执行超时时，中断 HystrixCommand.run() 的执行，默认：true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;34&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interruptOnTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;35&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# hystrixCommand 命令执行超时时间，单位：毫秒，默认：1000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;36&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 首先要考虑接口的响应时间，其次要考虑 ribbon 的超时时间和重试次数&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;37&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeoutInMilliseconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;更多配置请 参考：https://github.com/Netflix/Hystrix/wiki/Configuration&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/Netflix/Hystrix/wiki/Configuration" target="_blank" rel="noopener"
 &gt;https://github.com/Netflix/Hystrix/wiki/Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://www.baoguoding.com/2019/08/471-hystrix08.html" target="_blank" rel="noopener"
 &gt;http://www.baoguoding.com/2019/08/471-hystrix08.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.csdn.net/shiyong1949/article/details/119201924" target="_blank" rel="noopener"
 &gt;https://blog.csdn.net/shiyong1949/article/details/119201924&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>阿里云面试题分享</title><link>https://xiaobox.github.io/p/2020-05-12-a-li-yun-mian-shi-ti-fen-xiang/</link><pubDate>Tue, 12 May 2020 14:23:10 +0000</pubDate><guid>https://xiaobox.github.io/p/2020-05-12-a-li-yun-mian-shi-ti-fen-xiang/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-05-12-a-li-yun-mian-shi-ti-fen-xiang/cover.jpg" alt="Featured image of post 阿里云面试题分享" /&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-05-12-a-li-yun-mian-shi-ti-fen-xiang/001-9285f426.jpg"&gt;&lt;/p&gt;
&lt;p&gt;突如其来的电话面试&lt;/p&gt;
&lt;p&gt;事情是这样的，今天吃完晚饭在外面公园遛达了一圈，回到家没一会儿接到了一个电话，说是阿里云的，心中疑惑，不对呀，我刚收到回阿里的拒信（之前投过，被拒了）。&lt;/p&gt;
&lt;p&gt;聊了一下，虽然不太清楚阿里的流程，但老哥态度特别好，电话面试一下总是没问题的。&lt;/p&gt;
&lt;p&gt;下面分享下问题&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一 给你一个 ipv4 的地址，把它转到 Int , 用一个Int变量装。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个题很明显直接装是有可能越界装不下的。int是32位。long倒是可以。&lt;/p&gt;
&lt;p&gt;我想了一会儿说了一个答案，没对，后来老哥给我解释了一下，然后恍然大明白了，其实是个小技巧。直接上代码吧。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * 将 ip 字符串转换为 int 类型的数字
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * &amp;lt;p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * 思路就是将 ip 的每一段数字转为 8 位二进制数，并将它们放在结果的适当位置上
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; *
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * @param ipString ip字符串，如 127.0.0.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * @return ip字符串对应的 int 值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ip2Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 取 ip 的各段&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipSlices&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;\\.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipSlices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 将 ip 的每一段解析为 int，并根据位置左移 8 位&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;intSlice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ipSlices&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 求与&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;intSlice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;那怎么再从int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;转成ipv4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;字符串呢&lt;/span&gt;&lt;span class="err"&gt;？&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;其实也很简单&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;思路是一样的&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;将&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;值的&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;位分为&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;个&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;位数字&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;然后这&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;个&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;位的数字用&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;255&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;的数字进行表示&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;用点号分隔即可&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;&lt;span class="n"&gt;我们也基于位运算&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;行代码即可实现&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="n"&gt;cpp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * 将 int 转换为 ip 字符串
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; *
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * @param ipInt 用 int 表示的 ip 值
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * @return ip字符串，如 127.0.0.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;34&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;35&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;int2Ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipInt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;36&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;4&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;37&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;38&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 每 8 位为一段，这里取当前要处理的最高位的位置&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;39&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;40&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 取当前处理的 ip 段的值&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;41&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipInt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;255&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;42&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 将当前 ip 段转换为 0 ~ 255 的数字，注意这里必须使用无符号右移&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;43&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipString&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;44&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;45&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ipString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;46&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;二 设计一个分布式的图片存储系统 QPS：5K以上 可以使用通用的中件间。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 针对这个问题我扯了半天，感觉有些在点儿上，有些不在，想来如果你设计过，有过设计经验应该多数能说到点儿上。别的不说，光可高用就能扯半天，比如集群故障情况下的失效转移（Failover）。不同故障下的解决方案（瞬间故障、临时故障、永久故障）。网上相关资料也挺多的，可以参考开源系统的设计比如：FastDFS。是一个由 C 语言实现的开源轻量级分布式文件系统(https://github.com/happyfish100/fastdfs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-05-12-a-li-yun-mian-shi-ti-fen-xiang/002-f2e7317d.jpg"&gt;&lt;/p&gt;
&lt;p&gt;关注公众号 获取更多精彩内容&lt;/p&gt;</description></item><item><title>滴滴一面（高级java）面试题分享</title><link>https://xiaobox.github.io/p/2020-05-11-di-di-yi-mian-gao-ji-java-mian-shi-ti-fen-xiang/</link><pubDate>Mon, 11 May 2020 07:25:08 +0000</pubDate><guid>https://xiaobox.github.io/p/2020-05-11-di-di-yi-mian-gao-ji-java-mian-shi-ti-fen-xiang/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-05-11-di-di-yi-mian-gao-ji-java-mian-shi-ti-fen-xiang/cover.jpg" alt="Featured image of post 滴滴一面（高级java）面试题分享" /&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-05-11-di-di-yi-mian-gao-ji-java-mian-shi-ti-fen-xiang/001-4d881798.jpg"&gt;&lt;/p&gt;
&lt;h3 id="1-说下对-volatile关键字的理解"&gt;&lt;a href="#1-%e8%af%b4%e4%b8%8b%e5%af%b9-volatile%e5%85%b3%e9%94%ae%e5%ad%97%e7%9a%84%e7%90%86%e8%a7%a3" class="header-anchor"&gt;&lt;/a&gt;1 说下对 volatile关键字的理解
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;volatile可以禁止指令重排序优化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;保证可见性、不保证原子性(也就是说多个线程并发修改某个变量时，依旧会产生多线程问题，但适合使用一个线程写，多个线程读的场合。)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;以下场景可以使用volatile&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运算结果并不依赖变量的当前值，或者能够确保只有单一的线程修改变量的值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;变量不需要与其他的状态变量共同参与不变约束&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原理：volatile语义中的内存屏障volatile的内存屏障策略非常严格保守，非常悲观且毫无安全感的心态：在每个volatile写操作前插入StoreStore屏障，在写操作后插入StoreLoad屏障；在每个volatile读操作前插入LoadLoad屏障，在读操作后插入LoadStore屏障；由于内存屏障的作用，避免了volatile变量和其它指令重排序、线程之间实现了通信，使得volatile表现出了锁的特性。&lt;/p&gt;
&lt;h3 id="2-jvm调过优没有是怎么做的排查问题时一般会用哪些命令"&gt;&lt;a href="#2-jvm%e8%b0%83%e8%bf%87%e4%bc%98%e6%b2%a1%e6%9c%89%e6%98%af%e6%80%8e%e4%b9%88%e5%81%9a%e7%9a%84%e6%8e%92%e6%9f%a5%e9%97%ae%e9%a2%98%e6%97%b6%e4%b8%80%e8%88%ac%e4%bc%9a%e7%94%a8%e5%93%aa%e4%ba%9b%e5%91%bd%e4%bb%a4" class="header-anchor"&gt;&lt;/a&gt;2 jvm调过优没有，是怎么做的？排查问题时一般会用哪些命令？
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt; jps(JVM Process Status):虚拟机进程状况工具 显示虚拟机进程 jps -l
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt; jstat(JVM Statistics Monitoring Tool):监控虚拟机各种运行状态
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt; jinfo(Configuration Info for Java):java配置信息工具
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt; jmap(Memory Map for Java) 堆转储快照
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt; jstack(Stack Trace for Java) java堆栈跟踪工具
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-aqs-原理大概说一下"&gt;&lt;a href="#3-aqs-%e5%8e%9f%e7%90%86%e5%a4%a7%e6%a6%82%e8%af%b4%e4%b8%80%e4%b8%8b" class="header-anchor"&gt;&lt;/a&gt;3 AQS 原理大概说一下
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;可参考 ： [彻底搞懂AQS](http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247484343&amp;amp;idx=1&amp;amp;sn=0c0ac16161f09cadd00483addbf6e598&amp;amp;chksm=eb6dbc31dc1a35278931f76fce310d6ead4aba125fc2370aeb52a03b2dc4a78c0d4d95fae420&amp;amp;scene=21#wechat_redirect)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="4-redis-高可用实现方式"&gt;&lt;a href="#4-redis-%e9%ab%98%e5%8f%af%e7%94%a8%e5%ae%9e%e7%8e%b0%e6%96%b9%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;4 Redis 高可用实现方式
&lt;/h3&gt;&lt;h3 id="redis-cluster-或哨兵机制"&gt;&lt;a href="#redis-cluster-%e6%88%96%e5%93%a8%e5%85%b5%e6%9c%ba%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;redis cluster 或哨兵机制
&lt;/h3&gt;&lt;h3 id="5-kafka-或-rocketmq-实现原理"&gt;&lt;a href="#5-kafka-%e6%88%96-rocketmq-%e5%ae%9e%e7%8e%b0%e5%8e%9f%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;5 Kafka 或 RocketMq 实现原理
&lt;/h3&gt;&lt;h3 id="问的太广了自己知道什么有逻辑的表达一下吧"&gt;&lt;a href="#%e9%97%ae%e7%9a%84%e5%a4%aa%e5%b9%bf%e4%ba%86%e8%87%aa%e5%b7%b1%e7%9f%a5%e9%81%93%e4%bb%80%e4%b9%88%e6%9c%89%e9%80%bb%e8%be%91%e7%9a%84%e8%a1%a8%e8%be%be%e4%b8%80%e4%b8%8b%e5%90%a7" class="header-anchor"&gt;&lt;/a&gt;问的太广了，自己知道什么有逻辑的表达一下吧
&lt;/h3&gt;&lt;h3 id="6-spring-cloud-和-dubbo区别"&gt;&lt;a href="#6-spring-cloud-%e5%92%8c-dubbo%e5%8c%ba%e5%88%ab" class="header-anchor"&gt;&lt;/a&gt;6 spring cloud 和 dubbo区别
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt; 主要是RPC和生态上的区别
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="7-spring-cloud-用过哪些组件-"&gt;&lt;a href="#7-spring-cloud-%e7%94%a8%e8%bf%87%e5%93%aa%e4%ba%9b%e7%bb%84%e4%bb%b6-" class="header-anchor"&gt;&lt;/a&gt;7 spring cloud 用过哪些组件 ？
&lt;/h3&gt;&lt;h3 id="可参考-spring及spring-cloud框架主要组件介绍"&gt;&lt;a href="#%e5%8f%af%e5%8f%82%e8%80%83-spring%e5%8f%8aspring-cloud%e6%a1%86%e6%9e%b6%e4%b8%bb%e8%a6%81%e7%bb%84%e4%bb%b6%e4%bb%8b%e7%bb%8d" class="header-anchor"&gt;&lt;/a&gt;可参考 ：&lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247484358&amp;amp;idx=1&amp;amp;sn=f58ad629f83c3222d6eb7b2e4f739879&amp;amp;chksm=eb6dbc40dc1a355673bc17b44f22841ee415c8a592ff8a4e6f725b458325373b7fe5277ffc72&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;spring及spring cloud框架主要组件介绍&lt;/a&gt;
&lt;/h3&gt;&lt;h3 id="8-hystrix-熔断器有哪些模式"&gt;&lt;a href="#8-hystrix-%e7%86%94%e6%96%ad%e5%99%a8%e6%9c%89%e5%93%aa%e4%ba%9b%e6%a8%a1%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;8 Hystrix 熔断器有哪些模式
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" data-title-escaped="image.png" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" title="image.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;closed：请求正常时，不使用熔断器；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;open：统计请求的失败比例，达到阀值时，打开熔断器，请求被降级处理；延时一段时候后（默认休眠时间是5S）会进入halfopen状态；默认失败比例阀值是50%，请求次数最少不低于20次；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;halfopen：在进入该状态后会放入部分请求；判断请求是否成功，不成功，进入open状态，重新计时，进入halfopen状态；成功，进入closed状态，&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="9-介绍下项目"&gt;&lt;a href="#9-%e4%bb%8b%e7%bb%8d%e4%b8%8b%e9%a1%b9%e7%9b%ae" class="header-anchor"&gt;&lt;/a&gt;9 介绍下项目
&lt;/h3&gt;&lt;h3 id=""&gt;&lt;a href="#" class="header-anchor"&gt;&lt;/a&gt;
&lt;/h3&gt;&lt;h3 id="10-有什么问题问我的"&gt;&lt;a href="#10-%e6%9c%89%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98%e9%97%ae%e6%88%91%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;10 有什么问题问我的？
&lt;/h3&gt;</description></item><item><title>快手一面（高级java）面试真题分享</title><link>https://xiaobox.github.io/p/2020-05-06-kuai-shou-yi-mian-gao-ji-java-mian-shi-zhen-ti-fen-xiang/</link><pubDate>Wed, 06 May 2020 10:37:03 +0000</pubDate><guid>https://xiaobox.github.io/p/2020-05-06-kuai-shou-yi-mian-gao-ji-java-mian-shi-zhen-ti-fen-xiang/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-05-06-kuai-shou-yi-mian-gao-ji-java-mian-shi-zhen-ti-fen-xiang/cover.jpg" alt="Featured image of post 快手一面（高级java）面试真题分享" /&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-05-06-kuai-shou-yi-mian-gao-ji-java-mian-shi-zhen-ti-fen-xiang/001-ad0a6a34.png"&gt;&lt;/p&gt;
&lt;h3 id=""&gt;&lt;a href="#" class="header-anchor"&gt;&lt;/a&gt;
&lt;/h3&gt;&lt;h3 id="1-arraylist-linkedlist-区别"&gt;&lt;a href="#1-arraylist-linkedlist-%e5%8c%ba%e5%88%ab" class="header-anchor"&gt;&lt;/a&gt;1 ArrayList LinkedList 区别
&lt;/h3&gt;&lt;p&gt;ArrayList&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;采用数组的方式来存储对象&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;非线程安全&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每次按照1.5倍（位运算）的比率通过copeOf的方式扩容&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;初始数组容量为10&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LinkedList&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;基于双向链表机制实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;非线程安全的&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-arraylistlinkedlist-的boolean-adde-e-e-removeint-indexvoid-addint-index-e-element-三个方法分别的时间复杂度"&gt;&lt;a href="#2-arraylistlinkedlist-%e7%9a%84boolean-adde-e-e-removeint-indexvoid-addint-index-e-element-%e4%b8%89%e4%b8%aa%e6%96%b9%e6%b3%95%e5%88%86%e5%88%ab%e7%9a%84%e6%97%b6%e9%97%b4%e5%a4%8d%e6%9d%82%e5%ba%a6" class="header-anchor"&gt;&lt;/a&gt;2 ArrayList、LinkedList 的boolean add(E e) 、E remove(int index)、void add(int index, E element) 三个方法，分别的时间复杂度
&lt;/h3&gt;&lt;p&gt;ArrayList:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;add(E e) 在不考虑扩容的情况下时间复杂度为：O(1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;add(int index,E element) 时间复杂度为：O(n) 在第几个元素后面插入，后面的元素需要向后移动&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;remove(int index) 时间复杂度为：O(n) 在第几个元素后面插入，后面的元素需要向后移动&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LinkedList:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;add(E e) 时间复杂度为：O(1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;add(int index,E element) 时间复杂度为：O(n) 需要先查找到第几个元素，直接指针指向操作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;remove(int index) 时间复杂度为：O(n)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总结：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ArrayList 对于随机位置的add/remove，时间复杂度为 O(n),但是对于列表末尾的添加/删除操作,时间复杂度是 O(1).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LinkedList对于随机位置的add/remove，时间复杂度为 O(n),但是对于列表 末尾/开头 的添加/删除操作,时间复杂度是 O(1).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-hashmap-数据结构18为什么用红黑树"&gt;&lt;a href="#3-hashmap-%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%8418%e4%b8%ba%e4%bb%80%e4%b9%88%e7%94%a8%e7%ba%a2%e9%bb%91%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;3 HashMap 数据结构，1.8为什么用红黑树?
&lt;/h3&gt;&lt;h3 id="参考系列文章经典面试题之hashmap一"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e7%b3%bb%e5%88%97%e6%96%87%e7%ab%a0%e7%bb%8f%e5%85%b8%e9%9d%a2%e8%af%95%e9%a2%98%e4%b9%8bhashmap%e4%b8%80" class="header-anchor"&gt;&lt;/a&gt;参考系列文章：&lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247484239&amp;amp;idx=1&amp;amp;sn=370298f66cfb9b85df7420cb09a5ce2e&amp;amp;chksm=eb6dbcc9dc1a35df77495c0718c3b59f6eae1eef449ab3f0ad23884533a8173de073458cf2d3&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;经典面试题之HashMap(一)&lt;/a&gt;
&lt;/h3&gt;&lt;h3 id="4-hashmap-求hash值的算法"&gt;&lt;a href="#4-hashmap-%e6%b1%82hash%e5%80%bc%e7%9a%84%e7%ae%97%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;4 HashMap 求hash值的算法？
&lt;/h3&gt;&lt;h3 id="参考系列文章--经典面试题之hashmap二"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e7%b3%bb%e5%88%97%e6%96%87%e7%ab%a0--%e7%bb%8f%e5%85%b8%e9%9d%a2%e8%af%95%e9%a2%98%e4%b9%8bhashmap%e4%ba%8c" class="header-anchor"&gt;&lt;/a&gt;参考系列文章 : &lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247484244&amp;amp;idx=1&amp;amp;sn=6df3488ca359fac892456fabdeb6520a&amp;amp;chksm=eb6dbcd2dc1a35c406ff444c029edb87aa8db8abc83b9e53dde392b113c3093cf3187cf25342&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;经典面试题之HashMap(二)&lt;/a&gt;&lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247484249&amp;amp;idx=1&amp;amp;sn=fddb98d346845cb42740b8fdc5bb05fe&amp;amp;chksm=eb6dbcdfdc1a35c90a0e31c43ad9a1a9378685801b4ad632b276c7682f1f5da8b6ee64c183a9&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;&lt;/a&gt;
&lt;/h3&gt;&lt;h3 id="5-写代码实现一下hashmap的put方法"&gt;&lt;a href="#5-%e5%86%99%e4%bb%a3%e7%a0%81%e5%ae%9e%e7%8e%b0%e4%b8%80%e4%b8%8bhashmap%e7%9a%84put%e6%96%b9%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;5 写代码：实现一下HashMap的put方法
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt; 这个题我说实话，我自己是无法完整的写出来，但大致思路是能说得上来。所以，如果我是面试官的话，要求至少是把思路说出来。能完全写出来的佩服，因为put方法还牵扯很多上下文的信息，这些都记住不易。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="参考系列文章一次性搞定hashmap面试"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e7%b3%bb%e5%88%97%e6%96%87%e7%ab%a0%e4%b8%80%e6%ac%a1%e6%80%a7%e6%90%9e%e5%ae%9ahashmap%e9%9d%a2%e8%af%95" class="header-anchor"&gt;&lt;/a&gt;参考系列文章：&lt;a class="link" href="http://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247484249&amp;amp;idx=1&amp;amp;sn=fddb98d346845cb42740b8fdc5bb05fe&amp;amp;chksm=eb6dbcdfdc1a35c90a0e31c43ad9a1a9378685801b4ad632b276c7682f1f5da8b6ee64c183a9&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;一次性搞定HashMap面试&lt;/a&gt;
&lt;/h3&gt;&lt;h3 id="6-说明一下java的异常体系"&gt;&lt;a href="#6-%e8%af%b4%e6%98%8e%e4%b8%80%e4%b8%8bjava%e7%9a%84%e5%bc%82%e5%b8%b8%e4%bd%93%e7%b3%bb" class="header-anchor"&gt;&lt;/a&gt;6 说明一下java的异常体系
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" data-title-escaped="image.png" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" title="image.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" data-title-escaped="image.png" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" title="image.png"&gt;&lt;/p&gt;
&lt;h3 id="7--redis-怎样实现分布式锁-setnx--和-set区别"&gt;&lt;a href="#7--redis-%e6%80%8e%e6%a0%b7%e5%ae%9e%e7%8e%b0%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81-setnx--%e5%92%8c-set%e5%8c%ba%e5%88%ab" class="header-anchor"&gt;&lt;/a&gt;7 Redis 怎样实现分布式锁? setnx 和 set区别
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt; 用setnx 可以实现分布式锁

 set:
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;将字符串值 value 关联到 key 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果 key 已经持有其他值， SET 就覆写旧值，无视类型。&lt;/p&gt;
&lt;p&gt;setnx:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将 key 的值设为 value ，当且仅当 key 不存在。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若给定的 key 已经存在，则 SETNX 不做任何动作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SETNX 是『SET if Not eXists』(如果不存在，则 SET)的简写。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="8-一个接口调用次数-如果用-static-long-countercounter-统计有什么问题没有如果用volatile修饰呢"&gt;&lt;a href="#8-%e4%b8%80%e4%b8%aa%e6%8e%a5%e5%8f%a3%e8%b0%83%e7%94%a8%e6%ac%a1%e6%95%b0-%e5%a6%82%e6%9e%9c%e7%94%a8-static-long-countercounter-%e7%bb%9f%e8%ae%a1%e6%9c%89%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98%e6%b2%a1%e6%9c%89%e5%a6%82%e6%9e%9c%e7%94%a8volatile%e4%bf%ae%e9%a5%b0%e5%91%a2" class="header-anchor"&gt;&lt;/a&gt;8 一个接口调用次数 ，如果用 static long counter；counter++; 统计有什么问题没有？如果用volatile修饰呢？
&lt;/h3&gt;&lt;pre&gt;&lt;code&gt; 有问题，如果在多线程环境下，会出现数据不对的情况。

 如果用volatile也不能解决这个问题，因为volatile 虽然能够保证有序性和可见性，但就这个例子来讲，运算的结果已经依赖当前变量的值了（counter++） 这样是不能使用volatile的，如果用了，结果也是不对。原因是在counter++中,这条语句编译后的字节码指令不是一句，在多线程环境下其他线程可能已经把值改了，操作数栈顶的值可能就成了过期的数据。 

 **那应该怎么办呢？**

 可以用原子类操作，比如 AtomicInteger

 **AtomicInteger的原理？**

 主要是利用了CAS

 **描述下CAS过程和原理？**
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所谓CAS，即“比较与交换”（Compare-and-swap），是最常见的乐观锁实现方式，看官应该对这个概念很熟悉。一次CAS过程是原子的，包含3个操作数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;需要访问的内存地址V；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;该内存地址中存储的预期值A；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;希望向该地址写入的新值B。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当且仅当V中的值与A相同时，其值才会更新成B，否则就不执行任何动作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用，允许java调用其他语言。而compareAndSwapInt就是借助C来调用CPU底层指令(Atomic::cmpxchg(x,addr,e))实现的。

 **CAS存在的问题？**

 如果CAS失败，会一直进行尝试。如果CAS长时间一直不成功，可能会给CPU带来很大的开销。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="9--讲一讲熟悉的项目"&gt;&lt;a href="#9--%e8%ae%b2%e4%b8%80%e8%ae%b2%e7%86%9f%e6%82%89%e7%9a%84%e9%a1%b9%e7%9b%ae" class="header-anchor"&gt;&lt;/a&gt;9 讲一讲熟悉的项目
&lt;/h3&gt;</description></item><item><title>阿里面试分享</title><link>https://xiaobox.github.io/p/2020-04-25-a-li-mian-shi-fen-xiang/</link><pubDate>Sat, 25 Apr 2020 07:49:29 +0000</pubDate><guid>https://xiaobox.github.io/p/2020-04-25-a-li-mian-shi-fen-xiang/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-04-25-a-li-mian-shi-fen-xiang/cover.jpg" alt="Featured image of post 阿里面试分享" /&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="384px" data-flex-grow="160" height="600" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xiaobox.github.io/p/2020-04-25-a-li-mian-shi-fen-xiang/001-85fc325c.jpg" width="960"&gt;&lt;/p&gt;
&lt;p&gt;通过与透露题目的朋友得知以下两题出自蚂蚁金服的远程一面。&lt;/p&gt;
&lt;p&gt;题目本身并不很难，难的是在阿里评测的平台上，脱离IDE，徒手coding。那个平台其实就是给你一个网址打开就像记事本一样，面试官可以实时看到你写的什么，你以可以说话跟他语音交流。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No1&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;请用java实现以下shell脚本的功能&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;cat /home/admin/logs/webx.log | grep &amp;ldquo;Login&amp;rdquo; | uniq -c | sort -nr&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;strong&gt;从webx.log里查找包含&amp;quot;Login&amp;quot;的行，去重并排序&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我的实现：&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>