<?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://blog.nicelylit.net/categories/engineering/</link><description>Recent content in 工程 on 有点稳</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 15 Aug 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.nicelylit.net/categories/engineering/index.xml" rel="self" type="application/rss+xml"/><item><title>大模型（一）</title><link>https://blog.nicelylit.net/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%B8%80/</link><pubDate>Fri, 15 Aug 2025 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%B8%80/</guid><description>&lt;h3 id="大模型是什么"&gt;大模型是什么？
&lt;/h3&gt;&lt;p&gt;大模型是大语言模型（Large Language Model, LLM）的简称，基于海量数据和庞大参数规模得到的机器学习模型，能够理解和生成自然语言和代码等内容。&lt;/p&gt;
&lt;h3 id="大模型有什么核心特点"&gt;大模型有什么核心特点？
&lt;/h3&gt;&lt;p&gt;大模型的核心特点是参数规模大、通用性强、上下文理解。&lt;/p&gt;
&lt;p&gt;大模型的参数规模在十亿到万亿量级，比如GPT-4有1.8万亿。有些模型参数会带在名字里，比如Qwen-72B指的是千问通用大模型的720亿参数的版本。&lt;/p&gt;
&lt;p&gt;大模型的通用性在于任务无关性、小样本学习和多模态能力。大模型预训练学习广泛的知识，可以适用于多种任务，问答、翻译、编程等。大模型无需额外训练，仅通过示例就可以执行新任务。多模态大模型可以同时处理文本、图像、语音等输入。&lt;/p&gt;
&lt;p&gt;大模型支持长文本交互，能结合上下文生成连贯的回答。大模型能识别上下文代词的指代以及省略，维持主题避免重复或矛盾，提取分散在多段落中的关键信息。大模型的上下文理解能力得益于注意力机制和位置编码，但也有长程衰减的特点，注意力权重下降。上下文有窗口限制，比如GPT-4支持128K个token，超出会被丢弃。&lt;/p&gt;
&lt;p&gt;大模型的局限性体现在幻觉、数据偏见和算力要求高。幻觉是在上下文模糊的情况下产生的一种虚构内容的现象。训练数据中如果有偏见那么大模型也会产生偏见。目前训练和部署都需要大量的GPU资源。&lt;/p&gt;
&lt;h3 id="大模型有哪些应用场景呢"&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;li&gt;内容创作：营销文案、广告创意、新闻快讯、剧本大纲、字幕生成、编辑校对、文字润色，可结合图像、语音、视频处理的技术生成多模态的内容&lt;/li&gt;
&lt;li&gt;分析诊断：疾病辅助诊断、辅助数学证明、科研论文阅读、智能顾问、自适应学习、合规审查、风险分析&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="大模型有哪些核心能力呢"&gt;大模型有哪些核心能力呢？
&lt;/h3&gt;&lt;p&gt;GPT-5直接给出了16项能力（Capability），是DeepSeek给出的两倍，看上去也不太好说哪个就能包含或者推导出另一个，难以剔除，只能进行分层（Level）和分组（Group），GPT-5给出的结果基本没有太多可挑剔的，它直接对能力分了七层和六组，不过我还是觉得太多了，索性让它把六组做了分层，它给出了三个方案，我选择了大小适中的方案介绍。&lt;/p&gt;
&lt;p&gt;四个分层是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L1: 表示与变换&lt;/li&gt;
&lt;li&gt;L2: 推理与决策&lt;/li&gt;
&lt;li&gt;L3: 编排与行动&lt;/li&gt;
&lt;li&gt;L4: 治理与保障&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四层中，前三层的关系是上一层是下一层的基础，而第四层需要贯穿整个三层。六个分组是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;G1: 语言与表示&lt;/li&gt;
&lt;li&gt;G2: 语义变换&lt;/li&gt;
&lt;li&gt;G3: 认知推理&lt;/li&gt;
&lt;li&gt;G4: 交互控制&lt;/li&gt;
&lt;li&gt;G5: 外界接入与行动&lt;/li&gt;
&lt;li&gt;G6: 治理与保障&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;六个分组到四个层的归属关系是这样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L1: G1, G2&lt;/li&gt;
&lt;li&gt;L2: G3&lt;/li&gt;
&lt;li&gt;L3: G4, G5&lt;/li&gt;
&lt;li&gt;L4: G6&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;详细来看看16项能力的描述和示例。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C1: 语言理解（NLU）- 读懂意图、上下文、指代、情感、语义相似度；意图识别、问答、分类、相似句召回&lt;/li&gt;
&lt;li&gt;C2: 语言生成（NLG）- 连贯生成与风格控制、改写；邮件/文案撰写、语气转换、SEO内容&lt;/li&gt;
&lt;li&gt;C3: 翻译与跨语言 - 多语互译、术语一致、语域迁移；技术文档本地化、多语客服&lt;/li&gt;
&lt;li&gt;C4: 长上下文与记忆 - 超长文档处理与跨段一致性；长合同问答、项目历史跟进&lt;/li&gt;
&lt;li&gt;C5: 多模态理解与生成 - 图像/音频/视频/表格理解与描述；图表解读、截图问答、图像说明&lt;/li&gt;
&lt;li&gt;C6: 信息抽取与结构化 - 实体/关系抽取、要点表格化；合同要点提取、舆情三元组抽取&lt;/li&gt;
&lt;li&gt;C7: 摘要与压缩 - 多粒度总结、对比摘要、主题归纳；会议纪要、论文速览、新闻汇编&lt;/li&gt;
&lt;li&gt;C8: 推理与规划 - 常识/因果/数学推理，任务分解与计划；解题思路、行程规划、流程优化&lt;/li&gt;
&lt;li&gt;C9: 数据分析与轻量可视化 - 描述统计、趋势洞察、异常解读；报表解读、A/B结果解释&lt;/li&gt;
&lt;li&gt;C10: 指令跟随与对话管理 - 按自然语言步骤执行，保持多轮上下文；流程性任务执行、对话机器人&lt;/li&gt;
&lt;li&gt;C11: 个性化与风格控制 - 角色设定、语气/长度/格式可控；法律/学术风格、对话式教程&lt;/li&gt;
&lt;li&gt;C12: 检索与知识增强（RAG）- 结合外部文档检索提升事实性与时效；企业知识库问答、条文引用&lt;/li&gt;
&lt;li&gt;C13: 工具使用（函数与插件调用）- 调用API/数据库/计算器/浏览器等；下单、查库存、运行SQL、网页查证&lt;/li&gt;
&lt;li&gt;C14: 代码能力 - 生成、重构、补全、调试、解释；API示例、单测补全、错误定位&lt;/li&gt;
&lt;li&gt;C15: 代理与自动化 - 拆解-执行-回顾的自主循环；竞品调研到成稿的一体化执行&lt;/li&gt;
&lt;li&gt;C16: 安全与可控性 - 敏感识别、合规约束、审计可追踪；内容审核、PII识别、策略对齐&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它给的示例中，大部分我还都知道是什么内容，像语域迁移、舆情三元组、PII识别的概念都是头次听说，它的知识储备的确让人类的个体望尘莫及啊。最后，16项能力到六个分组的归属关系是这样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;G1: C1, C2, C3, C4, C5&lt;/li&gt;
&lt;li&gt;G2: C6, C7&lt;/li&gt;
&lt;li&gt;G3: C8, C9&lt;/li&gt;
&lt;li&gt;G4: C10, C11&lt;/li&gt;
&lt;li&gt;G5: C12, C13, C14, C15&lt;/li&gt;
&lt;li&gt;G6: C16&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="大模型能力的评价维度有哪些"&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;li&gt;时效性：对近期事实/变化的掌握与更新能力，比如近期实时回答的正确率、线上模型主题分布和训练分布漂移等。&lt;/li&gt;
&lt;li&gt;成本与性能：服务成本与性能效率，比如首token的延时、平均token生成速率、单次推理成本、能耗比等。&lt;/li&gt;
&lt;li&gt;安全与合规：有害内容、越权、隐私与法规风险，比如毒性内容触发率、个人身份信息泄漏率等。&lt;/li&gt;
&lt;li&gt;用户体验：用户主观满意与交互效率，用户满意度、净推荐值、会话留存率、平均会话长度等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="大模型的局限与缓解方法"&gt;大模型的局限与缓解方法？
&lt;/h3&gt;&lt;p&gt;从大模型能力的四个分层上来看局限性和缓解策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表示与转换：符号接地问题（Symbol Grounding Problem）是说抽象的概念与概念的符号如何能对接上真实的对象或者指代的符号。这个局限性一方面来自于多模态的表示不足，另一方面来自于大模型不具备真实世界的体验，而依赖于统计模式。从梅拉尼·米歇尔的观点看，人工智能还缺乏直觉知识，也就是人们普遍习以为常但并未被记录成文字的知识。表示不足容易造成幻觉，目前缓解的办法是通过内容对齐、检索增强、不可回答时拒答、结合知识图谱等。&lt;/li&gt;
&lt;li&gt;推理与决策：因果推理薄弱，有长推理漂移与数学/逻辑错误。这个局限性来自于用统计的相关性代替因果逻辑。从梅拉尼·米歇尔的观点看，人工智能还缺乏抽象、类比、元认知。抽象是说通过抽取不同事物之间的共性特征而构建新的概念。类比是抽取共性的基础上进行预测。元认知是一种反思观察思维过程的过程与抽象。在大模型涌现出思维链能力后，这个局限性有所缓解，其他的缓解办法还有引入工具计算、分步验证、断言检查、规划-执行-回顾等。&lt;/li&gt;
&lt;li&gt;外界接入与行动：物理执行缺失，感知外界变化滞后，实时交互延时。这个局限性来自于文本的符号世界与物理世界的割裂，传感器和执行器难以闭环。数据训练虽然成本高、周期长，但跟传感器和执行器的接入和演进速度来比，已经算是低成本和极快的迭代速度了。缓解办法是发展模型上下文协议（MCP）增强模型接入工具的能力。&lt;/li&gt;
&lt;li&gt;安全保障：隐私泄漏、偏见与毒性输出、对抗攻击脆弱。这个局限性来自于安全目标的不可计算性。缓解办法是数据脱敏、价值观的统一、内容过滤、对抗性测试等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="大模型能力落地范式有哪些"&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;li&gt;工具与程序增强&lt;/li&gt;
&lt;li&gt;编排与工作流&lt;/li&gt;
&lt;li&gt;模型定制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;纯生成，也就是通过提示词工程，提供少量样本参考，做结构化的生成。这种方式直接通过调用通用或者推理大模型的API，选择合适的初始化配置，结合着提示词优化就可以实现，成本较低。适合简单问答，不需要实时信息，没有特殊安全与隐私合规的需求。&lt;/p&gt;
&lt;p&gt;知识增强生成，也就是在用户和大模型之间，加入额外的系统对用户原始请求或者大模型的答案来增强知识，从而增加大模型回复的准确性。具有代表性的是检索增强生成（RAG），通过信息检索技术，在文档库中检索相关的文档片段，作为额外的知识，一起传给大模型，来降低幻觉，提升信息的时效性。这种方式适合专业领域知识并未公开但可以内部检索的需求，还有时效新闻来不及训练的需求。&lt;/p&gt;
&lt;p&gt;工具与程序增强，也就是让大模型具备使用外部工具和执行程序的能力，大模型作为代理人去使用工具完成任务或者补充精确计算结果。这种方式适合编程这种环境相对封闭的需求，还有简化企业内部工具使用的需求。&lt;/p&gt;
&lt;p&gt;编排与工作流，也就是让大模型作为多个代理人角色承担计划、执行、回顾等多种任务，自动完成完整的工作流。这种方式适合自动化运营和批量报表生产。&lt;/p&gt;
&lt;p&gt;模型定制，也就是通过指令对齐、模型微调让大模型能适应特定任务或者领域。这种方式需要额外训练，对数据规模和成本要求较高，适合法律、医疗、编程等专业领域。&lt;/p&gt;
&lt;h3 id="大模型的原理和核心技术"&gt;大模型的原理和核心技术？
&lt;/h3&gt;&lt;p&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;li&gt;显存分层与内存优化&lt;/li&gt;
&lt;li&gt;通信栈和互联硬件优化&lt;/li&gt;
&lt;li&gt;并行分片技术&lt;/li&gt;
&lt;li&gt;CUDA生态和编译优化&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;li&gt;深度学习理论&lt;/li&gt;
&lt;li&gt;神经网络理论&lt;/li&gt;
&lt;li&gt;Transformer架构&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;/ul&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;li&gt;打包和加载技术&lt;/li&gt;
&lt;li&gt;数据采样与混合策略&lt;/li&gt;
&lt;li&gt;数据治理与可观测技术&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>三种平均数</title><link>https://blog.nicelylit.net/posts/%E4%B8%89%E7%A7%8D%E5%B9%B3%E5%9D%87%E6%95%B0/</link><pubDate>Tue, 12 Aug 2025 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E4%B8%89%E7%A7%8D%E5%B9%B3%E5%9D%87%E6%95%B0/</guid><description>&lt;p&gt;算术平均、几何平均和调和平均在实际生活中应用广泛，只要有度量、有统计、要比较的地方都需要它们，理解它们的差别才能更好地使用它们，不论是设计新指标还是读取报告中的指标，都要知道它指代的真实含义和局限性。&lt;/p&gt;
&lt;p&gt;定义&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;算术平均是n个数的和除以n。&lt;/li&gt;
&lt;li&gt;几何平均是n个数的积开n次方。&lt;/li&gt;
&lt;li&gt;调和平均是n除以n个数的倒数和。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;算术平均：班级中有20人，20个人都参加了数学考试，拿到了20个分数，班级的平均分就是所有人的分数求和除以20。&lt;/li&gt;
&lt;li&gt;算术平均：一个地区有男性人数20万，每个人都有身高，基本落在150厘米到190厘米之间，平均身高就是所有身高求和除以人数。&lt;/li&gt;
&lt;li&gt;几何平均：假设股票连续四天的收益率依次是+100%、-50%、+100%、-50%，平均收益率0，也就是(1+100%)乘以(1-50%)乘以(1+100%)乘以(1-50%)，然后开四次方，等于1，无收益。&lt;/li&gt;
&lt;li&gt;几何平均：人口增长率连续几年下降，求这几年的平均增长率，也是相乘后开方。&lt;/li&gt;
&lt;li&gt;调和平均：一艘船走同一段路程，顺流而下的速度是5米每秒，逆流而上的速度是4米每秒，平均速度是4.4米每秒。&lt;/li&gt;
&lt;li&gt;调和平均：n个并联电路的电阻依次是R1, R2, &amp;hellip;, Rn，等价于用了一个n/(1/R1+1/R2+&amp;hellip;+1/Rn)的电阻。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;动机&lt;/p&gt;
&lt;p&gt;当我们希望用一个数字去刻画相同性质的一组数字的特征的时候，就可能需要平均数。统计学里常常需要做这样的事情，实际生活中，刻画群体特征，也需要这样的一些指标。最近看大模型中F1指标，就是Precision和Recall的调和平均，又想到了这个问题。&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;/p&gt;
&lt;p&gt;三个平均数的大小关系是&lt;/p&gt;
&lt;p&gt;算数平均数 &amp;gt;= 几何平均数 &amp;gt;= 调和平均数&lt;/p&gt;
&lt;p&gt;当n个数完全相同的时候，取到等号。&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;li&gt;两个数值的几何平均从几何上看，一般也有两个角度。第一，当两个数值代表长方形的两条边长时，得到同样面积的正方形的边长就是这个几何平均数。第二，当两个数值代表两条首尾相接共线的线段时，以总线段中点为圆心，总线段一半长为半径做圆，从相接点向圆做垂线，这段垂线段长度就是几何平均数。&lt;/li&gt;
&lt;li&gt;几何平均到三维的几何含义，第一个角度可以说得到相同体积的立方体边长，是积等效均衡，第二个角度可以说三个数的比例均衡。&lt;/li&gt;
&lt;li&gt;调和平均刻画了完成同样一件事情不同方法或者不同角度的效率的平均。&lt;/li&gt;
&lt;li&gt;调和平均的几何意义是面积相同的n个长方形，若各自的底长度不变，得到总面积不变的一个大的长方形的高也就是原来n个高的调和平均。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a class="link" href="https://nicelylit.net/wp-content/uploads/2025/08/three_means.png" target="_blank" rel="noopener"
 &gt;&lt;img class="gallery-image" data-flex-basis="420px" data-flex-grow="175" height="584" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://nicelylit.net/wp-content/uploads/2025/08/three_means-1024x584.png" srcset="https://blog.nicelylit.net/three_means-1024x584_15045663305556180579_hu_cb21d0d36fd6424e.png 800w, https://nicelylit.net/wp-content/uploads/2025/08/three_means-1024x584.png 1024w" width="1024"&gt;&lt;/a&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;li&gt;效率一类倒数可叠加的量求平均用调和平均，比如速度、折射率、电阻并联等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;讨论&lt;/p&gt;
&lt;p&gt;三个平均数在很多时候，从计算的数值上看，差别并不大，似乎不用那么严格影响也不大。比如一个人90分，一个人80分，算数平均是85.00分，几何平均是84.85分，调和平均是84.71分，用哪个也都差不过一分。但一些特殊情况就不那么回事了，比如一个人10分，一个人90分，这个差别就大了，如果有一个0分，几何和调和就都得0分了，或者严格来说调和无意义，0不能取倒数。再比如前面举的收益率的例子，如果用了算数平均就会错误地判断四年的平均增长率是+25%，但实际上四年的平均增长率是零。因此，实际应用中，一定要注意量的实际含义，选择合适的平均数。&lt;/p&gt;</description></item><item><title>微服务（一）</title><link>https://blog.nicelylit.net/posts/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%80/</link><pubDate>Mon, 09 Jun 2025 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%80/</guid><description>&lt;h2 id="概述"&gt;概述
&lt;/h2&gt;&lt;h3 id="什么是微服务"&gt;什么是微服务？
&lt;/h3&gt;&lt;p&gt;微服务是一种软件架构风格，相较于单体架构（整体式架构），应用被拆分为多个小型、独立服务，每个服务围绕特定业务功能构建，独立开发、部署、扩展和维护，服务间通过轻量级的通信机制协作，来组合完成完整应用。微服务和微服务架构是一个意思。每个微服务由各个小型独立团队负责。&lt;/p&gt;
&lt;h3 id="为什么需要微服务"&gt;为什么需要微服务？
&lt;/h3&gt;&lt;p&gt;微服务架构从单体架构（整体式架构）演进而来，经历了从单体架构到面向服务（SOA）架构，再到微服务架构的变化。微服务架构使应用程序更易于扩展和更快地开发，从而加速创新并缩短新功能的上市时间。&lt;/p&gt;
&lt;p&gt;微服务架构具有自治和专用的特点。自治加快了应用的发布速度，让开发变得敏捷、功能扩展更灵活、工具选择更自由、部署更轻松。专用降低了应用功能的耦合性，提高了代码模块的重用，也缩小了故障影响提升了可用性，让应用更具弹性和可伸缩性。&lt;/p&gt;
&lt;h3 id="微服务和云原生是什么关系"&gt;微服务和云原生是什么关系？
&lt;/h3&gt;&lt;p&gt;微服务架构是云原生架构的基石，除了微服务之外，云原生的关键技术还包括容器、Serverless和DevOps。微服务架构的核心目的是通过模块化来提升应用研发的效率，而云原生的核心目的是最大化地利用云平台的特性来实现高效的资源利用、快速迭代和自动化运维，两者相辅相成。&lt;/p&gt;
&lt;h3 id="构建微服务有哪些典型的服务组件"&gt;构建微服务有哪些典型的服务组件？
&lt;/h3&gt;&lt;p&gt;典型的微服务组件可以从七个角度来看：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;业务核心组件：服务实例、服务接口&lt;/li&gt;
&lt;li&gt;通信组件：API网关、RPC通信（同步）、消息队列（异步）、定时任务（异步）、注册中心&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;/ol&gt;
&lt;h2 id="参考阅读"&gt;参考阅读
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;a class="link" href="https://aws.amazon.com/cn/microservices/" target="_blank" rel="noopener"
 &gt;什么是微服务？&lt;/a&gt;AWS&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.aliyun.com/getting-started/what-is/what-is-microservice" target="_blank" rel="noopener"
 &gt;什么是微服务？&lt;/a&gt;阿里云&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.ruanyifeng.com/blog/2022/04/microservice.html" target="_blank" rel="noopener"
 &gt;微服务是什么？&lt;/a&gt;阮一峰&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.amazonaws.cn/knowledge/what-is-cloud-native/" target="_blank" rel="noopener"
 &gt;什么是云原生？&lt;/a&gt;AWS&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.amazonaws.cn/knowledge/what-is-cloud-native/" target="_blank" rel="noopener"
 &gt;云原生架构&lt;/a&gt; AWS&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.aliyun.com/getting-started/what-is/what-is-cloud-native#3749b362e7doq" target="_blank" rel="noopener"
 &gt;什么是云原生？&lt;/a&gt;阿里云&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://cloud.google.com/learn/what-is-cloud-native?hl=zh-CN" target="_blank" rel="noopener"
 &gt;什么是云原生？&lt;/a&gt;Google Cloud&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>虚拟化技术（二）</title><link>https://blog.nicelylit.net/posts/%E8%99%9A%E6%8B%9F%E5%8C%96%E6%8A%80%E6%9C%AF%E4%BA%8C/</link><pubDate>Sat, 31 May 2025 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E8%99%9A%E6%8B%9F%E5%8C%96%E6%8A%80%E6%9C%AF%E4%BA%8C/</guid><description>&lt;h3 id="虚拟化有哪些核心概念"&gt;虚拟化有哪些核心概念？
&lt;/h3&gt;&lt;h4 id="服务器的虚拟化"&gt;服务器的虚拟化
&lt;/h4&gt;&lt;p&gt;虚拟化的核心就是虚拟机监视器（Virtual Machine Monitor, VMM/Hypervisor），它是一个虚拟化层，将硬件抽象成虚拟计算资源，分配给虚拟机使用。硬件是一台物理机，有些情况下也被称作宿主机（Host Machine）。虚拟机运行在VMM之上，也被称作客户机（Guest Machine）。在虚拟机上才会安装操作系统（OS），用以通过服务和应用供用户操作完成一些特定的任务。VMM直接运行在硬件上的也被称作裸金属型的。此外，还有一种运行在操作系统上的被称作宿主型的。&lt;/p&gt;
&lt;p&gt;服务器的虚拟化的抽象资源包括处理器（CPU或者GPU）、内存和I/O，CPU和内存用于通用计算，GPU用于图形计算、并行计算、智能计算，I/O用于扩展外部设备，包括存储和网络等。从功能上看，虚拟机监视器提供虚拟机的创建、资源的隔离和调度、虚拟化的设备管理等三项功能。此外，为保障虚拟资源的执行效率，降低虚拟化的损耗，VMM的很大一部分工作是性能优化，许多的架构设计方案都是为了达成这个目标。&lt;/p&gt;
&lt;h5 id="cpu的虚拟化"&gt;CPU的虚拟化
&lt;/h5&gt;&lt;p&gt;物理CPU虚拟化的产物是虚拟CPU，或者vCPU，物理CPU也被称作pCPU。CPU的核心功能是接受指令请求和处理指令，串行接受指令请求和处理指令的单元是线程。模拟出vCPU的主要工作就是构造出线程的部件和功能，包括控制器、运算器、寄存器和高速缓冲存储器等部件，取指令、翻译指令和执行指令等功能。vCPU的数量取决于三个因素，物理CPU的数量、一个物理CPU内的核心数、一个核心的超线程数。&lt;/p&gt;
&lt;p&gt;每个虚拟机可以被分配所有的vCPU，也可以分配部分，VMM负责vCPU的调度，也就是把vCPU上的指令转换为物理CPU上的指令。依据虚拟化方法的不同，有全虚拟化、硬件辅助虚拟化和半虚拟化。虚拟化方法需要VMM和虚拟机甚至是客户机的操作系统能够配合和兼容。全虚拟化也就是完全在软件层面模拟接受指令请求和处理指令的过程，这种情况下陷入内核执行的指令数量很多，因此性能较差。硬件辅助虚拟化是针对特权指令才陷入到特权级别执行，在CPU中实现了虚拟机的上下文切换和状态管理，提升了性能。半虚拟化需要修改操作系统来支持hypercall，使得客户机的操作系统也能运行在物理CPU的特权模式下。&lt;/p&gt;
&lt;h5 id="内存的虚拟化"&gt;内存的虚拟化
&lt;/h5&gt;&lt;p&gt;内存虚拟化的产物是虚拟内存，但实际上虚拟内存在没提虚拟化的时候，就已经有了。虚拟内存是为了给每个进程分配逻辑一致的独立的地址空间，有别于物理内存。在内存的虚拟化中是为了给每个虚拟机分配逻辑一致的独立的地址空间，在进程地址空间之外又加了一层，这样算下来是四个地址：客户机的虚拟地址（GVA）、客户机的物理地址（GPA）、VMM的虚拟地址（HVA）、VMM的物理地址（HPA）。不管几层，虚拟化方法的核心功能都是类似的，都是地址映射，实现机制就是通过页表分页管理地址映射，提高效率的方法就是减少映射转换的次数。&lt;/p&gt;
&lt;p&gt;可被虚拟化的内存资源数量受到GPU支持能力、主板内存插槽数量、单条内存容量、内存类型等因素的限制。虚拟内存的容量受到物理内存容量和内存超分配技术的限制。&lt;/p&gt;
&lt;h5 id="io的虚拟化"&gt;I/O的虚拟化
&lt;/h5&gt;&lt;p&gt;除去CPU和内存外，其他的设备都算作I/O，I/O往往是通过接口外挂上去后，由设备控制器与CPU和内存进行控制权、状态和数据的交换的。控制是双向的，由设备发起的是中断请求，由CPU发起的是设备访问。中断请求和设备访问都会下发指令，使得另一方的状态发生变化，同时携带要传递的数据。为提高CPU的效率，对于大量的数据移动，会通过DMA完成。&lt;/p&gt;
&lt;p&gt;虚拟化的目的是为了分隔资源和利用资源，梳理来看这里面的资源有设备、接口、总线和中断处理器/RTC/DMA等四项。虚拟化设备需要单独拎出来看，不同的设备虚拟化的方法有所不同，共性是接入CPU和内存的方式，以及通信和控制的方式。接口用于接入设备，物理实体上是各种端口和插槽，从通信机制上看，一种是端口I/O，另一种是内存映射I/O。总线用于设备和CPU、内存间的通信，连接了设备和CPU、内存。接口和总线的虚拟化，就是接入配置和通信机制的虚拟化。通信机制的核心部分是中断处理器、RTC和DMA完成的，虚拟化通信机制的主要部分就是虚拟化它们。&lt;/p&gt;
&lt;p&gt;具体到虚拟化的解决方案所要讨论的，就是VMM中有没有必要实现某种资源的虚拟化，某种资源的虚拟化可否放在用户空间实现。&lt;/p&gt;
&lt;p&gt;虚拟I/O的设备数量受限于物理主机的I/O带宽（网络带宽、存储带宽等）、VMM的参数配置（资源类型、设备配置等）、物理设备虚拟化的支持情况、虚拟设备的类型和数量（虚拟网卡、虚拟控制器等）等因素。&lt;/p&gt;
&lt;h5 id="gpu的虚拟化"&gt;GPU的虚拟化
&lt;/h5&gt;&lt;p&gt;从硬件上看，GPU通常是显卡的一部分，显卡属于外部设备，需要跟CPU和内存配合工作。CUDA编程让GPU能够支持通用的并行计算（GPGPU），在CUDA编程之前GPU主要通过图形接口执行图形渲染任务。随着GPU被人工智能领域用来训练深度学习的神经网络，这样的计算也开始被称作智能计算。&lt;/p&gt;
&lt;p&gt;物理GPU虚拟化的产物是vGPU，将vGPU分配不同的虚拟机使用，为了保障性能，也有直通的资源独占的方式。由于显卡的外设属性，也需要考虑设备驱动，可GPU又不同于其他的外设，具备计算的能力，因此GPU的核心在处理计算任务的时候也被看作是服务器，而不仅仅是向CPU发起中断请求的客户端。在英伟达vCUDA核心的设计上，有三个模块客户端、服务端和管理端。客户端是安装在虚拟机上的显卡驱动程序，负责对CUDA应用程序API的拦截、解析、包装、代理请求和代理响应。服务端是位于一个特权虚拟机中，负责接受管理端的资源注册，以及为客户端的请求提供服务。管理端才是GPU的管理器，位于特权域VMM中，负责GPU的动态调度、负载均衡和故障恢复。&lt;/p&gt;
&lt;p&gt;物理GPU的数量受限于主板显卡的卡槽数量、显卡的类型、总线带宽和电源供应等因素。虚拟GPU的数量受限于物理显卡的数量、VMM的配置和调度策略、GPU和内存资源等因素。&lt;/p&gt;
&lt;h4 id="存储的虚拟化"&gt;存储的虚拟化
&lt;/h4&gt;&lt;p&gt;存储虚拟化需要从两个角度来看，一个是用户的使用角度，另一个是物理设备的管理。&lt;/p&gt;
&lt;p&gt;从用户使用角度看，有直接挂载磁盘的块存储，也有直接挂载文件系统的文件存储，还有更扁平化的对象存储。&lt;/p&gt;
&lt;p&gt;块存储相当于一块空白的磁盘，支持创建、初始化、挂载、卸载等操作。创建的结果是一个虚拟磁盘，初始化的结果是要对虚拟磁盘进行分区或者格式化，并创建文件系统。在分布式环境中，创建和初始化的结果跨越了单机的限制，但实质上与本地磁盘管理要做的事情类似，都是元数据的管理。虚拟磁盘的挂载和卸载也是元数据的操作。除了元数据操作外，实际数据的读写需要遵从一些分布式的网络存储协议，并且要做好元数据中分区、块和实际物理存储之间的映射关系。操作系统使用的存储协议需要考虑网络环境，iSCSI（Internet Small Computer System Interface）是基于IP的存储协议，适用于局域网和广域网连接的块存储；Fibre Channel是专用于存储区域网络的高速传输协议；NVMe-oF是基于NVMe的网络协议。&lt;/p&gt;
&lt;p&gt;文件存储不再增加分区和块的概念，元数据中直接管理文件和文件夹。网络文件存储协议有NFS（Network File System）、CIFS/SMB（Common Internet File System/Server Message Block）和AFP（Apple Filing Protocol）。对象存储相比文件存储更加扁平化，元数据管理存储空间（Bucket）和对象。对象存储首先是亚马逊AWS的S3现实，也被称作S3协议。&lt;/p&gt;
&lt;p&gt;从物理设备管理的角度看，存储虚拟化分三个层次，最底层是存储资源的标准化接入，中间层是统一的数据管理，再上层是虚拟存储整合成资源池。存储的资源主要是磁盘，从介质上是两类，机械硬盘（Hard Drive Disk, HDD）和固态硬盘（Solid State Drive, SSD），此外，还有混合固态硬盘和磁带。存储资源接入主机可以是单块内置或外置，也可以是多块构成的存储阵列。机械硬盘接入主要通过SATA接口，也可以通过SAS（Serial Attached SCSI）扩展器，或者网络协议iSCSI和FC。固态硬盘接入主要通过PCIe槽位，利用NVMe访问和传输协议控制硬盘读写。同一块硬盘通过分区形成不同的存储盘，多块存储盘的物理分区通过逻辑卷组形成跨磁盘的逻辑卷（虚拟分区）。集中的块存储服务通过专门的网络结构和网络协议搭建形成SAN（Storage Area Network）存储。集中的文件存储服务通过瘦服务器将磁盘阵列管理起来形成NAS（Network Attached Storage）存储。通过主机服务器来访问存储的架构是以服务器为中心的架构，成为直连DAS（Directed Attached Storage）存储。虚拟主机也可以接入本地存储镜像。&lt;/p&gt;
&lt;h4 id="网络的虚拟化"&gt;网络的虚拟化
&lt;/h4&gt;&lt;p&gt;网络由网络节点和传输介质组成，此外在节点之间传输信息需要协议进行控制数据传输。对网络进行虚拟化就是对网络节点、传输介质和传输协议进行设计和实现。虚拟化的网络称作Overlay网络，物理网络称作Underlay网络。&lt;/p&gt;
&lt;p&gt;网络节点按照功能分主要有五种客户终端、主机服务器、交换机、路由器、网关。对于网络实现来讲终端和服务器都能归结到网卡（NIC）上。传输介质有同轴电缆、双绞线、光纤、无线电波等。传输协议按照网络分层是底层在外层，上层在里层，一层包一层，封装和拆包的过程恰好相反。&lt;/p&gt;
&lt;p&gt;虚拟化后的网络节点也被称作网元，都通过软件定义实现。虚拟主机通过挂载虚拟网卡进行网络通信，两台虚拟机之间通过虚拟交换机通信，虚拟网卡和虚拟交换机之间通过虚拟链路通信。跨物理机的虚拟机之间的通信除了需要隧道封装技术外，还需要经过虚拟地址和物理地址的映射转换的NAT网关。Overlay的隧道封装技术主流的包括两种GRE（Generic Routing Encapsulation）和VxLAN（Virtual eXtensible LAN）。&lt;/p&gt;</description></item><item><title>虚拟化技术（一）</title><link>https://blog.nicelylit.net/posts/%E8%99%9A%E6%8B%9F%E5%8C%96%E6%8A%80%E6%9C%AF%E4%B8%80/</link><pubDate>Fri, 04 Apr 2025 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E8%99%9A%E6%8B%9F%E5%8C%96%E6%8A%80%E6%9C%AF%E4%B8%80/</guid><description>&lt;h3 id="为什么需要虚拟化"&gt;为什么需要虚拟化？
&lt;/h3&gt;&lt;p&gt;虚拟化可以对同一物理资源进行隔离处理，分给不同的用户使用，避免资源的闲置，提高资源的利用率。虚拟化是云计算的核心技术，云基础设施服务提供商对虚拟化的资源进行池化管理，通过资源的隔离和编排使得租用的用户能够在分钟级的时间内低成本获取到所需的资源，也提升了业务部署的灵活性。&lt;/p&gt;
&lt;p&gt;归纳起来有以下几点：1) 提升资源利用率，2) 降低成本，3) 屏蔽硬件差异，4) 提供资源隔离，5) 部署更灵活，6) 提升容灾能力。&lt;/p&gt;
&lt;h3 id="虚拟化技术为何得以持续发展"&gt;虚拟化技术为何得以持续发展？
&lt;/h3&gt;&lt;p&gt;资源的标准化和规模化提升了资源的获取效率和资源的利用率，相较于非标准化和小规模的资源建设和使用来说有了利润。算力是大数据处理、信息服务、人工智能的要素之一，应用场景涉及的领域广阔。云基础设施服务提供商能专注在标准化和规模化算力和平台建设上，成为整个社会进入智能化的推动引擎，而各领域的消费者能够专注在业务开发上面而减轻设施的维护开销。&lt;/p&gt;
&lt;h3 id="什么是虚拟化"&gt;什么是虚拟化？
&lt;/h3&gt;&lt;p&gt;虚拟化是一种资源管理技术，它将单台物理计算资源抽象、转换后呈现，分割成多台虚拟机、操作系统或者容器。&lt;/p&gt;
&lt;p&gt;虚拟化的资源类型可以从多种层次来看，最为核心和基础的是计算（CPU/GPU/内存）、存储（磁盘）、网络、I/O。从云服务商的资产管理角度看，有服务器、数据中心。从消费者角度看，有应用、数据、桌面。&lt;/p&gt;
&lt;p&gt;多数时候人们所说的虚拟化就是指服务器的虚拟化，简单理解就是在一台物理机上运行多台虚拟机。&lt;/p&gt;
&lt;h3 id="虚拟化和容器化有什么区别"&gt;虚拟化和容器化有什么区别？
&lt;/h3&gt;&lt;p&gt;两者之所以值得比较，是因为虚拟化和容器化的结果都是一个可供应用运行的计算环境。从虚拟的层次来看，虚拟化通常是直接对物理资源的虚拟化，而容器化是&lt;a class="link" href="https://en.wikipedia.org/wiki/Virtualization#Containerization" target="_blank" rel="noopener"
 &gt;操作系统级别的虚拟化&lt;/a&gt;，也有一种说法是&lt;a class="link" href="https://aws.amazon.com/what-is/virtualization/?nc1=h_ls" target="_blank" rel="noopener"
 &gt;应用级别的虚拟化&lt;/a&gt;。虚拟机上可以安装容器，但反之却不可以。&lt;/p&gt;
&lt;p&gt;容器化来自于操作系统的一个功能，内核允许存在多个隔离的用户空间的实例，这个实例被称作容器，或者分区，或者虚拟环境（VEs），又或者监狱（FreeBSD jails）。运行在操作系统中的程序可以看到计算机所有的资源，包括连接的设备、文件和文件夹、网络共享、CPU性能等，而运行在容器中的程序只能看到容器的内容和分配给容器的设备。从使用场景来看，容器化更轻量，更易扩展，更容易移植。容器化的代表是Docker。&lt;/p&gt;
&lt;h3 id="虚拟化和模拟仿真有什么区别"&gt;虚拟化和模拟、仿真有什么区别？
&lt;/h3&gt;&lt;p&gt;虚拟化（Virtualization）、模拟（Simulation）和仿真（Emulation）都是为了构建一个运行环境，一般都是为了构建一个区别于物理实体的逻辑实体。不同之处在于构建的目的，虚拟化是为了提升硬件资源的利用率，通常是用于生产环境的，而模拟和仿真是为了测试和研究，性能往往较差。&lt;/p&gt;</description></item><item><title>MaxCompute SQL的数组字段的合并</title><link>https://blog.nicelylit.net/posts/maxcompute-sql%E7%9A%84%E6%95%B0%E7%BB%84%E5%AD%97%E6%AE%B5%E7%9A%84%E5%90%88%E5%B9%B6/</link><pubDate>Sun, 09 Feb 2025 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/maxcompute-sql%E7%9A%84%E6%95%B0%E7%BB%84%E5%AD%97%E6%AE%B5%E7%9A%84%E5%90%88%E5%B9%B6/</guid><description>&lt;p&gt;在处理字段分组聚合时，如果需要多层次的聚合，有时还需要在新的层次进行该字段的合并，但SQL语言并没有提供直接的语法和简单的函数进行支持，经过&lt;code&gt;MAP_UNION&lt;/code&gt;的去重操作，结合其他的一些数组转字典的操作，可以实现该功能。具体来说，已知表1中有两行数据&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;c1&lt;/th&gt;
 &lt;th&gt;c2&lt;/th&gt;
 &lt;th&gt;c3&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;s1&lt;/td&gt;
 &lt;td&gt;d1&lt;/td&gt;
 &lt;td&gt;r1,r2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;s1&lt;/td&gt;
 &lt;td&gt;d2&lt;/td&gt;
 &lt;td&gt;r1,r3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;怎么得到表2&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;c1&lt;/th&gt;
 &lt;th&gt;c2&lt;/th&gt;
 &lt;th&gt;c3&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;s1&lt;/td&gt;
 &lt;td&gt;d1,d2&lt;/td&gt;
 &lt;td&gt;r1,r2,r3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;得到表2的数据，最直接的思路是通过&lt;code&gt;GROUP BY&lt;/code&gt;进行分组聚合，c2列可以用过&lt;code&gt;COLLECT_SET&lt;/code&gt;函数外加&lt;code&gt;ARRAY_JOIN&lt;/code&gt;实现。对于c3列，需要先&lt;code&gt;SPLIT&lt;/code&gt;成数组，然后再分组合并。查询&lt;a class="link" href="https://help.aliyun.com/zh/maxcompute/user-guide/built-in-functions/?spm=a2c4g.11186623.help-menu-27797.d_2_1_3_2.ca5647a5Ye54gn&amp;amp;scm=20140722.H_455448._.OR_help-T_cn~zh-V_1" target="_blank" rel="noopener"
 &gt;MaxCompute SQL的函数手册&lt;/a&gt;，在数组方面并没有这样的功能。随后看到&lt;code&gt;MAP_UNION&lt;/code&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="cl"&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;d1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;r1,r2&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c3&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNION&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;d2&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;r1,r3&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c3&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="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="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ARRAY_JOIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COLLECT_SET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ARRAY_JOIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAP_KEYS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAP_UNION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAP_FROM_ARRAYS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPLIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ARRAY_REPEAT&lt;/span&gt;&lt;span class="p"&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 class="k"&gt;SIZE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPLIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))))),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&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="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_table&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="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&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="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;另外一种思路是设法让表1回到表3的状态，然后再对c3通过&lt;code&gt;COLLECT_SET&lt;/code&gt;函数外加&lt;code&gt;ARRAY_JOIN&lt;/code&gt;来实现。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;c1&lt;/th&gt;
 &lt;th&gt;c2&lt;/th&gt;
 &lt;th&gt;c3&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;s1&lt;/td&gt;
 &lt;td&gt;d1&lt;/td&gt;
 &lt;td&gt;r1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;s1&lt;/td&gt;
 &lt;td&gt;d1&lt;/td&gt;
 &lt;td&gt;r2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;s1&lt;/td&gt;
 &lt;td&gt;d2&lt;/td&gt;
 &lt;td&gt;r1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;s1&lt;/td&gt;
 &lt;td&gt;d2&lt;/td&gt;
 &lt;td&gt;r3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在MaxCompute SQL中，可以用&lt;a class="link" href="https://help.aliyun.com/zh/maxcompute/user-guide/lateral-view" target="_blank" rel="noopener"
 &gt;&lt;code&gt;LATERAL VIEW&lt;/code&gt;&lt;/a&gt;语句结合&lt;a class="link" href="https://help.aliyun.com/zh/maxcompute/user-guide/explode?spm=a2c4g.11186623.help-menu-search-27797.d_0" target="_blank" rel="noopener"
 &gt;&lt;code&gt;EXPLODE&lt;/code&gt;&lt;/a&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="cl"&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&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="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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;d1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;r1,r2&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c3&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNION&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;d2&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c2&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;r1,r3&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c3&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="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="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ARRAY_JOIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COLLECT_SET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&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="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ARRAY_JOIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COLLECT_SET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;TRIM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c3_item&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&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="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_table&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="cl"&gt;&lt;span class="k"&gt;LATERAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VIEW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXPLODE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPLIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c3_item&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="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c1&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="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;</description></item><item><title>项目总结模板</title><link>https://blog.nicelylit.net/posts/%E9%A1%B9%E7%9B%AE%E6%80%BB%E7%BB%93%E6%A8%A1%E6%9D%BF/</link><pubDate>Mon, 23 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E9%A1%B9%E7%9B%AE%E6%80%BB%E7%BB%93%E6%A8%A1%E6%9D%BF/</guid><description>
 &lt;blockquote&gt;
 &lt;p&gt;1. &lt;strong&gt;&lt;em&gt;为什么需要项目总结模板呢？&lt;/em&gt;&lt;/strong&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;p&gt;非功利务虚的目的，为了反思自身在每个项目中的成长，能力的增长和知识的积累。&lt;/p&gt;
&lt;p&gt;2. &lt;em&gt;&lt;strong&gt;做项目总结应当注意什么呢？&lt;/strong&gt;&lt;/em&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;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;1. &lt;strong&gt;我在项目中的主要职责是什么？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;第一要落在动词上：主导、负责、带领、指导、评审，参与就可以不用写了，而提议、设计、编写、测试等是具体的工作，是展开时要说的。
第二要落在对象上，即人与事，要具体化和量化，人要有数字，事要有范围，说清楚边界与局限。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;2. &lt;strong&gt;我在项目中取得了哪些关键成果？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;围绕攻克的难题与技术挑战，对项目上的事与人产生正面的效果，提升团队的工作效率。
从事上讲，是创新、创意，寻找全新视角、推动僵局。从人上讲，是获得客户的认可、上级的认可、成为团队的放大器。上级不要局限为自己的直属领导，也包括其他与自己合作的级别更高者，级别更高者往往有更高的能力与权力，即便都没有，也还有更多的信息获取渠道。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;描述方法一般遵循STAR法则，情形、任务、行动和结果。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;3. &lt;strong&gt;我在项目中取得那些关键的成果有什么成功的经验可以分享？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;围绕能力，即专业能力与领导力，而不是知识性的结论。知识性的总结功夫应该落在平时，而非项目总结的时候。知识往往很容易在网络和书籍中获得，将个人知识结构化、体系化重点要放在对领域问题的认识上。对领域问题的认识更加深入往往是做项目的副产品，要以具体的例子来说明能力。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;专业能力比方说编码能力、设计能力、践行最佳实践的能力，可以讲方法论。要以专业能力好为论点，用具体的领域知识来举例说明。能力很抽象，需要具体的例子。
领导力就以亚马逊的领导力准则来说即可，比方说沟通能力可以在敢于谏言服从大局方面举例，分析能力和推断能力可以在刨根问底方面举例。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;4. &lt;strong&gt;我在项目中有哪些做得不够好值得提升的方面？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;与前一个问题本质相同，一体两面，也要围绕能力，即专业能力与领导力。不同于前面一个问题，这个问题还可以谈谈打算采取怎样的计划与行动来提升。&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a class="link" href="https://amazon.jobs/content/en/our-workplace/leadership-principles" target="_blank" rel="noopener"
 &gt;亚马逊领导力准则&lt;/a&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Customer Obsession&lt;/li&gt;
&lt;li&gt;Ownership&lt;/li&gt;
&lt;li&gt;Invent and Simplify&lt;/li&gt;
&lt;li&gt;Are Right, A Lot&lt;/li&gt;
&lt;li&gt;Learn and Be Curious&lt;/li&gt;
&lt;li&gt;Hire and Develop the Best&lt;/li&gt;
&lt;li&gt;Insist on the Highest Standards&lt;/li&gt;
&lt;li&gt;Think Big&lt;/li&gt;
&lt;li&gt;Bias for Action&lt;/li&gt;
&lt;li&gt;Frugality&lt;/li&gt;
&lt;li&gt;Earn Trust&lt;/li&gt;
&lt;li&gt;Dive Deep&lt;/li&gt;
&lt;li&gt;Have Backbone; Disagree and Commit&lt;/li&gt;
&lt;li&gt;Deliver Results&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;新增的最后两条Strive to be Earth’s Best Employer和Success and Scale Bring Broad Responsibility个人不是很认同值得与前十四条并列，它们不像前十四条一样属于行之有效的简单能力，而是复合能力，故而省去。&lt;/p&gt;</description></item><item><title>谈解决问题的方法：从偷跑流量谈起</title><link>https://blog.nicelylit.net/posts/%E8%B0%88%E8%A7%A3%E5%86%B3%E9%97%AE%E9%A2%98%E7%9A%84%E6%96%B9%E6%B3%95%E4%BB%8E%E5%81%B7%E8%B7%91%E6%B5%81%E9%87%8F%E8%B0%88%E8%B5%B7/</link><pubDate>Thu, 28 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E8%B0%88%E8%A7%A3%E5%86%B3%E9%97%AE%E9%A2%98%E7%9A%84%E6%96%B9%E6%B3%95%E4%BB%8E%E5%81%B7%E8%B7%91%E6%B5%81%E9%87%8F%E8%B0%88%E8%B5%B7/</guid><description>
 &lt;blockquote&gt;
 &lt;p&gt;&lt;em&gt;2018年01月02日分享于pFinder邮件组，作为《&lt;a class="link" href="https://blog.nicelylit.net/posts/%E5%8F%82%E5%8A%A0%E5%B7%A5%E4%BD%9C%E4%BB%A5%E6%9D%A5%E7%9A%84%E6%88%90%E9%95%BF%E4%B8%80/" &gt;参加工作以来的成长（一）&lt;/a&gt;》的附件。&lt;/em&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;高效快速地找到一个复杂系统中问题发生的根本原因，是每个工程师的必修课。当我们面对这样一些问题时，往往不知道复杂系统的全貌和所有细节，这种状况下应该怎么办？对于一个训练有素的工程师，肯定不是头脑发热，不知所措，或者到处去找人询问，而是从有限的问题本身出发，不断地反问自己，如果要找到问题的答案需要知道什么信息，层层地挖掘出更多的相关信息，从而找到问题的症结。这是一个结合深度搜索和广度搜索的技能，并且需要恰到好处的结合，想得太深可能会让自己陷入一个死胡同，关注的太广可能会让自己迷失在太多的细节当中，而忘记了来时的路，和最终要达成的目的。&lt;/p&gt;
&lt;p&gt;前段时间仅花了两天的时间就找到了一个手机应用偷跑流量的根本原因，着实兴奋了一整天。解决了问题是一方面，发现自己在高效地找到一个完全不熟悉的问题发生的根本原因方面有了长进是更重要的另一方面。一直思量着把这个过程记录下来，却不得空，今天总算可以静下心来写写了。&lt;/p&gt;
&lt;p&gt;我被委派的任务是调查一个手机应用偷跑流量的问题，具体来讲，测试人员测试发现公司的安卓手机应用在预装的定制机上，未启动的情况下就已经开始有流量消耗，并且测试人员反映发现偷跑流量时有跟个推（国内的一个推送服务）相关的日志。&lt;/p&gt;
&lt;p&gt;收到任务时，我的情况是，1) 对安卓只有一年的开发经验，而且是断断续续地利用业余时间学习；2) 对安卓的推送机制基本不了解，仅对推送的过程了解一些；3) 对如何预装应用完全不清楚；4) 对公司复杂的手机应用知道一些跟WebView相关的部分，估摸着看过的代码最多也就几千行，而整个应用仅核心两个包的代码就有十几万行，附属提供功能的依赖包至少有一百多个，整个依赖的包有近两千个，整个应用的代码量在百万行甚至上千万行的数量级上，整个应用的完整的构建需要两个多钟头（当然局部的修改不会进行完整的构建）。&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;li&gt;对于未启动前的阶段是安装阶段，这个是常识，那么安装阶段安卓平台做了哪些事情呢？&lt;/li&gt;
&lt;li&gt;如果非预装应用也有这样的问题，那么问题也一定出在安装阶段，同样也需要知道问题3的答案。&lt;/li&gt;
&lt;li&gt;如果个推在偷跑流量，那么一定得有后台运行的服务被启动，否则谁去打印日志呢？进一步这个服务为什么会在安装阶段被启动？这两个疑问也都指向了问题3。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在清楚问题和我所处的状况下，有正常逻辑的人都不难问出以上的问题。将这些问题用纸（或者云笔记）记录下来极为重要，因为尝试着去回答每个问题时，你还会问出自己更多的问题，更多的问题增加了广度，让自己对问题了解得更多，但同时也很容易丢失目标，不要让自己大脑不停去重复想前面提出的5个问题，大脑会很累。写下来还有个好处是一天没有解决完的问题，第二天可以快速重新捡起来。&lt;/p&gt;
&lt;p&gt;说完写下来的重要性（重要的事情总要说三遍），接下来回头看下提出的5个问题的特点。第一，它们跟原始问题中的几个关键因素直接相关，比如预装、流量消耗、打印日志、个推服务。第二，它们是对原始问题的分解，彼此之间有很强的逻辑关系，这一方面可以让思考变得严密，另一方面也更加具体。第三，它们运用我有限的常识和对比的方法，引入了更多的我熟悉但还没有透彻掌握的因素，比如启动前的步骤只有安装，打印日志一定有后台服务运行。总结下来，提问的时候，首先要寻找原始问题中的关键词，然后结合自己的常识，运用逻辑和对比的方法，引入更多的因素，分解出更多的问题。&lt;/p&gt;
&lt;p&gt;除了将分解的5个问题写到一页纸上以外，还要另外新建5页纸，将5个问题分别作为5页纸的标题，为后续的问题分解、搜索和寻找答案做记录。&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;li&gt;预装应用被装在了设备的哪个目录下？&lt;/li&gt;
&lt;li&gt;非预装应用被装在了设备的哪个目录下？&lt;/li&gt;
&lt;li&gt;预装应用有哪些区别于非预装应用的特点？&lt;/li&gt;
&lt;li&gt;如何做非预装应用的对比测试？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;提出这些问题的动机和方法同提出前面5个问题的动机和方法类似，我进行了第二层的深度分解。这一层的问题进一步具体，同样需要用纸记录下来，逐个地回答。同一层级的问题之间有个特点，前面有提到，就是彼此之间有很强的逻辑关系。可能其中的某几个问题还比较抽象，但只要有一个问题足够具体，利用现有的认知做简单的搜索可以得到答案，那么就不必要进一步加深分解的层次，以免节外生枝，消耗不必要的脑力。&lt;/p&gt;
&lt;p&gt;拿上面分解的6个问题来看，问题1和6都比较抽象，剩下的很具体，尝试着回答了2到5，那么会把1和6更佳的具体化，至少在自己的大脑中是如此。相比于3到5，问题2也显得不够具体，所以先回答3到5是个不错的想法。&lt;/p&gt;
&lt;p&gt;回答未知的问题，以前的人们只能翻书，效率很低，现在有搜索引擎，虽然很快，但也需要能有效利用。这就要提到如何增强自己的搜索能力，如何综合利用多个搜索引擎的结果，如何快速定位自己关心问题的答案。回答这几个问题同样可以重复前面采用的方法，进行问题分解，为避免跑题，在此只列举一些我个人的经验。首先，用中文关键词在百度或者Bing中搜索，搜索出来的答案可以辅助拓展关键词，但不可以全用，大多数时候不准确，不全面。其次，准确地将关键词翻译成英文，在Bing或者Google上搜索，高质量的答案一般来自于官方文档、Stackoverflow、Google论坛，博客上面的答案可以用来辅助拓展关键词和充实答案。再有，学会关键词转换，转换关键词的过程其实是转换思考问题角度的过程，在很多山穷水尽的时候，转换思路或许能柳暗花明。还有，启用搜索引擎的高级搜索功能，只找某几个网站的搜索结果，或者要求关键词严格匹配，直接滤除掉低质量的结果，减少干扰。最后，浏览搜索结果，看相关度的时候，要始终想着要解决的问题，以免被带偏。问题越具体，越容易找到相关度高的结果。&lt;/p&gt;
&lt;p&gt;在百度中搜索以下两组关键词：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.baidu.com/s?wd=%E5%AE%89%E5%8D%93%20%E9%A2%84%E8%A3%85%E5%BA%94%E7%94%A8%20%E5%AE%89%E8%A3%85%E7%9B%AE%E5%BD%95&amp;amp;rsv_spt=1&amp;amp;rsv_iqid=0x816ee9100000c646&amp;amp;issp=1&amp;amp;f=8&amp;amp;rsv_bp=1&amp;amp;rsv_idx=2&amp;amp;ie=utf-8&amp;amp;rqlang=cn&amp;amp;tn=baiduhome_pg&amp;amp;rsv_enter=1&amp;amp;oq=%25E5%25AE%2589%25E5%258D%2593%2520%25E9%259D%259E%25E9%25A2%2584%25E8%25A3%2585%25E5%25BA%2594%25E7%2594%25A8%2520%25E5%25AE%2589%25E8%25A3%2585%25E7%259B%25AE%25E5%25BD%2595&amp;amp;inputT=9585&amp;amp;rsv_t=8174w5l%2Fz4iozxSft4mO9wSazB59pk51FAXCiujs89KnLK2upQfXvEXnvMQYt%2FPkt7P2&amp;amp;rsv_pq=801f451a0000d39a&amp;amp;rsv_sug3=46&amp;amp;bs=%E5%AE%89%E5%8D%93%20%E9%9D%9E%E9%A2%84%E8%A3%85%E5%BA%94%E7%94%A8%20%E5%AE%89%E8%A3%85%E7%9B%AE%E5%BD%95" target="_blank" rel="noopener"
 &gt;安卓 预装应用 安装目录&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.baidu.com/s?wd=%E5%AE%89%E5%8D%93%20%E5%BA%94%E7%94%A8%20%E5%AE%89%E8%A3%85%E7%9B%AE%E5%BD%95&amp;amp;rsv_spt=1&amp;amp;rsv_iqid=0x816ee9100000c646&amp;amp;issp=1&amp;amp;f=8&amp;amp;rsv_bp=1&amp;amp;rsv_idx=2&amp;amp;ie=utf-8&amp;amp;rqlang=cn&amp;amp;tn=baiduhome_pg&amp;amp;rsv_enter=0&amp;amp;oq=%25E5%25AE%2589%25E5%258D%2593%2520%25E9%25A2%2584%25E8%25A3%2585%25E5%25BA%2594%25E7%2594%25A8%2520%25E5%25AE%2589%25E8%25A3%2585%25E7%259B%25AE%25E5%25BD%2595&amp;amp;inputT=1030&amp;amp;rsv_t=85c8VCn3jzp066CfUiu208IrrRmlbNuOrGBjNI1DB5Vn0KGoaushNVtF1aRY%2BmOjO4Nk&amp;amp;rsv_pq=aaef80ef0000d264&amp;amp;rsv_sug3=48&amp;amp;rsv_sug1=32&amp;amp;rsv_sug7=000&amp;amp;bs=%E5%AE%89%E5%8D%93%20%E9%A2%84%E8%A3%85%E5%BA%94%E7%94%A8%20%E5%AE%89%E8%A3%85%E7%9B%AE%E5%BD%95" target="_blank" rel="noopener"
 &gt;安卓 应用 安装目录&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在Bing上搜索以下三组关键词：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://cn.bing.com/search?q=Android%20preload%20application%20installation%20location&amp;amp;qs=n&amp;amp;form=QBRE&amp;amp;sp=-1&amp;amp;pq=android%20preload%20application%20installation%20location&amp;amp;sc=0-49&amp;amp;sk=&amp;amp;cvid=262AD12124F8468AB2C9625DF9385E7D" target="_blank" rel="noopener"
 &gt;Android preload application installation location&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://cn.bing.com/search?q=Android%20application%20installation%20location&amp;amp;qs=n&amp;amp;form=QBRE&amp;amp;sp=-1&amp;amp;pq=android%20application%20installation%20location&amp;amp;sc=1-41&amp;amp;sk=&amp;amp;cvid=72F99F60ADAB423EBCCE1C5BF4F8581F" target="_blank" rel="noopener"
 &gt;Android application installation location&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://cn.bing.com/search?q=Android%20application%20installation%20folder&amp;amp;qs=n&amp;amp;form=QBRE&amp;amp;sp=-1&amp;amp;pq=undefined&amp;amp;sc=0-39&amp;amp;sk=&amp;amp;cvid=529FDC60434249D8BA41ECAF27448B77" target="_blank" rel="noopener"
 &gt;Android application installation folder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;百度中第一组搜索关键词的头五名：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://zhidao.baidu.com/question/501345661.html" target="_blank" rel="noopener"
 &gt;怎样知道安卓系统上安装的应用程序其所在文件夹&amp;hellip;_百度知道&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://jingyan.baidu.com/article/a3f121e4d9e05cfc9052bbfb.html" target="_blank" rel="noopener"
 &gt;安卓手机如何彻底删除预装应用_百度经验&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cnblogs.com/feijian/p/5160392.html" target="_blank" rel="noopener"
 &gt;教你怎么把安卓应用软件放到系统根目录system/app下 - 飞&amp;hellip;_博客园&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://jingyan.baidu.com/article/ca2d939d3056d8eb6d31ce51.html" target="_blank" rel="noopener"
 &gt;安卓手机安装后的安装包文件怎么找_百度经验&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://blog.csdn.net/snail_coder/article/details/20213069" target="_blank" rel="noopener"
 &gt;Android应用程序的安装位置 - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;百度中第二组搜素关键词的头五名：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://zhidao.baidu.com/question/501345661.html" target="_blank" rel="noopener"
 &gt;怎样知道安卓系统上安装的应用程序其所在文件夹&amp;hellip;_百度知道&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://blog.csdn.net/vurtne_ye/article/details/41722583" target="_blank" rel="noopener"
 &gt;android APK应用安装过程以及默认安装路径 - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://blog.csdn.net/libaineu2004/article/details/25247711" target="_blank" rel="noopener"
 &gt;Android开发出来的APP在手机的安装路径是? - CSDN博客&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://ask.zol.com.cn/q/1009414.html" target="_blank" rel="noopener"
 &gt;安卓软件安装目录在什么位置?-其他软件-ZOL问答堂&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://bbs.gfan.com/android-8066580-1-1.html" target="_blank" rel="noopener"
 &gt;各种APP的安装路径在哪? - Sony Xperia Z3/Compact 安&amp;hellip;_机锋论坛&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;快速的浏览10篇帖子，有些提到/data/app，有些提到/data/data，有些提到/system/app，还有些提到/system/priv-app。各个帖子试图去解决的问题不同，可能不是对我关心问题的正面回答，但不妨碍我将它们作为候选答案和新的搜索关键词。在浏览帖子的过程中，我了解到有人问过&lt;a class="link" href="https://jingyan.baidu.com/article/a3f121e4d9e05cfc9052bbfb.html" target="_blank" rel="noopener"
 &gt;安卓手机如何彻底删除预装应用_百度经验&lt;/a&gt;，我获得的认识是系统应用可能很难删除。在浏览帖子&lt;a class="link" href="http://blog.csdn.net/snail_coder/article/details/20213069" target="_blank" rel="noopener"
 &gt;Android应用程序的安装位置 - CSDN博客&lt;/a&gt;时，我发现标题虽然相关，但内容在谈应用如何能放在外部空间，根据过去对存储分级的认识，我可以联想到默认的应用应该放在内部空间中，这时候勾起了我强烈的兴趣去了解安卓系统的存储结构。面对这种情况，如果时间充裕，不妨搜索一下，以满足自己的好奇心，如果时间不允许，最好就此打住，以免花太多的时间在拓展问题上。浏览的所有帖子中，&lt;a class="link" href="http://blog.csdn.net/vurtne_ye/article/details/41722583" target="_blank" rel="noopener"
 &gt;android APK应用安装过程以及默认安装路径 - CSDN博客&lt;/a&gt;的质量最高，不妨把这个链接留下，写到自己创建的笔记中。&lt;/p&gt;
&lt;p&gt;综合中文的搜索结果其实已经基本得到了问题3和4的答案，在笔记中做下记录。问题3的答案是/system/app和/system/priv-app，问题4的答案是/data/app。如果觉得这两个问题太小，也可以合并成一个笔记。为进一步确认我们的判断，阅读下英文的搜索结果，发现有人正面回答这个问题，比如下面的几个帖子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://android.stackexchange.com/questions/3002/where-in-the-file-system-are-applications-installed" target="_blank" rel="noopener"
 &gt;Where in the file system are applications installed?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://stackoverflow.com/questions/10533030/where-is-apk-location-for-apps-that-are-installed-on-sdcard" target="_blank" rel="noopener"
 &gt;where is .apk location for apps that are installed on sdcard?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://stackoverflow.com/questions/15322063/where-does-android-app-package-gets-installed-on-phone" target="_blank" rel="noopener"
 &gt;Where does Android app package gets installed on phone&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;确认得出的结论正确后，还需要到设备上实际操作一下，得到最终的确认。帖子&lt;a class="link" href="https://stackoverflow.com/questions/15322063/where-does-android-app-package-gets-installed-on-phone" target="_blank" rel="noopener"
 &gt;Where does Android app package gets installed on phone&lt;/a&gt;中的一个回答者提到了adb shell pm list packages -f这条命令，去执行一下，并且在笔记中做记录。&lt;/p&gt;
&lt;p&gt;问题5相比于问题3和4相对复杂些，复杂性在于要从两个本身就有些复杂的概念中寻找差异。类比地来想，问题3和4分别在问“中国人住在地球的哪里”、“美国人住在地球的哪里”，而问题5却要回答“中国人和美国人有什么差别”。第一类问题具体到了被考察对象（预装应用或者中国人）千百万个属性中的一个。第二类问题却没有框定哪些属性。对于复杂概念的属性，人们往往还会根据复杂概念本身跟外部的依赖关系、用途而对属性进行进一步抽象、分类，以便于容易认知和讨论。拿中国人和美国人来讲，可以讨论的属性没有穷尽，但可讨论的抽象出的类别相对有限，比如生物特征、生活方式、历史习俗、社会结构、思维方式等。实际上简单想想都会觉得，问题5的复杂度肯定比回答中国人和美国人的差别要低得多了，然而后者虽然客观来讲更困难，但在感情上却是容易的。思考它不仅可以辅助拓展问题5的搜索关键词还能够明确我所关心的差别方向。经过这一番类比，我能想到去限定问题5的类别对比范围，我更在乎启动前的差别，至于启动后，暂时可以不必了解，没差别最好。想到这里，我可以写下以下的几组搜索关键词&lt;/p&gt;
&lt;p&gt;Difference normal app preload app Android
Difference normal app preload app installation Android
Difference normal app system app Android
Difference normal app system app before start Android
预装应用 非预装应用 差别 安卓
预装应用 非预装应用 差别 安装 安卓
预装应用 非预装应用 差别 启动前 安卓&lt;/p&gt;
&lt;p&gt;经过在Bing和百度中的搜索，我找到一些相关的答案，很多在谈权限的问题。不过搜索过程中，我同时还发现我的用词不够准确，大家很少叫normal app，而更多用的是user app；preload app也很少用，而更多用的system app。中文中大家更倾向于用普通应用和系统应用。看到系统，我又想起了我在前面找到问题3和4的答案，于是我改进自己的搜索关键词&lt;/p&gt;
&lt;p&gt;Difference user app system app Android
Difference user app system app permission Android
Difference /data/app /system/app Android
普通应用 系统应用 差别 安卓
普通应用 系统应用 启动前 差别 安卓
/system/app /data/app 差别 安卓&lt;/p&gt;
&lt;p&gt;多次重新搜索（re-search）后，终于找到看起来靠谱的两个答案。第一篇是StackExchange上面，有人问“&lt;a class="link" href="https://android.stackexchange.com/questions/17871/what-are-the-differences-between-a-system-app-and-user-app" target="_blank" rel="noopener"
 &gt;What are the differences between a system app and user app?&lt;/a&gt;”，高票的答案解释清楚了主要是拥有系统权限的不同。这样的答案可以促进自己知识的结构化，却不对解决问题产生直接帮助，因为我还是不知道什么系统权限有差别。跟系统硬件相关的权限有很多，到底由哪个造成。从头到尾读安卓的系统权限相关的文档，或者是拿一个一个的系统权限常量串到代码库中去搜索都不太现实。在我即将准备进一步优化搜索关键词的时候，第二篇CSDN上的一篇博客“&lt;a class="link" href="http://blog.csdn.net/imesong/article/details/39326203" target="_blank" rel="noopener"
 &gt;System APP 与普通 APP 简析&lt;/a&gt;”清晰地为我指明了方向。博客中的第二点不仅说了权限有差别，并且说了是广播的接收权限有差别，系统应用即便用户没有打开应用，也一样可以收到广播消息，而普通应用必须打开应用后才能收到广播消息。&lt;/p&gt;
&lt;p&gt;问题答案找到这里，我虽然只能回答第一个大问题的3个小问题，但是似乎已经距离找到答案很近了。我已经将可能出问题的范围缩小到了广播接收器和权限，外加一个条件就是个推，于是我兴奋地去个推包的AndroidManifest.xml中找所有的广播接收器和权限声明语句。看到以下几行可疑度比较高的语句。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;android.permission.RECEIVE_BOOT_COMPLETED&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;receiver&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.PushReceiver&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;intent-filter&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;android.intent.action.BOOT_COMPLETED&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;android.net.conn.CONNECTIVITY_CHANGE&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;android.intent.action.USER_PRESENT&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!-- 省略 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/intent-filter&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/receiver&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;权限一行的声明在说，应用在请求启动完成时的某种权限。广播接收器的声明在说，接收器在匹配到以下的三种事件发生的广播情况下能执行一定的任务，包括android.intent.action.BOOT_COMPLETED、android.net.conn.CONNECTIVITY_CHANGE和android.intent.action.USER_PRESENT。这时候，我再去找官方文档阅读印证我的理解是否正确。阅读后发现这三个事件过滤器的确是只针对系统应用才能产生效果的。进一步确信后，我创建一个修复分支，删除这几行代码后，需要验证是否真的就是这几行代码起的作用。对于这个验证，我有两点预期断言（Assertion）：1) 在预装应用中不再出现偷跑流量的问题 2) 对于非预装应用的推送功能并没有因为这几行代码的删除而执行异常。第一个断言属于功能性测试，为了修复不满足需求的漏洞，第二个断言属于回归测试，为了避免修复引入新的问题。&lt;/p&gt;
&lt;p&gt;做功能性的测试我需要先搭建测试环境，如果测试环境搭建好了，我预期基本上问题1, 2, 6的答案也就都有了。在这个问题方面，因为测试，外组有专门的人员负责，对我而言也只是个临时的任务，于是我在尝试搭建测试环境失败后，将任务转给了他。由于他人在美国，我需要明确要他做的事情和预期，在转交任务前，我做的测试尝试包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找问题2的答案，&lt;/li&gt;
&lt;li&gt;做初步的回归测试。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;回归测试的环境是现成的，我将带着修复的应用装到普通用户应用区域，完整地测试了推送功能和查看错误日志，并无异常发生，所以回归测试在我这里算是过了，后期还需要我们组专门的测试人员进行重复测试。&lt;/p&gt;
&lt;p&gt;找问题2的答案，相比问题5难度要小，不过执行操作比较多，而且存在着损坏硬件的风险。系统区是ROM区，只有ROOT权限才可以读写，并且启动加载的操作系统内核程序也存放在这个区域，因而操作风险较大。我尝试了两三次向一台ROOT过的机器强制覆盖应用都导致整个手机系统重启，并产生了系统崩溃的报告。为了尽快完成修复测试，我将带着修复的应用转给了外组的测试人员，他在五天之后给出了测试结果，平均每天的流量消耗是零。&lt;/p&gt;
&lt;p&gt;问题已解决，有闲暇的时间里，我将剩下的一些问题做了一些简单搜索、思考和整理，以增加我对系统局部模块的认识，就不在这里赘述。&lt;/p&gt;
&lt;p&gt;回顾整个问题解决的过程，积累结构性的知识和特定的领域细节固然重要，但反思和迭代自己解决问题的能力和方法更能让人感到一种成长的愉悦。如果特定领域的知识积累到一定程度，比如对安卓系统非常精通的人，大概看到这样问题的发生，就已经推测出可能出问题的方向，一点都不需要这个复杂的搜索、重新搜索的过程，而是直接去代码中找了。然而，现实中，所有的技术更新迭代的速度超乎我们的想象得快，做到对一切细节都能把握得很清楚是件不可能的事情，这个时候练就快速在大脑中索引和找到细节的能力就显得更加重要。学会反问自己恰当的问题和良好的记录习惯是快速掌握这个技能的核心要素。另外，我想这个技能应该也是精通某项技术不可或缺的能力，换句话说，如果很难以习得这样的能力，基本和精通无缘了。学科领域细分的今天，只有跨领域才能做出点东西，习得这样的能力也只是开始，与此同时，这个技能也是快速介入其它相关领域的好途径。&lt;/p&gt;</description></item><item><title>项目文档管理纲要</title><link>https://blog.nicelylit.net/posts/%E9%A1%B9%E7%9B%AE%E6%96%87%E6%A1%A3%E7%AE%A1%E7%90%86%E7%BA%B2%E8%A6%81/</link><pubDate>Thu, 28 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E9%A1%B9%E7%9B%AE%E6%96%87%E6%A1%A3%E7%AE%A1%E7%90%86%E7%BA%B2%E8%A6%81/</guid><description>&lt;figure&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;编号&lt;/th&gt;
 &lt;th&gt;1&lt;/th&gt;
 &lt;th&gt;2&lt;/th&gt;
 &lt;th&gt;3&lt;/th&gt;
 &lt;th&gt;4&lt;/th&gt;
 &lt;th&gt;5&lt;/th&gt;
 &lt;th&gt;6&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;a class="link" href="https://blog.nicelylit.net/posts/%E5%A6%82%E4%BD%95%E4%BD%9C%E6%8F%90%E6%A1%88/" &gt;提案&lt;/a&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;/td&gt;
 &lt;td&gt;模板&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;英文名称&lt;/td&gt;
 &lt;td&gt;Proposals&lt;/td&gt;
 &lt;td&gt;Reports&lt;/td&gt;
 &lt;td&gt;Manuals&lt;/td&gt;
 &lt;td&gt;Trackers&lt;/td&gt;
 &lt;td&gt;Notes&lt;/td&gt;
 &lt;td&gt;Templates&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;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;常见用途&lt;/td&gt;
 &lt;td&gt;立项书、计划书（包括项目计划、产品路线、测试计划、变更计划、部署计划等）、合同、草案（包括财务预算、架构设计的草案、规格说明书的草案等）。&lt;/td&gt;
 &lt;td&gt;花费分析、可行性分析、调查报告、诊断报告、测试报告（或称质量报告）、用户分析、竞品分析、需求分析。&lt;/td&gt;
 &lt;td&gt;用户手册、标准作业程序（SOP）、开发环境搭建手册、测试用例与执行手册、运维操作手册、常见问题解答（FAQ）、客服渠道手册。&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;文档结构&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;td&gt;笔记结构灵活，关键是记录的细节，为方便后续查看，适当分节组织和描述信息的来源与服务场景。&lt;/td&gt;
 &lt;td&gt;模板中通常是两类元素，一类是不变量的字符串，另一类是变量，变量要用特殊字体高亮或者说明需要替换。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;亚马逊领导力[1]&lt;/td&gt;
 &lt;td&gt;Invent and Simplify Think Big Have Backbone; Disagree and Commit Success and Scale Bring Broad Responsibility&lt;/td&gt;
 &lt;td&gt;Are Right, A Lot Learn and Be Curious Dive Deep&lt;/td&gt;
 &lt;td&gt;Customer Obsession Bias for Action Earn Trust Deliver Results&lt;/td&gt;
 &lt;td&gt;Hire and Develop the Best Frugality Strive to be Earth’s Best Employer&lt;/td&gt;
 &lt;td&gt;Insist on the Highest Standards&lt;/td&gt;
 &lt;td&gt;Ownership&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;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;偏向的项目阶段[2]&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;td&gt;执行阶段&lt;/td&gt;
 &lt;td&gt;所有阶段&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;版本控制[3]&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;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;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;主导角色[4]&lt;/td&gt;
 &lt;td&gt;SDM&lt;/td&gt;
 &lt;td&gt;SDE/PMT&lt;/td&gt;
 &lt;td&gt;SDE/PMT/TPM&lt;/td&gt;
 &lt;td&gt;SDM/TPM&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;figcaption&gt;
&lt;p&gt;项目文档管理纲要
[1] 亚马逊领导利准则：https://amazon.jobs/content/en/our-workplace/leadership-principles [2]项目阶段：启动、计划、执行、监控、执行。https://www.atlassian.com/zh/work-management/project-management/phases [3] 版本控制：结构性的调整，修改大版本号。局部内容的变动，修改小版本号。[4] 主导角色：Software Development Manager (SDM), Software Development Engineer (SDE), Product Manager – Technical (PMT), Technical Program Manager (TPM)&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;</description></item><item><title>如何作提案</title><link>https://blog.nicelylit.net/posts/%E5%A6%82%E4%BD%95%E4%BD%9C%E6%8F%90%E6%A1%88/</link><pubDate>Wed, 27 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%A6%82%E4%BD%95%E4%BD%9C%E6%8F%90%E6%A1%88/</guid><description>&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;p&gt;会后应当做好提案的迭代与归档。提案是一种重要的创作成果。但凡是创作，不可能不经过修改，严重时内容失焦、提案毫无结构、利益方缺失，短时间看，一方面源自于作者文字叙述技艺的不足，另一方面源自于作者会前工作的不充分，长时间看，来源于眼界的狭隘、专业素养的欠缺与对提案产生预期效用的信心的缺乏。不论哪种病根，都要从自身能力上找问题去修正，做好迭代与归档不仅是为了给出短期的书面交代与总结，更是为了长远的自我成长。勤于做迭代与归档需要两个心态做支撑，始终期待自我的成长与随时察觉到自我的傲慢。&lt;/p&gt;
&lt;p&gt;讨论会第一作用是做决策，但也起着正式知会各利益方并将决策公开公正的作用，换言之，即便有些利益方参与感弱，提案发起人也有义务通知到。不轻视和不忽视他人感受是团结友方、获得敌方尊重的良缘。&lt;/p&gt;
&lt;p&gt;提案服务于项目的各个阶段，不限于立项、架构、设计与测试。任何需要与人做决定的会议中，会议的发起人都应当持有一种提案的意识，只是决议的事情参与人数和影响较小的时候，书面叙述的形式可以适当简化。&lt;/p&gt;
&lt;p&gt;一种常见的提案结构，是先阐述问题，再讲述愿景，然后给出提议，包括对蓝图进行描绘以及对提议与备选方案进行对比讨论。提案的结构并非固定不变，但往往前面所说的三种要素都要有。提案往往是分析与综合后得到的结果，一种误区是只做分析不做综合，把提案写成分析报告或者调研笔记，另一种误区是做了一种提议，但分析与综合皆不足，把提案写成详细设计或者规格说明书。这两种误区倒不全然是不好的结果，只是不是理想的提案。在需要审核的细节较多的时候，把详细设计或规格说明书当作一种提案也未见得不是一件好事，节省沟通次数。&lt;/p&gt;</description></item><item><title>有关工具的思考</title><link>https://blog.nicelylit.net/posts/%E5%B7%A5%E5%85%B7/</link><pubDate>Sat, 11 Feb 2023 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%B7%A5%E5%85%B7/</guid><description>&lt;p&gt;2023-03-03&lt;/p&gt;
&lt;p&gt;做工具的主人要有积极的态度与恰当的方法。方法恰当却态度不积极，是工具的敌人。方法不恰当态度积极，是工具的学徒。方法不恰当还态度不积极，是工具的奴隶。傲慢使人成为工具的敌人，懒惰使人成为工具的奴隶。人极容易沦为工具的奴隶。实际上，持有积极的态度已经是在运用恰当的方法。&lt;/p&gt;
&lt;p&gt;2023-03-02&lt;/p&gt;
&lt;p&gt;能不能、行不行、有没有，这样的问题总是充满挑战。若是能、行、有，也就不成为问题。问题，是困惑，是不满，是视野的狭窄，是内心的闭塞，是心胸的狭隘。解决问题，需要创造工具，而创造工具的方法永远都是要拓宽视野，开放内心，打开胸怀。心怀天下，不是一句抒情，而是真的装着每个人的困惑、不安与无奈。&lt;/p&gt;
&lt;p&gt;2023-03-01&lt;/p&gt;
&lt;p&gt;服务具有人的主观性，是人与人来往的途径，也表露着人的动机与目的。人们将客观的一般工具看作是无善恶的，但服务却有善恶。火药可以用来开山通路，也可以用来杀人，开山通路不一定都是善的，杀人也不全然是恶的。&lt;/p&gt;
&lt;p&gt;2023-02-28&lt;/p&gt;
&lt;p&gt;人活着的价值在于服务他人。价值于服务中体现，这是生存的需要，是成长的需要，是从痛苦中解脱的需要。劳动创造价值，服务背后是艰辛的劳动。&lt;/p&gt;
&lt;p&gt;2023-02-27&lt;/p&gt;
&lt;p&gt;服务是一类特殊的工具。相比于一般的工具，服务中留存的人的意图更鲜明、主观性更强，需求被满足得更及时。服务的形态更多是人对人的，而不像一般工具是人对物的。&lt;/p&gt;
&lt;p&gt;2023-02-26&lt;/p&gt;
&lt;p&gt;软件工具满足了人便捷、快速、大量地处理信息的需要。一切人所形成的观念都能表示成信息。处理信息在不同情形中侧重点不同，有的侧重计算的执行，有的侧重信息的存储和传输，还有的侧重硬件的操控。&lt;/p&gt;
&lt;p&gt;2023-02-25&lt;/p&gt;
&lt;p&gt;工具的操作界面是创造者意图的留存与妥协。在使用者操作工具之时，是两者意念上的相合。人与人之间意念的相合是超越观念的。&lt;/p&gt;
&lt;p&gt;2023-02-24&lt;/p&gt;
&lt;p&gt;工具必须可操作。操作工具，需要对应的能力，需要清晰的预期，需要针对意外的处理方法。人的能力通过操作转移到工具上，像是工具具备了某种能力。&lt;/p&gt;
&lt;p&gt;2023-02-23&lt;/p&gt;
&lt;p&gt;找到工具意味着工具可被定位。定位就要有方向和位置，尽管描述方向和位置的形式各异，但都需要先建立起空间的观念，并在空间上进行分割形成区域后进行命名或编号。时间是人与人之间用来协调与同步的工具。朝代是叙述历史的工具，定位朝代，要先建立起轴的观念，再进行分段和命名。&lt;/p&gt;
&lt;p&gt;2023-02-22&lt;/p&gt;
&lt;p&gt;对比就是个强大的工具。心中有了差异，才造成了真的差异。平等是差异产生后和而不同的状态，在无差异之前，也无所谓平等。使用对比这个工具就是主动让心中的差异浮现，自然地观察差异。&lt;/p&gt;
&lt;p&gt;2023-02-21&lt;/p&gt;
&lt;p&gt;观念（Idea）是对比的基础，一切都要形成观念才可以进行比较。观念通过语言来传达，肢体的、表情的、声音的、文字的。观察观念产生的来源是解读观念内涵与延伸的起点。观念的产生来源有外部的和内部的，外部通过感官刺激，内部通过心理活动和思维活动。&lt;/p&gt;
&lt;p&gt;2023-02-20&lt;/p&gt;
&lt;p&gt;知道此工具意味着此工具可被识别。这个有别于那个，因而这个是做这个的工具，那个是做那个的工具。识别产生于差异当中，可对比是可识别的前提。物质是可对比的，文字是可对比的，情绪和思想也都是可对比的。&lt;/p&gt;
&lt;p&gt;2023-02-19&lt;/p&gt;
&lt;p&gt;创造工具是为了构建使用的需要，是从多到一，是对过去的交代。创造汽车，构建了乘坐的需要。一辆汽车综合了耐磨的轮子、稳固的材料、宽敞的空间和充足的动力。汽车时代的到来，是马车时代的结束。&lt;/p&gt;
&lt;p&gt;2023-02-18&lt;/p&gt;
&lt;p&gt;使用工具是为了构建一种需要，是从一到多，是对未来的开创。拿杯子喝水，构建了止渴的需要。杯子可以用来喝水，也可以用来喝茶和果汁。一个废弃的杯子，可以拿来浇花，也可以用来做笔筒，还可以改造后做喇叭。开创总是要回到需要的，而需要从来都来自于人自身。&lt;/p&gt;
&lt;p&gt;2023-02-17&lt;/p&gt;
&lt;p&gt;知道和找到合适的工具是使用好工具的前提。知道有知道的方法，找到有找到的途径。广泛地阅读、观察和记忆是知道的方法，深入地列举、联想、比对和评价是找到的途径。广泛意味着保持开放、积极和谦卑，深入意味着保持好奇、勤奋和真诚。合适和好是理想，这样的理想藏在对痛苦和不满的消解之后。&lt;/p&gt;
&lt;p&gt;2023-02-16&lt;/p&gt;
&lt;p&gt;工具是手段、途径和方法的具象形态，它们同实而异名。工具的形态千变万化，可以是物质的、文字的、情绪的、思想的，还可以是人本身。人人都不愿被当作工具来使用，却也躲不开被当作工具的宿命。一个人若想不被别人当作工具，自己要先不把别人当作工具。这样的原则不是口头上的，而是发自内心的，内外统一的。&lt;/p&gt;
&lt;p&gt;2023-02-15&lt;/p&gt;
&lt;p&gt;创造并不比使用更高级，惊人的创造需要恰到好处的使用。创造面向未来，是从多到一，却也是对过去的交代。使用贴近过去，是从一到多，却也是对未来的开创。创造在求新求变的人眼里，要有创新，有创意，但在追本尚古的人眼中，只是水到渠成，因缘际会的结果。新意是对现存事物的不满，是对从痛苦中解脱的执着。&lt;/p&gt;
&lt;p&gt;2023-02-14&lt;/p&gt;
&lt;p&gt;人不仅是工具的使用者和创造者，还是维修者、改良者、推广者和拥有者。工具被人群广泛使用，不是一夜之间完成的，甚至不是一个时代所完成的。与一些经历成功改良和推广的工具的存续时间相比，一个人的寿命是极其短暂的。&lt;/p&gt;
&lt;p&gt;2023-02-13&lt;/p&gt;
&lt;p&gt;人是工具的使用者和创造者，不论作为哪个角色，都是为了构建活动。或者使用工具构建服务，或者使用旧工具构建新工具，人都是主体，也可以统称为构建者。举例来说，Java程序员是构建者，或者使用Java编译器构建Java应用服务于他人，或者使用旧版本的Java编译器构建新版本的Java编译器。&lt;/p&gt;
&lt;p&gt;2023-02-12&lt;/p&gt;
&lt;p&gt;工具是构建活动的必要元素。修房子需要建房的工具，造火箭需要制作火箭的工具，类似地，写软件或者软件服务，也需要趁手的工具。&lt;/p&gt;
&lt;p&gt;2023-02-11&lt;/p&gt;
&lt;p&gt;软件是构建得到的，而不只是编写的。编写只说了构建活动的一环，即编码，它侧重于编排与书写，可从活动整体看，更重要的是构架和建设。&lt;/p&gt;</description></item><item><title>架构概述</title><link>https://blog.nicelylit.net/posts/%E6%9E%B6%E6%9E%84%E6%A6%82%E8%BF%B0/</link><pubDate>Sat, 28 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E6%9E%B6%E6%9E%84%E6%A6%82%E8%BF%B0/</guid><description>&lt;p&gt;原文链接：&lt;a class="link" href="https://source.android.google.cn/docs/core/architecture" target="_blank" rel="noopener"
 &gt;https://source.android.google.cn/docs/core/architecture&lt;/a&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;The Android Open System Platform (AOSP) is publicly available and modifiable Android source code. Anyone can download and modify AOSP for their device. AOSP provides a complete and fully functional implementation of the Android mobile platform.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;先说开放修改的自由精神，再说项目本身专注于提供什么。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Note: AOSP can&amp;rsquo;t provide support for apps that require backend services, such as a cloud messaging or advanced location services app. AOSP also doesn&amp;rsquo;t include a full set of end-user apps that might be needed for particular types of devices.&lt;/p&gt;

 &lt;/blockquote&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;p&gt;评论：&lt;/p&gt;
&lt;p&gt;要说清楚不支持什么，既是划清界限，也是免责申明。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;There are two levels of compatibility for devices implementing AOSP: AOSP compatibility and Android compatibility. An AOSP-compatible device must conform to the list of requirements in the Compatibility Definition Document (CDD). An Android-compatible device must conform to the list of requirements in the CDD and Vendor Software Requirements (VSR) and tests such as those in the Vendor Test Suite (VTS) and Compatibility Test Suite (CTS). For further information on Android compatibility, refer to the Android compatibility program.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;系统实现要能在各种各样的设备上运行，对安卓平台的维护者、设备硬件的供应商、设备的制造商而言，需要制定标准化的测试计划。测试是服务提供方站在消费方视角审视系统实现的方式，对功能的集成与产品质量的把控至关重要。系统的架构之初，测试计划应当在讨论之列。讨论测试计划是一种典型的逆向工作法。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;AOSP architecture&lt;/p&gt;
&lt;p&gt;The software stack for AOSP contains the following layers:&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;软件堆栈。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;分层的体系结构风格是平台类复杂系统的必然选择。作为平台至少要有三种角色，生产者、消费者和平台搭建者。每一层都能从生产消费的角度看，但每一层所使用的沟通语言不同。上层靠近应用和接口，接近人的自然语言，而下层靠近原理和实现，接近人的逻辑和形式语言。平台要为生产者和消费者搭建起恰当的沟通语言。平台的搭建成本不能超过生产者和消费者从中获得的价值和利益。平台搭建的沟通语言要简单而且有效，不能过度创造，也不能创造不足。要做到恰到好处，要充分探究生产者和消费者的需求。这样想来，深入了解一个应用软件或者平台软件，阅读测试计划比阅读架构文档和源代码来得更加高效。测试计划可谓是需求文档的落地版，也是平台功能的大纲。当然架构文档能把设计中的重点和要点汇总在一起，免去了通过测试计划来推测的麻烦。源代码当然是比测试计划和架构文档都更为准确的东西，但也很容易让人迷失在细节中，找不到方向，甚至还可能看错了东西。源代码是要结合测试结果来看的，不过一般测试结果很少记录得事无巨细，通常需要借助人工测试或者调试。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Following is a list of definitions for terms used in Figure 1:&lt;/p&gt;
&lt;p&gt;Android app&lt;/p&gt;
&lt;p&gt;An app created solely using the Android API. Google Play Store is widely used to find and download Android apps, though there are many other alternatives. In some cases, a device manufacturer might want to preinstall an Android app to support the core functionality of the device. If you&amp;rsquo;re interested in developing Android apps, refer to developers.android.com&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;这是消费者最为常见的一类安卓应用，也是开发者最为众多的一类。设备制造商往往也会开发一些这类应用，但更多是一些预装应用。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Privileged app&lt;/p&gt;
&lt;p&gt;An app created using a combination of the Android and system APIs. These apps must be preinstalled as privileged apps on a device.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;特权应用。&lt;/li&gt;
&lt;li&gt;结合使用安卓应用编程接口和系统应用编程接口。&lt;/li&gt;
&lt;li&gt;必须预装。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;访问系统应用编程接口需要较高权限，因而叫做特权应用。这种应用是必须的，例如桌面启动器和设置。这类应用消费者往往不能卸载。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Device manufacturer app&lt;/p&gt;
&lt;p&gt;An app created using a combination of the Android API, system API, and direct access to the Android framework implementation. Because a device manufacturer might directly access unstable APIs within the Android framework, these apps must be preinstalled on the device and can be updated only when the device&amp;rsquo;s system software is updated.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;对照来看，特权应用似乎可以单独升级。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;System API&lt;/p&gt;
&lt;p&gt;The System API represents Android APIs available only to partners and OEMs for inclusion in bundled applications. These APIs are marked as @SystemApi in the source code.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;系统应用编程接口。&lt;/li&gt;
&lt;li&gt;仅开放给谷歌的合作商和设备制造商。&lt;/li&gt;
&lt;li&gt;源代码中打了特殊标记。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;没有提供文档连接。这类编程接口恐怕只能通过源代码查找了。搜索代码库，找到一个&lt;a class="link" href="https://cs.android.com/android/platform/superproject/&amp;#43;/master:frameworks/base/core/java/android/os/SystemProperties.java?q=@SystemApi%20&amp;amp;ss=android" target="_blank" rel="noopener"
 &gt;系统属性类&lt;/a&gt;就是这类的接口，还有&lt;a class="link" href="https://cs.android.com/android/platform/superproject/&amp;#43;/master:libcore/dalvik/src/main/java/dalvik/system/ZygoteHooks.java?q=@SystemApi%20&amp;amp;ss=android" target="_blank" rel="noopener"
 &gt;应用孵化器钩子&lt;/a&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="cl"&gt;&lt;span class="cm"&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;* Gives access to the system properties store. The system properties
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;* store contains a list of string key-value pairs.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&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="cl"&gt;&lt;span class="cm"&gt;* &amp;lt;p&amp;gt;Use this class only for the system properties that are local. e.g., within
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;* an app, a partition, or a module. For system properties used across the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;* boundaries, formally define them in &amp;lt;code&amp;gt;*.sysprop&amp;lt;/code&amp;gt; files and use the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;* auto-generated methods. For more information, see &amp;lt;a href=
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;* &amp;#34;https://source.android.com/devices/architecture/sysprops-apis&amp;#34;&amp;gt;Implementing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;* System Properties as APIs&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&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="cl"&gt;&lt;span class="cm"&gt;* {@hide}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&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="cl"&gt;&lt;span class="nd"&gt;@SystemApi&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="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SystemProperties&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;Android API&lt;/p&gt;
&lt;p&gt;The Android API is the publicly available API for third-party Android app developers. For information on the Android API, refer to Android API reference.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安卓应用编程接口。&lt;/li&gt;
&lt;li&gt;三方应用开发者。&lt;/li&gt;
&lt;li&gt;编程接口文档链接。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;三方应该是站在设备制造商角度说的。设备制造商是一方，消费者是另一方，而开发者是第三方。至于谷歌和硬件的供应商应该同属于设备制造商一方，只做内部区分。这部分编程接口是有文档链接的。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Android framework&lt;/p&gt;
&lt;p&gt;A group of Java classes, interfaces, and other precompiled code upon which apps are built. Portions of the framework are publicly accessible through the use of the Android API. Other portions of the framework are available only to OEMs through the use of the system APIs. Android framework code runs inside an app&amp;rsquo;s process.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安卓框架。&lt;/li&gt;
&lt;li&gt;应用构建所依赖的Java类、接口和其它的预编译后的代码。&lt;/li&gt;
&lt;li&gt;安卓框架皆是通过应用编程接口来被使用的。&lt;/li&gt;
&lt;li&gt;安卓框架运行在每一个应用的进程中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;安卓应用编程接口和系统应用编程接口都是接口，而安卓框架是这些接口的实现。对开发者而言，开发过程中所依赖的安卓库就包含了编程接口和安卓框架，打包生成的构建产物应当是不包含这个安卓框架的，而是从孵化器进程（Zygote）复刻应用时候就从固定位置加载的，甚至可能不需要额外加载就已经在孵化器进程中包含了。具体是如何实现的恐怕要看代码。&lt;a class="link" href="https://source.android.google.cn/docs/compatibility/13/android-13-cdd?hl=en#37_runtime_compatibility" target="_blank" rel="noopener"
 &gt;兼容性定义文档&lt;/a&gt;中没做要求。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;System services&lt;/p&gt;
&lt;p&gt;System services are modular, focused components such as system_server, SurfaceFlinger, and MediaService. Functionality exposed by Android framework API communicates with system services to access the underlying hardware.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;系统服务。&lt;/li&gt;
&lt;li&gt;模块化、功能集中。&lt;/li&gt;
&lt;li&gt;举了三例：system_server, SurfaceFlinger, and MediaService。&lt;/li&gt;
&lt;li&gt;暴露出去的安卓框架的编程接口实际是通过系统服务来跟硬件打交道的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;system_server中集成了大部分的系统服务。SurfaceFlinger用于图形显示的缓存服务。MediaService用于多媒体的服务。这些服务是从运行的状态来说的。使用编程接口的应用进程是客户端，而这些服务进程是服务器，所以这些服务进程的名字很多以server结尾。服务的注册和查找要通过ServiceManager来实现。由于这些服务进程实际上提供了安卓框架的功能实现，所以这些服务进程也可以称作框架进程。官方在&lt;a class="link" href="https://source.android.google.cn/docs/core/architecture/hidl/binder-ipc?hl=zh-cn#vndbinder" target="_blank" rel="noopener"
 &gt;使用Binder进行进程间通信的文档&lt;/a&gt;中就是这么称呼的。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Android runtime (ART)&lt;/p&gt;
&lt;p&gt;A Java runtime environment provided by AOSP. ART performs the translation of the app&amp;rsquo;s bytecode into processor-specific instructions that are executed by the device&amp;rsquo;s runtime environment.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安卓运行时。&lt;/li&gt;
&lt;li&gt;提供Java运行时环境。&lt;/li&gt;
&lt;li&gt;安卓运行时将应用字节码翻译成特定处理器的指令。&lt;/li&gt;
&lt;li&gt;在设备的运行时环境执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;代码有三种表示，源代码、字节码和机器码。源代码支持Java和Kotlin。字节码支持DEX，需要从Java字节码做变换。机器码取决于设备的处理器。从字节码翻译到机器码，若发生在安装阶段称作AOT，若发生在运行阶段称作JIT。早先的Dalvik方式是以虚拟机的方式直接解释执行DEX码，ART方式能够兼容Dalvik方式。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Hardware abstraction layer (HAL)&lt;/p&gt;
&lt;p&gt;A HAL is an abstraction layer with a standard interface for hardware vendors to implement. HALs allow Android to be agnostic about lower-level driver implementations. Using a HAL lets you implement functionality without affecting or modifying the higher level system. For further information, see the HAL overview.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;这个抽象层的规定是安卓兼容性计划所覆盖的。兼容性的定义文档中有&lt;a class="link" href="https://source.android.google.cn/docs/compatibility/13/android-13-cdd?hl=en#7_hardware_compatibility" target="_blank" rel="noopener"
 &gt;对硬件兼容性的要求&lt;/a&gt;。&lt;a class="link" href="https://source.android.google.cn/docs/compatibility/cts#areas-covered" target="_blank" rel="noopener"
 &gt;兼容性测试套件&lt;/a&gt;中没有特别针对硬件抽象层的，但上层的框架应该对此有依赖。&lt;a class="link" href="https://source.android.google.cn/docs/core/tests/vts" target="_blank" rel="noopener"
 &gt;供应商的软件需求&lt;/a&gt;没有专门的文档。供应商的测试套件在各种硬件专门的目录下，例如wifi 1.5的功能测试放在&lt;a class="link" href="https://cs.android.com/android/platform/superproject/&amp;#43;/master:hardware/interfaces/wifi/1.5/vts/functional/" target="_blank" rel="noopener"
 &gt;hardware/interfaces/wifi/1.5/vts/functional&lt;/a&gt;下。所有硬件的抽象层定义放在&lt;a class="link" href="https://cs.android.com/android/platform/superproject/&amp;#43;/master:hardware/interfaces/" target="_blank" rel="noopener"
 &gt;hardware/interfaces&lt;/a&gt;下。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Native daemons and libraries&lt;/p&gt;
&lt;p&gt;Native daemons in this layer include init, healthd, logd, and storaged. These daemons interact directly with the kernel or other interfaces and don&amp;rsquo;t depend on a userspace-based HAL implementation.&lt;/p&gt;
&lt;p&gt;Native libraries in this layer include libc, liblog, libutils, libbinder, and libselinux. These Native libraries interact directly with the kernel or other interfaces and don&amp;rsquo;t depend on a userspace-based HAL implementation.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;本地守护进程和库。&lt;/li&gt;
&lt;li&gt;本地守护进程举了四例：init、healthd、logd、storaged。&lt;/li&gt;
&lt;li&gt;守护进程直接跟内核交互。&lt;/li&gt;
&lt;li&gt;守护进程跟其它接口交互并不依赖硬件抽象层实现。&lt;/li&gt;
&lt;li&gt;本地库举了五个例子：libc、liblog、libutils、libbinder、libselinux。&lt;/li&gt;
&lt;li&gt;本地库直接跟内核交互。&lt;/li&gt;
&lt;li&gt;本地库跟其它接口交互并不依赖硬件抽象层实现。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;init进程是用户空间的第一个进程，进程ID通常是1。本地守护进程运行在用户空间，都是从init复刻的。守护进程间的通信方式不是Binder。应用进程和框架进程一样可以访问本地守护进程。举例来说，应用进程通过&lt;a class="link" href="https://developer.android.com/reference/android/util/Log" target="_blank" rel="noopener"
 &gt;android.util.Log&lt;/a&gt;写运行日志，而底层依赖的&lt;a class="link" href="https://cs.android.com/android/platform/superproject/&amp;#43;/master:system/logging/liblog/" target="_blank" rel="noopener"
 &gt;liblog&lt;/a&gt;使用了Socket通信向&lt;a class="link" href="https://cs.android.com/android/platform/superproject/&amp;#43;/master:system/logging/liblog/logd_writer.cpp;l=74-79" target="_blank" rel="noopener"
 &gt;logd发起写日志&lt;/a&gt;的请求。本地库可以被不同的可执行程序所共享，应用进程也可以，比方说应用进程通过JNI在libandroid_runtime中使用liblog进行写日志。&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="cl"&gt;&lt;span class="c1"&gt;// android.jar&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="cl"&gt;&lt;span class="c1"&gt;// frameworks/base/core/java/android/util/Log.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="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&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;w&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Nullable&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="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@NonNull&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="n"&gt;msg&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="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;println_native&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG_ID_MAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WARN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&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="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="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@UnsupportedAppUsage&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="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="kd"&gt;native&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;println_native&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;bufID&lt;/span&gt;&lt;span class="p"&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;priority&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;msg&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// libandroid_runtime.so
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// frameworks/base/core/jni/android_util_Log.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;jint&lt;/span&gt; &lt;span class="nf"&gt;android_util_Log_println_native&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JNIEnv&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jobject&lt;/span&gt; &lt;span class="n"&gt;clazz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;jint&lt;/span&gt; &lt;span class="n"&gt;bufID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jint&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jstring&lt;/span&gt; &lt;span class="n"&gt;tagObj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jstring&lt;/span&gt; &lt;span class="n"&gt;msgObj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 删掉了一些代码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;__android_log_buf_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bufID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;android_LogPriority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ReleaseStringUTFChars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagObj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ReleaseStringUTFChars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgObj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;JNINativeMethod&lt;/span&gt; &lt;span class="n"&gt;gMethods&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cm"&gt;/* name, signature, funcPtr */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;isLoggable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;(Ljava/lang/String;I)Z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;android_util_Log_isLoggable&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;println_native&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;(IILjava/lang/String;Ljava/lang/String;)I&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;android_util_Log_println_native&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;logger_entry_max_payload_native&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;()I&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;android_util_Log_logger_entry_max_payload_native&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// liblog.so
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// system/logging/liblog/logger_write.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;__android_logger_function&lt;/span&gt; &lt;span class="n"&gt;user_set_logger_function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;__android_logger_function&lt;/span&gt; &lt;span class="nf"&gt;get_logger_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_set_logger_function&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user_set_logger_function&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;__android_logger_function&lt;/span&gt; &lt;span class="n"&gt;default_logger_function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#if __ANDROID__
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_file_logger_path&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;file_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;__android_log_logd_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;file_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;default_logger_function&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;__android_log_write_log_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__android_log_message&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;log_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ErrnoRestorer&lt;/span&gt; &lt;span class="n"&gt;errno_restorer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 删掉了一些代码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;get_logger_function&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;&lt;span class="n"&gt;log_message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;__android_log_buf_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bufID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;prio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ErrnoRestorer&lt;/span&gt; &lt;span class="n"&gt;errno_restorer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;__android_log_is_loggable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ANDROID_LOG_VERBOSE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EPERM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;__android_log_message&lt;/span&gt; &lt;span class="n"&gt;log_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__android_log_message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;bufID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;__android_log_write_log_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;log_message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;__android_log_logd_logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;__android_log_message&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;log_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;buffer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_message&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;LOG_ID_DEFAULT&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;LOG_ID_MAIN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;log_message&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 删掉了一些代码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;write_to_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;log_id_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;write_to_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_id_t&lt;/span&gt; &lt;span class="n"&gt;log_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;iovec&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 删掉了一些代码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LogdWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PmsgWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// liblog.so
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// system/logging/liblog/logd_writer.cpp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;LogdWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_id_t&lt;/span&gt; &lt;span class="n"&gt;logId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;timespec&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;iovec&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ssize_t&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="n"&gt;headerLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;iovec&lt;/span&gt; &lt;span class="n"&gt;newVec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;headerLength&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;android_log_header_t&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payloadSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;atomic_int&lt;/span&gt; &lt;span class="n"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;LogdSocket&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;logd_socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;logId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;LOG_ID_SECURITY&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;LogdSocket&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BlockingSocket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LogdSocket&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NonBlockingSocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;Kernel&lt;/p&gt;
&lt;p&gt;The kernel is the central part of any operating system and talks to the underlying hardware on a device. Where possible, the AOSP kernel is split into hardware-agnostic modules and vendor-specific modules. For a description, including definitions, of AOSP kernel components, refer to the Kernel overview.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;要点：&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;p&gt;评论：&lt;/p&gt;
&lt;p&gt;依据生产过程，定制内核需要三方参与，AOSP项目维护者、硬件单片机和外设的供应商、原始设备生产商。拿我手上的华为荣耀Play 6T来说，谷歌公司是AOSP项目维护者，联发科和华为是硬件供应商、荣耀终端有限公司是原始设备生产商。依据&lt;a class="link" href="https://source.android.google.cn/docs/core/architecture" target="_blank" rel="noopener"
 &gt;通用内核映像（GKI）项目&lt;/a&gt;的说法，内核由四部分构成，Linux长期支持版（LTS）内核、安卓公共内核、供应商内核、生产商设备内核。&lt;/p&gt;</description></item><item><title>博客维护大纲</title><link>https://blog.nicelylit.net/posts/%E5%8D%9A%E5%AE%A2%E7%BB%B4%E6%8A%A4%E5%A4%A7%E7%BA%B2/</link><pubDate>Wed, 25 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%8D%9A%E5%AE%A2%E7%BB%B4%E6%8A%A4%E5%A4%A7%E7%BA%B2/</guid><description>&lt;p&gt;从2014年12月搭建博客以来，对站点做过三次大的维护，分别是2016年7月、2020年6月、2023年1月，具体维护的内容记录在了&lt;a class="link" href="https://mjm1990.com/article/%e5%8d%9a%e5%ae%a2%e7%bb%b4%e6%8a%a4%e6%97%a5%e5%bf%97" target="_blank" rel="noopener"
 &gt;维护日志&lt;/a&gt;中，而这篇是为了规范化维护的各个方面，用以节省维护的时间成本，同时也给其他的博客人做个参考。&lt;/p&gt;
&lt;h1 id="托管服务"&gt;托管服务
&lt;/h1&gt;&lt;p&gt;站点的数据库、网页服务器、域名、数字证书都需要托管服务，定制主题源代码也需要，此外也为垃圾评论过滤购买了服务。Google Fonts、MathJax等资源外链需要注意防火墙和内容分发网的效率。&lt;/p&gt;
&lt;h1 id="服务费用"&gt;服务费用
&lt;/h1&gt;&lt;p&gt;在服务质量有保障的提前下，拣便宜免费的用。目前数据库和网页服务器用的&lt;a class="link" href="https://www.aliyun.com/product/ecs/hosting?spm=5176.19720258.J_3207526240.36.19d176f4Z6BGZK" target="_blank" rel="noopener"
 &gt;阿里云的独享虚拟主机基础版&lt;/a&gt;，域名和解析服务用的&lt;a class="link" href="https://wanwang.aliyun.com/domain/dns?spm=5176.23524904.J_3207526240.67.671125b3q9mWQr" target="_blank" rel="noopener"
 &gt;阿里云云解析DNS&lt;/a&gt;，数字证书用的&lt;a class="link" href="https://www.aliyun.com/product/cas" target="_blank" rel="noopener"
 &gt;DigitCert免费版&lt;/a&gt;，垃圾评论过滤用的&lt;a class="link" href="https://cleantalk.org/price-anti-spam" target="_blank" rel="noopener"
 &gt;CleanTalk单站点无限制版&lt;/a&gt;。&lt;/p&gt;
&lt;h1 id="站点功能"&gt;站点功能
&lt;/h1&gt;&lt;p&gt;站点功能涉及角色包括读者、作者、机器人、管理员、测试员和开发者。一项功能通常只涉及一个角色，但一项功能的发布可能涉及多项功能的添加。例如若要为读者提供更好的字体阅读体验，同时需要为作者提供更丰富的字体选择，还可能需要为管理员提供字体的开启和禁用功能，也需要为开发者提供字体的调试和预览环境。个人博客功能开发与维护，一人身兼六种角色，考虑成本，需要有所取舍。&lt;/p&gt;
&lt;h1 id="开发环境"&gt;开发环境
&lt;/h1&gt;&lt;p&gt;主题定制是搭建博客的众多理由之一，WordPress通过主题机制和插件机制能满足大部分的需求。但如果市场中没有合适的选择，就需要自己动手开发主题或者插件，这种情况下自动化程度高的开发、测试和部署环境能够节省时间。这次维护对主题做了升级的同时，还提升了开发环境的自动化程度。&lt;/p&gt;
&lt;h1 id="数据备份"&gt;数据备份
&lt;/h1&gt;&lt;p&gt;数据库服务版本升级、数据库服务更换、测试环境数据导入都需要做好数据备份。&lt;/p&gt;
&lt;h1 id="方法技术"&gt;方法技术
&lt;/h1&gt;&lt;p&gt;添加一项站点功能或者修复一个漏洞需要一定的方法和技术，优先查看市场上的插件以及插件使用手册、评测结果并亲自体验，其次查找野路子并评估短期方案风险与长期方案进行对比，按情况择定。&lt;/p&gt;
&lt;h1 id="维护日志"&gt;维护日志
&lt;/h1&gt;&lt;p&gt;站点的维护历史。虽然叫做日志，但维护往往比较集中并且时间仓促，实则以月为单位，记录简短概括。&lt;a class="link" href="https://blog.nicelylit.net/posts/%E5%8D%9A%E5%AE%A2%E7%BB%B4%E6%8A%A4%E6%97%A5%E5%BF%97/" &gt;查看详情→&lt;/a&gt;&lt;/p&gt;</description></item><item><title>博客维护日志</title><link>https://blog.nicelylit.net/posts/%E5%8D%9A%E5%AE%A2%E7%BB%B4%E6%8A%A4%E6%97%A5%E5%BF%97/</link><pubDate>Wed, 25 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%8D%9A%E5%AE%A2%E7%BB%B4%E6%8A%A4%E6%97%A5%E5%BF%97/</guid><description>&lt;h3 id="2023年1月"&gt;2023年1月
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;升级WordPress版本至6.1.1、MySQL至5.7.25-log、PHP至5.6。&lt;/li&gt;
&lt;li&gt;为mjm1990.com域名开启SSL数字证书。关于为何要开启SSL，查看&lt;a class="link" href="https://jaeger.itscoder.com/web/2017/08/30/github-page-https" target="_blank" rel="noopener"
 &gt;这篇&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;清除两万两千多条垃圾评论。&lt;/li&gt;
&lt;li&gt;启用并购买CleanTalk垃圾评论过滤服务。&lt;/li&gt;
&lt;li&gt;通过Docker Compose提升主题开发的自动化程度，并用Github&lt;a class="link" href="https://github.com/jiamingm1990/JeremysWorld" target="_blank" rel="noopener"
 &gt;托管源代码&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;更换定制主题jeremysworld，包括改善移动端用户体验、添加导航菜单、开启并引入Google字体、升级MathJax版本、支持管理员修改备案号。&lt;/li&gt;
&lt;li&gt;发布&lt;a class="link" href="https://mjm1990.com/%e5%bd%92%e6%a1%a3" target="_blank" rel="noopener"
 &gt;归档&lt;/a&gt;、&lt;a class="link" href="https://mjm1990.com/%e5%85%b3%e4%ba%8e%e6%88%91" target="_blank" rel="noopener"
 &gt;关于我&lt;/a&gt;页面。&lt;/li&gt;
&lt;li&gt;添加站点favicon和logo，并发布&lt;a class="link" href="https://mjm1990.com/article/%e5%8d%9a%e5%ae%a2%e6%a0%87%e5%bf%97%e6%95%85%e4%ba%8b" target="_blank" rel="noopener"
 &gt;博客标志故事&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;替换用户头像服务为Cravatar。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="2020年6月"&gt;2020年6月
&lt;/h3&gt;&lt;p&gt;万网关停X享主机-X3，切换到独享虚拟主机基础版。&lt;/p&gt;
&lt;h3 id="2018年1月"&gt;2018年1月
&lt;/h3&gt;&lt;p&gt;显示文章浏览次数。&lt;/p&gt;
&lt;h3 id="2016年7月"&gt;2016年7月
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;根据不同页面类型，启动永久链接。&lt;/li&gt;
&lt;li&gt;更换定制主题BlogJ。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="2014年12月"&gt;2014年12月
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;博客上线。&lt;/li&gt;
&lt;li&gt;更新备案号。&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>购置个人台式机小记</title><link>https://blog.nicelylit.net/posts/%E8%B4%AD%E7%BD%AE%E4%B8%AA%E4%BA%BA%E5%8F%B0%E5%BC%8F%E6%9C%BA%E5%B0%8F%E8%AE%B0/</link><pubDate>Mon, 04 Apr 2022 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E8%B4%AD%E7%BD%AE%E4%B8%AA%E4%BA%BA%E5%8F%B0%E5%BC%8F%E6%9C%BA%E5%B0%8F%E8%AE%B0/</guid><description>&lt;h1 id="购置因由"&gt;购置因由
&lt;/h1&gt;&lt;p&gt;年初作计划时，重新对深入理解安卓系统发生了兴趣。四年前，曾尝试在Windows的虚拟机上编译，失败告终。如今不只是工作中需要相关知识，更是带着一种惊叹与好奇，想要更多地接近时代所造就的一些逻辑上的庞然大物中所潜藏的智能，以安抚生起的不平静的思绪与心情。&lt;/p&gt;
&lt;p&gt;细想来，只身在移动互联网和物联网的时代，对云与端的深入理解，在于系统而非应用。系统才是构建起端与云生态的支柱。云侧的分布式系统是端系统的放大版，其中的核心技术挑战所需要的方法与技能并没有超出端侧的系统太多，差别之处，仅仅是外在表现的形式。云侧的能力再庞大，没有端侧的呈现和交互也是空无意义。即便是端系统，其构建速度也已远超想象，令人望洋兴叹。开源的安卓系统无疑是一个宝藏，值得花些闲暇时间深入一番。&lt;/p&gt;
&lt;p&gt;三月初，又尝试在MacOS的虚拟机上编译，再次失败。原因是内存不足，虚拟机几乎全部的时间都在做内存磁盘交换，宿主机性能也下降明显，鼠标响应迟缓。&lt;a class="link" href="https://source.android.google.cn/setup/build/requirements" target="_blank" rel="noopener"
 &gt;官方文档&lt;/a&gt;推荐的硬件配置是至少16GB的内存，建议64GB。并举了两个例子说，72核上，一次完整编译用了40分钟，6核上，用了3个小时。我的笔记本配着英特尔i5双核四线程的处理器，4MB L3缓存，16GB DDR3 2133MHz内存。开始以为只是会慢，也还可以用，实测后表明不可行。因此，决定购买一台主机。&lt;/p&gt;
&lt;h1 id="购买过程"&gt;购买过程
&lt;/h1&gt;&lt;h2 id="坚定购买意向"&gt;坚定购买意向
&lt;/h2&gt;&lt;p&gt;产生了购买意向，但仍旧摇摆。犹疑有三。其一，主机较重，但也较为精细，搬家不便。其二，使用频率可能不高。其三，家庭耗电增多，废弃和回收麻烦。&lt;/p&gt;
&lt;p&gt;为了坚定意向，还需要找更多的理由来说服自己。&lt;/p&gt;
&lt;p&gt;在云服务商中简单浏览了一下满足我配置需求的云主机，都价格高昂。改装苹果笔记本的成本更是昂贵。价格战胜了重量。&lt;/p&gt;
&lt;p&gt;这台新的主机大概率是要直接装Ubuntu系统的，我也不会拿它作娱乐用途，笔记本和手机已经足够。唯一剩下的用途就是深入学习系统，不只在软件层面，还应该包括硬件层面，以弥补大学期间实践上的不足。操作系统的一个核心价值就是管理硬件，若对硬件不了解，也就谈不上管理。借此机会，对硬件做些深入了解，也仍旧是对专业领域内知识的增长。端设备的数量并没有因为云的出现了变得更少，代码库也没有变得更简单。通用系统和专用系统中涉及到的取舍平衡，如何在技术实现上得到体现，是个有趣的问题。嵌入式的操作系统中即使没有桌面系统中一些庞大工具（编辑器、编译器等）的身影，但构建操作系统的平台工具中这些部分也不可缺少。总之，我又为其增加了许多的使用价值。即使只是编译、运行和调试安卓系统，也不会使用频率不高。&lt;/p&gt;
&lt;p&gt;作为个人台式机，我没有搭建服务器的需要，不会7*24小时的运行。不使用的时候及时关机就好了。处理器大多数时候并不会高负荷运行，总功耗应该超不过电冰箱。至于废弃和回收，从前疏于理会，但此间事物没有东西是不损耗以及不需要花精力去保持其功能和效用的，我应当调整自己去做好这方面的管理。&lt;/p&gt;
&lt;h2 id="决定亲手组装"&gt;决定亲手组装
&lt;/h2&gt;&lt;p&gt;坚定了意向，就开始实施。最初我还在整装机中挑选，列了几个挑选的严格条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;处理器要是i5或i7的，不能低于笔记本的配置；&lt;/li&gt;
&lt;li&gt;内存要至少32GB，或者可以扩容到32GB以上；&lt;/li&gt;
&lt;li&gt;固态硬盘至少要512GB，机械硬盘2T以上；&lt;/li&gt;
&lt;li&gt;机器的重量和体积不可过大，要小巧便于搬运；&lt;/li&gt;
&lt;li&gt;机器外观要能看得过眼；&lt;/li&gt;
&lt;li&gt;品牌商客服要能说得过去。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;选了惠普、宏碁和戴尔的几款机器作了对比，比较下来，觉得惠普战99各方面都胜出，性价比高。可一想到硬件的扩展和维护，便觉得仍旧无法满足我的需求。我若没有亲历装机的过程，加装硬件可能还需要找人，而这样的事情很可能发生在内存和硬盘的升级方面。惠普战99的固态硬盘过小，销售网站也不支持直接加装和改造；内存的上限虽然适中，但原装的16GB可能也不够用。因此，决定亲手组装。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;名称&lt;/th&gt;
 &lt;th&gt;&lt;a class="link" href="https://item.jd.com/100016998929.html" target="_blank" rel="noopener"
 &gt;惠普HP小欧S01&lt;/a&gt; S01-pF277rcn&lt;/th&gt;
 &lt;th&gt;&lt;a class="link" href="https://item.jd.com/100011323001.html" target="_blank" rel="noopener"
 &gt;惠普(HP)战99&lt;/a&gt; HP ZHAN 99 Pro G4 MT&lt;/th&gt;
 &lt;th&gt;&lt;a class="link" href="https://item.jd.com/100030742808.html" target="_blank" rel="noopener"
 &gt;惠普HP小欧S01&lt;/a&gt; S01-pF254rcn&lt;/th&gt;
 &lt;th&gt;&lt;a class="link" href="https://item.jd.com/100021163396.html" target="_blank" rel="noopener"
 &gt;宏碁(Acer)商祺SQX4270 786N&lt;/a&gt;&lt;/th&gt;
 &lt;th&gt;&lt;a class="link" href="https://item.jd.com/100020307688.html" target="_blank" rel="noopener"
 &gt;戴尔dell成就3690&lt;/a&gt; Vostro 3690-R14NBR&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;价格&lt;/td&gt;
 &lt;td&gt;京东4899元&lt;/td&gt;
 &lt;td&gt;京东5499元&lt;/td&gt;
 &lt;td&gt;京东3199元&lt;/td&gt;
 &lt;td&gt;京东5499元&lt;/td&gt;
 &lt;td&gt;京东3799元&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;体积&lt;/td&gt;
 &lt;td&gt;长29cm 宽9.5cm 高27cm 体积7.4L&lt;/td&gt;
 &lt;td&gt;长27.7cm 宽17cm 高33.8cm 体积15.9L&lt;/td&gt;
 &lt;td&gt;长29cm 宽9.5cm 高27cm 体积7.4L&lt;/td&gt;
 &lt;td&gt;长30.79cm 宽10.2cm 高33.2cm 体积10.4L&lt;/td&gt;
 &lt;td&gt;长29cm 宽9.26cm 高29.28cm 体积7.9L&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;重量&lt;/td&gt;
 &lt;td&gt;毛重5.5kg 净重3.0kg&lt;/td&gt;
 &lt;td&gt;毛重7.75kg&lt;/td&gt;
 &lt;td&gt;毛重5.95kg 净重3.0kg&lt;/td&gt;
 &lt;td&gt;毛重6.6kg&lt;/td&gt;
 &lt;td&gt;毛重5.85kg 净重4.1kg&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;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;180W电源&lt;/td&gt;
 &lt;td&gt;典型能源消耗656kWh&lt;/td&gt;
 &lt;td&gt;180W电源&lt;/td&gt;
 &lt;td&gt;300W电源&lt;/td&gt;
 &lt;td&gt;典型能源消耗184kWh&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;测试&lt;/td&gt;
 &lt;td&gt;3.9万小时测试 230项测试 2000小时风扇满荷测试&lt;/td&gt;
 &lt;td&gt;105万小时无故障认证&lt;/td&gt;
 &lt;td&gt;3.9万小时测试 230项测试 2000小时风扇满荷测试 20万小时无故障&lt;/td&gt;
 &lt;td&gt;无数据&lt;/td&gt;
 &lt;td&gt;50项出厂测试&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;硬件接口&lt;/td&gt;
 &lt;td&gt;前置1个电源键 前置1个音频接口 前置预留光驱位 前置4个USB 3.2 Gen1 后置2个音频接口 后置1个VGA接口 后置1个HDMI接口 后置1个网络接口 后置4个USB 2.0 后置1个电源接口 内置PCIe x 16插槽 内置PCIe x 1插槽 内置2个内存插槽&lt;/td&gt;
 &lt;td&gt;前置1个电源键 前置1个音频接口 前置预留光驱位 前置4个USB 3.2 Gen1 前置2个USB 3.2 Gen2 后置2个音频接口 后置1个VGA接口 后置1个HDMI接口 后置1个串口 后置1个RJ-45网络接口 后置2个USB 2.0 后置1个电源接口 后置标准锁槽 后置集成配件电缆锁 后置挂环锁 内置系统风扇 内置CPU风扇&lt;/td&gt;
 &lt;td&gt;前置1个电源键 前置1个音频接口 前置预留光驱位 前置4个USB 3.2 Gen1 后置2个音频接口 后置1个VGA接口 后置1个HDMI接口 后置1个网络接口 后置4个USB 2.0 后置1个电源接口 内置PCIe x 16插槽 内置PCIe x 1插槽 内置2个内存插槽&lt;/td&gt;
 &lt;td&gt;前置1个开机键 前置1个重启键 前置1个电源灯 前置1个HDD灯 前置1个网络灯 前置2个音频接口 前置4个USB 3.2 Gen1 后置3个音频接口 后置1个HDMI接口 后置1个VGA接口 后置4个USB 2.0 后置1个RJ-45网络接口 后置2个PS2串口 后置1个电源接口 后置1个Kinsington锁孔 后置1个理线架 内置1个3.5英寸硬盘位 内置1个2.5英寸硬盘位&lt;/td&gt;
 &lt;td&gt;前置1个电源键 前置1个HDD灯 前置1个音频接口 前置2个USB 2.0 前置2个USB 3.2 Gen1 后置1个音频接口 后置1个HDMI接口 后置1个VGA接口 后置2个USB 2.0 后置2个USB 3.2 Gen1 后置1个前兆以太网接口 后置1个电源接口 后置1个电源指示灯 内置2个内存插槽&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;软件接口&lt;/td&gt;
 &lt;td&gt;支持蓝牙4.2 支持Wi-Fi&lt;/td&gt;
 &lt;td&gt;支持蓝牙 支持Wi-Fi 802.11ac 2.4GHz/5GHz双频&lt;/td&gt;
 &lt;td&gt;支持蓝牙4.2 支持Wi-Fi&lt;/td&gt;
 &lt;td&gt;支持蓝牙4.2 支持Wi-Fi 802.11ac&lt;/td&gt;
 &lt;td&gt;支持Wi-Fi&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;主板&lt;/td&gt;
 &lt;td&gt;芯片组H570 集成声卡 集成显卡 无线网卡&lt;/td&gt;
 &lt;td&gt;芯片组H570 独立显卡&lt;/td&gt;
 &lt;td&gt;芯片组H570 集成声卡 集成显卡 无线网卡&lt;/td&gt;
 &lt;td&gt;芯片组H510 独立显卡&lt;/td&gt;
 &lt;td&gt;芯片组B560 集成显卡&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;处理器&lt;/td&gt;
 &lt;td&gt;Intel第十代i7-10700 8核16线程&lt;/td&gt;
 &lt;td&gt;Intel第十一代i7-11700 2.5GHz 8核16线程 三级缓存16MB&lt;/td&gt;
 &lt;td&gt;Intel第十代i5-10400 6核12线程&lt;/td&gt;
 &lt;td&gt;Intel第十一代i7-11700 8核16线程&lt;/td&gt;
 &lt;td&gt;Intel第十一代i5-11400 6核12线程 三级缓存12MB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;内存&lt;/td&gt;
 &lt;td&gt;16GB DDR4 2933MHz 最大容量32GB&lt;/td&gt;
 &lt;td&gt;16GB DDR4 2933MHz 最大容量64GB&lt;/td&gt;
 &lt;td&gt;8GB DDR4 2666MHz 最大容量32GB&lt;/td&gt;
 &lt;td&gt;16GB DDR4 2666MHz 最大容量32GB&lt;/td&gt;
 &lt;td&gt;16GB DDR4 2933MHz 最大容量64GB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;硬盘&lt;/td&gt;
 &lt;td&gt;512GB PCIe M.2 SSD&lt;/td&gt;
 &lt;td&gt;2块1TB的3.5英寸HDD 7200rpm 1块256GB的M.2 SSD 最大支持2TB机械硬盘 最大支持1TB固态硬盘&lt;/td&gt;
 &lt;td&gt;1块1TB HDD 7200rpm 1块256GB SSD&lt;/td&gt;
 &lt;td&gt;1块1TB HDD 7200rpm 1块512GB SSD 最大支持512GB固态硬盘&lt;/td&gt;
 &lt;td&gt;1块1TB的3.5英寸HDD 7200rpm 1块256GB的M.2 SSD&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;显卡&lt;/td&gt;
 &lt;td&gt;集成UHD630&lt;/td&gt;
 &lt;td&gt;NVIDIA GeForce GTX 1660 2GB独立显存&lt;/td&gt;
 &lt;td&gt;集成UHD630&lt;/td&gt;
 &lt;td&gt;NVIDIA GeForce GT 730 2GB独立显存&lt;/td&gt;
 &lt;td&gt;集成UHD730&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;附赠&lt;/td&gt;
 &lt;td&gt;1个有线键盘 1个有线鼠标 1条电源线 1份保修卡&lt;/td&gt;
 &lt;td&gt;1个有线键盘 1个有线鼠标 1条电源线 1份保修卡&lt;/td&gt;
 &lt;td&gt;1个有线键盘 1个有线鼠标 1条电源线 1份保修卡&lt;/td&gt;
 &lt;td&gt;1个有线键盘 1个有线鼠标 1条电源线 1份保修卡&lt;/td&gt;
 &lt;td&gt;1个有线键盘 1个有线鼠标 1条电源线 1份保修卡&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;操作系统&lt;/td&gt;
 &lt;td&gt;Windows 11&lt;/td&gt;
 &lt;td&gt;Windows 11&lt;/td&gt;
 &lt;td&gt;Windows 11&lt;/td&gt;
 &lt;td&gt;Windows 10 64位家庭版&lt;/td&gt;
 &lt;td&gt;Windows 10&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;应用软件&lt;/td&gt;
 &lt;td&gt;预装正版Office家庭和学生版&lt;/td&gt;
 &lt;td&gt;预装正版Office家庭和学生版 Office必须要6个月内激活&lt;/td&gt;
 &lt;td&gt;预装正版Office家庭和学生版&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;标准保修是四年，光驱和外部设备一年。 升级保修五年，需要购买半年内绑定微信公众号注册捆绑成功后才可享受。显示器享受三年保修，一年上门服务。 微信公众号“惠普服务”，输入“保修升级”。 激活Windows和Office后的主机不能享受7天无理由退换货。&lt;/td&gt;
 &lt;td&gt;标准保修是主要部件两年，其他部件一年。 升级保修五年，需要购买半年内绑定微信公众号注册捆绑成功后才可享受。显示器享受三年保修，一年上门服务。 微信公众号“惠普服务”，输入“保修升级”。&lt;/td&gt;
 &lt;td&gt;三年有限上门保修。主要部件三年，其他部件一年。 微信公众号“Acer宏碁服务”。&lt;/td&gt;
 &lt;td&gt;三年整机上门保修。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;售后电话&lt;/td&gt;
 &lt;td&gt;固话 800-820-6616 手机 400-885-6616&lt;/td&gt;
 &lt;td&gt;固话 800-810-3888 手机 400-610-3888&lt;/td&gt;
 &lt;td&gt;固话 800-820-6616 手机 400-885-6616&lt;/td&gt;
 &lt;td&gt;手机 400-700-0118&lt;/td&gt;
 &lt;td&gt;固话 800-858-2968 手机 400-886-8611&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="围绕机箱规划"&gt;围绕机箱规划
&lt;/h2&gt;&lt;p&gt;挑选组装配件前，先从机箱入手，找了些视频和京东的商品对比。这样做是考虑到，最终放在家里的并不是一个个的配件，而是一个机箱，从它的物理参数开始，对内部的构造作全面的了解总是没错的。两个入门视频，质量一般，但基本的信息量也足够一个新人消化的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.bilibili.com/video/BV1C44y1C78v?from=search&amp;amp;seid=12190182940006320483&amp;amp;spm_id_from=333.337.0.0" target="_blank" rel="noopener"
 &gt;机箱篇：机箱选购指南（含机箱推荐）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.bilibili.com/video/BV1nt411v79G?from=search&amp;amp;seid=12190182940006320483&amp;amp;spm_id_from=333.337.0.0" target="_blank" rel="noopener"
 &gt;电脑机箱篇-科普机箱尺寸和风道的知识，以后买机箱不会懵逼了&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对比了十多个商品，最终都没买，可至少想清楚了机箱挑选中关键的参数。除了体积和重量以外，还需要关注&lt;/p&gt;
&lt;ol&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;机箱风扇大小、数量和位置&lt;/li&gt;
&lt;li&gt;硬盘位数量和位置&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;至于材质和外观，相对主观，一般人也都不会放弃考虑，无需强调。&lt;/p&gt;
&lt;h2 id="围绕主板规划"&gt;围绕主板规划
&lt;/h2&gt;&lt;p&gt;除了从机箱入手外，也可以从主板入手，先了解主板上外插的配件，再延展到机箱。总之，从空间入手，由整体到部分，由体积占比大的到占比小的，总错不了。实际上对于选购，看下面这两个视频更合适：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.bilibili.com/video/BV1k64y1h7QP/?spm_id_from=333.788.recommend_more_video.-1" target="_blank" rel="noopener"
 &gt;装机不求人之：硬件基础讲解，零基础新手打造电脑配置单教程。一站式台式机DIY入门指南！游戏主机/生产力主机全攻略！&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.bilibili.com/video/BV1jR4y1c71a?spm_id_from=333.999.0.0" target="_blank" rel="noopener"
 &gt;史上最强！主板选购万能攻略。一站式主板基础知识入门指南！带你全面了解主板基础参数！&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我在装完机后才注意到上面两个视频，也就懒得细看了。我对各个配件的性能需求相对明确，只是对主板本身的构造和扩展槽没有太过深入地从各个角度一个个地考察过。我的主要做法是三点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先找了些装机视频，按需，反复观看揣摩；&lt;/li&gt;
&lt;li&gt;然后具体挑选了一个有购买意向的主板，详细阅读了官网下载的中英文说明书；&lt;/li&gt;
&lt;li&gt;此外也制定了整体计划，并记录了挑选的配件和心得。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;装机视频中质量较高的是以下的两个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.bilibili.com/video/BV1UU4y1c74o?p=1" target="_blank" rel="noopener"
 &gt;装机不求人之：史上最详细传说级装机教程，零基础新手一站式装机攻略。内含AMD、Intel双平台DIY组装教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.bilibili.com/video/BV1jE411e7hw?p=1" target="_blank" rel="noopener"
 &gt;这可能是你能在网上找到最详细的装机教程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;挑选主板，先看主板品牌，再看主板人气排行，同时要权衡价格。为了避免质量上的风险，我只看了京东自营的商品。最终下载了华硕的以下两款说明书，详细地进行阅读。阅读中，一方面是熟悉主板上的空间结构、插槽用途、插槽标准，另一方面是要搞清楚匹配的配件有哪些。虽然华硕官网可以查到些推荐的配件品牌和型号，但仍旧不够全面。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.asus.com.cn/Motherboards-Components/Motherboards/TUF-Gaming/TUF-GAMING-B660M-PLUS-WIFI-D4/HelpDesk_Manual/" target="_blank" rel="noopener"
 &gt;TUF GAMING B660M-PLUS WIFI D4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.asus.com.cn/Motherboards-Components/Motherboards/PRIME/PRIME-B660M-A-WIFI-D4/HelpDesk_Manual/" target="_blank" rel="noopener"
 &gt;PRIME B660M-A WIFI D4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在整体计划方面，我考虑了挑选的方法、配件间的兼容性、机器的组装以及操作系统的安装，却忽视了配送过程和硬件驱动的安装。导致下单后更换了内存，配置过程中，系统安装花费了很大的精力。不过好在，软件是我擅长的部分，不需要联系客服，也算不上规划上的失误，只能说，即便当下个性化组装盛行，接口设计已经十分友好，个性化定制也依旧是个复杂的事情。&lt;/p&gt;
&lt;h2 id="选定配件清单"&gt;选定配件清单
&lt;/h2&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;th&gt;长 (cm)&lt;/th&gt;
 &lt;th&gt;宽 (cm)&lt;/th&gt;
 &lt;th&gt;高 (cm)&lt;/th&gt;
 &lt;th&gt;重量 (kg)&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;a class="link" href="https://item.jd.com/100031223640.html" target="_blank" rel="noopener"
 &gt;TUF GAMING B660M PLUS WI-FI D4&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;24.4&lt;/td&gt;
 &lt;td&gt;24.4&lt;/td&gt;
 &lt;td&gt;~5&lt;/td&gt;
 &lt;td&gt;1.6&lt;/td&gt;
 &lt;td&gt;1037&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;a class="link" href="https://item.jd.com/100026951642.html" target="_blank" rel="noopener"
 &gt;12600KF&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;4.5&lt;/td&gt;
 &lt;td&gt;3.75&lt;/td&gt;
 &lt;td&gt;~0.5&lt;/td&gt;
 &lt;td&gt;0.088&lt;/td&gt;
 &lt;td&gt;1926&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;a class="link" href="https://item.jd.com/100004036677.html" target="_blank" rel="noopener"
 &gt;TL-AS120&lt;/a&gt;+&lt;a class="link" href="https://item.jd.com/100015718719.html" target="_blank" rel="noopener"
 &gt;LGA17XX-SS2&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;7.3&lt;/td&gt;
 &lt;td&gt;15.4&lt;/td&gt;
 &lt;td&gt;0.935&lt;/td&gt;
 &lt;td&gt;142.4&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;a class="link" href="https://item.jd.com/100005171175.html" target="_blank" rel="noopener"
 &gt;复仇者LPX DDR4 3600 64GB(32G×2)&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;13.5&lt;/td&gt;
 &lt;td&gt;3.35&lt;/td&gt;
 &lt;td&gt;0.7&lt;/td&gt;
 &lt;td&gt;~0.1&lt;/td&gt;
 &lt;td&gt;2368&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;a class="link" href="https://item.jd.com/100019517363.html" target="_blank" rel="noopener"
 &gt;SN770 1TB&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;8&lt;/td&gt;
 &lt;td&gt;2.2&lt;/td&gt;
 &lt;td&gt;0.238&lt;/td&gt;
 &lt;td&gt;0.041&lt;/td&gt;
 &lt;td&gt;868&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;a class="link" href="https://item.jd.com/100024901908.html" target="_blank" rel="noopener"
 &gt;GeForce GT730-SL-2GD5-BRK GDDR5 2GB&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;17&lt;/td&gt;
 &lt;td&gt;~4&lt;/td&gt;
 &lt;td&gt;~6&lt;/td&gt;
 &lt;td&gt;0.455&lt;/td&gt;
 &lt;td&gt;496&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;a class="link" href="https://item.jd.com/7546124.html" target="_blank" rel="noopener"
 &gt;HGE650&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;17&lt;/td&gt;
 &lt;td&gt;15&lt;/td&gt;
 &lt;td&gt;8.6&lt;/td&gt;
 &lt;td&gt;2.68&lt;/td&gt;
 &lt;td&gt;599&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;a class="link" href="https://item.jd.com/100025663574.html" target="_blank" rel="noopener"
 &gt;平头哥M2&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;40 (&amp;gt; 24.4)&lt;/td&gt;
 &lt;td&gt;20.5 (&amp;gt; 15.4)&lt;/td&gt;
 &lt;td&gt;38 (&amp;gt; 30.75)&lt;/td&gt;
 &lt;td&gt;4.6&lt;/td&gt;
 &lt;td&gt;99&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;a class="link" href="https://item.jd.com/1026103.html" target="_blank" rel="noopener"
 &gt;游戏风暴&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;2.5&lt;/td&gt;
 &lt;td&gt;0.1&lt;/td&gt;
 &lt;td&gt;9.9*3&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;td&gt;40&lt;/td&gt;
 &lt;td&gt;20.5&lt;/td&gt;
 &lt;td&gt;38&lt;/td&gt;
 &lt;td&gt;10.6&lt;/td&gt;
 &lt;td&gt;7565.1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;体积方面，主机箱的长取决于主板的长，宽取决于散热器的高，高取决于主板的宽加电源的高。重量方面，机箱、电源和主板占大头。表中有些数据用的是商品介绍中的毛重，即带着纸箱的重量，实际应该会轻个一两公斤，没有实测。价格方面，内存、处理器和主板占大头，这也符合我的需求；硬盘、电源和显卡是下一梯队；散热器、机箱和风扇较为廉价，我没有RGB灯的需求，似乎有点浪费主板的针脚，可我也没配机械硬盘，满足需求就好，关键要过心理关。&lt;/p&gt;
&lt;p&gt;内存最初选了金士顿KF432C16BBK2 DDR4，但两天过后，仍旧不发货，状态总变，于是换了64GB榜单中排名第二的美商海盗船。价格虽然贵了两百左右，但频率也有提升。显卡最初选了华硕PH GeForce GT1030-O2G 1252-1531MHz GDDR5，但下单前突然缺货，便换了差一些的GT730，也完全够用。电源曾想选全汉MS600，SFX的电源，其中虽然包含了ATX的转换面板，但还是担心模线长度不够在MATX的机箱中走线，只好牺牲小重量而取了大长度。&lt;/p&gt;
&lt;h1 id="装机过程"&gt;装机过程
&lt;/h1&gt;&lt;p&gt;装机过程是一个典型的工程实践，符合工程学的规律。作为工程师，这一步应该是职业技能的直接应用。硬件与软件在方法论的层面，也没有太多的不同。方法层面归结为以下五点：&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;p&gt;购买过程中，通过看视频和说明书，我已经在头脑中过了几遍装机过程。意外处理和调试方法考虑得较少，但也有所涉及。&lt;/p&gt;
&lt;h2 id="描述成功状态"&gt;描述成功状态
&lt;/h2&gt;&lt;div class="floatblock floatblock-right brand-block" style="--floatblock-w: 480px;"&gt;
 
&lt;img src="https://blog.nicelylit.net/wp-content/uploads/2022/04/image1.png" alt="logo" style="max-width:480px; margin: 0 auto 12px;"&gt;

&lt;/div&gt;
&lt;p&gt;在观看装机视频过程中，注意到成功的开机状态应该是在显示器上看到类似下面图片一样的界面。具体来说，至少应当包含以下三个方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;硬件自检程序完成，进入UEFI BIOS界面，并能看到所有硬件的状态；&lt;/li&gt;
&lt;li&gt;查看各个硬件信息是否符合商品描述；&lt;/li&gt;
&lt;li&gt;查看机箱前面板和后面板的接口是否都能正常工作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;前两步到BIOS出现，基本可以算硬件没有故障。第三步需要装上操作系统后再实测以下，特别是网络接口和音频接口。显示器接口、电源开关、重启开关、电源指示灯和USB接口在BIOS阶段也都会得到检查。只要不是硬件的问题，就能省去沟通客服的麻烦。&lt;/p&gt;
&lt;h2 id="划分大的阶段"&gt;划分大的阶段
&lt;/h2&gt;&lt;p&gt;我给自己设定了两个阶段的目标。第一阶段，将配件全部组装好放入机箱后，不完全拧上8个固定螺丝，接好线，不扣主机盖，只接一个机箱风扇，不理线，插上电源线和显示器查看状态；第二阶段，将前面剩余的步骤全部做完，再作测试和检查。&lt;/p&gt;
&lt;h2 id="操作步骤和注意事项"&gt;操作步骤和注意事项
&lt;/h2&gt;&lt;p&gt;装机过程的主要步骤和次序：&lt;/p&gt;
&lt;ol&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;处理器上涂抹硅胶&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;/ol&gt;
&lt;div class="floatblock floatblock-right brand-block" style="--floatblock-w: 480px;"&gt;
 
&lt;img src="https://blog.nicelylit.net/wp-content/uploads/2022/04/image2.jpg" alt="logo" style="max-width:480px; margin: 0 auto 12px;"&gt;

&lt;/div&gt;
&lt;p&gt;主要的注意事项：&lt;/p&gt;
&lt;ol&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;避免用手触碰任何一个配件上裸露的电路元件&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;按压内存要用力均匀&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;散热器螺丝紧固程度要左右均衡&lt;/li&gt;
&lt;li&gt;风扇的风向要符合机箱的设计&lt;/li&gt;
&lt;li&gt;避免螺丝留在主板上&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="检验步骤和注意事项"&gt;检验步骤和注意事项
&lt;/h2&gt;&lt;p&gt;主要的接线检查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;电源接主板20+4pin&lt;/li&gt;
&lt;li&gt;电源接处理器8+4pin或8pin&lt;/li&gt;
&lt;li&gt;散热器风扇接主板4pin&lt;/li&gt;
&lt;li&gt;前面板接主板USB 3.0 19针 角缺一针&lt;/li&gt;
&lt;li&gt;前面板接主板USB 2.0 9针 角缺一针&lt;/li&gt;
&lt;li&gt;前面板接音频 9针 边缺一针&lt;/li&gt;
&lt;li&gt;前面板电源指示灯接主板 2pin 分正负极&lt;/li&gt;
&lt;li&gt;前面板硬盘指示灯接主板 2pin 分正负极&lt;/li&gt;
&lt;li&gt;前面板电源开机键接主板 2pin&lt;/li&gt;
&lt;li&gt;前面板电源重启键接主板 2pin&lt;/li&gt;
&lt;li&gt;机箱风扇接主板 4pin/3pin&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;由于我的配置简单，没有外挂的硬盘，显卡低端不需要单独供电，不玩灯效不需要考虑LED等的接线。电源出来的线只有两条。没有配置外挂的硬盘，也造成机箱头轻脚重。&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;li&gt;避免风扇供电的并联造成短路&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="意外处理和调试方法"&gt;意外处理和调试方法
&lt;/h2&gt;&lt;p&gt;意外处理的基本方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先说清楚出现了怎样的问题，&lt;/li&gt;
&lt;li&gt;其次要搞清楚造成问题的是哪个部分，&lt;/li&gt;
&lt;li&gt;然后要评估这个部分的故障是不是自己能解决的，优先考虑利用手边工具解决，若不行，再考虑其他的工具或者购买工具解决，最后是找人帮助或者找客服。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;意外的类型从伤害对象上可分为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;伤害自己；&lt;/li&gt;
&lt;li&gt;伤害某个配件；&lt;/li&gt;
&lt;li&gt;伤害自己和主机之外的人和物。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于调试，硬件方面，参考以下的视频足矣，软件方面，作为软件工程师，自己应该胜任所有问题的解决。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.bilibili.com/video/BV1UY411W7Y9?spm_id_from=333.999.0.0" target="_blank" rel="noopener"
 &gt;电脑点不亮？黑屏？重启？ 史上最强电脑问题排查诊断攻略！装机不求人系列【超详细】&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="处理意外情况"&gt;处理意外情况
&lt;/h2&gt;&lt;p&gt;注意事项越是做到提前心理有数，就越能避免意外的发生。不过也要减少过度担心，对于好的产品，工程师一定比用户有更加充分的考量。这次装机中，我过度担心风扇的3pin接口，插左插右的问题，网上也没人提，实际发现主板上做了很好的提示和兼容，不存在插错的问题。执行过程整体上比较顺利，一次成功，但也有两个小意外和一个大意外，但都还能独自处理。&lt;/p&gt;
&lt;p&gt;第一个小意外是，处理器盖子打开的方向和下压时的力度让人错愕。处理器的塑料保护盖没有在最下面的时候脱落，而是压到一半时候脱落，以为搞错了主板和处理器的型号，不过后来还是大胆地压了下去。&lt;/p&gt;
&lt;p&gt;第二个小意外是，机箱显卡槽位凸起对HDMI接口产生遮挡导致显卡的HDMI接口插不到底。想来这样的问题很难在网络挑选过程中发现，无奈只好让显卡不能垂直固定，偏了一定的角度。后面查看用户评论时候，有人也遇到过类似的问题，觉得这样做问题不大，我却有些担心长期如此造成PCIe插槽的形变。不过我嫌重新挑选和退换货麻烦，只能接受这点瑕疵。&lt;/p&gt;
&lt;p&gt;最大的意外是，因为疏忽了主机箱中有三种螺丝，导致固定主板时候出现滑丝。支撑主板的螺柱需要匹配直径小一点的螺丝，我没对螺丝分类，随机抓了个大的，导致螺丝拧不到底，固定不了主板，可反向松开，还把螺柱给带起来了。解决这个问题的关键是要将大号的螺丝从螺柱上拧下来，这步无论如何都要面对。基本思路是一只手用老虎钳夹住螺柱，同时另一只手松螺丝。随之产生了两种思路，一种是直接在机箱上操作，另一种是把主板取下来操作。第一种思路下，要在机箱上进一步固定紧螺柱，主板的背面螺柱会露出较长的部分，可以用老虎钳夹住，但由于机箱前面板和上面板的遮挡，看不清转动的位置，用力过大害怕直接把旁边的针脚碰断，也怕脱手直接将螺丝刀杵到主板上。另外，老虎钳夹住的部分并不算太长，若将眼睛放在主板背面，就顾及不到前面。若有两人，或许这种方法还可行。最终还是采用了第二种思路，折腾了许久才得到解决，代价是划伤了桌面。&lt;/p&gt;
&lt;p&gt;最后，还是应该配上第一时间成功点亮的照片，以作纪念。&lt;/p&gt;
&lt;p&gt;&lt;img alt="2065412220" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2022/04/2065412220.jpg"&gt;&lt;/p&gt;
&lt;h1 id="配置过程"&gt;配置过程
&lt;/h1&gt;&lt;h2 id="五个困难"&gt;五个困难
&lt;/h2&gt;&lt;p&gt;配置过程提前没做太多规划，只粗略地规划了以下三项：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;阅读官方BIOS文档&lt;/li&gt;
&lt;li&gt;制作USB启动盘&lt;/li&gt;
&lt;li&gt;安装操作系统&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此前有安装操作系统的经验和配置安卓系统编译环境的经验，因而只规划到了安装完操作系统为止，但实际上离成功在模拟器上运行安卓系统还有很远的距离。不过，如前所言，软件的领域，我应当能去解决所有遇到的问题。具体遇到的问题有以下五个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ubuntu 18.04提示有线网卡芯片无法识别，安装程序直接退出关机；&lt;/li&gt;
&lt;li&gt;Ubuntu 20.04与21.10无法识别无线网卡和蓝牙，驱动无法加载；&lt;/li&gt;
&lt;li&gt;家中没有有线鼠标，键盘无法操控每一个图形界面的功能；&lt;/li&gt;
&lt;li&gt;存放安卓开源系统的移动硬盘是苹果的文件系统，Ubuntu无法直接挂载；&lt;/li&gt;
&lt;li&gt;安卓模拟器无法正常加载系统。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="解决方法"&gt;解决方法
&lt;/h2&gt;&lt;p&gt;针对问题1，遇到的错误提示为“r8169 0000:05:00.0: unknown chip XID 641”。r8169是Realtek网络接口控制器的编号，这个错误意思应该是操作系统无法识别有线网卡，无法利用有线网卡控制器，因此不能进行后续安装。解决方法是安装高版本的20.04或者21.10，高版本内核中解决了这个问题。&lt;/p&gt;
&lt;p&gt;针对问题2，遇到的情况是，设置面板中无法设置无线网络和蓝牙。执行命令“sudo dmesg | grep iwlwifi”，发现驱动无法加载。查看英特尔官网和Linux固件的代码库，均找不到错误提示中所要求的驱动版本。安装最新的驱动，执行“ls /lib/firmware/iwlwifi-*”查看后也依旧不解决问题。最后发现是内核和固件驱动不匹配，需要内核与固件都升到最新。内核从5.13升级到了&lt;a class="link" href="https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.17.1/" target="_blank" rel="noopener"
 &gt;5.17&lt;/a&gt;。固件从默认升级到了&lt;a class="link" href="http://ftp.sjtu.edu.cn/ubuntu/pool/main/l/linux-firmware/linux-firmware_20220329.git681281e4-0ubuntu1_all.deb" target="_blank" rel="noopener"
 &gt;20220329的版本&lt;/a&gt;。具体做法可参考以下的三篇文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.csdn.net/chepwavege/article/details/123390479" target="_blank" rel="noopener"
 &gt;新电脑硬件DIY+ 安装Ubutun 18.04+排雷&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.csdn.net/qq_18683985/article/details/79961378" target="_blank" rel="noopener"
 &gt;更新Ubuntu内核到最新版本&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://ubuntuhandbook.org/index.php/2022/03/linux-kernel-5-17-released/" target="_blank" rel="noopener"
 &gt;Linux kernel 5.17 Released! How to Install it in Ubuntu 22.04&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解决问题2的一个前提是必须要有网络，否则无法下载。好在读研究生时候的一条网线留着还能用。另一个前提是最好把问题3给解决了，否则，没有鼠标极难操控。这个问题说容易也容易，说麻烦也麻烦。说容易是因为，随便找一个有线鼠标就能解决问题。说麻烦恰恰是因为我不想买这个有线鼠标。最终倒腾许久找到了一个解决方案。手上罗技的鼠标支持无线发射器模式，驱动控制和配对可以通过安装一个名为Solaar的程序完成。有了鼠标后，一切都变得高效起来。有了鼠标，连上无线和蓝牙，第一件事情就是检查音频接口。能走到这里，USB接口基本都检查过了，剩下几个后面板的USB是主板自带的，出故障概率不大。有线网络接口也检查过了，否则无线和蓝牙也用不了。其他的开机键、重启键、指示灯也都通过了检查。&lt;/p&gt;
&lt;p&gt;针对问题4，解决思路是把移动硬盘挂回到苹果笔记本，通过开启苹果笔记本的远程登录，用scp命令进行复制。在一个局域网中传输速度也有20MB/s，166GB传输需要2.4小时。&lt;/p&gt;
&lt;p&gt;针对问题5，前后编译了三个目标，前两次遇到的错误不同，第三次成功运行。第一次错误提示是一个音频的函数执行出错，现象是一个古老版本的模拟器界面被运行，但有该错误日志且系统并没有被启动起来。第二次错误提示是userdata-qemu.img文件不存在，并且模拟器的运行界面也没有运行。这三次编译都顺利完成，解决前两次模拟器运行的失败，大概需要增加特定的模拟器启动的参数，网络上不太容易找到直接的解决方案。如果要自己琢磨出解决方案，需要对模拟器参数和编译产物有较多的理解，暂且搁置。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;aosp_cf_x86_64_phone-userdebug&lt;/li&gt;
&lt;li&gt;aosp_x86_64-eng&lt;/li&gt;
&lt;li&gt;sdk_phone_x86_64&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="2065412220" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2022/04/Screenshot-from-2022-04-03-12-07-06.png"&gt;&lt;/p&gt;
&lt;h2 id="编译性能"&gt;编译性能
&lt;/h2&gt;&lt;p&gt;性能方面，第一次编译，耗时近两个小时。这个记录不及官方72核的40分钟，但却好过6核的3个小时。16核的算力还算不错。内存消耗没有想象中那么多，始终稳定在20%上下。处理器的温度在50摄氏度上下，固态硬盘的温度到了60摄氏度。第二次编译，耗时一小时一刻钟。第三次编译，耗时一刻钟，应该是复用了前两个编译目标的一些编译产物。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;#### build completed successfully (01:54:50 (hh:mm:ss)) ####&lt;/li&gt;
&lt;li&gt;#### build completed successfully (01:14:37 (hh:mm:ss)) ####&lt;/li&gt;
&lt;li&gt;#### build completed successfully (14:45 (mm:ss)) ####&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="小结"&gt;小结
&lt;/h1&gt;&lt;p&gt;这是一次有趣的购置体验，意外出现的量不小，但都还在个人以小时为单位能解决的范围内。从3月19日开始规划和调研，利用了三个周末和几个工作日的晚上，半个月顺利完成目标。归结下来，做成一件事，个人可控制的方面不外乎以下三点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;产生意向并坚定意向；&lt;/li&gt;
&lt;li&gt;想清楚目标并能描述清楚目标；&lt;/li&gt;
&lt;li&gt;在头脑中预先想清楚每个大的步骤、注意事项、意外的应对策略，详细分解稍微陌生一点的领域的事项到能够上手执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;做到意志坚定、成竹在胸，才能临阵不乱。&lt;/p&gt;</description></item><item><title>判定、构造和证明</title><link>https://blog.nicelylit.net/posts/%E5%88%A4%E5%AE%9A%E6%9E%84%E9%80%A0%E5%92%8C%E8%AF%81%E6%98%8E/</link><pubDate>Mon, 14 Jun 2021 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%88%A4%E5%AE%9A%E6%9E%84%E9%80%A0%E5%92%8C%E8%AF%81%E6%98%8E/</guid><description>&lt;h2 id="引子"&gt;引子
&lt;/h2&gt;
 &lt;blockquote&gt;
 &lt;p&gt;最近学习近世代数基础时，短短的一章，一共出现了32个定义、10个性质和30个定理。每一个性质和定理都要经过证明，并非一个顺畅的过程，特别是证明庞加莱定理的那个引理的证明，理解起来颇费精力。学习过程中最为愉快的是认识了同构。同构的定义将置换群与抽象群联系在了一起，从而构造同构的方法与构造对称群的子群的方法被联系在了一起，形成一个完美的闭环。&lt;/p&gt;
&lt;p&gt;课程中的性质和定理无非是一些判定，涉及到群、子群、陪集、正规子群、同态、同构的判定。课程中的定义和证明涉及到构造，包括正规子群、自同构的构造。任何一条都够抽象，很容易让人陷入到去找具体例子当中去，从而忘记了为什么要讨论当下的这条内容，这点与设计软件时，总是钻入下层甚至更底层有些类似。避免这样的状况发生的办法核心是一条，回到因果链的原始状态，找到那个大的因，并且从这个因上，感受到意义和价值。如果感受不到意义和价值，那放弃了，大概也没什么可惜的。例如，群论的产生的大的因是一元高次方程是否可根式求解的问题。我已经记不清为什么一年前会突然开始对这个问题非常感兴趣，但我很清楚，我迄今仍就对这个问题充满兴趣，虽然根式解的答案我已经很清楚，但了解其论证过程依旧能让我感到意义，并且激发我进一步的兴趣。&lt;/p&gt;
&lt;p&gt;主动做一件事情上让人觉得有意义，一定是过程中诱发了别样的生命体验，不一定舒适，但也不会太痛苦。学习近世代数诱发我体验到的是看清生命和思维的局限性，能够看清人生道路的边界，不至于走出去，偏离了终极目标。具体到思维上，经过此番学习，至少我对判定、构造和证明有了更加清晰的认识。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;判定、断定、断言是对所感知事物的描述和叙说，其结果称作是一个陈述或者一个命题。这个思维过程是基于感知和记忆来展开的，过程的结果以记忆的形式对内留存，或以语音和文字的形式对外留存。所形成的记忆并不局限于大脑，还包括身体各个部位的神经和肌肉，以致产生思维过程的本体，从而形成经验。当我们断言产生思维过程的本体与肉体同一的时候，会有超出经验的观察，被人们称作超验或者先验；可当我们断言本体跨越多个肉体的时候，只需要留存经验的概念来解释一切的观察。不论是超验还是经验都是基于本体可感知、可观察和可断言而谈的。可感知的事物在思维过程中被对象化、抽象化、符号化，从而脱离了感知，只留存为了记忆。&lt;/p&gt;
&lt;p&gt;构造、搭建、创造、创作是将经验运用在新的感知对象上，从而产生新的概念和感知对象的过程。在运用经验时，运用者必须意图明确并且能力充分，否则作用会产生偏差。明确意图与做判定的过程类似，要基于感知抽取记忆对象和符号，对将要进行的过程，全面清晰地描述。虽然使用描述这个字眼，但并不意味着一定要用嘴说或者用手写，而是通过念头来完成。明确意图意味着要将念头聚焦到与一个对象相关的一系列记忆上，也就是要专注。能够专注才能意图明确。能力是践行经验的要素。能力充分与否只有运用者才能够全面的认识到，因为从运用者的肉身到肉身以外的一切可感知的事物所具有的局限性就是运用者的能力。具备认知局限性的能力本身也是一种能力。可见能力必然是有边界的，否则所谓的创造也就没有了意义。人们追逐能力的提升，与追求出世间的自由是天然矛盾的，因为出世间的自由是没有边界的。回到世间的追求上，追求创造力的提升，也就是在追求局限性的缩小，也就要提升感知力、专注力和行动力。&lt;/p&gt;
&lt;p&gt;论述、论证、证明是命题（陈述）之间的推演，不产生新的概念和可感知对象。理想中推导和演绎的过程不需要感知力和行动力，这大概也是人们追求人工智能的一个基础假设，感知力和行动力对应到如今的计算机模型上，就是外部设备的功能。这样的假设显然与常人的经验是相悖的，具备与运用推导和演绎的能力往往需要强大的感知力和行动力（对于计算机，人们只好不停地加缓存，头疼于对信息不一致的问题）。现实中的论证过程，不可能是在所有的命题都具足的前提下进行的，这就要求论证者做判断的过程快速准确，若相信本体跨越多个肉体，那么论证者必然是一个经验丰富的跨越者。命题不具足的时候，往往还需要构造新的对象与符号，这又要求创造力。当然没有人会否认论证过程需要高度的专注，甚至从专注上升到了信念。&lt;/p&gt;</description></item><item><title>编译器前端回顾（下）</title><link>https://blog.nicelylit.net/posts/%E7%BC%96%E8%AF%91%E5%99%A8%E5%89%8D%E7%AB%AF%E5%9B%9E%E9%A1%BE%E4%B8%8B/</link><pubDate>Sun, 09 Aug 2020 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E7%BC%96%E8%AF%91%E5%99%A8%E5%89%8D%E7%AB%AF%E5%9B%9E%E9%A1%BE%E4%B8%8B/</guid><description>&lt;p&gt;编译器前端的程序主干是字符串的匹配，可真正的目的是翻译，将源代码转换为目标码。若将匹配流程比作骨架，那么翻译方法就是血肉。设计翻译方法，在于解决三个问题：如何表示源代码中的语义？如何表示目标码？如何将源代码到目标代码的翻译步骤添加到程序的主干流程当中？&lt;/p&gt;
&lt;p&gt;语义是藏在文字符号本身之上的一种可被解释与理解的特性。比方说，符号&amp;gt;，符号本身只是个开口向左的V，而藏在符号之上的特性是大于，用于体现符号左右两侧的量的大小关系；如果两个连在一起，还表示右移。源代码中藏有的语义可分两类，一类体现了目标码的计算特性，另一类体现了源代码的控制复杂特性。第一类在翻译中会通过不同的形式表示，最终翻译为目标码。第二类在翻译中辅助程序做安全检查和组织目标码结构。体现目标码的计算特性的语句类型有算术表达式、逻辑表达式、比较表达式、函数调用和控制流。体现源代码的控制复杂特性的语句类型主要是类型声明，以及一些附加的限定规则。&lt;/p&gt;
&lt;p&gt;在上下文无关文法的框架下，一篇源代码中的字符流，会被转换为文法符号流，语义特性可以作为文法符号的某个属性。语法制导定义中，依据属性对其他符号属性的依赖的不同，将属性分作了两类，继承属性和综合属性。继承属性值的计算依赖于产生式中当前符号左侧的其他文法符号的属性值。综合属性值的计算依赖于产生式中右侧的文法符号的属性值。上下文无关文法通过产生式将源代码映射为了一棵语法分析树，分析树上的结点是文法符号。继承属性值的计算依赖于父结点和兄弟结点的属性值。如果是L属性的语法制导定义，那么规定只依赖于左侧的兄弟结点。综合属性值的计算依赖于孩子结点的属性值。目标码是语法分析树文法结点的主属性，可以表示为综合属性，或者直接依照分析过程生成到文件中或者数组、字符串中。&lt;/p&gt;
&lt;p&gt;目标码要运行在解释器上，而解释器的类型是多样的，可能是物理机器，也可能是软件虚拟机，一种语言若要兼容多种运行环境，那么设计一种中间代码能够让前端的程序得以复用。特别是当有M种语言要运行在N种解释器上时，可以让编写编译器的数量从M*N降低到M+N。&lt;/p&gt;
&lt;p&gt;中间代码要尽可能地接近解释器上运行的指令。一条指令通常包含一个指令名，可以理解为操作符，以及不超过两个的操作数。如果一条指令有两个操作符，那么一定能够拆成两条指令。如果一条指令有多于两个操作数，那么也一定能够拆成两条以上的指令。比如，函数的调用就是个很好的例子。每个参数都能单独拆成一条单操作数指令，函数调用本身也是一条单操作数指令。这种一个操作符和两个操作数的表示方法称作三地址代码，再考虑到指令本身相对其他指令的序列编号，即地址，也就有了中间代码的四元表示。&lt;/p&gt;
&lt;p&gt;三地址码的另一种等价形式是语法树，中间结点是操作符，叶子结点是操作数。如果考虑子表达式的复用，更一般的形式是有向无环图。语法树不同于语法分析树，语法树与三地址码等价，而语法分析树同语法推导的分析过程等价。&lt;/p&gt;
&lt;p&gt;在语法制导定义中添加构造语法树的语义动作，伴随符号的匹配和产生式的规约，执行属性求值、语法树的构造与翻译。在产生式中引入表记的技术能够被用来回填无条件跳转指令中缺失的地址信息。设计语义动作可以依据以下的步骤进行：一、找出关联的几个属性；二、找出哪些是综合属性，哪些是继承属性；三、写出该有哪些语义动作；四、确定语义动作的顺序；五、校验。&lt;/p&gt;</description></item><item><title>编译器前端回顾（上）</title><link>https://blog.nicelylit.net/posts/%E7%BC%96%E8%AF%91%E5%99%A8%E5%89%8D%E7%AB%AF%E5%9B%9E%E9%A1%BE%E4%B8%8A/</link><pubDate>Sun, 17 May 2020 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E7%BC%96%E8%AF%91%E5%99%A8%E5%89%8D%E7%AB%AF%E5%9B%9E%E9%A1%BE%E4%B8%8A/</guid><description>&lt;p&gt;在设计编译器的前端时，程序驱动的主干是字符串的匹配，所回答的基本问题是_&lt;strong&gt;输入文本&lt;/strong&gt;&lt;em&gt;是否能够匹配**&lt;em&gt;预定义规则&lt;/em&gt;**，因而返回值是&lt;/em&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;/p&gt;
&lt;p&gt;如何将输入文本切割为合适粒度的字符串集合？就需要通过设计预定义规则对其进行描述。这样的规则系统就是上下文无关文法。一个文法是一个产生式的集合，一个产生式通过非终结符的替换，能够推导出一个句子，而一个句子可以是整个输入文本或者只是部分的输入文本。产生式规定了切割的粒度。&lt;/p&gt;
&lt;p&gt;产生式是值得深入讨论的模型，看似简单，却又千变万化。产生式的左端是非终结符号，规定可以被替换成右边的字符序列。产生式的右端是终结符和非终结符组成的字符串。终结符来自于字符表，而非终结符来自于字符表之外，单独构成另外的一个集合。&lt;/p&gt;
&lt;p&gt;观察单个的产生式，像是一个变量的定义，也像是一个函数的定义，像是一个类的定义，总之像是一种定义，就是拿一个符号来代表一组固定序列的符号。文字上人们称其为替换，逻辑上人们称其为抽象。看到产生式的左边，将其替换（展开）为右边的操作称作推导。看到产生式右边，将其替换（合并）为左边的操作称作归约。&lt;/p&gt;
&lt;p&gt;观察单个产生式的左边和右边的非终结符，存在一种特殊的形式：递归，即产生式右边的非终结符里包含产生式左边的符号。对于某个非终结符如果存在一个递归的展开（A → Aa ），那么一定还要有一个非递归的展开（A → b ），否则推导无法结束。容易联想到，在数学递推公式中等于在说，必须给定初始条件，否则推导无法结束。此外，这也往往作为检查递归程序是否正确的重要一点。&lt;/p&gt;
&lt;p&gt;根据左边的符号在右边出现的位置不同，分三种情况，开头、中间和结尾。出现在开头，被称作左递归。出现在结尾，被称作尾递归。出现在中间，就是普通的递归。左递归是设计文法时要避免的，含有左递归的文法无法进行自上而下的分析。消除左递归需要引入新的非终结符并利用空串，添加新的产生式。普通递归和尾递归在文法设计时没有特殊之处，可在设计解析器的时候，尾递归往往可以进行优化，即返回当前调用的上下文，清除当前栈帧，而进行新的调用，减少调用栈的层次。这样的优化虽然对执行本身起到了好处，但却给调试工具引入了麻烦。&lt;/p&gt;
&lt;p&gt;观察两个产生式之间的关系，有三种：并、嵌套和不相关。并指的是两个产生式具有相同的左端。嵌套指的是一个产生式的左端出现在另一个产生式的右端中。不相关是指不存在并和嵌套关系的关系。并的关系是对称的偏序关系，嵌套的关系是不对称的偏序关系。文法推导的过程中需要做两个选择，其一，存在并关系的产生式中选哪一个；其二，一个产生式的多个嵌套产生式中选哪一个。这也是文法产生二义性的来源。第一个选择可以就输入符号的情况加以选择，在自上而下的分析方法中表现为向前看几个符号的问题，在自下而上的分析方法中表现为不同项集之间的状态转移问题，此外，还有二义文法中利用优先级和结合性来额外处理的问题。第二个选择可以规定从左到右展开还是从右到左展开，从左到右的方法称作LL，从右到左的方法称作LR。第一个L是指的从左到右扫描输入文本，第二个是指的推导的展开次序。自下而上的分析方法虽然是归约式的，但其逆过程等价于一个从右到左的推导过程，因而常常被称作LR的。&lt;/p&gt;
&lt;p&gt;产生式模型的构建，将设计语言的问题转换为了设计产生式的问题，如何设计非终结符，如何规定非终结符的右端，选择设计多少个产生式，这是一个相对开放而又复杂的问题，并不在编译原理课程的讨论范围内。编译原理中关注和解决的问题是如何在给定了产生式集合的情况下，生成一个自动机，高效地完成匹配任务。&lt;/p&gt;
&lt;p&gt;编译原理中介绍了两种自动机，一种是不带栈的有穷状态自动机，另一种是带栈的有穷自动机。不带栈的有穷状态自动机通常被称作有穷状态机，通常包含一个状态转换的驱动程序、一个状态转换表、输入字符串和输出。带栈的有穷状态机通常被称作下推自动机，除了包含有穷状态机中的四种东西外，还包含一个不限大小的栈。虽然驱动程序和状态转换表两种自动机都有，但内容相似却不同，下推自动机的驱动程序中不只是移动输入和转移状态，还包括弹栈和入栈的操作。Lex可以用来将一组正则表达式生成一个有穷状态机，被用作词法分析器。Yacc可以用来将一组产生式生成一个下推自动机，被用作语法分析。正则文法能够表达的三种操作都能够用上下文无关无法表达，反之却不可以。如何选择哪些部分应该用正则表达，哪些部分应该用产生式表达，基本原则是如果能用正则就尽量用正则，如果不能则用产生式，例如括号、语句块、条件分支、循环、嵌套、递归等。&lt;/p&gt;
&lt;p&gt;若输入文本有错，报告错误的一个原则是，尽可能多、准确、快速地报告错误。若要达到多的目标，就要用到恐慌模式的思想，即碰到一个错误，记录下来，不要退出程序，消化错误，继续向前解析。采用的办法往往是丢弃掉一些输入，找到较为清晰的同步词法单元。若要达到准确的目标，就要用到短语层次的恢复策略，为不同的状态转移动作设计专门的报错例程。若要达到快速的目标，主要还是在于保证匹配算法本身的效率，与报告错误的处理关系不是太大，但也要尽量减小错误恢复的空间开销。&lt;/p&gt;</description></item><item><title>随机两点距离的期望</title><link>https://blog.nicelylit.net/posts/%E9%9A%8F%E6%9C%BA%E4%B8%A4%E7%82%B9%E8%B7%9D%E7%A6%BB%E7%9A%84%E6%9C%9F%E6%9C%9B/</link><pubDate>Sat, 17 Aug 2019 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E9%9A%8F%E6%9C%BA%E4%B8%A4%E7%82%B9%E8%B7%9D%E7%A6%BB%E7%9A%84%E6%9C%9F%E6%9C%9B/</guid><description>&lt;h3 id="问题"&gt;问题
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;单位正方形内两个随机点的距离期望是多少？&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="求解因由"&gt;求解因由
&lt;/h3&gt;&lt;p&gt;&lt;a class="link" href="https://mp.weixin.qq.com/s/q7xd7OUmFjsKqSDIdizGaA" target="_blank" rel="noopener"
 &gt;何昆师兄博士毕业之际&lt;/a&gt;，小龙师兄发朋友圈称赞其深刻的数学思维时，提到这个问题，引起了我的兴趣。我的数学天分一般，小时候没出现机缘参加数学竞赛的训练，但对欣赏数学形式的优美这件事情从来都有着强烈的共鸣，特别是通过复杂的推导过程得到简洁的解析解之后。&lt;/p&gt;
&lt;p&gt;上学读书时候，我对随机和概率这样的观念和工具总是不太认同，这大概也源于我认知天性中对殊相比共相更贴近真相的一种倾向。不过，在阅读了《&lt;a class="link" href="https://www.amazon.cn/dp/B01F8AUQ5A/ref=sr_1_2?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&amp;amp;keywords=%E5%A4%8D%E6%9D%82&amp;amp;qid=1566010780&amp;amp;s=gateway&amp;amp;sr=8-2" target="_blank" rel="noopener"
 &gt;复杂&lt;/a&gt;》和一些量子力学的科普读物后，我渐渐感受到随机和概率是当下和未来最具确定性的工具，虽然局限性非常明显，即需要大量的观察和经验，且多适用于集体行为的预测。不过话又说回来，这本也是当下科学方法论的局限。语言文字和符号的局限性数学语言无法躲避，如今科学方法论的局限性，数学也难以躲避，可是要说发现形式上的美感这件事情，随机计算领域应该蕴藏丰富，聊以慰藉受困于苦痛中的人们。&lt;/p&gt;
&lt;h3 id="解后感受"&gt;解后感受
&lt;/h3&gt;&lt;p&gt;这个问题的解析解形式很漂亮，求解过程也非常有趣，值得记录和分享。&lt;/p&gt;
&lt;h3 id="思考过程"&gt;思考过程
&lt;/h3&gt;&lt;p&gt;问题中的关键词是&lt;strong&gt;随机点&lt;/strong&gt;、&lt;strong&gt;距离&lt;/strong&gt;和&lt;strong&gt;期望&lt;/strong&gt;，需要先回顾这几个概念才好继续往下走。我的回顾方法是把问题简化为一维的问题，即&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;单位长度上两个随机点的距离期望是多少？&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;先看单位长度上一个点的随机。仔细考量，发现，能从两个角度来思考：分布律和概率密度函数。&lt;/p&gt;
&lt;p&gt;这两个角度能够通过几何方法产生关联和替换，但是彼此之间有着鲜明的鸿沟，即不是一样的东西。人们常讲“点构成线，线构成面，面构成体”，这是分布律的眼光，不是概率密度函数的眼光。通过分布律的眼光，才产生了极限、无穷多的势这样的观念。以概率密度函数的眼光来看，线和点完全就是不一样的两种东西，任意一段线可以由单位的线段计数组成，但绝对不是由点组成。再举例来讲，数字1既用来表示单位长度的线段，也用来表示单位大小的正方形，也用来表示单位大小的立方体。集合和映射的出现，丰富了代数手段，却容易让人忘记数字所代表的含义。数字再抽象，脱离了所表示的东西就显得毫无意义，推导本身也变得荒唐。&lt;/p&gt;
&lt;h5 id="角度一分布律"&gt;角度一：分布律
&lt;/h5&gt;&lt;p&gt;将单位长度转化为间距相等的\(n\)个点，这样随机两个点就容易找样本空间和组合数了。随机抛点的问题转变为了从\(n\)个数中有放回的取两次的问题。样本空间是&lt;/p&gt;
&lt;p&gt;$$n^2$$&lt;/p&gt;
&lt;p&gt;转换后，距离也容易度量，相邻两点间的距离是&lt;/p&gt;
&lt;p&gt;$$\frac1 {n-1}$$&lt;/p&gt;
&lt;p&gt;有了这两个基础，两个随机点的距离取值范围就清楚了，即&lt;/p&gt;
&lt;p&gt;$$0, \frac1 {n-1}, \frac2 {n-1}, &amp;hellip;, \frac{n-2} {n-1}, 1$$&lt;/p&gt;
&lt;p&gt;令\(L\)为随机事件——取任意两点——所得的距离，那么&lt;/p&gt;
&lt;p&gt;$$P(L=\frac{k}{n-1})=\frac{2(n-k)}{n^2}$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$1 \le k \le n-1$$&lt;/p&gt;
&lt;p&gt;对于距离为\(0\)的情况，其概率为&lt;/p&gt;
&lt;p&gt;$$P(L=0)=\frac{n}{n^2}=\frac{1}{n}$$&lt;/p&gt;
&lt;p&gt;根据分布律的规定，必须有&lt;/p&gt;
&lt;p&gt;$$\sum_{k=0}^{n-1}{P(L=\frac{k}{n-1})}=1$$&lt;/p&gt;
&lt;p&gt;验证&lt;/p&gt;
&lt;p&gt;$$\begin{align*} \sum_{k=0}^{n-1}{P(L=\frac{k}{n-1})} &amp;amp;=\frac{1}{n^2}[n+2\sum_{k=1}^{n-1}(n-k)] \\ &amp;amp;=\frac{1}{n^2}[n+2\cdot\frac{n(n-1)}{2}] \\ &amp;amp;=1 \end{align*}$$&lt;/p&gt;
&lt;p&gt;数学期望是对分布律上随机变量均值的描述，形式化为&lt;/p&gt;
&lt;p&gt;$$\begin{align*} \sum_{k=0}^{n-1}{L \cdot P(L=\frac{k}{n-1})}&amp;amp;=\sum_{k=1}^{n-1}\frac{2k(n-k)}{(n-1)n^2} \\ &amp;amp;=\frac{2}{(n-1)n^2}\sum_{k=1}^{n-1}{k(n-k)} \\ &amp;amp;=\frac{n+1}{3n} \end{align*}$$&lt;/p&gt;
&lt;p&gt;以&amp;quot;点密成线“的观点来看，当\(n\)趋于无穷时候，所得的期望值，即是单位长度上随机两点距离的期望值，为&lt;/p&gt;
&lt;p&gt;$$\lim_{n \to \infty}{\frac{n+1}{3n}}=\frac{1}{3}$$&lt;/p&gt;
&lt;h5 id="角度二概率密度函数"&gt;角度二：概率密度函数
&lt;/h5&gt;&lt;p&gt;连续型的概率只能讲同等规模的描述对象事件发生的概率，所有低于此等规模的描述对象的概率都是零。比如单位长度上，只能问点落在某个长度区域内的概率是多少，这样的概率值会是一个非零的数，因为这样的计算的描述对象是线段，样本空间是线段长度，不是点。倘若问点落在1/2处的概率，那一定是零，因为点相对于线段是不同的东西，甚至都很难说是不同量级的东西，定义这样事件发生的概率为零也没什么坏处。连续型的概率函数的事件通常都写成区间的形式，比如单位长度上一个随机点落在某个区域&lt;/p&gt;
&lt;p&gt;$$[x_1, x_2]$$&lt;/p&gt;
&lt;p&gt;这个事件表示为&lt;/p&gt;
&lt;p&gt;$$x_1\le X\le x_2$$&lt;/p&gt;
&lt;p&gt;基于前面的讨论，这个事件也等于&lt;/p&gt;
&lt;p&gt;$$x_1&amp;lt; X &amp;lt; x_2$$&lt;/p&gt;
&lt;p&gt;略去区域左边的边界（一般是零或者负的无穷大），概率函数为&lt;/p&gt;
&lt;p&gt;$$P(X\le x)=x$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$X \in [0,1], x \in [0, 1], X\le x \subseteq [0, 1]$$&lt;/p&gt;
&lt;p&gt;概率密度函数表征概率函数的变化率，形式上为概率函数的导数，因而单位长度上随机均匀分布的概率密度函数是&lt;/p&gt;
&lt;p&gt;$$f(x)=1$$&lt;/p&gt;
&lt;p&gt;验证&lt;/p&gt;
&lt;p&gt;$$\int_{0}^{1}f(x)\mathrm{d}x=1$$&lt;/p&gt;
&lt;p&gt;令\(L\)为随机事件——任意两点随机抛落在单位长度区间内——所得的距离，那么随机事件可以表示为&lt;/p&gt;
&lt;p&gt;$$L=|X_1-X_2|\le l$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$0\le l \le 1$$&lt;/p&gt;
&lt;p&gt;且\(X_1\)与\(X_2\)服从前述的均匀分布，即满足&lt;/p&gt;
&lt;p&gt;$$0\le X_1 \le 1, 0\le X_2 \le 1$$&lt;/p&gt;
&lt;p&gt;事件所在的样本空间为单位正方形，事件发生区域为二维图像上的一块面积，如图所示灰色区域。&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2019/08/053E930C-6CB3-443B-975F-ADEB1549DDEF.png" &gt;&lt;img alt="053E930C-6CB3-443B-975F-ADEB1549DDEF" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2019/08/053E930C-6CB3-443B-975F-ADEB1549DDEF.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;令\(y=X_2, x=X_1\)用积分的方法求灰色区域的面积为&lt;/p&gt;
&lt;p&gt;$$\begin{align*} &amp;amp;P(L\le l) \\ &amp;amp;=\int_0^l{(x+l)\mathrm{d}x}+\int_{l}^{1-l}{(2l)\mathrm{d}x}+\int_{1-l}^{1}(1-x+l)\mathrm{d}x\\ &amp;amp;=2l-l^2 \end{align*}$$&lt;/p&gt;
&lt;p&gt;验证当\(l=1\)时，有&lt;/p&gt;
&lt;p&gt;$$P(L\le 1)=1$$&lt;/p&gt;
&lt;p&gt;对分布函数求导数，得到距离的概率密度函数为&lt;/p&gt;
&lt;p&gt;$$f(l)=2-2l$$&lt;/p&gt;
&lt;p&gt;求数学期望，得&lt;/p&gt;
&lt;p&gt;$$\mathrm{E}(f(l))=\int_0^1{l\cdot (2-2l)\mathrm{d}l}=\frac{1}{3}$$&lt;/p&gt;
&lt;p&gt;不论用分布律还是概率密度函数推导都能得出一样的期望值，即1/3. 同理，原问题也能应用同样的两个思路求解，不过求解难度却明显增加，两种方法增加难度之处不大一样。&lt;/p&gt;
&lt;h3 id="解题过程"&gt;解题过程
&lt;/h3&gt;&lt;h5 id="角度一分布律-1"&gt;角度一：分布律
&lt;/h5&gt;&lt;p&gt;假设单位正方形内均匀分布了&lt;/p&gt;
&lt;p&gt;$$n^2$$&lt;/p&gt;
&lt;p&gt;个点，沿笛卡尔横纵坐标轴方向的相邻两点的距离均为&lt;/p&gt;
&lt;p&gt;$$\frac{1}{n-1}$$&lt;/p&gt;
&lt;p&gt;每个点可以通过唯一的坐标定位，即有矩阵 $$\frac{1}{n-1}\cdot \left[ \begin{array}{c} (0, 0) &amp;amp; (0, 1) &amp;amp; &amp;hellip; &amp;amp; (0, n-1) \\ (1, 0) &amp;amp; (1, 1) &amp;amp; &amp;hellip; &amp;amp; (1, n-1) \\ &amp;hellip; &amp;amp; &amp;hellip; &amp;amp; &amp;hellip; &amp;amp; &amp;hellip; \\ (n-1,0) &amp;amp; (n-1,1) &amp;amp; &amp;hellip; &amp;amp; (n-1, n-1) \end{array} \right ] $$&lt;/p&gt;
&lt;p&gt;令随机变量\(L\)表示为随机从\(n^2\)个点中有放回的抽取两次所得两点之间的欧几里得距离，于是开始考察距离取值的可能性。假设抽取的两个点，坐标分别为\((x_1, y_1)\)与\((x_2,y_2)\)，根据一维问题的经验，知道横坐标和纵坐标差值的取值可能性的数量各有\(n\)种，即&lt;/p&gt;
&lt;p&gt;$$\vec{d}=\frac{1}{n-1}\cdot \left[ \begin{array}{c} 0 \\ 1 \\ &amp;hellip; \\ n-1 \end{array} \right ] $$&lt;/p&gt;
&lt;p&gt;两点欧式距离取值如一下矩阵所示，即&lt;/p&gt;
&lt;p&gt;$$\begin{align*} \mathbf{D}&amp;amp;=\frac{1}{n-1} \\ &amp;amp;\cdot \left[ \begin{array}{c} 0 &amp;amp; 1 &amp;amp; &amp;hellip; &amp;amp; n-1 \\ 1 &amp;amp; \sqrt{2} &amp;amp; &amp;hellip; &amp;amp; \sqrt{1^2+(n-1)^2}\\ &amp;hellip; &amp;amp; &amp;hellip; &amp;amp; &amp;hellip; &amp;amp; &amp;hellip; \\ n-1 &amp;amp; \sqrt{(n-1)^2+1^2} &amp;amp; &amp;hellip; &amp;amp; (n-1)\sqrt{2} \end{array} \right ] \end{align*}$$&lt;/p&gt;
&lt;p&gt;矩阵的每个格子中距离\(d\)的计算方法，即&lt;/p&gt;
&lt;p&gt;$$\begin{align*} L&amp;amp;=d[(x_1, y_1), (x_2, y_2)] \\ &amp;amp;=d(|x_1-x_2|, |y_1-y_2|) \\ &amp;amp;=d(\frac{k_1}{n-1}, \frac{k_2}{n-1}) \\ &amp;amp;=\frac{\sqrt{k_1^2+k_2^2}}{n-1} \end{align*}$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$0\le k_1, k_2 \le n-1$$&lt;/p&gt;
&lt;p&gt;观察距离取值的矩阵\(\mathbf{D}\)，发现其具有对称性，只看右上三角矩阵即可，也就是只看&lt;/p&gt;
&lt;p&gt;$$0\le k_1 \le k_2 \le n-1$$&lt;/p&gt;
&lt;p&gt;的那些项。至此，事件发生的样本空间清楚了，取值有&lt;/p&gt;
&lt;p&gt;$$\frac{n(n+1)}{2}$$&lt;/p&gt;
&lt;p&gt;种可能性。虽然样本空间只有平方的量级，但抽样方法却是在四次方的样本空间下发生的，因而还得通过有放回的抽取四个数值（\(x_1, y_1, x_2, y_2\)）的事件或者有放回的抽取两个数值（\(k_1, k_2\)）的事件来模拟。不管是哪种，都需要分四类情况计算事件发生的概率。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;情况一：\(L=0\)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也即&lt;/p&gt;
&lt;p&gt;$$x_1=x_2 \wedge y_1=y_2$$&lt;/p&gt;
&lt;p&gt;或是&lt;/p&gt;
&lt;p&gt;$$k_1=k_2=0$$&lt;/p&gt;
&lt;p&gt;这种情况表明两次抽到了同一个点，样本空间是\(n^4\)，事件发生次数是\(n^2\)，因而事件发生概率为&lt;/p&gt;
&lt;p&gt;$$P(L=0)=\frac{n^2}{n^4}=\frac{1}{n^2}$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;情况二：\(L=\frac{k}{n-1}\)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也即&lt;/p&gt;
&lt;p&gt;$$x_1=x_2 \wedge y_1 \ne y_2 \vee x_1 \ne x_2 \wedge y_1 = y_2 $$&lt;/p&gt;
&lt;p&gt;或是&lt;/p&gt;
&lt;p&gt;$$k=k_1 \ne 0 \wedge k_2 = 0 \vee k=k2 \ne 0 \vee k_1=0$$&lt;/p&gt;
&lt;p&gt;这种情况表明两次抽到的两个点来自同一横坐标或者同一纵坐标上的不同点。差值为&lt;/p&gt;
&lt;p&gt;$$L=\frac{k}{n-1}$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$1\le k \le n-1$$&lt;/p&gt;
&lt;p&gt;的事件发生次数可以这样计算：假设两点横坐标相同，先选横坐标，有&lt;/p&gt;
&lt;p&gt;$$n$$&lt;/p&gt;
&lt;p&gt;种选法，满足差值的纵坐标有&lt;/p&gt;
&lt;p&gt;$$2(n-k)$$&lt;/p&gt;
&lt;p&gt;种选法。同理若两点纵坐标相同，根据对称性，得到相同的结论。综合得到事件发生的概率为&lt;/p&gt;
&lt;p&gt;$$P(L=\frac{k}{n-1})=\frac{4n(n-k)}{n^4}$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$1\le k \le n-1$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;情况三：\(L=\frac{\sqrt{2}k}{n-1}\)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也即&lt;/p&gt;
&lt;p&gt;$$|x_1-x_2|=|y_1-y_2| \ne 0$$&lt;/p&gt;
&lt;p&gt;或者&lt;/p&gt;
&lt;p&gt;$$k=k_1=k_2 \ne 0$$&lt;/p&gt;
&lt;p&gt;这种情况表明两个点的横坐标差值和纵坐标差值相等，给定差值，那么横纵坐标各只需要选一次。对于差值为&lt;/p&gt;
&lt;p&gt;$$L=\frac{\sqrt{2}k}{n-1}$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$1\le k \le n-1$$&lt;/p&gt;
&lt;p&gt;的事件发生次数可以这样计算：选横坐标，有&lt;/p&gt;
&lt;p&gt;$$2(n-k)$$&lt;/p&gt;
&lt;p&gt;种选法；选纵坐标，也有这样多种选法。依据乘法原理，综合得到事件发生的概率为&lt;/p&gt;
&lt;p&gt;$$P(L=\frac{\sqrt{2}k}{n-1})=\frac{4(n-k)^2}{n^4}$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$1\le k \le n-1$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;情况四：\(L=\frac{\sqrt{k_1^2+k_2^2}}{n-1}\)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也即&lt;/p&gt;
&lt;p&gt;$$|x_1-x_2|\ne |y_1-y_2| \ne 0$$&lt;/p&gt;
&lt;p&gt;或者&lt;/p&gt;
&lt;p&gt;$$k_1 \ne k_2 \ne 0$$&lt;/p&gt;
&lt;p&gt;这种情况对应到矩阵\(\mathbf{D}\)上，就是除去首行、首列、对角线外的其余部分，因为横纵坐标差在距离的计算上具有对称性，因而最终算得的概率系数为8. 计算方法类似情况二和三。&lt;/p&gt;
&lt;p&gt;$$P(L=\frac{\sqrt{k_1^2+k_2^2}}{n-1})=\frac{8(n-k_1)(n-k_2)}{n^4}$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$1\le k_1 &amp;lt; k_2 \le n-1$$&lt;/p&gt;
&lt;p&gt;综合四种情况，得到分布律为&lt;/p&gt;
&lt;p&gt;$$\left\{\begin{array}{llr} P(L=0)=\frac{n^2}{n^4}=\frac{1}{n^2} \\ P(L=\frac{k}{n-1})=\frac{4n(n-k)}{n^4} &amp;amp; 1\le k \le n-1 \\ P(L=\frac{\sqrt{2}k}{n-1})=\frac{4(n-k)^2}{n^4} &amp;amp; 1\le k \le n-1\\ P(L=\frac{\sqrt{k_1^2+k_2^2}}{n-1})=\frac{8(n-k_1)(n-k_2)}{n^4} &amp;amp; 1\le k_1 &amp;lt; k_2 \le n-1 \end{array}\right. $$&lt;/p&gt;
&lt;p&gt;验证所求分布律是否正确&lt;/p&gt;
&lt;p&gt;$$\begin{align*} &amp;amp;P(L=0)+\sum_{k=1}^{n-1}{P(L=\frac{k}{n-1})} \\ &amp;amp;+\sum_{k=1}^{n-1}{P(L=\frac{\sqrt{2}k}{n-1})} \\ &amp;amp;+\sum_{k_1=1}^{n-2}{\sum_{k_2=k_1+1}^{n-1}{P(L=\frac{\sqrt{k_1^2+k_2^2}}{n-1})}} \\ &amp;amp;=\frac{1}{n^2}+\frac{4}{n^3}\sum_{k=1}^{n-1}{(n-k)} \\ &amp;amp;+\frac{4}{n^4}\sum_{k=1}^{n-1}{(n-k)^2} \\ &amp;amp;+\frac{8}{n^4}\sum_{k_1=1}^{n-2}{\sum_{k_2=k_1+1}^{n-1}(n-k_1)(n-k_2)} \\ &amp;amp;=\frac{1}{n^2}+\frac{4}{n^3}\cdot \frac{n(n-1)}{2} \\ &amp;amp;+\frac{4}{n^4}\cdot \frac{n(n-1)(2n-1)}{6}\\ &amp;amp;+\frac{8}{n^4}\cdot \sum_{k_1=1}^{n-2}{(n-k_1)\sum_{k_2=k_1+1}^{n-1}(n-k_2)} \\ &amp;amp;=\frac{1}{n^2}+\frac{4}{n^3}\cdot \frac{n(n-1)}{2} \\ &amp;amp;+\frac{4}{n^4}\cdot \frac{n(n-1)(2n-1)}{6} \\ &amp;amp;+\frac{8}{n^4}\cdot \sum_{k_1=1}^{n-2}{(n-k_1)^2(n-k_1-1)} \\ &amp;amp;=\frac{2n-1}{n^2}+\frac{4}{n^4}\cdot \frac{n(n-1)(2n-1)}{6} \\ &amp;amp;+\frac{4}{n^4}\cdot [(\frac{n(n-1)}{2})^2-\frac{n(n-1)(2n-1)}{6}] \\ &amp;amp;=\frac{2n-1}{n^2}+\frac{(n-1)^2}{n^2} \\ &amp;amp;=1 \end{align*} $$&lt;/p&gt;
&lt;p&gt;该分布律的数学期望为&lt;/p&gt;
&lt;p&gt;$$\begin{align*} \mathrm{E_n}(L) &amp;amp;= 0\times \frac{1}{n^2} \\ &amp;amp;+ \sum_{k=1}^{n-1}\frac{k}{n-1}\frac{4n(n-k)}{n^4} \\ &amp;amp;+ \sum_{k=1}^{n-1}\frac{\sqrt{2}k}{n-1}\frac{4(n-k)^2}{n^4} \\ &amp;amp;+ \sum_{k_1=1}^{n-2}{\sum_{k_2=k_1+1}^{n-1}\frac{\sqrt{k_1^2+k_2^2}}{n-1}\frac{8(n-k_1)(n-k_2)}{n^4}} \end{align*}$$&lt;/p&gt;
&lt;p&gt;所求的单位正方形中随机两点的数学期望为&lt;/p&gt;
&lt;p&gt;$$\begin{align*} \mathrm{E}(L) &amp;amp;=\lim_{n\to \infty}\mathrm{E_n}(L) \\ &amp;amp;=\lim_{n\to \infty}\sum_{k_1=1}^{n-2}{\sum_{k_2=k_1+1}^{n-1}\frac{\sqrt{k_1^2+k_2^2}}{n-1}\frac{8(n-k_1)(n-k_2)}{n^4}} \end{align*}$$&lt;/p&gt;
&lt;p&gt;以上等式之所以成立在于当\(n\)趋于无穷大时，中间两项的分子都只是\(n\)的三次幂和四次幂，极限值都为零。对于第四项的和，我曾经一度寻求不等式放缩，但发现无论怎样放缩缝隙都非常大，并且不可能找到一个\(k_1\)和\(k_2\)构成的线性组合去替换\(\sqrt{k_1^2+k_2^2}\)。其实当把极限的数学形式写出来后，应该容易联想到转换为积分计算能将问题简化，毕竟在\(n\)趋向于无穷大时，\(n\)的最高幂次的系数决定了最终的结果，于是&lt;/p&gt;
&lt;p&gt;$$\begin{align*} &amp;amp;\lim_{n\to \infty}\mathrm{E_n}(L) \\ &amp;amp;=\lim_{n\to \infty}\frac{8}{(n-1)n^4}\sum_{k_1=1}^{n-2}{\sum_{k_2=k_1+1}^{n-1}\sqrt{k_1^2+k_2^2}(n-k_1)(n-k_2)} \\ &amp;amp;=\lim_{n\to \infty}\frac{8}{(n-1)n^4}\int_0^n\int_x^n\sqrt{x^2+y^2}(n-x)(n-y)dydx \end{align*}$$&lt;/p&gt;
&lt;p&gt;根据对称性，有&lt;/p&gt;
&lt;p&gt;$$\begin{align*} &amp;amp;\int_0^n\int_x^n\sqrt{x^2+y^2}(n-x)(n-y)dydx \\ &amp;amp;=\int_0^n\int_0^x\sqrt{x^2+y^2}(n-x)(n-y)dydx \end{align*}$$&lt;/p&gt;
&lt;p&gt;观察积分区域，发现其为90度的等腰三角形，转换到极坐标系更容易计算，于是&lt;/p&gt;
&lt;p&gt;$$x=\rho\cos\theta$$&lt;/p&gt;
&lt;p&gt;$$y=\rho\sin\theta$$&lt;/p&gt;
&lt;p&gt;积分区域从&lt;/p&gt;
&lt;p&gt;$$0\le x \le n$$&lt;/p&gt;
&lt;p&gt;$$0\le y \le x$$&lt;/p&gt;
&lt;p&gt;变为&lt;/p&gt;
&lt;p&gt;$$0\le \theta \le \frac{\pi}{4}$$&lt;/p&gt;
&lt;p&gt;$$0\le \rho \le n\sec\theta$$&lt;/p&gt;
&lt;p&gt;有&lt;/p&gt;
&lt;p&gt;$$\begin{align*} &amp;amp;\int_0^n\int_0^x\sqrt{x^2+y^2}(n-x)(n-y)\mathrm{d}y\mathrm{d}x \\ &amp;amp;=\int_0^{\frac{\pi}{4}}\int_0^{n\sec\theta}\rho(n-\rho\cos\theta)(n-\rho\sin\theta)\rho\mathrm{d}\rho\mathrm{d}\theta \end{align*}$$&lt;/p&gt;
&lt;p&gt;积分的内容展开后一共四项，根据积分的线性可叠加性，可以拆开逐项计算&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一项：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$ \begin{align*} &amp;amp;n^2\int_0^{\frac{\pi}{4}}\int_0^{n\sec\theta}\rho^2\mathrm{d}\rho\mathrm{d}\theta \\ &amp;amp;=\frac{n^5}{3}\int_0^{\frac{\pi}{4}}\sec^3\theta\mathrm{d}\theta \\ &amp;amp;=\frac{n^5}{3}\cdot \frac{1}{2}[\frac{\sin\theta}{\cos^2\theta}+\ln|\tan\theta+\sec\theta|]_0^{\frac{\pi}{4}} \\ &amp;amp;=\frac{n^5}{6}(\sqrt{2}+\ln(1+\sqrt{2})) \end{align*}$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第二项：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$ \begin{align*} n\int_0^{\frac{\pi}{4}}\cos\theta\int_0^{n\sec\theta}\rho^3\mathrm{d}\rho\mathrm{d}\theta &amp;amp;=\frac{n^5}{4}\int_0^{\frac{\pi}{4}}\sec^3\theta\mathrm{d}\theta \\ &amp;amp;=\frac{n^5}{8}(\sqrt{2}+\ln(1+\sqrt{2})) \end{align*}$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第三项：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$ \begin{align*} n\int_0^{\frac{\pi}{4}}\sin\theta\int_0^{n\sec\theta}\rho^3\mathrm{d}\rho\mathrm{d}\theta &amp;amp;=\frac{n^5}{4}\int_0^{\frac{\pi}{4}}\sin\theta\sec^4\theta\mathrm{d}\theta \\ &amp;amp;=\frac{n^5}{4}\int_0^{\frac{\pi}{4}}\cos^{-4}\theta\mathrm{d}\cos\theta \\ &amp;amp;=\frac{n^5}{12}(2\sqrt{2}-1) \end{align*}$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第四项：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$ \begin{align*} \int_0^{\frac{\pi}{4}}\cos\theta\sin\theta\int_0^{n\sec\theta}\rho^4\mathrm{d}\rho\mathrm{d}\theta &amp;amp;=\frac{n^5}{5}\int_0^{\frac{\pi}{4}}\sin\theta\sec^4\theta\mathrm{d}\theta \\ &amp;amp;=\frac{n^5}{15}(2\sqrt{2}-1) \end{align*}$$&lt;/p&gt;
&lt;p&gt;将计算所得带回原式，合并化简得到&lt;/p&gt;
&lt;p&gt;$$\begin{align*} &amp;amp;\lim_{n\to \infty}\mathrm{E_n}(L) \\ &amp;amp;=\lim_{n\to \infty}\frac{8}{(n-1)n^4}\cdot \frac{[5\ln(1+\sqrt{2})+\sqrt{2}+2]n^5}{120} \\ &amp;amp;= \frac{5\ln(1+\sqrt{2})+\sqrt{2}+2}{15} \\ &amp;amp;\approx {0.5214} \end{align*}$$&lt;/p&gt;
&lt;h5 id="角度二概率密度函数-1"&gt;角度二：概率密度函数
&lt;/h5&gt;&lt;p&gt;类比一维的解法，令\(L\)为随机事件——任意两点随机抛落在单位正方形区间内——所得的距离，那么随机事件可以表示为&lt;/p&gt;
&lt;p&gt;$$L=\sqrt{(X_1-X_2)^2+(Y_1-Y_2)^2}\le l$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$0\le l \le 1$$&lt;/p&gt;
&lt;p&gt;且\((X_1, Y_1)\)与\((X_2, Y_2)\)服从均匀分布&lt;/p&gt;
&lt;p&gt;$$P(X\le x, Y\le y)=xy$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$X, Y \in [0, 1]$$&lt;/p&gt;
&lt;p&gt;类比前面的思路，下一步要求概率分布&lt;/p&gt;
&lt;p&gt;$$P(L\le l)=\iiiint\limits_{\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\le l}f(x_1,x_2,y_1,y_2)\mathrm{d}x_1\mathrm{d}x_2\mathrm{d}y_1\mathrm{d}y_2$$&lt;/p&gt;
&lt;p&gt;单位区域内均匀分布的概率密度函数始终为1，即&lt;/p&gt;
&lt;p&gt;$$f(x_1,x_2,y_1,y_2)=1$$&lt;/p&gt;
&lt;p&gt;求解以上四重积分的难点在于，难以直观地看到8个四维空间上的三维平体和1个四维空间上的三维曲体所围成的四维体的形状，从而难以直接写出分变量积分的形式。还需要进一步的思考，当下想到的思考方向是利用数形结合的方式研究三维平体和三维曲体的相交特点，找出分区域分变量积分的形式。类比一维问题中求解灰色区域面积的方法，这个四重积分有机会简化为三重积分或是二重积分。等有时间再来补充完这个解法。&lt;/p&gt;</description></item><item><title>计算机科学的要义</title><link>https://blog.nicelylit.net/posts/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E7%9A%84%E8%A6%81%E4%B9%89/</link><pubDate>Sat, 15 Jun 2019 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E7%9A%84%E8%A6%81%E4%B9%89/</guid><description>&lt;p&gt;通信和控制是目的，计算和存储是手段，模块化是方法。&lt;/p&gt;
&lt;p&gt;通信和控制体现着两种截然不同的人生态度。通信强调的是信息交换，即信息在时空中的转移、流动，这样的交换一般是平等的，交换主体之间彼此互惠，协商互换。可惜，信息无法脱离物质而单独存在，故而需要依赖于某些介质，从而也必然存在着信息安全的问题。控制强调的是行为可预测，即信息的流动、物质的时空变换呈现出确定性的状态，要借助周期性的运动（时钟和计数）方能达成。世事无常，控制只能在可认知的时空边界中生效，并且还要承受可认知时空里复杂所带来的灾难，而通信的时空边界似乎能够不断地被突破，给人们带来无限的希望。&lt;/p&gt;
&lt;p&gt;计算和存储是时间和空间中乘载着通信信息的信息。计算是对算法执行过程的称谓，算法是对线性次序、非线性次序和重复结构的描述，以静态的源码，用动态的眼光，经过排列（旋转、组合）、搜索（查找、访问、移动）、变换（加运算）后，提取出用于通信的信息。存储是对信息的空间组织方式的称谓，信息的空间组织方式是无方向、可对称的，既可作为计算的操作原料，也能记录算法本身。通信和控制都需要借助计算和存储来完成，进行通信的信息和进行控制的信息也需要借助计算和存储来完成。&lt;/p&gt;
&lt;p&gt;模块化是控制复杂的有效方法，既符合人的直觉思维，又有强大的理性根基。说其符合直觉思维是因为直觉只能感知事物的表象，而模块化能够有效地封装起内部，只暴露出事物的表象，称之为接口，如果不运用理性，难以想象接口之下隐藏着什么。说其具有理性根基是因为模块化的可叠加性、依赖管理能够形式化，此外，完整的运用模块化，还需要人将注意力从接口毫无间断地转移到内部，并且既会自顶向下看，还会自底向上看，这需要多年的浸染和领悟。实际上，模块化都不只是计算机科学的方法论，它还是整个工程学认知论的核心，属原子论的变体。&lt;/p&gt;</description></item><item><title>全触分布</title><link>https://blog.nicelylit.net/posts/%E5%85%A8%E8%A7%A6%E5%88%86%E5%B8%83/</link><pubDate>Sun, 16 Sep 2018 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%85%A8%E8%A7%A6%E5%88%86%E5%B8%83/</guid><description>&lt;p&gt;最近想到一个问题，发现了一个有趣的分布，姑且叫其全触分布。可能这个分布已经有了名字，不过翻了一遍概率论的书，似乎没有看到有谈过这个分布。&lt;/p&gt;
&lt;h2 id="初始问题"&gt;初始问题
&lt;/h2&gt;&lt;p&gt;假设有两台服务器，各自有独立的缓存需要预热（即初始化缓存），而预热时只能通过相同的访问地址，访问负载均衡器之后，由负载均衡器等概率随机选择一台预热，问至少需要几次地址访问，才能保证两台服务器中的缓存都被预热过的概率超过99%？&lt;/p&gt;
&lt;p&gt;这个问题等价于一个投硬币的问题，问至少多少次伯努利试验后，才能保证正反面都出现过的概率超过99%？&lt;/p&gt;
&lt;p&gt;令随机事件\(X\)表示，第\(n\)次伯努利试验时，才使得正反面都出现过。显然，当\(n&amp;lt;2\)时，事件不会发生；当\(n=2\)时，一定是一次正面一次反面；当\(n&amp;gt;2\)时，一定是前\(n-1\)次都是某一面，剩下的第\(n\)次是另外一面。 据此知道，随机事件\(X=n\)的概率为&lt;/p&gt;
&lt;p&gt;$$P(X=n)=\frac{1}{2^{n-1}}$$&lt;/p&gt;
&lt;p&gt;其中\(n \ge 2\)，而当\(n &amp;lt; 2\)时，\(P(X=n)=0\)。&lt;/p&gt;
&lt;p&gt;验证所求分布律的正确性&lt;/p&gt;
&lt;p&gt;$$P(X\ge 2)=\frac{1}{2}+\frac{1}{2^2}+&amp;hellip;+\frac{1}{2^{n-1}}+&amp;hellip;=1$$&lt;/p&gt;
&lt;p&gt;分布应该如此。返回原始问题，得知在问，使得不等式\(P(X\le n)\ge0.99\)成立的最小的\(n\)为几。&lt;/p&gt;
&lt;p&gt;由于&lt;/p&gt;
&lt;p&gt;$$P(X\le n)=P(X=2)+P(X=3)+&amp;hellip;+P(X=n)$$&lt;/p&gt;
&lt;p&gt;$$=1-\frac{1}{2^{n-1}}$$&lt;/p&gt;
&lt;p&gt;求得&lt;/p&gt;
&lt;p&gt;$$n\ge 2\log_2{10}+1 \thickapprox7.6$$&lt;/p&gt;
&lt;p&gt;所以，最小的\(n\)为8，即至少8次伯努利试验后才能保证正反面都出现过的概率超过99%。&lt;/p&gt;
&lt;h2 id="问题拓展到m"&gt;&lt;strong&gt;问题拓展到\(m\)&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;进一步，假设原始问题中不止两台机器，而是\(m\)台，伯努利试验中也不是投掷硬币，而是从\(m\)个小球中随机选，问至少多少次试验后，才能保证每台机器或者每个小球都出现过的概率超过99%？&lt;/p&gt;
&lt;p&gt;类似地，令随机事件\(X\)表示，第\(n\)次试验时，才使得每个小球都出现过。显然，当\(n&amp;lt;m\)时，事件不会发生；当\(n=m\)时，一定是每个小球都只出现了一次；当\(n&amp;gt;m\)时，一定是前\(n-1\)次出现了\(m-1\)个小球，第\(n\)次恰好出现的是剩下的那个小球。&lt;/p&gt;
&lt;p&gt;\(n\)次试验的概率空间容易计算，是\(m^n\)，因为每次都有\(m\)种选择，一共要选\(n\)次。对于出现随机事件\(X\)的次数，可以假设为\(F(n,m)\)次，即\(F(n,m)\)表示第\(n\)次试验时，才使得\(m\)个小球每个小球都出现过，也即&lt;/p&gt;
&lt;p&gt;$$P(X=n|m)=\frac{F(n,m)}{m^n}$$&lt;/p&gt;
&lt;p&gt;因为\(P(X=n|m)\)表示第\(n\)次试验时，才使得每个小球都出现过，或每个机器都被触碰过，因而称作全触分布。&lt;/p&gt;
&lt;p&gt;最终求解的目标是\(P(X\le n | m)\ge0.99\)，而&lt;/p&gt;
&lt;p&gt;$$P(X\le n | m)=\sum_{i=m}^{n}{P(X=i|m)}=\sum_{i=m}^{n}{\frac{F(i,m)}{m^i}}$$&lt;/p&gt;
&lt;p&gt;所以，关键在于如何求解\(F(n,m)\)。前面分析到，当\(n&amp;gt;m\)时，一定是前\(n-1\)次出现了\(m-1\)个小球，第\(n\)次恰好出现的是剩下的那个小球。如果用\(G(n,m)\)表示\(n\)次试验中，\(m\)个小球都出现过的次数，那么&lt;/p&gt;
&lt;p&gt;$$F(n,m)=m \cdot G(n-1,m-1)$$&lt;/p&gt;
&lt;p&gt;\(G(n,m)\)不同于\(F(n,m)\)的地方在于，\(G(n,m)\)没有限制直到第\(n\)次试验，才满足\(m\)个小球都出现过，据此，有\(G(n,m)\ge F(n,m)\)。&lt;/p&gt;
&lt;p&gt;求解\(G(n,m)\)，可以将集合划分为两个相似的缩小集合。这个划分可以根据，\(m\)个小球中的某一个是否只出现在了第\(n\)次试验中。如果某一个小球只出现在第\(n\)次试验中，那么前\(n-1\)次，就只有\(m-1\)个小球出现，即共有\(m\cdot G(n-1, m-1)\)种；如果同样的小球，不止出现在第\(n\)次试验中，那么前\(n-1\)次，仍旧有\(m\)个小球出现，共有\(m\cdot G(n-1, m)\)种。综合可得&lt;/p&gt;
&lt;p&gt;$$G(n,m)=m\cdot G(n-1,m-1) + m\cdot G(n-1,m)$$&lt;/p&gt;
&lt;p&gt;由以上两个等式可以知道，\(G(n,m)\)具体比\(F(n,m)\)多了多少，多了\(m\cdot G(n-1, m)\)，即&lt;/p&gt;
&lt;p&gt;$$G(n,m)-F(n,m)= m\cdot G(n-1,m)$$&lt;/p&gt;
&lt;p&gt;在求解\(G(n,m)\)的解析形式前，根据定义能够得出以下两个恒等式，即&lt;/p&gt;
&lt;p&gt;当\(n&amp;lt;m\)时，\(G(n,m)\equiv 0\)；&lt;/p&gt;
&lt;p&gt;当\(n=m\)时，\(G(n,m)\equiv 1\) 。&lt;/p&gt;
&lt;p&gt;当求得解析解后，验证概率分布律时，将会再次通过代数的方式证明两个恒等式。 这是函数\(G(n,m)\)非常有趣的两个特性。&lt;/p&gt;
&lt;h2 id="求gnm的解析解"&gt;&lt;strong&gt;求\(G(n,m)\)的解析解&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;直接通过\(G(n,m)\)的递推形式，求解析形式比较困难，可以尝试用数学归纳法，先从较小的\(m\)开始。&lt;/p&gt;
&lt;p&gt;当\(m=1\)时，\(G(n,1) \equiv 1\)。&lt;/p&gt;
&lt;p&gt;当\(m=2\)时，\(G(n,2) = 2G(n-1,2)+2\)，解析解为\(G(n,2)=2^n-2\)。&lt;/p&gt;
&lt;p&gt;当\(m=3\)时，\(G(n,3) = 3G(n-1,3)+3 \times (2^{n-1}-2)\)，解析解为\(G(n,3)=3^n-3\times 2^n+3\)。&lt;/p&gt;
&lt;p&gt;当\(m=4\)时，\(G(n,4) = 4G(n-1,4)+4 \times (3^{n-1}-3\times 2^{n-1}+3)\)，解析解为\(G(n,4)=4^n-4\times 3^n + 6 \times 2^n -4\)。&lt;/p&gt;
&lt;p&gt;如果在求解到\(m = 4\)时，还没注意到组合系数的话，那大概需要补补组合数学基础了。&lt;/p&gt;
&lt;p&gt;有了组合系数的观察，一般地，\(G(n,m) = \sum _{k=0} ^{m} (-1)^k {m \choose k} (m-k)^n\)。&lt;/p&gt;
&lt;p&gt;将\(m = 1, 2, 3,4\)分别依次带入方程，得出同直接由递推式求得一致的结果。&lt;/p&gt;
&lt;p&gt;接下来验证，解析解能使递推式始终成立。由解析解方程，可知&lt;/p&gt;
&lt;p&gt;$$G(n-1,m-1) = \sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} (m-1-k)^{n-1}$$&lt;/p&gt;
&lt;p&gt;$$G(n-1,m) = \sum _{k=0} ^{m} (-1)^k {m \choose k} (m-k)^{n-1}$$&lt;/p&gt;
&lt;p&gt;令第一式的\(t=k+1\)，则&lt;/p&gt;
&lt;p&gt;\(G(n-1,m-1) = \sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} (m-1-k)^{n-1}\) \(= \sum _{t=1} ^{m} { (-1)^{t-1} {m-1 \choose t-1} (m-t)^{n-1} }\)&lt;/p&gt;
&lt;p&gt;于是&lt;/p&gt;
&lt;p&gt;\(G(n-1,m-1) + G(n-1,m)\)&lt;/p&gt;
&lt;p&gt;\(= \sum _{k=0} ^{m} (-1)^k {m \choose k} (m-k)^{n-1} + \sum _{t=1} ^{m} { (-1)^{t-1} {m-1 \choose t-1} (m-t)^{n-1} } \)&lt;/p&gt;
&lt;p&gt;\( = m^{n-1} + \sum _{k=1} ^{m} { (m-k)^{n-1} \lgroup (-1)^k {m \choose k} + (-1)^{k-1} {m-1 \choose k-1} \rgroup } \)&lt;/p&gt;
&lt;p&gt;\( =m^{n-1} + \sum _{k=1} ^{m} { (-1)^k (m - k) ^ {n-1} \lgroup {m \choose k} - {m-1 \choose k-1} \rgroup } \)&lt;/p&gt;
&lt;p&gt;\( =m^{n-1} + \sum _{k=1} ^{m} { (-1)^k (m - k) ^ {n-1} {m-1 \choose k} } \)&lt;/p&gt;
&lt;p&gt;\( =m^{n} \cdot \frac{1}{m} + \sum _{k=1} ^{m} { (-1)^k (m - k) ^ {n} {m-1 \choose k} \frac{m}{m-k} \cdot \frac{1}{m} } \)&lt;/p&gt;
&lt;p&gt;\( = \frac{1}{m} \cdot \lgroup m^{n} + \sum _{k=1} ^{m} { (-1)^k (m - k) ^ {n} {m \choose k} } \rgroup \)&lt;/p&gt;
&lt;p&gt;\( = \frac{1}{m} \cdot \sum _{k=0} ^{m} { (-1)^k {m \choose k} (m - k) ^ {n} } \)&lt;/p&gt;
&lt;p&gt;所以&lt;/p&gt;
&lt;p&gt;\(G(n,m)=m\cdot G(n-1,m-1) + m\cdot G(n-1,m)\)成立。&lt;/p&gt;
&lt;h2 id="验证分布律的正确性"&gt;&lt;strong&gt;验证分布律的正确性&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;由\(G(n,m) = \sum _{k=0} ^{m} (-1)^k {m \choose k} (m-k)^n\)和\(F(n,m)=m \cdot G(n-1,m-1)\)，得知&lt;/p&gt;
&lt;p&gt;$$F(n,m) = m \cdot \sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} (m-1-k)^{n-1}$$&lt;/p&gt;
&lt;p&gt;又\(P(X=n|m)=\frac{F(n,m)}{m^n}\)&lt;/p&gt;
&lt;p&gt;所以&lt;/p&gt;
&lt;p&gt;$$P(X=n|m)= \sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} \frac {(m-1-k)^{n-1}} {m^{n-1}}$$&lt;/p&gt;
&lt;p&gt;正确的分布律应满足\(P(X \ge m|m) \equiv 1\)，即须证明&lt;/p&gt;
&lt;p&gt;$$\sum _{n=m} ^{\infty} {P(X=n|m)} \equiv 1$$&lt;/p&gt;
&lt;p&gt;即证明&lt;/p&gt;
&lt;p&gt;$$\sum _{n=m} ^{\infty} { \sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} \frac {(m-1-k)^{n-1}} {m^{n-1}} } \equiv 1$$&lt;/p&gt;
&lt;p&gt;即证明&lt;/p&gt;
&lt;p&gt;$$\sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} \sum _{n=m} ^{\infty} { ( 1- \frac {1+k} {m} )^{n-1} } \equiv 1$$&lt;/p&gt;
&lt;p&gt;即证明&lt;/p&gt;
&lt;p&gt;$$\sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} \frac {m} {1+k} ( 1- \frac {1+k} {m} )^{m-1} \equiv 1$$&lt;/p&gt;
&lt;p&gt;即证明&lt;/p&gt;
&lt;p&gt;$$\sum _{k=0} ^{m-1} (-1)^k {m \choose 1+k} ( 1- \frac {1+k} {m} )^{m-1} \equiv 1$$&lt;/p&gt;
&lt;p&gt;即证明&lt;/p&gt;
&lt;p&gt;$$\sum _{k=0} ^{m-1} (-1)^k {m \choose 1+k} ( m-1-k )^{m-1} \equiv m^{m-1}$$&lt;/p&gt;
&lt;p&gt;也即证明&lt;/p&gt;
&lt;p&gt;$$\sum _{k=0} ^{m} (-1)^k {m \choose k} ( m-k )^{m-1} \equiv 0$$&lt;/p&gt;
&lt;p&gt;观察恒等式左边，发现似曾相识，回看\(G(n,m)\)的解释式&lt;/p&gt;
&lt;p&gt;$$G(n,m) = \sum _{k=0} ^{m} (-1)^k {m \choose k} (m-k)^n$$&lt;/p&gt;
&lt;p&gt;发现&lt;/p&gt;
&lt;p&gt;$$\sum _{k=0} ^{m} (-1)^k {m \choose k} ( m-k )^{m-1} = G(m-1, m)$$&lt;/p&gt;
&lt;p&gt;根据前面的恒等关系&lt;/p&gt;
&lt;p&gt;当\(n&amp;lt;m\)时，\(G(n,m)\equiv 0\)，得证。&lt;/p&gt;
&lt;h2 id="等式sum-_k0-m--1k-m-choose-k--m-k-m-1-equiv-0的常规证明"&gt;&lt;strong&gt;等式\(\sum _{k=0} ^{m} (-1)^k {m \choose k} ( m-k )^{m-1} \equiv 0\)的常规证明&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;直接证明\(G(m-1,m)\equiv 0\)，可能没有思路，可以先从\(G(0,m)\equiv 0\)和\(G(1,m)\equiv 0\)开始。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A. 证明\(G(0,m) = \sum _{k=0} ^{m} (-1)^k {m \choose k} \equiv 0\)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个恒等式，直接用二项式展开，即可证得。根据二项式定理，知道&lt;/p&gt;
&lt;p&gt;$$(x-1)^m = \sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot x^{m-k}$$&lt;/p&gt;
&lt;p&gt;令\(x=1\)，即得到&lt;/p&gt;
&lt;p&gt;$$0 = \sum _{k=0} ^{m} {m \choose k} (-1)^k$$&lt;/p&gt;
&lt;p&gt;得证\(G(0,m)\equiv 0\)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;B. 证明\(G(1,m) = \sum _{k=0} ^{m} (-1)^k {m \choose k} (m-k) \equiv 0\)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;类似于A步的证明，对二项式等式&lt;/p&gt;
&lt;p&gt;$$(x-1)^m = \sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot x^{m-k}$$&lt;/p&gt;
&lt;p&gt;两边同时求\(x\)的导数，得&lt;/p&gt;
&lt;p&gt;$$m\cdot (x-1)^{m-1} = \sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot (m-k) x^{m-k-1}$$&lt;/p&gt;
&lt;p&gt;令\(x=1\)，即得到&lt;/p&gt;
&lt;p&gt;$$0 = \sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot (m-k)$$&lt;/p&gt;
&lt;p&gt;得证\(G(1,m)\equiv 0\)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C. 证明\(\sum _{k=0} ^{m} (-1)^k {m \choose k} ( m-k )^{m-1} \equiv 0\)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;类似于B步骤的证明，对二项式等式&lt;/p&gt;
&lt;p&gt;$$(x-1)^m = \sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot x^{m-k}$$&lt;/p&gt;
&lt;p&gt;两边同时求\(x\)的\(n\)阶导函数（其中\(1 \le n \le m-1\)），得&lt;/p&gt;
&lt;p&gt;$$\frac{m!}{(m-n)!} \cdot (x-1) = \sum _{k=0} ^{m-n} {m \choose k} (-1)^k \cdot x^{m-k-n} \cdot \prod _{i=0} ^{n-1} (m-k-i)$$&lt;/p&gt;
&lt;p&gt;令\(x=1\)，即得到&lt;/p&gt;
&lt;p&gt;$$0 = \sum _{k=0} ^{m-n} {m \choose k} (-1)^k \cdot \prod _{i=0} ^{n-1} (m-k-i)$$&lt;/p&gt;
&lt;p&gt;因为当\(m-n+1\le k \le m\)时，有&lt;/p&gt;
&lt;p&gt;$$\prod _{i=0} ^{n-1} (m-k-i)=0$$&lt;/p&gt;
&lt;p&gt;所以&lt;/p&gt;
&lt;p&gt;\(0 = \sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot \prod _{i=0} ^{n-1} (m-k-i)\)&lt;/p&gt;
&lt;p&gt;\(= \sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot \sum _{i=1} ^{n} a_i \cdot (m-k)^i\)&lt;/p&gt;
&lt;p&gt;\(= \sum _{i=1} ^{n} a_i \cdot \sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot (m-k)^i\)&lt;/p&gt;
&lt;p&gt;因为对于所有的\(1 \le n \le m-1\)，都有&lt;/p&gt;
&lt;p&gt;$$\sum _{i=1} ^{n} a_i \cdot \sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot (m-k)^i =0$$&lt;/p&gt;
&lt;p&gt;因而，不论常数\(a_i\)是多少，都有&lt;/p&gt;
&lt;p&gt;$$\sum _{k=0} ^{m} {m \choose k} (-1)^k \cdot (m-k)^n =0$$&lt;/p&gt;
&lt;p&gt;成立。&lt;/p&gt;
&lt;p&gt;得证，当\(1 \le n \le m-1\)时，&lt;/p&gt;
&lt;p&gt;$$G(n,m)\equiv 0$$&lt;/p&gt;
&lt;p&gt;\(\sum _{k=0} ^{m} (-1)^k {m \choose k} ( m-k )^{m-1} \equiv 0\)显然是\(n=m-1\)时的情况。&lt;/p&gt;
&lt;p&gt;类似地，也能证得\(G(m,m)\equiv 1\)。&lt;/p&gt;
&lt;h2 id="求解pxle-n--mge099"&gt;求解\(P(X\le n | m)\ge0.99\)
&lt;/h2&gt;&lt;p&gt;回到最初的目标，&lt;/p&gt;
&lt;p&gt;\(P(X\le n | m) = \sum _{i=m} ^{n} {P(X=i|m)}\)&lt;/p&gt;
&lt;p&gt;\(= \sum _{i=m} ^{n} \sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} (1- \frac{1+k}{m})^{i-1} \)&lt;/p&gt;
&lt;p&gt;\( = \sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} \sum _{i=m} ^{n} (1- \frac{1+k}{m})^{i-1} \)&lt;/p&gt;
&lt;p&gt;\(= \sum _{k=0} ^{m-1} (-1)^k {m-1 \choose k} \frac {m} {1+k} [ (1- \frac{1+k}{m})^{m-1} - (1- \frac{1+k}{m})^{n} ] \)&lt;/p&gt;
&lt;p&gt;\(= \sum _{k=0} ^{m-1} (-1)^k {m \choose k+1} (1- \frac{1+k}{m})^{m-1} - \sum _{k=0} ^{m-1} (-1)^k {m \choose k+1} (1- \frac{1+k}{m})^{n} \)&lt;/p&gt;
&lt;p&gt;\( = 1 - \sum _{k=0} ^{m-1} (-1)^k {m \choose k+1} (1- \frac{1+k}{m})^{n} \)&lt;/p&gt;
&lt;p&gt;若求\(P(X\le n | m)\ge0.99\)&lt;/p&gt;
&lt;p&gt;即求&lt;/p&gt;
&lt;p&gt;$$1 - \sum _{k=0} ^{m-1} (-1)^k {m \choose k+1} (1- \frac{1+k}{m})^{n} \ge 0.99$$&lt;/p&gt;
&lt;p&gt;即求&lt;/p&gt;
&lt;p&gt;$$\sum _{k=0} ^{m-1} (-1)^k {m \choose k+1} (1- \frac{1+k}{m})^{n} \le 0.01$$&lt;/p&gt;
&lt;p&gt;即求&lt;/p&gt;
&lt;p&gt;$$0.01 \times m^n - \sum _{k=0} ^{m-1} (-1)^k {m \choose k+1} (m - 1 - k)^{n} \ge 0$$&lt;/p&gt;
&lt;p&gt;即求&lt;/p&gt;
&lt;p&gt;$$0.01 \times m^n - \sum _{k=1} ^{m} (-1)^{k-1} {m \choose k} (m - k)^{n} \ge 0$$&lt;/p&gt;
&lt;p&gt;有此不等式，可知不同\(m\)时，满足不等式的\(n\)的最小取值，下表列举了前10项&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;m 1 2 3 4 5 6 7 8 9 10
n 1 8 15 21 28 36 43 51 58 66
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;全触分布的分布律&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2018/09/touch-all-dist.png" &gt;&lt;img alt="touch-all-dist" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2018/09/touch-all-dist.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;全触分布的累积分布函数&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2018/09/touch-all-cum.png" &gt;&lt;img alt="touch-all-cum" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2018/09/touch-all-cum.png"&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>滚动硬币的启示</title><link>https://blog.nicelylit.net/posts/%E6%BB%9A%E5%8A%A8%E7%A1%AC%E5%B8%81%E7%9A%84%E5%90%AF%E7%A4%BA/</link><pubDate>Tue, 23 Jan 2018 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E6%BB%9A%E5%8A%A8%E7%A1%AC%E5%B8%81%E7%9A%84%E5%90%AF%E7%A4%BA/</guid><description>&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;p&gt;如果选择了滚动硬币的圆心，观察其轨迹的变化，立刻能够得出圆心在半圈内整整转过了一周，自然箭头还是向上。如果选择了滚动硬币上其它的点，就不那么容易得出这个结论了，因为其它的点的轨迹是条心脏线。为此我写了动画模拟了整个过程。&lt;/p&gt;
&lt;script src="https://nicelylit.net/wp-content/themes/jeremysworld/js/coin-path.js?ver=6.1.9" id="coin-path-js"&gt;&lt;/script&gt;
&lt;p&gt;&lt;canvas id="coinPath" style="margin:auto;display:block;" width="400" height="400"&gt;您的浏览器不支持Canvas API&lt;/canvas&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;blockquote&gt;
 &lt;p&gt;人心在把自己的能力施用于简单的观念时，其作用约可分为三种：第一，它可以把几个简单观念合成一个复合观念，因而造成一切复杂观念。第二，它可以把两个观念（不论是简单的或复杂的）并列起来，同时观察，可是并不把它们结合为一；这样，它就得到它的一切关系观念。第三，它可以把连带的其他观念排斥于主要观念的真正存在以外；这便叫做抽象作用，这样就造成一切概括的观念，这就分明表示出，人类的能力同其作用方式，在物质世界方面同理性世界方面，都是一样的。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;虽然数学和物理中经典的例子不停地向我们展示这样的技巧，只是当遇到新问题时，我们的思维和注意力容易被复杂的细节和对象带偏，从而抓不住本质的对象。如果我们在遇到新问题难以找到方向时，不妨想一下约翰·洛克给我们的启示，我们一定不会太过迷茫。&lt;/p&gt;</description></item><item><title>图灵停机问题的两种符号表述</title><link>https://blog.nicelylit.net/posts/%E5%9B%BE%E7%81%B5%E5%81%9C%E6%9C%BA%E9%97%AE%E9%A2%98%E7%9A%84%E4%B8%A4%E7%A7%8D%E7%AC%A6%E5%8F%B7%E8%A1%A8%E8%BF%B0/</link><pubDate>Sat, 09 Dec 2017 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%9B%BE%E7%81%B5%E5%81%9C%E6%9C%BA%E9%97%AE%E9%A2%98%E7%9A%84%E4%B8%A4%E7%A7%8D%E7%AC%A6%E5%8F%B7%E8%A1%A8%E8%BF%B0/</guid><description>&lt;p&gt;年中在读《量子计算与量子信息原理》的时候，写了一篇《&lt;a class="link" href="https://blog.nicelylit.net/posts/%E5%9B%BE%E7%81%B5%E5%81%9C%E6%9C%BA%E9%97%AE%E9%A2%98%E7%9A%84%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E8%AE%BA%E8%BF%B0/" &gt;图灵停机问题的一个简单论述&lt;/a&gt;》的阅读笔记，对大学时没学明白的图灵停机问题有了一些基本认识。今天在读《复杂》的时候，里面采用了另外一种等价的说法，在符号形式上看起来略有差异，如今记录一下加深印象。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;回顾量子一书中的描述&lt;/strong&gt;，先假设存在算法A具有判定任意算法是否能够停止运算的能力，用符号表述也就是&lt;/p&gt;
&lt;p&gt;对于算法T输入mh，可以停止运算时，A回答是 A(T(mh))=是&lt;/p&gt;
&lt;p&gt;对于算法T输入mf，不能停止运算时，A回答否 A(T(mf))=否&lt;/p&gt;
&lt;p&gt;现在构造算法B，对于A输出为是的，B算法不能停止运算 B(A(T(mh))=不&lt;/p&gt;
&lt;p&gt;对于A输出为否的，B算法可以停止运算 B(A(T(mf))=停&lt;/p&gt;
&lt;p&gt;此时，如果将算法B作为算法A和算法B的输入就会推出矛盾&lt;/p&gt;
&lt;p&gt;根据A算法具备的能力，那么以下的两个代入应该成立 A(B(A(T(mh)))=否 A(B(A(T(mf)))=是&lt;/p&gt;
&lt;p&gt;根据B算法具备的能力，那么以下的代入应该成立 B(A(B(A(T(mh))))=停 B(A(B(A(T(mf))))=不&lt;/p&gt;
&lt;p&gt;矛盾出现了，对于输入mh，既能停止运算，又不能停止运算；对于输入mf，既不能停止运算，有能停止运算。所以B不存在，A也不存在。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;复杂一书中的描述如下&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;H算法（图灵机）可以判定任意M算法对于任意输入I的停机行为，即 对于M(I)可以停止，H(M, I)=是 对于M(I)不可停止，H(M, I)=否&lt;/p&gt;
&lt;p&gt;如果将M算法作为M的输入，那么根据前面的假设H也能够得到是与否的结论。不过，这时，我们去构造一个算法H&amp;rsquo;，对于M(M)可以停止的时候，H&amp;rsquo;算法不可停止；相反则课停止，即 对于M(M)可以停止，H&amp;rsquo;(M,M)=不 对于M(M)不可停止，H&amp;rsquo;(M,M)=停&lt;/p&gt;
&lt;p&gt;这时问H&amp;rsquo;(H&amp;rsquo;,H&amp;rsquo;)会如何呢？ 根据H&amp;rsquo;构造的逻辑，H&amp;rsquo;(H&amp;rsquo;)如果可以停止，那么H&amp;rsquo;(H&amp;rsquo;,H&amp;rsquo;)不能停止，而如果它不能停止，又是可停止的，于是得到矛盾。&lt;/p&gt;
&lt;p&gt;这两种叙述看起来有许多不同，但本质的两个方面是相同的。其一，算法本身可以作为算法的输入，这点观察不是显而易见的，并且也不那么容易理解。这点观察有个简单的等价问题就是理发师问题，此外，还有就是罗素的集合悖论以及哥德尔证明数学完备性的基础。拓展到计算机中，所有学过计算机软件理论的人都感到学习编译原理的难度要高过操作系统、计算机网络等，主要原因大概就是因为编译过程的输入、处理规则和输出都是程序，非常反直觉。其二，无矛盾是理性推理的基石，这点学过一些基础数学证明的人都曾会有些体会。&lt;/p&gt;</description></item><item><title>求数组的子数组之和的最大值</title><link>https://blog.nicelylit.net/posts/%E6%B1%82%E6%95%B0%E7%BB%84%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E4%B9%8B%E5%92%8C%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC/</link><pubDate>Sun, 20 Aug 2017 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E6%B1%82%E6%95%B0%E7%BB%84%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E4%B9%8B%E5%92%8C%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC/</guid><description>&lt;p&gt;——尾递归的应用&lt;/p&gt;
&lt;p&gt;这是《编程之美》中2.14节提出的一个问题，问题的描述为&lt;/p&gt;
&lt;p&gt;一个有N个整数元素的一维数组(A[0], A[1], &amp;hellip;, A[n-2], A[n-1])，这个数组有很多子数组，那么子数组之和的最大值是什么？&lt;/p&gt;
&lt;p&gt;以往遇到这种问题的第一种思维模式也像书中的第一种解法一样去枚举出所有的子数组，对每个子数组内部元素求和，然后找出所有和中最大的。这是一种分解问题的思路，但所分解的子问题已经偏离了原始的问题。比如第一步的分解就是枚举所有的子数组，单就这步就已经是\(O(n^2)\)了，而后的两个子问题，一个是内部元素求和，另一个是找出所有和中最大的，这两个问题与原问题也不具有太多的共性，因而能够由此想到最优的算法极为困难。&lt;/p&gt;
&lt;p&gt;书中的解法二用了递归分解子问题的思路，却没有给出适当的语言描述，使用了循环。用循环去描述，对思考者而言不是一种好的语言，正如我在《&lt;a class="link" href="https://blog.nicelylit.net/posts/%E5%B0%BE%E9%80%92%E5%BD%92%E7%9A%84%E5%90%AF%E7%A4%BA/" &gt;尾递归的启示&lt;/a&gt;》一篇中写的那样，循环是一种机器友好的语言。这篇文章，我将会用纯粹地过程调用的方式来完成这个问题的解答，最终得到的优化算法与《编程之美》中提到的最后一种解法是等价的。当然递归子问题的分解与解法二也不太一样。&lt;/p&gt;
&lt;p&gt;对于原问题，可以分解为以下两个子问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;求不包含A[0]的所有子数组之和的最大值；&lt;/li&gt;
&lt;li&gt;求包含A[0]的所有子数组之和的最大值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原问题的解就是两个子问题解的较大者。显然，第一个子问题是原问题的递归，不包含A[0]的所有子数组，意味着(A[1], A[2], &amp;hellip;, A[n-1])的所有子数组。第二个问题是一个新的子问题，但比原问题要简单的子问题。假设我们记原问题为sub_arr_max，数组A为arr，数组的起始坐标为s，结束坐标为e，第二个子问题为max_of_all_subs，那么以上的分解，很容易翻译为以下的代码&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def sub_arr_max(arr, s, e):
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接下来是如何解决子问题max_of_all_subs。先回顾这个子问题的含义，这个子问题是说所有包含arr[s]元素的子数组元素和中，最大的值。这个子问题可以进一步分解为两个子问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;求不包含arr[e]的数组中所有包含arr[s]的最大值；&lt;/li&gt;
&lt;li&gt;求数组arr[s..e]所有元素的和。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个问题的分解中，第一个子问题又一次是原问题的递归，而第二个子问题是一个更为简单的递归问题。假设记第二个子问题为sum_of_all，那么以上的问题分解可以立刻翻译为&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def max_of_all_subs(arr, s, e):
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;解决子问题sum_of_all是一个常见的线性递归，可以表述为，当s &amp;gt; e时，&lt;/p&gt;
&lt;p&gt;\(sum\_of\_all (arr, s, e) = \) \(sum\_of\_all(arr, e, e-1) + arr[e]\)&lt;/p&gt;
&lt;p&gt;直接翻译以上的递归式就能得到下面的解法&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def sum_of_all(arr, s, e):
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以上就是一个完整可以运行的解法，并且很容易调试错误，作为寻找优化算法的起点。为了不偏离本文的核心要义，且不去分析这样的解法中有多少的重复计算和空间浪费。直接将三个过程依次改造为尾递归的形式，这个改造过程中，不仅会找到最优的解法，并且能够洞察原问题中的一些特殊性，算法分析也附带会变得清楚。&lt;/p&gt;
&lt;h2 id="第一步改造sum_of_all"&gt;第一步：改造sum_of_all
&lt;/h2&gt;&lt;p&gt;sum_of_all方法是一个线性递归的过程，我们可以在参数中增加一个变量res来记录从第一个元素arr[s]开始累加到s[e]的结果，每次递归调用只需要增加s即可。这个改造过程与《尾递归的启示》中提到的阶乘的改造过程几乎是相同的。一种写法如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def tsum_of_all(arr, s, e, res):
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;为了区分与前面算法的不同，重命名为tsum_of_all，t代表tail recursion的意思。有了新方法，在max_of_all_subs中可以这样调用&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def max_of_all_subs(arr, s, e):
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;或者&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def max_of_all_subs(arr, s, e):
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;两种写法等价。这里之所以要写出这两种，其实是想提醒，新增变量的初始值一定要注意，0是个很特殊的值，有些情形下并不能初始为0，第二步的改造中就会遇到这样的问题。&lt;/p&gt;
&lt;h2 id="第二步改造max_of_all_subs"&gt;第二步：改造max_of_all_subs
&lt;/h2&gt;&lt;p&gt;max_of_all_subs求解了arr[s], arr[s..s+1], arr[s..s+2], .., arr[s..e]中，所有数组各自求和中最大的那个。用动态规划的思路，就是从小的开始求解，arr[s]显然就是自己，而后的每一个，比如arr[s..s+i]，等于arr[s..s+i-1]的最大解和arr[s..s+i-1]的和加arr[s+i]两者比较的较大者。在求arr[s..s+i-1]时，可以保存两个结果，一个是arr[s..s+i-1]的最大解，记为res，另一个是arr[s..s+i-1]的和，记为ss，即sum_of_all问题已经融入到了问题的迭代中。改造后的算法如下&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def tmax_of_all_subs(arr, s, e, res, ss):
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;为了区分与前面算法的不同，重命名为tmax_of_all_subs。当在sub_arr_max中使用这个方法时候，需要注意的是，初始值res不能为0，理由是如果数组中的元素都是负数，那么所有的子数组和都无法比0大，造成错误。因而调用时候要从第一个元素开始&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def sub_arr_max(arr, s, e):
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第三步改造sub_arr_max"&gt;第三步：改造sub_arr_max
&lt;/h2&gt;&lt;p&gt;回顾改造尾递归的两个要素之一是从小问题开始，寻找合适的变量来记录中间结果。如果数组是一个元素，直接返回结果，如果是两个元素，我们可以记录一个元素时候的结果，然后同新加入元素之后的结构进行比较。最开始自顶向下考虑问题时，我们从A[0]元素开始划分，A[1..N-1]同原问题有着同样的结构，而从自底向上考虑时，可以反过来想，考虑到第i个元素时A[0..i-1]同原问题有着同样结构的子问题，而A[0..i]需要解决的是max_of_all_subs的问题。这时我们能够改写出一个tmax_of_all_subs的一个对称写法。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def tmax_of_all_subs(arr, s, e, res, ss):
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在sub_arr_max中最终比较的两个对象，其一是与原问题有着同样结构子问题的值，我们可以用一个变量去记录这个值，其二是tmax_of_all_subs问题，我们不需要每次去重算，只需要保存arr[s..e-1]中的最大值，能比较的是保存的最大值+arr[e]和arr[e]的比较中较大的作为新的保存值。为什么保存的arr[s..e-1]中最大值可以加arr[e]？因为这个最大值一定包含arr[e-1]，维护了tmax_of_all_subs这个问题求值的结构。如同在第二步中省去sum_of_all一样，这个问题中我们一样可以省去max_of_all_subs。由此就得到了最终的解法&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def tsub_arr_max(arr, s, e):
&lt;/code&gt;&lt;/pre&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;ul&gt;
&lt;li&gt;&lt;a class="link" href="http://mjm1990.com/article/%E5%B0%BE%E9%80%92%E5%BD%92%E7%9A%84%E5%90%AF%E7%A4%BA" target="_blank" rel="noopener"
 &gt;尾递归的启示&lt;/a&gt;?⭐️⭐️⭐️⭐️⭐️&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>尾递归的启示</title><link>https://blog.nicelylit.net/posts/%E5%B0%BE%E9%80%92%E5%BD%92%E7%9A%84%E5%90%AF%E7%A4%BA/</link><pubDate>Sat, 19 Aug 2017 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%B0%BE%E9%80%92%E5%BD%92%E7%9A%84%E5%90%AF%E7%A4%BA/</guid><description>&lt;p&gt;——读《计算机程序的构造和解释》第一章第二小节所想&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-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个递归中，最后一步操作是乘法，而非调用过程本身，因而不是尾递归。转换成尾递归的形式如下&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;factorial_iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;factorial_iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;factorial_iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&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;不论是同非尾递归过程比较还是专门的循环结构比较，都先需要明确的是过程调用意味着什么，过程到底是什么。&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;/p&gt;
&lt;p&gt;拿前面阶乘的例子来看，product就是用来保存中间状态的参数，counter是用来控制迭代次数的参数。尾递归实现中通过增加这两个参数，将空间开销由O(n)减到了O(1)。阶乘的例子由于是线性递归，所以不存在重复计算，但即便如此，非尾递归的实现中也需要先展开再规约，而尾递归的实现中只需要一遍就能得到结果，计算量也会减少一半。&lt;/p&gt;
&lt;p&gt;很多的问题的递归解法，呈现出树形递归的特点，即原问题的解决依赖于多个相同子问题的解决，比如换零钱的问题。&lt;/p&gt;
&lt;p&gt;假设只有50美分、25美分、10美分、5美分和1美分的面值，如果将给定数额的钱用前面提到的五种面额兑换，那么有多少种换法？&lt;/p&gt;
&lt;p&gt;这个问题跟求组合数的问题非常类似，可以将原问题分解为两个子问题。第一，完全不用50美分兑换，用剩下的四种面值兑换给定额度的钱；第二，用一个50美分兑换，对于除去50美分剩下的钱再用五种面值去兑换。这两个子问题彼此互斥，所以求和就是原问题的解。用程序语言描述即为&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rec_count_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个解法显然不是尾递归，最后一步操作是加法，不过非尾递归的解法往往非常容易辅助在脑海中产生求解问题的分解结构。分解的结构是棵二叉树，树的叶子节点要么是0，要么是1，因而叶子的数目与问题的结果是同量级的，这其中的重复计算量是巨大的。当amount是100时，问题的结果是292；当amount是1000时，问题的结果是801,451。虽然amount只增长了10倍，但结果却增长了2700多倍，这是超线性的增长。此外，这棵树不太平衡，如果记rcc(amount, kinds_of_coins-1)为左子树，另外一个为右子树，那么由于kinds_of_coins很快就减到了0，所以左边的深度较浅，最左边的深度只有kinds_of_coins+1；右边由于50美分减得很快，深度也较浅，最右边的深度为int(amount/50)+1，最深的部分在中间为amount+kinds_of_coins+1，所以整棵树像是一串葡萄一样。最大的深度也意味空间开销与amount+kinds_of_coins是同量级的。&lt;/p&gt;
&lt;p&gt;将这个问题改造成尾递归的形式，需要两点技巧，第一，尾递归一般意味着自底向上地先求解最终问题依赖的子问题的结果，因而需要在参数中增加适当的变量，开辟适当的空间，用于存储中间子问题结果，空间的大小越小，不仅省空间，往往同时也会提升运行效率；第二，当问题中有两个以上的输入时，就需要找对迭代的方法和维度，这点往往对节省空间起着至关重要的作用。&lt;/p&gt;
&lt;p&gt;如果用表格记录所有可能子问题的结果，那么表格一定只有(amount+1)*kinds_of_coins的大小，共有kinds_of_coins行，amount+1列。在求解表格中任何一个格子中的值时，都只依赖于前一行同列（kinds_of_coins-1, amount）与同行前面某列（kinds_of_coins, amount-first_denomination(kinds_of_coins)）的值，如果我们逐行地填充表格，就不必要保存着整张表，而只需要一行的存储空间。这样的空间开销不会比非尾递归大，时间上要远好。想清楚了第一点技巧，还要想清第二点，也就是说，起始时候，amount从0开始，kinds_of_coins从1开始计算，到底是先固定amount为0不变，将所有的kinds_of_coins都算出来呢，还是相反将kinds_of_coins固定为1，而去计算出对应所有amount的值。其实在选择存储空间大小时候，就已经潜在回答了这个问题，我们选择了一行，即为后者。明白了改造尾递归的两个方面，改起算法来也不会非常困难。尾递归的解法为&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tail_rec_count_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;trcc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trcc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kinds_of_coins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;kinds_of_coins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;trcc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kinds_of_coins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;remain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ca&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;first_denomination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;remain&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;remain&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;trcc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kinds_of_coins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&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;然而，值得注意的是并不是所有的语言都适合写成尾递归的形式，很多解释型的语言并没有对尾递归做优化，比如上面所用的&lt;a class="link" href="https://stackoverflow.com/questions/13591970/does-python-optimize-tail-recursion" target="_blank" rel="noopener"
 &gt;Python语言&lt;/a&gt;。编译型的语言多会对尾递归做优化，比如C、Java。我尝试在Python上运行tail_rec_count_change(1000)，结果抛出了RuntimeError: maximum recursion depth exceeded的异常，而在Java的虚拟机上运行，则会给出正常的结果。由此，尾递归适合用于辅助我们思考较为复杂的算法问题，最终实现时要改造为循环结构的迭代形式。&lt;/p&gt;
&lt;h2 id="后记"&gt;后记
&lt;/h2&gt;&lt;p&gt;除了换零钱的例子外，求解组合数、求幂的O(log n)算法、求数组的子数组之和的最大值也都是良好的练习，相应的解法可以&lt;a class="link" href="https://github.com/jeremy1990/small-problems/tree/master/tail-recursion-examples" target="_blank" rel="noopener"
 &gt;参考这里&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关阅读&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="http://mjm1990.com/article/%E6%B1%82%E6%95%B0%E7%BB%84%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E4%B9%8B%E5%92%8C%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC" target="_blank" rel="noopener"
 &gt;求数组的子数组之和的最大值&lt;/a&gt;?⭐️⭐️⭐️⭐️&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>图灵停机问题的一个简单论述</title><link>https://blog.nicelylit.net/posts/%E5%9B%BE%E7%81%B5%E5%81%9C%E6%9C%BA%E9%97%AE%E9%A2%98%E7%9A%84%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E8%AE%BA%E8%BF%B0/</link><pubDate>Sun, 21 May 2017 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%9B%BE%E7%81%B5%E5%81%9C%E6%9C%BA%E9%97%AE%E9%A2%98%E7%9A%84%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E8%AE%BA%E8%BF%B0/</guid><description>&lt;p&gt;图灵停机问题问的是“能否设计一个算法，在判定任意的一段算法对给定的一些输入在有限步骤后停止运算时，返回是，而在判定任意的一段算法对给定的另外一些输入无法在有限步骤后停止运算时，返回否“。图灵的回答是这样的算法无法被设计出来。&lt;/p&gt;
&lt;p&gt;问题中所谓的算法等价于图灵机。&lt;/p&gt;
&lt;p&gt;假设存在算法A，具备这样的能力。设想对任意的算法T，对于输入mh，能够在有限步骤后停止运算，那么，此时算法A对于(T, mh)回答是。同样的算法T，对于输入mf，无法在有限步骤后停止运算，那么此时算法A对于(T, mf)回答否。&lt;/p&gt;
&lt;p&gt;这时，我们构造算法B，对于算法A判定回答是的输入mh，算法B不能在有限步骤后停止运算，而对于算法A判定回答否的输入mf，算法B在有限步骤后停止运算。如果算法A存在，那么一定能够设计出算法B。&lt;/p&gt;
&lt;p&gt;根据A具备的能力，算法B在输入为mf时，有限步骤后停止运算，A应该输出是，然而根据B的构造，凡是A输出是的输入，B应该无法在有限步骤后停止运算。这与前面说的“B在输入为mf时，有限步骤后停止运算”相矛盾。因此算法B不存在，因而算法A也不存在。&lt;/p&gt;
&lt;p&gt;有人说设计这类算法的困难之处在于给出出现无穷循环的条件。&lt;/p&gt;
&lt;p&gt;还有人从图灵停机问题上看出了&lt;a class="link" href="http://blog.sciencenet.cn/blog-2322490-991454.html" target="_blank" rel="noopener"
 &gt;人的不确定本质&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;我个人好奇的两个问题是，其一，人脑的计算能力和图灵机是否等价；其二，逻辑推理的最为锐利的武器就是推导出矛盾，因为我们观测到的现实的具象的某一个存在都应是无矛盾的，然而思维本身却不是无矛盾的，矛盾如何形式化，形式化后对解决现有逻辑无法解决的问题有帮助吗，我们有能力将具象的某一个存在也变成一个矛盾的综合体吗。&lt;/p&gt;
&lt;p&gt;本质的好奇在于思维和宇宙是什么关系。宇宙是否自洽，具象的存在是否无矛盾。是思维囊括了宇宙还是宇宙滋养了思维。如果宇宙自洽，具象无矛盾的，那么思维的能力似乎要强于宇宙。如果宇宙不自洽，那么思维能力只是宇宙的小部分。我说这段话的时候，自洽与非自洽已经在假设将宇宙给了某种定性，或许宇宙就像思维一样，是一个自洽和非自洽的综合体。我们的思维倾向于更加的符合某种逻辑性，而宇宙恰好是那种逻辑性的表述。&lt;/p&gt;</description></item><item><title>三角形周长</title><link>https://blog.nicelylit.net/posts/%E4%B8%89%E8%A7%92%E5%BD%A2%E5%91%A8%E9%95%BF/</link><pubDate>Sat, 25 Feb 2017 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E4%B8%89%E8%A7%92%E5%BD%A2%E5%91%A8%E9%95%BF/</guid><description>
 &lt;blockquote&gt;
 &lt;p&gt;有\(n\)根棍子，棍子\(i\)的长度为\(a_i\)。想要从中选出3根棍子组成周长尽可能长的三角形。请输出最大周长的三根棍子，若无法组成三角形则输出空字符串。&lt;/p&gt;
&lt;p&gt;限制条件 \(3\le n\le 100\) \(1\le a_i\le 10^6\)&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;例如，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;输入： 2,3,4,5,10
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;输出： 3,4,5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;输入： 4,5,10,20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;输出： [空行]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;众所周知，满足三角形的条件为任意两边之和大于第三边，略微一想，等价于两短边之和大于最长边。知道这个条件后，立刻有了穷举的思路，利用三重循环，遍历所有可能的三元组，然后判断满足此条件的三元组周长是否大于已经发现的最大三元组周长，如果是，那么替换，否则继续找。寻找三元组有\(C^3_n\)种可能性，这是\(O(n^3)\)的算法。具体的实现可以&lt;a class="link" href="https://github.com/jeremy1990/small-problems/blob/master/max-triangle-circumference/solutions.py#L17-L30" target="_blank" rel="noopener"
 &gt;参考这里解法一&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;实际上，由于题目限制了说\(n\)的范围仅在3到100之间，所以3次方也并没有多少计算，所以这样的解法也可行。&lt;/p&gt;
&lt;p&gt;不过对于\(n\)很大的情况，三次方的算法会有很多的冗余计算，常常较慢。这个问题是否有更快的解决办法。我们可以进一步形式化。假设选出的三条边为\(a_i\),\(a_j\),\(a_k\)，构成三角形的条件为&lt;/p&gt;
&lt;p&gt;\(a_i\le a_j\le a_k\) \(2a_k&amp;lt;a_i+a_j+a_k\le 3a_k\)&lt;/p&gt;
&lt;p&gt;当\(a_i\le a_j\le a_k\)时，\(a_i+a_j+a_k\le 3a_k\)是显然成立的，\(2a_k&amp;lt;a_i+a_j+a_k\)才是三角形成立的重要条件，变形移项后发现\(a_i&amp;gt;a_k-a_j\)。在充分考虑大小关系和移项后的不等式之后，很快会有第二种思路：先对\(n\)根棍子按长度从小到大排序，从大到小循环\(a_k\)和\(a_j\)，然后只要\(a_{j-1}&amp;gt;a_k-a_j\)，那么就找到了一个合理的三角形。为什么只要找\(a_{j-1}\)就可以呢，因为排序后，如果\(a_{j-1}\)都小于等于\(a_k-a_j\)，那么更前面的数字就更不用看了。这个解法只对\(j\)和\(k\)做了循环，所以复杂度为\(O(n^2)\)。具体的实现可以&lt;a class="link" href="https://github.com/jeremy1990/small-problems/blob/master/max-triangle-circumference/solutions.py#L34-L48" target="_blank" rel="noopener"
 &gt;参考这里解法二&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;实测在\(n\)为200多时，解法一需要\(1s\)以上的时间，而解法二只需要几个毫秒。&lt;/p&gt;
&lt;p&gt;继续思考，还能更高效吗。前面形式化的时候，提到说\(a_i+a_j+a_k\le 3a_k\)是显然成立的。不过即使是显然成立的条件，也依旧能够作为剪枝的条件，何况还是个上确界。在第二种思路的基础上，如果继续向下看的\(a_k\)都发现\(3a_k\)已经小于等于当前找到的最大的周长，那么其余的部分就没有看的的必要了。这有种贪心的意味在其中。这个剪枝可以让算法的效率比几个毫秒还更快。具体的实现&lt;a class="link" href="https://github.com/jeremy1990/small-problems/blob/master/max-triangle-circumference/solutions.py#L51-L66" target="_blank" rel="noopener"
 &gt;参考这里解法三&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;实际上在想出解法二，论证为什么只要看\(a_{j-1}&amp;gt;a_k-a_j\)就可以的时候，我们就会产生一个猜想，最终的答案一定是排序后下标连续的三个数。对于\(a_k\)，周长的范围是大于\(2a_k\)，小于等于\(3a_k\)。由于\(i&amp;lt;j&amp;lt;k\)，那么必然满足小于等于\(3a_k\)的条件。对于下界，如果\(a_{k-2}+a_{k-1}\)都不能够大于\(a_k\)，那么也就不存在其它的\(i\)和\(j\)能满足大于\(a_k\)的条件了。所以只需要检查\(a_{k-2}+a_{k-1}&amp;gt;a_{k}\)即可。这样，只需要贪心地从最大的数字开始遍历就可以，最坏情况下是\(O(n)\)的复杂度。由此整个问题的瓶颈就转移给了排序。如果用基数或者计数排序，整个算法的复杂度就是\(O(n)\)了。具体的实现&lt;a class="link" href="https://github.com/jeremy1990/small-problems/blob/master/max-triangle-circumference/solutions.py#L72-L80" target="_blank" rel="noopener"
 &gt;参考这里解法四&lt;/a&gt;。&lt;/p&gt;</description></item><item><title>像素密度和设备像素比</title><link>https://blog.nicelylit.net/posts/%E5%83%8F%E7%B4%A0%E5%AF%86%E5%BA%A6%E5%92%8C%E8%AE%BE%E5%A4%87%E5%83%8F%E7%B4%A0%E6%AF%94/</link><pubDate>Fri, 03 Feb 2017 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%83%8F%E7%B4%A0%E5%AF%86%E5%BA%A6%E5%92%8C%E8%AE%BE%E5%A4%87%E5%83%8F%E7%B4%A0%E6%AF%94/</guid><description>
 &lt;blockquote&gt;
 &lt;p&gt;像素密度和设备像素比的概念事实上比想象中的复杂，希望这篇文章能够消除开发者和设计师们的疑虑和错误认知&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;显示器上常用的长度单位是像素（pixel），比如说一台显示器的分辨率是1280×800像素，一张照片的大小是1050×1500像素，一台照相机有300万像素。像素和物理上的长度单位不是等价的。物理上的长度是个连续的概念，所以物理上的长度单位也是连续的。像素是对显示器上的电子基础元器件或者图像文件的基础单元的抽象，是个具体离散的概念，只是用于计数，也没有平方、立方的概念。&lt;/p&gt;
&lt;p&gt;人对长度的感知，无非通过眼和手两种渠道。大多数时候用眼，少部分时候用手。举例来说用手的场景，比如写字、画画、投篮、扔标枪、射箭、做手术、开车等等，当然这些场景仍旧离不开眼的配合，不过手起了决定性的作用。具体地，以写字来看，普通的作文格子纸的格子大小为7.5mm×8mm，书写的汉字大小一般为3mm×3mm左右，所以普通人手能控制的精确度在1mm的数量级，书法家、画家和外科医生的手，应该能在0.1mm的数量级。相较而言，眼睛能够区分的精确度会更好，大约是0.02mm，原因是目前没有生产过比50分度更精确的游标卡尺，如果要更精确就需要借助一些物理量之间的转换去放大然后进行观测。不管精确度如何，都是对物理长度的感知。&lt;/p&gt;
&lt;h3 id="原始的像素密度"&gt;原始的像素密度
&lt;/h3&gt;&lt;p&gt;用像素去表示物理长度单位，需要一次转换，即单位英寸*的像素数，一般称作像素密度**。有些地方将像素密度写作PPI（pixels per inch)，而另外一些地方写作DPI（dots per inch），实际上都是一样的含义。这个类似的概念最早出现在印刷行业，印刷中的基本单位是点（Point, pt）[&lt;a class="link" href="https://en.wikipedia.org/wiki/Point_%5c%28typography%5c%29" target="_blank" rel="noopener"
 &gt;1&lt;/a&gt;]，著名的1个点代表1/72英寸已达成共识，因此也能说印刷标准中的点密度是72DPI。汉字印刷中常用的字体，小四是指12pt，即1/6inch或4.2mm，这意味着在不考虑字间距的情况下，1英寸能写6个小四的汉字。另外也能看出，一个小四的汉字是由12×12=144个点描绘出的图形。在高清的印刷中，多会选用300DPI，即单位英寸的点数是标准的4倍多，同样一个小四的汉字将需要50*50=2500个点描绘，自然更加高清。&lt;/p&gt;
&lt;p&gt;像素密度的概念很容易理解，但当应用到不同的场景后，特别是一些生产商偷换了概念后，问题就变得复杂了许多。&lt;/p&gt;
&lt;p&gt;先从最原始的像素密度含义说起。所有带显示器的硬件设备都会提供两个参数，总的像素数和屏幕尺寸。常见的屏幕都是长方形的***，所以一般爱用长乘宽的分辨率来说明总的像素数，比如文章一开头说的1280*800像素。用长乘宽的好处是屏幕比例也一目了然，同时在描述屏幕尺寸时候也可以简略地写几寸的屏幕，一般都用对角线的长度，这样显得屏幕比较大。有了像素数和屏幕尺寸，立即可以算出像素密度的值（表1列出了常见设备的像素密度比），这个值是所有屏幕的固有参数。像素密度越大，像素的物理长度就越小，同尺寸的屏幕就能放置更多的像素，设备就越有能力显示高像素数的图像，清晰度和细节由此增加。因而，像素密度是衡量显示器清晰度的重要指标。&lt;/p&gt;
&lt;p&gt;表1?常见设备的屏幕参数&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2017/02/common.devices.png" &gt;&lt;img alt="common.devices" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2017/02/common.devices-732x1024.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果比较不同尺寸的屏幕，像素密度就无法作为唯一的清晰度衡量指标了。众所周知，人的眼球是凸透镜的结构，可以自由的调节焦距。面对不同的物体距离，清晰度就不单取决于像素密度了，和距屏幕的距离也有关系。一个简单的模型如图1所示，距离屏幕越远，像素大小可以取的越大，像素密度可以越小。换句话说，远距离的屏幕可以用较小的像素密度达到和距离近的屏幕一样的清晰度。前面提到人的眼睛能够区分的精确度大约是0.02mm，这实际上省略了一个前提是，距离大约20厘米的情况下，忽略了这个前提就无意义了。清晰度实际上是在考验人眼睛的精确度，也就是对长度的分辨能力。任何人站在一米开外都看不到0.02mm的长度。如果要达到0.02mm的精确度，20厘米左右的书面，需要1270DPI的像素密度；60厘米左右的电脑显示器，需要423DPI的像素密度；2米左右的电视机，需要127DPI；20米左右的电影荧幕，需要12.7DPI。通过这个关系，也就理解为什么手机屏幕的像素密度一般要高于显示器，而显示器要高于电视机。人类采集的图像和视频毕竟不能和自然本身相比，总有个限度，利用好像素密度，可以节省资源。&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2017/02/view.distance.png" &gt;&lt;img alt="view.distance" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2017/02/view.distance-300x144.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;图1 视距和分辨最短物理长度的关系示意图&lt;/p&gt;
&lt;h3 id="图像的像素密度"&gt;图像的“像素密度”
&lt;/h3&gt;&lt;p&gt;电脑的图片一般都有个图像的像素密度（Image DPI）的属性，Windows系统上还分水平分辨率和垂直分辨率。这个属性对于在显示器上显示图像没有任何的影响，对图像的文件大小也不会有任何的影响。显示器上影响图像显示的仅仅是图像尺寸（像素），像素越多，能展示的细节越多。图像的文件大小也与这个值没有任何关系，而是跟图像尺寸、图像格式和图像内容有关。图像尺寸越大，像素越多，需要编码的信息就越多，文件大小越大。图像的格式决定了图像存储时使用何种压缩算法，对于相同尺寸的图像，有损压缩的格式（比如JPEG）比无损压缩的格式（比如BMP）文件小。即使相同的算法，相同的尺寸，不同图像存储大小也有差异，单色的图像会小，内容丰富的图像会大。&lt;/p&gt;
&lt;p&gt;实际上，图像文件的元数据中提供DPI这个属性是为了打印照片用的。确定了图像尺寸和图像的“像素密度”，就能够确定打印时的照片的物理尺寸。由此可见，这里所谓的“像素密度”中的像素其实是打印机中的点，并非显示器中的像素，当然理解为图像文件中的像素也没问题。提到图像文件中的像素，不得不说一句，照相机的说明书中的像素一般指的是这个。我们总是听到照相机宣传语说几百万像素或者几千万像素，鲜有听说多少乘多少的像素的说法，意义正在于此。&lt;/p&gt;
&lt;p&gt;Photoshop是修改图像尺寸和“像素密度”的有效工具，Mac上的Preview也能做到这点，Windows自带的图片浏览软件似乎不带这个功能。对于打印照片而言，了解常见的相片[&lt;a class="link" href="https://en.wikipedia.org/wiki/Photo_print_sizes" target="_blank" rel="noopener"
 &gt;2&lt;/a&gt;]和打印纸[&lt;a class="link" href="https://en.wikipedia.org/wiki/ISO_216" target="_blank" rel="noopener"
 &gt;3&lt;/a&gt;]的尺寸会对裁剪图像尺寸有所帮助。苹果给出的官方指南[&lt;a class="link" href="https://support.apple.com/en-us/HT201495" target="_blank" rel="noopener"
 &gt;4&lt;/a&gt;]，也是个不错的参考。&lt;/p&gt;
&lt;h3 id="字体的像素密度"&gt;字体的“像素密度”
&lt;/h3&gt;&lt;p&gt;显示器上显示字体的情况远比显示图像要复杂，“像素密度”再次被重新定义，这次改动的不是像素的含义，而是分母，物理长度的含义。&lt;/p&gt;
&lt;p&gt;先回到早期，苹果和微软第一次产生分歧的时候。20世纪80年代，苹果生产的Macintosh沿袭了印刷业的传统，用72个像素表示一个“英寸”。那时的阴极射线管（CTR）显示器中，一个像素的大小大约恰好是1/72英寸，所以纸面上印刷的同号字体与屏幕上的同号字体等大[&lt;a class="link" href="https://en.wikipedia.org/wiki/Dots_per_inch#Computer_monitor_DPI_standards" target="_blank" rel="noopener"
 &gt;5&lt;/a&gt;, &lt;a class="link" href="https://blogs.msdn.microsoft.com/fontblog/2005/11/08/where-does-96-dpi-come-from-in-windows/" target="_blank" rel="noopener"
 &gt;6&lt;/a&gt;]。然而，微软却用96个像素表示一个“英寸”，可以明显看出，这个“英寸”，在像素大小大约是1/72英寸的情况下，实际长度是1.3英寸。之所以这样做，有人分析说是因为微软认为人们看屏幕的距离一般比看纸面的距离多出了1/3，所以字体需要等比例放大[&lt;a class="link" href="https://en.wikipedia.org/wiki/Dots_per_inch#Computer_monitor_DPI_standards" target="_blank" rel="noopener"
 &gt;5&lt;/a&gt;]。96这个参数，一直沿用至今。从这次分歧可以看出，操作系统厂商，偷用了像素密度的概念，他们所谓的72DPI和96DPI中的1“英寸”都不是物理上的1英寸，为方便可以说是逻辑英寸。&lt;/p&gt;
&lt;p&gt;当下的屏幕，像素密度至少都在110PPI左右。不论是苹果的72DPI，还是微软的96DPI，逻辑的1英寸都比物理的1英寸要小。为此，我做了实际测量。以下的两个字“汉字”，是72号宋体字，可以写作72pt。在普通的Mac上，等于72px，在Windows上，等于96px（默认分辨率）。我预测在27英寸、分辨率为2560*1440的戴尔显示器上，像素密度为108PPI，Mac系统下，两个汉字的长度为3.4cm，误差不超过1mm；在14英寸、分辨率1366*768的Thinkpad显示器上，像素密度为113PPI，Windows系统下，两个汉字的长度为4.3cm，误差不超过1mm。实际测量后，结果和预测完全吻合。 汉字 纸面距离下的，两个72号字体物理长度应为2英寸，即5.08cm。以上两个显示器，使用距离都会比纸面远至少30%，甚至是3倍，而长度反而减少了33%和15%，显然，看起来会更加吃力。所以这样的显示器上，用12号字体，在Mac上就会不到3mm，加上距离的因素，至少应该使用14号字体才会显得舒服。&lt;/p&gt;
&lt;p&gt;对于像素密度在110PPI左右的电脑显示器，单个像素大小约0.2mm，虽然距离理想的423DPI（0.06mm）还有4倍的差距，不过对于大多数人已经足够用了，就好比普通的应用场景只需要1mm的精度，专业的应用场景才需要0.02mm甚至更高的精度。&lt;/p&gt;
&lt;p&gt;以这个4倍的差距来衡量移动手机屏幕，假设平均直视距离为15cm，理想的像素密度大约是1270DPI****，实际的像素密度是318PPI。查看表1，iPhone 6+的像素密度为461PPI，iPhone 6为326PPI，显示的清晰度能达到类似电脑显示器正常使用下的标准。&lt;/p&gt;
&lt;p&gt;回到字体上来，前面的“汉字”实验中，计算实际物理长度的方法是&lt;/p&gt;
&lt;p&gt;(操作系统DPI/72) * 字号 / 设备像素密度&lt;/p&gt;
&lt;p&gt;单位是英寸。对于普通的Mac系统，操作系统DPI是72，带入后，公式为&lt;/p&gt;
&lt;p&gt;字号/设备像素密度&lt;/p&gt;
&lt;p&gt;对于普通的Windows系统，操作系统DPI是96，带入后，公式为&lt;/p&gt;
&lt;p&gt;1.33 * 字号/设备像素密度&lt;/p&gt;
&lt;p&gt;操作系统DPI/72就如同一个放大系数一样，将屏幕上的字体调节到一个合适的尺寸。随着设备像素密度的不断提升，放大系数起到越来越重要的作用，这个系数能够将字体大小调整到合适的物理长度。苹果和微软在放大系数的设计中又一次产生了分歧，苹果抽取出了设备像素比的概念，不仅在操作系统层面能够用设备无关的单位控制控件大小，还将其写入webkit的核心中，在网页的视窗中也能继续发挥作用。苹果的笔记本Mac Pro 13英寸的Retina屏，设备像素密度是227，设备像素比是2，这样使用字号单位时如同在像素密度是113的屏幕上一样。举例来讲，12pt的长度在Mac Pro 13 Retina屏上长度与非Retina屏（像素密度为113的屏幕）显示的物理长度完全相同，注意这里所用的pt已经不同于1/72物理英寸，而是1/72逻辑英寸。在网页中，一般用px来表示字号，而少用pt。网页中的px已经不同于物理上的像素，而被称为逻辑像素，由逻辑像素转换为物理像素只需要乘以设备像素比即可。网页中的pt也不同于操作系统应用中的pt单位，以逻辑英寸来表达，而是用真实的物理英寸来表达。&lt;/p&gt;
&lt;p&gt;而微软，沿用了先前的一贯作风，去修改操作系统DPI，先后有了支持高清屏的120DPI和144DPI[&lt;a class="link" href="https://technet.microsoft.com/en-us/library/ff716252.aspx" target="_blank" rel="noopener"
 &gt;7&lt;/a&gt;]。对应到上面的公式，放大系数变为1.6和2。虽然最终殊途同归，不过不及设备像素比更为直接。在移动网页中，应用设备像素比的概念，苹果支持得较早[&lt;a class="link" href="http://www.quirksmode.org/blog/archives/2012/06/devicepixelrati.html" target="_blank" rel="noopener"
 &gt;8&lt;/a&gt;]。&lt;/p&gt;
&lt;p&gt;至此，有关像素密度和设备像素比的发展变化，以及这两个量给显示效果带来的影响已基本介绍完毕。&lt;/p&gt;
&lt;p&gt;最后不得不提的是Android系统中对这个问题的解决方案。Android定义了160DPI的屏为标准屏MDPI，这种屏幕上1dp=1px[&lt;a class="link" href="https://developer.android.google.cn/guide/practices/screens_support.html" target="_blank" rel="noopener"
 &gt;9&lt;/a&gt;]。基于此标准，又增加了四种高清屏尺寸，240DPI的HDPI，320DPI的XHDPI，480DPI的XXHDPI以及640DPI的XXXHDPI。这四种高清屏的1dp分别是1.5px、2px、3px和4px。这里的系数不同于设备像素比，但具有类似的功效。Android会将像素密度在某一个区间内的屏幕归到某个类别里面，然后dp和px之间的转换基于160，而非物理像素密度。举例来讲，150DPI和170DPI的手机屏幕都属于160DPI的标准屏，两个屏幕上的1dp=1px，但是对于12dp，两个屏幕会相差0.2mm。&lt;/p&gt;
&lt;p&gt;总而言之，在屏幕上显示文字或者图像，影响显示效果的是物理长度，而决定显示物理长度的是像素密度和设备像素比，进一步，决定像素密度和设备像素比的是人眼在不同视距下的分辨率。&lt;/p&gt;
&lt;h3 id="脚注"&gt;脚注
&lt;/h3&gt;&lt;p&gt;* 我们需要去习惯西方的传统计量系统，1码（yard）约等于3英尺（foot），1英尺约等于12英寸（inch），1英寸约等于2.54厘米。 **人们常常混用像素密度和分辨率的概念，比如&lt;a class="link" href="https://msdn.microsoft.com/en-us/library/ms537625%5c%28v=vs.85%5c%29.aspx" target="_blank" rel="noopener"
 &gt;MSDN上的这篇描述&lt;/a&gt;。我比较偏向用像素密度来表示PPI或DPI，而分辨率说整个屏幕的像素数，理由是如果用分辨率去描述像素密度，那么屏幕总的像素数就不知道如何称呼了。另外值得注意的是在不讨论物理尺寸的前提下说分辨率毫无价值。 *** 智能手表出现后，有些厂商开始考虑圆形的屏幕，由于像素都是方形的，圆形屏幕的厂商需要高分辨率去消除一圈的锯齿。 ****这里对理想像素密度的假设是20cm以内的视距，都无法区分比0.02mm更小的距离。&lt;/p&gt;
&lt;h3 id="参考链接"&gt;参考链接
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;[1] &lt;a class="link" href="https://en.wikipedia.org/wiki/Point_%5c%28typography%5c%29" target="_blank" rel="noopener"
 &gt;Point (typography)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[2] &lt;a class="link" href="https://en.wikipedia.org/wiki/Photo_print_sizes" target="_blank" rel="noopener"
 &gt;Photo print sizes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[3] &lt;a class="link" href="https://en.wikipedia.org/wiki/ISO_216" target="_blank" rel="noopener"
 &gt;ISO 216&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[4] &lt;a class="link" href="https://support.apple.com/en-us/HT201495" target="_blank" rel="noopener"
 &gt;Image resolution guideline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[5] &lt;a class="link" href="https://en.wikipedia.org/wiki/Dots_per_inch#Computer_monitor_DPI_standards" target="_blank" rel="noopener"
 &gt;Computer monitor DPI standards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[6] &lt;a class="link" href="https://blogs.msdn.microsoft.com/fontblog/2005/11/08/where-does-96-dpi-come-from-in-windows/" target="_blank" rel="noopener"
 &gt;Where does 96 DPI come from in Windows?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[7] &lt;a class="link" href="https://technet.microsoft.com/en-us/library/ff716252.aspx" target="_blank" rel="noopener"
 &gt;DPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[8] &lt;a class="link" href="http://www.quirksmode.org/blog/archives/2012/06/devicepixelrati.html" target="_blank" rel="noopener"
 &gt;devicePixelRatio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[9] &lt;a class="link" href="https://developer.android.google.cn/guide/practices/screens_support.html" target="_blank" rel="noopener"
 &gt;Supporting Multiple Screens&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>火车窗外</title><link>https://blog.nicelylit.net/posts/%E7%81%AB%E8%BD%A6%E7%AA%97%E5%A4%96/</link><pubDate>Wed, 01 Feb 2017 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E7%81%AB%E8%BD%A6%E7%AA%97%E5%A4%96/</guid><description>&lt;p&gt;每次坐火车时，总爱看窗外的风景。近处的树木看起来总比远处的高楼运动得快，令人眼花撩乱。细想来，以火车为参考系，不论近处的树木还是远处的高楼都以相同的速率向着火车运动的反方向运动，所以不应该是速率的变化造成的视觉差异，而应该是角度的变化率（即角速度）的不同造成的。&lt;/p&gt;
&lt;p&gt;假设初始零时刻，火车以速度\(v\)做匀速直线运动，取火车相对地面的运动方向为正方向。与此同时，假设我所处的位置为位移的零点，垂直于运动方向，距离我\(r\)之处有一个物体。显然初始时刻，这个物体与我的夹角为\(\pi/2\)。对于\(t\)时刻，在火车的运动方向上，物体到我的距离为\(vt\)，以我为参考系，那么物体向着反方向运动了\(vt\)，这时的夹角变为&lt;/p&gt;
&lt;p&gt;$$\theta=\frac{\pi}{2}+\arctan{\frac{vt}{r}}$$&lt;/p&gt;
&lt;p&gt;法向量方向垂直地面向上，物体做逆时针的旋转，旋转的角速率为&lt;/p&gt;
&lt;p&gt;$$\omega=\frac{d\theta}{d{t}}=\frac{vr}{r^2+(vt)^2}=\frac{v}{r+\frac{(vt)^2}{r}}$$&lt;/p&gt;
&lt;p&gt;固定\(r\)为常量，\(\theta\)随着时间的增加在增加，夹角弧度无限趋近于\(\pi\)。固定时间\(t\)为常量，\(\theta\)随着距离\(r\)的增加在减小，夹角弧度无限趋近于0。&lt;/p&gt;
&lt;p&gt;固定\(r\)为常量，\(\omega\)随着时间的增加在减小，角速率无限趋近于0. 固定时间\(t\)为常量，\(\omega\)随着距离\(r\)的增加先增大后减小，角速率有一个最大值，在最近处和最远处都无限趋近于0.&lt;/p&gt;
&lt;p&gt;有趣的位置是角速率最大的位置。当\(r=vt\)时，不同距离的物体在同一时刻的夹角弧度均为&lt;/p&gt;
&lt;p&gt;$$\frac{3\pi}{4}$$&lt;/p&gt;
&lt;p&gt;运动角速率达到最大值，且该角速率的大小为&lt;/p&gt;
&lt;p&gt;$$\omega=\frac{1}{2t}$$&lt;/p&gt;
&lt;p&gt;当\(t\)无限趋近于0时，运动角速率的最大值应该达到无穷大，而根据前面固定\(t\)的分析中，远离最大位置的距离上，运动角速率均是在无限的趋于零，无穷大和零已经难以分辨。&lt;/p&gt;
&lt;p&gt;下面代入一些具体的数字做些直观的感受。假设火车运动的速度为\(v=300km/h=83.3m/s\)，人眼的时间分辨误差是\(1/60s\)，那么在火车运动了\(0.017s\)后，1.4m处的物体运动角速度最大。比1.4m更近处的物体，运动角速度已经越过了最大值，开始减小了。另外由于这部分物体的夹角已经超过了\(3\pi/4\)，所以眼睛已经无法注意到这部分物体，而进一步跳到了位于更前方的物体（1.4m处），眼睛感到晃得很厉害。比1.4m更远处的物体，运动角速度还未越过最大值，角度不足\(3\pi/4\)，所以对比1.4m处的物体，眼睛感觉这部分物体运动很慢。&lt;/p&gt;</description></item><item><title>两个物体的简谐振动</title><link>https://blog.nicelylit.net/posts/%E4%B8%A4%E4%B8%AA%E7%89%A9%E4%BD%93%E7%9A%84%E7%AE%80%E8%B0%90%E6%8C%AF%E5%8A%A8/</link><pubDate>Wed, 04 Jan 2017 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E4%B8%A4%E4%B8%AA%E7%89%A9%E4%BD%93%E7%9A%84%E7%AE%80%E8%B0%90%E6%8C%AF%E5%8A%A8/</guid><description>&lt;p&gt;两个质量均为\(m\)的小车被长度为\(l\)的无形变的弹簧连在一起，放在光滑的水平面上，弹簧质量不计。初始时刻给左侧的小车一个\(v_0\)的初速度，求每个小车的运动方程。根据动能守恒得&lt;/p&gt;
&lt;p&gt;$$m\dot{x}+m\dot{y}=mv_0$$&lt;/p&gt;
&lt;p&gt;根据能量守恒得&lt;/p&gt;
&lt;p&gt;$$\frac{1}{2}m\dot{x}^2+\frac{1}{2}m\dot{y}^2+\frac{1}{2}k(y-x-l)^2=\frac{1}{2}mv_0^2$$&lt;/p&gt;
&lt;p&gt;直接去解这个微分方程组有些困难，可以换到整个系统的质心参考系中去考虑问题。整个系统的质心始终都在两小车连线的中点，根据动能守恒得&lt;/p&gt;
&lt;p&gt;$$2mv_c=mv_0$$&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$$v_c=\frac{1}{2}v_0$$&lt;/p&gt;
&lt;p&gt;根据牛顿第一定律，整个系统做匀速直线运动，所以立刻得到&lt;/p&gt;
&lt;p&gt;$$y-\frac{1}{2}v_0t=\frac{1}{2}v_0t-x$$&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$$y=v_0t-x$$&lt;/p&gt;
&lt;p&gt;即使有这个关系式，微分方程仍旧无法下手。不过转换到质心的参考系后，会发现两个小车相对质心在做简谐振动。以质心为原点，向右为正方向，设右侧小车的位移为\(x_r\)，左侧小车的位移为\(x_l\)，由前面的分析知道&lt;/p&gt;
&lt;p&gt;$$x_l=-x_r$$&lt;/p&gt;
&lt;p&gt;利用胡克定律得&lt;/p&gt;
&lt;p&gt;$$m\ddot{x_l}=-k(x_l-x_r+l)$$&lt;/p&gt;
&lt;p&gt;由牛顿第三定律得&lt;/p&gt;
&lt;p&gt;$$m\ddot{x_r}=-k(x_r-x_l-l)$$&lt;/p&gt;
&lt;p&gt;带入前面的位移关系，得到&lt;/p&gt;
&lt;p&gt;$$m\ddot{x_l}=-k(2x_l+l)$$&lt;/p&gt;
&lt;p&gt;$$m\ddot{x_r}=-k(2x_r-l)$$&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;这两个简谐振动的方程可以分别设为&lt;/p&gt;
&lt;p&gt;$$x_l(t)=A\cos(\omega t+\phi)-\frac{l}{2}$$&lt;/p&gt;
&lt;p&gt;$$x_r(t)=A\cos(\omega t+\phi)+\frac{l}{2}$$&lt;/p&gt;
&lt;p&gt;将解和这初始条件带入方程&lt;/p&gt;
&lt;p&gt;$$v_l(0)=\dot{x_l}(0)=\frac{1}{2}v_0$$&lt;/p&gt;
&lt;p&gt;$$v_r(0)=\dot{x_r}(0)=-\frac{1}{2}v_0$$&lt;/p&gt;
&lt;p&gt;立刻得到频率&lt;/p&gt;
&lt;p&gt;$$\omega=\sqrt{\frac{2k}{m}}$$&lt;/p&gt;
&lt;p&gt;相位&lt;/p&gt;
&lt;p&gt;$$\phi=\frac{\pi}{2}$$&lt;/p&gt;
&lt;p&gt;振幅为&lt;/p&gt;
&lt;p&gt;$$A=\frac{1}{2}v_0\sqrt{\frac{m}{2k}}$$&lt;/p&gt;
&lt;p&gt;又因为质心在做匀速直线运动，所以最终的方程为&lt;/p&gt;
&lt;p&gt;$$x(t)=\frac{v_0}{2}t+A\sin(\omega t)-\frac{l}{2}$$&lt;/p&gt;
&lt;p&gt;$$y(t)=\frac{v_0}{2}t-A\sin(\omega t)+\frac{l}{2}$$&lt;/p&gt;
&lt;p&gt;其中，&lt;/p&gt;
&lt;p&gt;$$\omega=\sqrt{\frac{2k}{m}}$$&lt;/p&gt;
&lt;p&gt;$$A=\frac{v_0}{2\omega}$$&lt;/p&gt;
&lt;p&gt;更一般地，如果两个小车质量分别为\(m\)和\(M\)，质量为\(m\)的小车在左侧，给它的初速度仍旧为\(v_0\)，则两个小车的运动方程为&lt;/p&gt;
&lt;p&gt;$$x(t)=\mu_1v_{0}t+\mu_2A\sin(\omega t)-\mu_2l$$&lt;/p&gt;
&lt;p&gt;$$y(t)=\mu_1v_{0}t-\mu_1A\sin(\omega t)+\mu_1l$$&lt;/p&gt;
&lt;p&gt;其中，&lt;/p&gt;
&lt;p&gt;$$\mu_1=\frac{m}{m+M}$$&lt;/p&gt;
&lt;p&gt;$$\mu_2=\frac{M}{m+M}$$&lt;/p&gt;
&lt;p&gt;$$\omega=\sqrt{\frac{m+M}{mM}k}$$&lt;/p&gt;
&lt;p&gt;$$A=\frac{v_0}{\omega}$$&lt;/p&gt;
&lt;p&gt;最近解出这个问题之后兴奋了一天，看到结果后，突然联想到二体问题。两个物体的简谐振动可能是二体运动在直径上的投影，就如同一个物体的简谐运动是匀速圆周运动的投影运动一般。等有时间了，再去算一下二体运动的解。&lt;/p&gt;
&lt;p&gt;此外，我师兄还用&lt;a class="link" href="http://blog.csdn.net/hkustlwu/article/details/54315978" target="_blank" rel="noopener"
 &gt;纯代数的方法&lt;/a&gt;求解了这个问题，得到了一样的结果。&lt;/p&gt;</description></item><item><title>公开密钥加密在SSH远程登录中的应用</title><link>https://blog.nicelylit.net/posts/%E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86%E5%9C%A8ssh%E8%BF%9C%E7%A8%8B%E7%99%BB%E5%BD%95%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8/</link><pubDate>Wed, 12 Oct 2016 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86%E5%9C%A8ssh%E8%BF%9C%E7%A8%8B%E7%99%BB%E5%BD%95%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8/</guid><description>&lt;p&gt;一直以来，都在享受着SSH免密码登录的远端服务器的好处，但总觉得对背后的逻辑不是太清楚，或者说，根本没有细想过，因为网上充斥了大量的，如何做，却不告诉为什么，下一次再去做的时候，又费半天功夫，所以有必要熟悉完整的流程。近来有了“&lt;a class="link" href="http://blog.csdn.net/sszgg2006/article/details/8199175" target="_blank" rel="noopener"
 &gt;深入理解加密、解密、数字签名和数字证书&lt;/a&gt;”这篇文章做底子，SSH免密码登录背后的逻辑也容易理解很多。&lt;/p&gt;
&lt;p&gt;SSH协议的设计初衷是为rsh、rlogin、rcp等命令提供更佳安全的实现方案，需要对一切传输的数据进行加密、检验一致性。公钥系统中对大块的数据加密往往使用运算速度较快的对称密钥，在SSH协议中称作会话密钥。让彼此双方获得会话密钥，并且保证会话密钥不被窃听的过程称作&lt;strong&gt;建立安全会话&lt;/strong&gt;。SSH-1建立安全会话的流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端发起访问远端服务器的请求；&lt;/li&gt;
&lt;li&gt;服务器发回主机密钥、服务器密钥、加密算法、校验序列等信息；&lt;/li&gt;
&lt;li&gt;客户端接到传回信息后，进行&lt;strong&gt;服务器认证&lt;/strong&gt;，如果传回的主机未知，需要人工判断是否信任这些信息，如果不信任，那么会话结束，否则，会话继续；&lt;/li&gt;
&lt;li&gt;服务器认证通过后，客户端随机生成会话密钥，用主机密钥和服务器密钥依次对会话密钥加密后发给服务器；&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;用户输入口令后，经过会话密钥加密传回给服务器；&lt;/li&gt;
&lt;li&gt;服务器验证用户身份。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;口令认证的方式简洁方便，但对每天需要登录很多不同的服务器的人而言，一遍遍重复输入口令比较繁琐，这种情况下采用公钥认证的方式会轻松许多，也更佳安全。公钥认证的方式流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端将用户密钥传给服务器；&lt;/li&gt;
&lt;li&gt;服务器在认证过的密钥库中查找，如果没有找到，那么请求失败，如果找到则生成一段随机序列，称作质询，用用户密钥加密后，发回给客户端；&lt;/li&gt;
&lt;li&gt;客户端拿私有密钥解密得到质询，附带结合上会话的标识（主机密钥、服务器密钥和校验序列通过MD5散列后得到的序列）通过MD5散列后的序列作为响应传回给服务器；&lt;/li&gt;
&lt;li&gt;服务器用同样的方式如果计算出相同的MD5序列，则验证通过。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;客户端在使用私有密钥时，需要私有密钥的口令，这个口令可以为空，这样就相当于免密码，但这种方式让私有密钥的安全性降低，不推荐这种做法。最佳实践是借助SSH代理，可以记住不同私有密钥的口令，当客户端查找私有密钥时，只需要通过SSH代理去完成，这样就避免了重复私有密钥口令。&lt;/p&gt;
&lt;p&gt;以上文章只说明了公钥认证背后的流程，并未涉及为何要这样设计流程，比如为什么需要同时有主机密钥和服务器密钥，为什么需要会话标识，这一系列的设计背后都有原因，能够搞明白这些原因，才能更好理解安全是什么。本文只是粗略的总结，对每次传输的细节有些省略，也没有涉及SSH-1和SSH-2的差别以及一些更加丰富的客户端验证方法，更加深入的内容可以参考相关阅读的第一篇。&lt;/p&gt;
&lt;h3 id="相关阅读"&gt;相关阅读
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="http://docstore.mik.ua/orelly/networking_2ndEd/ssh/ch03_04.htm" target="_blank" rel="noopener"
 &gt;&lt;em&gt;SSH: The Secure Shell The Definitive Guide&lt;/em&gt;, Inside SSH-1&lt;/a&gt; ⭐️⭐️⭐️⭐️⭐️&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://blog.csdn.net/sszgg2006/article/details/8199175" target="_blank" rel="noopener"
 &gt;深入理解加密、解密、数字签名和数字证书&lt;/a&gt;?⭐️⭐️⭐️⭐️⭐️&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>数字签名在安卓包发布中的应用</title><link>https://blog.nicelylit.net/posts/%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E5%9C%A8%E5%AE%89%E5%8D%93%E5%8C%85%E5%8F%91%E5%B8%83%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8/</link><pubDate>Tue, 11 Oct 2016 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E5%9C%A8%E5%AE%89%E5%8D%93%E5%8C%85%E5%8F%91%E5%B8%83%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8/</guid><description>&lt;p&gt;安卓包发布前必须要有正式签名才能在应用商店发布，对照&lt;a class="link" href="https://blog.nicelylit.net/posts/%E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD%E5%9B%BE%E8%A7%A3/" &gt;公开密钥基础设施图解&lt;/a&gt;的常规流程而言，安卓包签名仅是个子集，只有数字签名，不涉及加密。&lt;/p&gt;
&lt;p&gt;数字签名中核心的要素是私有密钥、公钥证书、原始摘要和签名摘要。涉及的参与者有应用开发者、应用商店和用户（操作系统）。逐一了解了每个核心要素和参与者也就清楚了整个签名的动机和操作步骤。&lt;/p&gt;
&lt;p&gt;数字签名是拿&lt;strong&gt;私有密钥&lt;/strong&gt;对原始摘要（不管是文件还是对称密钥本质上都是字符串）进行加密，利用大素数的一些特性对原字符串进行变换，得到签名摘要。这个签名摘要，只有用对应的公开密钥才能恢复为原始摘要。&lt;/p&gt;
&lt;p&gt;公开密钥是&lt;strong&gt;公钥证书&lt;/strong&gt;的主要内容，但不是全部。公钥证书中还写明了应用开发者的基本信息包括开发者组织名称、组织所在地、证书失效时间等信息。公钥证书又叫数字证书和身份证书。身份证书的称谓也反映了这个证书是用来识别应用来源身份的。公钥证书需要被权威机构认证，对于安卓而言，这个组织就是Google Play和国内各大应用商店。各大应用商店需要验证应用开发者每次的公钥证书是否一致，不一致的证书就可能遭到拒绝。除此以外，还需要对签名摘要拿公开密钥解密获得原始摘要，并且同原始摘要进行比对，但凡发现不一致的地方，就可能是文件遭到了篡改，同样会拒绝应用发布。实际上操作系统同样会验证公钥证书的一致性和签名摘要的完整性，仅会提醒，但不拒绝安装有风险的安卓包。是否安装有风险的包就取决于用户自己了。&lt;/p&gt;
&lt;p&gt;前面的描述中提到对比解密后的&lt;strong&gt;签名摘要&lt;/strong&gt;和&lt;strong&gt;原始摘要&lt;/strong&gt;可以发现文件是否遭篡改。很容易产生的疑问是为什么比对摘要就可以发现这点？要回答这个问题，就需要了解摘要是什么。安卓包中的原始摘要是一个字符串的集合，每个字符串对应包中的一个文件，每个字符串都是通过哈希散列算法对文件计算出的一个短序列。这个字符串的集合要求包中的文件一个都不能少，也一个都不能多。这个集合中不包含原始摘要文件自己、签名摘要和公钥证书。签名摘要也是类似于原始摘要的一个字符串集合，只是每个短序列是都用公开密钥签名后的结果，此外还增加了原始摘要文件的短序列和签名序列。&lt;/p&gt;
&lt;p&gt;私有密钥由开发者秘密保存，公钥证书、原始摘要和签名摘要随签名包一起发布。实际上签名包和未签名包的差别就是公钥证书、原始摘要和签名摘要。在签名包中，一般放在META-INF中，后缀一般是CER、MF和SF。&lt;/p&gt;
&lt;h6 id="安卓包签名示意图"&gt;安卓包签名示意图：
&lt;/h6&gt;&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2016/10/apk-signing-process.png" &gt;&lt;img alt="apk-signing-process" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2016/10/apk-signing-process.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h6 id="相关阅读"&gt;相关阅读：
&lt;/h6&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://developer.android.com/studio/publish/app-signing.html" target="_blank" rel="noopener"
 &gt;Sign Your App&lt;/a&gt;?⭐️⭐️⭐️⭐️⭐️&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://blog.csdn.net/sszgg2006/article/details/8199175" target="_blank" rel="noopener"
 &gt;深入理解加密、解密、数字签名和数字证书&lt;/a&gt;?⭐️⭐️⭐️⭐️⭐️&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://askubuntu.com/questions/167095/differentiate-between-apk-and-jar-files" target="_blank" rel="noopener"
 &gt;Differentiate between apk and jar files | the comment by?user229002&lt;/a&gt;?⭐️⭐️⭐️⭐️⭐️&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://blog.csdn.net/asmcvc/article/details/9311827" target="_blank" rel="noopener"
 &gt;android APK签名过程之MANIFEST.MF分析&lt;/a&gt;?⭐️⭐️⭐️&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://security.stackexchange.com/questions/3779/how-can-i-export-my-private-key-from-a-java-keytool-keystore" target="_blank" rel="noopener"
 &gt;How can I export my private key from a Java Keytool keystore?&lt;/a&gt;?⭐️⭐️⭐️&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>公开密钥基础设施图解</title><link>https://blog.nicelylit.net/posts/%E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD%E5%9B%BE%E8%A7%A3/</link><pubDate>Fri, 30 Sep 2016 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%85%AC%E5%BC%80%E5%AF%86%E9%92%A5%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD%E5%9B%BE%E8%A7%A3/</guid><description>&lt;p&gt;很久以来，一直搞不清楚各种加密算法、哈希函数的应用场景，直到读了“&lt;a class="link" href="http://blog.csdn.net/sszgg2006/article/details/8199175" target="_blank" rel="noopener"
 &gt;深入理解加密、解密、数字签名和数字证书&lt;/a&gt;”这篇博文后，一切概念立刻变得明朗起来，于是赶快画了一张图捋清了思路，方便自己和看到这篇文章的朋友回顾。因为原文作者写得足够精彩，逻辑性很强，所以在此只简略叙述图中所画内容。&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2016/09/basics-of-public-key-infrastrature.png" &gt;&lt;img alt="basics-of-public-key-infrastrature" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2016/09/basics-of-public-key-infrastrature.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A要通过网络将文件（Original File）发送给B，为了能让B安全收到，避免传输过程中被截获、窃听或伪造，目前相对安全的做法是图中描述的流程。&lt;/p&gt;
&lt;p&gt;A需要执行的操作：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(1) 用对称密钥（Symmetric Key）通过对称加密算法把文件加密成为密文文件（Encrypted File, or Cypher Text）；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(2) 用B的公开密钥（Public Key）通过非对称加密算法把对称密钥加密成为加密的对称密钥（Encrypted Symmetric Key）；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(3) 通过哈希算法生成文件的短摘要（Short Digest）；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(4) 用自己的私有密钥（Private Key）通过非对称加密算法对短摘要签名，生成签名摘要；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(5) 将自己的公开密钥和个人信息交付CA机构认证获取数字证书（Certificate）。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;最后将密文文件、加密的对称密钥、签名摘要和数字证书通过网络传给B。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;B收到A发来的文件需要执行的操作：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(1) 用自己的私有密钥对A发来的加密的对称密钥解密，获得解密的对称密钥（Decrypted Symmetric Key）；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(2) 用解密的对称密钥解密密文文件，获得解密文件（Decrypted File）；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(3) 用相同的哈希算法，生成解密文件的短摘要作为待测摘要（Tested Short Digest）；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(4) 用A发来的数字证书获取A的公开密钥；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(5) 用A的公开密钥对签名摘要解密得到解密的摘要（Decrypted Short Digest）；
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(6) 对比待测摘要和解密摘要，如果完全一致，说明解密文件就是原始文件，如果不一致，说明解密文件是伪造文件。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>初识log4j</title><link>https://blog.nicelylit.net/posts/%E5%88%9D%E8%AF%86log4j/</link><pubDate>Tue, 09 Aug 2016 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E5%88%9D%E8%AF%86log4j/</guid><description>&lt;p&gt;&lt;a class="link" href="https://logging.apache.org/log4j/1.2/manual.html" target="_blank" rel="noopener"
 &gt;log4j&lt;/a&gt;有三个主要的概念：记录员loggers、录入笔appenders和录入风格layouts。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;记录员&lt;/strong&gt;是分级别、继承式的。每个记录员有三个基本特征：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;名称的继承 除总记录员之外的所有记录员都要有名称。名称往往是全路径类名，例如com.foo.Bar。名称的继承是指记录员之间有继承关系，com.foo是com.foo.Bar的父亲，com是com.foo.Bar的爷爷。每个记录员的名称唯一，并且能够通过getLogger方法直接被获取。每个记录员在第一次被获取时被实例化，总记录员除外，它始终都存在，所有没父亲的记录员都继承自它。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每个记录员都有记录级别，默认系统给出的级别，分六档，从低到高依次是跟踪TRACE、调试DEBUG、记录INFO、警告WARN、错误ERROR、灾难FATAL。使用者被允许自定义新的级别，但不被建议，通常这六种也足够用了。&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;
&lt;p&gt;级别的继承 记录员的级别有两种方式取得，一种是使用者用setLevel直接指定，另一种是从祖先那里继承。直接指定就不必说了，继承也很简单，自己如果没被指定级别，那就继承父亲的级别，如果父亲还没有，那就继续向上，直到有祖先被指定级别。总记录员必须被指定级别，因为没有父亲了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;赢得纪录权利的规则 记录员的每一次记录信息都有级别检查，记录信息的级别由记录方法决定，比如，默认的六个方法trace, debug, info, warn, error, fatal。记录员用比自己级别低的记录方法，不会有信息输出，只有用了高级别（大于等于自身级别）的记录方法才会输出。还有个一般性的记录方法log，可以通过指定级别，随意变身默认的六种方法。所以记录员赢得记录的权利就是调用比自身级别高的记录方法。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;录入笔&lt;/strong&gt; 记录员只有方法没有工具也不知道把信息记到哪儿，录入笔就是这个工具。每个记录员可以有多个记录笔，不同记录笔记录到不同的平台，可以是控制台、文件、界面的某个控件、远程服务器的套接字端口、Java消息服务、Windows NT的日志系统、远程UNIX服务器的Syslog日志系统。&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;a class="link" href="https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html" target="_blank" rel="noopener"
 &gt;录入格式&lt;/a&gt;。&lt;/p&gt;</description></item><item><title>WordPress的插件机制</title><link>https://blog.nicelylit.net/posts/wordpress%E7%9A%84%E6%8F%92%E4%BB%B6%E6%9C%BA%E5%88%B6/</link><pubDate>Sun, 07 Aug 2016 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/wordpress%E7%9A%84%E6%8F%92%E4%BB%B6%E6%9C%BA%E5%88%B6/</guid><description>&lt;p&gt;&lt;a class="link" href="https://codex.wordpress.org/Plugin_API" target="_blank" rel="noopener"
 &gt;WordPress的插件机制&lt;/a&gt;展现了强大扩展能力，它将用户模板定制、插件开发等从核心处理流程中剥离，也让开发者在核心之外有机会修改核心处理流程中的任何一个环节。&lt;/p&gt;
&lt;h4 id="基本概念"&gt;基本概念
&lt;/h4&gt;&lt;p&gt;WordPress插件机制的核心是钩子（Hooks），每一次处理请求都会顺序执行一系列的钩子，每个钩子由唯一的标签（Tag）标识，每个钩子上能够注册数量不限的函数，在钩子执行中，函数按照注册时的优先级先后依次执行，每个钩子可以重复被执行。&lt;/p&gt;
&lt;p&gt;举例来讲，当服务器接到首页的请求时，第一个钩子_muplugins_loaded_会在加载完&lt;a class="link" href="https://www.sitepoint.com/wordpress-mu-plugins/" target="_blank" rel="noopener"
 &gt;自动激活（Must-Use）&lt;/a&gt;插件后被执行，第二个钩子_registered_taxonomy_会在系统初始化默认的分类系统（create_initial_taxonomies）中执行。在响应信息完成前，有&lt;a class="link" href="https://codex.wordpress.org/Plugin_API/Action_Reference" target="_blank" rel="noopener"
 &gt;几十个钩子&lt;/a&gt;被依次执行。&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2016/08/default_actions_sequence.png" &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2016/08/default_actions_sequence.png" &gt;&lt;img alt="default\\_actions\\_sequence" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2016/08/default_actions_sequence-1024x399.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果钩子上没有注册函数，那么执行钩子时并没有实际动作。比如_registered_taxonomy_是在每个分类系统注册结束时被执行，当初始化分类系统时，4.5.3版本默认注册了5个分类系统：category、post_tag、nav_menu、link_category、post_format，每个分类系统注册结束后_registered_taxonomy_都被执行一次，但由于WordPress核心并没有在这个钩子上注册过函数，所以没有实际的动作。&lt;/p&gt;
&lt;p&gt;如果钩子上注册了函数，那么执行钩子时，按照优先级从高到低顺序执行，优先级一样的，先注册的先执行。优先级用非负整数表示，数字越小优先级越高。4.5.3版本在加载主题模板前会执行_template_redirect_来处理若干URL重定向的问题。系统默认注册了6个函数，其中_wp_admin_bar_init优先级为0，wp_old_slug_redirect、redirect_canonical优先级为10，rest_output_link_header、wp_shortlink_header优先级为11，wp_redirect_admin_locations优先级为1000. 假如用户在Must-Use插件中注册了setup_custom_redirect到_muplugins_loaded_，而函数的内容是注册一个用户custom_redirect函数到_template_redirect_，那么系统在执行_muplugins_loaded_时，会执行setup_custom_redirect函数，从而注册custom_redirect到_template_redirect_。注册custom_redirect时，没有指定优先级，默认是10，这样就与wp_old_slug_redirect和redirect_canonical有着相同的优先级。由于执行_muplugins_loaded_是在default-filters.php（系统默认的两个优先级为10的函数都在这里注册）执行之后，所以custom_redirect执行时候也后执行。&lt;/p&gt;
&lt;h4 id="编程接口"&gt;编程接口
&lt;/h4&gt;&lt;p&gt;钩子的类型有两种，过滤钩子（Filter）和动作钩子（Action），所以编程接口（API）也有两组。这两组编程接口定义在wp-includes/plugin.php中，函数签名如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$function_to_add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$accepted_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;add_filter和add_action用于函数注册，apply_filters、apply_filters_ref_array、do_action、do_action_ref_array用于钩子执行，remove_filters、remove_all_filters、remove_action、remove_all_actions用于函数移除。函数注册和函数移除必须在钩子（要注册和要移除的钩子，非其它的钩子）执行前或者执行中才能生效。在钩子执行中注册或者移除仅对低优先级的函数生效。&lt;/p&gt;
&lt;p&gt;动作钩子是一类特殊的过滤钩子，两种钩子的标签和注册函数是共享的，都存储在**$wp_filter**对象中，换句话说用add_action注册的函数，用apply_filters执行在某些情况下也是可以的。事实上，动作钩子的add_action方法直接调用了add_filter方法。&lt;/p&gt;
&lt;p&gt;过滤钩子顾名思义，钩子上注册的函数要起过滤的作用。具体来说，会对某个对象的内容，可能是字符串、整数或者复杂对象做一定的修改后再返回修改后的对象。所以一系列的钩子连在一起，对这个对象形成了层层的过滤，直到钩子执行结束。过滤钩子主要的特点是，前一个函数的处理结果可以传给下一个函数。动作钩子相较而言，无法接收前一个函数的返回值。对比两个函数的参数，也可以发现apply_filters的第二个参数$value是必选参数，这个参数也就是要过滤的对象，这个参数在第二个函数执行时，会传入第一个函数的返回值，而后依此类推。do_action的第二个参数$args不是必选，即使有返回值也不会再向下传递。类似地，apply_filters_ref_array的返回值会由$args[0]继续向后传递。值得注意的是，如果要用apply_filters代替do_action，第二个参数在第二个函数之后就无法正常获取，因此前面提到的某些情况，即是第二个参数无意义的时候。当然最好不要用apply_filters去代替do_action。&lt;/p&gt;
&lt;p&gt;has_filter和has_action用于检查某个钩子是否存在，填写第二个参数可以检查某个钩子上是否注册了某个函数。&lt;/p&gt;
&lt;p&gt;current_filter和current_action用于获取当前执行函数所在的钩子的名称，而doing_filter和doing_action用于获取当前所有在执行的钩子的名称。前后两组的区别是当前执行函数只会有一个（单线程），对应的钩子也只有一个，而在执行的钩子可以有多个，因而钩子A的注册函数中还可能执行钩子B，而执行到B的注册函数是，A并没有执行完，只是暂时压入了栈中。&lt;strong&gt;$wp_current_filter&lt;/strong&gt;对象即是一个堆栈，用于存放执行中的钩子，而堆顶是正在执行函数的的钩子。doing_filter的参数为null时，只检查$wp_current_filter栈中是否为空，不为空，返回真；参数为某个钩子时，去检查钩子是否在栈中，如果在，返回真。&lt;/p&gt;
&lt;p&gt;did_action用于检查某个钩子是否执行过，执行过的钩子都会在**$wp_actions**中登记一次，而且登记顺序是符合执行顺序的。利用$wp_actions可以在最后一个钩子_shutdown_执行时，顺序打印所有执行过的钩子，来获取&lt;a class="link" href="http://wordpress.stackexchange.com/questions/162862/how-to-get-wordpress-hook-run-sequence" target="_blank" rel="noopener"
 &gt;真实的场景中钩子的执行顺序&lt;/a&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;show_hooks_order&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$GLOBALS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;wp_actions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;shutdown&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;show_hooks_order&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;了解有哪些钩子和钩子的作用是什么，除了直接查看源码外，Adam R Brown还汇总了历史上各个WordPress版本中&lt;a class="link" href="http://adambrown.info/p/wp_hooks" target="_blank" rel="noopener"
 &gt;钩子词典&lt;/a&gt;，也可以用作参考。&lt;/p&gt;
&lt;h4 id="结语"&gt;结语
&lt;/h4&gt;&lt;p&gt;WordPress的钩子机制是一种扩展流程的通用思想，不论是否做网站开发，都应该学习这种机制，从而了解庞大系统为何能变得庞大还脉络清晰、适应性强大的缘由，进一步也会帮助自身在设计系统时有所借鉴。&lt;/p&gt;</description></item><item><title>CSS实现垂直居中</title><link>https://blog.nicelylit.net/posts/css%E5%AE%9E%E7%8E%B0%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD/</link><pubDate>Sun, 01 Nov 2015 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/css%E5%AE%9E%E7%8E%B0%E5%9E%82%E7%9B%B4%E5%B1%85%E4%B8%AD/</guid><description>&lt;p&gt;在常规的网页中，宽一般有最大限制，而高没有，因而CSS规范并没有给出块级元素和多行文字垂直居中，建议性的实现方案。换句话说，但凡有垂直居中的要求，必然是设计者对包含块的高做了固定限制。&lt;/p&gt;
&lt;p&gt;默认的CSS块级元素的高由内容决定，通常内容有多少，高度就会算多少。固定了高意味着设计者必须确认内容的高度不会超过限制，否则会产生不合预期的结果。&lt;/p&gt;
&lt;p&gt;众所周知，限制高仅需要指定height样式属性即可。&lt;/p&gt;
&lt;p&gt;确认内容高度需要了解内容的组成，可能会是一张可替换的图片，也可能会是一段行数不确定的文本，甚至也可能是另外一个块级元素，每种内容有各自的模型在控制着自己的展示。&lt;/p&gt;
&lt;h3 id="情形1-垂直居中一张图片"&gt;情形1: 垂直居中一张图片
&lt;/h3&gt;&lt;p&gt;图片的模型最简单，行内元素，限制高，宽会等比例缩放。假设要在高度为200px的块中放置一个高度不超过100px的图片，垂直居中可以这样写：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;middleWrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;middleWrapper&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;max-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;middleWrapper&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/wp-content/uploads/2015/01/violin.jpg&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="height: 200px; line-height: 200px; border-color: black; border-width: 1px; border-style: solid"&gt;
 &lt;img src="https://blog.nicelylit.net/wp-content/uploads/2015/01/violin.jpg" style="vertical-align: middle; max-height: 100px"&gt;
&lt;/div&gt;
&lt;p&gt;上面的代码中重要之处在于line-height恰好等于height，这样整个包含块就变成了一行，在单行内居中行内元素，只需要指定vertical-align是middle即可。&lt;/p&gt;
&lt;p&gt;垂直居中一张图片的应用场景很多，比如一个带有标志图片的按钮，再比如购物网站商品页上商品的图片展示（固定的方框内放各种不同尺寸的图片）等。&lt;/p&gt;
&lt;h3 id="情形2-垂直居中一行"&gt;情形2: 垂直居中一行
&lt;/h3&gt;&lt;p&gt;这种情况与情形1类似，不同之处在于文字如果一行放不下会换行，当换出新的一行，文字便超出了包含块，这时通常的处理手法是将超出部分隐藏（overflow: hidden）。更加普遍的做法是设置样式属性white-space为nowrap不允许文字换行，并且将超出部分换用三个点代替（text-overflow: ellipsis）。下面的例子中，将内容超过一行的文本，垂直居中地放在高200px的包含块中，并且只显示一行。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;middleWrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;text-overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;ellipsis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;middleWrapper&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;middleWrapper&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;It is a truth universally acknowledged that a single man in possession of a good fortune must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered as the rightful property of some one or other of their daughters.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="height: 200px;
 line-height: 200px;
 text-overflow: ellipsis;
 overflow: hidden;
 border-color: black;
 border-width: 1px;
 border-style: solid"&gt;
 &lt;span style="vertical-align: middle; white-space: nowrap"&gt;It is a truth universally acknowledged that a single man in possession of a good fortune must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered as the rightful property of some one or other of their daughters.&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;注意white-space属性可以放在包含块中或是直接放在文本标签中均可，因为这个属性允许继承，并且只能作用在文本上。两个overflow属性却要放在包含块中，用户代理需要根据包含块的高度进行计算隐藏掉那一部分文字。值得一提的是text-overflow属性是CSS3中加入的新内容，主流浏览器都支持，但也有些例外，比如UC浏览器的Android手机版。&lt;/p&gt;
&lt;p&gt;垂直居中一行文字的应用场景包括按钮上的文字，水平导航栏中的文字等等。&lt;/p&gt;
&lt;h3 id="情形3-垂直居中几行文字"&gt;情形3: 垂直居中几行文字
&lt;/h3&gt;&lt;p&gt;如果要垂直居中的文字行数是确定的，并且包含块框的高度恰好等于总行框的高度，那么只需要将包含块的高度设置为总行框高度即可。例如有两行，字体13px，每行行高17px，那么包含块的高度为34px。&lt;/p&gt;
&lt;p&gt;这种情形最常见的应用场景是移动购物网站，浏览页商品描述的格式。这种场景下往往也要求能够隐藏多出来的行，以及将多出的部分替换为三个点。这种做法，纯粹的CSS无法胜任，只能计算出行宽，估算出展示的字符数用JS脚本控制。说估算是由于英文字体多数不是等宽，如果无法知道每个字母的宽度，难以精确地算出要展示的字符数目。&lt;/p&gt;
&lt;h3 id="情形4-垂直居中一段文字"&gt;情形4: 垂直居中一段文字
&lt;/h3&gt;&lt;p&gt;当一段文字行数不确定，且包含块的高度一定大于总行框高度时，需要将整段文字放在一个不限制高的行内块级元素中，像下面的写法一样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;middleWrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;middleWrapper&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;justify&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;middleWrapper&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;It is a truth universally acknowledged that a single man in possession of a good fortune must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered as the rightful property of some one or other of their daughters.&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="height: 200px;
 line-height: 200px;
 border-color: black;
 border-width: 1px;
 border-style: solid"&gt;
 &lt;div style="vertical-align: middle;
 display: inline-block;
 line-height: 1.5;
 text-align: justify"&gt;It is a truth universally acknowledged that a single man in possession of a good fortune must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered as the rightful property of some one or other of their daughters.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;不同于单行元素的居中，多行元素的line-height需要重置成较小的值，上面例子中取了文字的1.5倍，远小于父级元素的200px。将块级元素变成行内块级元素也是关键。如果仍旧只是块级元素，即使充值了line-height也不会生效，因为块级元素的一行会去和父级包含块的一行去居中对齐。&lt;/p&gt;
&lt;p&gt;这种情况的应用场景极少遇到，恐怕只有按照列为主题布局时，才可能需要。&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-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;middleWrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;table-cell&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;border-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;middleWrapper&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It is a truth universally acknowledged that a single man in possession of a good fortune must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered as the rightful property of some one or other of their daughters.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="height: 200px;
 line-height: 1.5;
 vertical-align: middle;
 display: table-cell;
 border-color: black;
 border-width: 1px;
 border-style: solid"&gt;
It is a truth universally acknowledged that a single man in possession of a good fortune must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered as the rightful property of some one or other of their daughters.
&lt;/div&gt;
&lt;p&gt;此外，还有一些方法也可行，但显得不够优雅，比如设定上下的padding为某个固定值可以应对某些情形等。&lt;/p&gt;</description></item><item><title>Git分支</title><link>https://blog.nicelylit.net/posts/git%E5%88%86%E6%94%AF/</link><pubDate>Wed, 13 May 2015 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/git%E5%88%86%E6%94%AF/</guid><description>&lt;p&gt;&lt;strong&gt;几乎所有的VCS都支持分支的机制，到底什么是分支，有什么作用？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分支是一份完整的项目代码，一个项目可以有很多分支。多数VCS工具的分支机制很昂贵，需要完整的拷贝项目代码，对于代码量大的目录往往需要很长的时间。Git用快照加校验码的方式，让分支模型变得异常轻便，创建和切换分支都很快。Git的每个分支都是一些校验码构成的树或者图，要想理解Git的分支模型，请仔细阅读&lt;a class="link" href="http://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell" target="_blank" rel="noopener"
 &gt;Branches in a Nutshell&lt;/a&gt;或者中文版&lt;a class="link" href="http://git.oschina.net/progit/3-Git-%E5%88%86%E6%94%AF.html#3.1-%E4%BD%95%E8%B0%93%E5%88%86%E6%94%AF" target="_blank" rel="noopener"
 &gt;何谓分支&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何创建分支呢？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git branch testing
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种方式是在当前分支基础上创建了testing分支。首先应该明白，如果项目有多个分支，当前只有一个分支位于工作区域。一般一份代码至少有一个主干分支，叫做master。如果想指定从某个分支基础上创建分支只需要在后面再加上分支名字即可，例如&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git branch testing master
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;就从master新建分支，不管当前处于哪个分支下。这个命令的执行不会将新建的分支testing切换到工作区域。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何切换分支呢？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git checkout testing
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或者可以在创建分支的时候执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git checkout -b testing
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;表示创建新的分支testing并且切换过去。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;是否在分支中新加的代码不会加在主干中？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;是这样的。如果是在分支中做提交，那么只会影响分支，不会影响主干和其他的分支，可以保持主干的稳定性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分支开发完后要怎么将代码加回主干？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git checkout?master
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git merge testing
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意切换到master时需要保证所有记录的修改都应该已经提交，否则切换会失败。加回主干一般称作合并，合并的过程是在被提交的分支上生成一个新的提交，将两个分支的代码差异合并起来的操作。当同一个文件在两个分支上都有修改时就会产生冲突，需要手动解决。详细的冲突解决的示例可以参考&lt;a class="link" href="http://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging" target="_blank" rel="noopener"
 &gt;Basic Merge Conflicts&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;应该如何查看当前有哪些分支呢？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git branch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;会显示所有分支的名字，更详细的信息可以加-v选项。还有两个选项也常用，&amp;ndash;merged显示已合并到当前分支上的那些分支，&amp;ndash;nomerged显示还未合并到当前分支上的那些分支。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分支合并后该怎么删除呢？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git branch -d &amp;lt;branch&lt;span class="se"&gt;\_&lt;/span&gt;name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;之前提到的是否都是本地仓库的操作，本地的提交怎么会影响到远端呢，也需要合并吗？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对的，之前说的都是本地的操作，提交到远端也需要合并，但命令不太一样，原则上有些差别。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么原则上的差别？&lt;/strong&gt;&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而能直接执行这个操作，需要当前分支跟踪着远端的某个分支。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git branch -vv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以查询本地跟踪远程的那些分支情况。不管用推送去合并哪个分支都需要本地的分支跟踪远程的某个分支。如果是要推送一个远程不存在的分支，那么直接会在远程新建一个对应名字的分支，并被本地分支跟踪，命令是&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git push &amp;lt;remote&lt;span class="se"&gt;\_&lt;/span&gt;name&amp;gt; &amp;lt;branch&lt;span class="se"&gt;\_&lt;/span&gt;name&amp;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;&lt;strong&gt;远端的分支在本地名字好像都是类似origin/master的格式？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;是的，origin是远端的名字，master是远端分支的名字。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;前面好像有提过，从远程拉取代码使用fetch或者pull的命令，差别在哪里？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;会拉取所有在远端名为origin下的所有分支到本地，并且名字都叫origin/&amp;lt;branch_name&amp;gt;。这些拉取的分支是不能够直接切换和修改的，需要对它们用本地分支进行跟踪。如果本地已经有对应的跟踪分支，切换过去，直接进行合并，即执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git checkout tracking_branch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git merge origin/tracking_branch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;命令pull将fetch操作和merge操作一并执行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果本地没有跟踪分支，那么怎么新建一个呢？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git checkout -b branch_name remote_name/branch_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;这个命令看起来和本地新建一个分支好像没有差别？&lt;/strong&gt;&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git checkout --track remote_name/branch_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果要改变本地分支追踪的远程分支，可以执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git checkout -u remote_name/branch_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;假如错误地推送给远程一个分支，怎么能够删除？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git push remote_name --delete branch_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;命令和选项都太多了，平常应该怎么用这些命令来做开发？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通常情况下，远端只会有一个分支，姑且叫做远端的主干分支。远端主干分支上的代码都是稳定的，经过测试，可以直接编译、执行、部署。本地开发时，需要有一个本地主干来跟踪远程的主干，因而这个主干通常不做开发，也一般都是稳定的。如果代码中有bug，那么从本地主干上切换出一个新的分支来做修改，修改完后再合并到主干，主干测试无误，推送到远端主干进行测试。如果代码要开发新的功能，也从主干上切换出一个新的分支来添加代码，添加完后，测试无误合并到主干，然后推送到主干进行测试。所以稳定的主干一般只有一个，不管是开发新功能还是修改漏洞都要在新分支上完成。&lt;/p&gt;
&lt;p&gt;需要注意的一点是多人使用远端分支时候，主干推送可能会出错。这种情况发生在本地主干与远程的主干不同步，合作开发者已经将更新的提交推送到了远端，这时候需要本地主干同步后，再做本地的合并，然后再推送。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;意思是说每次合并主干前，都执行一次&lt;code&gt;$ git pull&lt;/code&gt;吗？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;是的，最好这样。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;之前有提到过&lt;code&gt;$ git pull -r&lt;/code&gt;的操作，与上面有什么差别？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个命令会以rebase的方式合并，而不是merge。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么是rebase的方式？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;假设在本地功能分支合并到主干分支的时候，忘记去同步远端主干到本地，这个时候直接执行$ git pull的话，可能会产生合并冲突，如果远端和本地的最新提交都修改了同一个文件的话。这种情况下，不仅需要手动合并，而且向远端提交分支的时候还后产生一个合并分支的分叉，提交历史看起来不太整齐。如果用rebase合并，Git会将本地合并的提交接在远端提交的后面，等价于先同步远端主干到本地，再去合并。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;rebase可以单独使用吗？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以的。rebase也能够用在本地功能分支向主干分支合并的时候。本地的主干可能有多个分支同时进行开发，也有可能同步了远程的主干，继续向前，所有头部的提交已经不再是当前功能分支切换时的情况了，这个时候rebase就很有用，直接在当前的功能分支下执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git rebase master
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;能够让当前功能分支的新提交追加在主干新提交之后。另外值得提一句rebase操作的-i操作还能够在合并的时候修改提交历史，这个功能其实很好用，但也引起了很多的争议。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么样的争议？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;反对者的声音是提交历史记录了原本发生的历史，而历史不应该被改变，使用rebase等于在篡改历史，你相当于是在对过去说谎。支持者的声音是提交历史里应该记录的是项目如何被完成这个完整的故事，哪些保留，哪些删除，应该有所取舍，没有人会将一本书的草稿出版。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;听起来挺有趣，那争议的结果怎样呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其实就是我们现在看到的，两种方式都保留在Git中。有一点建议是_只用rebase修改自己本地仓库的提交，千万不要修改本地之外的提交_（Do not rebase commits that exist outside your repository）。&lt;/p&gt;
&lt;p&gt;这段文字改写自《Pro Git》一书的第二版第三章Branching部分。这里的改写仅用作回顾，不推荐直接学习，原书中有很多图示的说明更加直观。第二章请参考&lt;a class="link" href="http://mjm1990.com/?p=133" target="_blank" rel="noopener"
 &gt;Git基础&lt;/a&gt;。&lt;/p&gt;</description></item><item><title>Git基础</title><link>https://blog.nicelylit.net/posts/git%E5%9F%BA%E7%A1%80/</link><pubDate>Fri, 08 May 2015 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/git%E5%9F%BA%E7%A1%80/</guid><description>&lt;p&gt;&lt;strong&gt;如何开始用Git呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以通过命令行和界面使用Git，但推荐命令行。掌握了命令行，界面也就基本掌握了，反之就不行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何创建一个Git仓库呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有两种方式：&lt;/p&gt;
&lt;p&gt;第一种针对本地的某个项目，还未被Git记录过，那么cd到对应项目的根目录下，执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git add *.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git add LICENSE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git commit -m &lt;span class="s1"&gt;&amp;#39;initial project version&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;后面三句先不解释含义，后面会逐步说明。只说明第一句&lt;code&gt;git init&lt;/code&gt;，表示在当前目录下，生成&lt;code&gt;.git&lt;/code&gt;仓库来记录该目录下的项目。&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git clone https://github.com/libgit2/libgit2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这句命令会在当前目录新建一个名为libgit2的目录，并在其中放置远端同步过来的项目。如果想让目录叫别的名字，执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git clone https://github.com/libgit2/libgit2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;会让目录叫mylibgit。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何确定成功初始化了一个Git仓库呢？&lt;/strong&gt;&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果返回On branch master之类的信息，则表明初始化成功。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何开始用Git记录文件？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Git能够识别目录中的文件是已记录（tracked）还是未记录（untracked）。开始记录某个文件，需执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git add &amp;lt;filename&amp;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;&lt;strong&gt;如何跟进已修改的文件？&lt;/strong&gt;&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git add &amp;lt;filename&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;如何查看已记录文件的修改后未跟进与已跟进之间的差异？&lt;/strong&gt;&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git diff
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;如何查看已记录文件已跟进部分与前一次提交文件之间的差异？&lt;/strong&gt;&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git diff --staged
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或者执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git diff --cached
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;当执行&lt;code&gt;git status&lt;/code&gt;时候，Git总把一些不相关的文件列在未记录的文件中，怎么样能够不看到这些文件，这些文件都不会被记录？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Git提供了配置忽略文件（Ignoring Files）的方法，即配置.gitignore文件。该文件的每一行为一项，#开头的行会被当作注释。一行一般被用来配置要屏蔽的某一类文件，语法上支持通配符。&lt;a class="link" href="https://github.com/github/gitignore" target="_blank" rel="noopener"
 &gt;https://github.com/github/gitignore&lt;/a&gt;中有很多很好的例子可以做参考。配置好的.gitignore文件放在项目根目录下即可。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何提交修改？&lt;/strong&gt;&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git commit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;会弹出编辑器让编辑提交的信息。或者直接用-m，即&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git commit -m &lt;span class="s1"&gt;&amp;#39;this is the message&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;需要注意的是提交的修改都必须是已跟进的内容，如果想跳过跟进的步骤，直接加入-a选项，即&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git commit -a -m &lt;span class="s1"&gt;&amp;#39;this is the message&amp;#39;&lt;/span&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;至此git init部分的后三句命令应该都清楚含义了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何删除一个文件呢？&lt;/strong&gt;&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ rm &amp;lt;filename&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种方式仅会从磁盘上删除掉文件，但删除的操作并未被提交，还需要手动跟进提交一次。更容易的方法是执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git rm &amp;lt;filename&amp;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;&lt;strong&gt;如果只是想不跟进这个文件，并不像从磁盘上删除该怎么办？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git rm --cached &amp;lt;filename&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;怎么对跟进的文件重命名呢？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git mv &amp;lt;before_name&amp;gt; &amp;lt;after_name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;等价于执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ mv &amp;lt;before_name&amp;gt; &amp;lt;after_name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git rm &amp;lt;before_name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git add &amp;lt;after_name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;提交的流程已经清楚，但要想查看提交历史怎么办？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git log
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;?执行这条命令仅能够看到每次提交的校验码、作者、时间、修改的信息，怎么能够看到修改的概况？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git log --stat
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;这条命令确实可以看到每次提交删除和新增的概况，不过要看具体的差异怎么办？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git log -p
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;要是只想看最近两次的提交呢？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git log -2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;提交历史虽然是按照时间的倒序排列的，但似乎无法知道各个提交之间的继承关系？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git log --graph
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;实际上&lt;code&gt;git log&lt;/code&gt;还有查找特定字段&lt;code&gt;--grep&lt;/code&gt;，自定义输出格式&lt;code&gt;--pretty&lt;/code&gt;，显示单行&lt;code&gt;--oneline&lt;/code&gt;等等一些更加复杂的命令，具体情况可参考&lt;a class="link" href="http://git-scm.com/docs" target="_blank" rel="noopener"
 &gt;Git的帮助手册&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;有时候查看提交历史后才想起来，有些文件忘记提交了，想覆盖最后一次提交的历史怎么办？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git commit --amend
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行这句命令后，可以重新提交并覆盖最后一次的提交历史，实际上等了解了rebase后可以随意修改自己提交的历史。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;有时候提交时错误跟进了一些文件，怎么样能够取消跟进？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git reset HEAD &amp;lt;file&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;有时候想直接放弃某个文件的修改怎么办？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git checkout -- &amp;lt;file&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;到目前似乎都只是在讲本地的一些操作，怎么样能够知道远端仓库的信息？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git remote
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以显示有哪些远端的分支&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git remote -v
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以显示分支的具体URL，关于分支的概念，在Git分支中会有更详细的说明。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果有多个远端仓库，怎么添加？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git remote add &amp;lt;branch_name&amp;gt; &amp;lt;url&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;如何获取添加的远端分支的代码？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git fetch &amp;lt;branch_name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个操作，仅仅将远端代码同步到本地，不能编辑和修改，也不会自动并入到本地的任何一个分支。如果有某个本地分支在跟踪这个远程分支，直接切换到相应分支，执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git pull
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或者&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git pull -r
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;-r表示以rebase的方式并入本地分支，主要是将远端的提交记录与本地的提交做次序上的先后调整。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何将本地的代码推送到远端分支？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git push &amp;lt;remote_branch&amp;gt; &amp;lt;local_branch&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或者当前的分支跟踪了远程的分支，直接执行&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;前面显示远端分支时候最多就是名字和URL，怎么显示更加详细的信息？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git remote show &amp;lt;remote_branch&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;怎么对远端分支重命名？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git remote rename &amp;lt;old_name&amp;gt; &amp;lt;new_name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;怎么删除一个远端分支？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git remote rm &amp;lt;remote_branch&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;打标签是记录软件里程碑的重要功能，Git是怎么支持这个特性的？&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git tag
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以查看已经打过的标签。标签分含附注的标签和轻量级的标签，打含附注标签的命令是&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git tag -a &amp;lt;tag_version&amp;gt; -m &amp;lt;tag_message&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;打轻量级的标签的命令是&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git tag &amp;lt;tag_version&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;查看某个标签的具体情况的命令是&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git show &amp;lt;tag_version&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;以上打标签的方法都是针对最新一次提交，对历史的某次提交打标签，只需要指定上检验码就好了。另外这些标签在本地打完，并不会被push命令推送到远端，除非显式的指出&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git push &amp;lt;remote_brach&amp;gt; &amp;lt;tag_version&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Git中有些命令似乎很长，但又用得很频繁，有没有办法可以简化？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Git提供了异名机制，可以自定义命令，例如&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git config --global alias.co checkout
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git config --global alias.br branch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git config --global alias.ci commit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git config --global alias.st status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git config --global alias.unstage &lt;span class="s1"&gt;&amp;#39;reset HEAD --&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git config --global alias.last &lt;span class="s1"&gt;&amp;#39;log -1 HEAD&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段文字改写自《Pro Git》一书的第二版第二章Basics部分。这里的改写仅用作回顾，不推荐直接学习，原书中有很多图示的说明更加直观。第一章请参考&lt;a class="link" href="http://mjm1990.com/?p=112" target="_blank" rel="noopener"
 &gt;Git初识&lt;/a&gt;。&lt;/p&gt;</description></item><item><title>Git初识</title><link>https://blog.nicelylit.net/posts/git%E5%88%9D%E8%AF%86/</link><pubDate>Wed, 06 May 2015 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/git%E5%88%9D%E8%AF%86/</guid><description>&lt;h5 id="为什么要进行版本控制"&gt;为什么要进行版本控制？
&lt;/h5&gt;&lt;p&gt;我们需要记录对一些文档的编辑历史，特别是当多人合作编辑同一个文档时。有了这些编辑历史的记录，不仅能够方便查看更改，同时也能够回滚到过去的某个版本。&lt;/p&gt;
&lt;h5 id="版本控制系统有哪些应用"&gt;版本控制系统有哪些应用？
&lt;/h5&gt;&lt;p&gt;版本控制多用在代码管理中，但也有其他的应用，比如Google Docs、Wikipedia History等。&lt;/p&gt;
&lt;h5 id="版本控制系统经历了怎样的演进"&gt;版本控制系统经历了怎样的演进？
&lt;/h5&gt;&lt;p&gt;简单说是从本地化版本控制系统（Local Version Control System）到中心化版本控制系统（Centralized Version Control System），再到分布式版本控制系统（Distributed Version Control System）。&lt;/p&gt;
&lt;p&gt;最初的版本控制用于本地的文档管理，通过一个数据库来记录修改前后的差异，被称作本地化版本控制系统（Local Version Control System, LVCS）。这种系统无法支持多人间的协作，进而产生了中心化版本控制系统（Centralized Version Control System, CVCS），将版本管理的数据库移到服务器端。CVCS中，客户端程序都只能够与服务器端进行同步，一旦服务器出现故障，所有的客户端程序都无法使用，服务器端的存储结构和历史记录也都可能丢失。分布式版本控制系统（Distributed Version Control System, DVCS），通过在每个客户端都克隆一个完整的服务器存储结构，从而解决了前面的问题。&lt;/p&gt;
&lt;h5 id="每种不同的版本控制系统有哪些代表"&gt;每种不同的版本控制系统有哪些代表？
&lt;/h5&gt;&lt;ul&gt;
&lt;li&gt;LVCS：RCS&lt;/li&gt;
&lt;li&gt;CVCS：CSV, Subversion, Perforce&lt;/li&gt;
&lt;li&gt;DVCS：Git, Mercurial, Bazaar or Darcs&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="分布式的版本控制系统是否等于本地化的版本控制系统加上中心化的版本控制系统"&gt;分布式的版本控制系统是否等于本地化的版本控制系统加上中心化的版本控制系统？
&lt;/h5&gt;&lt;p&gt;功能上可以这样讲，但实现逻辑有着很大的不同。（&lt;strong&gt;怎样的实现逻辑呢？&lt;/strong&gt;）不管是本地化还是中心化的VCS，主要记录的是修改文件之间的差异（又称补丁，differences or patches）。若要获得某个版本，就等于将很多的差异文件相加。分布式的VCS，记录的是每次提交的完整文件，常被称作快照（snapshots）。（&lt;strong&gt;这两种方式各自有哪些优缺点呢？&lt;/strong&gt;）记录差异的优点在于磁盘空间消耗小，但获得不同的版本需要较多的运算。记录快照的优点在于获得不同的版本与对比版本差异计算都很快，缺点在于磁盘空间消耗大。由于磁盘空间现如今都不会是瓶颈，所以这个缺点基本可以忽略。分布式的版本控制系统带来的好处更多，所以越来越流行，特别是Git。&lt;/p&gt;
&lt;h5 id="git是怎样记录文件的"&gt;Git是怎样记录文件的？
&lt;/h5&gt;&lt;p&gt;Git用SHA-1算法产生的40位长的校验码（checksum）记录所有的文件。具体讲是对每个被提交过的文件执行SHA-1算法，生成校验码作为该文件的名称或者身份，Git主要管理这些校验码。实际上不仅每个文件被执行SHA-1算法，每次的提交（commit）操作也会生成一个校验码，所以只要是提交过的内容，都有办法进行恢复。&lt;/p&gt;
&lt;h5 id="git的基本结构是怎样的"&gt;Git的基本结构是怎样的？
&lt;/h5&gt;&lt;p&gt;Git管理的文件会有三种状态：已提交（committed）、已修改（modified）、已跟进（staged）。已提交是指文件已经被保存在了本地的仓库中。已修改是指已提交过的文件有了新的修改但还没有提交。已跟进是准备提交前的一种状态。对应于三种状态，Git内部被分为三个区域，本地工作区（Working Directory）、跟进区又称缓存区（Staging Area）、本地仓库（Git directory）。已提交的文件记录在了本地仓库，已修改的文件仅位于本地工作区，而已跟进的文件位于跟进区。本地工作区是我们工作交互的区域，编辑修改的文件都位于这个区域，而提交前需要先做一步跟进，放在跟进区才能够提交。&lt;/p&gt;
&lt;h5 id="怎么样获取git"&gt;怎么样获取Git？
&lt;/h5&gt;&lt;p&gt;&lt;a class="link" href="http://git-scm.com/download/linux" target="_blank" rel="noopener"
 &gt;Linux&lt;/a&gt;: sudo yum install git （Redhat系列） sudo apt-get install git （Debian系列） &lt;a class="link" href="http://git-scm.com/download/mac" target="_blank" rel="noopener"
 &gt;Mac&lt;/a&gt;和&lt;a class="link" href="http://git-scm.com/download/windows" target="_blank" rel="noopener"
 &gt;Windows&lt;/a&gt;: 直接从Git网站下载安装包或者在GitHub网站下载安装包&lt;/p&gt;
&lt;h5 id="初次使用git需要做哪些配置呢"&gt;初次使用Git需要做哪些配置呢？
&lt;/h5&gt;&lt;p&gt;Git对每次的提交都会记录提交者的名字和邮箱，所以初次使用需要进行配置。此外，Git的许多命令都会调用编辑器进行交互，所以也需要配置默认的编辑器。&lt;/p&gt;
&lt;h5 id="怎么做这些配置呢"&gt;怎么做这些配置呢？
&lt;/h5&gt;&lt;p&gt;配置前需要简单明确Git对用户的配置信息有三个层次的管理：&lt;code&gt;/etc/gitconfig&lt;/code&gt;、&lt;code&gt;~/.gitconfig&lt;/code&gt;、&lt;code&gt;.git/config&lt;/code&gt;。三个层次后一个层次覆盖前一个，比如在&lt;code&gt;~/.gitconfig&lt;/code&gt;和&lt;code&gt;/etc/gitconfig&lt;/code&gt;配置了同一个属性，&lt;code&gt;~/.gitconfig&lt;/code&gt;会生效。&lt;code&gt;/etc/gitconfig&lt;/code&gt;的修改会影响所有系统用户，&lt;code&gt;~/.gitconfig&lt;/code&gt;只影响当前用户，&lt;code&gt;.git/config&lt;/code&gt;只影响当前的项目包。具体可以直接编辑这三个文件，或者通过命令行完成。举例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git config --global user.name &lt;span class="s2"&gt;&amp;#34;Jon Doe&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置文件中都是以键值对存储配置，例如&lt;code&gt;user.name=&amp;quot;Jon Doe&amp;quot;&lt;/code&gt;。&lt;code&gt;--global&lt;/code&gt;表示设置在&lt;code&gt;~/.gitconfig&lt;/code&gt;中，&lt;code&gt;--system&lt;/code&gt;表示设置在&lt;code&gt;/etc/gitconfig&lt;/code&gt;中。&lt;/p&gt;
&lt;h5 id="如何确定当前项目包的配置情况呢"&gt;如何确定当前项目包的配置情况呢？
&lt;/h5&gt;&lt;p&gt;执行命令&lt;code&gt;git config --list&lt;/code&gt;。&lt;/p&gt;
&lt;h5 id="git中怎么获得命令的帮助"&gt;Git中怎么获得命令的帮助？
&lt;/h5&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ git &lt;span class="nb"&gt;help&lt;/span&gt; &amp;lt;verb&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段文字改写自《Pro Git》一书的第二版第一章Introduction部分。阅读过程中，发现写得非常流畅，是本值得力荐的好书。这里的改写仅用作回顾，不推荐直接学习，原书中有很多图示的说明更加直观。&lt;/p&gt;</description></item><item><title>让博客支持数学公式</title><link>https://blog.nicelylit.net/posts/%E8%AE%A9%E5%8D%9A%E5%AE%A2%E6%94%AF%E6%8C%81%E6%95%B0%E5%AD%A6%E5%85%AC%E5%BC%8F/</link><pubDate>Thu, 05 Feb 2015 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/%E8%AE%A9%E5%8D%9A%E5%AE%A2%E6%94%AF%E6%8C%81%E6%95%B0%E5%AD%A6%E5%85%AC%E5%BC%8F/</guid><description>&lt;p&gt;之前的解决方案通过JavaScript脚本将公式转换为图片，即使用&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2015/02/ASCIIMathTeXImg.js" &gt;ASCIIMathTeXImg.js&lt;/a&gt;，在&lt;code&gt;&amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;&lt;/code&gt;中引用如下的代码。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;lt;?php bloginfo(&amp;#39;template_url&amp;#39;); ?&amp;gt;/js/ASCIIMathTeXImg.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;后面Google时发现了MathJax，提供了更加强大的公式服务。直接引用下面的代码即可支持。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;相比于第一种方式，第二种方式优势至少有两点：其一，加载的JavaScript脚本都是引用别人的链接，不会耗费自己托管服务器的流量；其二，功能更多，支持用户自定义，可以选择LaTex、MathML或者AsciiMath，解析结果也不再是图片，而是HTML、SVG或MathML。&lt;/p&gt;
&lt;p&gt;直接用&lt;a class="link" href="http://www.mathjax.org/" target="_blank" rel="noopener"
 &gt;MathJax&lt;/a&gt;服务唯一的担心是，如果被墙将无法访问，像Google的字体服务一样。实际上，MathJax也支持将脚本&lt;a class="link" href="http://docs.mathjax.org/en/latest/start.html" target="_blank" rel="noopener"
 &gt;安装到自己的博客服务器&lt;/a&gt;上，源代码托管在&lt;a class="link" href="https://github.com/mathjax/MathJax" target="_blank" rel="noopener"
 &gt;GitHub&lt;/a&gt;上，但整个程序脚本较多，占空间，也会耗流量，所以还是推荐在没有被墙的情况下，直接使用服务为好。&lt;/p&gt;
&lt;p&gt;下面这个公式用第二种方式生成。&lt;/p&gt;
&lt;p&gt;$$e^{i\pi}+1=0$$&lt;/p&gt;</description></item><item><title>WordPress站点加载慢的分析</title><link>https://blog.nicelylit.net/posts/wordpress%E7%AB%99%E7%82%B9%E5%8A%A0%E8%BD%BD%E6%85%A2%E7%9A%84%E5%88%86%E6%9E%90/</link><pubDate>Thu, 08 Jan 2015 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/wordpress%E7%AB%99%E7%82%B9%E5%8A%A0%E8%BD%BD%E6%85%A2%E7%9A%84%E5%88%86%E6%9E%90/</guid><description>&lt;p&gt;Chrome浏览器或者火狐浏览器按Ctrl+Shift+I，打开开发者工具，选择Network，可以用来分析加载网页时到底哪些链接拖慢了加载速度。以火狐浏览器为例，可以看到以下的分析结果：&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2015/01/slow_analysis_1.png" &gt;&lt;img alt="slow\\_analysis\\_1" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2015/01/slow_analysis_1-610x255.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;拖慢整个节奏的是来自域名fonts.googleapis.com的请求，无法解析的原因不言自明。找到原因后去相应的文件中修改代码即可解决问题。我站点使用模板的问题出现在style.css中，不同的模板会有不一样的变化，有了开发者工具，可以灵活应对。网上多有记录一般引起站点加载慢除了站点模板中有用google服务外，还有avatar也是一大类，这个可以通过安装Simple Local Avatar插件解决。修改后，加载时间立刻从40s减到了3.84s。&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2015/01/slow_analysis_2.png" &gt;&lt;img alt="slow\\_analysis\\_2" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2015/01/slow_analysis_2-610x255.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;由于模板来自网上，加之自己对前端也不太熟，看这个分析报告，可优化的代码还有很多，以后慢慢改吧。另外，以下的这张图显示Chrome加载速度略快。&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://blog.nicelylit.net/wp-content/uploads/2015/01/slow_analysis_3.png" &gt;&lt;img alt="slow\\_analysis\\_3" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.nicelylit.net/wp-content/uploads/2015/01/slow_analysis_3-610x255.png"&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>CSS基本语法的总结</title><link>https://blog.nicelylit.net/posts/css%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95%E7%9A%84%E6%80%BB%E7%BB%93/</link><pubDate>Fri, 07 Jan 2011 00:00:00 +0000</pubDate><guid>https://blog.nicelylit.net/posts/css%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95%E7%9A%84%E6%80%BB%E7%BB%93/</guid><description>&lt;p&gt;今天系统的学习了一下CSS(Cascading Style Sheets)的语法。之前看书时候感觉很乱但最后总结下来其实也就两个大的方面。现在记录下来以便帮助以后回顾。&lt;/p&gt;
&lt;h3 id="css代码放置的位置"&gt;&lt;strong&gt;CSS代码放置的位置&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1. 内联式样式表&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里是指的每个html标签都有一个基本属性style，直接可以利用style设置该标签的样式。例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;font-family:Times New Roman; color:blue&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;content with decoration&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;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;2. 嵌入式样式表&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;要对同一个页面中某种标签都产生统一的样式，而不必要采用上面的第一种方式，提高了代码的重用性。这种方式需要将代码放置在页面的head标签中，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;按这种方式定义的只要该页面中使用&lt;code&gt;&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&lt;/code&gt;标签，则会使用该样式，除非定义自身的style属性则会覆盖全局的设定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 外部样式表&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了进一步提高代码的复用性，有时候许多的页面会采用同样的样式，这样就有必要将相同的样式部分提取成一个专门的叠层样式文件，后缀名通常为.css。如果要在某个html中使用它，则需要用如下的语句包含进去，并且放置在&lt;code&gt;&amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;&lt;/code&gt;中：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;外部css文件名&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;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;4. 输入样式表&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这种方式是提供css文件中包含另外一个css的语法。当然也能够在html的嵌入式样式定义中使用。使用方法就是包含如下的语句：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;IMPORT&lt;/span&gt; &lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;外部css文件名&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="样式选择器"&gt;&lt;strong&gt;样式选择器&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;样式选择器一般说是六种，实际个人感觉就是四种基本的加上两种不同的复合定义方式。样式的定义本身就是属性的关键字style。后来为了提供全局的加入了style标签。但是无论采用上面提到四种方式的哪一种都只是针对具体某个标签或者全部的某种标签。例如页面中有十个p标签，如果想让其中4个有一组修饰样式，另外6中为另外一种修饰样式，则只能单个的定义每个p的style属性。代码量最少也就是定义一个全局的控制其中6个一致的，剩余的4个分别定义。但引入class样式选择器后上面的问题的解决就方便的多，只需要定义两个。css样式选择器使得样式的定义方式多种多样，这里就不做过多的个人体会了，重点说总结。样式选择器的六种分别是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. html选择器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个是最基本的css修饰的对象，即每个body以内的html标签一般都可以修饰。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. class选择器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每个html标签基本都有一个class属性使得它们即便标签名不同，也能够具有同样的修饰样式。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果针对的是某中标签，则为如下定义方式：&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;tradition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;使用时
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;```html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tradition&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;content with decoration&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;如果要想让不同标签名的标签具有相同的修饰方式，只需以点开始定义样式，如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;tradition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;使用时
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;```html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tradition&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;content with decoration&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tradition&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;index.html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;content with decoration&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;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;3. id选择器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;id选择器也是利用每个标签的另外一个属性，id. 进行定义，类似于class，但需要以进号开头，由于id名的唯一性，通常id用于定义div标签中整个区域块的修饰。定义方式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;top&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;引用时&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;top&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;4. 伪元素选择器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有些标签不同属性代表不同的状态，也可以具有当前状态的修饰，常用的就是链接标签a. 如下面的定义&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;**5. 关联选择器**
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;关联选择器相当于对标签之间做了次且运算，只有按照定义标签之间的组合顺序才能够正确的应用该定义的样式，比如下面的定义：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;```html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;center&lt;/span&gt; &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="nt"&gt;em&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面的组合情况说明只有center标签里面有h1标签，并且h1中套用了em标签的情况下才能正确的应用该样式。如下的使用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;center&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;This is a &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;test&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;center&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意上面例子的显示结果只有test是蓝色的。另外需要注意的是几个标签之间都是空格分隔。&lt;/p&gt;
&lt;p&gt;当然关联也不止局限于html标签，当然也可以是自己定义class标签和id标签。如下的定义：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;tradition&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;top&lt;/span&gt; &lt;span class="nt"&gt;em&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&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;&lt;strong&gt;6. 组合选择器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;组合选择器相当于同一样式应用于不同标签，或者不同类别，不同id的简化定义。各个标签、class名或者id名之间使用逗号，即&amp;quot;,&amp;quot;，隔开。应用方式如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;tradition&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;top&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;em&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Roman&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item></channel></rss>